From: Dan Fuhry Date: Sat, 22 Mar 2025 01:26:52 +0000 (-0400) Subject: machines bugfixes X-Git-Url: https://go.fuhry.dev/?a=commitdiff_plain;h=a976d01c0df9c21690506577448efcc6381ee5bb;p=runtime.git machines bugfixes - handle interface:deleted and host:deleted events - coredns plugin: only call tryInit if we fail to load the saved state from disk - coredns plugin: make all hostname lookups case insensitive - coredns plugin: fix .. lookups - coredns plugin: bump init ticker interval to 10 seconds - coredns plugin: tighten lock window when updating registry from API - coredns plugin: increase verbosity level of some really noisy log messages - tweak mqtt startup - fix `allow/deny unknown-clients` being added to dhcp subnets with only one range - add captive portal service in openbsd, fix maclist template --- diff --git a/machines/agent.go b/machines/agent.go index dc5a8cf..d66c25a 100644 --- a/machines/agent.go +++ b/machines/agent.go @@ -191,10 +191,24 @@ var actionHooks = []*actionHook{ ), }, { - thing: "iface", + thing: "interface", action: "*", fileGenerator: multiFile( - staticFiles("dhcpd4.conf", "dhcpd6.conf"), + staticFiles("maclist.conf"), + ), + }, + { + thing: "interface", + action: "deleted", + fileGenerator: multiFile( + staticFiles("dhcpd4.conf", "dhcpd6.conf", "maclist.conf"), + ), + }, + { + thing: "host", + action: "deleted", + fileGenerator: multiFile( + staticFiles("dhcpd4.conf", "dhcpd6.conf", "maclist.conf"), ), }, } diff --git a/machines/coredns_plugin/registry.go b/machines/coredns_plugin/registry.go index 26a0dfc..854ba09 100644 --- a/machines/coredns_plugin/registry.go +++ b/machines/coredns_plugin/registry.go @@ -107,12 +107,12 @@ func (r *registry) Startup() error { } else { r.log.Noticef("failed to load saved state from %s, no records will be served "+ "until the Machines API is reachable: %v", machinesRegistryStorePath, err) - } - err = r.tryInit() - if err != nil { - r.log.Warningf("failed to initialize API state, continuing startup; " + - "Machines records will be served once the API is available") + err = r.tryInit() + if err != nil { + r.log.Warningf("failed to initialize API state, continuing startup; " + + "Machines records will be served once the API is available") + } } go r.monitorEvents() @@ -324,7 +324,7 @@ func (r *registry) LookupHost(qname string) (*Result, error) { return nil, fmt.Errorf("plugin not yet initialized") } - fqdn := strings.TrimSuffix(qname, ".") + fqdn := strings.ToLower(strings.TrimSuffix(qname, ".")) r.log.V(2).Debugf("LookupHost(%s)", fqdn) if strings.HasSuffix(fqdn, ".in-addr.arpa") { @@ -333,13 +333,13 @@ func (r *registry) LookupHost(qname string) (*Result, error) { return r.lookupReverseIPv6(fqdn) } - nameParts := strings.Split(fqdn, ".") - basename := strings.ToLower(nameParts[0]) - domainName := strings.ToLower(strings.Join(nameParts[1:], ".")) + var basename, domainName string var domain *machines.Domain for _, d := range r.store.Domains { - if d.Name == domainName { + if strings.HasSuffix(fqdn, "."+d.Name) { + domainName = d.Name + basename = fqdn[:len(fqdn)-len(d.Name)-1] domain = d break } @@ -347,10 +347,22 @@ func (r *registry) LookupHost(qname string) (*Result, error) { if domain == nil { // domain name is not known to us, maybe the query is for a domain at another site - r.log.V(1).Debugf("domain is not known to us: %s", domainName) + r.log.V(2).Debugf("domain is not known to us: %s", domainName) return nil, fmt.Errorf("domain is not known to us: %s", domainName) } + baseParts := strings.Split(basename, ".") + switch len(baseParts) { + case 1: + return r.lookupHostLastSeenIface(basename) + case 2: + return r.lookupHostWithIface(basename) + default: + return nil, nil + } +} + +func (r *registry) lookupHostLastSeenIface(basename string) (*Result, error) { hostID, ok := r.store.HostNames[basename] if !ok { // host not found @@ -376,7 +388,50 @@ func (r *registry) LookupHost(qname string) (*Result, error) { r.log.V(1).Debugf("host %q: no valid LastDomain.ID", basename) return nil, nil } - domain, ok = r.store.Domains[iface.LastDomain.ID()] + domain, ok := r.store.Domains[iface.LastDomain.ID()] + if !ok { + // last seen domain is not known to us, maybe the host was last seen at a different site + r.log.V(1).Debugf("host %q: last seen domain is not known to us: %s", basename, iface.LastDomain.ID()) + return nil, nil + } + + return &Result{ + domain: domain, + host: host, + iface: iface, + }, nil +} + +func (r *registry) lookupHostWithIface(basename string) (*Result, error) { + ifaceID, ok := r.store.HostInterfaceNames[basename] + if !ok { + // host not found + r.log.V(1).Debugf("interface.hostname not found: %s", basename) + return nil, nil + } + iface, ok := r.store.Ifaces[ifaceID] + if ifaceID == "" || !ok { + // host has never been seen on any interface + r.log.V(1).Debugf("host %q: host has never been seen on any interface", basename) + return nil, nil + } + if iface.LastDomain.ID() == "" { + // no valid record of the last domain this iface was seen on + r.log.V(1).Debugf("host %q: no valid LastDomain.ID", basename) + return nil, nil + } + if iface.Host.ID() == "" { + r.log.V(1).Debugf("interface %q, basename %q: hostID unset", ifaceID, basename) + return nil, nil + } + hostID := iface.Host.ID() + host, ok := r.store.Hosts[hostID] + if !ok { + // hostname map out of date??? + r.log.V(1).Debugf("host %q, hostID %s found in hostname table but not in host list", basename, hostID) + return nil, fmt.Errorf("hostID %s found in hostname table but not in host list", hostID) + } + domain, ok := r.store.Domains[iface.LastDomain.ID()] if !ok { // last seen domain is not known to us, maybe the host was last seen at a different site r.log.V(1).Debugf("host %q: last seen domain is not known to us: %s", basename, iface.LastDomain.ID()) @@ -399,7 +454,6 @@ func (r *registry) lookupReverseIPv4(ptrName string) (*Result, error) { parts = parts[0:4] slices.Reverse(parts) - ip := net.ParseIP(strings.Join(parts, ".")) if ip == nil { r.log.Errorf("failed to parse IP in ptr query: %s", strings.Join(parts, ".")) @@ -650,7 +704,9 @@ func (r *registry) processHostSeen(hostID, ifaceID, newIP string) { r.store.patchIface(iface) } + host := r.store.Hosts[hostID] iface := r.store.Ifaces[ifaceID] + host.LastSeenIface.Set(iface) now := time.Now().Unix() iface.LastSeen = machines.Timestamp(now) @@ -662,31 +718,36 @@ func (r *registry) processHostSeen(hostID, ifaceID, newIP string) { } r.store.patchIface(iface) + r.store.patchHost(host) } func (r *registry) tryInit() error { + r.log.Noticef("trying to init event listener") client, err := machines.NewDefaultMachinesClient() if err != nil { + r.log.Warningf("init machines client failed: %v", err) return err } client.SetupStats(r.mb) eventsCh, err := client.NewEventListener(r.ctx) if err != nil { + r.log.Warningf("init event listener failed: %v", err) return err } - if !r.store.initialized() { + r.client = client + r.eventsCh = eventsCh + + if !r.store.initialized() || !r.store.fresh() { err = r.store.fetch(client) if err != nil { + r.log.Warningf("fresh data fetch failed: %v", err) return err } r.didLiveFetch = true } - r.client = client - r.eventsCh = eventsCh - return nil } @@ -694,9 +755,15 @@ func (r *Result) Answer(ques dns.Question) (int, []dns.RR) { answers := make([]dns.RR, 0) qname := strings.ToLower(strings.TrimSuffix(ques.Name, ".")) - nameParts := strings.Split(qname, ".") - basename := strings.ToLower(nameParts[0]) - domainName := strings.ToLower(strings.Join(nameParts[1:], ".")) + var basename, domainName string + if strings.HasSuffix(qname, "."+r.domain.Name) { + basename = strings.TrimSuffix(qname, "."+r.domain.Name) + domainName = qname[len(basename)+1:] + } else { + nameParts := strings.Split(qname, ".") + basename = nameParts[0] + domainName = strings.Join(nameParts[1:], ".") + } if strings.HasSuffix(qname, ".in-addr.arpa") || strings.HasSuffix(qname, ".ip6.arpa") { if (r.iface.LastIPv4.Defined() || r.iface.LastIPv6.Defined()) && (ques.Qtype == dns.TypePTR || ques.Qtype == dns.TypeANY) { @@ -722,7 +789,7 @@ func (r *Result) Answer(ques dns.Question) (int, []dns.RR) { Target: fmt.Sprintf("%s.%s.", basename, r.domain.Name), } answers = append(answers, msg) - } else if strings.HasPrefix(qname, r.host.Name+".") { + } else if strings.HasPrefix(qname, r.host.Name+".") || strings.HasPrefix(qname, fmt.Sprintf("%s.%s.", r.iface.NameScrubbed, r.host.Name)) { if r.iface.LastIPv4.Defined() && (ques.Qtype == dns.TypeA || ques.Qtype == dns.TypeANY) { msg := &dns.A{ Hdr: dns.RR_Header{ @@ -781,7 +848,7 @@ func (r *registry) monitorEvents() { defer (func() { r.stopCh <- struct{}{} })() if r.client == nil { - initTicker := time.NewTicker(1 * time.Second) + initTicker := time.NewTicker(10 * time.Second) initLoop: for { @@ -821,20 +888,24 @@ func (r *registry) monitorEvents() { r.stats.eventsReceived.WithLabelValues(mbclient.KV{"thing": event.ItemType, "action": event.Event}).Add(1) if event.ItemType == "host" && event.Event == "seen" { if via, ok := event.Tags["via"]; !ok || via != "dhcp" { + r.log.V(1).Debugf("skip seen event: not via dhcp") continue } ip, ok := event.Tags["ip"] if !ok || ip == "" { + r.log.V(1).Debugf("skip seen event: ip tag not present") continue } hostID, ok := event.Tags["host"] if !ok || hostID == "" { + r.log.V(1).Debugf("skip seen event: host tag not present") continue } ifaceID, ok := event.Tags["iface"] if !ok || ifaceID == "" { + r.log.V(1).Debugf("skip seen event: iface tag not present") continue } diff --git a/machines/coredns_plugin/registry_store.go b/machines/coredns_plugin/registry_store.go index 9ba83e8..1fc9de2 100644 --- a/machines/coredns_plugin/registry_store.go +++ b/machines/coredns_plugin/registry_store.go @@ -4,9 +4,11 @@ import ( "encoding/json" "flag" "fmt" + "net" "os" "strings" "sync" + "time" "go.fuhry.dev/runtime/constants" "go.fuhry.dev/runtime/machines" @@ -17,6 +19,7 @@ type registryStore struct { mu sync.Mutex `json:"-"` log *log.Logger + lastRefresh time.Time Sites map[string]*machines.Site `json:"Sites"` Domains map[string]*machines.Domain `json:"Domains"` Hosts map[string]*machines.Host `json:"Hosts"` @@ -47,6 +50,10 @@ func (rs *registryStore) initialized() bool { return rs.Sites != nil && rs.Domains != nil && rs.Hosts != nil && rs.Ifaces != nil && rs.Records != nil } +func (rs *registryStore) fresh() bool { + return rs.lastRefresh.Add(refreshInterval).After(time.Now()) +} + func (rs *registryStore) saveState() error { if !rs.initialized() { return nil @@ -105,9 +112,6 @@ func (rs *registryStore) loadState() error { } func (rs *registryStore) fetch(client machines.MachinesClient) error { - rs.mu.Lock() - defer rs.mu.Unlock() - sites := []*machines.Site{} err := client.APICall("sites", nil, &sites) if err != nil { @@ -149,6 +153,9 @@ func (rs *registryStore) fetch(client machines.MachinesClient) error { return err } + rs.mu.Lock() + defer rs.mu.Unlock() + // update state only after all information is collected rs.Sites = make(map[string]*machines.Site) for _, s := range sites { @@ -171,7 +178,7 @@ func (rs *registryStore) fetch(client machines.MachinesClient) error { for _, h := range hosts { h.Name = strings.ToLower(h.Name) rs.Hosts[h.ID()] = h - rs.HostNames[h.Name] = h.ID() + rs.HostNames[strings.ToLower(h.Name)] = h.ID() } rs.Ifaces = make(map[string]*machines.Iface) @@ -205,6 +212,7 @@ func (rs *registryStore) fetch(client machines.MachinesClient) error { } rs.processRecords(records) + rs.lastRefresh = time.Now() return nil } @@ -259,3 +267,28 @@ func (rs *registryStore) patchIface(iface *machines.Iface) { rs.Ifaces[iface.ID()] = iface } + +func (rs *registryStore) DomainForFqdn(qname string) *machines.Domain { + for _, domain := range rs.Domains { + if strings.HasSuffix(qname, "."+domain.Name) { + return domain + } + } + + return nil +} + +func (rs *registryStore) DomainForAddress(addr net.Addr) *machines.Domain { + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return nil + } + + for _, domain := range rs.Domains { + if domain.ContainsIP(udpAddr.IP) { + return domain + } + } + + return nil +} diff --git a/machines/coredns_plugin/setup.go b/machines/coredns_plugin/setup.go index 54b7775..ab828a0 100644 --- a/machines/coredns_plugin/setup.go +++ b/machines/coredns_plugin/setup.go @@ -57,19 +57,21 @@ func (m *Machines) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms Authoritative: true, Response: true, }, - Answer: make([]dns.RR, 0), + Answer: nil, } for _, ques := range r.Question { if rc, rrs := m.r.LookupRecord(ques); rc != dns.RcodeNameError { domain := m.r.(*registry).domainFromQuestion(ques) - m.r.(*registry).stats.recordLookups.WithLabelValues(mbclient.KV{ - "domain": domain.Name, - "rcode": rcodeToStr(rc), - }).Add(1) + if domain != nil { + m.r.(*registry).stats.recordLookups.WithLabelValues(mbclient.KV{ + "domain": domain.Name, + "rcode": rcodeToStr(rc), + }).Add(1) + } handled = true answer.Question = append(answer.Question, ques) - m.log.V(2).Debugf(" -> rcode %d, rrs %+v", rcode, rrs) + m.log.V(3).Debugf(" -> rcode %d, rrs %+v", rcode, rrs) if rc != dns.RcodeSuccess { m.log.Errorf("error in LookupRecord(%s): rcode=%d", ques.Name, rc) rcode = rc @@ -82,7 +84,7 @@ func (m *Machines) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms handled = true answer.Question = append(answer.Question, ques) if result == nil { - m.log.V(2).Debugf(" -> not found") + m.log.V(3).Debugf(" -> not found") rcode = dns.RcodeNameError m.r.(*registry).stats.hostLookups.WithLabelValues(mbclient.KV{ @@ -90,7 +92,7 @@ func (m *Machines) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms "rcode": rcodeToStr(rcode), }).Add(1) } else { - m.log.V(2).Debugf(" -> domain %s, host %s, iface %s", result.domain.ID(), result.host.ID(), result.iface.ID()) + m.log.V(3).Debugf(" -> domain %s, host %s, iface %s", result.domain.ID(), result.host.ID(), result.iface.ID()) r, rrs := result.Answer(ques) m.r.(*registry).stats.hostLookups.WithLabelValues(mbclient.KV{ @@ -111,7 +113,7 @@ func (m *Machines) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms } if handled { - m.log.V(3).Debugf(" -> WRITE %+v", answer) + m.log.V(4).Debugf(" -> WRITE %+v", answer) answer.MsgHdr.Rcode = rcode err := w.WriteMsg(answer) if err != nil { @@ -120,6 +122,6 @@ func (m *Machines) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Ms } return rcode, nil } - m.log.V(3).Debugf("NEXT!") + m.log.V(4).Debugf("NEXT!") return plugin.NextOrFailure(m.Name(), m.Next, ctx, w, r) } diff --git a/machines/event_watcher.go b/machines/event_watcher.go index 4efa632..1b87c90 100644 --- a/machines/event_watcher.go +++ b/machines/event_watcher.go @@ -3,7 +3,6 @@ package machines import ( "context" "encoding/json" - "errors" "fmt" "net/url" "time" @@ -39,6 +38,8 @@ func (mc *machinesClient) NewEventListener(ctx context.Context) (chan MachinesMq return nil, fmt.Errorf("events url %q is invalid: %v", mc.eventsUrl, err) } + msgChan := make(chan MachinesMqttEvent) + credentialsProvider := func() (string, string) { accessToken, err := oauthClient.getAccessToken() if err != nil { @@ -50,34 +51,7 @@ func (mc *machinesClient) NewEventListener(ctx context.Context) (chan MachinesMq return accessToken, "x" } - mqttOpts.Servers = append(mqttOpts.Servers, eventsUrl) - mqttOpts.SetCredentialsProvider(credentialsProvider) - mqttOpts.SetPingTimeout(10 * time.Second) - mqttOpts.SetConnectTimeout(10 * time.Second) - mqttOpts.SetCleanSession(true) - mqttOpts.SetAutoReconnect(true) - mqttOpts.SetConnectRetry(false) - mqttOpts.SetMaxReconnectInterval(30 * time.Second) - - client := mqtt.NewClient(mqttOpts) - t := client.Connect() - - select { - case <-t.Done(): - if err = t.Error(); err != nil { - return nil, err - } - case <-ctx.Done(): - return nil, context.Canceled - } - - if !client.IsConnected() { - return nil, errors.New("somehow we are still not connected??") - } - - msgChan := make(chan MachinesMqttEvent) - - handler := func(client mqtt.Client, msg mqtt.Message) { + onMessage := func(client mqtt.Client, msg mqtt.Message) { msg.Ack() mc.logger.V(2).Debugf("got raw mqtt msg: %s", msg.Payload()) @@ -102,12 +76,37 @@ func (mc *machinesClient) NewEventListener(ctx context.Context) (chan MachinesMq } } - t = client.Subscribe(constants.MachinesMqttTopic, byte(0), handler) + onConnect := func(client mqtt.Client) { + mqttLogger.Noticef("(Re)connected to server") + t := client.Subscribe(constants.MachinesMqttTopic, byte(0), onMessage) + select { + case <-t.Done(): + mqttLogger.Noticef("(Re)subscribed to topic") + break + case <-ctx.Done(): + close(msgChan) + } + } + + mqttOpts.Servers = append(mqttOpts.Servers, eventsUrl) + mqttOpts.SetCredentialsProvider(credentialsProvider) + mqttOpts.SetPingTimeout(10 * time.Second) + mqttOpts.SetConnectTimeout(10 * time.Second) + mqttOpts.SetCleanSession(true) + mqttOpts.SetAutoReconnect(true) + mqttOpts.SetConnectRetry(true) + mqttOpts.SetMaxReconnectInterval(30 * time.Second) + mqttOpts.SetOnConnectHandler(onConnect) + + client := mqtt.NewClient(mqttOpts) + t := client.Connect() + select { case <-t.Done(): - break + if err = t.Error(); err != nil { + return nil, err + } case <-ctx.Done(): - close(msgChan) return nil, ctx.Err() } diff --git a/machines/machines_agent/main.go b/machines/machines_agent/main.go index 04052cc..af9546d 100644 --- a/machines/machines_agent/main.go +++ b/machines/machines_agent/main.go @@ -216,7 +216,7 @@ func main() { } logger.Noticef("starting main event loop") - defer logger.Critical("stopping xx0r-machines-agent") + defer logger.Critical("stopping machines-agent") mainLoop: for { diff --git a/machines/render.go b/machines/render.go index bfc8b0f..b79408b 100644 --- a/machines/render.go +++ b/machines/render.go @@ -42,6 +42,7 @@ var templateVarMap = varMap{ "rad.conf": {"domains", "site", "routeraddresses"}, "radvd.conf": {"domains", "site", "routeraddresses"}, "pf-captive-portal.conf": {"domains", "apiServer"}, + "maclist.conf": {"domains", "interfaces"}, } var baseVars = []string{ @@ -78,6 +79,10 @@ var outputFileMap = map[string]*artifact{ filePath: "/etc/radvd.conf", serviceName: "radvd", }, + "maclist.conf": { + filePath: "/etc/pf/maclist.conf", + serviceName: "captive", + }, } type calculator struct { @@ -359,7 +364,7 @@ func (c *calculator) realGet(varName string, args ...string) (any, error) { return sitesMap, nil case "interfaces": ifaces := make([]*Iface, 0) - err := c.client.APICall("interfaces", nil, &ifaces) + err := c.client.APICall("interfaces?expand[]=hosts", nil, &ifaces) if err != nil { return nil, fmt.Errorf("while fetching interfaces: %+v", err) } diff --git a/machines/services.go b/machines/services.go index 4e71146..ed20751 100644 --- a/machines/services.go +++ b/machines/services.go @@ -1,5 +1,12 @@ package machines +import ( + "os/exec" + "path" + + "go.fuhry.dev/runtime/utils/log" +) + type ServiceStatus struct { Running bool Pid int @@ -22,6 +29,52 @@ func (s *noopService) Status() (*ServiceStatus, error) { return &ServiceStatus{Running: true, Pid: 0}, nil } +type oneshotService struct { + command []string + stopCommand []string +} + +func (s *oneshotService) EnsureStarted() error { + return s.do(s.command) +} + +func (s *oneshotService) EnsureStopped() error { + if s.stopCommand != nil { + return s.do(s.stopCommand) + } + + return nil +} + +func (s *oneshotService) ReloadOrRestart(startIfStopped bool) error { + return s.do(s.command) +} + +func (s *oneshotService) Status() (*ServiceStatus, error) { + return &ServiceStatus{ + Running: true, + Pid: 0, + }, nil +} + +func (s *oneshotService) do(cmdline []string) error { + executable := cmdline[0] + if !path.IsAbs(executable) { + exe, err := exec.LookPath(executable) + if err != nil { + return err + } + executable = exe + } + + logger.Noticef("running command: %s %+v", executable, cmdline[1:]) + cmd := exec.Command(executable, cmdline[1:]...) + cmd.Stdout = logger.AppendPrefix("[stdout]").NewWriter(log.INFO) + cmd.Stderr = logger.AppendPrefix("[stderr]").NewWriter(log.ERROR) + err := cmd.Run() + return err +} + var services = make(map[string]Service) func registerService(name string, svc Service) { diff --git a/machines/services_openbsd.go b/machines/services_openbsd.go index 3ceb26d..b340416 100644 --- a/machines/services_openbsd.go +++ b/machines/services_openbsd.go @@ -178,4 +178,7 @@ func init() { pidFile: "/var/run/dhcpd6.pid", daemon: true, }) + registerService("captive", &oneshotService{ + command: []string{"bash", "-c", "for f in /etc/hostname.bridge*; do /sbin/ifconfig ${f#/etc/hostname.} flushrule vlan${f#/etc/hostname.bridge} rulefile /etc/pf/maclist.conf; done"}, + }) } diff --git a/machines/templates/dhcpd4.conf.j2 b/machines/templates/dhcpd4.conf.j2 index 67dbc09..be513cd 100644 --- a/machines/templates/dhcpd4.conf.j2 +++ b/machines/templates/dhcpd4.conf.j2 @@ -63,11 +63,13 @@ subnet {{ domain.IPv4Address }} netmask {{ domain.IPv4PrefixLength.Mask() }} { # {{ range.Name }} pool { range {{ domain.Ranges[id].IPv4Start }} {{ domain.Ranges[id].IPv4End }}; - {% if domain.Ranges[id].ID() == domain.DefaultRange.ID() %} - allow unknown-clients; - {%- else -%} - deny unknown-clients; - {%- endif %} + {%- if domain.Ranges.length > 1 -%} + {% if domain.Ranges[id].ID() == domain.DefaultRange.ID() %} + allow unknown-clients; + {%- else -%} + deny unknown-clients; + {%- endif %} + {%- endif -%} {% for res in domain.Ranges[id].Reservations %} {%- if res.AddressFamily == 'inet' -%} diff --git a/machines/templates/dhcpd6.conf.j2 b/machines/templates/dhcpd6.conf.j2 index 683088e..9904ea3 100644 --- a/machines/templates/dhcpd6.conf.j2 +++ b/machines/templates/dhcpd6.conf.j2 @@ -49,11 +49,13 @@ on commit { # {{ range.Name }} pool6 { range6 {{ range.IPv6Start }} {{ range.IPv6End }}; - {% if range.ID() == domain.DefaultRange.ID() %} - allow unknown-clients; - {%- else -%} - deny unknown-clients; - {%- endif %} + {%- if domain.Ranges.length > 1 -%} + {% if range.ID() == domain.DefaultRange.ID() %} + allow unknown-clients; + {%- else -%} + deny unknown-clients; + {%- endif %} + {%- endif -%} {%- for res in range.Reservations -%} {% if res.AddressFamily == 'inet6' %} diff --git a/machines/templates/maclist.conf.j2 b/machines/templates/maclist.conf.j2 index b283f67..d28f5f7 100644 --- a/machines/templates/maclist.conf.j2 +++ b/machines/templates/maclist.conf.j2 @@ -1,8 +1,12 @@ # This file is automatically generated by machines-agent and will be overwritten # any time a change is made to registered hosts/interfaces. -{% for iface in interfaces -%} -{% if iface.host != None and 'disabled' not in iface.host.flags and 'quarantine' not in iface.host.owner.flags -%} -pass in on vlan{{ domain.vlan_id }} src {{ iface.hardware_address }} tag PERMIT +{% for domain in domains %} +{%- if 'captive_portal' in domain.Features %} +{%- for iface in interfaces -%} +{% if iface.Host.Defined() -%} +pass in on vlan{{ domain.VlanID }} src {{ iface.HardwareAddress }} tag PERMIT {% endif -%} -{% endfor -%} +{%- endfor -%} +{%- endif -%} +{%- endfor -%} \ No newline at end of file diff --git a/machines/types.go b/machines/types.go index b9e1923..9095926 100644 --- a/machines/types.go +++ b/machines/types.go @@ -372,6 +372,30 @@ type EndorsementKey struct { } `json:"endorsement_key"` } +func (d *Domain) ContainsIP(ip net.IP) bool { + if ip4 := ip.To4(); ip4 != nil { + + } else if ip6 := ip.To16(); ip6 != nil { + + } + + return false +} + +func (d *Domain) IPv4Network() net.IPNet { + return net.IPNet{ + IP: d.IPv4Address.AsIP(), + Mask: d.IPv4PrefixLength.IPMask(), + } +} + +func (d *Domain) IPv6Network() net.IPNet { + return net.IPNet{ + IP: d.IPv6Address.AsIP(), + Mask: d.IPv6PrefixLength.IPMask(), + } +} + func (t Timestamp) AsTime() time.Time { return time.Unix(int64(t), 0) }