fix(net/portmapper): clean up mappings and UPnP connections · coder/tailscale@c82775e · GitHub
Skip to content

Commit c82775e

Browse files
committed
fix(net/portmapper): clean up mappings and UPnP connections
1 parent e42b84b commit c82775e

4 files changed

Lines changed: 307 additions & 17 deletions

File tree

‎net/portmapper/portmapper.go‎

Lines changed: 24 additions & 3 deletions

‎net/portmapper/portmapper_test.go‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ package portmapper
55

66
import (
77
"context"
8+
"encoding/binary"
9+
"net"
10+
"net/netip"
811
"os"
912
"reflect"
1013
"strconv"
@@ -122,3 +125,67 @@ func TestPCPIntegration(t *testing.T) {
122125
t.Errorf("got nil mapping after successful createOrGetMapping")
123126
}
124127
}
128+
129+
func TestSetMappingLockedReleasesAfterClose(t *testing.T) {
130+
t.Parallel()
131+
for _, tt := range []struct {
132+
name string
133+
version byte
134+
pktLen, lifetimeOff, internalOff, externalOff int
135+
}{
136+
{"PMP", pmpVersion, 12, 8, 4, 6},
137+
{"PCP", pcpVersion, 60, 4, 40, 42},
138+
} {
139+
t.Run(tt.name, func(t *testing.T) {
140+
t.Parallel()
141+
pc, err := net.ListenPacket("udp4", "127.0.0.1:0")
142+
if err != nil {
143+
t.Fatal(err)
144+
}
145+
defer pc.Close()
146+
c := NewClient(t.Logf, nil, nil, nil)
147+
c.testPxPPort = uint16(pc.LocalAddr().(*net.UDPAddr).Port)
148+
lo := netip.MustParseAddr("127.0.0.1")
149+
gw, internal := netip.AddrPortFrom(lo, c.testPxPPort), netip.AddrPortFrom(lo, 41641)
150+
newMapping := func(port uint16) mapping {
151+
ext := netip.AddrPortFrom(lo, port)
152+
if tt.version == pmpVersion {
153+
return &pmpMapping{c: c, gw: gw, internal: internal, external: ext}
154+
}
155+
return &pcpMapping{c: c, gw: gw, internal: internal, external: ext}
156+
}
157+
158+
before := newMapping(4241)
159+
c.mu.Lock()
160+
installed := c.setMappingLocked(before)
161+
retained := c.mapping
162+
c.mu.Unlock()
163+
if !installed || retained != before {
164+
t.Fatalf("setMappingLocked before Close = %v, retained %v", installed, retained)
165+
}
166+
c.Close()
167+
c.mu.Lock()
168+
installed = c.setMappingLocked(newMapping(4242))
169+
retained = c.mapping
170+
c.mu.Unlock()
171+
if installed || retained != nil {
172+
t.Errorf("setMappingLocked after Close = %v, retained %v; want false, nil", installed, retained)
173+
}
174+
175+
pc.SetReadDeadline(time.Now().Add(10 * time.Second))
176+
buf := make([]byte, 1500)
177+
for _, port := range []uint16{4241, 4242} {
178+
n, _, err := pc.ReadFrom(buf)
179+
if err != nil {
180+
t.Fatalf("reading delete for port %d: %v", port, err)
181+
}
182+
if pkt := buf[:n]; n != tt.pktLen || pkt[0] != tt.version ||
183+
binary.BigEndian.Uint32(pkt[tt.lifetimeOff:]) != 0 ||
184+
binary.BigEndian.Uint16(pkt[tt.internalOff:]) != internal.Port() ||
185+
binary.BigEndian.Uint16(pkt[tt.externalOff:]) != port {
186+
t.Errorf("got % x; want %s delete for external port %d", pkt, tt.name, port)
187+
}
188+
}
189+
})
190+
}
191+
}

‎net/portmapper/upnp.go‎

Lines changed: 28 additions & 14 deletions

0 commit comments

Comments
 (0)