mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-09 03:46:14 -08:00
* chore(go): use lowercase module import path * ci: scope checks for Go module changes
693 lines
18 KiB
Go
693 lines
18 KiB
Go
package host_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/netip"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
corehost "github.com/easytier/easytier/easytier-go"
|
|
"github.com/easytier/easytier/easytier-go/platform"
|
|
"github.com/easytier/easytier/easytier-go/platform/netstd"
|
|
hostproto "github.com/easytier/easytier/easytier-go/proto"
|
|
)
|
|
|
|
func TestPublicLifecycleDoesNotExposeWazero(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
host, err := corehost.New(ctx, corehost.Options{})
|
|
if err != nil {
|
|
t.Fatalf("create host: %v", err)
|
|
}
|
|
defer host.Close(ctx)
|
|
instance, err := host.CreateInstance(
|
|
ctx,
|
|
instanceConfig(t, 1, "10.144.0.1", 0, false, false),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("create instance: %v", err)
|
|
}
|
|
defer instance.Close(ctx)
|
|
if err := instance.Start(ctx); err != nil {
|
|
t.Fatalf("start instance: %v", err)
|
|
}
|
|
if state := instance.State(); state != corehost.StateRunning {
|
|
t.Fatalf("state = %d, want running", state)
|
|
}
|
|
if err := instance.Start(ctx); err == nil {
|
|
t.Fatal("started running instance twice")
|
|
}
|
|
if state := instance.State(); state != corehost.StateRunning {
|
|
t.Fatalf("duplicate start terminated instance: state=%d", state)
|
|
}
|
|
if err := instance.Stop(ctx); err != nil {
|
|
t.Fatalf("stop instance: %v", err)
|
|
}
|
|
if err := instance.Wait(ctx); err != nil {
|
|
t.Fatalf("wait for instance: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPublicFacadeConnectsTwoCoresAndExchangesPacket(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
sockets := &recordingSocketFactory{}
|
|
host, err := corehost.New(ctx, corehost.Options{
|
|
Platform: platform.Services{Sockets: sockets},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create host: %v", err)
|
|
}
|
|
defer host.Close(ctx)
|
|
server, err := host.CreateInstance(
|
|
ctx,
|
|
instanceConfig(t, 1, "10.144.0.1", 0, false, true),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("create server: %v", err)
|
|
}
|
|
defer server.Close(ctx)
|
|
if err := server.Start(ctx); err != nil {
|
|
t.Fatalf("start server: %v", err)
|
|
}
|
|
port := sockets.listenerPort(t)
|
|
client, err := host.CreateInstance(
|
|
ctx,
|
|
instanceConfig(t, 2, "10.144.0.2", port, true, false),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("create client: %v", err)
|
|
}
|
|
defer client.Close(ctx)
|
|
if err := client.Start(ctx); err != nil {
|
|
t.Fatalf("start client: %v", err)
|
|
}
|
|
|
|
packet := ipv4Packet(
|
|
net.IPv4(10, 144, 0, 2),
|
|
net.IPv4(10, 144, 0, 1),
|
|
[]byte("public-go-host"),
|
|
)
|
|
var received []byte
|
|
exchangeDeadline := time.Now().Add(10 * time.Second)
|
|
for time.Now().Before(exchangeDeadline) {
|
|
if err := client.SendPacket(ctx, packet); err != nil {
|
|
t.Fatalf("send packet: %v", err)
|
|
}
|
|
receiveContext, stopReceive := context.WithTimeout(ctx, 100*time.Millisecond)
|
|
received, err = server.ReceivePacket(receiveContext)
|
|
timedOut := errors.Is(err, context.DeadlineExceeded)
|
|
stopReceive()
|
|
if err == nil {
|
|
break
|
|
}
|
|
if !timedOut {
|
|
t.Fatalf("receive packet: %v", err)
|
|
}
|
|
}
|
|
if string(received) != string(packet) {
|
|
t.Fatalf("received packet = %x, want %x", received, packet)
|
|
}
|
|
event := waitForEvent(t, ctx, client.Events(), "peer_added")
|
|
if !strings.Contains(event.Message, "PeerAdded") {
|
|
t.Fatalf("peer event message = %q", event.Message)
|
|
}
|
|
peers, err := client.ListPeer(ctx)
|
|
if err != nil {
|
|
t.Fatalf("list connected peers: %v", err)
|
|
}
|
|
if len(peers) == 0 {
|
|
t.Fatal("connected peer list is empty")
|
|
}
|
|
|
|
if err := client.Stop(ctx); err != nil {
|
|
t.Fatalf("stop client: %v", err)
|
|
}
|
|
if err := server.Stop(ctx); err != nil {
|
|
t.Fatalf("stop server: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPublicConfigUsesCoreTCPPortForward(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
sockets := &recordingSocketFactory{}
|
|
host, err := corehost.New(ctx, corehost.Options{
|
|
Platform: platform.Services{Sockets: sockets},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create host: %v", err)
|
|
}
|
|
defer host.Close(ctx)
|
|
|
|
server, err := host.CreateInstance(
|
|
ctx,
|
|
instanceConfig(t, 201, "10.144.0.201", 0, false, true),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("create server: %v", err)
|
|
}
|
|
defer server.Close(ctx)
|
|
if err := server.Start(ctx); err != nil {
|
|
t.Fatalf("start server: %v", err)
|
|
}
|
|
underlayPort := sockets.listenerPort(t)
|
|
overlayListener := listenTCPEventually(t, ctx, server)
|
|
defer overlayListener.Close()
|
|
go func() {
|
|
for {
|
|
connection, acceptErr := overlayListener.Accept()
|
|
if acceptErr != nil {
|
|
return
|
|
}
|
|
go func() {
|
|
defer connection.Close()
|
|
_, _ = io.Copy(connection, connection)
|
|
}()
|
|
}
|
|
}()
|
|
|
|
probe, err := net.Listen("tcp4", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("reserve port-forward address: %v", err)
|
|
}
|
|
forwardAddress := netip.MustParseAddrPort(probe.Addr().String())
|
|
if err := probe.Close(); err != nil {
|
|
t.Fatalf("release port-forward address: %v", err)
|
|
}
|
|
destination := netip.AddrPortFrom(
|
|
netip.MustParseAddr("10.144.0.201"),
|
|
uint16(overlayListener.Addr().(*net.TCPAddr).Port),
|
|
)
|
|
clientConfig, err := corehost.NewInstanceConfigBuilder("default").
|
|
NetworkSecret("test").
|
|
Hostname("go-host-202").
|
|
IPv4(netip.MustParsePrefix("10.144.0.202/24")).
|
|
AddPeers(fmt.Sprintf("tcp://127.0.0.1:%d", underlayPort)).
|
|
AddPortForwards(corehost.PortForwardConfig{
|
|
Protocol: corehost.PortForwardTCP,
|
|
Bind: forwardAddress,
|
|
Destination: destination,
|
|
}).
|
|
P2P(corehost.P2PPolicy{Disable: true}).
|
|
Encryption(false).
|
|
Build()
|
|
if err != nil {
|
|
t.Fatalf("build client config: %v", err)
|
|
}
|
|
client, err := host.CreateInstance(ctx, clientConfig)
|
|
if err != nil {
|
|
t.Fatalf("create client: %v", err)
|
|
}
|
|
defer client.Close(ctx)
|
|
if err := client.Start(ctx); err != nil {
|
|
t.Fatalf("start client: %v", err)
|
|
}
|
|
|
|
payload := []byte("core-owned-port-forward")
|
|
var lastErr error
|
|
forwardDeadline := time.Now().Add(10 * time.Second)
|
|
for time.Now().Before(forwardDeadline) {
|
|
connection, dialErr := net.DialTimeout(
|
|
"tcp4",
|
|
forwardAddress.String(),
|
|
200*time.Millisecond,
|
|
)
|
|
if dialErr == nil {
|
|
_ = connection.SetDeadline(time.Now().Add(time.Second))
|
|
_, writeErr := connection.Write(payload)
|
|
response := make([]byte, len(payload))
|
|
_, readErr := io.ReadFull(connection, response)
|
|
_ = connection.Close()
|
|
if writeErr == nil && readErr == nil &&
|
|
string(response) == string(payload) {
|
|
return
|
|
}
|
|
if writeErr == nil && readErr == nil {
|
|
lastErr = fmt.Errorf("forwarded response = %q", response)
|
|
} else {
|
|
lastErr = errors.Join(writeErr, readErr)
|
|
}
|
|
} else {
|
|
lastErr = dialErr
|
|
}
|
|
select {
|
|
case <-time.After(20 * time.Millisecond):
|
|
case <-ctx.Done():
|
|
t.Fatalf("wait for core port forward: %v", ctx.Err())
|
|
}
|
|
}
|
|
t.Fatalf("core TCP port forward did not carry traffic: %v", lastErr)
|
|
}
|
|
|
|
func TestPublicConfigUsesCoreUDPPortForwardForMaximumPayload(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
sockets := &recordingSocketFactory{}
|
|
host, err := corehost.New(ctx, corehost.Options{
|
|
Platform: platform.Services{Sockets: sockets},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create host: %v", err)
|
|
}
|
|
defer host.Close(ctx)
|
|
|
|
server, err := host.CreateInstance(
|
|
ctx,
|
|
instanceConfig(t, 203, "10.144.0.203", 0, false, true),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("create server: %v", err)
|
|
}
|
|
defer server.Close(ctx)
|
|
if err := server.Start(ctx); err != nil {
|
|
t.Fatalf("start server: %v", err)
|
|
}
|
|
underlayPort := sockets.listenerPort(t)
|
|
reflectorDone := make(chan error, 1)
|
|
go func() {
|
|
for {
|
|
packet, receiveErr := server.ReceivePacket(ctx)
|
|
if receiveErr != nil {
|
|
reflectorDone <- receiveErr
|
|
return
|
|
}
|
|
response, ok := reflectIPv4UDPFragment(packet)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if sendErr := server.SendPacket(ctx, response); sendErr != nil {
|
|
reflectorDone <- sendErr
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
probe, err := net.ListenUDP(
|
|
"udp4",
|
|
&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)},
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("reserve UDP port-forward address: %v", err)
|
|
}
|
|
forwardAddress := netip.MustParseAddrPort(probe.LocalAddr().String())
|
|
if err := probe.Close(); err != nil {
|
|
t.Fatalf("release UDP port-forward address: %v", err)
|
|
}
|
|
destination := netip.AddrPortFrom(
|
|
netip.MustParseAddr("10.144.0.203"),
|
|
41000,
|
|
)
|
|
clientConfig, err := corehost.NewInstanceConfigBuilder("default").
|
|
NetworkSecret("test").
|
|
Hostname("go-host-204").
|
|
IPv4(netip.MustParsePrefix("10.144.0.204/24")).
|
|
AddPeers(fmt.Sprintf("tcp://127.0.0.1:%d", underlayPort)).
|
|
AddPortForwards(corehost.PortForwardConfig{
|
|
Protocol: corehost.PortForwardUDP,
|
|
Bind: forwardAddress,
|
|
Destination: destination,
|
|
}).
|
|
P2P(corehost.P2PPolicy{Disable: true}).
|
|
Encryption(false).
|
|
Build()
|
|
if err != nil {
|
|
t.Fatalf("build client config: %v", err)
|
|
}
|
|
client, err := host.CreateInstance(ctx, clientConfig)
|
|
if err != nil {
|
|
t.Fatalf("create client: %v", err)
|
|
}
|
|
defer client.Close(ctx)
|
|
if err := client.Start(ctx); err != nil {
|
|
t.Fatalf("start client: %v", err)
|
|
}
|
|
eventContext, stopEvent := context.WithTimeout(ctx, 5*time.Second)
|
|
_ = waitForEvent(
|
|
t,
|
|
eventContext,
|
|
client.Events(),
|
|
"gateway_port_forward_added",
|
|
)
|
|
stopEvent()
|
|
waitForOverlayRoute(
|
|
t,
|
|
ctx,
|
|
client,
|
|
netip.MustParseAddr("10.144.0.203"),
|
|
)
|
|
waitForOverlayRoute(
|
|
t,
|
|
ctx,
|
|
server,
|
|
netip.MustParseAddr("10.144.0.204"),
|
|
)
|
|
|
|
connection, err := net.DialUDP(
|
|
"udp4",
|
|
nil,
|
|
net.UDPAddrFromAddrPort(forwardAddress),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("dial UDP port forward: %v", err)
|
|
}
|
|
defer connection.Close()
|
|
var lastErr error
|
|
forwardDeadline := time.Now().Add(10 * time.Second)
|
|
for time.Now().Before(forwardDeadline) {
|
|
if err := connection.SetDeadline(time.Now().Add(500 * time.Millisecond)); err != nil {
|
|
t.Fatalf("set UDP port-forward deadline: %v", err)
|
|
}
|
|
if _, err := connection.Write([]byte("warmup")); err != nil {
|
|
lastErr = err
|
|
continue
|
|
}
|
|
warmup := make([]byte, len("warmup"))
|
|
length, err := connection.Read(warmup)
|
|
if err != nil {
|
|
lastErr = err
|
|
select {
|
|
case reflectorErr := <-reflectorDone:
|
|
t.Fatalf("reflect UDP port-forward packet: %v", reflectorErr)
|
|
default:
|
|
}
|
|
continue
|
|
}
|
|
if string(warmup[:length]) == "warmup" {
|
|
lastErr = nil
|
|
break
|
|
}
|
|
lastErr = fmt.Errorf("UDP port-forward warmup response = %q", warmup[:length])
|
|
}
|
|
if lastErr != nil {
|
|
t.Fatalf("core UDP port forward did not become ready: %v", lastErr)
|
|
}
|
|
|
|
payload := make([]byte, 65_507)
|
|
for index := range payload {
|
|
payload[index] = byte(index)
|
|
}
|
|
response := make([]byte, len(payload))
|
|
forwardDeadline = time.Now().Add(10 * time.Second)
|
|
for time.Now().Before(forwardDeadline) {
|
|
if err := connection.SetDeadline(time.Now().Add(500 * time.Millisecond)); err != nil {
|
|
t.Fatalf("set UDP port-forward deadline: %v", err)
|
|
}
|
|
if _, err := connection.Write(payload); err != nil {
|
|
lastErr = err
|
|
continue
|
|
}
|
|
length, err := connection.Read(response)
|
|
if err != nil {
|
|
lastErr = err
|
|
select {
|
|
case reflectorErr := <-reflectorDone:
|
|
t.Fatalf("reflect maximum UDP port-forward payload: %v", reflectorErr)
|
|
default:
|
|
}
|
|
continue
|
|
}
|
|
if string(response[:length]) == "warmup" {
|
|
continue
|
|
}
|
|
if length != len(payload) {
|
|
t.Fatalf(
|
|
"forwarded UDP payload length = %d, want %d",
|
|
length,
|
|
len(payload),
|
|
)
|
|
}
|
|
if !bytes.Equal(response[:length], payload) {
|
|
t.Fatal("forwarded UDP payload contents changed")
|
|
}
|
|
return
|
|
}
|
|
t.Fatalf("core UDP port forward did not carry maximum payload: %v", lastErr)
|
|
}
|
|
|
|
func reflectIPv4UDPFragment(packet []byte) ([]byte, bool) {
|
|
if len(packet) < 20 || packet[0]>>4 != 4 || packet[9] != 17 {
|
|
return nil, false
|
|
}
|
|
headerLength := int(packet[0]&0x0f) * 4
|
|
totalLength := int(binary.BigEndian.Uint16(packet[2:4]))
|
|
if headerLength < 20 || totalLength < headerLength ||
|
|
totalLength > len(packet) {
|
|
return nil, false
|
|
}
|
|
response := append([]byte(nil), packet[:totalLength]...)
|
|
source := append([]byte(nil), response[12:16]...)
|
|
copy(response[12:16], response[16:20])
|
|
copy(response[16:20], source)
|
|
fragmentOffset := binary.BigEndian.Uint16(response[6:8]) & 0x1fff
|
|
if fragmentOffset == 0 {
|
|
if totalLength < headerLength+8 {
|
|
return nil, false
|
|
}
|
|
// Swapping both endpoints preserves the UDP checksum sum, including
|
|
// its pseudo-header, so only the IPv4 header checksum must change.
|
|
sourcePort := append([]byte(nil), response[headerLength:headerLength+2]...)
|
|
copy(
|
|
response[headerLength:headerLength+2],
|
|
response[headerLength+2:headerLength+4],
|
|
)
|
|
copy(response[headerLength+2:headerLength+4], sourcePort)
|
|
}
|
|
response[10], response[11] = 0, 0
|
|
binary.BigEndian.PutUint16(
|
|
response[10:12],
|
|
ipv4Checksum(response[:headerLength]),
|
|
)
|
|
return response, true
|
|
}
|
|
|
|
func waitForOverlayRoute(
|
|
t *testing.T,
|
|
ctx context.Context,
|
|
instance *corehost.Instance,
|
|
target netip.Addr,
|
|
) {
|
|
t.Helper()
|
|
octets := target.As4()
|
|
targetValue := binary.BigEndian.Uint32(octets[:])
|
|
for {
|
|
routes, err := instance.ListRoute(ctx)
|
|
if err != nil {
|
|
t.Fatalf("list routes: %v", err)
|
|
}
|
|
for _, route := range routes {
|
|
if route.GetIpv4Addr().GetAddress().GetAddr() == targetValue {
|
|
return
|
|
}
|
|
}
|
|
select {
|
|
case <-time.After(20 * time.Millisecond):
|
|
case <-ctx.Done():
|
|
t.Fatalf("wait for overlay route to %s: %v", target, ctx.Err())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPublicEventStreamClosesWithInstance(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
host, err := corehost.New(ctx, corehost.Options{})
|
|
if err != nil {
|
|
t.Fatalf("create host: %v", err)
|
|
}
|
|
defer host.Close(ctx)
|
|
instance, err := host.CreateInstance(
|
|
ctx,
|
|
instanceConfig(t, 3, "10.144.0.3", 0, false, false),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("create instance: %v", err)
|
|
}
|
|
events := instance.Events()
|
|
if err := instance.Close(ctx); err != nil {
|
|
t.Fatalf("close instance: %v", err)
|
|
}
|
|
select {
|
|
case _, open := <-events:
|
|
if open {
|
|
t.Fatal("event stream remained open after instance close")
|
|
}
|
|
case <-ctx.Done():
|
|
t.Fatal("event stream did not close with instance")
|
|
}
|
|
}
|
|
|
|
func TestEmbeddedCoreInfoIsPublicWithoutArtifactBytes(t *testing.T) {
|
|
info := corehost.CoreInfo()
|
|
if len(info.EasyTierCommit) != 40 {
|
|
t.Fatalf("EasyTier commit = %q", info.EasyTierCommit)
|
|
}
|
|
if len(info.SHA256) != 64 {
|
|
t.Fatalf("EasyTier SHA-256 = %q", info.SHA256)
|
|
}
|
|
if info.EasyTierCommit != hostproto.EasyTierCommit {
|
|
t.Fatalf(
|
|
"EasyTier artifact commit %q != protobuf commit %q",
|
|
info.EasyTierCommit,
|
|
hostproto.EasyTierCommit,
|
|
)
|
|
}
|
|
if len(hostproto.SchemaSHA256) != 64 {
|
|
t.Fatalf("protobuf schema SHA-256 = %q", hostproto.SchemaSHA256)
|
|
}
|
|
}
|
|
|
|
func TestPublicCreateInstanceAcceptsSecureModeConfig(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
host, err := corehost.New(ctx, corehost.Options{})
|
|
if err != nil {
|
|
t.Fatalf("create host: %v", err)
|
|
}
|
|
defer host.Close(ctx)
|
|
|
|
privateKey := make([]byte, 32)
|
|
for index := range privateKey {
|
|
privateKey[index] = byte(index + 1)
|
|
}
|
|
config, err := corehost.NewInstanceConfigBuilder("secure-test").
|
|
NetworkSecret("test").
|
|
P2P(corehost.P2PPolicy{Disable: true}).
|
|
Encryption(false).
|
|
SecureModeWithPrivateKey(privateKey).
|
|
Build()
|
|
if err != nil {
|
|
t.Fatalf("build secure-mode config: %v", err)
|
|
}
|
|
instance, err := host.CreateInstance(ctx, config)
|
|
if err != nil {
|
|
t.Fatalf("create secure-mode instance: %v", err)
|
|
}
|
|
defer instance.Close(ctx)
|
|
}
|
|
|
|
func instanceConfig(
|
|
t *testing.T,
|
|
id int,
|
|
ipv4 string,
|
|
port int,
|
|
connect bool,
|
|
listen bool,
|
|
) corehost.InstanceConfig {
|
|
t.Helper()
|
|
builder := corehost.NewInstanceConfigBuilder("default").
|
|
NetworkSecret("test").
|
|
Hostname(fmt.Sprintf("go-host-%d", id)).
|
|
IPv4(netip.MustParsePrefix(ipv4 + "/24")).
|
|
P2P(corehost.P2PPolicy{Disable: true}).
|
|
Encryption(false)
|
|
if connect {
|
|
builder.AddPeers(fmt.Sprintf("tcp://127.0.0.1:%d", port))
|
|
} else if listen {
|
|
builder.AddListeners(fmt.Sprintf("tcp://127.0.0.1:%d", port))
|
|
}
|
|
config, err := builder.Build()
|
|
if err != nil {
|
|
t.Fatalf("build instance config: %v", err)
|
|
}
|
|
return config
|
|
}
|
|
|
|
func waitForEvent(
|
|
t *testing.T,
|
|
ctx context.Context,
|
|
events <-chan corehost.Event,
|
|
kind string,
|
|
) corehost.Event {
|
|
t.Helper()
|
|
for {
|
|
select {
|
|
case event, open := <-events:
|
|
if !open {
|
|
t.Fatalf("event stream closed before %q", kind)
|
|
}
|
|
if event.Kind == kind {
|
|
return event
|
|
}
|
|
case <-ctx.Done():
|
|
t.Fatalf("wait for event %q: %v", kind, ctx.Err())
|
|
}
|
|
}
|
|
}
|
|
|
|
type recordingSocketFactory struct {
|
|
inner netstd.SocketFactory
|
|
mu sync.Mutex
|
|
port int
|
|
}
|
|
|
|
func (factory *recordingSocketFactory) ConnectTCP(
|
|
ctx context.Context,
|
|
options platform.TCPConnectOptions,
|
|
) (net.Conn, error) {
|
|
return factory.inner.ConnectTCP(ctx, options)
|
|
}
|
|
|
|
func (factory *recordingSocketFactory) BindUDP(
|
|
ctx context.Context,
|
|
options platform.UDPBindOptions,
|
|
) (net.PacketConn, error) {
|
|
return factory.inner.BindUDP(ctx, options)
|
|
}
|
|
|
|
func (factory *recordingSocketFactory) ListenTCP(
|
|
ctx context.Context,
|
|
options platform.TCPListenOptions,
|
|
) (net.Listener, error) {
|
|
listener, err := factory.inner.ListenTCP(ctx, options)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
factory.mu.Lock()
|
|
factory.port = listener.Addr().(*net.TCPAddr).Port
|
|
factory.mu.Unlock()
|
|
return listener, nil
|
|
}
|
|
|
|
func (factory *recordingSocketFactory) listenerPort(t *testing.T) int {
|
|
t.Helper()
|
|
factory.mu.Lock()
|
|
defer factory.mu.Unlock()
|
|
if factory.port == 0 {
|
|
t.Fatal("EasyTier did not create a TCP listener")
|
|
}
|
|
return factory.port
|
|
}
|
|
|
|
func ipv4Packet(source, destination net.IP, payload []byte) []byte {
|
|
packet := make([]byte, 20+len(payload))
|
|
packet[0] = 0x45
|
|
binary.BigEndian.PutUint16(packet[2:4], uint16(len(packet)))
|
|
packet[8] = 64
|
|
packet[9] = 1
|
|
copy(packet[12:16], source.To4())
|
|
copy(packet[16:20], destination.To4())
|
|
copy(packet[20:], payload)
|
|
binary.BigEndian.PutUint16(packet[10:12], ipv4Checksum(packet[:20]))
|
|
return packet
|
|
}
|
|
|
|
func ipv4Checksum(header []byte) uint16 {
|
|
var sum uint32
|
|
for index := 0; index < len(header); index += 2 {
|
|
sum += uint32(binary.BigEndian.Uint16(header[index : index+2]))
|
|
}
|
|
for sum > 0xffff {
|
|
sum = (sum & 0xffff) + (sum >> 16)
|
|
}
|
|
return ^uint16(sum)
|
|
}
|