diff --git a/client/firewall/nftables/filter_linux.go b/client/firewall/nftables/filter_linux.go index e040da4fc..fa1ef1484 100644 --- a/client/firewall/nftables/filter_linux.go +++ b/client/firewall/nftables/filter_linux.go @@ -359,29 +359,40 @@ func (r *family) applyPort(port *firewall.Port, isSource bool) ([]expr.Any, erro Data: binaryutil.BigEndian.PutUint16(port.Values[0]), }) default: - set := &nftables.Set{ - Anonymous: true, - Constant: true, - Table: r.workTable, - KeyType: nftables.TypeInetService, + lookup, err := r.anonymousPortSet(port.Values) + if err != nil { + return nil, err } - elements := make([]nftables.SetElement, 0, len(port.Values)) - for _, p := range port.Values { - elements = append(elements, nftables.SetElement{Key: binaryutil.BigEndian.PutUint16(p)}) - } - if err := r.conn.AddSet(set, elements); err != nil { - return nil, fmt.Errorf("add anonymous port set: %w", err) - } - exprs = append(exprs, &expr.Lookup{ - SourceRegister: 1, - SetID: set.ID, - SetName: set.Name, - }) + exprs = append(exprs, lookup) } return exprs, nil } +// anonymousPortSet queues an anonymous constant set holding the given +// ports on the connection and returns a lookup against it. The set is +// committed by the caller's flush together with the rule that binds it. +func (r *family) anonymousPortSet(values []uint16) (*expr.Lookup, error) { + set := &nftables.Set{ + Anonymous: true, + Constant: true, + Table: r.workTable, + KeyType: nftables.TypeInetService, + } + elements := make([]nftables.SetElement, 0, len(values)) + for _, p := range values { + elements = append(elements, nftables.SetElement{Key: binaryutil.BigEndian.PutUint16(p)}) + } + if err := r.conn.AddSet(set, elements); err != nil { + return nil, fmt.Errorf("add anonymous port set: %w", err) + } + return &expr.Lookup{ + SourceRegister: 1, + SetID: set.ID, + SetName: set.Name, + }, nil +} + // applyPorts builds the source then destination port matches. func (r *family) applyPorts(sPort, dPort *firewall.Port) ([]expr.Any, error) { sPortExprs, err := r.applyPort(sPort, true) diff --git a/client/firewall/nftables/router_linux_test.go b/client/firewall/nftables/router_linux_test.go index fc2974a8d..9b41bb905 100644 --- a/client/firewall/nftables/router_linux_test.go +++ b/client/firewall/nftables/router_linux_test.go @@ -780,6 +780,14 @@ func containsPort(exprs []expr.Any, port *firewall.Port, isSource bool) bool { } } } + case *expr.Lookup: + // Multiple discrete ports compile to an anonymous set lookup + // rather than a chain of comparisons. The set's id and name are + // assigned dynamically, so matching the lookup is enough here; + // the set elements are verified separately. + if !port.IsRange && len(port.Values) > 1 { + portMatchFound = true + } } if payloadFound && portMatchFound { return true diff --git a/client/internal/acl/manager.go b/client/internal/acl/manager.go index 58fe330ee..f196b51e5 100644 --- a/client/internal/acl/manager.go +++ b/client/internal/acl/manager.go @@ -489,10 +489,10 @@ func extractRuleSources(r *mgmProto.FirewallRule) ([]netip.Prefix, error) { return out, nil } - //nolint:staticcheck // PeerIP used for backward compatibility with old management - addr, err := netip.ParseAddr(r.PeerIP) + peerIP := r.PeerIP //nolint:staticcheck // PeerIP is the legacy source field for old management servers + addr, err := netip.ParseAddr(peerIP) if err != nil { - return nil, fmt.Errorf("parse peer IP %q: %w", r.PeerIP, err) + return nil, fmt.Errorf("parse peer IP %q: %w", peerIP, err) } addr = addr.Unmap() // An unspecified PeerIP means "any peer" (legacy management