mirror of
https://github.com/tailscale/tailscale.git
synced 2026-10-23 03:37:19 +09:00
tstest/natlab/vnet: send "unsupported version" NAT-PMP/PCP responses
When receiving a PCP request, a NAT-PMP-only server must reply with an unsupported version response. Same for a PCP-only server receiving a NAT-PMP request. The tailscale client sends both a NAT-PMP and a PCP request when probing for portmapping support and it correctly handle any unsupported version response it receives. Updates #21713 Change-Id: I0ec8b4a6c1de14bcc64b82f1988cde7fc01f5747 Signed-off-by: Francois Marier <francois@tailscale.com>
This commit is contained in:
1 parent
e310c02787
commit
48ac67151e
2 files changed
+98
-30
No files matched your search
@@ -115,6 +115,14 @@ func easyPMP(e *vmtest.Env) *vmtest.Node {
|
||||
fmt.Sprintf("192.168.%d.1/24", n), vnet.EasyNAT, vnet.NATPMP))
|
||||
}
|
||||
|
||||
func easyPxP(e *vmtest.Env) *vmtest.Node {
|
||||
n := e.NumNodes()
|
||||
return e.AddNode(fmt.Sprintf("node-%d", n),
|
||||
e.AddNetwork(
|
||||
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
||||
fmt.Sprintf("192.168.%d.1/24", n), vnet.EasyNAT, vnet.PCP, vnet.NATPMP))
|
||||
}
|
||||
|
||||
func hardPCP(e *vmtest.Env) *vmtest.Node {
|
||||
n := e.NumNodes()
|
||||
return e.AddNode(fmt.Sprintf("node-%d", n),
|
||||
@@ -131,6 +139,14 @@ func hardPMP(e *vmtest.Env) *vmtest.Node {
|
||||
fmt.Sprintf("10.7.%d.1/24", n), vnet.HardNAT, vnet.NATPMP))
|
||||
}
|
||||
|
||||
func hardPxP(e *vmtest.Env) *vmtest.Node {
|
||||
n := e.NumNodes()
|
||||
return e.AddNode(fmt.Sprintf("node-%d", n),
|
||||
e.AddNetwork(
|
||||
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
||||
fmt.Sprintf("10.7.%d.1/24", n), vnet.HardNAT, vnet.PCP, vnet.NATPMP))
|
||||
}
|
||||
|
||||
func hardNoDERPOrEndpoints(e *vmtest.Env) *vmtest.Node {
|
||||
n := e.NumNodes()
|
||||
return e.AddNode(fmt.Sprintf("node-%d", n),
|
||||
@@ -309,14 +325,16 @@ var types = []nodeType{
|
||||
{"hard", hard},
|
||||
{"easyPCP", easyPCP},
|
||||
{"easyPMP", easyPMP},
|
||||
{"easyPxP", easyPxP},
|
||||
{"hardPCP", hardPCP},
|
||||
{"hardPMP", hardPMP},
|
||||
{"hardPxP", hardPxP},
|
||||
{"one2one", one2one},
|
||||
{"sameLAN", sameLAN},
|
||||
{"cgnat", cgnat},
|
||||
}
|
||||
|
||||
var pair = flag.String("pair", "", "comma-separated pair of types to test (easy, easyAF, hard, easyPCP, easyPMP, hardPCP, hardPMP, one2one, sameLAN)")
|
||||
var pair = flag.String("pair", "", "comma-separated pair of types to test (easy, easyAF, hard, easyPCP, easyPMP, easyPxP, hardPCP, hardPMP, hardPxP, one2one, sameLAN)")
|
||||
|
||||
func TestPair(t *testing.T) {
|
||||
t1, t2, ok := strings.Cut(*pair, ",")
|
||||
|
||||
+79
-29
@@ -92,6 +92,11 @@ const (
|
||||
|
||||
versionNATPMP = 0
|
||||
versionPCP = 2
|
||||
|
||||
resultSuccess = 0
|
||||
resultUnsuppVersion = 1
|
||||
|
||||
opcodeResponseBit = 128
|
||||
)
|
||||
|
||||
func (s *Server) PopulateDERPMapIPs() error {
|
||||
@@ -2058,17 +2063,8 @@ func (n *network) handleUDPPacketForRouter(ep EthernetPacket, udp *layers.UDP, t
|
||||
return
|
||||
}
|
||||
|
||||
if dstIP == n.lanIP4.Addr() && isNATPMP(udp) {
|
||||
n.handleNATPMPRequest(UDPPacket{
|
||||
Src: netip.AddrPortFrom(srcIP, uint16(udp.SrcPort)),
|
||||
Dst: netip.AddrPortFrom(dstIP, uint16(udp.DstPort)),
|
||||
Payload: udp.Payload,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if isPCP(udp) {
|
||||
n.handlePCPRequest(UDPPacket{
|
||||
if dstIP == n.lanIP4.Addr() && udp.DstPort == pxpPort && len(udp.Payload) >= 2 { // TODO(fmarier): support IPv6 PCP
|
||||
n.handlePxPRequest(UDPPacket{
|
||||
Src: netip.AddrPortFrom(srcIP, uint16(udp.SrcPort)),
|
||||
Dst: netip.AddrPortFrom(dstIP, uint16(udp.DstPort)),
|
||||
Payload: udp.Payload,
|
||||
@@ -2533,14 +2529,6 @@ func isDNSRequest(pkt gopacket.Packet) bool {
|
||||
return ok && dns.QR == false && len(dns.Questions) > 0
|
||||
}
|
||||
|
||||
func isNATPMP(udp *layers.UDP) bool {
|
||||
return udp.DstPort == pxpPort && len(udp.Payload) > 0 && udp.Payload[0] == versionNATPMP
|
||||
}
|
||||
|
||||
func isPCP(udp *layers.UDP) bool {
|
||||
return udp.DstPort == pxpPort && len(udp.Payload) > 0 && udp.Payload[0] == versionPCP
|
||||
}
|
||||
|
||||
func makeSTUNReply(req UDPPacket) (res UDPPacket, ok bool) {
|
||||
txid, err := stun.ParseBindingRequest(req.Payload)
|
||||
if err != nil {
|
||||
@@ -2845,6 +2833,68 @@ func (n *network) createARPResponse(pkt gopacket.Packet) ([]byte, error) {
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (n *network) handlePxPRequest(req UDPPacket) {
|
||||
if req.Payload[1]&opcodeResponseBit != 0 {
|
||||
n.logf("vnet: ignoring NAT-PMP/PCP packet with response bit set % 02x", req.Payload)
|
||||
return
|
||||
}
|
||||
switch req.Payload[0] {
|
||||
case versionNATPMP:
|
||||
switch {
|
||||
case n.svcs.Contains(NATPMP):
|
||||
n.handleNATPMPRequest(req)
|
||||
case n.svcs.Contains(PCP):
|
||||
n.writePCPUnsuppVersion(req)
|
||||
}
|
||||
case versionPCP:
|
||||
switch {
|
||||
case n.svcs.Contains(PCP):
|
||||
n.handlePCPRequest(req)
|
||||
case n.svcs.Contains(NATPMP):
|
||||
n.writeNATPMPUnsuppVersion(req)
|
||||
}
|
||||
default:
|
||||
n.logf("vnet: ignoring NAT-PMP/PCP packet with unknown version % 02x", req.Payload)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *network) writeNATPMPUnsuppVersion(req UDPPacket) {
|
||||
res := make([]byte, 0, 8)
|
||||
res = append(res,
|
||||
versionNATPMP,
|
||||
0, // opcode is 0 as per https://www.rfc-editor.org/rfc/rfc6886#section-3.5
|
||||
0, resultUnsuppVersion,
|
||||
)
|
||||
epoch := uint32(time.Now().Unix())
|
||||
res = binary.BigEndian.AppendUint32(res, epoch)
|
||||
n.WriteUDPPacketNoNAT(UDPPacket{
|
||||
Src: req.Dst,
|
||||
Dst: req.Src,
|
||||
Payload: res,
|
||||
})
|
||||
}
|
||||
|
||||
func (n *network) writePCPUnsuppVersion(req UDPPacket) {
|
||||
opcode := req.Payload[1]
|
||||
res := make([]byte, 0, 24)
|
||||
res = append(res,
|
||||
versionPCP,
|
||||
opcodeResponseBit|opcode,
|
||||
0, // reserved
|
||||
resultUnsuppVersion,
|
||||
)
|
||||
lifetimeSec := uint32(1800) // 30 min
|
||||
res = binary.BigEndian.AppendUint32(res, lifetimeSec)
|
||||
epoch := uint32(time.Now().Unix())
|
||||
res = binary.BigEndian.AppendUint32(res, epoch)
|
||||
res = append(res, make([]byte, 12)...) // reserved (96 bits)
|
||||
n.WriteUDPPacketNoNAT(UDPPacket{
|
||||
Src: req.Dst,
|
||||
Dst: req.Src,
|
||||
Payload: res,
|
||||
})
|
||||
}
|
||||
|
||||
func (n *network) handleNATPMPRequest(req UDPPacket) {
|
||||
if !n.svcs.Contains(NATPMP) {
|
||||
return
|
||||
@@ -2854,14 +2904,14 @@ func (n *network) handleNATPMPRequest(req UDPPacket) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Payload) == 2 && req.Payload[0] == opcodeANNOUNCE {
|
||||
if len(req.Payload) == 2 && req.Payload[1] == opcodeANNOUNCE {
|
||||
// https://www.rfc-editor.org/rfc/rfc6886#section-3.2
|
||||
|
||||
res := make([]byte, 0, 12)
|
||||
res = append(res,
|
||||
versionNATPMP,
|
||||
128+opcodeANNOUNCE,
|
||||
0, 0, // result code success
|
||||
opcodeResponseBit|opcodeANNOUNCE,
|
||||
0, resultSuccess,
|
||||
)
|
||||
epoch := uint32(time.Now().Unix())
|
||||
res = binary.BigEndian.AppendUint32(res, epoch)
|
||||
@@ -2895,8 +2945,8 @@ func (n *network) handleNATPMPRequest(req UDPPacket) {
|
||||
res := make([]byte, 0, 16)
|
||||
res = append(res,
|
||||
versionNATPMP,
|
||||
128+opcodeMAP,
|
||||
0, 0, // result code success
|
||||
opcodeResponseBit|opcodeMAP,
|
||||
0, resultSuccess,
|
||||
)
|
||||
epoch := uint32(time.Now().Unix())
|
||||
res = binary.BigEndian.AppendUint32(res, epoch)
|
||||
@@ -2936,9 +2986,9 @@ func (n *network) handlePCPRequest(req UDPPacket) {
|
||||
res := make([]byte, 0, 24)
|
||||
res = append(res,
|
||||
versionPCP,
|
||||
128+opcodeANNOUNCE,
|
||||
0, // reserved
|
||||
0, // result code success
|
||||
opcodeResponseBit|opcodeANNOUNCE,
|
||||
0, // reserved
|
||||
resultSuccess,
|
||||
0, 0, 0, 0, // lifetime (0 for ANNOUNCE)
|
||||
)
|
||||
epoch := uint32(time.Now().Unix())
|
||||
@@ -2990,9 +3040,9 @@ func (n *network) handlePCPRequest(req UDPPacket) {
|
||||
res := make([]byte, 0, 60)
|
||||
res = append(res,
|
||||
versionPCP,
|
||||
128+opcodeMAP,
|
||||
opcodeResponseBit|opcodeMAP,
|
||||
0, // reserved
|
||||
0, // result code success
|
||||
resultSuccess,
|
||||
)
|
||||
res = binary.BigEndian.AppendUint32(res, lifetimeSec)
|
||||
epoch := uint32(time.Now().Unix())
|
||||
|
||||
Reference in new issue
Block a user