mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-01 22:15:51 +00:00
160 lines
4.1 KiB
Go
160 lines
4.1 KiB
Go
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
|
||
|
package appc
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"encoding/hex"
|
||
|
"io"
|
||
|
"net"
|
||
|
"net/netip"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
|
||
|
"tailscale.com/net/memnet"
|
||
|
)
|
||
|
|
||
|
func echoConnOnce(conn net.Conn) {
|
||
|
defer conn.Close()
|
||
|
|
||
|
b := make([]byte, 256)
|
||
|
n, err := conn.Read(b)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if _, err := conn.Write(b[:n]); err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestTCPRoundRobinHandler(t *testing.T) {
|
||
|
h := tcpRoundRobinHandler{
|
||
|
To: []string{"yeet.com"},
|
||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||
|
if network != "tcp" {
|
||
|
t.Errorf("network = %s, want %s", network, "tcp")
|
||
|
}
|
||
|
if addr != "yeet.com:22" {
|
||
|
t.Errorf("addr = %s, want %s", addr, "yeet.com:22")
|
||
|
}
|
||
|
|
||
|
c, s := memnet.NewConn("outbound", 1024)
|
||
|
go echoConnOnce(s)
|
||
|
return c, nil
|
||
|
},
|
||
|
}
|
||
|
|
||
|
cSock, sSock := memnet.NewTCPConn(netip.MustParseAddrPort("10.64.1.2:22"), netip.MustParseAddrPort("10.64.1.2:22"), 1024)
|
||
|
h.Handle(sSock)
|
||
|
|
||
|
// Test data write and read, the other end will echo back
|
||
|
// a single stanza
|
||
|
want := "hello"
|
||
|
if _, err := io.WriteString(cSock, want); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
got := make([]byte, len(want))
|
||
|
if _, err := io.ReadAtLeast(cSock, got, len(got)); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if string(got) != want {
|
||
|
t.Errorf("got %q, want %q", got, want)
|
||
|
}
|
||
|
|
||
|
// The other end closed the socket after the first echo, so
|
||
|
// any following read should error.
|
||
|
io.WriteString(cSock, "deadass heres some data on god fr")
|
||
|
if _, err := io.ReadAtLeast(cSock, got, len(got)); err == nil {
|
||
|
t.Error("read succeeded on closed socket")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Capture of first TCP data segment for a connection to https://pkgs.tailscale.com
|
||
|
const tlsStart = `45000239ff1840004006f9f5c0a801f2
|
||
|
c726b5efcf9e01bbe803b21394e3b752
|
||
|
801801f641dc00000101080ade3474f2
|
||
|
2fb93ee71603010200010001fc030303
|
||
|
c3acbd19d2624765bb19af4bce03365e
|
||
|
1d197f5bb939cdadeff26b0f8e7a0620
|
||
|
295b04127b82bae46aac4ff58cffef25
|
||
|
eba75a4b7a6de729532c411bd9dd0d2c
|
||
|
00203a3a130113021303c02bc02fc02c
|
||
|
c030cca9cca8c013c014009c009d002f
|
||
|
003501000193caca0000000a000a0008
|
||
|
1a1a001d001700180010000e000c0268
|
||
|
3208687474702f312e31002b0007062a
|
||
|
2a03040303ff01000100000d00120010
|
||
|
04030804040105030805050108060601
|
||
|
000b00020100002300000033002b0029
|
||
|
1a1a000100001d0020d3c76bef062979
|
||
|
a812ce935cfb4dbe6b3a84dc5ba9226f
|
||
|
23b0f34af9d1d03b4a001b0003020002
|
||
|
00120000446900050003026832000000
|
||
|
170015000012706b67732e7461696c73
|
||
|
63616c652e636f6d002d000201010005
|
||
|
00050100000000001700003a3a000100
|
||
|
0015002d000000000000000000000000
|
||
|
00000000000000000000000000000000
|
||
|
00000000000000000000000000000000
|
||
|
0000290094006f0069e76f2016f963ad
|
||
|
38c8632d1f240cd75e00e25fdef295d4
|
||
|
7042b26f3a9a543b1c7dc74939d77803
|
||
|
20527d423ff996997bda2c6383a14f49
|
||
|
219eeef8a053e90a32228df37ddbe126
|
||
|
eccf6b085c93890d08341d819aea6111
|
||
|
0d909f4cd6b071d9ea40618e74588a33
|
||
|
90d494bbb5c3002120d5a164a16c9724
|
||
|
c9ef5e540d8d6f007789a7acf9f5f16f
|
||
|
bf6a1907a6782ed02b`
|
||
|
|
||
|
func fakeSNIHeader() []byte {
|
||
|
b, err := hex.DecodeString(strings.Replace(tlsStart, "\n", "", -1))
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return b[0x34:] // trim IP + TCP header
|
||
|
}
|
||
|
|
||
|
func TestTCPSNIHandler(t *testing.T) {
|
||
|
h := tcpSNIHandler{
|
||
|
Allowlist: []string{"pkgs.tailscale.com"},
|
||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||
|
if network != "tcp" {
|
||
|
t.Errorf("network = %s, want %s", network, "tcp")
|
||
|
}
|
||
|
if addr != "pkgs.tailscale.com:443" {
|
||
|
t.Errorf("addr = %s, want %s", addr, "pkgs.tailscale.com:443")
|
||
|
}
|
||
|
|
||
|
c, s := memnet.NewConn("outbound", 1024)
|
||
|
go echoConnOnce(s)
|
||
|
return c, nil
|
||
|
},
|
||
|
}
|
||
|
|
||
|
cSock, sSock := memnet.NewTCPConn(netip.MustParseAddrPort("10.64.1.2:22"), netip.MustParseAddrPort("10.64.1.2:443"), 1024)
|
||
|
h.Handle(sSock)
|
||
|
|
||
|
// Fake a TLS handshake record with an SNI in it.
|
||
|
if _, err := cSock.Write(fakeSNIHeader()); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
|
||
|
// Test read, the other end will echo back
|
||
|
// a single stanza, which is at least the beginning of the SNI header.
|
||
|
want := fakeSNIHeader()[:5]
|
||
|
if _, err := cSock.Write(want); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
got := make([]byte, len(want))
|
||
|
if _, err := io.ReadAtLeast(cSock, got, len(got)); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if !bytes.Equal(got, want) {
|
||
|
t.Errorf("got %q, want %q", got, want)
|
||
|
}
|
||
|
}
|