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 }