Skip to content

Commit 911915f

Browse files
committed
derpcat: it's alive!
UA 605 PDX-ORD en route to Ireland. yay not buying the wifi. Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
1 parent 024a240 commit 911915f

5 files changed

Lines changed: 193 additions & 13 deletions

File tree

derpcat/derpcat.go

Lines changed: 107 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import (
55
"encoding/base64"
66
"errors"
77
"fmt"
8+
"io"
89
"log"
910
"net"
1011
"net/netip"
12+
"slices"
1113
"strings"
14+
"sync"
1215
"time"
1316

1417
"github.com/fxamacker/cbor/v2"
@@ -24,8 +27,10 @@ import (
2427
"tailscale.com/types/key"
2528
"tailscale.com/types/logger"
2629
"tailscale.com/types/netmap"
30+
"tailscale.com/util/cmpx"
2731
"tailscale.com/util/mak"
2832
"tailscale.com/wgengine"
33+
"tailscale.com/wgengine/filter"
2934
"tailscale.com/wgengine/netstack"
3035
"tailscale.com/wgengine/router"
3136
"tailscale.com/wgengine/wgcfg"
@@ -53,6 +58,9 @@ type locoBackend struct {
5358
dm *tailcfg.DERPMap
5459
logf logger.Logf
5560
serverPub key.NodePublic // non-zero if we're a client (server's public key)
61+
62+
mu sync.Mutex
63+
clients map[key.NodePublic]*tailcfg.Node // for the server
5664
}
5765

5866
func (b *locoBackend) Close() error {
@@ -105,9 +113,20 @@ func NewServer(priv key.NodePrivate, logf logger.Logf, regs ...*tailcfg.DERPRegi
105113
}
106114
ns.ProcessLocalIPs = true
107115
ns.ProcessSubnets = true
116+
ns.GetTCPHandlerForFlow = func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) {
117+
logf("XXX connection from %v to %v", src, dst)
118+
if dst.Port() != 80 {
119+
return nil, true // sends RST
120+
}
121+
return func(c net.Conn) {
122+
io.WriteString(c, "Hello from port 80\n")
123+
c.Close()
124+
}, true
125+
}
108126
lb.ns = ns
109127

110128
e := sys.Engine.Get()
129+
e.SetFilter(filter.NewAllowAllForTest(logf)) // TODO: trashy
111130
dialer.UseNetstackForIP = func(ip netip.Addr) bool {
112131
_, ok := e.PeerForIP(ip)
113132
return ok
@@ -220,15 +239,20 @@ func (lb *locoBackend) Start() error {
220239
}
221240
if lb.serverPub.IsZero() {
222241
// We're the server. (hence the serverPub is zero)
242+
discoPriv := lb.priv.AsDiscoPrivate()
243+
mc.SetDisco(discoPriv)
244+
mc.BeDerpCatServer(lb.onMeow)
245+
223246
nm.SelfNode = (&tailcfg.Node{
224247
ID: 1,
225248
StableID: "1",
226249
Name: "server.derpcat.",
227250
User: 100,
228251
Key: lb.pub,
229-
DiscoKey: mc.DiscoPublicKey(), // TODO: change how disco works
252+
DiscoKey: discoPriv.Public(),
230253
Addresses: []netip.Prefix{lb.addrPrefix},
231254
AllowedIPs: []netip.Prefix{lb.addrPrefix},
255+
DERP: "127.3.3.40:1",
232256
}).View()
233257
} else {
234258
// We're the client.
@@ -241,20 +265,23 @@ func (lb *locoBackend) Start() error {
241265
Name: "client.derpcat.",
242266
User: 100,
243267
Key: lb.pub,
244-
DiscoKey: mc.DiscoPublicKey(), // TODO: change how disco works
268+
DiscoKey: mc.DiscoPublicKey(),
245269
Addresses: []netip.Prefix{lb.addrPrefix},
270+
DERP: "127.3.3.40:1",
246271
}).View()
247272
nm.Peers = append(nm.Peers, (&tailcfg.Node{
248273
ID: 1,
249274
StableID: "1",
250275
Name: "server.derpcat.",
251276
User: 100,
252277
Key: lb.serverPub,
253-
DiscoKey: key.NewDisco().Public(), // TODO: change how disco works. This is dummy placeholder to placate magicsock for now
278+
DiscoKey: lb.serverPub.AsDiscoPublic(),
254279
Addresses: []netip.Prefix{serverAddrPrefix},
255280
AllowedIPs: []netip.Prefix{serverAddrPrefix},
281+
DERP: "127.3.3.40:1",
256282
}).View())
257283
}
284+
nm.Addresses = nm.SelfNode.Addresses().AsSlice() // dumb redundant field for now
258285
e.SetNetworkMap(nm)
259286
mc.SetNetworkUp(true)
260287
lb.logf("NetworkMap: %v", logger.AsJSON(nm))
@@ -285,6 +312,71 @@ func (lb *locoBackend) Start() error {
285312
return nil
286313
}
287314

315+
func (b *locoBackend) onMeow(src key.NodePublic, discoPub key.DiscoPublic) {
316+
b.mu.Lock()
317+
defer b.mu.Unlock()
318+
if _, ok := b.clients[src]; ok {
319+
return
320+
}
321+
id := len(b.clients) + 2 // server id ID 1, clients are IDs 2, 3, ...
322+
mak.Set(&b.clients, src, &tailcfg.Node{
323+
ID: tailcfg.NodeID(id),
324+
StableID: tailcfg.StableNodeID(fmt.Sprint(id)),
325+
Name: fmt.Sprintf("client%d.derpcat.", id),
326+
User: 100,
327+
Key: src,
328+
DiscoKey: discoPub,
329+
Addresses: []netip.Prefix{pfxOf(dcAddrForKey(src))},
330+
AllowedIPs: []netip.Prefix{pfxOf(dcAddrForKey(src))},
331+
DERP: "127.3.3.40:1",
332+
})
333+
334+
nm := &netmap.NetworkMap{
335+
PrivateKey: b.priv,
336+
SelfNode: (&tailcfg.Node{
337+
ID: 1,
338+
StableID: "1",
339+
Name: "server.derpcat.",
340+
User: 100,
341+
Key: b.pub,
342+
DiscoKey: b.priv.AsDiscoPrivate().Public(), // TODO: cache
343+
Addresses: []netip.Prefix{b.addrPrefix},
344+
AllowedIPs: []netip.Prefix{b.addrPrefix},
345+
DERP: "127.3.3.40:1",
346+
}).View(),
347+
}
348+
nm.Addresses = nm.SelfNode.Addresses().AsSlice() // dumb redundant field for now
349+
for _, n := range b.clients {
350+
nm.Peers = append(nm.Peers, n.View())
351+
}
352+
slices.SortFunc(nm.Peers, func(a, b tailcfg.NodeView) int {
353+
return cmpx.Compare(a.ID(), b.ID())
354+
})
355+
eng := b.sys.Engine.Get()
356+
eng.SetNetworkMap(nm)
357+
358+
wgConf := &wgcfg.Config{
359+
Name: "self",
360+
PrivateKey: b.priv,
361+
Addresses: []netip.Prefix{b.addrPrefix},
362+
MTU: 1280,
363+
Peers: []wgcfg.Peer{}, // TODO: add peers dynamically as they disco to us
364+
}
365+
for _, p := range b.clients {
366+
wgConf.Peers = append(wgConf.Peers, wgcfg.Peer{
367+
PublicKey: p.Key,
368+
AllowedIPs: p.AllowedIPs,
369+
})
370+
}
371+
routerConf := &router.Config{
372+
LocalAddrs: []netip.Prefix{b.addrPrefix},
373+
}
374+
dnsConf := &dns.Config{}
375+
if err := eng.Reconfig(wgConf, routerConf, dnsConf); err != nil {
376+
panic(fmt.Sprintf("e.Reconfig: %v", err))
377+
}
378+
}
379+
288380
func (b *locoBackend) Status() *ipnstate.Status {
289381
mc := b.sys.MagicSock.Get()
290382
eng := b.sys.Engine.Get()
@@ -372,11 +464,14 @@ func NewClient(logf logger.Logf, server ConnBlob) (*Client, error) {
372464
if err != nil {
373465
return nil, fmt.Errorf("newNetstack: %w", err)
374466
}
375-
ns.ProcessLocalIPs = false
376-
ns.ProcessSubnets = false
467+
ns.ProcessLocalIPs = true // required to even reply to TCP SYNs client sends out
468+
ns.GetTCPHandlerForFlow = func(src, dst netip.AddrPort) (handler func(net.Conn), intercept bool) {
469+
return nil, true // don't accept any incoming connections to client
470+
}
377471
lb.ns = ns
378472

379473
e := sys.Engine.Get()
474+
e.SetFilter(filter.NewAllowAllForTest(logf)) // TODO: trashy
380475
dialer.UseNetstackForIP = func(ip netip.Addr) bool {
381476
_, ok := e.PeerForIP(ip)
382477
return ok
@@ -391,8 +486,9 @@ func NewClient(logf logger.Logf, server ConnBlob) (*Client, error) {
391486
}, nil
392487
}
393488

394-
func (c *Client) Start() error { return c.lb.Start() }
395-
func (c *Client) Close() error { return c.lb.Close() }
489+
func (c *Client) PublicKey() key.NodePublic { return c.lb.pub }
490+
func (c *Client) Start() error { return c.lb.Start() }
491+
func (c *Client) Close() error { return c.lb.Close() }
396492

397493
type PingResult struct {
398494
Latency time.Duration
@@ -422,3 +518,7 @@ func (c *Client) Ping(ctx context.Context) (PingResult, error) {
422518
return zero, ctx.Err()
423519
}
424520
}
521+
522+
func pfxOf(a netip.Addr) netip.Prefix {
523+
return netip.PrefixFrom(a, a.BitLen())
524+
}

derpcat/derpcat_test.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ package derpcat
22

33
import (
44
"context"
5+
"errors"
6+
"io"
7+
"net"
58
"testing"
9+
"time"
610

11+
"tailscale.com/tstest"
712
"tailscale.com/tstest/integration"
813
"tailscale.com/types/key"
914
"tailscale.com/types/logger"
@@ -20,7 +25,7 @@ func mkLogger(t testing.TB, name string) logger.Logf {
2025
}
2126

2227
func TestDERPCat(t *testing.T) {
23-
dm := integration.RunDERPAndSTUN(t, mkLogger(t, "derpstun"), "127.0.0.1")
28+
derper, dm := integration.RunDERPAndSTUN(t, mkLogger(t, "derpstun"), "127.0.0.1")
2429
t.Logf("DERPMap: %v", logger.AsJSON(dm))
2530

2631
reg := dm.Regions[1]
@@ -40,6 +45,15 @@ func TestDERPCat(t *testing.T) {
4045
t.Fatalf("server Start: %v", err)
4146
}
4247

48+
if err := tstest.WaitFor(5*time.Second, func() error {
49+
if derper.IsClientConnectedForTest(priv.Public()) {
50+
return nil
51+
}
52+
return errors.New("server not connected to derper")
53+
}); err != nil {
54+
t.Fatal(err)
55+
}
56+
4357
c, err := NewClient(mkLogger(t, "client"), s.ConnBlob())
4458
if err != nil {
4559
t.Fatalf("NewClient: %v", err)
@@ -49,9 +63,33 @@ func TestDERPCat(t *testing.T) {
4963
}
5064
t.Cleanup(func() { c.Close() })
5165

66+
if false {
67+
if err := tstest.WaitFor(5*time.Second, func() error {
68+
if derper.IsClientConnectedForTest(c.PublicKey()) {
69+
return nil
70+
}
71+
return errors.New("server not connected to derper")
72+
}); err != nil {
73+
t.Fatal(err)
74+
}
75+
}
76+
77+
t.Logf("Client is %v", c.PublicKey())
78+
5279
pi, err := c.Ping(context.Background())
5380
if err != nil {
5481
t.Fatalf("Ping: %v", err)
5582
}
5683
t.Logf("got ping: %+v", pi)
84+
85+
time.Sleep(1 * time.Second)
86+
87+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
88+
defer cancel()
89+
conn, err := c.lb.sys.Dialer.Get().UserDial(ctx, "tcp", net.JoinHostPort(s.lb.addr.String(), "80"))
90+
if err != nil {
91+
t.Fatalf("UserDial = %v, %v", conn, err)
92+
}
93+
all, err := io.ReadAll(conn)
94+
t.Logf("Got: %q, %v", all, err)
5795
}

tstest/integration/integration.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ func exe() string {
293293
// RunDERPAndSTUN runs a local DERP and STUN server for tests, returning the derpMap
294294
// that clients should use. This creates resources that must be cleaned up with the
295295
// returned cleanup function.
296-
func RunDERPAndSTUN(t testing.TB, logf logger.Logf, ipAddress string) (derpMap *tailcfg.DERPMap) {
296+
func RunDERPAndSTUN(t testing.TB, logf logger.Logf, ipAddress string) (derpServer *derp.Server, derpMap *tailcfg.DERPMap) {
297297
t.Helper()
298298

299299
d := derpserver.New(key.NewNode(), logf)
@@ -344,7 +344,7 @@ func RunDERPAndSTUN(t testing.TB, logf logger.Logf, ipAddress string) (derpMap *
344344
ln.Close()
345345
})
346346

347-
return m
347+
return d, m
348348
}
349349

350350
// LogCatcher is a minimal logcatcher for the logtail upload client.

types/key/node.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,11 @@ func (k NodePublic) WireGuardGoString() string {
378378
b[second+3] = b64((k.k[31] << 2) & 63)
379379
return string(b)
380380
}
381+
382+
func (k NodePrivate) AsDiscoPrivate() DiscoPrivate {
383+
return DiscoPrivate(k)
384+
}
385+
386+
func (k NodePublic) AsDiscoPublic() DiscoPublic {
387+
return DiscoPublic(k)
388+
}

0 commit comments

Comments
 (0)