mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-13 11:05:46 +00:00
930 lines
30 KiB
Go
930 lines
30 KiB
Go
package tun
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"crypto/rand"
|
||
|
|
"encoding/binary"
|
||
|
|
"io"
|
||
|
|
mrand "math/rand/v2"
|
||
|
|
"net/netip"
|
||
|
|
"sync"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/xtls/xray-core/common/buf"
|
||
|
|
"github.com/xtls/xray-core/common/net"
|
||
|
|
"gvisor.dev/gvisor/pkg/buffer"
|
||
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||
|
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||
|
|
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||
|
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||
|
|
gstack "gvisor.dev/gvisor/pkg/tcpip/stack"
|
||
|
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||
|
|
)
|
||
|
|
|
||
|
|
const testMTU = 1500
|
||
|
|
|
||
|
|
// link is a tun with the test on its other side
|
||
|
|
type link struct {
|
||
|
|
// in is what the stack reads
|
||
|
|
in chan []byte
|
||
|
|
// out takes what the stack writes
|
||
|
|
out func(packet []byte)
|
||
|
|
// segments is set if the link takes segments that are larger than the MTU, wrong tells what was wrong with one
|
||
|
|
segments bool
|
||
|
|
wrong func(args ...any)
|
||
|
|
done chan struct{}
|
||
|
|
once sync.Once
|
||
|
|
}
|
||
|
|
|
||
|
|
func newLink() *link {
|
||
|
|
return &link{in: make(chan []byte, 512), done: make(chan struct{})}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *link) Start() error { return nil }
|
||
|
|
func (l *link) Name() (string, error) { return "test", nil }
|
||
|
|
func (l *link) Index() (int, error) { return 1, nil }
|
||
|
|
|
||
|
|
func (l *link) Close() error {
|
||
|
|
l.once.Do(func() { close(l.done) })
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *link) ReadPacket(b []byte) ([]byte, error) {
|
||
|
|
select {
|
||
|
|
case packet := <-l.in:
|
||
|
|
return b[packetRoom : packetRoom+copy(b[packetRoom:], packet)], nil
|
||
|
|
case <-l.done:
|
||
|
|
return nil, io.EOF
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (l *link) WritePacket(header, payload []byte) error {
|
||
|
|
l.out(append(append(make([]byte, 0, len(header)+len(payload)), header...), payload...))
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Segments implements segmenter
|
||
|
|
func (l *link) Segments() bool {
|
||
|
|
return l.segments
|
||
|
|
}
|
||
|
|
|
||
|
|
// WriteSegment implements segmenter as the system does when it sends a segment on: a packet for every mss bytes
|
||
|
|
func (l *link) WriteSegment(header []byte, payload [][]byte, mss int) error {
|
||
|
|
data := bytes.Join(payload, nil)
|
||
|
|
size := ipv4HeaderSize
|
||
|
|
if header[0]>>4 == 6 {
|
||
|
|
size = ipv6HeaderSize
|
||
|
|
}
|
||
|
|
if expected := ^checksum(pseudoSum(header[:size], protoTCP, len(header)-size+len(data))); binary.BigEndian.Uint16(header[size+16:]) != expected {
|
||
|
|
l.wrong("checksum of a segment for the link to split: ", header[size+16:size+18], ", expected ", expected)
|
||
|
|
}
|
||
|
|
for sent := 0; sent < len(data); {
|
||
|
|
n := min(mss, len(data)-sent)
|
||
|
|
h := bytes.Clone(header)
|
||
|
|
t := h[size:]
|
||
|
|
binary.BigEndian.PutUint32(t[4:], binary.BigEndian.Uint32(t[4:])+uint32(sent))
|
||
|
|
if sent+n < len(data) {
|
||
|
|
t[13] &^= tcpFin | tcpPsh
|
||
|
|
}
|
||
|
|
t[16], t[17] = 0, 0
|
||
|
|
setIPLength(h[:size], len(h)+n)
|
||
|
|
binary.BigEndian.PutUint16(t[16:], transportChecksum(h[:size], protoTCP, t, data[sent:sent+n]))
|
||
|
|
l.out(append(h, data[sent:sent+n]...))
|
||
|
|
sent += n
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func newTestStack(t *testing.T, l *link, handle func(net.Conn, net.Destination)) *stack {
|
||
|
|
s := &stack{ctx: context.Background(), tun: l, mtu: testMTU, window: tcpWindow, handle: handle, conns: make(map[flow]*tcpConn)}
|
||
|
|
if l.segments {
|
||
|
|
s.segmenter, s.window, l.wrong = l, tcpLargeWindow, t.Error
|
||
|
|
}
|
||
|
|
s.udp = newUdpConnectionHandler(handle, s.writeUDP)
|
||
|
|
s.Start()
|
||
|
|
t.Cleanup(func() {
|
||
|
|
s.Close()
|
||
|
|
l.Close()
|
||
|
|
})
|
||
|
|
return s
|
||
|
|
}
|
||
|
|
|
||
|
|
// forgotten waits until the stack has no connection left
|
||
|
|
func forgotten(t *testing.T, s *stack) {
|
||
|
|
for deadline := time.Now().Add(10 * time.Second); ; time.Sleep(10 * time.Millisecond) {
|
||
|
|
s.mu.Lock()
|
||
|
|
n := len(s.conns)
|
||
|
|
s.mu.Unlock()
|
||
|
|
if n == 0 {
|
||
|
|
if opening := s.opening.Load(); opening != 0 {
|
||
|
|
t.Fatal("connections that are still counted as being opened: ", opening)
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if time.Now().After(deadline) {
|
||
|
|
t.Fatal("connections left in the stack: ", n)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// echo sends back what it reads, and closes when the other side has
|
||
|
|
func echo(conn net.Conn, _ net.Destination) {
|
||
|
|
defer conn.Close()
|
||
|
|
for {
|
||
|
|
mb, err := conn.(buf.Reader).ReadMultiBuffer()
|
||
|
|
if err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
if conn.(buf.Writer).WriteMultiBuffer(mb) != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// A real TCP on the other side of a tun that loses packets both ways gets back what it sent.
|
||
|
|
func TestTCPEcho(t *testing.T) {
|
||
|
|
for _, test := range []struct {
|
||
|
|
name string
|
||
|
|
// one packet of lose is lost
|
||
|
|
lose int
|
||
|
|
size int
|
||
|
|
// the tun takes segments that are larger than its MTU
|
||
|
|
segments bool
|
||
|
|
}{
|
||
|
|
{"lossless", 0, 16 << 20, false},
|
||
|
|
{"lossy", 100, 4 << 20, false},
|
||
|
|
{"very lossy", 20, 512 << 10, false},
|
||
|
|
{"lossless, large segments", 0, 16 << 20, true},
|
||
|
|
{"lossy, large segments", 100, 4 << 20, true},
|
||
|
|
} {
|
||
|
|
t.Run(test.name, func(t *testing.T) {
|
||
|
|
l := newLink()
|
||
|
|
l.segments = test.segments
|
||
|
|
random := mrand.New(mrand.NewPCG(1, uint64(test.lose)))
|
||
|
|
var mu sync.Mutex
|
||
|
|
lost := func() bool {
|
||
|
|
mu.Lock()
|
||
|
|
defer mu.Unlock()
|
||
|
|
return test.lose != 0 && random.IntN(test.lose) == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// the system on the other side of the tun
|
||
|
|
system := gstack.New(gstack.Options{
|
||
|
|
NetworkProtocols: []gstack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||
|
|
TransportProtocols: []gstack.TransportProtocolFactory{tcp.NewProtocol},
|
||
|
|
})
|
||
|
|
defer system.Destroy()
|
||
|
|
endpoint := channel.New(512, testMTU, "")
|
||
|
|
if err := system.CreateNIC(1, endpoint); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
for _, address := range []tcpip.ProtocolAddress{
|
||
|
|
{Protocol: ipv4.ProtocolNumber, AddressWithPrefix: tcpip.AddrFrom4([4]byte{10, 0, 0, 1}).WithPrefix()},
|
||
|
|
{Protocol: ipv6.ProtocolNumber, AddressWithPrefix: tcpip.AddrFrom16([16]byte{0xfd, 15: 1}).WithPrefix()},
|
||
|
|
} {
|
||
|
|
if err := system.AddProtocolAddress(1, address, gstack.AddressProperties{}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
system.SetRouteTable([]tcpip.Route{{Destination: header.IPv4EmptySubnet, NIC: 1}, {Destination: header.IPv6EmptySubnet, NIC: 1}})
|
||
|
|
sack := tcpip.TCPSACKEnabled(true)
|
||
|
|
system.SetTransportProtocolOption(tcp.ProtocolNumber, &sack)
|
||
|
|
// without RACK: after a tail loss probe of it that is lost too, it has no timer left to send what was lost again
|
||
|
|
recovery := tcpip.TCPRecovery(0)
|
||
|
|
system.SetTransportProtocolOption(tcp.ProtocolNumber, &recovery)
|
||
|
|
|
||
|
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
|
defer cancel()
|
||
|
|
go func() {
|
||
|
|
for {
|
||
|
|
packet := endpoint.ReadContext(ctx)
|
||
|
|
if packet == nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
var b []byte
|
||
|
|
for _, part := range packet.AsSlices() {
|
||
|
|
b = append(b, part...)
|
||
|
|
}
|
||
|
|
packet.DecRef()
|
||
|
|
if !lost() {
|
||
|
|
select {
|
||
|
|
case l.in <- b:
|
||
|
|
default:
|
||
|
|
// as a tun does when its queue is full
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
l.out = func(b []byte) {
|
||
|
|
if lost() {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
protocol := ipv4.ProtocolNumber
|
||
|
|
if b[0]>>4 == 6 {
|
||
|
|
protocol = ipv6.ProtocolNumber
|
||
|
|
}
|
||
|
|
packet := gstack.NewPacketBuffer(gstack.PacketBufferOptions{Payload: buffer.MakeWithData(b)})
|
||
|
|
endpoint.InjectInbound(protocol, packet)
|
||
|
|
packet.DecRef()
|
||
|
|
}
|
||
|
|
s := newTestStack(t, l, echo)
|
||
|
|
|
||
|
|
for _, to := range []struct {
|
||
|
|
address tcpip.Address
|
||
|
|
protocol tcpip.NetworkProtocolNumber
|
||
|
|
}{
|
||
|
|
{tcpip.AddrFrom4([4]byte{10, 0, 0, 2}), ipv4.ProtocolNumber},
|
||
|
|
{tcpip.AddrFrom16([16]byte{0xfd, 15: 2}), ipv6.ProtocolNumber},
|
||
|
|
} {
|
||
|
|
conn, err := gonet.DialTCP(system, tcpip.FullAddress{NIC: 1, Addr: to.address, Port: 80}, to.protocol)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
sent := make([]byte, test.size)
|
||
|
|
rand.Read(sent)
|
||
|
|
go func() {
|
||
|
|
conn.Write(sent)
|
||
|
|
conn.CloseWrite()
|
||
|
|
}()
|
||
|
|
conn.SetReadDeadline(time.Now().Add(2 * time.Minute))
|
||
|
|
received, err := io.ReadAll(conn)
|
||
|
|
if err != nil || !bytes.Equal(received, sent) {
|
||
|
|
t.Fatal("received ", len(received), " of ", len(sent), " bytes, equal: ", bytes.Equal(received, sent), ", error: ", err)
|
||
|
|
}
|
||
|
|
conn.Close()
|
||
|
|
forgotten(t, s)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// peer is the system's end of one connection, with every segment made by the test
|
||
|
|
type peer struct {
|
||
|
|
t *testing.T
|
||
|
|
l *link
|
||
|
|
sent chan []byte
|
||
|
|
src, dst netip.AddrPort
|
||
|
|
// seq is the next that the peer sends, ack the next that it expects
|
||
|
|
seq, ack uint32
|
||
|
|
window uint16
|
||
|
|
}
|
||
|
|
|
||
|
|
func newPeer(t *testing.T, src, dst string) (*peer, *link) {
|
||
|
|
l := newLink()
|
||
|
|
p := &peer{t: t, l: l, sent: make(chan []byte, 1024), src: netip.MustParseAddrPort(src), dst: netip.MustParseAddrPort(dst), seq: 1000, window: 0xffff}
|
||
|
|
l.out = func(b []byte) { p.sent <- b }
|
||
|
|
return p, l
|
||
|
|
}
|
||
|
|
|
||
|
|
// send gives the stack a segment of the peer
|
||
|
|
func (p *peer) send(seq uint32, flags byte, options, payload []byte) {
|
||
|
|
var b [ipv6HeaderSize + tcpHeaderSize + 40]byte
|
||
|
|
n := putIPHeader(b[:], protoTCP, p.src.Addr(), p.dst.Addr())
|
||
|
|
t := b[n : n+tcpHeaderSize+len(options)]
|
||
|
|
binary.BigEndian.PutUint16(t, p.src.Port())
|
||
|
|
binary.BigEndian.PutUint16(t[2:], p.dst.Port())
|
||
|
|
binary.BigEndian.PutUint32(t[4:], seq)
|
||
|
|
binary.BigEndian.PutUint32(t[8:], p.ack)
|
||
|
|
t[12] = byte(len(t)/4) << 4
|
||
|
|
t[13] = flags
|
||
|
|
binary.BigEndian.PutUint16(t[14:], p.window)
|
||
|
|
copy(t[tcpHeaderSize:], options)
|
||
|
|
setIPLength(b[:n], n+len(t)+len(payload))
|
||
|
|
binary.BigEndian.PutUint16(t[16:], transportChecksum(b[:n], protoTCP, t, payload))
|
||
|
|
p.l.in <- append(b[:n+len(t):n+len(t)], payload...)
|
||
|
|
}
|
||
|
|
|
||
|
|
// packet takes the next packet that the stack wrote
|
||
|
|
func (p *peer) packet() []byte {
|
||
|
|
p.t.Helper()
|
||
|
|
select {
|
||
|
|
case b := <-p.sent:
|
||
|
|
return b
|
||
|
|
case <-time.After(5 * time.Second):
|
||
|
|
p.t.Fatal("no packet from the stack")
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// receive takes the next segment that the stack wrote and checks its addresses and checksums
|
||
|
|
func (p *peer) receive() segment {
|
||
|
|
p.t.Helper()
|
||
|
|
b := p.packet()
|
||
|
|
size := ipv4HeaderSize
|
||
|
|
if b[0]>>4 == 6 {
|
||
|
|
size = ipv6HeaderSize
|
||
|
|
} else if checksum(sum(b[:size], 0)) != 0 {
|
||
|
|
p.t.Fatal("wrong checksum of the ip header")
|
||
|
|
}
|
||
|
|
packet, ok := parsePacket(b)
|
||
|
|
if !ok || packet.proto != protoTCP || packet.src != p.dst.Addr() || packet.dst != p.src.Addr() {
|
||
|
|
p.t.Fatal("not a segment of the connection: ", b)
|
||
|
|
}
|
||
|
|
t := packet.payload
|
||
|
|
if transportChecksum(b[:size], protoTCP, t, nil) != 0 {
|
||
|
|
p.t.Fatal("wrong checksum of the segment")
|
||
|
|
}
|
||
|
|
if binary.BigEndian.Uint16(t) != p.dst.Port() || binary.BigEndian.Uint16(t[2:]) != p.src.Port() {
|
||
|
|
p.t.Fatal("wrong ports: ", t[:4])
|
||
|
|
}
|
||
|
|
offset := int(t[12]>>4) * 4
|
||
|
|
return segment{
|
||
|
|
seq: binary.BigEndian.Uint32(t[4:]),
|
||
|
|
ack: binary.BigEndian.Uint32(t[8:]),
|
||
|
|
flags: t[13],
|
||
|
|
window: binary.BigEndian.Uint16(t[14:]),
|
||
|
|
options: t[tcpHeaderSize:offset],
|
||
|
|
payload: t[offset:],
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// silent fails if the stack writes something within d
|
||
|
|
func (p *peer) silent(d time.Duration) {
|
||
|
|
p.t.Helper()
|
||
|
|
select {
|
||
|
|
case b := <-p.sent:
|
||
|
|
p.t.Fatal("unexpected packet from the stack: ", b)
|
||
|
|
case <-time.After(d):
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// connect opens the connection with the given options in the SYN and returns the options of the answer
|
||
|
|
func (p *peer) connect(options []byte) []byte {
|
||
|
|
p.t.Helper()
|
||
|
|
p.send(p.seq, tcpSyn, options, nil)
|
||
|
|
p.seq++
|
||
|
|
s := p.receive()
|
||
|
|
if s.flags != tcpSyn|tcpAck || s.ack != p.seq {
|
||
|
|
p.t.Fatal("not an answer to the SYN: flags ", s.flags, ", ack ", s.ack)
|
||
|
|
}
|
||
|
|
p.ack = s.seq + 1
|
||
|
|
p.send(p.seq, tcpAck, nil, nil)
|
||
|
|
return s.options
|
||
|
|
}
|
||
|
|
|
||
|
|
// accepted is what a test gets of the connections that the stack hands over
|
||
|
|
type accepted struct {
|
||
|
|
conn net.Conn
|
||
|
|
dest net.Destination
|
||
|
|
}
|
||
|
|
|
||
|
|
func accept(conns chan accepted) func(net.Conn, net.Destination) {
|
||
|
|
return func(conn net.Conn, dest net.Destination) { conns <- accepted{conn, dest} }
|
||
|
|
}
|
||
|
|
|
||
|
|
func take(t *testing.T, conns chan accepted) accepted {
|
||
|
|
t.Helper()
|
||
|
|
select {
|
||
|
|
case a := <-conns:
|
||
|
|
return a
|
||
|
|
case <-time.After(5 * time.Second):
|
||
|
|
t.Fatal("no connection from the stack")
|
||
|
|
return accepted{}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func write(t *testing.T, conn net.Conn, payload []byte) {
|
||
|
|
t.Helper()
|
||
|
|
if err := conn.(buf.Writer).WriteMultiBuffer(buf.MergeBytes(nil, payload)); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func read(t *testing.T, conn net.Conn, expected string) {
|
||
|
|
t.Helper()
|
||
|
|
mb, err := conn.(buf.Reader).ReadMultiBuffer()
|
||
|
|
if err != nil || mb.String() != expected {
|
||
|
|
t.Fatal("read ", mb.String(), ", expected ", expected, ", error: ", err)
|
||
|
|
}
|
||
|
|
buf.ReleaseMulti(mb)
|
||
|
|
}
|
||
|
|
|
||
|
|
// The options of the SYN decide what the answer has, and both ends close the connection in turn.
|
||
|
|
func TestTCPHandshakeAndClose(t *testing.T) {
|
||
|
|
for _, test := range []struct {
|
||
|
|
src, dst string
|
||
|
|
options, expected []byte
|
||
|
|
}{
|
||
|
|
{"10.0.0.1:1234", "10.0.0.2:80", nil, []byte{2, 4, 5, 0xb4}},
|
||
|
|
{"10.0.0.1:1234", "10.0.0.2:80", []byte{2, 4, 2, 0, 4, 2, 1, 3, 3, 7, 0, 0}, []byte{2, 4, 5, 0xb4, 1, 1, 4, 2, 1, 3, 3, tcpWindowShift}},
|
||
|
|
{"[fd00::1]:1234", "[fd00::2]:80", []byte{2, 4, 5, 0xa0, 1, 3, 3, 7}, []byte{2, 4, 5, 0xa0, 1, 3, 3, tcpWindowShift}},
|
||
|
|
} {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, test.src, test.dst)
|
||
|
|
s := newTestStack(t, l, accept(conns))
|
||
|
|
if options := p.connect(test.options); !bytes.Equal(options, test.expected) {
|
||
|
|
t.Fatal("options ", options, ", expected ", test.expected)
|
||
|
|
}
|
||
|
|
a := take(t, conns)
|
||
|
|
if a.dest != net.TCPDestination(net.ParseAddress(p.dst.Addr().String()), net.Port(80)) || a.conn.RemoteAddr().String() != test.src {
|
||
|
|
t.Fatal("connection from ", a.conn.RemoteAddr(), " to ", a.dest)
|
||
|
|
}
|
||
|
|
|
||
|
|
// the system closes its side, what it sent is read first
|
||
|
|
p.send(p.seq, tcpAck|tcpPsh|tcpFin, nil, []byte("abc"))
|
||
|
|
p.seq += 4
|
||
|
|
if s := p.receive(); s.flags != tcpAck || s.ack != p.seq {
|
||
|
|
t.Fatal("the FIN is not acknowledged: flags ", s.flags, ", ack ", s.ack)
|
||
|
|
}
|
||
|
|
read(t, a.conn, "abc")
|
||
|
|
if _, err := a.conn.(buf.Reader).ReadMultiBuffer(); err != io.EOF {
|
||
|
|
t.Fatal("no end of the stream: ", err)
|
||
|
|
}
|
||
|
|
// the other side is still open
|
||
|
|
write(t, a.conn, []byte("def"))
|
||
|
|
if s := p.receive(); string(s.payload) != "def" || s.seq != p.ack || s.flags != tcpAck|tcpPsh {
|
||
|
|
t.Fatal("wrong segment: seq ", s.seq, ", flags ", s.flags, ", payload ", s.payload)
|
||
|
|
}
|
||
|
|
p.ack += 3
|
||
|
|
a.conn.Close()
|
||
|
|
if s := p.receive(); s.flags != tcpFin|tcpAck || s.seq != p.ack {
|
||
|
|
t.Fatal("not a FIN: flags ", s.flags, ", seq ", s.seq)
|
||
|
|
}
|
||
|
|
p.ack++
|
||
|
|
// what the system sends again is not something that nobody reads anymore
|
||
|
|
p.send(p.seq-4, tcpAck|tcpPsh|tcpFin, nil, []byte("abc"))
|
||
|
|
if s := p.receive(); s.flags != tcpAck || s.ack != p.seq {
|
||
|
|
t.Fatal("what was sent again is not acknowledged: flags ", s.flags, ", ack ", s.ack)
|
||
|
|
}
|
||
|
|
p.send(p.seq, tcpAck, nil, nil)
|
||
|
|
forgotten(t, s)
|
||
|
|
// what comes for it now is refused
|
||
|
|
p.send(p.seq, tcpAck, nil, nil)
|
||
|
|
if s := p.receive(); s.flags != tcpRst || s.seq != p.ack {
|
||
|
|
t.Fatal("not a reset: flags ", s.flags, ", seq ", s.seq)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// What comes after a lost segment is kept and told to the system, and is read once the lost one comes.
|
||
|
|
func TestTCPOutOfOrder(t *testing.T) {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
||
|
|
newTestStack(t, l, accept(conns))
|
||
|
|
p.connect([]byte{2, 4, 5, 0xb4, 4, 2, 0, 0})
|
||
|
|
a := take(t, conns)
|
||
|
|
|
||
|
|
sack := func(s segment, blocks ...uint32) {
|
||
|
|
t.Helper()
|
||
|
|
expected := []byte{1, 1, 5, byte(2 + 4*len(blocks))}
|
||
|
|
for _, edge := range blocks {
|
||
|
|
expected = binary.BigEndian.AppendUint32(expected, edge)
|
||
|
|
}
|
||
|
|
if s.flags != tcpAck || s.ack != p.seq || !bytes.Equal(s.options, expected) {
|
||
|
|
t.Fatal("ack ", s.ack, " with options ", s.options, ", expected ", p.seq, " with ", expected)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// "abc" is lost, then "ghi" and "def" come, each told with the block that it is in first
|
||
|
|
p.send(p.seq+6, tcpAck, nil, []byte("ghi"))
|
||
|
|
sack(p.receive(), p.seq+6, p.seq+9)
|
||
|
|
p.send(p.seq+12, tcpAck, nil, []byte("mno"))
|
||
|
|
sack(p.receive(), p.seq+12, p.seq+15, p.seq+6, p.seq+9)
|
||
|
|
p.send(p.seq+3, tcpAck, nil, []byte("def"))
|
||
|
|
sack(p.receive(), p.seq+3, p.seq+9, p.seq+12, p.seq+15)
|
||
|
|
// the same again changes nothing
|
||
|
|
p.send(p.seq+3, tcpAck, nil, []byte("defg"))
|
||
|
|
sack(p.receive(), p.seq+3, p.seq+9, p.seq+12, p.seq+15)
|
||
|
|
|
||
|
|
p.send(p.seq, tcpAck, nil, []byte("abc"))
|
||
|
|
p.seq += 9
|
||
|
|
sack(p.receive(), p.seq+3, p.seq+6)
|
||
|
|
read(t, a.conn, "abcdefghi")
|
||
|
|
p.send(p.seq, tcpAck, nil, []byte("jklm"))
|
||
|
|
p.seq += 6
|
||
|
|
if s := p.receive(); s.ack != p.seq || len(s.options) != 0 {
|
||
|
|
t.Fatal("ack ", s.ack, " with options ", s.options)
|
||
|
|
}
|
||
|
|
read(t, a.conn, "jklmno")
|
||
|
|
}
|
||
|
|
|
||
|
|
// A segment is as large as the system makes it when the tun does not split what it reads.
|
||
|
|
func TestTCPLargeSegment(t *testing.T) {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
||
|
|
newTestStack(t, l, accept(conns))
|
||
|
|
p.connect(nil)
|
||
|
|
a := take(t, conns)
|
||
|
|
|
||
|
|
sent := make([]byte, 60000)
|
||
|
|
rand.Read(sent)
|
||
|
|
p.send(p.seq, tcpAck|tcpPsh, nil, sent)
|
||
|
|
var received []byte
|
||
|
|
for len(received) < len(sent) {
|
||
|
|
mb, err := a.conn.(buf.Reader).ReadMultiBuffer()
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
for _, b := range mb {
|
||
|
|
received = append(received, b.Bytes()...)
|
||
|
|
}
|
||
|
|
buf.ReleaseMulti(mb)
|
||
|
|
}
|
||
|
|
if !bytes.Equal(received, sent) {
|
||
|
|
t.Fatal("read something else than what was sent")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// What the system does not acknowledge is sent again: at once when it says what it has past it, or after a while.
|
||
|
|
func TestTCPRetransmit(t *testing.T) {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
||
|
|
newTestStack(t, l, accept(conns))
|
||
|
|
p.connect([]byte{2, 4, 0, 100, 4, 2, 0, 0})
|
||
|
|
a := take(t, conns)
|
||
|
|
|
||
|
|
sent := make([]byte, 450)
|
||
|
|
rand.Read(sent)
|
||
|
|
go func() {
|
||
|
|
if err := a.conn.(buf.Writer).WriteMultiBuffer(buf.MergeBytes(nil, sent)); err != nil {
|
||
|
|
t.Error(err)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
for i := 0; i < 5; i++ {
|
||
|
|
limit := 100
|
||
|
|
if i == 4 {
|
||
|
|
limit = 50
|
||
|
|
}
|
||
|
|
if s := p.receive(); s.seq != p.ack+uint32(100*i) || !bytes.Equal(s.payload, sent[100*i:100*i+limit]) {
|
||
|
|
t.Fatal("segment ", i, ": seq ", s.seq-p.ack, " with ", len(s.payload), " bytes")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// the second and the third segment are lost
|
||
|
|
p.ack += 100
|
||
|
|
p.send(p.seq, tcpAck, nil, nil)
|
||
|
|
block := func(from, to uint32) []byte {
|
||
|
|
b := []byte{1, 1, 5, 10}
|
||
|
|
return binary.BigEndian.AppendUint32(binary.BigEndian.AppendUint32(b, from), to)
|
||
|
|
}
|
||
|
|
p.send(p.seq, tcpAck, block(p.ack+200, p.ack+350), nil)
|
||
|
|
for i := 1; i < 3; i++ {
|
||
|
|
if s := p.receive(); s.seq != p.ack+uint32(100*(i-1)) || !bytes.Equal(s.payload, sent[100*i:100*i+100]) {
|
||
|
|
t.Fatal("segment ", i, " sent again: seq ", s.seq-p.ack, " with ", len(s.payload), " bytes")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// said again, it is not sent again
|
||
|
|
p.send(p.seq, tcpAck, block(p.ack+200, p.ack+350), nil)
|
||
|
|
p.silent(tcpTick)
|
||
|
|
// the one that was sent again came, the other one did not: it is sent after a while, without what the system has
|
||
|
|
p.ack += 100
|
||
|
|
p.send(p.seq, tcpAck, block(p.ack+100, p.ack+250), nil)
|
||
|
|
p.silent(tcpTick)
|
||
|
|
if s := p.receive(); s.seq != p.ack || !bytes.Equal(s.payload, sent[200:300]) {
|
||
|
|
t.Fatal("segment 2 sent again later: seq ", s.seq-p.ack, " with ", len(s.payload), " bytes")
|
||
|
|
}
|
||
|
|
p.ack += 250
|
||
|
|
p.send(p.seq, tcpAck, nil, nil)
|
||
|
|
p.silent(2 * tcpRetransmitTicks * tcpTick)
|
||
|
|
}
|
||
|
|
|
||
|
|
// No more is sent at once than has arrived before: twice as much when all of it arrives, half of it after a loss.
|
||
|
|
func TestTCPCongestion(t *testing.T) {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
||
|
|
newTestStack(t, l, accept(conns))
|
||
|
|
p.connect([]byte{2, 4, 0, 100, 4, 2, 0, 0})
|
||
|
|
a := take(t, conns)
|
||
|
|
|
||
|
|
go a.conn.(buf.Writer).WriteMultiBuffer(buf.MergeBytes(nil, make([]byte, 8000)))
|
||
|
|
// next is where what is new starts
|
||
|
|
next := p.ack
|
||
|
|
segments := func(seqs ...uint32) {
|
||
|
|
t.Helper()
|
||
|
|
for _, seq := range seqs {
|
||
|
|
if s := p.receive(); s.seq != seq || len(s.payload) != 100 {
|
||
|
|
t.Fatal("seq ", s.seq-p.ack, " with ", len(s.payload), " bytes, expected ", seq-p.ack, " with 100")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
p.silent(tcpTick / 2)
|
||
|
|
}
|
||
|
|
fresh := func(n int) (seqs []uint32) {
|
||
|
|
for ; n > 0; n-- {
|
||
|
|
seqs = append(seqs, next)
|
||
|
|
next += 100
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
segments(fresh(tcpStart)...)
|
||
|
|
p.ack = next
|
||
|
|
p.send(p.seq, tcpAck, nil, nil)
|
||
|
|
segments(fresh(2 * tcpStart)...)
|
||
|
|
// the first of them is lost: it is sent again, and with what is new there is half as much on its way as before
|
||
|
|
p.send(p.seq, tcpAck, binary.BigEndian.AppendUint32(binary.BigEndian.AppendUint32([]byte{1, 1, 5, 10}, p.ack+100), next), nil)
|
||
|
|
segments(append([]uint32{p.ack}, fresh(tcpStart-1)...)...)
|
||
|
|
// from then on a segment more for as much as there was on its way, and never a part of one
|
||
|
|
p.ack = next
|
||
|
|
p.send(p.seq, tcpAck, nil, nil)
|
||
|
|
segments(fresh(tcpStart + 2)...)
|
||
|
|
}
|
||
|
|
|
||
|
|
// No more is sent than the system takes, and the writer goes on when it takes more.
|
||
|
|
// The FIN that comes with a segment before the one in front of it is taken when that one has come.
|
||
|
|
func TestTCPEndOutOfOrder(t *testing.T) {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
||
|
|
newTestStack(t, l, accept(conns))
|
||
|
|
p.connect(nil)
|
||
|
|
a := take(t, conns)
|
||
|
|
p.send(p.seq+3, tcpAck|tcpFin, nil, []byte("def"))
|
||
|
|
if s := p.receive(); s.ack != p.seq {
|
||
|
|
t.Fatal("ack ", s.ack, ", expected ", p.seq)
|
||
|
|
}
|
||
|
|
p.send(p.seq, tcpAck, nil, []byte("abc"))
|
||
|
|
p.seq += 7
|
||
|
|
if s := p.receive(); s.ack != p.seq {
|
||
|
|
t.Fatal("ack ", s.ack, ", expected ", p.seq, " with the end")
|
||
|
|
}
|
||
|
|
read(t, a.conn, "abcdef")
|
||
|
|
if _, err := a.conn.(buf.Reader).ReadMultiBuffer(); err != io.EOF {
|
||
|
|
t.Fatal("no end after what was read: ", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The answer to the SYN-ACK tells how far the system is, and a segment is not sent again before
|
||
|
|
// its acknowledgment had the time to come from there.
|
||
|
|
func TestTCPRoundTrip(t *testing.T) {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
||
|
|
newTestStack(t, l, accept(conns))
|
||
|
|
p.send(p.seq, tcpSyn, nil, nil)
|
||
|
|
p.seq++
|
||
|
|
p.ack = p.receive().seq + 1
|
||
|
|
time.Sleep(150 * time.Millisecond)
|
||
|
|
p.send(p.seq, tcpAck, nil, nil)
|
||
|
|
a := take(t, conns)
|
||
|
|
write(t, a.conn, []byte("abc"))
|
||
|
|
for range 2 {
|
||
|
|
if s := p.receive(); s.seq != p.ack || string(s.payload) != "abc" {
|
||
|
|
t.Fatal("seq ", s.seq, " with ", s.payload)
|
||
|
|
}
|
||
|
|
p.silent(350 * time.Millisecond)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// A peer that says its segments are tiny is not believed all the way.
|
||
|
|
func TestTCPSmallestSegment(t *testing.T) {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
||
|
|
newTestStack(t, l, accept(conns))
|
||
|
|
p.connect([]byte{2, 4, 0, 1})
|
||
|
|
a := take(t, conns)
|
||
|
|
write(t, a.conn, make([]byte, 100))
|
||
|
|
if s := p.receive(); len(s.payload) != tcpSmallest {
|
||
|
|
t.Fatal("a segment of ", len(s.payload), " bytes")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestTCPWindow(t *testing.T) {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
||
|
|
newTestStack(t, l, accept(conns))
|
||
|
|
p.window = 250
|
||
|
|
p.connect(nil)
|
||
|
|
a := take(t, conns)
|
||
|
|
|
||
|
|
sent := make([]byte, 1000)
|
||
|
|
rand.Read(sent)
|
||
|
|
done := make(chan struct{})
|
||
|
|
go func() {
|
||
|
|
if err := a.conn.(buf.Writer).WriteMultiBuffer(buf.MergeBytes(nil, sent)); err != nil {
|
||
|
|
t.Error(err)
|
||
|
|
}
|
||
|
|
close(done)
|
||
|
|
}()
|
||
|
|
var received []byte
|
||
|
|
for len(received) < len(sent) {
|
||
|
|
for space := 250; space > 0; {
|
||
|
|
s := p.receive()
|
||
|
|
if s.seq != p.ack || len(s.payload) > space {
|
||
|
|
t.Fatal("seq ", s.seq, " with ", len(s.payload), " bytes, expected ", p.ack, " with ", space, " at most")
|
||
|
|
}
|
||
|
|
received = append(received, s.payload...)
|
||
|
|
p.ack += uint32(len(s.payload))
|
||
|
|
space -= len(s.payload)
|
||
|
|
}
|
||
|
|
// nothing more before the window opens
|
||
|
|
p.silent(tcpTick / 2)
|
||
|
|
p.send(p.seq, tcpAck, nil, nil)
|
||
|
|
}
|
||
|
|
<-done
|
||
|
|
if !bytes.Equal(received, sent) {
|
||
|
|
t.Fatal("received something else than what was written")
|
||
|
|
}
|
||
|
|
|
||
|
|
// the time that the window was closed is not time without an acknowledgment of what is sent after it.
|
||
|
|
// It is closed with a keepalive: that is answered, so the stack has seen it before something is written
|
||
|
|
p.window = 0
|
||
|
|
p.send(p.seq-1, tcpAck, nil, nil)
|
||
|
|
p.receive()
|
||
|
|
done = make(chan struct{})
|
||
|
|
go func() {
|
||
|
|
if err := a.conn.(buf.Writer).WriteMultiBuffer(buf.MergeBytes(nil, []byte("abc"))); err != nil {
|
||
|
|
t.Error(err)
|
||
|
|
}
|
||
|
|
close(done)
|
||
|
|
}()
|
||
|
|
p.silent(4 * tcpTick)
|
||
|
|
p.window = 250
|
||
|
|
p.send(p.seq, tcpAck, nil, nil)
|
||
|
|
if s := p.receive(); s.seq != p.ack || string(s.payload) != "abc" {
|
||
|
|
t.Fatal("seq ", s.seq, " with ", s.payload)
|
||
|
|
}
|
||
|
|
<-done
|
||
|
|
p.silent(tcpTick + tcpTick/2)
|
||
|
|
}
|
||
|
|
|
||
|
|
// A keepalive is answered, and not before a segment that was made before it is written.
|
||
|
|
func TestTCPKeepalive(t *testing.T) {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
||
|
|
// the write of the segment with what is written below takes until it is let go
|
||
|
|
out, writing, written := l.out, make(chan struct{}), make(chan struct{})
|
||
|
|
l.out = func(b []byte) {
|
||
|
|
if b[ipv4HeaderSize+13]&tcpPsh != 0 {
|
||
|
|
close(writing)
|
||
|
|
<-written
|
||
|
|
}
|
||
|
|
out(b)
|
||
|
|
}
|
||
|
|
newTestStack(t, l, accept(conns))
|
||
|
|
p.connect(nil)
|
||
|
|
a := take(t, conns)
|
||
|
|
|
||
|
|
p.send(p.seq-1, tcpAck, nil, nil)
|
||
|
|
if s := p.receive(); s.flags != tcpAck || s.seq != p.ack || s.ack != p.seq {
|
||
|
|
t.Fatal("not an answer to the keepalive: flags ", s.flags, ", seq ", s.seq, ", ack ", s.ack)
|
||
|
|
}
|
||
|
|
|
||
|
|
go func() {
|
||
|
|
if err := a.conn.(buf.Writer).WriteMultiBuffer(buf.MergeBytes(nil, []byte("abc"))); err != nil {
|
||
|
|
t.Error(err)
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
<-writing
|
||
|
|
p.send(p.seq-1, tcpAck, nil, nil)
|
||
|
|
p.silent(tcpTick / 2)
|
||
|
|
close(written)
|
||
|
|
if s := p.receive(); string(s.payload) != "abc" || s.seq != p.ack {
|
||
|
|
t.Fatal("wrong segment: seq ", s.seq, ", flags ", s.flags, ", payload ", s.payload)
|
||
|
|
}
|
||
|
|
p.ack += 3
|
||
|
|
if s := p.receive(); s.flags != tcpAck || s.seq != p.ack || s.ack != p.seq || len(s.payload) != 0 {
|
||
|
|
t.Fatal("not an answer to the keepalive: flags ", s.flags, ", seq ", s.seq, ", ack ", s.ack)
|
||
|
|
}
|
||
|
|
p.send(p.seq, tcpAck, nil, nil)
|
||
|
|
}
|
||
|
|
|
||
|
|
// A reset ends the connection for its reader, and a connection that is closed with something unread is reset.
|
||
|
|
func TestTCPReset(t *testing.T) {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, "10.0.0.1:1234", "10.0.0.2:80")
|
||
|
|
s := newTestStack(t, l, accept(conns))
|
||
|
|
p.connect(nil)
|
||
|
|
a := take(t, conns)
|
||
|
|
// a reset from somewhere else than where the connection is changes nothing
|
||
|
|
p.send(p.seq+tcpWindow+1, tcpRst, nil, nil)
|
||
|
|
p.send(p.seq-1, tcpRst, nil, nil)
|
||
|
|
p.send(p.seq, tcpAck|tcpPsh, nil, []byte("abc"))
|
||
|
|
p.seq += 3
|
||
|
|
read(t, a.conn, "abc")
|
||
|
|
if s := p.receive(); s.flags != tcpAck || s.ack != p.seq {
|
||
|
|
t.Fatal("not acknowledged: flags ", s.flags, ", ack ", s.ack)
|
||
|
|
}
|
||
|
|
p.send(p.seq, tcpRst, nil, nil)
|
||
|
|
if _, err := a.conn.(buf.Reader).ReadMultiBuffer(); err == nil || err == io.EOF {
|
||
|
|
t.Fatal("no error after the reset: ", err)
|
||
|
|
}
|
||
|
|
a.conn.Close()
|
||
|
|
forgotten(t, s)
|
||
|
|
|
||
|
|
// what came before the reset is read, to its end
|
||
|
|
p.src = netip.MustParseAddrPort("10.0.0.1:1236")
|
||
|
|
p.connect(nil)
|
||
|
|
a = take(t, conns)
|
||
|
|
p.send(p.seq, tcpAck|tcpPsh|tcpFin, nil, []byte("abc"))
|
||
|
|
p.seq += 4
|
||
|
|
if s := p.receive(); s.flags != tcpAck || s.ack != p.seq {
|
||
|
|
t.Fatal("not acknowledged: flags ", s.flags, ", ack ", s.ack)
|
||
|
|
}
|
||
|
|
// with the number of the FIN, as the answer to a segment that was made before the FIN came
|
||
|
|
p.send(p.seq-1, tcpRst, nil, nil)
|
||
|
|
forgotten(t, s)
|
||
|
|
read(t, a.conn, "abc")
|
||
|
|
if _, err := a.conn.(buf.Reader).ReadMultiBuffer(); err != io.EOF {
|
||
|
|
t.Fatal("no end after what was read: ", err)
|
||
|
|
}
|
||
|
|
a.conn.Close()
|
||
|
|
|
||
|
|
p.src = netip.MustParseAddrPort("10.0.0.1:1235")
|
||
|
|
p.connect(nil)
|
||
|
|
a = take(t, conns)
|
||
|
|
p.send(p.seq, tcpAck|tcpPsh, nil, []byte("abc"))
|
||
|
|
p.seq += 3
|
||
|
|
// the timer acknowledges what nobody reads
|
||
|
|
if s := p.receive(); s.flags != tcpAck || s.ack != p.seq {
|
||
|
|
t.Fatal("not acknowledged: flags ", s.flags, ", ack ", s.ack)
|
||
|
|
}
|
||
|
|
a.conn.Close()
|
||
|
|
if s := p.receive(); s.flags != tcpRst|tcpAck || s.seq != p.ack {
|
||
|
|
t.Fatal("not a reset: flags ", s.flags, ", seq ", s.seq)
|
||
|
|
}
|
||
|
|
forgotten(t, s)
|
||
|
|
|
||
|
|
// one that the system gives up before it is open is not one that is being opened anymore
|
||
|
|
p.src = netip.MustParseAddrPort("10.0.0.1:1237")
|
||
|
|
p.send(p.seq, tcpSyn, nil, nil)
|
||
|
|
p.seq++
|
||
|
|
p.receive()
|
||
|
|
p.send(p.seq, tcpRst, nil, nil)
|
||
|
|
forgotten(t, s)
|
||
|
|
}
|
||
|
|
|
||
|
|
// A UDP packet starts a connection, which answers from any address, and an echo request is answered.
|
||
|
|
func TestUDPAndICMP(t *testing.T) {
|
||
|
|
for _, test := range []struct{ src, dst, other string }{
|
||
|
|
{"10.0.0.1:1234", "10.0.0.2:53", "10.0.0.3:5353"},
|
||
|
|
{"[fd00::1]:1234", "[fd00::2]:53", "[fd00::3]:5353"},
|
||
|
|
} {
|
||
|
|
conns := make(chan accepted, 1)
|
||
|
|
p, l := newPeer(t, test.src, test.dst)
|
||
|
|
newTestStack(t, l, accept(conns))
|
||
|
|
|
||
|
|
var b [ipv6HeaderSize + udpHeaderSize]byte
|
||
|
|
n := putIPHeader(b[:], protoUDP, p.src.Addr(), p.dst.Addr())
|
||
|
|
u := b[n : n+udpHeaderSize]
|
||
|
|
binary.BigEndian.PutUint16(u, p.src.Port())
|
||
|
|
binary.BigEndian.PutUint16(u[2:], p.dst.Port())
|
||
|
|
binary.BigEndian.PutUint16(u[4:], udpHeaderSize+3)
|
||
|
|
setIPLength(b[:n], n+udpHeaderSize+3)
|
||
|
|
l.in <- append(b[:n+udpHeaderSize:n+udpHeaderSize], "abc"...)
|
||
|
|
a := take(t, conns)
|
||
|
|
mb, err := a.conn.(buf.Reader).ReadMultiBuffer()
|
||
|
|
if err != nil || mb.String() != "abc" || *mb[0].UDP != a.dest || a.dest.NetAddr() != test.dst || a.conn.RemoteAddr().String() != test.src {
|
||
|
|
t.Fatal("read ", mb.String(), " from ", a.conn.RemoteAddr(), " to ", a.dest, ", error: ", err)
|
||
|
|
}
|
||
|
|
buf.ReleaseMulti(mb)
|
||
|
|
|
||
|
|
for _, from := range []string{test.dst, test.other} {
|
||
|
|
answer := buf.FromBytes([]byte("defg"))
|
||
|
|
source := net.UDPDestination(net.ParseAddress(netip.MustParseAddrPort(from).Addr().String()), net.Port(netip.MustParseAddrPort(from).Port()))
|
||
|
|
answer.UDP = &source
|
||
|
|
if err := a.conn.(buf.Writer).WriteMultiBuffer(buf.MultiBuffer{answer}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
raw := p.packet()
|
||
|
|
packet, ok := parsePacket(raw)
|
||
|
|
u := packet.payload
|
||
|
|
// the system may have to split it on the way to where it goes
|
||
|
|
if !ok || raw[0]>>4 == 4 && raw[6] != 0 || packet.proto != protoUDP || netip.AddrPortFrom(packet.src, binary.BigEndian.Uint16(u)).String() != from ||
|
||
|
|
netip.AddrPortFrom(packet.dst, binary.BigEndian.Uint16(u[2:])) != p.src || string(u[udpHeaderSize:]) != "defg" {
|
||
|
|
t.Fatal("wrong packet from ", from, ": ", packet)
|
||
|
|
}
|
||
|
|
var ip [ipv6HeaderSize]byte
|
||
|
|
if size := putIPHeader(ip[:], protoUDP, packet.src, packet.dst); transportChecksum(ip[:size], protoUDP, u, nil) != 0 {
|
||
|
|
t.Fatal("wrong checksum of the packet from ", from)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
a.conn.Close()
|
||
|
|
|
||
|
|
proto, request, reply := byte(protoICMPv4), byte(8), byte(0)
|
||
|
|
if p.src.Addr().Is6() {
|
||
|
|
proto, request, reply = protoICMPv6, 128, 129
|
||
|
|
}
|
||
|
|
n = putIPHeader(b[:], proto, p.src.Addr(), p.dst.Addr())
|
||
|
|
setIPLength(b[:n], n+12)
|
||
|
|
l.in <- append(b[:n:n], request, 0, 0, 0, 1, 2, 3, 4, 'a', 'b', 'c', 'd')
|
||
|
|
packet, ok := parsePacket(p.packet())
|
||
|
|
m := packet.payload
|
||
|
|
if !ok || packet.proto != proto || packet.src != p.dst.Addr() || packet.dst != p.src.Addr() || m[0] != reply || string(m[4:]) != "\x01\x02\x03\x04abcd" {
|
||
|
|
t.Fatal("wrong echo reply: ", packet)
|
||
|
|
}
|
||
|
|
var ip [ipv6HeaderSize]byte
|
||
|
|
size := putIPHeader(ip[:], proto, packet.src, packet.dst)
|
||
|
|
if proto == protoICMPv4 && checksum(sum(m, 0)) != 0 || proto == protoICMPv6 && transportChecksum(ip[:size], proto, m, nil) != 0 {
|
||
|
|
t.Fatal("wrong checksum of the echo reply")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// The checksum is that of the 16-bit words added one by one.
|
||
|
|
func TestChecksum(t *testing.T) {
|
||
|
|
b := make([]byte, 5000)
|
||
|
|
rand.Read(b)
|
||
|
|
for size := 0; size < len(b); size += 1 + size/64 {
|
||
|
|
expected := uint32(size)
|
||
|
|
for i := 0; i < size; i += 2 {
|
||
|
|
expected += uint32(b[i]) << 8
|
||
|
|
if i+1 < size {
|
||
|
|
expected += uint32(b[i+1])
|
||
|
|
}
|
||
|
|
expected = expected>>16 + expected&0xffff
|
||
|
|
}
|
||
|
|
if got := checksum(sum(b[:size], uint64(size))); got != ^uint16(expected) {
|
||
|
|
t.Fatal("checksum of ", size, " bytes: ", got, ", expected ", ^uint16(expected))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|