package iptables import ( "context" "fmt" "net" "net/netip" "sync" "github.com/coreos/go-iptables/iptables" "github.com/hashicorp/go-multierror" log "github.com/sirupsen/logrus" nberrors "github.com/netbirdio/netbird/client/errors" firewall "github.com/netbirdio/netbird/client/firewall/manager" "github.com/netbirdio/netbird/client/iface" "github.com/netbirdio/netbird/client/internal/statemanager" ) // Manager of iptables firewall type Manager struct { mutex sync.Mutex wgIface iFaceMapper ipv4Client *iptables.IPTables aclMgr *aclManager router *router } // iFaceMapper defines subset methods of interface required for manager type iFaceMapper interface { Name() string Address() iface.WGAddress IsUserspaceBind() bool } // Create iptables firewall manager func Create(wgIface iFaceMapper) (*Manager, error) { iptablesClient, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) if err != nil { return nil, fmt.Errorf("init iptables: %w", err) } m := &Manager{ wgIface: wgIface, ipv4Client: iptablesClient, } m.router, err = newRouter(iptablesClient, wgIface) if err != nil { return nil, fmt.Errorf("create router: %w", err) } m.aclMgr, err = newAclManager(iptablesClient, wgIface, chainRTFWD) if err != nil { return nil, fmt.Errorf("create acl manager: %w", err) } return m, nil } func (m *Manager) Init(stateManager *statemanager.Manager) error { state := &ShutdownState{ InterfaceState: &InterfaceState{ NameStr: m.wgIface.Name(), WGAddress: m.wgIface.Address(), UserspaceBind: m.wgIface.IsUserspaceBind(), }, } stateManager.RegisterState(state) if err := stateManager.UpdateState(state); err != nil { log.Errorf("failed to update state: %v", err) } if err := m.router.init(stateManager); err != nil { return fmt.Errorf("router init: %w", err) } if err := m.aclMgr.init(stateManager); err != nil { // TODO: cleanup router return fmt.Errorf("acl manager init: %w", err) } // persist early to ensure cleanup of chains if err := stateManager.PersistState(context.Background()); err != nil { log.Errorf("failed to persist state: %v", err) } return nil } // AddPeerFiltering adds a rule to the firewall // // Comment will be ignored because some system this feature is not supported func (m *Manager) AddPeerFiltering( ip net.IP, protocol firewall.Protocol, sPort *firewall.Port, dPort *firewall.Port, direction firewall.RuleDirection, action firewall.Action, ipsetName string, comment string, ) ([]firewall.Rule, error) { m.mutex.Lock() defer m.mutex.Unlock() return m.aclMgr.AddPeerFiltering(ip, protocol, sPort, dPort, direction, action, ipsetName) } func (m *Manager) AddRouteFiltering( sources []netip.Prefix, destination netip.Prefix, proto firewall.Protocol, sPort *firewall.Port, dPort *firewall.Port, action firewall.Action, ) (firewall.Rule, error) { m.mutex.Lock() defer m.mutex.Unlock() if !destination.Addr().Is4() { return nil, fmt.Errorf("unsupported IP version: %s", destination.Addr().String()) } return m.router.AddRouteFiltering(sources, destination, proto, sPort, dPort, action) } // DeletePeerRule from the firewall by rule definition func (m *Manager) DeletePeerRule(rule firewall.Rule) error { m.mutex.Lock() defer m.mutex.Unlock() return m.aclMgr.DeletePeerRule(rule) } func (m *Manager) DeleteRouteRule(rule firewall.Rule) error { m.mutex.Lock() defer m.mutex.Unlock() return m.router.DeleteRouteRule(rule) } func (m *Manager) IsServerRouteSupported() bool { return true } func (m *Manager) AddNatRule(pair firewall.RouterPair) error { m.mutex.Lock() defer m.mutex.Unlock() return m.router.AddNatRule(pair) } func (m *Manager) RemoveNatRule(pair firewall.RouterPair) error { m.mutex.Lock() defer m.mutex.Unlock() return m.router.RemoveNatRule(pair) } func (m *Manager) SetLegacyManagement(isLegacy bool) error { return firewall.SetLegacyManagement(m.router, isLegacy) } // Reset firewall to the default state func (m *Manager) Reset(stateManager *statemanager.Manager) error { m.mutex.Lock() defer m.mutex.Unlock() var merr *multierror.Error if err := m.aclMgr.Reset(); err != nil { merr = multierror.Append(merr, fmt.Errorf("reset acl manager: %w", err)) } if err := m.router.Reset(); err != nil { merr = multierror.Append(merr, fmt.Errorf("reset router: %w", err)) } // attempt to delete state only if all other operations succeeded if merr == nil { if err := stateManager.DeleteState(&ShutdownState{}); err != nil { merr = multierror.Append(merr, fmt.Errorf("delete state: %w", err)) } } return nberrors.FormatErrorOrNil(merr) } // AllowNetbird allows netbird interface traffic func (m *Manager) AllowNetbird() error { if !m.wgIface.IsUserspaceBind() { return nil } _, err := m.AddPeerFiltering( net.ParseIP("0.0.0.0"), "all", nil, nil, firewall.RuleDirectionIN, firewall.ActionAccept, "", "", ) if err != nil { return fmt.Errorf("failed to allow netbird interface traffic: %w", err) } _, err = m.AddPeerFiltering( net.ParseIP("0.0.0.0"), "all", nil, nil, firewall.RuleDirectionOUT, firewall.ActionAccept, "", "", ) return err } // Flush doesn't need to be implemented for this manager func (m *Manager) Flush() error { return nil } func getConntrackEstablished() []string { return []string{"-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"} }