mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-24 17:13:30 +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
3
.gitignore
vendored
3
.gitignore
vendored
@ -10,4 +10,5 @@ infrastructure_files/management.json
|
|||||||
infrastructure_files/docker-compose.yml
|
infrastructure_files/docker-compose.yml
|
||||||
*.syso
|
*.syso
|
||||||
client/.distfiles/
|
client/.distfiles/
|
||||||
infrastructure_files/setup.env
|
infrastructure_files/setup.env
|
||||||
|
.vscode
|
||||||
|
@ -3,6 +3,9 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/ssh"
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
mgm "github.com/netbirdio/netbird/management/client"
|
mgm "github.com/netbirdio/netbird/management/client"
|
||||||
@ -11,8 +14,6 @@ import (
|
|||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var managementURLDefault *url.URL
|
var managementURLDefault *url.URL
|
||||||
@ -40,7 +41,24 @@ type Config struct {
|
|||||||
WgPort int
|
WgPort int
|
||||||
IFaceBlackList []string
|
IFaceBlackList []string
|
||||||
// SSHKey is a private SSH key in a PEM format
|
// 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
|
// createNewConfig creates a new config generating a new Wireguard key and saving to file
|
||||||
|
@ -3,11 +3,12 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/netbirdio/netbird/client/ssh"
|
|
||||||
nbStatus "github.com/netbirdio/netbird/client/status"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/client/ssh"
|
||||||
|
nbStatus "github.com/netbirdio/netbird/client/status"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/system"
|
"github.com/netbirdio/netbird/client/system"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
@ -190,6 +191,7 @@ func createEngineConfig(key wgtypes.Key, config *Config, peerConfig *mgmProto.Pe
|
|||||||
WgPrivateKey: key,
|
WgPrivateKey: key,
|
||||||
WgPort: config.WgPort,
|
WgPort: config.WgPort,
|
||||||
SSHKey: []byte(config.SSHKey),
|
SSHKey: []byte(config.SSHKey),
|
||||||
|
NATExternalIPs: config.NATExternalIPs,
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.PreSharedKey != "" {
|
if config.PreSharedKey != "" {
|
||||||
|
@ -3,12 +3,6 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"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"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@ -18,6 +12,13 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"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/peer"
|
||||||
"github.com/netbirdio/netbird/client/internal/proxy"
|
"github.com/netbirdio/netbird/client/internal/proxy"
|
||||||
"github.com/netbirdio/netbird/iface"
|
"github.com/netbirdio/netbird/iface"
|
||||||
@ -66,6 +67,8 @@ type EngineConfig struct {
|
|||||||
|
|
||||||
// SSHKey is a private SSH key in a PEM format
|
// SSHKey is a private SSH key in a PEM format
|
||||||
SSHKey []byte
|
SSHKey []byte
|
||||||
|
|
||||||
|
NATExternalIPs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Engine is a mechanism responsible for reacting on Signal and Management stream events and managing connections to the remote peers.
|
// 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,
|
UDPMuxSrflx: e.udpMuxSrflx,
|
||||||
ProxyConfig: proxyConfig,
|
ProxyConfig: proxyConfig,
|
||||||
LocalWgPort: e.config.WgPort,
|
LocalWgPort: e.config.WgPort,
|
||||||
|
NATExternalIPs: e.parseNATExternalIPMappings(),
|
||||||
}
|
}
|
||||||
|
|
||||||
peerConn, err := peer.NewConn(config, e.statusRecorder)
|
peerConn, err := peer.NewConn(config, e.statusRecorder)
|
||||||
@ -918,3 +922,77 @@ func (e *Engine) receiveSignalEvents() {
|
|||||||
|
|
||||||
e.signal.WaitStreamConnected()
|
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 (
|
import (
|
||||||
"context"
|
"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"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/proxy"
|
"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"
|
"github.com/pion/ice/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConnConfig is a peer Connection configuration
|
// ConnConfig is a peer Connection configuration
|
||||||
@ -39,6 +39,8 @@ type ConnConfig struct {
|
|||||||
UDPMuxSrflx ice.UniversalUDPMux
|
UDPMuxSrflx ice.UniversalUDPMux
|
||||||
|
|
||||||
LocalWgPort int
|
LocalWgPort int
|
||||||
|
|
||||||
|
NATExternalIPs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// OfferAnswer represents a session establishment offer or answer
|
// OfferAnswer represents a session establishment offer or answer
|
||||||
@ -152,6 +154,7 @@ func (conn *Conn) reCreateAgent() error {
|
|||||||
InterfaceFilter: interfaceFilter(conn.config.InterfaceBlackList),
|
InterfaceFilter: interfaceFilter(conn.config.InterfaceBlackList),
|
||||||
UDPMux: conn.config.UDPMux,
|
UDPMux: conn.config.UDPMux,
|
||||||
UDPMuxSrflx: conn.config.UDPMuxSrflx,
|
UDPMuxSrflx: conn.config.UDPMuxSrflx,
|
||||||
|
NAT1To1IPs: conn.config.NATExternalIPs,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -284,7 +287,7 @@ func (conn *Conn) Open() error {
|
|||||||
host, _, _ := net.SplitHostPort(remoteConn.LocalAddr().String())
|
host, _, _ := net.SplitHostPort(remoteConn.LocalAddr().String())
|
||||||
rhost, _, _ := net.SplitHostPort(remoteConn.RemoteAddr().String())
|
rhost, _, _ := net.SplitHostPort(remoteConn.RemoteAddr().String())
|
||||||
// direct Wireguard connection
|
// 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 {
|
} else {
|
||||||
log.Infof("connected to peer %s [laddr <-> raddr] [%s <-> %s]", conn.config.Key, remoteConn.LocalAddr().String(), remoteConn.RemoteAddr().String())
|
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
|
// and then signals them to the remote peer
|
||||||
func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
func (conn *Conn) onICECandidate(candidate ice.Candidate) {
|
||||||
if candidate != nil {
|
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())
|
log.Debugf("discovered local candidate %s", candidate.String())
|
||||||
go func() {
|
go func() {
|
||||||
err := conn.signalCandidate(candidate)
|
err := conn.signalCandidate(candidate)
|
||||||
|
Loading…
Reference in New Issue
Block a user