mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-21 23:53:14 +01:00
External NAT IP mapping support (#487)
* External NAT IP mapping support * Ignore blacklisted interfaces, even if in user specified in mapping
This commit is contained in:
parent
53c532bbb4
commit
f604956246
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,3 +11,4 @@ infrastructure_files/docker-compose.yml
|
||||
*.syso
|
||||
client/.distfiles/
|
||||
infrastructure_files/setup.env
|
||||
.vscode
|
||||
|
@ -3,6 +3,9 @@ package internal
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/netbirdio/netbird/client/ssh"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
mgm "github.com/netbirdio/netbird/management/client"
|
||||
@ -11,8 +14,6 @@ import (
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
var managementURLDefault *url.URL
|
||||
@ -40,7 +41,24 @@ type Config struct {
|
||||
WgPort int
|
||||
IFaceBlackList []string
|
||||
// SSHKey is a private SSH key in a PEM format
|
||||
SSHKey string
|
||||
SSHKey string
|
||||
|
||||
// ExternalIP mappings, if different than the host interface IP
|
||||
//
|
||||
// External IP must not be behind a CGNAT and port-forwarding for incoming UDP packets from WgPort on ExternalIP
|
||||
// to WgPort on host interface IP must be present. This can take form of single port-forwarding rule, 1:1 DNAT
|
||||
// mapping ExternalIP to host interface IP, or a NAT DMZ to host interface IP.
|
||||
//
|
||||
// A single mapping will take the form of: external[/internal]
|
||||
// external (required): either the external IP address or "stun" to use STUN to determine the external IP address
|
||||
// internal (optional): either the internal/interface IP address or an interface name
|
||||
//
|
||||
// examples:
|
||||
// "12.34.56.78" => all interfaces IPs will be mapped to external IP of 12.34.56.78
|
||||
// "12.34.56.78/eth0" => IPv4 assigned to interface eth0 will be mapped to external IP of 12.34.56.78
|
||||
// "12.34.56.78/10.1.2.3" => interface IP 10.1.2.3 will be mapped to external IP of 12.34.56.78
|
||||
|
||||
NATExternalIPs []string
|
||||
}
|
||||
|
||||
// createNewConfig creates a new config generating a new Wireguard key and saving to file
|
||||
|
@ -3,11 +3,12 @@ package internal
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/client/ssh"
|
||||
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/netbirdio/netbird/client/ssh"
|
||||
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
@ -190,6 +191,7 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe
|
||||
WgPrivateKey: key,
|
||||
WgPort: config.WgPort,
|
||||
SSHKey: []byte(config.SSHKey),
|
||||
NATExternalIPs: config.NATExternalIPs,
|
||||
}
|
||||
|
||||
if config.PreSharedKey != "" {
|
||||
|
@ -3,12 +3,6 @@ package internal
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/netbirdio/netbird/client/internal/dns"
|
||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||
nbssh "github.com/netbirdio/netbird/client/ssh"
|
||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
@ -18,6 +12,13 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/dns"
|
||||
"github.com/netbirdio/netbird/client/internal/routemanager"
|
||||
nbssh "github.com/netbirdio/netbird/client/ssh"
|
||||
nbstatus "github.com/netbirdio/netbird/client/status"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/proxy"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
@ -66,6 +67,8 @@ type EngineConfig struct {
|
||||
|
||||
// SSHKey is a private SSH key in a PEM format
|
||||
SSHKey []byte
|
||||
|
||||
NATExternalIPs []string
|
||||
}
|
||||
|
||||
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
||||
@ -825,6 +828,7 @@ func (e Engine) createPeerConn(pubKey string, allowedIPs string) (*peer.Conn, er
|
||||
UDPMuxSrflx: e.udpMuxSrflx,
|
||||
ProxyConfig: proxyConfig,
|
||||
LocalWgPort: e.config.WgPort,
|
||||
NATExternalIPs: e.parseNATExternalIPMappings(),
|
||||
}
|
||||
|
||||
peerConn, err := peer.NewConn(config, e.statusRecorder)
|
||||
@ -918,3 +922,77 @@ func (e *Engine) receiveSignalEvents() {
|
||||
|
||||
e.signal.WaitStreamConnected()
|
||||
}
|
||||
|
||||
func (e* Engine) parseNATExternalIPMappings() []string {
|
||||
var mappedIPs []string
|
||||
var ignoredIFaces = make(map[string]interface{})
|
||||
for _, iFace := range(e.config.IFaceBlackList) {
|
||||
ignoredIFaces[iFace] = nil
|
||||
}
|
||||
for _, mapping := range e.config.NATExternalIPs {
|
||||
var external, internal string
|
||||
var externalIP, internalIP net.IP
|
||||
var err error
|
||||
split := strings.Split(mapping, "/")
|
||||
if len(split) > 2 {
|
||||
log.Warnf("ignoring invalid external mapping '%s', too many delimiters", mapping)
|
||||
break
|
||||
}
|
||||
if len(split) > 1 {
|
||||
internal = split[1]
|
||||
internalIP = net.ParseIP(internal)
|
||||
if internalIP == nil {
|
||||
// not a properly formatted IP address, maybe it's interface name?
|
||||
if _, present := ignoredIFaces[internal]; present {
|
||||
log.Warnf("internal interface '%s' in blacklist, ignoring external mapping '%s'", internal, mapping)
|
||||
break
|
||||
}
|
||||
internalIP, err = findIPFromInterfaceName(internal)
|
||||
if err != nil {
|
||||
log.Warnf("error finding interface IP for interface '%s', ignoring external mapping '%s': %v", internal, mapping, err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
external = split[0]
|
||||
externalIP = net.ParseIP(external)
|
||||
if externalIP == nil {
|
||||
log.Warnf("invalid external IP, ignoring external IP mapping '%s'", mapping)
|
||||
break
|
||||
}
|
||||
if externalIP != nil {
|
||||
mappedIP := externalIP.String()
|
||||
if internalIP != nil {
|
||||
mappedIP = mappedIP + "/" + internalIP.String()
|
||||
}
|
||||
mappedIPs = append(mappedIPs, mappedIP)
|
||||
log.Infof("parsed external IP mapping of '%s' as '%s'", mapping, mappedIP)
|
||||
}
|
||||
}
|
||||
if len(mappedIPs) != len(e.config.NATExternalIPs) {
|
||||
log.Warnf("one or more external IP mappings failed to parse, ignoring all mappings")
|
||||
return nil
|
||||
}
|
||||
return mappedIPs
|
||||
}
|
||||
|
||||
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
||||
iface, err := net.InterfaceByName(ifaceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return findIPFromInterface(iface)
|
||||
}
|
||||
|
||||
func findIPFromInterface(iface *net.Interface) (net.IP, error) {
|
||||
ifaceAddrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, addr := range ifaceAddrs {
|
||||
if ipv4Addr := addr.(*net.IPNet).IP.To4(); ipv4Addr != nil {
|
||||
return ipv4Addr, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("interface %s don't have an ipv4 address", iface.Name)
|
||||
}
|
@ -2,18 +2,18 @@ package peer
|
||||
|
||||
import (
|
||||
"context"
|
||||
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/proxy"
|
||||
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||
"github.com/netbirdio/netbird/client/system"
|
||||
"github.com/netbirdio/netbird/iface"
|
||||
"github.com/pion/ice/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
)
|
||||
|
||||
// ConnConfig is a peer Connection configuration
|
||||
@ -39,6 +39,8 @@ type ConnConfig struct {
|
||||
UDPMuxSrflx ice.UniversalUDPMux
|
||||
|
||||
LocalWgPort int
|
||||
|
||||
NATExternalIPs []string
|
||||
}
|
||||
|
||||
// OfferAnswer represents a session establishment offer or answer
|
||||
@ -152,6 +154,7 @@ func (conn *Conn) reCreateAgent() error {
|
||||
InterfaceFilter: interfaceFilter(conn.config.InterfaceBlackList),
|
||||
UDPMux: conn.config.UDPMux,
|
||||
UDPMuxSrflx: conn.config.UDPMuxSrflx,
|
||||
NAT1To1IPs: conn.config.NATExternalIPs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -284,7 +287,7 @@ func (conn *Conn) Open() error {
|
||||
host, _, _ := net.SplitHostPort(remoteConn.LocalAddr().String())
|
||||
rhost, _, _ := net.SplitHostPort(remoteConn.RemoteAddr().String())
|
||||
// direct Wireguard connection
|
||||
log.Infof("directly connected to peer %s [laddr <-> raddr] [%s:%d <-> %s:%d]", conn.config.Key, host, iface.DefaultWgPort, rhost, iface.DefaultWgPort)
|
||||
log.Infof("directly connected to peer %s [laddr <-> raddr] [%s:%d <-> %s:%d]", conn.config.Key, host, conn.config.LocalWgPort, rhost, remoteWgPort)
|
||||
} else {
|
||||
log.Infof("connected to peer %s [laddr <-> raddr] [%s <-> %s]", conn.config.Key, remoteConn.LocalAddr().String(), remoteConn.RemoteAddr().String())
|
||||
}
|
||||
@ -448,6 +451,7 @@ func (conn *Conn) SetSignalCandidate(handler func(candidate ice.Candidate) error
|
||||
// and then signals them to the remote peer
|
||||
func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
||||
if candidate != nil {
|
||||
// TODO: reported port is incorrect for CandidateTypeHost, makes understanding ICE use via logs confusing as port is ignored
|
||||
log.Debugf("discovered local candidate %s", candidate.String())
|
||||
go func() {
|
||||
err := conn.signalCandidate(candidate)
|
||||
|
Loading…
Reference in New Issue
Block a user