mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-16 19:49:56 +00:00
94 lines
3.2 KiB
Go
94 lines
3.2 KiB
Go
package iptables
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/coreos/go-iptables/iptables"
|
|
"github.com/hashicorp/go-multierror"
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
nberrors "github.com/netbirdio/netbird/client/errors"
|
|
)
|
|
|
|
// InterfaceAllower opens the NetBird interface on the iptables filter INPUT
|
|
// chain so the host firewall doesn't drop traffic the userspace firewall
|
|
// handles. It is the fallback used when nftables is unavailable (an
|
|
// iptables-legacy host).
|
|
//
|
|
// It opens INPUT only: the userspace router never forwards in the kernel.
|
|
// firewalld trust is handled by the uspfilter manager, not here.
|
|
type InterfaceAllower struct {
|
|
ifaceName string
|
|
ipt4 *iptables.IPTables
|
|
// ipt6 is nil when the interface has no IPv6 overlay address.
|
|
ipt6 *iptables.IPTables
|
|
}
|
|
|
|
// NewInterfaceAllower builds an iptables allower for the interface. It returns
|
|
// an error when iptables is unavailable, so the caller can fall back to
|
|
// firewalld trust.
|
|
func NewInterfaceAllower(wgIface iFaceMapper) (*InterfaceAllower, error) {
|
|
ipt4, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("iptables not available: %w", err)
|
|
}
|
|
if _, err := ipt4.ListChains(tableFilter); err != nil {
|
|
return nil, fmt.Errorf("iptables filter table not available: %w", err)
|
|
}
|
|
|
|
a := &InterfaceAllower{ifaceName: wgIface.Name(), ipt4: ipt4}
|
|
|
|
// Missing v6 must not break the v4 path: open v4 only and continue.
|
|
if wgIface.Address().HasIPv6() {
|
|
ipt6, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
|
if err != nil {
|
|
log.Warnf("ip6tables not available, opening interface on v4 only: %v", err)
|
|
} else if _, err := ipt6.ListChains(tableFilter); err != nil {
|
|
log.Warnf("ip6tables filter table not available, opening interface on v4 only: %v", err)
|
|
} else {
|
|
a.ipt6 = ipt6
|
|
}
|
|
}
|
|
|
|
return a, nil
|
|
}
|
|
|
|
// Apply inserts the interface accept rule on the filter INPUT chain. It removes
|
|
// any stale rule first so an unclean exit (e.g. SIGKILL, where Close never ran)
|
|
// is recovered deterministically rather than accumulating duplicates.
|
|
func (a *InterfaceAllower) Apply() error {
|
|
var merr *multierror.Error
|
|
for _, ipt := range a.clients() {
|
|
if err := ipt.DeleteIfExists(tableFilter, chainInput, a.inputRule()...); err != nil {
|
|
merr = multierror.Append(merr, fmt.Errorf("clean stale interface accept rule: %w", err))
|
|
}
|
|
if err := ipt.Insert(tableFilter, chainInput, 1, a.inputRule()...); err != nil {
|
|
merr = multierror.Append(merr, fmt.Errorf("add interface accept rule: %w", err))
|
|
}
|
|
}
|
|
return nberrors.FormatErrorOrNil(merr)
|
|
}
|
|
|
|
// Close removes the interface accept rule.
|
|
func (a *InterfaceAllower) Close() error {
|
|
var merr *multierror.Error
|
|
for _, ipt := range a.clients() {
|
|
if err := ipt.DeleteIfExists(tableFilter, chainInput, a.inputRule()...); err != nil {
|
|
merr = multierror.Append(merr, fmt.Errorf("remove interface accept rule: %w", err))
|
|
}
|
|
}
|
|
return nberrors.FormatErrorOrNil(merr)
|
|
}
|
|
|
|
func (a *InterfaceAllower) inputRule() []string {
|
|
return []string{"-i", a.ifaceName, "-j", "ACCEPT"}
|
|
}
|
|
|
|
func (a *InterfaceAllower) clients() []*iptables.IPTables {
|
|
clients := []*iptables.IPTables{a.ipt4}
|
|
if a.ipt6 != nil {
|
|
clients = append(clients, a.ipt6)
|
|
}
|
|
return clients
|
|
}
|