Files
tomswall/internal/config/interfaces.go
T
unkinben 8d9a76c751 Add comprehensive nftables compiler with shorewall feature parity
Rewrites the compiler from ~440 to ~1700 lines covering all major shorewall
firewall features: loopback, conntrack fast-path, anti-spoof, DHCP, intra-zone,
blacklist/whitelist, conntrack notrack, tunnels (13 types), rules with sections,
DNAT/redirect, SNAT/masquerade, static NAT, policies with zone exclusions,
MSS clamping, rate limiting, connection limiting, negated addresses, ICMP type
matching, TCP RST reject, user/UID matching, mark match/set, NFQUEUE, NONAT,
and policy-level rate/conn limiting.

Adds full config types for all shorewall subsystems (mangle, accounting, maclist,
netmap, providers, tunnels, conntrack, blrules, proxyarp/ndp, routes, tc, secmarks),
shorewall migration tooling, expanded CLI commands, expression-level diff engine,
and 49 unit tests.
2026-07-01 23:56:44 +10:00

99 lines
3.0 KiB
Go

package config
import (
"fmt"
"strings"
)
type Interface struct {
Zone string `yaml:"zone,omitempty"`
Interface string `yaml:"interface"`
Options InterfaceOptions `yaml:"options,omitempty"`
}
type InterfaceOptions struct {
// Rule generation options
DHCP bool `yaml:"dhcp,omitempty"`
TCPFlags *bool `yaml:"tcpflags,omitempty"`
NoSmurfs bool `yaml:"nosmurfs,omitempty"`
RouteBack *bool `yaml:"routeback,omitempty"`
Bridge bool `yaml:"bridge,omitempty"`
DestOnly bool `yaml:"destonly,omitempty"`
Unmanaged bool `yaml:"unmanaged,omitempty"`
Upnp bool `yaml:"upnp,omitempty"`
// Startup behavior
Optional bool `yaml:"optional,omitempty"`
Required bool `yaml:"required,omitempty"`
Wait int `yaml:"wait,omitempty"`
// Logical-to-physical mapping
Physical string `yaml:"physical,omitempty"`
// TCP MSS clamping for forwarded SYN packets
MSS int `yaml:"mss,omitempty"`
// Limit zone to specific networks
Nets []string `yaml:"nets,omitempty"`
// Sysctl adjustments (applied on startup)
RouteFilter *int `yaml:"routefilter,omitempty"`
LogMartians *bool `yaml:"logmartians,omitempty"`
ArpFilter *bool `yaml:"arp_filter,omitempty"`
ArpIgnore *int `yaml:"arp_ignore,omitempty"`
ProxyArp *bool `yaml:"proxyarp,omitempty"`
SourceRoute *bool `yaml:"sourceroute,omitempty"`
// IPv6: controls acceptance of Router Advertisements (0/1/2)
AcceptRA *int `yaml:"accept_ra,omitempty"`
}
// PhysicalName returns the actual interface name (physical if set, else logical).
func (iface *Interface) PhysicalName() string {
if iface.Options.Physical != "" {
return iface.Options.Physical
}
return iface.Interface
}
// IsWildcard returns true if the interface matches multiple devices (e.g. "ppp+").
func (iface *Interface) IsWildcard() bool {
return strings.HasSuffix(iface.PhysicalName(), "+")
}
func (c *Config) validateInterfaces() error {
seen := make(map[string]bool)
fwZone := c.FirewallZone()
for i, iface := range c.Interfaces {
if iface.Interface == "" {
return fmt.Errorf("interface[%d]: interface name required", i)
}
if strings.Contains(iface.Interface, ":") {
return fmt.Errorf("interface[%d] %q: virtual interfaces (e.g. eth0:0) not supported; use the physical option instead", i, iface.Interface)
}
if iface.Zone != "" {
if iface.Zone == fwZone {
return fmt.Errorf("interface[%d] %q: firewall zone must not be listed in interfaces", i, iface.Interface)
}
if _, ok := c.Zones[iface.Zone]; !ok {
return fmt.Errorf("interface[%d] %q: zone %q not defined", i, iface.Interface, iface.Zone)
}
}
if iface.Options.Unmanaged && iface.Zone != "" {
return fmt.Errorf("interface[%d] %q: unmanaged interfaces must have an empty zone", i, iface.Interface)
}
if iface.Options.Optional && iface.Options.Required {
return fmt.Errorf("interface[%d] %q: optional and required are mutually exclusive", i, iface.Interface)
}
if seen[iface.Interface] {
return fmt.Errorf("interface[%d]: duplicate interface %q", i, iface.Interface)
}
seen[iface.Interface] = true
}
return nil
}