diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 1522dee..07c8261 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -16,8 +16,8 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v2 with: - # Run on the latest minor release of Go 1.14: - go-version: ^1.14 + # Run on the latest minor release of Go 1.18: + go-version: ^1.18 id: go - name: Check out code into the Go module directory @@ -51,8 +51,8 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v2 with: - # Run on the latest minor release of Go 1.14: - go-version: ^1.14 + # Run on the latest minor release of Go 1.18: + go-version: ^1.18 id: go - name: Check out code into the Go module directory @@ -78,8 +78,8 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v2 with: - # Run on the latest minor release of Go 1.14: - go-version: ^1.14 + # Run on the latest minor release of Go 1.18: + go-version: ^1.18 id: go - name: Check out code into the Go module directory diff --git a/cmd/dhcp4d/dhcp4d.go b/cmd/dhcp4d/dhcp4d.go index e445f4f..8376155 100644 --- a/cmd/dhcp4d/dhcp4d.go +++ b/cmd/dhcp4d/dhcp4d.go @@ -97,6 +97,9 @@ var ( } return dur.Truncate(1 * time.Second).String() }, + "zero": func(t time.Time) bool { + return t.IsZero() + }, }).Parse(` @@ -156,8 +159,8 @@ form { MAC address Vendor VendorIdentifier -Start Expiry +Last ACK {{ range $idx, $l := . }} @@ -174,18 +177,15 @@ form { {{$l.HardwareAddr}} {{$l.Vendor}} {{$l.VendorIdentifier}} -{{$l.Start}} -{{ if $l.Expired }} -{{ since $l.Expiry }} -expired -{{ else }} -{{ if $l.Static }} -static -{{ else }} -{{ timefmt $l.Expiry }} +{{ if (not (zero $l.LastACK)) }} +{{ timefmt $l.LastACK }} +{{ if $l.Active }} active {{ end }} +{{ if $l.Expired }} +expired +{{ end }} {{ end }} @@ -193,10 +193,16 @@ form { {{ end }} {{ end }} +

Static Leases

{{ template "table" .StaticLeases }} +
+ +

Dynamic Leases

+ {{ template "table" .DynamicLeases }}
+ `)) @@ -371,17 +377,20 @@ func newSrv(permDir string) (*srv, error) { Vendor string Expired bool Static bool + Active bool } leasesMu.Lock() defer leasesMu.Unlock() static := make([]tmplLease, 0, len(leases)) dynamic := make([]tmplLease, 0, len(leases)) + now := time.Now() tl := func(l *dhcp4d.Lease) tmplLease { return tmplLease{ Lease: *l, Vendor: ouiDB.Lookup(l.HardwareAddr[:8]), - Expired: l.Expired(time.Now()), + Expired: l.Expired(now), + Active: l.Active(now), Static: l.Expiry.IsZero(), } } @@ -444,7 +453,7 @@ func newSrv(permDir string) (*srv, error) { Addr: latest.Addr.String(), HardwareAddr: latest.HardwareAddr, Expiration: latest.Expiry.In(time.UTC), - Start: latest.Start.In(time.UTC), + Start: latest.LastACK.In(time.UTC), VendorIdentifier: latest.VendorIdentifier, } leaseJSON, err := json.Marshal(leaseVal) diff --git a/cmd/netconfigd/netconfigd.go b/cmd/netconfigd/netconfigd.go index 3d16b3c..c9863fc 100644 --- a/cmd/netconfigd/netconfigd.go +++ b/cmd/netconfigd/netconfigd.go @@ -47,6 +47,8 @@ var ( func init() { var c nftables.Conn + filter4 := &nftables.Table{Family: nftables.TableFamilyIPv4, Name: "filter"} + filter6 := &nftables.Table{Family: nftables.TableFamilyIPv6, Name: "filter"} for _, metric := range []struct { name string labels prometheus.Labels @@ -56,18 +58,34 @@ func init() { { name: "filter_forward", labels: prometheus.Labels{"family": "ipv4"}, - obj: &nftables.CounterObj{ - Table: &nftables.Table{Family: nftables.TableFamilyIPv4, Name: "filter"}, - Name: "fwded", - }, + obj: &nftables.CounterObj{Table: filter4, Name: "fwded"}, }, { name: "filter_forward", labels: prometheus.Labels{"family": "ipv6"}, - obj: &nftables.CounterObj{ - Table: &nftables.Table{Family: nftables.TableFamilyIPv6, Name: "filter"}, - Name: "fwded", - }, + obj: &nftables.CounterObj{Table: filter6, Name: "fwded"}, + }, + + { + name: "filter_input", + labels: prometheus.Labels{"family": "ipv4"}, + obj: &nftables.CounterObj{Table: filter4, Name: "inputc"}, + }, + { + name: "filter_input", + labels: prometheus.Labels{"family": "ipv6"}, + obj: &nftables.CounterObj{Table: filter6, Name: "inputc"}, + }, + + { + name: "filter_output", + labels: prometheus.Labels{"family": "ipv4"}, + obj: &nftables.CounterObj{Table: filter4, Name: "outputc"}, + }, + { + name: "filter_output", + labels: prometheus.Labels{"family": "ipv6"}, + obj: &nftables.CounterObj{Table: filter6, Name: "outputc"}, }, } { metric := metric // copy @@ -75,12 +93,11 @@ func init() { updateCounter := func() { mu.Lock() defer mu.Unlock() - objs, err := c.GetObjReset(metric.obj) - if err != nil || - len(objs) != 1 { + obj, err := c.ResetObject(metric.obj) + if err != nil { return } - if co, ok := objs[0].(*nftables.CounterObj); ok { + if co, ok := obj.(*nftables.CounterObj); ok { metric.packets += co.Packets metric.bytes += co.Bytes } diff --git a/go.mod b/go.mod index b61c795..e16abb1 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/krolaw/dhcp4 v0.0.0-20190909130307-a50d88189771 github.com/libdns/cloudflare v0.1.0 github.com/libdns/libdns v0.2.0 - github.com/mdlayher/ndp v0.0.0-20200602162440-17ab9e3e5567 + github.com/mdlayher/ndp v0.10.0 github.com/mdlayher/netlink v1.6.0 // indirect github.com/mdlayher/packet v1.0.0 github.com/miekg/dns v1.1.42 @@ -30,9 +30,9 @@ require ( github.com/vishvananda/netlink v1.1.1-0.20200221165523-c79a4b7b4066 github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd + golang.org/x/net v0.0.0-20220225172249-27dd8689420f golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220209214540-3681064d5158 + golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba golang.zx2c4.com/wireguard v0.0.20201118 // indirect golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210506160403-92e472f520a5 diff --git a/go.sum b/go.sum index 5448d66..3d9af06 100644 --- a/go.sum +++ b/go.sum @@ -110,7 +110,6 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -224,8 +223,8 @@ github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQob github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo= github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0= github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc= -github.com/mdlayher/ndp v0.0.0-20200602162440-17ab9e3e5567 h1:x+xs91ZJ+lr0C6sedWeREvck4uGCt+AA1kKXwsHB6jI= -github.com/mdlayher/ndp v0.0.0-20200602162440-17ab9e3e5567/go.mod h1:32w/5dDZWVSEOxyniAgKK4d7dHTuO6TCxWmUznQe3f8= +github.com/mdlayher/ndp v0.10.0 h1:Zdwol2bq1EHY8xSnejIYkq6LEj7dLjLymJX0o/2tjGw= +github.com/mdlayher/ndp v0.10.0/go.mod h1:Uv6IWvgvqirNUu2N3ZXJEB86xu6foyUsG0NrClSSfek= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/netlink v0.0.0-20191009155606-de872b0d824b/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= @@ -438,7 +437,6 @@ golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -450,8 +448,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210504132125-bbd867fde50d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -460,7 +459,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -494,7 +492,6 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602100848-8d3cce7afc34/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -521,8 +518,9 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf h1:Fm4IcnUL803i92qDlmB0obyHmosDrxZWxJL3gIeNqOw= +golang.org/x/sys v0.0.0-20220317061510-51cd9980dadf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= diff --git a/integration/netconfig/netconfig_test.go b/integration/netconfig/netconfig_test.go index 843ceb4..4b1221e 100644 --- a/integration/netconfig/netconfig_test.go +++ b/integration/netconfig/netconfig_test.go @@ -137,19 +137,20 @@ func goldenNftablesRules(additionalForwarding bool) string { add := "" if additionalForwarding { add = ` - iifname "uplink0" tcp dport 8045 dnat to 192.168.42.22:8045` + ip daddr != 127.0.0.0/8 ip daddr != 10.0.0.0/24 fib daddr type 2 tcp dport 8045 dnat to 192.168.42.22:8045` } return `table ip nat { chain prerouting { type nat hook prerouting priority 0; policy accept; - iifname "uplink0" tcp dport 8080 dnat to 192.168.42.23:9999` + add + ` - iifname "uplink0" tcp dport 8040-8060 dnat to 192.168.42.99:8040-8060 - iifname "uplink0" udp dport 53 dnat to 192.168.42.99:53 + ip daddr != 127.0.0.0/8 ip daddr != 10.0.0.0/24 fib daddr type 2 tcp dport 8080 dnat to 192.168.42.23:9999` + add + ` + ip daddr != 127.0.0.0/8 ip daddr != 10.0.0.0/24 fib daddr type 2 tcp dport 8040-8060 dnat to 192.168.42.99:8040-8060 + ip daddr != 127.0.0.0/8 ip daddr != 10.0.0.0/24 fib daddr type 2 udp dport 53 dnat to 192.168.42.99:53 } chain postrouting { type nat hook postrouting priority 100; policy accept; oifname "uplink0" masquerade + iifname "lan0" oifname "lan0" ct status 0x20 masquerade } } table ip filter { @@ -157,14 +158,11 @@ table ip filter { packets 23 bytes 42 } - chain forward { - type filter hook forward priority 0; policy accept; - oifname "uplink0" tcp flags 0x2 tcp option maxseg size set rt mtu - counter name "fwded" + counter inputc { + packets 23 bytes 42 } -} -table ip6 filter { - counter fwded { + + counter outputc { packets 23 bytes 42 } @@ -173,6 +171,45 @@ table ip6 filter { oifname "uplink0" tcp flags 0x2 tcp option maxseg size set rt mtu counter name "fwded" } + + chain input { + type filter hook input priority 0; policy accept; + counter name "inputc" + } + + chain output { + type filter hook output priority 0; policy accept; + counter name "outputc" + } +} +table ip6 filter { + counter fwded { + packets 23 bytes 42 + } + + counter inputc { + packets 23 bytes 42 + } + + counter outputc { + packets 23 bytes 42 + } + + chain forward { + type filter hook forward priority 0; policy accept; + oifname "uplink0" tcp flags 0x2 tcp option maxseg size set rt mtu + counter name "fwded" + } + + chain input { + type filter hook input priority 0; policy accept; + counter name "inputc" + } + + chain output { + type filter hook output priority 0; policy accept; + counter name "outputc" + } }` } diff --git a/internal/dhcp4d/dhcp4d.go b/internal/dhcp4d/dhcp4d.go index eed7acd..e522e6e 100644 --- a/internal/dhcp4d/dhcp4d.go +++ b/internal/dhcp4d/dhcp4d.go @@ -43,14 +43,18 @@ type Lease struct { Hostname string `json:"hostname"` HostnameOverride string `json:"hostname_override"` Expiry time.Time `json:"expiry"` - Start time.Time `json:"start"` VendorIdentifier string `json:"vendor"` + LastACK time.Time `json:"last_ack"` } func (l *Lease) Expired(at time.Time) bool { return !l.Expiry.IsZero() && at.After(l.Expiry) } +func (l *Lease) Active(at time.Time) bool { + return !l.LastACK.IsZero() && at.Before(l.LastACK.Add(leasePeriod)) +} + type Handler struct { serverIP net.IP start net.IP // first IP address to hand out @@ -106,23 +110,25 @@ func NewHandler(dir string, iface *net.Interface, ifaceName string, conn net.Pac copy(start, serverIP) start[len(start)-1]++ return &Handler{ - rawConn: conn, - iface: iface, - leasesHW: make(map[string]int), - leasesIP: make(map[int]*Lease), - serverIP: serverIP, - start: start, - leaseRange: 230, - // Apple recommends a DHCP lease time of 1 hour in - // https://support.apple.com/de-ch/HT202068, - // so if 20 minutes ever causes any trouble, - // we should try increasing it to 1 hour. - LeasePeriod: 20 * time.Minute, + rawConn: conn, + iface: iface, + leasesHW: make(map[string]int), + leasesIP: make(map[int]*Lease), + serverIP: serverIP, + start: start, + leaseRange: 230, + LeasePeriod: leasePeriod, options: options, timeNow: time.Now, }, nil } +// Apple recommends a DHCP lease time of 1 hour in +// https://support.apple.com/de-ch/HT202068, +// so if 20 minutes ever causes any trouble, +// we should try increasing it to 1 hour. +const leasePeriod = 20 * time.Minute + // SetLeases overwrites the leases database with the specified leases, typically // loaded from persistent storage. There is no locking, so SetLeases must be // called before Serve. @@ -132,6 +138,9 @@ func (h *Handler) SetLeases(leases []*Lease) { h.leasesHW = make(map[string]int) h.leasesIP = make(map[int]*Lease) for _, l := range leases { + if l.LastACK.IsZero() { + l.LastACK = l.Expiry + } h.leasesHW[l.HardwareAddr] = l.Num h.leasesIP[l.Num] = l } @@ -344,8 +353,8 @@ func (h *Handler) serveDHCP(p dhcp4.Packet, msgType dhcp4.MessageType, options d HardwareAddr: hwAddr, Expiry: now.Add(h.leasePeriodForDevice(hwAddr)), Hostname: string(options[dhcp4.OptionHostName]), - Start: now, VendorIdentifier: string(bytes.ToValidUTF8(bytes.ReplaceAll(options[dhcp4.OptionVendorClassIdentifier], []byte{0}, []byte{}), []byte{})), + LastACK: h.timeNow(), } copy(lease.Addr, reqIP.To4()) diff --git a/internal/netconfig/netconfig.go b/internal/netconfig/netconfig.go index c69b857..620d910 100644 --- a/internal/netconfig/netconfig.go +++ b/internal/netconfig/netconfig.go @@ -412,6 +412,73 @@ func nfifname(n string) []byte { return b } +// matchUplinkIP is conceptually equivalent to "ip daddr ", but +// without actually using the IP address of the uplink0 interface (which would +// mean that rules need to change when the IP address changes). +// +// Instead, it uses “fib daddr type local” to match all locally-configured IP +// addresses and then excludes the loopback and LAN IP addresses. +func matchUplinkIP() []expr.Any { + return []expr.Any{ + // [ payload load 4b @ network header + 16 => reg 1 ] + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseNetworkHeader, + Offset: 16, // TODO + Len: 4, // TODO + }, + // [ bitwise reg 1 = (reg=1 & 0x000000ff ) ^ 0x00000000 ] + &expr.Bitwise{ + DestRegister: 1, + SourceRegister: 1, + Len: 4, + Mask: []byte{0xff, 0x00, 0x00, 0x00}, // 255.0.0.0, i.e. /8 + Xor: []byte{0x00, 0x00, 0x00, 0x00}, + }, + // [ cmp neq reg 1 0x0000007f ] + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: []byte{0x7f, 0x00, 0x00, 0x00}, + }, + + // [ payload load 4b @ network header + 16 => reg 1 ] + &expr.Payload{ + DestRegister: 1, + Base: expr.PayloadBaseNetworkHeader, + Offset: 16, // TODO + Len: 4, // TODO + }, + // [ bitwise reg 1 = (reg=1 & 0x00ffffff ) ^ 0x00000000 ] + &expr.Bitwise{ + DestRegister: 1, + SourceRegister: 1, + Len: 4, + Mask: []byte{0xff, 0xff, 0xff, 0x00}, // 255.255.255.0, i.e. /24 + Xor: []byte{0x00, 0x00, 0x00, 0x00}, + }, + // [ cmp neq reg 1 0x0000000a ] + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: []byte{0x0a, 0x00, 0x00, 0x00}, + }, + + // [ fib daddr type => reg 1 ] + &expr.Fib{ + Register: 1, + FlagDADDR: true, + ResultADDRTYPE: true, + }, + // [ cmp eq reg 1 0x00000002 ] + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: []byte{0x02, 0x00, 0x00, 0x00}, + }, + } +} + func portForwardExpr(ifname string, proto uint8, portMin, portMax uint16, dest net.IP, dportMin, dportMax uint16) []expr.Any { var cmp []expr.Any if portMin == portMax { @@ -439,16 +506,7 @@ func portForwardExpr(ifname string, proto uint8, portMin, portMax uint16, dest n }, } } - ex := []expr.Any{ - // [ meta load iifname => reg 1 ] - &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, - // [ cmp eq reg 1 0x696c7075 0x00306b6e 0x00000000 0x00000000 ] - &expr.Cmp{ - Op: expr.CmpOpEq, - Register: 1, - Data: nfifname(ifname), - }, - + ex := append(matchUplinkIP(), // [ meta load l4proto => reg 1 ] &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, // [ cmp eq reg 1 0x00000006 ] @@ -464,8 +522,7 @@ func portForwardExpr(ifname string, proto uint8, portMin, portMax uint16, dest n Base: expr.PayloadBaseTransportHeader, Offset: 2, // TODO Len: 2, // TODO - }, - } + }) ex = append(ex, cmp...) ex = append(ex, // [ immediate reg 1 0x0217a8c0 ] @@ -594,35 +651,13 @@ func applyPortForwardings(dir, ifname string, c *nftables.Conn, nat *nftables.Ta var DefaultCounterObj = &nftables.CounterObj{} func getCounterObj(c *nftables.Conn, o *nftables.CounterObj) *nftables.CounterObj { - objs, err := c.GetObj(o) + obj, err := c.GetObject(o) if err != nil { o.Bytes = DefaultCounterObj.Bytes o.Packets = DefaultCounterObj.Packets return o } - { - // TODO: remove this workaround once travis has workers with a newer kernel - // than its current Ubuntu trusty kernel (Linux 4.4.0): - var filtered []nftables.Obj - for _, obj := range objs { - co, ok := obj.(*nftables.CounterObj) - if !ok { - continue - } - if co.Table.Name != o.Table.Name { - continue - } - filtered = append(filtered, obj) - } - objs = filtered - } - if got, want := len(objs), 1; got != want { - log.Printf("could not carry counter values: unexpected number of objects in table %v: got %d, want %d", o.Table.Name, got, want) - o.Bytes = DefaultCounterObj.Bytes - o.Packets = DefaultCounterObj.Packets - return o - } - if co, ok := objs[0].(*nftables.CounterObj); ok { + if co, ok := obj.(*nftables.CounterObj); ok { return co } o.Bytes = DefaultCounterObj.Bytes @@ -630,6 +665,52 @@ func getCounterObj(c *nftables.Conn, o *nftables.CounterObj) *nftables.CounterOb return o } +func hairpinDNAT() []expr.Any { + return []expr.Any{ + // [ meta load oifname => reg 1 ] + &expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1}, + // [ cmp eq reg 1 0x306e616c 0x00000000 0x00000000 0x00000000 ] + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: nfifname("lan0"), + }, + + // [ meta load oifname => reg 1 ] + &expr.Meta{Key: expr.MetaKeyOIFNAME, Register: 1}, + // [ cmp eq reg 1 0x306e616c 0x00000000 0x00000000 0x00000000 ] + &expr.Cmp{ + Op: expr.CmpOpEq, + Register: 1, + Data: nfifname("lan0"), + }, + + // [ ct load status => reg 1 ] + &expr.Ct{ + Register: 1, + SourceRegister: false, + Key: expr.CtKeySTATUS, + }, + // [ bitwise reg 1 = (reg=1 & 0x00000020 ) ^ 0x00000000 ] + &expr.Bitwise{ + DestRegister: 1, + SourceRegister: 1, + Len: 4, + Mask: []byte{0x20, 0x00, 0x00, 0x00}, + Xor: []byte{0x00, 0x00, 0x00, 0x00}, + }, + + // [ cmp neq reg 1 0x00000000 ] + &expr.Cmp{ + Op: expr.CmpOpNeq, + Register: 1, + Data: []byte{0x00, 0x00, 0x00, 0x00}, + }, + // [ masq ] + &expr.Masq{}, + } +} + func applyFirewall(dir, ifname string) error { c := &nftables.Conn{} @@ -673,6 +754,12 @@ func applyFirewall(dir, ifname string) error { }, }) + c.AddRule(&nftables.Rule{ + Table: nat, + Chain: postrouting, + Exprs: hairpinDNAT(), + }) + if err := applyPortForwardings(dir, ifname, c, nat, prerouting); err != nil { return err } @@ -782,6 +869,56 @@ func applyFirewall(dir, ifname string) error { }, }, }) + + input := c.AddChain(&nftables.Chain{ + Name: "input", + Hooknum: nftables.ChainHookInput, + Priority: nftables.ChainPriorityFilter, + Table: filter, + Type: nftables.ChainTypeFilter, + }) + + counterObj = getCounterObj(c, &nftables.CounterObj{ + Table: filter, + Name: "inputc", + }) + counter = c.AddObj(counterObj).(*nftables.CounterObj) + c.AddRule(&nftables.Rule{ + Table: filter, + Chain: input, + Exprs: []expr.Any{ + // [ counter name input ] + &expr.Objref{ + Type: NFT_OBJECT_COUNTER, + Name: counter.Name, + }, + }, + }) + + output := c.AddChain(&nftables.Chain{ + Name: "output", + Hooknum: nftables.ChainHookOutput, + Priority: nftables.ChainPriorityFilter, + Table: filter, + Type: nftables.ChainTypeFilter, + }) + + counterObj = getCounterObj(c, &nftables.CounterObj{ + Table: filter, + Name: "outputc", + }) + counter = c.AddObj(counterObj).(*nftables.CounterObj) + c.AddRule(&nftables.Rule{ + Table: filter, + Chain: output, + Exprs: []expr.Any{ + // [ counter name output ] + &expr.Objref{ + Type: NFT_OBJECT_COUNTER, + Name: counter.Name, + }, + }, + }) } return c.Flush() diff --git a/internal/radvd/radvd.go b/internal/radvd/radvd.go index 45818e3..df3257d 100644 --- a/internal/radvd/radvd.go +++ b/internal/radvd/radvd.go @@ -18,6 +18,7 @@ package radvd import ( "log" "net" + "net/netip" "sync" "time" @@ -144,21 +145,21 @@ func (s *Server) sendAdvertisement(addr net.Addr) error { if err != nil { return err } - var linkLocal net.IP + var linkLocal netip.Addr for _, addr := range addrs { ipnet, ok := addr.(*net.IPNet) if !ok { continue } if ipv6LinkLocal.Contains(ipnet.IP) { - linkLocal = ipnet.IP + linkLocal, _ = netip.AddrFromSlice(ipnet.IP) break } } - if !linkLocal.Equal(net.IPv6zero) { + if linkLocal.IsValid() && !linkLocal.IsUnspecified() { options = append(options, &ndp.RecursiveDNSServer{ Lifetime: 30 * time.Minute, - Servers: []net.IP{linkLocal}, + Servers: []netip.Addr{linkLocal}, }) } } @@ -170,13 +171,14 @@ func (s *Server) sendAdvertisement(addr net.Addr) error { ones = 64 } + addr, _ := netip.AddrFromSlice(prefix.IP) options = append(options, &ndp.PrefixInformation{ PrefixLength: uint8(ones), OnLink: true, AutonomousAddressConfiguration: true, ValidLifetime: 2 * time.Hour, PreferredLifetime: 30 * time.Minute, - Prefix: prefix.IP, + Prefix: addr, }) } diff --git a/website/content/installation.md b/website/content/installation.md index 3597735..68f8430 100644 --- a/website/content/installation.md +++ b/website/content/installation.md @@ -107,8 +107,6 @@ Example: Schema: see [`portForwardings`]( https://github.com/rtr7/router7/blob/f86e20be5305fc0e7e77421e0f2abde98a84f2a7/internal/netconfig/netconfig.go#L431) -Please be aware that Hairpinning is currently not supported (see issue [#53 Support for Hairpinning](https://github.com/rtr7/router7/issues/53])) - ## Updates Run e.g. `rtr7-safe-update -updates_dir=$HOME/router7/updates` to: