Files
tailscale/feature/conn25/flowtable_test.go
Michael Ben-Ami ce7789071f feature/conn25: add NATing support with flow caching
Introduce a datapathHandler that implements hooks that will
receive packets from the tstun.Wrapper. This commit does not wire
those up just yet.

Perform DNAT from Magic IP to Transit IP on outbound flows on clients,
and reverse SNAT in the reverse direction.

Perform DNAT from Transit IP to final destination IP on outbound flows
on connectors, and reverse SNAT in the reverse direction.

Introduce FlowTable to cache validated flows by 5-tuple for fast lookups
after the first packet.

Flow expiration is not covered, and is intended as future work before
the feature is officially released.

Fixes tailscale/corp#34249
Fixes tailscale/corp#35995

Co-authored-by: Fran Bull <fran@tailscale.com>
Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
2026-03-18 11:49:47 -04:00

126 lines
3.2 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package conn25
import (
"net/netip"
"testing"
"tailscale.com/net/flowtrack"
"tailscale.com/net/packet"
"tailscale.com/types/ipproto"
)
func TestFlowTable(t *testing.T) {
ft := NewFlowTable(0)
fwdTuple := flowtrack.MakeTuple(
ipproto.UDP,
netip.MustParseAddrPort("1.2.3.4:1000"),
netip.MustParseAddrPort("4.3.2.1:80"),
)
// Reverse tuple is defined by caller. Doesn't have to be mirror image of fwd.
// To account for intentional modifications, like NAT.
revTuple := flowtrack.MakeTuple(
ipproto.UDP,
netip.MustParseAddrPort("4.3.2.2:80"),
netip.MustParseAddrPort("1.2.3.4:1000"),
)
fwdAction, revAction := 0, 0
fwdData := FlowData{
Tuple: fwdTuple,
Action: func(_ *packet.Parsed) { fwdAction++ },
}
revData := FlowData{
Tuple: revTuple,
Action: func(_ *packet.Parsed) { revAction++ },
}
// For this test setup, from the tun device will be "forward",
// and from WG will be "reverse".
if err := ft.NewFlowFromTunDevice(fwdData, revData); err != nil {
t.Fatalf("got non-nil error for new flow from tun device")
}
// Test basic lookups.
lookupFwd, ok := ft.LookupFromTunDevice(fwdTuple)
if !ok {
t.Fatalf("got not found on first lookup from tun device")
}
lookupFwd.Action(nil)
if fwdAction != 1 {
t.Errorf("action for fwd tuple key was not executed")
}
lookupRev, ok := ft.LookupFromWireGuard(revTuple)
if !ok {
t.Fatalf("got not found on first lookup from WireGuard")
}
lookupRev.Action(nil)
if revAction != 1 {
t.Errorf("action for rev tuple key was not executed")
}
// Test not found error.
notFoundTuple := flowtrack.MakeTuple(
ipproto.UDP,
netip.MustParseAddrPort("1.2.3.4:1000"),
netip.MustParseAddrPort("4.0.4.4:80"),
)
if _, ok := ft.LookupFromTunDevice(notFoundTuple); ok {
t.Errorf("expected not found for foreign tuple")
}
// Wrong direction is also not found.
if _, ok := ft.LookupFromWireGuard(fwdTuple); ok {
t.Errorf("expected not found for wrong direction tuple")
}
// Overwriting forward tuple removes its reverse pair as well.
newRevData := FlowData{
Tuple: flowtrack.MakeTuple(
ipproto.UDP,
netip.MustParseAddrPort("9.9.9.9:99"),
netip.MustParseAddrPort("8.8.8.8:88"),
),
Action: func(_ *packet.Parsed) {},
}
if err := ft.NewFlowFromTunDevice(
fwdData,
newRevData,
); err != nil {
t.Fatalf("got non-nil error for new flow from tun device")
}
if _, ok := ft.LookupFromWireGuard(revTuple); ok {
t.Errorf("expected not found for removed reverse tuple")
}
// Overwriting reverse tuple removes its forward pair as well.
if err := ft.NewFlowFromTunDevice(
FlowData{
Tuple: flowtrack.MakeTuple(
ipproto.UDP,
netip.MustParseAddrPort("8.8.8.8:88"),
netip.MustParseAddrPort("9.9.9.9:99"),
),
Action: func(_ *packet.Parsed) {},
},
newRevData, // This is the same "reverse" data installed in previous test.
); err != nil {
t.Fatalf("got non-nil error for new flow from tun device")
}
if _, ok := ft.LookupFromTunDevice(fwdTuple); ok {
t.Errorf("expected not found for removed forward tuple")
}
// Nil action returns an error.
if err := ft.NewFlowFromTunDevice(
FlowData{},
FlowData{},
); err == nil {
t.Errorf("expected non-nil error for nil data")
}
}