diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..26378c1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +FROM arm32v7/golang:1.9 AS builder + +ENV GOPATH /go +WORKDIR /go/src + +RUN go get github.com/bhoriuchi/go-bunyan/bunyan +RUN go get github.com/gorilla/mux +RUN go get github.com/gorilla/handlers + +RUN mkdir -p /go/src/github.com/cjimti/iotwifi +COPY . /go/src/github.com/cjimti/iotwifi + +RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o /go/bin/wifi /go/src/github.com/cjimti/iotwifi/main.go + +FROM arm32v6/alpine + +RUN apk update +RUN apk add bridge hostapd wireless-tools wpa_supplicant dnsmasq iw + +RUN mkdir -p /etc/wpa_supplicant/ +COPY ./dev/configs/wpa_supplicant.conf /etc/wpa_supplicant/wpa_supplicant.conf + +WORKDIR / + +COPY --from=builder /go/bin/wifi /wifi +ENTRYPOINT ["/wifi"] + + diff --git a/Makefile b/Makefile index 35d52ba..dd17a0a 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,16 @@ -IMAGE ?= iotwifi +IMAGE ?= cjimti/iotwifi NAME ?= iotwifi +all: build push + dev: dev_build dev_run +build: + docker build -t $(IMAGE) . + +push: + docker push $(IMAGE) + dev_build: docker build -t $(IMAGE) ./dev/ diff --git a/cfg/wificfg.json b/cfg/wificfg.json index 985869b..af1d97a 100644 --- a/cfg/wificfg.json +++ b/cfg/wificfg.json @@ -6,7 +6,7 @@ }, "host_apd_cfg": { "ip": "192.168.27.1", - "ssid": "iot-wifi", + "ssid": "iot-wifi-cfg-3", "wpa_passphrase":"iotwifipass", "channel": "6" }, diff --git a/dev/Dockerfile b/dev/Dockerfile index 002c473..79dce66 100644 --- a/dev/Dockerfile +++ b/dev/Dockerfile @@ -12,5 +12,5 @@ ENV GOPATH /go WORKDIR /go/src RUN go get github.com/bhoriuchi/go-bunyan/bunyan -RUN go get github.com/ThomasRooney/gexpect RUN go get github.com/gorilla/mux +RUN go get github.com/gorilla/handlers diff --git a/iotwifi/commands.go b/iotwifi/commands.go index b54185b..f34ed95 100644 --- a/iotwifi/commands.go +++ b/iotwifi/commands.go @@ -6,29 +6,6 @@ import ( "github.com/bhoriuchi/go-bunyan/bunyan" ) -type SetupCfg struct { - DnsmasqCfg DnsmasqCfg `json:"dnsmasq_cfg"` - HostApdCfg HostApdCfg `json:"host_apd_cfg"` - WpaSupplicantCfg WpaSupplicantCfg `json:"wpa_supplicant_cfg"` -} - -type DnsmasqCfg struct { - Address string `json:"address"` // --address=/#/192.168.27.1", - DhcpRange string `json:"dhcp_range"` // "--dhcp-range=192.168.27.100,192.168.27.150,1h", - VendorClass string `json:"vendor_class"` // "--dhcp-vendorclass=set:device,IoT", -} - -type HostApdCfg struct { - Ssid string `json:"ssid"` // ssid=iotwifi2 - WpaPassphrase string `json:"wpa_passphrase"` // wpa_passphrase=iotwifipass - Channel string `json:"channel"` // channel=6 - Ip string `json:"ip"` // 192.168.27.1 -} - -type WpaSupplicantCfg struct { - CfgFile string `json:"cfg_file"` // /etc/wpa_supplicant/wpa_supplicant.conf -} - // Command for device network commands type Command struct { Log bunyan.Logger @@ -72,11 +49,12 @@ func (c *Command) CheckApInterface() { // StartWpaSupplicant func (c *Command) StartWpaSupplicant() { + args := []string{ "-d", "-Dnl80211", "-iwlan0", - "-c" + c.SetupCfg.WpaSupplicantCfg.CfgFile, + "-c/etc/wpa_supplicant/wpa_supplicant.conf", } cmd := exec.Command("wpa_supplicant", args...) @@ -101,30 +79,3 @@ func (c *Command) StartDnsmasq() { cmd := exec.Command("dnsmasq", args...) go c.Runner.ProcessCmd("dnsmasq", cmd) } - -// StartHostapd -func (c *Command) StartHostapd() { - - c.Runner.Log.Info("Starting hostapd.") - - cmd := exec.Command("hostapd", "-d", "/dev/stdin") - hostapdPipe, _ := cmd.StdinPipe() - c.Runner.ProcessCmd("hostapd", cmd) - - cfg := `interface=uap0 -ssid=` + c.SetupCfg.HostApdCfg.Ssid + ` -hw_mode=g -channel=` + c.SetupCfg.HostApdCfg.Channel + ` -macaddr_acl=0 -auth_algs=1 -ignore_broadcast_ssid=0 -wpa=2 -wpa_passphrase=` + c.SetupCfg.HostApdCfg.WpaPassphrase + ` -wpa_key_mgmt=WPA-PSK -wpa_pairwise=TKIP -rsn_pairwise=CCMP` - - hostapdPipe.Write([]byte(cfg)) - hostapdPipe.Close() - -} diff --git a/iotwifi/iotwifi.go b/iotwifi/iotwifi.go index a43eb1e..cd4af57 100644 --- a/iotwifi/iotwifi.go +++ b/iotwifi/iotwifi.go @@ -12,6 +12,8 @@ import ( "os" "os/exec" "time" + "net/http" + "regexp" "github.com/bhoriuchi/go-bunyan/bunyan" ) @@ -34,21 +36,46 @@ type CmdMessage struct { Stdin *io.WriteCloser } -func loadCfg() (*SetupCfg, error) { - fileData, err := ioutil.ReadFile("./cfg/wificfg.json") - if err != nil { - panic(err) - } +func loadCfg(cfgLocation string) (*SetupCfg, error) { v := &SetupCfg{} + var jsonData []byte + + urlDelimR, _ := regexp.Compile("://") + isUrl := urlDelimR.Match([]byte(cfgLocation)) + + // if not a url + if !isUrl { + fileData, err := ioutil.ReadFile(cfgLocation) + if err != nil { + panic(err) + } + jsonData = fileData + } + + if isUrl { + res, err := http.Get(cfgLocation) + if err != nil { + panic(err) + } + + defer res.Body.Close() + + urlData, err := ioutil.ReadAll(res.Body) + if err != nil { + panic(err) + } - err = json.Unmarshal(fileData, v) + jsonData = urlData + } + + err := json.Unmarshal(jsonData, v) return v, err } // RunWifi starts AP and Station -func RunWifi(log bunyan.Logger, messages chan CmdMessage) { +func RunWifi(log bunyan.Logger, messages chan CmdMessage, cfgLocation string) { log.Info("Loading IoT Wifi...") @@ -59,7 +86,7 @@ func RunWifi(log bunyan.Logger, messages chan CmdMessage) { Commands: make(map[string]*exec.Cmd, 0), } - setupCfg, err := loadCfg() + setupCfg, err := loadCfg(cfgLocation) if err != nil { log.Error("Could not load config: %s", err.Error()) return @@ -77,14 +104,27 @@ func RunWifi(log bunyan.Logger, messages chan CmdMessage) { os.Exit(1) }) - wpacfg := NewWpaCfg(log) - wpacfg.StartAP() + wpacfg := NewWpaCfg(log, cfgLocation) + wpacfg.StartAP() + time.Sleep(10 * time.Second) command.StartWpaSupplicant() + + // Scan + time.Sleep(5 * time.Second) + wpacfg.ScanNetworks() + command.StartDnsmasq() + go func() { + for { + wpacfg.ScanNetworks() + time.Sleep(30 * time.Second) + } + }() + // staticFields for logger staticFields := make(map[string]interface{}) diff --git a/iotwifi/types.go b/iotwifi/types.go new file mode 100644 index 0000000..415960c --- /dev/null +++ b/iotwifi/types.go @@ -0,0 +1,25 @@ +package iotwifi + +type SetupCfg struct { + DnsmasqCfg DnsmasqCfg `json:"dnsmasq_cfg"` + HostApdCfg HostApdCfg `json:"host_apd_cfg"` + WpaSupplicantCfg WpaSupplicantCfg `json:"wpa_supplicant_cfg"` +} + +type DnsmasqCfg struct { + Address string `json:"address"` // --address=/#/192.168.27.1", + DhcpRange string `json:"dhcp_range"` // "--dhcp-range=192.168.27.100,192.168.27.150,1h", + VendorClass string `json:"vendor_class"` // "--dhcp-vendorclass=set:device,IoT", +} + +type HostApdCfg struct { + Ssid string `json:"ssid"` // ssid=iotwifi2 + WpaPassphrase string `json:"wpa_passphrase"` // wpa_passphrase=iotwifipass + Channel string `json:"channel"` // channel=6 + Ip string `json:"ip"` // 192.168.27.1 +} + +type WpaSupplicantCfg struct { + CfgFile string `json:"cfg_file"` // /etc/wpa_supplicant/wpa_supplicant.conf +} + diff --git a/iotwifi/wpacfg.go b/iotwifi/wpacfg.go index 0cc58d7..58662cc 100644 --- a/iotwifi/wpacfg.go +++ b/iotwifi/wpacfg.go @@ -15,6 +15,7 @@ import ( type WpaCfg struct { Log bunyan.Logger WpaCmd []string + WpaCfg *SetupCfg } type WpaNetwork struct { @@ -37,10 +38,17 @@ type WpaConnection struct { Message string `json:"message"` } -func NewWpaCfg(log bunyan.Logger) *WpaCfg { +func NewWpaCfg(log bunyan.Logger, cfgLocation string) *WpaCfg { + + setupCfg, err := loadCfg(cfgLocation) + if err != nil { + log.Error("Could not load config: %s", err.Error()) + panic(err) + } return &WpaCfg{ Log: log, + WpaCfg: setupCfg, } } @@ -49,7 +57,8 @@ func (wpa *WpaCfg) StartAP() { wpa.Log.Info("Starting Hostapd.") command := &Command{ - Log: wpa.Log, + Log: wpa.Log, + SetupCfg: wpa.WpaCfg, } command.RemoveApInterface() @@ -71,25 +80,25 @@ func (wpa *WpaCfg) StartAP() { stdOutScanner := bufio.NewScanner(cmdStdoutReader) go func() { for stdOutScanner.Scan() { - wpa.Log.Info("GOT: %s", stdOutScanner.Text()) + wpa.Log.Info("HOSTAPD GOT: %s", stdOutScanner.Text()) messages <- stdOutScanner.Text() } }() cfg := `interface=uap0 -ssid=iotwifi2 +ssid=` + wpa.WpaCfg.HostApdCfg.Ssid + ` hw_mode=g -channel=6 +channel=` + wpa.WpaCfg.HostApdCfg.Channel + ` macaddr_acl=0 auth_algs=1 ignore_broadcast_ssid=0 wpa=2 -wpa_passphrase=iotwifipass +wpa_passphrase=` + wpa.WpaCfg.HostApdCfg.WpaPassphrase + ` wpa_key_mgmt=WPA-PSK wpa_pairwise=TKIP rsn_pairwise=CCMP` - + wpa.Log.Info("Hostapd CFG: %s", cfg) hostapdPipe.Write([]byte(cfg)) cmd.Start() diff --git a/main.go b/main.go index e4b8fa0..7aca37f 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "github.com/bhoriuchi/go-bunyan/bunyan" "github.com/cjimti/iotwifi/iotwifi" "github.com/gorilla/mux" + "github.com/gorilla/handlers" ) type ApiReturn struct { @@ -37,9 +38,11 @@ func main() { messages := make(chan iotwifi.CmdMessage, 1) - go iotwifi.RunWifi(blog, messages) + cfgUrl := setEnvIfEmpty("IOTWIFI_CFG", "cfg/wificfg.json") + port := setEnvIfEmpty("IOTWIFI_PORT", "8080") - wpacfg := iotwifi.NewWpaCfg(blog) + go iotwifi.RunWifi(blog, messages, cfgUrl) + wpacfg := iotwifi.NewWpaCfg(blog, cfgUrl) apiPayloadReturn := func(w http.ResponseWriter, message string, payload interface{}) { apiReturn := &ApiReturn{ @@ -91,7 +94,6 @@ func main() { status, err := wpacfg.Status() if err != nil { - //http.Error(w, err.Error(), http.StatusInternalServerError) blog.Error(err.Error()) return } @@ -108,7 +110,6 @@ func main() { connection, err := wpacfg.ConnectNetwork(creds) if err != nil { - //http.Error(w, err.Error(), http.StatusInternalServerError) blog.Error(err.Error()) return } @@ -172,18 +173,6 @@ func main() { w.Write(ret) } - // api headers for csx allowance - allowHeaders := func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, Content-Length, X-Requested-With, Accept, Origin") - w.Header().Set("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS") - - next.ServeHTTP(w, r) - }) - } - // common log middleware for api logHandler := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -199,18 +188,43 @@ func main() { // setup router and middleware r := mux.NewRouter() - r.Use(allowHeaders) r.Use(logHandler) + // set app routes r.HandleFunc("/status", statusHandler) - r.HandleFunc("/connect", connectHandler) + r.HandleFunc("/connect", connectHandler).Methods("POST") r.HandleFunc("/scan", scanHandler) r.HandleFunc("/kill", killHandler) http.Handle("/", r) + // CORS + headersOk := handlers.AllowedHeaders([]string{"Content-Type","Authorization","Content-Length","X-Requested-With","Accept","Origin"}) + originsOk := handlers.AllowedOrigins([]string{"*"}) + methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS","DELETE"}) + // serve http - blog.Info("HTTP Listening on 8080") - http.ListenAndServe(":8080", nil) + blog.Info("HTTP Listening on " + port) + http.ListenAndServe(":" + port, handlers.CORS(originsOk,headersOk,methodsOk)(r)) + +} + +// getEnv gets an environment variable or sets a default if +// one does not exist. +func getEnv(key, fallback string) string { + value := os.Getenv(key) + if len(value) == 0 { + return fallback + } + + return value +} + +// setEnvIfEmp