From 149fcc0f2dccbe0018122f1efa5b8977cf6ff273 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 22 Nov 2019 14:21:08 +0300 Subject: [PATCH 1/7] * dhcp,clients: DHCP server module is passed to Clients module during initialization. --- dhcpd/dhcpd.go | 12 ++++++++++++ home/clients.go | 18 ++++++++++++++---- home/clients_test.go | 4 ++-- home/config.go | 8 +++++++- home/home.go | 4 +++- 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index 1231063f..74bc45d4 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -88,6 +88,13 @@ func (s *Server) CheckConfig(config ServerConfig) error { return tmpServer.setConfig(config) } +// Create - create object +func Create(config ServerConfig) *Server { + s := Server{} + s.conf = config + return &s +} + // Init checks the configuration and initializes the server func (s *Server) Init(config ServerConfig) error { err := s.setConfig(config) @@ -98,6 +105,11 @@ func (s *Server) Init(config ServerConfig) error { return nil } +// WriteDiskConfig - write configuration +func (s *Server) WriteDiskConfig(c *ServerConfig) { + *c = s.conf +} + func (s *Server) setConfig(config ServerConfig) error { s.conf = config s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename) diff --git a/home/clients.go b/home/clients.go index 2b92c4d2..2a5abb50 100644 --- a/home/clients.go +++ b/home/clients.go @@ -12,6 +12,7 @@ import ( "sync" "time" + "github.com/AdguardTeam/AdGuardHome/dhcpd" "github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/utils" ) @@ -59,17 +60,20 @@ type clientsContainer struct { idIndex map[string]*Client // IP -> client ipHost map[string]*ClientHost // IP -> Hostname lock sync.Mutex + + dhcpServer *dhcpd.Server } // Init initializes clients container // Note: this function must be called only once -func (clients *clientsContainer) Init(objects []clientObject) { +func (clients *clientsContainer) Init(objects []clientObject, dhcpServer *dhcpd.Server) { if clients.list != nil { log.Fatal("clients.list != nil") } clients.list = make(map[string]*Client) clients.idIndex = make(map[string]*Client) clients.ipHost = make(map[string]*ClientHost) + clients.dhcpServer = dhcpServer clients.addFromConfig(objects) go clients.periodicUpdate() @@ -190,7 +194,10 @@ func (clients *clientsContainer) Find(ip string) (Client, bool) { } } - macFound := config.dhcpServer.FindMACbyIP(ipAddr) + if clients.dhcpServer == nil { + return Client{}, false + } + macFound := clients.dhcpServer.FindMACbyIP(ipAddr) if macFound == nil { return Client{}, false } @@ -533,13 +540,16 @@ func (clients *clientsContainer) addFromSystemARP() { // add clients from DHCP that have non-empty Hostname property func (clients *clientsContainer) addFromDHCP() { - leases := config.dhcpServer.Leases() + if clients.dhcpServer == nil { + return + } + leases := clients.dhcpServer.Leases() n := 0 for _, l := range leases { if len(l.Hostname) == 0 { continue } - ok, _ := config.clients.AddHost(l.IP.String(), l.Hostname, ClientSourceDHCP) + ok, _ := clients.AddHost(l.IP.String(), l.Hostname, ClientSourceDHCP) if ok { n++ } diff --git a/home/clients_test.go b/home/clients_test.go index 70493a19..c830ee34 100644 --- a/home/clients_test.go +++ b/home/clients_test.go @@ -12,7 +12,7 @@ func TestClients(t *testing.T) { var b bool clients := clientsContainer{} - clients.Init(nil) + clients.Init(nil, nil) // add c = Client{ @@ -149,7 +149,7 @@ func TestClients(t *testing.T) { func TestClientsWhois(t *testing.T) { var c Client clients := clientsContainer{} - clients.Init(nil) + clients.Init(nil, nil) whois := [][]string{{"orgname", "orgname-val"}, {"country", "country-val"}} // set whois info on new client diff --git a/home/config.go b/home/config.go index c7bbb269..61053ebe 100644 --- a/home/config.go +++ b/home/config.go @@ -67,7 +67,7 @@ type configuration struct { dnsctx dnsContext dnsFilter *dnsfilter.Dnsfilter dnsServer *dnsforward.Server - dhcpServer dhcpd.Server + dhcpServer *dhcpd.Server httpServer *http.Server httpsServer HTTPSServer @@ -325,6 +325,12 @@ func (c *configuration) write() error { config.DNS.DnsfilterConf = c } + if config.dhcpServer != nil { + c := dhcpd.ServerConfig{} + config.dhcpServer.WriteDiskConfig(&c) + config.DHCP = c + } + configFile := config.getConfigFilename() log.Debug("Writing YAML file: %s", configFile) yamlText, err := yaml.Marshal(&config) diff --git a/home/home.go b/home/home.go index cf407a00..9a059cd5 100644 --- a/home/home.go +++ b/home/home.go @@ -20,6 +20,7 @@ import ( "syscall" "time" + "github.com/AdguardTeam/AdGuardHome/dhcpd" "github.com/AdguardTeam/golibs/log" "github.com/NYTimes/gziphandler" "github.com/gobuffalo/packr" @@ -118,7 +119,8 @@ func run(args options) { } } - config.clients.Init(config.Clients) + config.dhcpServer = dhcpd.Create(config.DHCP) + config.clients.Init(config.Clients, config.dhcpServer) config.Clients = nil if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") && From 477a4bbf546ae0c79439fc9149b878a6fae8433c Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 11 Oct 2019 19:56:18 +0300 Subject: [PATCH 2/7] * dhcp: move HTTP handlers to dhcpd/ --- dhcpd/dhcp_http.go | 461 +++++++++++++++++++++++++++++++++++++++++++++ dhcpd/dhcpd.go | 19 +- home/control.go | 6 - home/dhcp.go | 417 ---------------------------------------- home/helpers.go | 16 -- home/home.go | 3 + 6 files changed, 480 insertions(+), 442 deletions(-) create mode 100644 dhcpd/dhcp_http.go diff --git a/dhcpd/dhcp_http.go b/dhcpd/dhcp_http.go new file mode 100644 index 00000000..d194d055 --- /dev/null +++ b/dhcpd/dhcp_http.go @@ -0,0 +1,461 @@ +package dhcpd + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "os/exec" + "runtime" + "strings" + "time" + + "github.com/AdguardTeam/golibs/file" + "github.com/AdguardTeam/golibs/log" +) + +func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) { + text := fmt.Sprintf(format, args...) + log.Info("DHCP: %s %s: %s", r.Method, r.URL, text) + http.Error(w, text, code) +} + +// []Lease -> JSON +func convertLeases(inputLeases []Lease, includeExpires bool) []map[string]string { + leases := []map[string]string{} + for _, l := range inputLeases { + lease := map[string]string{ + "mac": l.HWAddr.String(), + "ip": l.IP.String(), + "hostname": l.Hostname, + } + + if includeExpires { + lease["expires"] = l.Expiry.Format(time.RFC3339) + } + + leases = append(leases, lease) + } + return leases +} + +func (s *Server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) { + leases := convertLeases(s.Leases(), true) + staticLeases := convertLeases(s.StaticLeases(), false) + status := map[string]interface{}{ + "config": s.conf, + "leases": leases, + "static_leases": staticLeases, + } + + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(status) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "Unable to marshal DHCP status json: %s", err) + return + } +} + +type staticLeaseJSON struct { + HWAddr string `json:"mac"` + IP string `json:"ip"` + Hostname string `json:"hostname"` +} + +type dhcpServerConfigJSON struct { + ServerConfig `json:",inline"` + StaticLeases []staticLeaseJSON `json:"static_leases"` +} + +func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { + newconfig := dhcpServerConfigJSON{} + err := json.NewDecoder(r.Body).Decode(&newconfig) + if err != nil { + httpError(r, w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err) + return + } + + err = s.CheckConfig(newconfig.ServerConfig) + if err != nil { + httpError(r, w, http.StatusBadRequest, "Invalid DHCP configuration: %s", err) + return + } + + err = s.Stop() + if err != nil { + log.Error("failed to stop the DHCP server: %s", err) + } + + err = s.Init(newconfig.ServerConfig) + if err != nil { + httpError(r, w, http.StatusBadRequest, "Invalid DHCP configuration: %s", err) + return + } + s.conf.ConfigModified() + + if newconfig.Enabled { + + staticIP, err := hasStaticIP(newconfig.InterfaceName) + if !staticIP && err == nil { + err = setStaticIP(newconfig.InterfaceName) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "Failed to configure static IP: %s", err) + return + } + } + + err = s.Start() + if err != nil { + httpError(r, w, http.StatusBadRequest, "Failed to start DHCP server: %s", err) + return + } + } +} + +type netInterface struct { + Name string `json:"name"` + MTU int `json:"mtu"` + HardwareAddr string `json:"hardware_address"` + Addresses []string `json:"ip_addresses"` + Flags string `json:"flags"` +} + +// getValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP +// invalid interface is a ppp interface or the one that doesn't allow broadcasts +func getValidNetInterfaces() ([]net.Interface, error) { + ifaces, err := net.Interfaces() + if err != nil { + return nil, fmt.Errorf("Couldn't get list of interfaces: %s", err) + } + + netIfaces := []net.Interface{} + + for i := range ifaces { + if ifaces[i].Flags&net.FlagPointToPoint != 0 { + // this interface is ppp, we're not interested in this one + continue + } + + iface := ifaces[i] + netIfaces = append(netIfaces, iface) + } + + return netIfaces, nil +} + +func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { + response := map[string]interface{}{} + + ifaces, err := getValidNetInterfaces() + if err != nil { + httpError(r, w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err) + return + } + + for _, iface := range ifaces { + if iface.Flags&net.FlagLoopback != 0 { + // it's a loopback, skip it + continue + } + if iface.Flags&net.FlagBroadcast == 0 { + // this interface doesn't support broadcast, skip it + continue + } + addrs, err := iface.Addrs() + if err != nil { + httpError(r, w, http.StatusInternalServerError, "Failed to get addresses for interface %s: %s", iface.Name, err) + return + } + + jsonIface := netInterface{ + Name: iface.Name, + MTU: iface.MTU, + HardwareAddr: iface.HardwareAddr.String(), + } + + if iface.Flags != 0 { + jsonIface.Flags = iface.Flags.String() + } + // we don't want link-local addresses in json, so skip them + for _, addr := range addrs { + ipnet, ok := addr.(*net.IPNet) + if !ok { + // not an IPNet, should not happen + httpError(r, w, http.StatusInternalServerError, "SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr) + return + } + // ignore link-local + if ipnet.IP.IsLinkLocalUnicast() { + continue + } + jsonIface.Addresses = append(jsonIface.Addresses, ipnet.IP.String()) + } + if len(jsonIface.Addresses) != 0 { + response[iface.Name] = jsonIface + } + + } + + err = json.NewEncoder(w).Encode(response) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "Failed to marshal json with available interfaces: %s", err) + return + } +} + +// Perform the following tasks: +// . Search for another DHCP server running +// . Check if a static IP is configured for the network interface +// Respond with results +func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + errorText := fmt.Sprintf("failed to read request body: %s", err) + log.Error(errorText) + http.Error(w, errorText, http.StatusBadRequest) + return + } + + interfaceName := strings.TrimSpace(string(body)) + if interfaceName == "" { + errorText := fmt.Sprintf("empty interface name specified") + log.Error(errorText) + http.Error(w, errorText, http.StatusBadRequest) + return + } + + found, err := CheckIfOtherDHCPServersPresent(interfaceName) + + othSrv := map[string]interface{}{} + foundVal := "no" + if found { + foundVal = "yes" + } else if err != nil { + foundVal = "error" + othSrv["error"] = err.Error() + } + othSrv["found"] = foundVal + + staticIP := map[string]interface{}{} + isStaticIP, err := hasStaticIP(interfaceName) + staticIPStatus := "yes" + if err != nil { + staticIPStatus = "error" + staticIP["error"] = err.Error() + } else if !isStaticIP { + staticIPStatus = "no" + staticIP["ip"] = getFullIP(interfaceName) + } + staticIP["static"] = staticIPStatus + + result := map[string]interface{}{} + result["other_server"] = othSrv + result["static_ip"] = staticIP + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(result) + if err != nil { + httpError(r, w, http.StatusInternalServerError, "Failed to marshal DHCP found json: %s", err) + return + } +} + +// Check if network interface has a static IP configured +func hasStaticIP(ifaceName string) (bool, error) { + if runtime.GOOS == "windows" { + return false, errors.New("Can't detect static IP: not supported on Windows") + } + + body, err := ioutil.ReadFile("/etc/dhcpcd.conf") + if err != nil { + return false, err + } + lines := strings.Split(string(body), "\n") + nameLine := fmt.Sprintf("interface %s", ifaceName) + withinInterfaceCtx := false + + for _, line := range lines { + line = strings.TrimSpace(line) + + if withinInterfaceCtx && len(line) == 0 { + // an empty line resets our state + withinInterfaceCtx = false + } + + if len(line) == 0 || line[0] == '#' { + continue + } + line = strings.TrimSpace(line) + + if !withinInterfaceCtx { + if line == nameLine { + // we found our interface + withinInterfaceCtx = true + } + + } else { + if strings.HasPrefix(line, "interface ") { + // we found another interface - reset our state + withinInterfaceCtx = false + continue + } + if strings.HasPrefix(line, "static ip_address=") { + return true, nil + } + } + } + + return false, nil +} + +// Get IP address with netmask +func getFullIP(ifaceName string) string { + cmd := exec.Command("ip", "-oneline", "-family", "inet", "address", "show", ifaceName) + log.Tracef("executing %s %v", cmd.Path, cmd.Args) + d, err := cmd.Output() + if err != nil || cmd.ProcessState.ExitCode() != 0 { + return "" + } + + fields := strings.Fields(string(d)) + if len(fields) < 4 { + return "" + } + _, _, err = net.ParseCIDR(fields[3]) + if err != nil { + return "" + } + + return fields[3] +} + +// Get gateway IP address +func getGatewayIP(ifaceName string) string { + cmd := exec.Command("ip", "route", "show", "dev", ifaceName) + log.Tracef("executing %s %v", cmd.Path, cmd.Args) + d, err := cmd.Output() + if err != nil || cmd.ProcessState.ExitCode() != 0 { + return "" + } + + fields := strings.Fields(string(d)) + if len(fields) < 3 || fields[0] != "default" { + return "" + } + + ip := net.ParseIP(fields[2]) + if ip == nil { + return "" + } + + return fields[2] +} + +// Set a static IP for network interface +func setStaticIP(ifaceName string) error { + ip := getFullIP(ifaceName) + if len(ip) == 0 { + return errors.New("Can't get IP address") + } + + body, err := ioutil.ReadFile("/etc/dhcpcd.conf") + if err != nil { + return err + } + + ip4, _, err := net.ParseCIDR(ip) + if err != nil { + return err + } + + add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n", + ifaceName, ip) + body = append(body, []byte(add)...) + + gatewayIP := getGatewayIP(ifaceName) + if len(gatewayIP) != 0 { + add = fmt.Sprintf("static routers=%s\n", + gatewayIP) + body = append(body, []byte(add)...) + } + + add = fmt.Sprintf("static domain_name_servers=%s\n\n", + ip4) + body = append(body, []byte(add)...) + + err = file.SafeWrite("/etc/dhcpcd.conf", body) + if err != nil { + return err + } + + return nil +} + +func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) { + + lj := staticLeaseJSON{} + err := json.NewDecoder(r.Body).Decode(&lj) + if err != nil { + httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err) + return + } + + ip, _ := parseIPv4(lj.IP) + if ip == nil { + httpError(r, w, http.StatusBadRequest, "invalid IP") + return + } + + mac, _ := net.ParseMAC(lj.HWAddr) + + lease := Lease{ + IP: ip, + HWAddr: mac, + Hostname: lj.Hostname, + } + err = s.AddStaticLease(lease) + if err != nil { + httpError(r, w, http.StatusBadRequest, "%s", err) + return + } +} + +func (s *Server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Request) { + + lj := staticLeaseJSON{} + err := json.NewDecoder(r.Body).Decode(&lj) + if err != nil { + httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err) + return + } + + ip, _ := parseIPv4(lj.IP) + if ip == nil { + httpError(r, w, http.StatusBadRequest, "invalid IP") + return + } + + mac, _ := net.ParseMAC(lj.HWAddr) + + lease := Lease{ + IP: ip, + HWAddr: mac, + Hostname: lj.Hostname, + } + err = s.RemoveStaticLease(lease) + if err != nil { + httpError(r, w, http.StatusBadRequest, "%s", err) + return + } +} + +func (s *Server) registerHandlers() { + s.conf.HTTPRegister("GET", "/control/dhcp/status", s.handleDHCPStatus) + s.conf.HTTPRegister("GET", "/control/dhcp/interfaces", s.handleDHCPInterfaces) + s.conf.HTTPRegister("POST", "/control/dhcp/set_config", s.handleDHCPSetConfig) + s.conf.HTTPRegister("POST", "/control/dhcp/find_active_dhcp", s.handleDHCPFindActiveServer) + s.conf.HTTPRegister("POST", "/control/dhcp/add_static_lease", s.handleDHCPAddStaticLease) + s.conf.HTTPRegister("POST", "/control/dhcp/remove_static_lease", s.handleDHCPRemoveStaticLease) +} diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index 74bc45d4..676447b4 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "net" + "net/http" "path/filepath" "strings" "sync" @@ -45,6 +46,12 @@ type ServerConfig struct { // IP conflict detector: time (ms) to wait for ICMP reply. // 0: disable ICMPTimeout uint `json:"icmp_timeout_msec" yaml:"icmp_timeout_msec"` + + // Called when the configuration is changed by HTTP request + ConfigModified func() `json:"-" yaml:"-"` + + // Register an HTTP handler + HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `json:"-" yaml:"-"` } // Server - the current state of the DHCP server @@ -92,6 +99,9 @@ func (s *Server) CheckConfig(config ServerConfig) error { func Create(config ServerConfig) *Server { s := Server{} s.conf = config + if s.conf.HTTPRegister != nil { + s.registerHandlers() + } return &s } @@ -111,9 +121,6 @@ func (s *Server) WriteDiskConfig(c *ServerConfig) { } func (s *Server) setConfig(config ServerConfig) error { - s.conf = config - s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename) - iface, err := net.InterfaceByName(config.InterfaceName) if err != nil { printInterfaces() @@ -165,6 +172,12 @@ func (s *Server) setConfig(config ServerConfig) error { dhcp4.OptionDomainNameServer: s.ipnet.IP, } + oldconf := s.conf + s.conf = config + s.conf.WorkDir = oldconf.WorkDir + s.conf.HTTPRegister = oldconf.HTTPRegister + s.conf.ConfigModified = oldconf.ConfigModified + s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename) return nil } diff --git a/home/control.go b/home/control.go index 143f73fc..6aa9cc5d 100644 --- a/home/control.go +++ b/home/control.go @@ -424,12 +424,6 @@ func registerControlHandlers() { httpRegister(http.MethodGet, "/control/i18n/current_language", handleI18nCurrentLanguage) http.HandleFunc("/control/version.json", postInstall(optionalAuth(handleGetVersionJSON))) httpRegister(http.MethodPost, "/control/update", handleUpdate) - httpRegister(http.MethodGet, "/control/dhcp/status", handleDHCPStatus) - httpRegister(http.MethodGet, "/control/dhcp/interfaces", handleDHCPInterfaces) - httpRegister(http.MethodPost, "/control/dhcp/set_config", handleDHCPSetConfig) - httpRegister(http.MethodPost, "/control/dhcp/find_active_dhcp", handleDHCPFindActiveServer) - httpRegister(http.MethodPost, "/control/dhcp/add_static_lease", handleDHCPAddStaticLease) - httpRegister(http.MethodPost, "/control/dhcp/remove_static_lease", handleDHCPRemoveStaticLease) httpRegister(http.MethodGet, "/control/access/list", handleAccessList) httpRegister(http.MethodPost, "/control/access/set", handleAccessSet) diff --git a/home/dhcp.go b/home/dhcp.go index c184b661..dcb7a28b 100644 --- a/home/dhcp.go +++ b/home/dhcp.go @@ -1,426 +1,9 @@ package home import ( - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net" - "net/http" - "os/exec" - "runtime" - "strings" - "time" - - "github.com/AdguardTeam/AdGuardHome/dhcpd" - "github.com/AdguardTeam/golibs/file" - "github.com/AdguardTeam/golibs/log" "github.com/joomcode/errorx" ) -// []dhcpd.Lease -> JSON -func convertLeases(inputLeases []dhcpd.Lease, includeExpires bool) []map[string]string { - leases := []map[string]string{} - for _, l := range inputLeases { - lease := map[string]string{ - "mac": l.HWAddr.String(), - "ip": l.IP.String(), - "hostname": l.Hostname, - } - - if includeExpires { - lease["expires"] = l.Expiry.Format(time.RFC3339) - } - - leases = append(leases, lease) - } - return leases -} - -func handleDHCPStatus(w http.ResponseWriter, r *http.Request) { - leases := convertLeases(config.dhcpServer.Leases(), true) - staticLeases := convertLeases(config.dhcpServer.StaticLeases(), false) - status := map[string]interface{}{ - "config": config.DHCP, - "leases": leases, - "static_leases": staticLeases, - } - - w.Header().Set("Content-Type", "application/json") - err := json.NewEncoder(w).Encode(status) - if err != nil { - httpError(w, http.StatusInternalServerError, "Unable to marshal DHCP status json: %s", err) - return - } -} - -type leaseJSON struct { - HWAddr string `json:"mac"` - IP string `json:"ip"` - Hostname string `json:"hostname"` -} - -type dhcpServerConfigJSON struct { - dhcpd.ServerConfig `json:",inline"` - StaticLeases []leaseJSON `json:"static_leases"` -} - -func handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) { - newconfig := dhcpServerConfigJSON{} - err := json.NewDecoder(r.Body).Decode(&newconfig) - if err != nil { - httpError(w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err) - return - } - - newconfig.ServerConfig.WorkDir = config.ourWorkingDir - err = config.dhcpServer.CheckConfig(newconfig.ServerConfig) - if err != nil { - httpError(w, http.StatusBadRequest, "Invalid DHCP configuration: %s", err) - return - } - - err = config.dhcpServer.Stop() - if err != nil { - log.Error("failed to stop the DHCP server: %s", err) - } - - err = config.dhcpServer.Init(newconfig.ServerConfig) - if err != nil { - httpError(w, http.StatusBadRequest, "Invalid DHCP configuration: %s", err) - return - } - - if newconfig.Enabled { - - staticIP, err := hasStaticIP(newconfig.InterfaceName) - if !staticIP && err == nil { - err = setStaticIP(newconfig.InterfaceName) - if err != nil { - httpError(w, http.StatusInternalServerError, "Failed to configure static IP: %s", err) - return - } - } - - err = config.dhcpServer.Start() - if err != nil { - httpError(w, http.StatusBadRequest, "Failed to start DHCP server: %s", err) - return - } - } - - config.DHCP = newconfig.ServerConfig - httpUpdateConfigReloadDNSReturnOK(w, r) -} - -func handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) { - response := map[string]interface{}{} - - ifaces, err := getValidNetInterfaces() - if err != nil { - httpError(w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err) - return - } - - for _, iface := range ifaces { - if iface.Flags&net.FlagLoopback != 0 { - // it's a loopback, skip it - continue - } - if iface.Flags&net.FlagBroadcast == 0 { - // this interface doesn't support broadcast, skip it - continue - } - addrs, err := iface.Addrs() - if err != nil { - httpError(w, http.StatusInternalServerError, "Failed to get addresses for interface %s: %s", iface.Name, err) - return - } - - jsonIface := netInterface{ - Name: iface.Name, - MTU: iface.MTU, - HardwareAddr: iface.HardwareAddr.String(), - } - - if iface.Flags != 0 { - jsonIface.Flags = iface.Flags.String() - } - // we don't want link-local addresses in json, so skip them - for _, addr := range addrs { - ipnet, ok := addr.(*net.IPNet) - if !ok { - // not an IPNet, should not happen - httpError(w, http.StatusInternalServerError, "SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr) - return - } - // ignore link-local - if ipnet.IP.IsLinkLocalUnicast() { - continue - } - jsonIface.Addresses = append(jsonIface.Addresses, ipnet.IP.String()) - } - if len(jsonIface.Addresses) != 0 { - response[iface.Name] = jsonIface - } - - } - - err = json.NewEncoder(w).Encode(response) - if err != nil { - httpError(w, http.StatusInternalServerError, "Failed to marshal json with available interfaces: %s", err) - return - } -} - -// Perform the following tasks: -// . Search for another DHCP server running -// . Check if a static IP is configured for the network interface -// Respond with results -func handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) { - body, err := ioutil.ReadAll(r.Body) - if err != nil { - errorText := fmt.Sprintf("failed to read request body: %s", err) - log.Error(errorText) - http.Error(w, errorText, http.StatusBadRequest) - return - } - - interfaceName := strings.TrimSpace(string(body)) - if interfaceName == "" { - errorText := fmt.Sprintf("empty interface name specified") - log.Error(errorText) - http.Error(w, errorText, http.StatusBadRequest) - return - } - - found, err := dhcpd.CheckIfOtherDHCPServersPresent(interfaceName) - - othSrv := map[string]interface{}{} - foundVal := "no" - if found { - foundVal = "yes" - } else if err != nil { - foundVal = "error" - othSrv["error"] = err.Error() - } - othSrv["found"] = foundVal - - staticIP := map[string]interface{}{} - isStaticIP, err := hasStaticIP(interfaceName) - staticIPStatus := "yes" - if err != nil { - staticIPStatus = "error" - staticIP["error"] = err.Error() - } else if !isStaticIP { - staticIPStatus = "no" - staticIP["ip"] = getFullIP(interfaceName) - } - staticIP["static"] = staticIPStatus - - result := map[string]interface{}{} - result["other_server"] = othSrv - result["static_ip"] = staticIP - - w.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(w).Encode(result) - if err != nil { - httpError(w, http.StatusInternalServerError, "Failed to marshal DHCP found json: %s", err) - return - } -} - -// Check if network interface has a static IP configured -func hasStaticIP(ifaceName string) (bool, error) { - if runtime.GOOS == "windows" { - return false, errors.New("Can't detect static IP: not supported on Windows") - } - - body, err := ioutil.ReadFile("/etc/dhcpcd.conf") - if err != nil { - return false, err - } - lines := strings.Split(string(body), "\n") - nameLine := fmt.Sprintf("interface %s", ifaceName) - withinInterfaceCtx := false - - for _, line := range lines { - line = strings.TrimSpace(line) - - if withinInterfaceCtx && len(line) == 0 { - // an empty line resets our state - withinInterfaceCtx = false - } - - if len(line) == 0 || line[0] == '#' { - continue - } - line = strings.TrimSpace(line) - - if !withinInterfaceCtx { - if line == nameLine { - // we found our interface - withinInterfaceCtx = true - } - - } else { - if strings.HasPrefix(line, "interface ") { - // we found another interface - reset our state - withinInterfaceCtx = false - continue - } - if strings.HasPrefix(line, "static ip_address=") { - return true, nil - } - } - } - - return false, nil -} - -// Get IP address with netmask -func getFullIP(ifaceName string) string { - cmd := exec.Command("ip", "-oneline", "-family", "inet", "address", "show", ifaceName) - log.Tracef("executing %s %v", cmd.Path, cmd.Args) - d, err := cmd.Output() - if err != nil || cmd.ProcessState.ExitCode() != 0 { - return "" - } - - fields := strings.Fields(string(d)) - if len(fields) < 4 { - return "" - } - _, _, err = net.ParseCIDR(fields[3]) - if err != nil { - return "" - } - - return fields[3] -} - -// Get gateway IP address -func getGatewayIP(ifaceName string) string { - cmd := exec.Command("ip", "route", "show", "dev", ifaceName) - log.Tracef("executing %s %v", cmd.Path, cmd.Args) - d, err := cmd.Output() - if err != nil || cmd.ProcessState.ExitCode() != 0 { - return "" - } - - fields := strings.Fields(string(d)) - if len(fields) < 3 || fields[0] != "default" { - return "" - } - - ip := net.ParseIP(fields[2]) - if ip == nil { - return "" - } - - return fields[2] -} - -// Set a static IP for network interface -func setStaticIP(ifaceName string) error { - ip := getFullIP(ifaceName) - if len(ip) == 0 { - return errors.New("Can't get IP address") - } - - body, err := ioutil.ReadFile("/etc/dhcpcd.conf") - if err != nil { - return err - } - - ip4, _, err := net.ParseCIDR(ip) - if err != nil { - return err - } - - add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n", - ifaceName, ip) - body = append(body, []byte(add)...) - - gatewayIP := getGatewayIP(ifaceName) - if len(gatewayIP) != 0 { - add = fmt.Sprintf("static routers=%s\n", - gatewayIP) - body = append(body, []byte(add)...) - } - - add = fmt.Sprintf("static domain_name_servers=%s\n\n", - ip4) - body = append(body, []byte(add)...) - - err = file.SafeWrite("/etc/dhcpcd.conf", body) - if err != nil { - return err - } - - return nil -} - -func handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) { - - lj := leaseJSON{} - err := json.NewDecoder(r.Body).Decode(&lj) - if err != nil { - httpError(w, http.StatusBadRequest, "json.Decode: %s", err) - return - } - - ip := parseIPv4(lj.IP) - if ip == nil { - httpError(w, http.StatusBadRequest, "invalid IP") - return - } - - mac, _ := net.ParseMAC(lj.HWAddr) - - lease := dhcpd.Lease{ - IP: ip, - HWAddr: mac, - Hostname: lj.Hostname, - } - err = config.dhcpServer.AddStaticLease(lease) - if err != nil { - httpError(w, http.StatusBadRequest, "%s", err) - return - } - returnOK(w) -} - -func handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Request) { - - lj := leaseJSON{} - err := json.NewDecoder(r.Body).Decode(&lj) - if err != nil { - httpError(w, http.StatusBadRequest, "json.Decode: %s", err) - return - } - - ip := parseIPv4(lj.IP) - if ip == nil { - httpError(w, http.StatusBadRequest, "invalid IP") - return - } - - mac, _ := net.ParseMAC(lj.HWAddr) - - lease := dhcpd.Lease{ - IP: ip, - HWAddr: mac, - Hostname: lj.Hostname, - } - err = config.dhcpServer.RemoveStaticLease(lease) - if err != nil { - httpError(w, http.StatusBadRequest, "%s", err) - return - } - returnOK(w) -} - func startDHCPServer() error { if !config.DHCP.Enabled { // not enabled, don't do anything diff --git a/home/helpers.go b/home/helpers.go index 6b0f01ed..f4be0b38 100644 --- a/home/helpers.go +++ b/home/helpers.go @@ -1,7 +1,6 @@ package home import ( - "bytes" "context" "errors" "fmt" @@ -338,21 +337,6 @@ func _Func() string { return path.Base(f.Name()) } -// Parse input string and return IPv4 address -func parseIPv4(s string) net.IP { - ip := net.ParseIP(s) - if ip == nil { - return nil - } - - v4InV6Prefix := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff} - if !bytes.Equal(ip[0:12], v4InV6Prefix) { - return nil - } - - return ip.To4() -} - // SplitNext - split string by a byte and return the first chunk // Whitespace is trimmed func SplitNext(str *string, splitBy byte) string { diff --git a/home/home.go b/home/home.go index 9a059cd5..1ff0f128 100644 --- a/home/home.go +++ b/home/home.go @@ -119,6 +119,9 @@ func run(args options) { } } + config.DHCP.WorkDir = config.ourWorkingDir + config.DHCP.HTTPRegister = httpRegister + config.DHCP.ConfigModified = onConfigModified config.dhcpServer = dhcpd.Create(config.DHCP) config.clients.Init(config.Clients, config.dhcpServer) config.Clients = nil From df92941ae0d464f5a4523c905cac8a88e8d4ddb4 Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Mon, 14 Oct 2019 12:12:06 +0300 Subject: [PATCH 3/7] + POST /control/dhcp/reset: Reset DHCP configuration --- AGHTechDoc.md | 15 +++++++++++++++ dhcpd/dhcp_http.go | 24 ++++++++++++++++++++++++ dhcpd/dhcpd.go | 9 +++++---- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/AGHTechDoc.md b/AGHTechDoc.md index 93ae634c..c3d21db2 100644 --- a/AGHTechDoc.md +++ b/AGHTechDoc.md @@ -28,6 +28,7 @@ Contents: * "Enable DHCP" command * Static IP check/set * Add a static lease + * API: Reset DHCP configuration * DNS access settings * List access settings * Set access settings @@ -543,6 +544,20 @@ Response: 200 OK +### API: Reset DHCP configuration + +Clear all DHCP leases and configuration settings. +DHCP server will be stopped if it's currently running. + +Request: + + POST /control/dhcp/reset + +Response: + + 200 OK + + ## TLS diff --git a/dhcpd/dhcp_http.go b/dhcpd/dhcp_http.go index d194d055..4d9ef538 100644 --- a/dhcpd/dhcp_http.go +++ b/dhcpd/dhcp_http.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net" "net/http" + "os" "os/exec" "runtime" "strings" @@ -451,6 +452,28 @@ func (s *Server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Requ } } +func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) { + err := s.Stop() + if err != nil { + log.Error("DHCP: Stop: %s", err) + } + + err = os.Remove(s.conf.DBFilePath) + if err != nil && !os.IsNotExist(err) { + log.Error("DHCP: os.Remove: %s: %s", s.conf.DBFilePath, err) + } + + oldconf := s.conf + s.conf = ServerConfig{} + s.conf.LeaseDuration = 86400 + s.conf.ICMPTimeout = 1000 + s.conf.WorkDir = oldconf.WorkDir + s.conf.HTTPRegister = oldconf.HTTPRegister + s.conf.ConfigModified = oldconf.ConfigModified + s.conf.DBFilePath = oldconf.DBFilePath + s.conf.ConfigModified() +} + func (s *Server) registerHandlers() { s.conf.HTTPRegister("GET", "/control/dhcp/status", s.handleDHCPStatus) s.conf.HTTPRegister("GET", "/control/dhcp/interfaces", s.handleDHCPInterfaces) @@ -458,4 +481,5 @@ func (s *Server) registerHandlers() { s.conf.HTTPRegister("POST", "/control/dhcp/find_active_dhcp", s.handleDHCPFindActiveServer) s.conf.HTTPRegister("POST", "/control/dhcp/add_static_lease", s.handleDHCPAddStaticLease) s.conf.HTTPRegister("POST", "/control/dhcp/remove_static_lease", s.handleDHCPRemoveStaticLease) + s.conf.HTTPRegister("POST", "/control/dhcp/reset", s.handleReset) } diff --git a/dhcpd/dhcpd.go b/dhcpd/dhcpd.go index 676447b4..ac90050f 100644 --- a/dhcpd/dhcpd.go +++ b/dhcpd/dhcpd.go @@ -39,13 +39,14 @@ type ServerConfig struct { SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"` RangeStart string `json:"range_start" yaml:"range_start"` RangeEnd string `json:"range_end" yaml:"range_end"` - LeaseDuration uint `json:"lease_duration" yaml:"lease_duration"` // in seconds - WorkDir string `json:"-" yaml:"-"` - DBFilePath string `json:"-" yaml:"-"` // path to DB file + LeaseDuration uint32 `json:"lease_duration" yaml:"lease_duration"` // in seconds // IP conflict detector: time (ms) to wait for ICMP reply. // 0: disable - ICMPTimeout uint `json:"icmp_timeout_msec" yaml:"icmp_timeout_msec"` + ICMPTimeout uint32 `json:"icmp_timeout_msec" yaml:"icmp_timeout_msec"` + + WorkDir string `json:"-" yaml:"-"` + DBFilePath string `json:"-" yaml:"-"` // path to DB file // Called when the configuration is changed by HTTP request ConfigModified func() `json:"-" yaml:"-"` From a4dedacf43d6aa6baa284c08e22b179323a87f5e Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Tue, 15 Oct 2019 17:14:24 +0300 Subject: [PATCH 4/7] + client: handle DHCP reset --- client/src/__locales/en.json | 3 +- client/src/actions/index.js | 16 +++++ client/src/api/Api.js | 6 ++ client/src/components/Settings/Dhcp/Form.js | 64 ++++++++++++++------ client/src/components/Settings/Dhcp/index.js | 44 ++++++++------ client/src/containers/Dhcp.js | 2 + client/src/reducers/index.js | 10 +++ 7 files changed, 109 insertions(+), 36 deletions(-) diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 9ff11424..6b0bc715 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -17,7 +17,7 @@ "dhcp_leases": "DHCP leases", "dhcp_static_leases": "DHCP static leases", "dhcp_leases_not_found": "No DHCP leases found", - "dhcp_config_saved": "Saved DHCP server config", + "dhcp_config_saved": "DHCP config successfully saved", "form_error_required": "Required field", "form_error_ip4_format": "Invalid IPv4 format", "form_error_ip6_format": "Invalid IPv6 format", @@ -45,6 +45,7 @@ "dhcp_new_static_lease": "New static lease", "dhcp_static_leases_not_found": "No DHCP static leases found", "dhcp_add_static_lease": "Add static lease", + "dhcp_reset": "Are you sure you want to reset DHCP config?", "delete_confirm": "Are you sure you want to delete \"{{key}}\"?", "form_enter_hostname": "Enter hostname", "error_details": "Error details", diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 28c2a713..d01130f6 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -470,6 +470,22 @@ export const toggleDhcp = values => async (dispatch) => { } }; +export const resetDhcpRequest = createAction('RESET_DHCP_REQUEST'); +export const resetDhcpSuccess = createAction('RESET_DHCP_SUCCESS'); +export const resetDhcpFailure = createAction('RESET_DHCP_FAILURE'); + +export const resetDhcp = () => async (dispatch) => { + dispatch(resetDhcpRequest()); + try { + const status = await apiClient.resetDhcp(); + dispatch(resetDhcpSuccess(status)); + dispatch(addSuccessToast('dhcp_config_saved')); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(resetDhcpFailure()); + } +}; + export const toggleLeaseModal = createAction('TOGGLE_LEASE_MODAL'); export const addStaticLeaseRequest = createAction('ADD_STATIC_LEASE_REQUEST'); diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 72d6d527..2393a89d 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -248,6 +248,7 @@ class Api { DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' }; DHCP_ADD_STATIC_LEASE = { path: 'dhcp/add_static_lease', method: 'POST' }; DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' }; + DHCP_RESET = { path: 'dhcp/reset', method: 'POST' }; getDhcpStatus() { const { path, method } = this.DHCP_STATUS; @@ -295,6 +296,11 @@ class Api { return this.makeRequest(path, method, parameters); } + resetDhcp() { + const { path, method } = this.DHCP_RESET; + return this.makeRequest(path, method); + } + // Installation INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' }; INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' }; diff --git a/client/src/components/Settings/Dhcp/Form.js b/client/src/components/Settings/Dhcp/Form.js index 86de6340..8bbef865 100644 --- a/client/src/components/Settings/Dhcp/Form.js +++ b/client/src/components/Settings/Dhcp/Form.js @@ -50,6 +50,23 @@ const renderInterfaceValues = (interfaceValues => ( )); +const clearFields = (change, resetDhcp, t) => { + const fields = { + interface_name: '', + gateway_ip: '', + subnet_mask: '', + range_start: '', + range_end: '', + lease_duration: 86400, + }; + + // eslint-disable-next-line no-alert + if (window.confirm(t('dhcp_reset'))) { + Object.keys(fields).forEach(field => change(field, fields[field])); + resetDhcp(); + } +}; + let Form = (props) => { const { t, @@ -61,6 +78,8 @@ let Form = (props) => { interfaceValue, processingConfig, processingInterfaces, + resetDhcp, + change, } = props; return ( @@ -160,31 +179,42 @@ let Form = (props) => { - +
+ + +
); }; Form.propTypes = { - handleSubmit: PropTypes.func, - submitting: PropTypes.bool, - invalid: PropTypes.bool, - interfaces: PropTypes.object, + handleSubmit: PropTypes.func.isRequired, + submitting: PropTypes.bool.isRequired, + invalid: PropTypes.bool.isRequired, + interfaces: PropTypes.object.isRequired, interfaceValue: PropTypes.string, - initialValues: PropTypes.object, - processingConfig: PropTypes.bool, - processingInterfaces: PropTypes.bool, - enabled: PropTypes.bool, - t: PropTypes.func, + initialValues: PropTypes.object.isRequired, + processingConfig: PropTypes.bool.isRequired, + processingInterfaces: PropTypes.bool.isRequired, + enabled: PropTypes.bool.isRequired, + t: PropTypes.func.isRequired, + resetDhcp: PropTypes.func.isRequired, + change: PropTypes.func.isRequired, }; - const selector = formValueSelector('dhcpForm'); Form = connect((state) => { diff --git a/client/src/components/Settings/Dhcp/index.js b/client/src/components/Settings/Dhcp/index.js index 626c18d4..959922ce 100644 --- a/client/src/components/Settings/Dhcp/index.js +++ b/client/src/components/Settings/Dhcp/index.js @@ -154,7 +154,15 @@ class Dhcp extends Component { }; render() { - const { t, dhcp } = this.props; + const { + t, + dhcp, + resetDhcp, + findActiveDhcp, + addStaticLease, + removeStaticLease, + toggleLeaseModal, + } = this.props; const statusButtonClass = classnames({ 'btn btn-primary btn-standard': true, 'btn btn-primary btn-standard btn-loading': dhcp.processingStatus, @@ -184,6 +192,7 @@ class Dhcp extends Component { processingConfig={dhcp.processingConfig} processingInterfaces={dhcp.processingInterfaces} enabled={enabled} + resetDhcp={resetDhcp} />
@@ -191,9 +200,7 @@ class Dhcp extends Component { @@ -260,16 +267,17 @@ class Dhcp extends Component { } Dhcp.propTypes = { - dhcp: PropTypes.object, - toggleDhcp: PropTypes.func, - getDhcpStatus: PropTypes.func, - setDhcpConfig: PropTypes.func, - findActiveDhcp: PropTypes.func, - addStaticLease: PropTypes.func, - removeStaticLease: PropTypes.func, - toggleLeaseModal: PropTypes.func, - getDhcpInterfaces: PropTypes.func, - t: PropTypes.func, + dhcp: PropTypes.object.isRequired, + toggleDhcp: PropTypes.func.isRequired, + getDhcpStatus: PropTypes.func.isRequired, + setDhcpConfig: PropTypes.func.isRequired, + findActiveDhcp: PropTypes.func.isRequired, + addStaticLease: PropTypes.func.isRequired, + removeStaticLease: PropTypes.func.isRequired, + toggleLeaseModal: PropTypes.func.isRequired, + getDhcpInterfaces: PropTypes.func.isRequired, + t: PropTypes.func.isRequired, + resetDhcp: PropTypes.func.isRequired, }; export default withNamespaces()(Dhcp); diff --git a/client/src/containers/Dhcp.js b/client/src/containers/Dhcp.js index 6f2b8f47..1f9d6b62 100644 --- a/client/src/containers/Dhcp.js +++ b/client/src/containers/Dhcp.js @@ -8,6 +8,7 @@ import { toggleLeaseModal, addStaticLease, removeStaticLease, + resetDhcp, } from '../actions'; import Dhcp from '../components/Settings/Dhcp'; @@ -28,6 +29,7 @@ const mapDispatchToProps = { toggleLeaseModal, addStaticLease, removeStaticLease, + resetDhcp, }; export default connect( diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index 0e8ff407..3eca36a6 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -289,6 +289,16 @@ const dhcp = handleActions( return newState; }, + [actions.resetDhcpRequest]: state => ({ ...state, processingReset: true }), + [actions.resetDhcpFailure]: state => ({ ...state, processingReset: false }), + [actions.resetDhcpSuccess]: state => ({ + ...state, + processingReset: false, + config: { + enabled: false, + }, + }), + [actions.toggleLeaseModal]: (state) => { const newState = { ...state, From 90ce70225fb110fbbc04992a2eb0ccb203cb312a Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Wed, 23 Oct 2019 18:52:58 +0300 Subject: [PATCH 5/7] + openapi: /dhcp/reset --- openapi/openapi.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 6514922c..608b5848 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -451,6 +451,16 @@ paths: 200: description: OK + /dhcp/reset: + post: + tags: + - dhcp + operationId: dhcpReset + summary: "Reset DHCP configuration" + responses: + 200: + description: OK + # -------------------------------------------------- # Filtering status methods # -------------------------------------------------- From e9cb8666ce2e7ddfc0b60eb0eaf6ee9b8824740b Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 22 Nov 2019 14:37:25 +0300 Subject: [PATCH 6/7] * clients: refactor --- home/clients.go | 2 ++ home/clients_http.go | 40 ++++++++++++++++++++-------------------- home/control.go | 1 - 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/home/clients.go b/home/clients.go index 2a5abb50..4e6486e2 100644 --- a/home/clients.go +++ b/home/clients.go @@ -77,6 +77,8 @@ func (clients *clientsContainer) Init(objects []clientObject, dhcpServer *dhcpd. clients.addFromConfig(objects) go clients.periodicUpdate() + + clients.registerWebHandlers() } type clientObject struct { diff --git a/home/clients_http.go b/home/clients_http.go index dbfbf873..7c39b68d 100644 --- a/home/clients_http.go +++ b/home/clients_http.go @@ -36,15 +36,15 @@ type clientListJSON struct { } // respond with information about configured clients -func handleGetClients(w http.ResponseWriter, r *http.Request) { +func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http.Request) { data := clientListJSON{} - config.clients.lock.Lock() - for _, c := range config.clients.list { + clients.lock.Lock() + for _, c := range clients.list { cj := clientToJSON(c) data.Clients = append(data.Clients, cj) } - for ip, ch := range config.clients.ipHost { + for ip, ch := range clients.ipHost { cj := clientHostJSON{ IP: ip, Name: ch.Host, @@ -69,7 +69,7 @@ func handleGetClients(w http.ResponseWriter, r *http.Request) { data.AutoClients = append(data.AutoClients, cj) } - config.clients.lock.Unlock() + clients.lock.Unlock() w.Header().Set("Content-Type", "application/json") e := json.NewEncoder(w).Encode(data) @@ -139,7 +139,7 @@ func clientHostToJSON(ip string, ch ClientHost) clientHostJSONWithID { } // Add a new client -func handleAddClient(w http.ResponseWriter, r *http.Request) { +func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { httpError(w, http.StatusBadRequest, "failed to read request body: %s", err) @@ -158,7 +158,7 @@ func handleAddClient(w http.ResponseWriter, r *http.Request) { httpError(w, http.StatusBadRequest, "%s", err) return } - ok, err := config.clients.Add(*c) + ok, err := clients.Add(*c) if err != nil { httpError(w, http.StatusBadRequest, "%s", err) return @@ -173,7 +173,7 @@ func handleAddClient(w http.ResponseWriter, r *http.Request) { } // Remove client -func handleDelClient(w http.ResponseWriter, r *http.Request) { +func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { httpError(w, http.StatusBadRequest, "failed to read request body: %s", err) @@ -187,7 +187,7 @@ func handleDelClient(w http.ResponseWriter, r *http.Request) { return } - if !config.clients.Del(cj.Name) { + if !clients.Del(cj.Name) { httpError(w, http.StatusBadRequest, "Client not found") return } @@ -202,7 +202,7 @@ type updateJSON struct { } // Update client's properties -func handleUpdateClient(w http.ResponseWriter, r *http.Request) { +func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { httpError(w, http.StatusBadRequest, "failed to read request body: %s", err) @@ -226,7 +226,7 @@ func handleUpdateClient(w http.ResponseWriter, r *http.Request) { return } - err = config.clients.Update(dj.Name, *c) + err = clients.Update(dj.Name, *c) if err != nil { httpError(w, http.StatusBadRequest, "%s", err) return @@ -237,7 +237,7 @@ func handleUpdateClient(w http.ResponseWriter, r *http.Request) { } // Get the list of clients by IP address list -func handleFindClient(w http.ResponseWriter, r *http.Request) { +func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() data := []map[string]interface{}{} for i := 0; ; i++ { @@ -246,9 +246,9 @@ func handleFindClient(w http.ResponseWriter, r *http.Request) { break } el := map[string]interface{}{} - c, ok := config.clients.Find(ip) + c, ok := clients.Find(ip) if !ok { - ch, ok := config.clients.FindAutoClient(ip) + ch, ok := clients.FindAutoClient(ip) if !ok { continue // a client with this IP isn't found } @@ -277,10 +277,10 @@ func handleFindClient(w http.ResponseWriter, r *http.Request) { } // RegisterClientsHandlers registers HTTP handlers -func RegisterClientsHandlers() { - httpRegister("GET", "/control/clients", handleGetClients) - httpRegister("POST", "/control/clients/add", handleAddClient) - httpRegister("POST", "/control/clients/delete", handleDelClient) - httpRegister("POST", "/control/clients/update", handleUpdateClient) - httpRegister("GET", "/control/clients/find", handleFindClient) +func (clients *clientsContainer) registerWebHandlers() { + httpRegister("GET", "/control/clients", clients.handleGetClients) + httpRegister("POST", "/control/clients/add", clients.handleAddClient) + httpRegister("POST", "/control/clients/delete", clients.handleDelClient) + httpRegister("POST", "/control/clients/update", clients.handleUpdateClient) + httpRegister("GET", "/control/clients/find", clients.handleFindClient) } diff --git a/home/control.go b/home/control.go index 6aa9cc5d..73508f79 100644 --- a/home/control.go +++ b/home/control.go @@ -431,7 +431,6 @@ func registerControlHandlers() { RegisterFilteringHandlers() RegisterTLSHandlers() - RegisterClientsHandlers() RegisterBlockedServicesHandlers() RegisterAuthHandlers() From a6926169815e03d00f9cfac0417da6f797d539ae Mon Sep 17 00:00:00 2001 From: Simon Zolin Date: Fri, 29 Nov 2019 17:35:26 +0300 Subject: [PATCH 7/7] - clients: fix tests --- home/clients.go | 8 ++++++-- home/clients_test.go | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/home/clients.go b/home/clients.go index 4e6486e2..0cc5c65a 100644 --- a/home/clients.go +++ b/home/clients.go @@ -62,6 +62,8 @@ type clientsContainer struct { lock sync.Mutex dhcpServer *dhcpd.Server + + testing bool // if TRUE, this object is used for internal tests } // Init initializes clients container @@ -76,9 +78,11 @@ func (clients *clientsContainer) Init(objects []clientObject, dhcpServer *dhcpd. clients.dhcpServer = dhcpServer clients.addFromConfig(objects) - go clients.periodicUpdate() + if !clients.testing { + go clients.periodicUpdate() - clients.registerWebHandlers() + clients.registerWebHandlers() + } } type clientObject struct { diff --git a/home/clients_test.go b/home/clients_test.go index c830ee34..99a5404d 100644 --- a/home/clients_test.go +++ b/home/clients_test.go @@ -11,6 +11,7 @@ func TestClients(t *testing.T) { var e error var b bool clients := clientsContainer{} + clients.testing = true clients.Init(nil, nil) @@ -149,6 +150,7 @@ func TestClients(t *testing.T) { func TestClientsWhois(t *testing.T) { var c Client clients := clientsContainer{} + clients.testing = true clients.Init(nil, nil) whois := [][]string{{"orgname", "orgname-val"}, {"country", "country-val"}}