mirror of
https://github.com/netbirdio/netbird.git
synced 2025-02-04 20:39:35 +01:00
ad9f044aad
- Add stateful firewall functionality for UDP/TCP/ICMP in userspace firewalll - Removes all egress drop rules/filters, still needs refactoring so we don't add output rules to any chains/filters. - on Linux, if the OUTPUT policy is DROP then we don't do anything about it (no extra allow rules). This is up to the user, if they don't want anything leaving their machine they'll have to manage these rules explicitly.
221 lines
5.2 KiB
Go
221 lines
5.2 KiB
Go
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
|
|
go func() {
|
|
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("allow netbird interface traffic: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 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"}
|
|
}
|