Skip to content

Commit

Permalink
[client] Add block lan access flag for routers (#3171)
Browse files Browse the repository at this point in the history
  • Loading branch information
lixmal authored Jan 15, 2025
1 parent 5a82477 commit 78795a4
Show file tree
Hide file tree
Showing 9 changed files with 475 additions and 343 deletions.
2 changes: 2 additions & 0 deletions client/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (
extraIFaceBlackListFlag = "extra-iface-blacklist"
dnsRouteIntervalFlag = "dns-router-interval"
systemInfoFlag = "system-info"
blockLANAccessFlag = "block-lan-access"
)

var (
Expand Down Expand Up @@ -73,6 +74,7 @@ var (
anonymizeFlag bool
debugSystemInfoFlag bool
dnsRouteInterval time.Duration
blockLANAccess bool

rootCmd = &cobra.Command{
Use: "netbird",
Expand Down
9 changes: 9 additions & 0 deletions client/cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func init() {
)
upCmd.PersistentFlags().StringSliceVar(&extraIFaceBlackList, extraIFaceBlackListFlag, nil, "Extra list of default interfaces to ignore for listening")
upCmd.PersistentFlags().DurationVar(&dnsRouteInterval, dnsRouteIntervalFlag, time.Minute, "DNS route update interval")
upCmd.PersistentFlags().BoolVar(&blockLANAccess, blockLANAccessFlag, false, "Block access to local networks (LAN) when using this peer as a router or exit node")
}

func upFunc(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -160,6 +161,10 @@ func runInForegroundMode(ctx context.Context, cmd *cobra.Command) error {
ic.DisableFirewall = &disableFirewall
}

if cmd.Flag(blockLANAccessFlag).Changed {
ic.BlockLANAccess = &blockLANAccess
}

providedSetupKey, err := getSetupKey()
if err != nil {
return err
Expand Down Expand Up @@ -290,6 +295,10 @@ func runInDaemonMode(ctx context.Context, cmd *cobra.Command) error {
loginRequest.DisableFirewall = &disableFirewall
}

if cmd.Flag(blockLANAccessFlag).Changed {
loginRequest.BlockLanAccess = &blockLANAccess
}

var loginErr error

var loginResp *proto.LoginResponse
Expand Down
14 changes: 14 additions & 0 deletions client/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ type ConfigInput struct {
DisableServerRoutes *bool
DisableDNS *bool
DisableFirewall *bool

BlockLANAccess *bool
}

// Config Configuration type
Expand All @@ -89,6 +91,8 @@ type Config struct {
DisableDNS bool
DisableFirewall bool

BlockLANAccess bool

// SSHKey is a private SSH key in a PEM format
SSHKey string

Expand Down Expand Up @@ -455,6 +459,16 @@ func (config *Config) apply(input ConfigInput) (updated bool, err error) {
updated = true
}

if input.BlockLANAccess != nil && *input.BlockLANAccess != config.BlockLANAccess {
if *input.BlockLANAccess {
log.Infof("blocking LAN access")
} else {
log.Infof("allowing LAN access")
}
config.BlockLANAccess = *input.BlockLANAccess
updated = true
}

if input.ClientCertKeyPath != "" {
config.ClientCertKeyPath = input.ClientCertKeyPath
updated = true
Expand Down
2 changes: 2 additions & 0 deletions client/internal/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe
DisableServerRoutes: config.DisableServerRoutes,
DisableDNS: config.DisableDNS,
DisableFirewall: config.DisableFirewall,

BlockLANAccess: config.BlockLANAccess,
}

if config.PreSharedKey != "" {
Expand Down
79 changes: 79 additions & 0 deletions client/internal/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ import (
"sync/atomic"
"time"

"github.com/hashicorp/go-multierror"
"github.com/pion/ice/v3"
"github.com/pion/stun/v2"
log "github.com/sirupsen/logrus"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/protobuf/proto"

nberrors "github.com/netbirdio/netbird/client/errors"
"github.com/netbirdio/netbird/client/firewall"
"github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/iface"
Expand Down Expand Up @@ -114,6 +116,8 @@ type EngineConfig struct {
DisableServerRoutes bool
DisableDNS bool
DisableFirewall bool

BlockLANAccess bool
}

// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
Expand Down Expand Up @@ -482,6 +486,10 @@ func (e *Engine) initFirewall() error {
}
}

if e.config.BlockLANAccess {
e.blockLanAccess()
}

if e.rpManager == nil || !e.config.RosenpassEnabled {
return nil
}
Expand All @@ -508,6 +516,35 @@ func (e *Engine) initFirewall() error {
return nil
}

func (e *Engine) blockLanAccess() {
var merr *multierror.Error

// TODO: keep this updated
toBlock, err := getInterfacePrefixes()
if err != nil {
merr = multierror.Append(merr, fmt.Errorf("get local addresses: %w", err))
}

log.Infof("blocking route LAN access for networks: %v", toBlock)
v4 := netip.PrefixFrom(netip.IPv4Unspecified(), 0)
for _, network := range toBlock {
if _, err := e.firewall.AddRouteFiltering(
[]netip.Prefix{v4},
network,
manager.ProtocolALL,
nil,
nil,
manager.ActionDrop,
); err != nil {
merr = multierror.Append(merr, fmt.Errorf("add fw rule for network %s: %w", network, err))
}
}

if merr != nil {
log.Warnf("encountered errors blocking IPs to block LAN access: %v", nberrors.FormatErrorOrNil(merr))
}
}

// modifyPeers updates peers that have been modified (e.g. IP address has been changed).
// It closes the existing connection, removes it from the peerConns map, and creates a new one.
func (e *Engine) modifyPeers(peersUpdate []*mgmProto.RemotePeerConfig) error {
Expand Down Expand Up @@ -1689,3 +1726,45 @@ func isChecksEqual(checks []*mgmProto.Checks, oChecks []*mgmProto.Checks) bool {
return slices.Equal(checks.Files, oChecks.Files)
})
}

func getInterfacePrefixes() ([]netip.Prefix, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, fmt.Errorf("get interfaces: %w", err)
}

var prefixes []netip.Prefix
var merr *multierror.Error

for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
merr = multierror.Append(merr, fmt.Errorf("get addresses for interface %s: %w", iface.Name, err))
continue
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
merr = multierror.Append(merr, fmt.Errorf("cast address to IPNet: %v", addr))
continue
}
addr, ok := netip.AddrFromSlice(ipNet.IP)
if !ok {
merr = multierror.Append(merr, fmt.Errorf("cast IPNet to netip.Addr: %v", ipNet.IP))
continue
}
ones, _ := ipNet.Mask.Size()
prefix := netip.PrefixFrom(addr.Unmap(), ones).Masked()
ip := prefix.Addr()

// TODO: add IPv6
if !ip.Is4() || ip.IsLoopback() || ip.IsMulticast() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
continue
}

prefixes = append(prefixes, prefix)
}
}

return prefixes, nberrors.FormatErrorOrNil(merr)
}
Loading

0 comments on commit 78795a4

Please sign in to comment.