forked from quic-go/quic-go
The new QUIC proxy allows to listen on “:0”, which allows us to get rid of all “address already in use” errors. The constructor now takes an Opts struct, which makes configuring it easier.
338 lines
11 KiB
Go
338 lines
11 KiB
Go
package quicproxy
|
|
|
|
import (
|
|
"bytes"
|
|
"net"
|
|
"strconv"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/lucas-clemente/quic-go"
|
|
"github.com/lucas-clemente/quic-go/protocol"
|
|
. "github.com/onsi/ginkgo"
|
|
. "github.com/onsi/gomega"
|
|
)
|
|
|
|
type packetData []byte
|
|
|
|
var _ = Describe("QUIC Proxy", func() {
|
|
var serverAddr string
|
|
|
|
makePacket := func(p protocol.PacketNumber, payload []byte) []byte {
|
|
b := &bytes.Buffer{}
|
|
hdr := quic.PublicHeader{
|
|
PacketNumber: p,
|
|
PacketNumberLen: protocol.PacketNumberLen6,
|
|
ConnectionID: 1337,
|
|
TruncateConnectionID: false,
|
|
}
|
|
hdr.Write(b, protocol.VersionWhatever, protocol.PerspectiveServer)
|
|
raw := b.Bytes()
|
|
raw = append(raw, payload...)
|
|
return raw
|
|
}
|
|
|
|
BeforeEach(func() {
|
|
serverAddr = "localhost:7331"
|
|
})
|
|
|
|
Context("Proxy setup and teardown", func() {
|
|
It("sets up the UDPProxy", func() {
|
|
proxy, err := NewQuicProxy("localhost:13370", Opts{RemoteAddr: serverAddr})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
Expect(proxy.clientDict).To(HaveLen(0))
|
|
|
|
// check that port 13370 is in use
|
|
addr, err := net.ResolveUDPAddr("udp", "localhost:13370")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
_, err = net.ListenUDP("udp", addr)
|
|
Expect(err).To(MatchError("listen udp 127.0.0.1:13370: bind: address already in use"))
|
|
|
|
err = proxy.Close() // stopping is tested in the next test
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("stops the UDPProxy", func() {
|
|
proxy, err := NewQuicProxy("localhost:13371", Opts{RemoteAddr: serverAddr})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = proxy.Close()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
// check that port 13371 is not in use anymore
|
|
addr, err := net.ResolveUDPAddr("udp", "localhost:13371")
|
|
Expect(err).ToNot(HaveOccurred())
|
|
ln, err := net.ListenUDP("udp", addr)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = ln.Close()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
|
|
It("has the correct LocalAddr and LocalPort", func() {
|
|
proxy, err := NewQuicProxy("localhost:13372", Opts{RemoteAddr: serverAddr})
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(proxy.LocalAddr().String()).To(Equal("127.0.0.1:13372"))
|
|
Expect(proxy.LocalPort()).To(Equal(13372))
|
|
|
|
err = proxy.Close()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
})
|
|
})
|
|
|
|
Context("Proxy tests", func() {
|
|
var (
|
|
serverConn *net.UDPConn
|
|
serverReceivedPackets []packetData
|
|
serverNumPacketsSent int
|
|
clientConn *net.UDPConn
|
|
proxy *QuicProxy
|
|
)
|
|
|
|
// start the proxy on port 10001
|
|
startProxy := func(opts Opts) {
|
|
var err error
|
|
proxy, err = NewQuicProxy("localhost:10001", opts)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
clientConn, err = net.DialUDP("udp", nil, proxy.LocalAddr().(*net.UDPAddr))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
|
|
BeforeEach(func() {
|
|
serverReceivedPackets = serverReceivedPackets[:0]
|
|
serverNumPacketsSent = 0
|
|
|
|
// setup a dump UDP server on port 7331
|
|
// in production this would be a QUIC server
|
|
raddr, err := net.ResolveUDPAddr("udp", serverAddr)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
serverConn, err = net.ListenUDP("udp", raddr)
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
go func() {
|
|
for {
|
|
buf := make([]byte, protocol.MaxPacketSize)
|
|
// the ReadFromUDP will error as soon as the UDP conn is closed
|
|
n, addr, err2 := serverConn.ReadFromUDP(buf)
|
|
if err2 != nil {
|
|
return
|
|
}
|
|
data := buf[0:n]
|
|
serverReceivedPackets = append(serverReceivedPackets, packetData(data))
|
|
// echo the packet
|
|
serverConn.WriteToUDP(data, addr)
|
|
serverNumPacketsSent++
|
|
}
|
|
}()
|
|
})
|
|
|
|
AfterEach(func() {
|
|
err := proxy.Close()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = serverConn.Close()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
err = clientConn.Close()
|
|
Expect(err).ToNot(HaveOccurred())
|
|
time.Sleep(200 * time.Millisecond)
|
|
})
|
|
|
|
Context("no packet drop", func() {
|
|
It("relays packets from the client to the server", func() {
|
|
startProxy(Opts{RemoteAddr: serverAddr})
|
|
// send the first packet
|
|
_, err := clientConn.Write(makePacket(1, []byte("foobar")))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Eventually(func() map[string]*connection { return proxy.clientDict }).Should(HaveLen(1))
|
|
var conn *connection
|
|
for _, conn = range proxy.clientDict {
|
|
Expect(atomic.LoadUint64(&conn.incomingPacketCounter)).To(Equal(uint64(1)))
|
|
}
|
|
|
|
// send the second packet
|
|
_, err = clientConn.Write(makePacket(2, []byte("decafbad")))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Eventually(func() []packetData { return serverReceivedPackets }).Should(HaveLen(2))
|
|
Expect(proxy.clientDict).To(HaveLen(1))
|
|
Expect(string(serverReceivedPackets[0])).To(ContainSubstring("foobar"))
|
|
Expect(string(serverReceivedPackets[1])).To(ContainSubstring("decafbad"))
|
|
})
|
|
|
|
It("relays packets from the server to the client", func() {
|
|
startProxy(Opts{RemoteAddr: serverAddr})
|
|
// send the first packet
|
|
_, err := clientConn.Write(makePacket(1, []byte("foobar")))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Eventually(func() map[string]*connection { return proxy.clientDict }).Should(HaveLen(1))
|
|
var key string
|
|
var conn *connection
|
|
for key, conn = range proxy.clientDict {
|
|
Eventually(func() uint64 { return atomic.LoadUint64(&conn.outgoingPacketCounter) }).Should(Equal(uint64(1)))
|
|
}
|
|
|
|
// send the second packet
|
|
_, err = clientConn.Write(makePacket(2, []byte("decafbad")))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
|
|
Expect(proxy.clientDict).To(HaveLen(1))
|
|
Eventually(func() uint64 { return proxy.clientDict[key].outgoingPacketCounter }).Should(BeEquivalentTo(2))
|
|
|
|
var clientReceivedPackets []packetData
|
|
// receive the packets echoed by the server on client side
|
|
go func() {
|
|
for {
|
|
buf := make([]byte, protocol.MaxPacketSize)
|
|
// the ReadFromUDP will error as soon as the UDP conn is closed
|
|
n, _, err2 := clientConn.ReadFromUDP(buf)
|
|
if err2 != nil {
|
|
return
|
|
}
|
|
data := buf[0:n]
|
|
clientReceivedPackets = append(clientReceivedPackets, packetData(data))
|
|
}
|
|
}()
|
|
|
|
Eventually(func() []packetData { return serverReceivedPackets }).Should(HaveLen(2))
|
|
Expect(serverReceivedPackets).To(HaveLen(2))
|
|
Expect(serverNumPacketsSent).To(Equal(2))
|
|
Eventually(func() []packetData { return clientReceivedPackets }).Should(HaveLen(2))
|
|
Expect(string(clientReceivedPackets[0])).To(ContainSubstring("foobar"))
|
|
Expect(string(clientReceivedPackets[1])).To(ContainSubstring("decafbad"))
|
|
})
|
|
})
|
|
|
|
Context("Drop Callbacks", func() {
|
|
It("drops incoming packets", func() {
|
|
opts := Opts{
|
|
RemoteAddr: serverAddr,
|
|
DropPacket: func(d Direction, p protocol.PacketNumber) bool {
|
|
return d == DirectionIncoming && p%2 == 0
|
|
},
|
|
}
|
|
startProxy(opts)
|
|
|
|
// send 6 packets
|
|
for i := 1; i <= 6; i++ {
|
|
_, err := clientConn.Write(makePacket(protocol.PacketNumber(i), []byte("foobar"+strconv.Itoa(i))))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
Eventually(func() []packetData { return serverReceivedPackets }).Should(HaveLen(3))
|
|
Consistently(func() []packetData { return serverReceivedPackets }).Should(HaveLen(3))
|
|
})
|
|
|
|
It("drops outgoing packets", func() {
|
|
opts := Opts{
|
|
RemoteAddr: serverAddr,
|
|
DropPacket: func(d Direction, p protocol.PacketNumber) bool {
|
|
return d == DirectionOutgoing && p%2 == 0
|
|
},
|
|
}
|
|
startProxy(opts)
|
|
|
|
var clientReceivedPackets []packetData
|
|
// receive the packets echoed by the server on client side
|
|
go func() {
|
|
for {
|
|
buf := make([]byte, protocol.MaxPacketSize)
|
|
// the ReadFromUDP will error as soon as the UDP conn is closed
|
|
n, _, err2 := clientConn.ReadFromUDP(buf)
|
|
if err2 != nil {
|
|
return
|
|
}
|
|
data := buf[0:n]
|
|
clientReceivedPackets = append(clientReceivedPackets, packetData(data))
|
|
}
|
|
}()
|
|
|
|
// send 6 packets
|
|
for i := 1; i <= 6; i++ {
|
|
_, err := clientConn.Write(makePacket(protocol.PacketNumber(i), []byte("foobar"+strconv.Itoa(i))))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
|
|
Eventually(func() []packetData { return clientReceivedPackets }).Should(HaveLen(3))
|
|
Consistently(func() []packetData { return clientReceivedPackets }).Should(HaveLen(3))
|
|
})
|
|
})
|
|
|
|
Context("Delay Callback", func() {
|
|
It("delays incoming packets", func() {
|
|
opts := Opts{
|
|
RemoteAddr: serverAddr,
|
|
// delay packet 1 by 200 ms
|
|
// delay packet 2 by 400 ms
|
|
// ...
|
|
DelayPacket: func(d Direction, p protocol.PacketNumber) time.Duration {
|
|
if d == DirectionOutgoing {
|
|
return 0
|
|
}
|
|
return time.Duration(200*p) * time.Millisecond
|
|
},
|
|
}
|
|
startProxy(opts)
|
|
|
|
// send 3 packets
|
|
start := time.Now()
|
|
for i := 1; i <= 3; i++ {
|
|
_, err := clientConn.Write(makePacket(protocol.PacketNumber(i), []byte("foobar"+strconv.Itoa(i))))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
Eventually(func() []packetData { return serverReceivedPackets }).Should(HaveLen(1))
|
|
Expect(time.Now()).To(BeTemporally("~", start.Add(200*time.Millisecond), 50*time.Millisecond))
|
|
Eventually(func() []packetData { return serverReceivedPackets }).Should(HaveLen(2))
|
|
Expect(time.Now()).To(BeTemporally("~", start.Add(400*time.Millisecond), 50*time.Millisecond))
|
|
Eventually(func() []packetData { return serverReceivedPackets }).Should(HaveLen(3))
|
|
Expect(time.Now()).To(BeTemporally("~", start.Add(600*time.Millisecond), 50*time.Millisecond))
|
|
})
|
|
|
|
It("delays outgoing packets", func() {
|
|
opts := Opts{
|
|
RemoteAddr: serverAddr,
|
|
// delay packet 1 by 200 ms
|
|
// delay packet 2 by 400 ms
|
|
// ...
|
|
DelayPacket: func(d Direction, p protocol.PacketNumber) time.Duration {
|
|
if d == DirectionIncoming {
|
|
return 0
|
|
}
|
|
return time.Duration(200*p) * time.Millisecond
|
|
},
|
|
}
|
|
startProxy(opts)
|
|
|
|
var clientReceivedPackets []packetData
|
|
// receive the packets echoed by the server on client side
|
|
go func() {
|
|
for {
|
|
buf := make([]byte, protocol.MaxPacketSize)
|
|
// the ReadFromUDP will error as soon as the UDP conn is closed
|
|
n, _, err2 := clientConn.ReadFromUDP(buf)
|
|
if err2 != nil {
|
|
return
|
|
}
|
|
data := buf[0:n]
|
|
clientReceivedPackets = append(clientReceivedPackets, packetData(data))
|
|
}
|
|
}()
|
|
|
|
// send 3 packets
|
|
start := time.Now()
|
|
for i := 1; i <= 3; i++ {
|
|
_, err := clientConn.Write(makePacket(protocol.PacketNumber(i), []byte("foobar"+strconv.Itoa(i))))
|
|
Expect(err).ToNot(HaveOccurred())
|
|
}
|
|
// the packets should have arrived immediately at the server
|
|
Eventually(func() []packetData { return serverReceivedPackets }).Should(HaveLen(3))
|
|
Expect(time.Now()).To(BeTemporally("~", start, 50*time.Millisecond))
|
|
Eventually(func() []packetData { return clientReceivedPackets }).Should(HaveLen(1))
|
|
Expect(time.Now()).To(BeTemporally("~", start.Add(200*time.Millisecond), 50*time.Millisecond))
|
|
Eventually(func() []packetData { return clientReceivedPackets }).Should(HaveLen(2))
|
|
Expect(time.Now()).To(BeTemporally("~", start.Add(400*time.Millisecond), 50*time.Millisecond))
|
|
Eventually(func() []packetData { return clientReceivedPackets }).Should(HaveLen(3))
|
|
Expect(time.Now()).To(BeTemporally("~", start.Add(600*time.Millisecond), 50*time.Millisecond))
|
|
})
|
|
})
|
|
})
|
|
})
|