From 66088d5b66505b9be2770a36a9727fe8fabfa409 Mon Sep 17 00:00:00 2001 From: Patrick Date: Tue, 14 Feb 2023 15:14:00 +1300 Subject: [PATCH] feat(hotspot): add access point on timer using dhcp, dnsmasg, hostapd --- api/api.go | 16 +++- cmd/managementd/hostapd.conf | 12 +++ cmd/managementd/hotspot.go | 177 +++++++++++++++++++++++++++++++++++ cmd/managementd/main.go | 64 +++++++++++++ 4 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 cmd/managementd/hostapd.conf create mode 100644 cmd/managementd/hotspot.go diff --git a/api/api.go b/api/api.go index 76e83ad..e5f8bdd 100644 --- a/api/api.go +++ b/api/api.go @@ -325,6 +325,16 @@ func (api *ManagementAPI) ClearConfigSection(w http.ResponseWriter, r *http.Requ } } +func (api *ManagementAPI) GetLocation(w http.ResponseWriter, r *http.Request) { + location := goconfig.Location{} + jsonString, err := json.Marshal(location) + if err != nil { + serverError(&w, err) + return + } + w.Write(jsonString) +} + // SetLocation is for specifically writing to location setting. func (api *ManagementAPI) SetLocation(w http.ResponseWriter, r *http.Request) { log.Println("update location") @@ -496,7 +506,7 @@ func (api *ManagementAPI) StartSaltUpdate(w http.ResponseWriter, r *http.Request } } -//GetSaltUpdateState will get the salt update status +// GetSaltUpdateState will get the salt update status func (api *ManagementAPI) GetSaltUpdateState(w http.ResponseWriter, r *http.Request) { state, err := saltrequester.State() if err != nil { @@ -507,7 +517,7 @@ func (api *ManagementAPI) GetSaltUpdateState(w http.ResponseWriter, r *http.Requ json.NewEncoder(w).Encode(state) } -//GetSaltAutoUpdate will check if salt auto update is enabled +// GetSaltAutoUpdate will check if salt auto update is enabled func (api *ManagementAPI) GetSaltAutoUpdate(w http.ResponseWriter, r *http.Request) { autoUpdate, err := saltrequester.IsAutoUpdateOn() if err != nil { @@ -518,7 +528,7 @@ func (api *ManagementAPI) GetSaltAutoUpdate(w http.ResponseWriter, r *http.Reque json.NewEncoder(w).Encode(map[string]interface{}{"autoUpdate": autoUpdate}) } -//PostSaltAutoUpdate will set if auto update is enabled or not +// PostSaltAutoUpdate will set if auto update is enabled or not func (api *ManagementAPI) PostSaltAutoUpdate(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { parseFormErrorResponse(&w, err) diff --git a/cmd/managementd/hostapd.conf b/cmd/managementd/hostapd.conf new file mode 100644 index 0000000..9c98fa6 --- /dev/null +++ b/cmd/managementd/hostapd.conf @@ -0,0 +1,12 @@ +# Note this file has no ssid as it is set by the script +country_code=GB +interface=wlan0 +hw_mode=g +channel=7 +macaddr_acl=0 +auth_algs=1 +ignore_broadcast_ssid=0 +wpa=2 +wpa_key_mgmt=WPA-PSK +wpa_pairwise=TKIP +rsn_pairwise=CCMP \ No newline at end of file diff --git a/cmd/managementd/hotspot.go b/cmd/managementd/hotspot.go new file mode 100644 index 0000000..be4431c --- /dev/null +++ b/cmd/managementd/hotspot.go @@ -0,0 +1,177 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "strings" +) + +// refactor createAPConfig to remove duplication +func createAPConfig(name string) error { + file_name := "/etc/hostapd/hostapd.conf" + config_lines := []string{ + "country_code=NZ", + "interface=wlan0", + "ssid=" + name, + "hw_mode=g", + "channel=7", + "macaddr_acl=0", + "ignore_broadcast_ssid=0", + "wpa=2", + "wpa_passphrase=feathers", + "wpa_key_mgmt=WPA-PSK", + "wpa_pairwise=TKIP", + "rsn_pairwise=CCMP", + } + return creatConfigFile(file_name, config_lines) +} + +func startAccessPoint(name string) error { + return exec.Command("systemctl", "restart", "hostapd").Start() +} + +func stopAccessPoint() error { + return exec.Command("systemctl", "stop", "hostapd").Start() +} + +const router_ip = "192.168.4.1" + +var dhcp_config_default = []string{ + "hostname", + "clientid", + "persistent", + "option rapid_commit", + "option domain_name_servers, domain_name, domain_search, host_name", + "option classless_static_routes", + "option ntp_servers", + "option interface_mtu", + "require dhcp_server_identifier", + "slaac private", +} + +var dhcp_config_lines = []string{ + "interface wlan0", + "static ip_address=" + router_ip + "/24", + "nohook wpa_supplicant", +} + +func createDHCPConfig() error { + file_path := "/etc/dhcpcd.conf" + + // append to dhcpcd.conf if lines don't already exist + config_lines := append(dhcp_config_default, dhcp_config_lines...) + if err := writeLines(file_path, config_lines); err != nil { + return err + } + return nil +} + +func removeLines(file_path string, removed_lines []string) error { + file, err := os.Open(file_path) + if err != nil { + return err + } + defer file.Close() + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if !contains(removed_lines, line) { + lines = append(lines, line) + } + } + + return writeLines(file_path, lines) +} + +func writeLines(file_path string, lines []string) error { + file, err := os.Create(file_path) + if err != nil { + return err + } + defer file.Close() + + w := bufio.NewWriter(file) + for _, line := range lines { + _, _ = fmt.Fprintln(w, line) + } + return w.Flush() +} + +func contains(lines []string, line string) bool { + for _, l := range lines { + if strings.TrimSpace(l) == strings.TrimSpace(line) { + return true + } + } + return false +} + +func startDHCP() error { + // modify /etc/dhcpcd.conf + if err := createDHCPConfig(); err != nil { + return err + } + + return exec.Command("systemctl", "restart", "dhcpcd").Run() +} + +func restartDHCP() error { + if err := writeLines("/etc/dhcpcd.conf", dhcp_config_default); err != nil { + return err + } + return exec.Command("systemctl", "restart", "dhcpcd").Run() +} + +func checkIsConnectedToNetwork() error { + // check if network is up + if err := exec.Command("iwgetid", "wlan0", "-r").Run(); err != nil { + return err + } + return nil +} + +func createDNSConfig(router_ip string, ip_range string) error { + // DNSMASQ config + file_name := "/etc/dnsmasq.conf" + config_lines := []string{ + "interface=wlan0", + "dhcp-range=" + ip_range + ",12h", + "domain=wlan", + "address=/#/" + router_ip, + } + return creatConfigFile(file_name, config_lines) + +} + +func startDNS() error { + return exec.Command("systemctl", "restart", "dnsmasq").Start() +} + +func stopDNS() error { + return exec.Command("systemctl", "stop", "dnsmasq").Start() +} + +func creatConfigFile(name string, config []string) error { + file, err := os.Create(name) + if err != nil { + return err + } + defer file.Close() + + w := bufio.NewWriter(file) + for _, line := range config { + _, err = fmt.Fprintln(w, line) + if err != nil { + return err + } + } + err = w.Flush() + if err != nil { + return err + } + return nil +} diff --git a/cmd/managementd/main.go b/cmd/managementd/main.go index f85582d..a7d58ca 100644 --- a/cmd/managementd/main.go +++ b/cmd/managementd/main.go @@ -114,6 +114,7 @@ func main() { apiRouter.HandleFunc("/config", apiObj.SetConfig).Methods("POST") apiRouter.HandleFunc("/clear-config-section", apiObj.ClearConfigSection).Methods("POST") apiRouter.HandleFunc("/location", apiObj.SetLocation).Methods("POST") // Set location via a POST request. + apiRouter.HandleFunc("/location", apiObj.GetLocation).Methods("GET") // Set location via a POST request. apiRouter.HandleFunc("/clock", apiObj.GetClock).Methods("GET") apiRouter.HandleFunc("/clock", apiObj.PostClock).Methods("POST") apiRouter.HandleFunc("/version", apiObj.GetVersion).Methods("GET") @@ -133,9 +134,72 @@ func main() { apiRouter.HandleFunc("/service-restart", apiObj.RestartService).Methods("POST") apiRouter.Use(basicAuth) + //Setup Hotspot and stop after 5 minutes using a new goroutine + go func() { + ssid := "bushnet" + router_ip := "192.168.4.1" + log.Printf("Setting DHCP to default...") + if err := restartDHCP(); err != nil { + log.Printf("Error restarting dhcpcd: %s", err) + } + // Wait 1 minute before starting hotspot + time.Sleep(1 * time.Minute) + // Check if already connected to a network + // If not connected to a network, start hotspot + if err := checkIsConnectedToNetwork(); err != nil { + log.Printf("Error checking network: %s", err) + log.Printf("Starting Hotspot...") + log.Printf("Creating Configs...") + if err := createAPConfig(ssid); err != nil { + log.Printf("Error creating hostapd config: %s", err) + } + if err := createDNSConfig(router_ip, "192.168.4.2,192.168.4.20"); err != nil { + log.Printf("Error creating dnsmasq config: %s", err) + } + + log.Printf("Starting DHCP...") + if err := startDHCP(); err != nil { + log.Printf("Error starting dhcpcd: %s", err) + } + log.Printf("Starting DNS...") + if err := startDNS(); err != nil { + log.Printf("Error starting dnsmasq: %s", err) + } + log.Printf("Starting Access Point...") + if err := startAccessPoint(ssid); err != nil { + log.Printf("Error starting hostapd: %s", err) + } + // Wait 5 minutes before stopping hotspot extending timer + // if apiRouter is used + t := time.NewTimer(1 * time.Minute) + apiRouter.Use(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Reset(5 * time.Minute) + next.ServeHTTP(w, r) + }) + }) + + <-t.C + + log.Printf("Stopping Hotspot") + if err := stopAccessPoint(); err != nil { + log.Printf("Error stopping hotspot: %s", err) + } + if err := stopDNS(); err != nil { + log.Printf("Error stopping dnsmasq: %s", err) + } + if err := restartDHCP(); err != nil { + log.Printf("Error restarting dhcp: %s", err) + } + } else { + log.Printf("Already connected to a network") + } + }() + listenAddr := fmt.Sprintf(":%d", config.Port) log.Printf("listening on %s", listenAddr) log.Fatal(http.ListenAndServe(listenAddr, router)) + } func basicAuth(next http.Handler) http.Handler {