mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 08:16:39 +00:00
Add IPv6 support to SSH server, client config, and netflow logger
This commit is contained in:
@@ -41,6 +41,14 @@ func (e *Engine) setupSSHPortRedirection() error {
|
||||
}
|
||||
log.Infof("SSH port redirection enabled: %s:22 -> %s:22022", localAddr, localAddr)
|
||||
|
||||
if v6 := e.wgInterface.Address().IPv6; v6.IsValid() {
|
||||
if err := e.firewall.AddInboundDNAT(v6, firewallManager.ProtocolTCP, 22, 22022); err != nil {
|
||||
log.Warnf("failed to add IPv6 SSH port redirection: %v", err)
|
||||
} else {
|
||||
log.Infof("SSH port redirection enabled: [%s]:22 -> [%s]:22022", v6, v6)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -137,12 +145,13 @@ func (e *Engine) extractPeerSSHInfo(remotePeers []*mgmProto.RemotePeerConfig) []
|
||||
continue
|
||||
}
|
||||
|
||||
peerIP := e.extractPeerIP(peerConfig)
|
||||
peerIP, peerIPv6 := e.extractPeerIPs(peerConfig)
|
||||
hostname := e.extractHostname(peerConfig)
|
||||
|
||||
peerInfo = append(peerInfo, sshconfig.PeerSSHInfo{
|
||||
Hostname: hostname,
|
||||
IP: peerIP,
|
||||
IPv6: peerIPv6,
|
||||
FQDN: peerConfig.GetFqdn(),
|
||||
})
|
||||
}
|
||||
@@ -150,16 +159,26 @@ func (e *Engine) extractPeerSSHInfo(remotePeers []*mgmProto.RemotePeerConfig) []
|
||||
return peerInfo
|
||||
}
|
||||
|
||||
// extractPeerIP extracts IP address from peer's allowed IPs
|
||||
func (e *Engine) extractPeerIP(peerConfig *mgmProto.RemotePeerConfig) string {
|
||||
if len(peerConfig.GetAllowedIps()) == 0 {
|
||||
return ""
|
||||
// extractPeerIPs extracts IPv4 and IPv6 overlay addresses from peer's allowed IPs.
|
||||
// Only considers host routes (/32, /128) within the overlay networks to avoid
|
||||
// picking up routed prefixes or static routes like 2620:fe::fe/128.
|
||||
func (e *Engine) extractPeerIPs(peerConfig *mgmProto.RemotePeerConfig) (v4, v6 netip.Addr) {
|
||||
wgAddr := e.wgInterface.Address()
|
||||
for _, allowedIP := range peerConfig.GetAllowedIps() {
|
||||
prefix, err := netip.ParsePrefix(allowedIP)
|
||||
if err != nil {
|
||||
log.Warnf("failed to parse AllowedIP %q: %v", allowedIP, err)
|
||||
continue
|
||||
}
|
||||
addr := prefix.Addr().Unmap()
|
||||
switch {
|
||||
case addr.Is4() && prefix.Bits() == 32 && wgAddr.Network.Contains(addr) && !v4.IsValid():
|
||||
v4 = addr
|
||||
case addr.Is6() && prefix.Bits() == 128 && wgAddr.IPv6Net.IsValid() && wgAddr.IPv6Net.Contains(addr) && !v6.IsValid():
|
||||
v6 = addr
|
||||
}
|
||||
}
|
||||
|
||||
if prefix, err := netip.ParsePrefix(peerConfig.GetAllowedIps()[0]); err == nil {
|
||||
return prefix.Addr().String()
|
||||
}
|
||||
return ""
|
||||
return v4, v6
|
||||
}
|
||||
|
||||
// extractHostname extracts short hostname from FQDN
|
||||
@@ -208,7 +227,7 @@ func (e *Engine) GetPeerSSHKey(peerAddress string) ([]byte, bool) {
|
||||
|
||||
fullStatus := statusRecorder.GetFullStatus()
|
||||
for _, peerState := range fullStatus.Peers {
|
||||
if peerState.IP == peerAddress || peerState.FQDN == peerAddress {
|
||||
if peerState.IP == peerAddress || peerState.FQDN == peerAddress || peerState.IPv6 == peerAddress {
|
||||
if len(peerState.SSHHostKey) > 0 {
|
||||
return peerState.SSHHostKey, true
|
||||
}
|
||||
@@ -262,6 +281,13 @@ func (e *Engine) startSSHServer(jwtConfig *sshserver.JWTConfig) error {
|
||||
return fmt.Errorf("start SSH server: %w", err)
|
||||
}
|
||||
|
||||
if v6 := wgAddr.IPv6; v6.IsValid() {
|
||||
v6Addr := netip.AddrPortFrom(v6, sshserver.InternalSSHPort)
|
||||
if err := server.AddListener(e.ctx, v6Addr); err != nil {
|
||||
log.Warnf("failed to add IPv6 SSH listener: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
e.sshServer = server
|
||||
|
||||
if netstackNet := e.wgInterface.GetNet(); netstackNet != nil {
|
||||
@@ -330,6 +356,12 @@ func (e *Engine) cleanupSSHPortRedirection() error {
|
||||
}
|
||||
log.Debugf("SSH port redirection removed: %s:22 -> %s:22022", localAddr, localAddr)
|
||||
|
||||
if v6 := e.wgInterface.Address().IPv6; v6.IsValid() {
|
||||
if err := e.firewall.RemoveInboundDNAT(v6, firewallManager.ProtocolTCP, 22, 22022); err != nil {
|
||||
log.Debugf("failed to remove IPv6 SSH port redirection: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ func (c *ConnTrack) handleEvent(event nfct.Event) {
|
||||
case nftypes.TCP, nftypes.UDP, nftypes.SCTP:
|
||||
srcPort = flow.TupleOrig.Proto.SourcePort
|
||||
dstPort = flow.TupleOrig.Proto.DestinationPort
|
||||
case nftypes.ICMP:
|
||||
case nftypes.ICMP, nftypes.ICMPv6:
|
||||
icmpType = flow.TupleOrig.Proto.ICMPType
|
||||
icmpCode = flow.TupleOrig.Proto.ICMPCode
|
||||
}
|
||||
@@ -231,8 +231,14 @@ func (c *ConnTrack) relevantFlow(mark uint32, srcIP, dstIP netip.Addr) bool {
|
||||
}
|
||||
|
||||
// fallback if mark rules are not in place
|
||||
wgnet := c.iface.Address().Network
|
||||
return wgnet.Contains(srcIP) || wgnet.Contains(dstIP)
|
||||
addr := c.iface.Address()
|
||||
if addr.Network.Contains(srcIP) || addr.Network.Contains(dstIP) {
|
||||
return true
|
||||
}
|
||||
if addr.IPv6Net.IsValid() {
|
||||
return addr.IPv6Net.Contains(srcIP) || addr.IPv6Net.Contains(dstIP)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// mapRxPackets maps packet counts to RX based on flow direction
|
||||
@@ -291,17 +297,16 @@ func (c *ConnTrack) inferDirection(mark uint32, srcIP, dstIP netip.Addr) nftypes
|
||||
}
|
||||
|
||||
// fallback if marks are not set
|
||||
wgaddr := c.iface.Address().IP
|
||||
wgnetwork := c.iface.Address().Network
|
||||
addr := c.iface.Address()
|
||||
switch {
|
||||
case wgaddr == srcIP:
|
||||
case addr.IP == srcIP || (addr.IPv6.IsValid() && addr.IPv6 == srcIP):
|
||||
return nftypes.Egress
|
||||
case wgaddr == dstIP:
|
||||
case addr.IP == dstIP || (addr.IPv6.IsValid() && addr.IPv6 == dstIP):
|
||||
return nftypes.Ingress
|
||||
case wgnetwork.Contains(srcIP):
|
||||
case addr.Network.Contains(srcIP) || (addr.IPv6Net.IsValid() && addr.IPv6Net.Contains(srcIP)):
|
||||
// netbird network -> resource network
|
||||
return nftypes.Ingress
|
||||
case wgnetwork.Contains(dstIP):
|
||||
case addr.Network.Contains(dstIP) || (addr.IPv6Net.IsValid() && addr.IPv6Net.Contains(dstIP)):
|
||||
// resource network -> netbird network
|
||||
return nftypes.Egress
|
||||
}
|
||||
|
||||
@@ -24,15 +24,17 @@ type Logger struct {
|
||||
cancel context.CancelFunc
|
||||
statusRecorder *peer.Status
|
||||
wgIfaceNet netip.Prefix
|
||||
wgIfaceNetV6 netip.Prefix
|
||||
dnsCollection atomic.Bool
|
||||
exitNodeCollection atomic.Bool
|
||||
Store types.Store
|
||||
}
|
||||
|
||||
func New(statusRecorder *peer.Status, wgIfaceIPNet netip.Prefix) *Logger {
|
||||
func New(statusRecorder *peer.Status, wgIfaceIPNet, wgIfaceIPNetV6 netip.Prefix) *Logger {
|
||||
return &Logger{
|
||||
statusRecorder: statusRecorder,
|
||||
wgIfaceNet: wgIfaceIPNet,
|
||||
wgIfaceNetV6: wgIfaceIPNetV6,
|
||||
Store: store.NewMemoryStore(),
|
||||
}
|
||||
}
|
||||
@@ -88,11 +90,11 @@ func (l *Logger) startReceiver() {
|
||||
var isSrcExitNode bool
|
||||
var isDestExitNode bool
|
||||
|
||||
if !l.wgIfaceNet.Contains(event.SourceIP) {
|
||||
if !l.isOverlayIP(event.SourceIP) {
|
||||
event.SourceResourceID, isSrcExitNode = l.statusRecorder.CheckRoutes(event.SourceIP)
|
||||
}
|
||||
|
||||
if !l.wgIfaceNet.Contains(event.DestIP) {
|
||||
if !l.isOverlayIP(event.DestIP) {
|
||||
event.DestResourceID, isDestExitNode = l.statusRecorder.CheckRoutes(event.DestIP)
|
||||
}
|
||||
|
||||
@@ -136,6 +138,10 @@ func (l *Logger) UpdateConfig(dnsCollection, exitNodeCollection bool) {
|
||||
l.exitNodeCollection.Store(exitNodeCollection)
|
||||
}
|
||||
|
||||
func (l *Logger) isOverlayIP(ip netip.Addr) bool {
|
||||
return l.wgIfaceNet.Contains(ip) || (l.wgIfaceNetV6.IsValid() && l.wgIfaceNetV6.Contains(ip))
|
||||
}
|
||||
|
||||
func (l *Logger) shouldStore(event *types.EventFields, isExitNode bool) bool {
|
||||
// check dns collection
|
||||
if !l.dnsCollection.Load() && event.Protocol == types.UDP &&
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
logger := logger.New(nil, netip.Prefix{})
|
||||
logger := logger.New(nil, netip.Prefix{}, netip.Prefix{})
|
||||
logger.Enable()
|
||||
|
||||
event := types.EventFields{
|
||||
|
||||
@@ -35,11 +35,12 @@ type Manager struct {
|
||||
|
||||
// NewManager creates a new netflow manager
|
||||
func NewManager(iface nftypes.IFaceMapper, publicKey []byte, statusRecorder *peer.Status) *Manager {
|
||||
var prefix netip.Prefix
|
||||
var prefix, prefixV6 netip.Prefix
|
||||
if iface != nil {
|
||||
prefix = iface.Address().Network
|
||||
prefixV6 = iface.Address().IPv6Net
|
||||
}
|
||||
flowLogger := logger.New(statusRecorder, prefix)
|
||||
flowLogger := logger.New(statusRecorder, prefix, prefixV6)
|
||||
|
||||
var ct nftypes.ConnTracker
|
||||
if runtime.GOOS == "linux" && iface != nil && !iface.IsUserspaceBind() {
|
||||
@@ -269,7 +270,7 @@ func toProtoEvent(publicKey []byte, event *nftypes.Event) *proto.FlowEvent {
|
||||
},
|
||||
}
|
||||
|
||||
if event.Protocol == nftypes.ICMP {
|
||||
if event.Protocol == nftypes.ICMP || event.Protocol == nftypes.ICMPv6 {
|
||||
protoEvent.FlowFields.ConnectionInfo = &proto.FlowFields_IcmpInfo{
|
||||
IcmpInfo: &proto.ICMPInfo{
|
||||
IcmpType: uint32(event.ICMPType),
|
||||
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
ICMP = Protocol(1)
|
||||
TCP = Protocol(6)
|
||||
UDP = Protocol(17)
|
||||
ICMPv6 = Protocol(58)
|
||||
SCTP = Protocol(132)
|
||||
)
|
||||
|
||||
@@ -30,6 +31,8 @@ func (p Protocol) String() string {
|
||||
return "TCP"
|
||||
case 17:
|
||||
return "UDP"
|
||||
case 58:
|
||||
return "ICMPv6"
|
||||
case 132:
|
||||
return "SCTP"
|
||||
default:
|
||||
|
||||
@@ -75,7 +75,7 @@ func (m *Manager) addPeer(rosenpassPubKey []byte, rosenpassAddr string, wireGuar
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse rosenpass address: %w", err)
|
||||
}
|
||||
peerAddr := fmt.Sprintf("%s:%s", wireGuardIP, strPort)
|
||||
peerAddr := net.JoinHostPort(wireGuardIP, strPort)
|
||||
if pcfg.Endpoint, err = net.ResolveUDPAddr("udp", peerAddr); err != nil {
|
||||
return fmt.Errorf("failed to resolve peer endpoint address: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user