mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
156 lines
3.6 KiB
Go
156 lines
3.6 KiB
Go
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||
|
// Use of this source code is governed by a BSD-style
|
||
|
// license that can be found in the LICENSE file.
|
||
|
|
||
|
package portmapper
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"net/http"
|
||
|
"net/http/httptest"
|
||
|
"sync"
|
||
|
|
||
|
"inet.af/netaddr"
|
||
|
)
|
||
|
|
||
|
// TestIGD is an IGD (Intenet Gateway Device) for testing. It supports fake
|
||
|
// implementations of NAT-PMP, PCP, and/or UPnP to test clients against.
|
||
|
type TestIGD struct {
|
||
|
upnpConn net.PacketConn // for UPnP discovery
|
||
|
pxpConn net.PacketConn // for NAT-PMP and/or PCP
|
||
|
ts *httptest.Server
|
||
|
|
||
|
doPMP bool
|
||
|
doPCP bool
|
||
|
doUPnP bool // TODO: more options for 3 flavors of UPnP services
|
||
|
|
||
|
mu sync.Mutex // guards below
|
||
|
counters igdCounters
|
||
|
}
|
||
|
|
||
|
type igdCounters struct {
|
||
|
numUPnPDiscoRecv int32
|
||
|
numUPnPOtherUDPRecv int32
|
||
|
numUPnPHTTPRecv int32
|
||
|
numPMPRecv int32
|
||
|
numPMPDiscoRecv int32
|
||
|
numPCPRecv int32
|
||
|
numPCPDiscoRecv int32
|
||
|
numPMPPublicAddrRecv int32
|
||
|
numPMPBogusRecv int32
|
||
|
}
|
||
|
|
||
|
func NewTestIGD() (*TestIGD, error) {
|
||
|
d := &TestIGD{
|
||
|
doPMP: true,
|
||
|
doPCP: true,
|
||
|
doUPnP: true,
|
||
|
}
|
||
|
var err error
|
||
|
if d.upnpConn, err = net.ListenPacket("udp", "127.0.0.1:1900"); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if d.pxpConn, err = net.ListenPacket("udp", "127.0.0.1:5351"); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
d.ts = httptest.NewServer(http.HandlerFunc(d.serveUPnPHTTP))
|
||
|
go d.serveUPnPDiscovery()
|
||
|
go d.servePxP()
|
||
|
return d, nil
|
||
|
}
|
||
|
|
||
|
func (d *TestIGD) Close() error {
|
||
|
d.ts.Close()
|
||
|
d.upnpConn.Close()
|
||
|
d.pxpConn.Close()
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (d *TestIGD) inc(p *int32) {
|
||
|
d.mu.Lock()
|
||
|
defer d.mu.Unlock()
|
||
|
(*p)++
|
||
|
}
|
||
|
|
||
|
func (d *TestIGD) stats() igdCounters {
|
||
|
d.mu.Lock()
|
||
|
defer d.mu.Unlock()
|
||
|
return d.counters
|
||
|
}
|
||
|
|
||
|
func (d *TestIGD) serveUPnPHTTP(w http.ResponseWriter, r *http.Request) {
|
||
|
http.NotFound(w, r) // TODO
|
||
|
}
|
||
|
|
||
|
func (d *TestIGD) serveUPnPDiscovery() {
|
||
|
buf := make([]byte, 1500)
|
||
|
for {
|
||
|
n, src, err := d.upnpConn.ReadFrom(buf)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
pkt := buf[:n]
|
||
|
if bytes.Equal(pkt, uPnPPacket) { // a super lazy "parse"
|
||
|
d.inc(&d.counters.numUPnPDiscoRecv)
|
||
|
resPkt := []byte(fmt.Sprintf("HTTP/1.1 200 OK\r\nCACHE-CONTROL: max-age=120\r\nST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\nUSN: uuid:bee7052b-49e8-3597-b545-55a1e38ac11::urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\nEXT:\r\nSERVER: Tailscale-Test/1.0 UPnP/1.1 MiniUPnPd/2.2.1\r\nLOCATION: %s\r\nOPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n01-NLS: 1627958564\r\nBOOTID.UPNP.ORG: 1627958564\r\nCONFIGID.UPNP.ORG: 1337\r\n\r\n", d.ts.URL+"/rootDesc.xml"))
|
||
|
d.upnpConn.WriteTo(resPkt, src)
|
||
|
} else {
|
||
|
d.inc(&d.counters.numUPnPOtherUDPRecv)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// servePxP serves NAT-PMP and PCP, which share a port number.
|
||
|
func (d *TestIGD) servePxP() {
|
||
|
buf := make([]byte, 1500)
|
||
|
for {
|
||
|
n, a, err := d.pxpConn.ReadFrom(buf)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
ua := a.(*net.UDPAddr)
|
||
|
src, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone)
|
||
|
if !ok {
|
||
|
panic("bogus addr")
|
||
|
}
|
||
|
pkt := buf[:n]
|
||
|
if len(pkt) < 2 {
|
||
|
continue
|
||
|
}
|
||
|
ver := pkt[0]
|
||
|
switch ver {
|
||
|
default:
|
||
|
continue
|
||
|
case pmpVersion:
|
||
|
d.handlePMPQuery(pkt, src)
|
||
|
case pcpVersion:
|
||
|
d.handlePCPQuery(pkt, src)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (d *TestIGD) handlePMPQuery(pkt []byte, src netaddr.IPPort) {
|
||
|
d.inc(&d.counters.numPMPRecv)
|
||
|
if len(pkt) < 2 {
|
||
|
return
|
||
|
}
|
||
|
op := pkt[1]
|
||
|
switch op {
|
||
|
case pmpOpMapPublicAddr:
|
||
|
if len(pkt) != 2 {
|
||
|
d.inc(&d.counters.numPMPBogusRecv)
|
||
|
return
|
||
|
}
|
||
|
d.inc(&d.counters.numPMPPublicAddrRecv)
|
||
|
|
||
|
}
|
||
|
// TODO
|
||
|
}
|
||
|
|
||
|
func (d *TestIGD) handlePCPQuery(pkt []byte, src netaddr.IPPort) {
|
||
|
d.inc(&d.counters.numPCPRecv)
|
||
|
// TODO
|
||
|
}
|