Skip to content

Commit 6215aac

Browse files
committed
Redirect dns forwarder port 5353 to new listening port 22054
1 parent 709e24e commit 6215aac

File tree

18 files changed

+1244
-95
lines changed

18 files changed

+1244
-95
lines changed

client/firewall/iptables/manager_linux.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,22 @@ func (m *Manager) UpdateSet(set firewall.Set, prefixes []netip.Prefix) error {
260260
return m.router.UpdateSet(set, prefixes)
261261
}
262262

263+
// AddInboundDNAT adds an inbound DNAT rule redirecting traffic from NetBird peers to local services
264+
func (m *Manager) AddInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, sourcePort, targetPort uint16) error {
265+
m.mutex.Lock()
266+
defer m.mutex.Unlock()
267+
268+
return m.router.AddInboundDNAT(localAddr, protocol, sourcePort, targetPort)
269+
}
270+
271+
// RemoveInboundDNAT removes inbound DNAT rule
272+
func (m *Manager) RemoveInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, sourcePort, targetPort uint16) error {
273+
m.mutex.Lock()
274+
defer m.mutex.Unlock()
275+
276+
return m.router.RemoveInboundDNAT(localAddr, protocol, sourcePort, targetPort)
277+
}
278+
263279
func getConntrackEstablished() []string {
264280
return []string{"-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}
265281
}

client/firewall/iptables/router_linux.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,54 @@ func (r *router) UpdateSet(set firewall.Set, prefixes []netip.Prefix) error {
880880
return nberrors.FormatErrorOrNil(merr)
881881
}
882882

883+
// AddInboundDNAT adds an inbound DNAT rule redirecting traffic from NetBird peers to local services
884+
func (r *router) AddInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, sourcePort, targetPort uint16) error {
885+
ruleID := fmt.Sprintf("inbound-dnat-%s-%s-%d-%d", localAddr.String(), protocol, sourcePort, targetPort)
886+
887+
if _, exists := r.rules[ruleID]; exists {
888+
return nil
889+
}
890+
891+
dnatRule := []string{
892+
"-i", r.wgIface.Name(),
893+
"-p", strings.ToLower(string(protocol)),
894+
"--dport", strconv.Itoa(int(sourcePort)),
895+
"-d", localAddr.String(),
896+
"-m", "addrtype", "--dst-type", "LOCAL",
897+
"-j", "DNAT",
898+
"--to-destination", ":" + strconv.Itoa(int(targetPort)),
899+
}
900+
901+
ruleInfo := ruleInfo{
902+
table: tableNat,
903+
chain: chainRTRDR,
904+
rule: dnatRule,
905+
}
906+
907+
if err := r.iptablesClient.Append(ruleInfo.table, ruleInfo.chain, ruleInfo.rule...); err != nil {
908+
return fmt.Errorf("add inbound DNAT rule: %w", err)
909+
}
910+
r.rules[ruleID] = ruleInfo.rule
911+
912+
r.updateState()
913+
return nil
914+
}
915+
916+
// RemoveInboundDNAT removes inbound DNAT rule
917+
func (r *router) RemoveInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, sourcePort, targetPort uint16) error {
918+
ruleID := fmt.Sprintf("inbound-dnat-%s-%s-%d-%d", localAddr.String(), protocol, sourcePort, targetPort)
919+
920+
if dnatRule, exists := r.rules[ruleID]; exists {
921+
if err := r.iptablesClient.Delete(tableNat, chainRTRDR, dnatRule...); err != nil {
922+
return fmt.Errorf("delete inbound DNAT rule: %w", err)
923+
}
924+
delete(r.rules, ruleID)
925+
}
926+
927+
r.updateState()
928+
return nil
929+
}
930+
883931
func applyPort(flag string, port *firewall.Port) []string {
884932
if port == nil {
885933
return nil

client/firewall/manager/firewall.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,20 @@ type Manager interface {
151151

152152
DisableRouting() error
153153

154-
// AddDNATRule adds a DNAT rule
154+
// AddDNATRule adds outbound DNAT rule for forwarding external traffic to the NetBird network.
155155
AddDNATRule(ForwardRule) (Rule, error)
156156

157-
// DeleteDNATRule deletes a DNAT rule
157+
// DeleteDNATRule deletes the outbound DNAT rule.
158158
DeleteDNATRule(Rule) error
159159

160160
// UpdateSet updates the set with the given prefixes
161161
UpdateSet(hash Set, prefixes []netip.Prefix) error
162+
163+
// AddInboundDNAT adds an inbound DNAT rule redirecting traffic from NetBird peers to local services
164+
AddInboundDNAT(localAddr netip.Addr, protocol Protocol, sourcePort, targetPort uint16) error
165+
166+
// RemoveInboundDNAT removes inbound DNAT rule
167+
RemoveInboundDNAT(localAddr netip.Addr, protocol Protocol, sourcePort, targetPort uint16) error
162168
}
163169

164170
func GenKey(format string, pair RouterPair) string {

client/firewall/nftables/manager_linux.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,22 @@ func (m *Manager) UpdateSet(set firewall.Set, prefixes []netip.Prefix) error {
376376
return m.router.UpdateSet(set, prefixes)
377377
}
378378

379+
// AddInboundDNAT adds an inbound DNAT rule redirecting traffic from NetBird peers to local services
380+
func (m *Manager) AddInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, sourcePort, targetPort uint16) error {
381+
m.mutex.Lock()
382+
defer m.mutex.Unlock()
383+
384+
return m.router.AddInboundDNAT(localAddr, protocol, sourcePort, targetPort)
385+
}
386+
387+
// RemoveInboundDNAT removes inbound DNAT rule
388+
func (m *Manager) RemoveInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, sourcePort, targetPort uint16) error {
389+
m.mutex.Lock()
390+
defer m.mutex.Unlock()
391+
392+
return m.router.RemoveInboundDNAT(localAddr, protocol, sourcePort, targetPort)
393+
}
394+
379395
func (m *Manager) createWorkTable() (*nftables.Table, error) {
380396
tables, err := m.rConn.ListTablesOfFamily(nftables.TableFamilyIPv4)
381397
if err != nil {

client/firewall/nftables/router_linux.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,103 @@ func (r *router) UpdateSet(set firewall.Set, prefixes []netip.Prefix) error {
13501350
return nil
13511351
}
13521352

1353+
// AddInboundDNAT adds an inbound DNAT rule redirecting traffic from NetBird peers to local services
1354+
func (r *router) AddInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, sourcePort, targetPort uint16) error {
1355+
ruleID := fmt.Sprintf("inbound-dnat-%s-%s-%d-%d", localAddr.String(), protocol, sourcePort, targetPort)
1356+
1357+
if _, exists := r.rules[ruleID]; exists {
1358+
return nil
1359+
}
1360+
1361+
protoNum, err := protoToInt(protocol)
1362+
if err != nil {
1363+
return fmt.Errorf("convert protocol to number: %w", err)
1364+
}
1365+
1366+
exprs := []expr.Any{
1367+
&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},
1368+
&expr.Cmp{
1369+
Op: expr.CmpOpEq,
1370+
Register: 1,
1371+
Data: ifname(r.wgIface.Name()),
1372+
},
1373+
&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 2},
1374+
&expr.Cmp{
1375+
Op: expr.CmpOpEq,
1376+
Register: 2,
1377+
Data: []byte{protoNum},
1378+
},
1379+
&expr.Payload{
1380+
DestRegister: 3,
1381+
Base: expr.PayloadBaseTransportHeader,
1382+
Offset: 2,
1383+
Len: 2,
1384+
},
1385+
&expr.Cmp{
1386+
Op: expr.CmpOpEq,
1387+
Register: 3,
1388+
Data: binaryutil.BigEndian.PutUint16(sourcePort),
1389+
},
1390+
}
1391+
1392+
exprs = append(exprs, applyPrefix(netip.PrefixFrom(localAddr, 32), false)...)
1393+
1394+
exprs = append(exprs,
1395+
&expr.Immediate{
1396+
Register: 1,
1397+
Data: localAddr.AsSlice(),
1398+
},
1399+
&expr.Immediate{
1400+
Register: 2,
1401+
Data: binaryutil.BigEndian.PutUint16(targetPort),
1402+
},
1403+
&expr.NAT{
1404+
Type: expr.NATTypeDestNAT,
1405+
Family: uint32(nftables.TableFamilyIPv4),
1406+
RegAddrMin: 1,
1407+
RegProtoMin: 2,
1408+
RegProtoMax: 0,
1409+
},
1410+
)
1411+
1412+
dnatRule := &nftables.Rule{
1413+
Table: r.workTable,
1414+
Chain: r.chains[chainNameRoutingRdr],
1415+
Exprs: exprs,
1416+
UserData: []byte(ruleID),
1417+
}
1418+
r.conn.AddRule(dnatRule)
1419+
1420+
if err := r.conn.Flush(); err != nil {
1421+
return fmt.Errorf("add inbound DNAT rule: %w", err)
1422+
}
1423+
1424+
r.rules[ruleID] = dnatRule
1425+
1426+
return nil
1427+
}
1428+
1429+
// RemoveInboundDNAT removes inbound DNAT rule
1430+
func (r *router) RemoveInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, sourcePort, targetPort uint16) error {
1431+
if err := r.refreshRulesMap(); err != nil {
1432+
return fmt.Errorf(refreshRulesMapError, err)
1433+
}
1434+
1435+
ruleID := fmt.Sprintf("inbound-dnat-%s-%s-%d-%d", localAddr.String(), protocol, sourcePort, targetPort)
1436+
1437+
if rule, exists := r.rules[ruleID]; exists {
1438+
if err := r.conn.DelRule(rule); err != nil {
1439+
return fmt.Errorf("delete inbound DNAT rule %s: %w", ruleID, err)
1440+
}
1441+
if err := r.conn.Flush(); err != nil {
1442+
return fmt.Errorf("flush delete inbound DNAT rule: %w", err)
1443+
}
1444+
delete(r.rules, ruleID)
1445+
}
1446+
1447+
return nil
1448+
}
1449+
13531450
// applyNetwork generates nftables expressions for networks (CIDR) or sets
13541451
func (r *router) applyNetwork(
13551452
network firewall.Network,

client/firewall/uspfilter/filter.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ type Manager struct {
109109
dnatMappings map[netip.Addr]netip.Addr
110110
dnatMutex sync.RWMutex
111111
dnatBiMap *biDNATMap
112+
113+
// Port-specific DNAT for SSH redirection
114+
portDNATEnabled atomic.Bool
115+
portDNATMap *portDNATMap
116+
portDNATMutex sync.RWMutex
117+
portNATTracker *portNATTracker
112118
}
113119

114120
// decoder for packages
@@ -196,6 +202,8 @@ func create(iface common.IFaceMapper, nativeFirewall firewall.Manager, disableSe
196202
netstack: netstack.IsEnabled(),
197203
localForwarding: enableLocalForwarding,
198204
dnatMappings: make(map[netip.Addr]netip.Addr),
205+
portDNATMap: &portDNATMap{rules: []portDNATRule{}},
206+
portNATTracker: newPortNATTracker(),
199207
}
200208
m.routingEnabled.Store(false)
201209

@@ -632,6 +640,7 @@ func (m *Manager) filterOutbound(packetData []byte, size int) bool {
632640

633641
m.trackOutbound(d, srcIP, dstIP, size)
634642
m.translateOutboundDNAT(packetData, d)
643+
m.translateOutboundPortReverse(packetData, d)
635644

636645
return false
637646
}
@@ -759,6 +768,15 @@ func (m *Manager) filterInbound(packetData []byte, size int) bool {
759768
return false
760769
}
761770

771+
if translated := m.translateInboundPortDNAT(packetData, d); translated {
772+
// Re-decode after port DNAT translation to update port information
773+
if err := d.parser.DecodeLayers(packetData, &d.decoded); err != nil {
774+
m.logger.Error1("Failed to re-decode packet after port DNAT: %v", err)
775+
return true
776+
}
777+
srcIP, dstIP = m.extractIPs(d)
778+
}
779+
762780
if translated := m.translateInboundReverse(packetData, d); translated {
763781
// Re-decode after translation to get original addresses
764782
if err := d.parser.DecodeLayers(packetData, &d.decoded); err != nil {

client/firewall/uspfilter/log/log.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ type logMessage struct {
5050
arg4 any
5151
arg5 any
5252
arg6 any
53+
arg7 any
54+
arg8 any
5355
}
5456

5557
// Logger is a high-performance, non-blocking logger
@@ -94,7 +96,6 @@ func (l *Logger) SetLevel(level Level) {
9496
log.Debugf("Set uspfilter logger loglevel to %v", levelStrings[level])
9597
}
9698

97-
9899
func (l *Logger) Error(format string) {
99100
if l.level.Load() >= uint32(LevelError) {
100101
select {
@@ -185,6 +186,15 @@ func (l *Logger) Debug2(format string, arg1, arg2 any) {
185186
}
186187
}
187188

189+
func (l *Logger) Debug3(format string, arg1, arg2, arg3 any) {
190+
if l.level.Load() >= uint32(LevelDebug) {
191+
select {
192+
case l.msgChannel <- logMessage{level: LevelDebug, format: format, arg1: arg1, arg2: arg2, arg3: arg3}:
193+
default:
194+
}
195+
}
196+
}
197+
188198
func (l *Logger) Trace1(format string, arg1 any) {
189199
if l.level.Load() >= uint32(LevelTrace) {
190200
select {
@@ -239,6 +249,16 @@ func (l *Logger) Trace6(format string, arg1, arg2, arg3, arg4, arg5, arg6 any) {
239249
}
240250
}
241251

252+
// Trace8 logs a trace message with 8 arguments (8 placeholder in format string)
253+
func (l *Logger) Trace8(format string, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 any) {
254+
if l.level.Load() >= uint32(LevelTrace) {
255+
select {
256+
case l.msgChannel <- logMessage{level: LevelTrace, format: format, arg1: arg1, arg2: arg2, arg3: arg3, arg4: arg4, arg5: arg5, arg6: arg6, arg7: arg7, arg8: arg8}:
257+
default:
258+
}
259+
}
260+
}
261+
242262
func (l *Logger) formatMessage(buf *[]byte, msg logMessage) {
243263
*buf = (*buf)[:0]
244264
*buf = time.Now().AppendFormat(*buf, "2006-01-02T15:04:05-07:00")
@@ -260,6 +280,12 @@ func (l *Logger) formatMessage(buf *[]byte, msg logMessage) {
260280
argCount++
261281
if msg.arg6 != nil {
262282
argCount++
283+
if msg.arg7 != nil {
284+
argCount++
285+
if msg.arg8 != nil {
286+
argCount++
287+
}
288+
}
263289
}
264290
}
265291
}
@@ -283,6 +309,10 @@ func (l *Logger) formatMessage(buf *[]byte, msg logMessage) {
283309
formatted = fmt.Sprintf(msg.format, msg.arg1, msg.arg2, msg.arg3, msg.arg4, msg.arg5)
284310
case 6:
285311
formatted = fmt.Sprintf(msg.format, msg.arg1, msg.arg2, msg.arg3, msg.arg4, msg.arg5, msg.arg6)
312+
case 7:
313+
formatted = fmt.Sprintf(msg.format, msg.arg1, msg.arg2, msg.arg3, msg.arg4, msg.arg5, msg.arg6, msg.arg7)
314+
case 8:
315+
formatted = fmt.Sprintf(msg.format, msg.arg1, msg.arg2, msg.arg3, msg.arg4, msg.arg5, msg.arg6, msg.arg7, msg.arg8)
286316
}
287317

288318
*buf = append(*buf, formatted...)
@@ -390,4 +420,4 @@ func (l *Logger) Stop(ctx context.Context) error {
390420
case <-done:
391421
return nil
392422
}
393-
}
423+
}

0 commit comments

Comments
 (0)