WIP: Simple working client/server

This commit is contained in:
jdl 2024-12-16 20:51:30 +01:00
parent b4a6149cee
commit 62bf956c10
44 changed files with 1841 additions and 0 deletions

21
node/bitset.go Normal file
View File

@ -0,0 +1,21 @@
package node
const bitSetSize = 512 // Multiple of 64.
type bitSet [bitSetSize / 64]uint64
func (bs *bitSet) Set(i int) {
bs[i/64] |= 1 << (i % 64)
}
func (bs *bitSet) Clear(i int) {
bs[i/64] &= ^(1 << (i % 64))
}
func (bs *bitSet) ClearAll() {
clear(bs[:])
}
func (bs *bitSet) Get(i int) bool {
return bs[i/64]&(1<<(i%64)) != 0
}

48
node/bitset_test.go Normal file
View File

@ -0,0 +1,48 @@
package node
import (
"math/rand"
"testing"
)
func TestBitSet(t *testing.T) {
state := make([]bool, bitSetSize)
for i := range state {
state[i] = rand.Float32() > 0.5
}
bs := bitSet{}
for i := range state {
if state[i] {
bs.Set(i)
}
}
for i := range state {
if bs.Get(i) != state[i] {
t.Fatal(i, state[i], bs.Get(i))
}
}
for i := range state {
if rand.Float32() > 0.5 {
state[i] = false
bs.Clear(i)
}
}
for i := range state {
if bs.Get(i) != state[i] {
t.Fatal(i, state[i], bs.Get(i))
}
}
bs.ClearAll()
for i := range state {
if bs.Get(i) {
t.Fatal(i, bs.Get(i))
}
}
}

5
node/cmd/client/build.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
go build
sudo setcap cap_net_admin+iep ./client
./client 144.76.78.93

15
node/cmd/client/main.go Normal file
View File

@ -0,0 +1,15 @@
package main
import (
"log"
"os"
"vppn/node"
)
func main() {
if len(os.Args) != 2 {
log.Fatalf("Usage: %s <addr:port>", os.Args[0])
}
n := node.NewTmpNodeClient()
n.RunClient(os.Args[1])
}

7
node/cmd/server/build.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
go build
ssh kevin "killall server"
scp server kevin:/home/jdl/tmp/
ssh root@kevin "sudo setcap cap_net_admin+iep /home/jdl/tmp/server"
ssh kevin "/home/jdl/tmp/server"

8
node/cmd/server/main.go Normal file
View File

@ -0,0 +1,8 @@
package main
import "vppn/node"
func main() {
n := node.NewTmpNodeServer()
n.RunServer()
}

103
node/conn.go Normal file
View File

@ -0,0 +1,103 @@
package node
import (
"log"
"net"
"net/netip"
"vppn/fasttime"
)
type connWriter struct {
*net.UDPConn
localIP byte
buf []byte
counters [256]uint64
lookup func(byte) *peer
}
func newConnWriter(conn *net.UDPConn, localIP byte, lookup func(byte) *peer) *connWriter {
w := &connWriter{
UDPConn: conn,
localIP: localIP,
buf: make([]byte, bufferSize),
lookup: lookup,
}
for i := range w.counters {
w.counters[i] = uint64(fasttime.Now() << 30)
}
return w
}
func (w *connWriter) WriteTo(remoteIP, packetType byte, data []byte) error {
peer := w.lookup(remoteIP)
if peer == nil || peer.Addr == nil {
log.Printf("No peer: %d", remoteIP)
return nil
}
w.counters[remoteIP]++
h := header{
Counter: w.counters[remoteIP],
SourceIP: w.localIP,
ViaIP: 0,
DestIP: remoteIP,
PacketType: packetType,
DataSize: uint16(len(data)),
}
buf := w.buf[:len(data)+headerSize]
h.Marshal(buf)
copy(buf[headerSize:], data)
_, err := w.WriteToUDPAddrPort(buf, *peer.Addr)
return err
}
// ----------------------------------------------------------------------------
type connReader struct {
*net.UDPConn
localIP byte
counters [256]uint64
lookup func(byte) *peer
}
func newConnReader(conn *net.UDPConn, localIP byte, lookup func(byte) *peer) *connReader {
return &connReader{
UDPConn: conn,
localIP: localIP,
lookup: lookup,
}
}
func (r *connReader) Read(buf []byte) (remoteAddr netip.AddrPort, h header, data []byte, err error) {
var n int
for {
n, remoteAddr, err = r.ReadFromUDPAddrPort(buf[:bufferSize])
if err != nil {
return
}
buf = buf[:n]
if n < headerSize {
continue // Packet it soo short.
}
h.Parse(buf)
data = buf[headerSize:]
if len(data) != int(h.DataSize) {
continue // Packet is corrupt.
}
if h.Counter > r.counters[h.SourceIP] {
r.counters[h.SourceIP] = h.Counter
}
return
}
}

76
node/dupcheck.go Normal file
View File

@ -0,0 +1,76 @@
package node
type dupCheck struct {
bitSet
head int
tail int
headCounter uint64
tailCounter uint64 // Also next expected counter value.
}
func newDupCheck(headCounter uint64) *dupCheck {
return &dupCheck{
headCounter: headCounter,
tailCounter: headCounter + 1,
tail: 1,
}
}
func (dc *dupCheck) IsDup(counter uint64) bool {
// Before head => it's late, say it's a dup.
if counter < dc.headCounter {
return true
}
// It's within the counter bounds.
if counter < dc.tailCounter {
index := (int(counter-dc.headCounter) + dc.head) % bitSetSize
if dc.Get(index) {
return true
}
dc.Set(index)
return false
}
// It's more than 1 beyond the tail.
delta := counter - dc.tailCounter
// Full clear.
if delta >= bitSetSize {
dc.ClearAll()
dc.Set(0)
dc.tail = 1
dc.head = 2
dc.tailCounter = counter + 1
dc.headCounter = dc.tailCounter - bitSetSize
return false
}
// Clear if necessary.
for i := 0; i < int(delta); i++ {
dc.put(false)
}
dc.put(true)
return false
}
func (dc *dupCheck) put(set bool) {
if set {
dc.Set(dc.tail)
} else {
dc.Clear(dc.tail)
}
dc.tail = (dc.tail + 1) % bitSetSize
dc.tailCounter++
if dc.head == dc.tail {
dc.head = (dc.head + 1) % bitSetSize
dc.headCounter++
}
}

57
node/dupcheck_test.go Normal file
View File

@ -0,0 +1,57 @@
package node
import (
"log"
"testing"
)
func TestDupCheck(t *testing.T) {
dc := newDupCheck(0)
for i := range bitSetSize {
if dc.IsDup(uint64(i)) {
t.Fatal("!")
}
}
type TestCase struct {
Counter uint64
Dup bool
}
testCases := []TestCase{
{0, true},
{1, true},
{2, true},
{3, true},
{63, true},
{256, true},
{510, true},
{511, true},
{512, false},
{0, true},
{512, true},
{513, false},
{517, false},
{512, true},
{513, true},
{514, false},
{515, false},
{516, false},
{517, true},
{2512, false},
{2000, true},
{2001, false},
{4000, false},
{4000 - 512, true}, // Too old.
{4000 - 511, false}, // Just in the window.
}
for i, tc := range testCases {
if ok := dc.IsDup(tc.Counter); ok != tc.Dup {
log.Printf("%b", dc.bitSet)
log.Printf("%+v", *dc)
t.Fatal(i, ok, tc)
}
}
}

3
node/globals.go Normal file
View File

@ -0,0 +1,3 @@
package node
const bufferSize = if_mtu + 128

32
node/header.go Normal file
View File

@ -0,0 +1,32 @@
package node
import "unsafe"
const headerSize = 24
type header struct {
Counter uint64 // Init with fasttime.Now() << 30 to ensure monotonic.
SourceIP byte
ViaIP byte
DestIP byte
PacketType byte // The packet type. See PACKET_* constants.
DataSize uint16 // Data size following associated data.
}
func (hdr *header) Parse(nb []byte) {
hdr.Counter = *(*uint64)(unsafe.Pointer(&nb[0]))
hdr.SourceIP = nb[8]
hdr.ViaIP = nb[9]
hdr.DestIP = nb[10]
hdr.PacketType = nb[11]
hdr.DataSize = *(*uint16)(unsafe.Pointer(&nb[12]))
}
func (hdr header) Marshal(buf []byte) {
*(*uint64)(unsafe.Pointer(&buf[0])) = hdr.Counter
buf[8] = hdr.SourceIP
buf[9] = hdr.ViaIP
buf[10] = hdr.DestIP
buf[11] = hdr.PacketType
*(*uint16)(unsafe.Pointer(&buf[12])) = hdr.DataSize
}

23
node/header_test.go Normal file
View File

@ -0,0 +1,23 @@
package node
import "testing"
func TestHeaderMarshalParse(t *testing.T) {
nIn := header{
Counter: 3212,
SourceIP: 34,
ViaIP: 20,
DestIP: 200,
PacketType: 44,
DataSize: 1235,
}
buf := make([]byte, headerSize)
nIn.Marshal(buf)
nOut := header{}
nOut.Parse(buf)
if nIn != nOut {
t.Fatal(nIn, nOut)
}
}

177
node/interface.go Normal file
View File

@ -0,0 +1,177 @@
package node
import (
"fmt"
"io"
"net"
"os"
"syscall"
"golang.org/x/sys/unix"
)
// Get next packet, returning packet, ip, and possible error.
func readNextPacket(iface io.ReadWriteCloser, buf []byte) ([]byte, byte, error) {
var (
version byte
ip byte
)
for {
n, err := iface.Read(buf[:cap(buf)])
if err != nil {
return nil, ip, err
}
if n < 20 {
continue // Packet too short.
}
buf = buf[:n]
version = buf[0] >> 4
switch version {
case 4:
ip = buf[19]
case 6:
if len(buf) < 40 {
continue // Packet too short.
}
ip = buf[39]
default:
continue // Invalid version.
}
return buf, ip, nil
}
}
const (
if_mtu = 1200
if_queue_len = 1000
)
func openInterface(network []byte, localIP byte, name string) (io.ReadWriteCloser, error) {
if len(network) != 4 {
return nil, fmt.Errorf("expected network to be 4 bytes, got %d", len(network))
}
ip := net.IPv4(network[0], network[1], network[2], localIP)
//////////////////////////
// Create TUN Interface //
//////////////////////////
tunFD, err := syscall.Open("/dev/net/tun", syscall.O_RDWR|unix.O_CLOEXEC, 0600)
if err != nil {
return nil, fmt.Errorf("failed to open TUN device: %w", err)
}
// New interface request.
req, err := unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create new TUN interface request: %w", err)
}
// Flags:
//
// IFF_NO_PI => don't add packet info data to packets sent to the interface.
// IFF_TUN => create a TUN device handling IP packets.
req.SetUint16(unix.IFF_NO_PI | unix.IFF_TUN)
err = unix.IoctlIfreq(tunFD, unix.TUNSETIFF, req)
if err != nil {
return nil, fmt.Errorf("failed to set TUN device settings: %w", err)
}
// Name may not be exactly the same?
name = req.Name()
/////////////
// Set MTU //
/////////////
// We need a socket file descriptor to set other options for some reason.
sockFD, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil {
return nil, fmt.Errorf("failed to open socket: %w", err)
}
defer unix.Close(sockFD)
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create MTU interface request: %w", err)
}
req.SetUint32(if_mtu)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFMTU, req); err != nil {
return nil, fmt.Errorf("failed to set interface MTU: %w", err)
}
//////////////////////
// Set Queue Length //
//////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create IP interface request: %w", err)
}
req.SetUint16(if_queue_len)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFTXQLEN, req); err != nil {
return nil, fmt.Errorf("failed to set interface queue length: %w", err)
}
/////////////////////
// Set IP and Mask //
/////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create IP interface request: %w", err)
}
if err := req.SetInet4Addr(ip.To4()); err != nil {
return nil, fmt.Errorf("failed to set interface request IP: %w", err)
}
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFADDR, req); err != nil {
return nil, fmt.Errorf("failed to set interface IP: %w", err)
}
// SET MASK - must happen after setting address.
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create mask interface request: %w", err)
}
if err := req.SetInet4Addr(net.IPv4(255, 255, 255, 0).To4()); err != nil {
return nil, fmt.Errorf("failed to set interface request mask: %w", err)
}
if err := unix.IoctlIfreq(sockFD, unix.SIOCSIFNETMASK, req); err != nil {
return nil, fmt.Errorf("failed to set interface mask: %w", err)
}
////////////////////////
// Bring Interface Up //
////////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create up interface request: %w", err)
}
// Get current flags.
if err = unix.IoctlIfreq(sockFD, unix.SIOCGIFFLAGS, req); err != nil {
return nil, fmt.Errorf("failed to get interface flags: %w", err)
}
flags := req.Uint16() | unix.IFF_UP | unix.IFF_RUNNING
// Set UP flag / broadcast flags.
req.SetUint16(flags)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFFLAGS, req); err != nil {
return nil, fmt.Errorf("failed to set interface up: %w", err)
}
return os.NewFile(uintptr(tunFD), "tun"), nil
}

33
node/peer.go Normal file
View File

@ -0,0 +1,33 @@
package node
import (
"net/netip"
"sync/atomic"
)
type peer struct {
IP byte
// TODO: Version
Addr *netip.AddrPort
// TODO: ViaIP
// TODO: EncPubKey
// TODO: SignPrivKey
}
type peerRepo [256]*atomic.Pointer[peer]
func newPeerRepo() peerRepo {
pr := peerRepo{}
for i := range pr {
pr[i] = &atomic.Pointer[peer]{}
}
return pr
}
func (pr peerRepo) Get(ip byte) *peer {
return pr[ip].Load()
}
func (pr peerRepo) Set(ip byte, p *peer) {
pr[ip].Store(p)
}

164
node/tmp-server.go Normal file
View File

@ -0,0 +1,164 @@
package node
import (
"fmt"
"io"
"log"
"net"
"net/netip"
"runtime/debug"
)
var (
network = []byte{10, 1, 1, 0}
serverIP = byte(1)
clientIP = byte(2)
port = uint16(5151)
netName = "testnet"
)
func must(err error) {
if err != nil {
panic(err)
}
}
type TmpNode struct {
network []byte
localIP byte
peers peerRepo
port uint16
netName string
iface io.ReadWriteCloser
w *connWriter
r *connReader
}
func NewTmpNodeServer() *TmpNode {
n := &TmpNode{
localIP: serverIP,
network: network,
peers: newPeerRepo(),
port: port,
netName: netName,
}
var err error
n.iface, err = openInterface(n.network, n.localIP, n.netName)
must(err)
myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", n.port))
must(err)
conn, err := net.ListenUDP("udp", myAddr)
must(err)
n.w = newConnWriter(conn, n.localIP, n.peers.Get)
n.r = newConnReader(conn, n.localIP, n.peers.Get)
return n
}
func NewTmpNodeClient() *TmpNode {
n := &TmpNode{
localIP: clientIP,
network: network,
peers: newPeerRepo(),
port: port,
netName: netName,
}
var err error
n.iface, err = openInterface(n.network, n.localIP, n.netName)
must(err)
myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", n.port))
must(err)
conn, err := net.ListenUDP("udp", myAddr)
must(err)
n.w = newConnWriter(conn, n.localIP, n.peers.Get)
n.r = newConnReader(conn, n.localIP, n.peers.Get)
return n
}
func (n *TmpNode) RunServer() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("%v", r)
debug.PrintStack()
}
}()
// Get remoteAddr from a packet.
buf := make([]byte, bufferSize)
remoteAddr, h, _, err := n.r.Read(buf)
must(err)
log.Printf("Got remote addr: %d -> %v", h.SourceIP, remoteAddr)
must(err)
n.peers.Set(h.SourceIP, &peer{
IP: h.SourceIP,
Addr: &remoteAddr,
})
go n.readFromIFace()
n.readFromConn()
}
func (n *TmpNode) RunClient(srvAddrStr string) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("%v", r)
debug.PrintStack()
}
}()
serverAddr, err := netip.ParseAddrPort(fmt.Sprintf("%s:%d", srvAddrStr, port))
must(err)
log.Printf("Setting %d => %v", serverIP, serverAddr)
n.peers.Set(serverIP, &peer{
IP: serverIP,
Addr: &serverAddr,
})
must(n.w.WriteTo(serverIP, 1, []byte{1, 2, 3, 4, 5, 6, 7, 8}))
go n.readFromIFace()
n.readFromConn()
}
func (n *TmpNode) readFromIFace() {
var (
buf = make([]byte, bufferSize)
packet []byte
remoteIP byte
err error
)
for {
packet, remoteIP, err = readNextPacket(n.iface, buf)
must(err)
must(n.w.WriteTo(remoteIP, 1, packet))
}
}
func (node *TmpNode) readFromConn() {
var (
buf = make([]byte, bufferSize)
packet []byte
err error
)
for {
_, _, packet, err = node.r.Read(buf)
must(err)
// We assume that we're only receiving packets from one source.
_, err = node.iface.Write(packet)
must(err)
}
}

1
stage1/README.md Normal file
View File

@ -0,0 +1 @@
## Stage1: Point-to-point Tunnel w/ no Encryption

32
stage1/client.go Normal file
View File

@ -0,0 +1,32 @@
package stage1
import (
"fmt"
"net"
"net/netip"
"runtime/debug"
)
func RunClient(serverAddrStr string) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("%v", r)
debug.PrintStack()
}
}()
iface, err := openInterface(network, clientIP, netName)
must(err)
myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port))
must(err)
conn, err := net.ListenUDP("udp", myAddr)
must(err)
serverAddr, err := netip.ParseAddrPort(fmt.Sprintf("%s:%d", serverAddrStr, port))
must(err)
go readFromIFace(iface, conn, serverIP, serverAddr)
readFromConn(iface, conn)
}

7
stage1/cmd/client/build.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
go build
scp client kevin:/home/jdl/tmp
ssh root@home "setcap cap_net_admin+iep /home/jdl/tmp/client"
ssh home "/home/jdl/tmp/client 192.168.1.21"

14
stage1/cmd/client/main.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"log"
"os"
"vppn/stage1"
)
func main() {
if len(os.Args) != 2 {
log.Fatalf("Usage: %s <addr:port>", os.Args[0])
}
stage1.RunClient(os.Args[1])
}

4
stage1/cmd/server/build.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
go build
sudo setcap cap_net_admin+iep server

14
stage1/cmd/server/main.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"log"
"os"
"vppn/stage1"
)
func main() {
if len(os.Args) != 2 {
log.Fatalf("Usage: %s <addr>", os.Args[0])
}
stage1.RunServer(os.Args[1])
}

142
stage1/interface.go Normal file
View File

@ -0,0 +1,142 @@
package stage1
import (
"fmt"
"io"
"net"
"os"
"syscall"
"golang.org/x/sys/unix"
)
const (
if_mtu = 1200
if_queue_len = 1000
)
func openInterface(network []byte, localIP byte, name string) (io.ReadWriteCloser, error) {
if len(network) != 4 {
return nil, fmt.Errorf("expected network to be 4 bytes, got %d", len(network))
}
ip := net.IPv4(network[0], network[1], network[2], localIP)
//////////////////////////
// Create TUN Interface //
//////////////////////////
tunFD, err := syscall.Open("/dev/net/tun", syscall.O_RDWR|unix.O_CLOEXEC, 0600)
if err != nil {
return nil, fmt.Errorf("failed to open TUN device: %w", err)
}
// New interface request.
req, err := unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create new TUN interface request: %w", err)
}
// Flags:
//
// IFF_NO_PI => don't add packet info data to packets sent to the interface.
// IFF_TUN => create a TUN device handling IP packets.
req.SetUint16(unix.IFF_NO_PI | unix.IFF_TUN)
err = unix.IoctlIfreq(tunFD, unix.TUNSETIFF, req)
if err != nil {
return nil, fmt.Errorf("failed to set TUN device settings: %w", err)
}
// Name may not be exactly the same?
name = req.Name()
/////////////
// Set MTU //
/////////////
// We need a socket file descriptor to set other options for some reason.
sockFD, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil {
return nil, fmt.Errorf("failed to open socket: %w", err)
}
defer unix.Close(sockFD)
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create MTU interface request: %w", err)
}
req.SetUint32(if_mtu)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFMTU, req); err != nil {
return nil, fmt.Errorf("failed to set interface MTU: %w", err)
}
//////////////////////
// Set Queue Length //
//////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create IP interface request: %w", err)
}
req.SetUint16(if_queue_len)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFTXQLEN, req); err != nil {
return nil, fmt.Errorf("failed to set interface queue length: %w", err)
}
/////////////////////
// Set IP and Mask //
/////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create IP interface request: %w", err)
}
if err := req.SetInet4Addr(ip.To4()); err != nil {
return nil, fmt.Errorf("failed to set interface request IP: %w", err)
}
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFADDR, req); err != nil {
return nil, fmt.Errorf("failed to set interface IP: %w", err)
}
// SET MASK - must happen after setting address.
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create mask interface request: %w", err)
}
if err := req.SetInet4Addr(net.IPv4(255, 255, 255, 0).To4()); err != nil {
return nil, fmt.Errorf("failed to set interface request mask: %w", err)
}
if err := unix.IoctlIfreq(sockFD, unix.SIOCSIFNETMASK, req); err != nil {
return nil, fmt.Errorf("failed to set interface mask: %w", err)
}
////////////////////////
// Bring Interface Up //
////////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create up interface request: %w", err)
}
// Get current flags.
if err = unix.IoctlIfreq(sockFD, unix.SIOCGIFFLAGS, req); err != nil {
return nil, fmt.Errorf("failed to get interface flags: %w", err)
}
flags := req.Uint16() | unix.IFF_UP | unix.IFF_RUNNING
// Set UP flag / broadcast flags.
req.SetUint16(flags)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFFLAGS, req); err != nil {
return nil, fmt.Errorf("failed to set interface up: %w", err)
}
return os.NewFile(uintptr(tunFD), "tun"), nil
}

109
stage1/server.go Normal file
View File

@ -0,0 +1,109 @@
package stage1
import (
"fmt"
"io"
"log"
"net"
"net/netip"
"runtime/debug"
)
var (
network = []byte{10, 1, 1, 0}
serverIP = byte(1)
clientIP = byte(2)
port = uint16(5151)
netName = "testnet"
bufferSize = if_mtu * 2
)
func must(err error) {
if err != nil {
panic(err)
}
}
func RunServer(clientAddrStr string) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("%v", r)
debug.PrintStack()
}
}()
iface, err := openInterface(network, serverIP, netName)
must(err)
myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port))
must(err)
conn, err := net.ListenUDP("udp", myAddr)
must(err)
clientAddr, err := netip.ParseAddrPort(fmt.Sprintf("%s:%d", clientAddrStr, port))
must(err)
go readFromIFace(iface, conn, clientIP, clientAddr)
readFromConn(iface, conn)
}
func readFromIFace(iface io.ReadWriteCloser, conn *net.UDPConn, remoteIP byte, remoteAddr netip.AddrPort) {
var (
n int
packet = make([]byte, bufferSize)
version byte
ip byte
err error
)
for {
n, err = iface.Read(packet[:bufferSize])
must(err)
packet = packet[:n]
if len(packet) < 20 {
log.Printf("Dropping small packet: %d", n)
continue
}
packet = packet[:n]
version = packet[0] >> 4
switch version {
case 4:
ip = packet[19]
case 6:
ip = packet[39]
default:
log.Printf("Dropping packet with IP version: %d", version)
continue
}
if ip != remoteIP {
log.Printf("Dropping packet for incorrect IP: %d", ip)
continue
}
_, err = conn.WriteToUDPAddrPort(packet, remoteAddr)
must(err)
}
}
func readFromConn(iface io.ReadWriteCloser, conn *net.UDPConn) {
var (
n int
packet = make([]byte, bufferSize)
err error
)
for {
// We assume that we're only receiving packets from one source.
n, err = conn.Read(packet[:bufferSize])
must(err)
packet = packet[:n]
_, err = iface.Write(packet)
must(err)
}
}

1
stage1/startup.go Normal file
View File

@ -0,0 +1 @@
package stage1

4
stage2/README.md Normal file
View File

@ -0,0 +1,4 @@
## Stage2:
* Point-to-point Tunnel w/ no Encryption
* Server gets client's addr from first packet

35
stage2/client.go Normal file
View File

@ -0,0 +1,35 @@
package stage2
import (
"fmt"
"net"
"net/netip"
"runtime/debug"
)
func RunClient(serverAddrStr string) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("%v", r)
debug.PrintStack()
}
}()
iface, err := openInterface(network, clientIP, netName)
must(err)
myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port))
must(err)
conn, err := net.ListenUDP("udp", myAddr)
must(err)
serverAddr, err := netip.ParseAddrPort(fmt.Sprintf("%s:%d", serverAddrStr, port))
must(err)
_, err = conn.WriteToUDPAddrPort([]byte{1, 2, 3, 4, 5, 6, 7, 8}, serverAddr)
must(err)
go readFromIFace(iface, conn, serverIP, serverAddr)
readFromConn(iface, conn)
}

5
stage2/cmd/client/build.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
go build
sudo setcap cap_net_admin+iep ./client
./client 144.76.78.93

14
stage2/cmd/client/main.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"log"
"os"
"vppn/stage2"
)
func main() {
if len(os.Args) != 2 {
log.Fatalf("Usage: %s <addr:port>", os.Args[0])
}
stage2.RunClient(os.Args[1])
}

6
stage2/cmd/server/build.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
go build
scp server kevin:/home/jdl/tmp/
ssh root@kevin "sudo setcap cap_net_admin+iep /home/jdl/tmp/server"
ssh kevin "/home/jdl/tmp/server"

View File

@ -0,0 +1,7 @@
package main
import "vppn/stage2"
func main() {
stage2.RunServer()
}

142
stage2/interface.go Normal file
View File

@ -0,0 +1,142 @@
package stage2
import (
"fmt"
"io"
"net"
"os"
"syscall"
"golang.org/x/sys/unix"
)
const (
if_mtu = 1200
if_queue_len = 1000
)
func openInterface(network []byte, localIP byte, name string) (io.ReadWriteCloser, error) {
if len(network) != 4 {
return nil, fmt.Errorf("expected network to be 4 bytes, got %d", len(network))
}
ip := net.IPv4(network[0], network[1], network[2], localIP)
//////////////////////////
// Create TUN Interface //
//////////////////////////
tunFD, err := syscall.Open("/dev/net/tun", syscall.O_RDWR|unix.O_CLOEXEC, 0600)
if err != nil {
return nil, fmt.Errorf("failed to open TUN device: %w", err)
}
// New interface request.
req, err := unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create new TUN interface request: %w", err)
}
// Flags:
//
// IFF_NO_PI => don't add packet info data to packets sent to the interface.
// IFF_TUN => create a TUN device handling IP packets.
req.SetUint16(unix.IFF_NO_PI | unix.IFF_TUN)
err = unix.IoctlIfreq(tunFD, unix.TUNSETIFF, req)
if err != nil {
return nil, fmt.Errorf("failed to set TUN device settings: %w", err)
}
// Name may not be exactly the same?
name = req.Name()
/////////////
// Set MTU //
/////////////
// We need a socket file descriptor to set other options for some reason.
sockFD, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil {
return nil, fmt.Errorf("failed to open socket: %w", err)
}
defer unix.Close(sockFD)
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create MTU interface request: %w", err)
}
req.SetUint32(if_mtu)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFMTU, req); err != nil {
return nil, fmt.Errorf("failed to set interface MTU: %w", err)
}
//////////////////////
// Set Queue Length //
//////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create IP interface request: %w", err)
}
req.SetUint16(if_queue_len)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFTXQLEN, req); err != nil {
return nil, fmt.Errorf("failed to set interface queue length: %w", err)
}
/////////////////////
// Set IP and Mask //
/////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create IP interface request: %w", err)
}
if err := req.SetInet4Addr(ip.To4()); err != nil {
return nil, fmt.Errorf("failed to set interface request IP: %w", err)
}
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFADDR, req); err != nil {
return nil, fmt.Errorf("failed to set interface IP: %w", err)
}
// SET MASK - must happen after setting address.
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create mask interface request: %w", err)
}
if err := req.SetInet4Addr(net.IPv4(255, 255, 255, 0).To4()); err != nil {
return nil, fmt.Errorf("failed to set interface request mask: %w", err)
}
if err := unix.IoctlIfreq(sockFD, unix.SIOCSIFNETMASK, req); err != nil {
return nil, fmt.Errorf("failed to set interface mask: %w", err)
}
////////////////////////
// Bring Interface Up //
////////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create up interface request: %w", err)
}
// Get current flags.
if err = unix.IoctlIfreq(sockFD, unix.SIOCGIFFLAGS, req); err != nil {
return nil, fmt.Errorf("failed to get interface flags: %w", err)
}
flags := req.Uint16() | unix.IFF_UP | unix.IFF_RUNNING
// Set UP flag / broadcast flags.
req.SetUint16(flags)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFFLAGS, req); err != nil {
return nil, fmt.Errorf("failed to set interface up: %w", err)
}
return os.NewFile(uintptr(tunFD), "tun"), nil
}

112
stage2/server.go Normal file
View File

@ -0,0 +1,112 @@
package stage2
import (
"fmt"
"io"
"log"
"net"
"net/netip"
"runtime/debug"
)
var (
network = []byte{10, 1, 1, 0}
serverIP = byte(1)
clientIP = byte(2)
port = uint16(5151)
netName = "testnet"
bufferSize = if_mtu * 2
)
func must(err error) {
if err != nil {
panic(err)
}
}
func RunServer() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("%v", r)
debug.PrintStack()
}
}()
iface, err := openInterface(network, serverIP, netName)
must(err)
myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port))
must(err)
conn, err := net.ListenUDP("udp", myAddr)
must(err)
// Get remoteAddr from a packet.
buf := make([]byte, 8)
_, remoteAddr, err := conn.ReadFromUDPAddrPort(buf)
log.Printf("Got remote addr: %v", remoteAddr)
must(err)
go readFromIFace(iface, conn, clientIP, remoteAddr)
readFromConn(iface, conn)
}
func readFromIFace(iface io.ReadWriteCloser, conn *net.UDPConn, remoteIP byte, remoteAddr netip.AddrPort) {
var (
n int
packet = make([]byte, bufferSize)
version byte
ip byte
err error
)
for {
n, err = iface.Read(packet[:bufferSize])
must(err)
packet = packet[:n]
if len(packet) < 20 {
log.Printf("Dropping small packet: %d", n)
continue
}
packet = packet[:n]
version = packet[0] >> 4
switch version {
case 4:
ip = packet[19]
case 6:
ip = packet[39]
default:
log.Printf("Dropping packet with IP version: %d", version)
continue
}
if ip != remoteIP {
log.Printf("Dropping packet for incorrect IP: %d", ip)
continue
}
_, err = conn.WriteToUDPAddrPort(packet, remoteAddr)
must(err)
}
}
func readFromConn(iface io.ReadWriteCloser, conn *net.UDPConn) {
var (
n int
packet = make([]byte, bufferSize)
err error
)
for {
// We assume that we're only receiving packets from one source.
n, err = conn.Read(packet[:bufferSize])
must(err)
packet = packet[:n]
_, err = iface.Write(packet)
must(err)
}
}

1
stage2/startup.go Normal file
View File

@ -0,0 +1 @@
package stage2

16
stage3/README.md Normal file
View File

@ -0,0 +1,16 @@
## Stage3:
* Point-to-point Tunnel w/ no Encryption
* Server gets client's addr from first packet
* Add packet counter to detect skipped and late packets
### Learnings
* Directional packet loss is an issue.
* Sending to hetzner: ~380 Mbits/sec
* From hetzner: ~800 Mbits/sec
* Runs of dropped packets are generally small < 30
* Saw a few cases of 100-200
* Runs of correctly-sequenced packets are generally >> drops
* Late packets aren't so common
* Dropping late packets causes large slow-down.

35
stage3/client.go Normal file
View File

@ -0,0 +1,35 @@
package stage3
import (
"fmt"
"net"
"net/netip"
"runtime/debug"
)
func RunClient(serverAddrStr string) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("%v", r)
debug.PrintStack()
}
}()
iface, err := openInterface(network, clientIP, netName)
must(err)
myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port))
must(err)
conn, err := net.ListenUDP("udp", myAddr)
must(err)
serverAddr, err := netip.ParseAddrPort(fmt.Sprintf("%s:%d", serverAddrStr, port))
must(err)
_, err = conn.WriteToUDPAddrPort([]byte{1, 2, 3, 4, 5, 6, 7, 8}, serverAddr)
must(err)
go readFromIFace(iface, conn, clientIP, serverIP, serverAddr)
readFromConn(iface, conn, serverIP)
}

5
stage3/cmd/client/build.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
go build
sudo setcap cap_net_admin+iep ./client
./client 144.76.78.93

14
stage3/cmd/client/main.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"log"
"os"
"vppn/stage3"
)
func main() {
if len(os.Args) != 2 {
log.Fatalf("Usage: %s <addr:port>", os.Args[0])
}
stage3.RunClient(os.Args[1])
}

7
stage3/cmd/server/build.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
go build
ssh kevin "killall server"
scp server kevin:/home/jdl/tmp/
ssh root@kevin "sudo setcap cap_net_admin+iep /home/jdl/tmp/server"
ssh kevin "/home/jdl/tmp/server"

View File

@ -0,0 +1,7 @@
package main
import "vppn/stage3"
func main() {
stage3.RunServer()
}

142
stage3/interface.go Normal file
View File

@ -0,0 +1,142 @@
package stage3
import (
"fmt"
"io"
"net"
"os"
"syscall"
"golang.org/x/sys/unix"
)
const (
if_mtu = 1200
if_queue_len = 1000
)
func openInterface(network []byte, localIP byte, name string) (io.ReadWriteCloser, error) {
if len(network) != 4 {
return nil, fmt.Errorf("expected network to be 4 bytes, got %d", len(network))
}
ip := net.IPv4(network[0], network[1], network[2], localIP)
//////////////////////////
// Create TUN Interface //
//////////////////////////
tunFD, err := syscall.Open("/dev/net/tun", syscall.O_RDWR|unix.O_CLOEXEC, 0600)
if err != nil {
return nil, fmt.Errorf("failed to open TUN device: %w", err)
}
// New interface request.
req, err := unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create new TUN interface request: %w", err)
}
// Flags:
//
// IFF_NO_PI => don't add packet info data to packets sent to the interface.
// IFF_TUN => create a TUN device handling IP packets.
req.SetUint16(unix.IFF_NO_PI | unix.IFF_TUN)
err = unix.IoctlIfreq(tunFD, unix.TUNSETIFF, req)
if err != nil {
return nil, fmt.Errorf("failed to set TUN device settings: %w", err)
}
// Name may not be exactly the same?
name = req.Name()
/////////////
// Set MTU //
/////////////
// We need a socket file descriptor to set other options for some reason.
sockFD, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_IP)
if err != nil {
return nil, fmt.Errorf("failed to open socket: %w", err)
}
defer unix.Close(sockFD)
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create MTU interface request: %w", err)
}
req.SetUint32(if_mtu)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFMTU, req); err != nil {
return nil, fmt.Errorf("failed to set interface MTU: %w", err)
}
//////////////////////
// Set Queue Length //
//////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create IP interface request: %w", err)
}
req.SetUint16(if_queue_len)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFTXQLEN, req); err != nil {
return nil, fmt.Errorf("failed to set interface queue length: %w", err)
}
/////////////////////
// Set IP and Mask //
/////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create IP interface request: %w", err)
}
if err := req.SetInet4Addr(ip.To4()); err != nil {
return nil, fmt.Errorf("failed to set interface request IP: %w", err)
}
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFADDR, req); err != nil {
return nil, fmt.Errorf("failed to set interface IP: %w", err)
}
// SET MASK - must happen after setting address.
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create mask interface request: %w", err)
}
if err := req.SetInet4Addr(net.IPv4(255, 255, 255, 0).To4()); err != nil {
return nil, fmt.Errorf("failed to set interface request mask: %w", err)
}
if err := unix.IoctlIfreq(sockFD, unix.SIOCSIFNETMASK, req); err != nil {
return nil, fmt.Errorf("failed to set interface mask: %w", err)
}
////////////////////////
// Bring Interface Up //
////////////////////////
req, err = unix.NewIfreq(name)
if err != nil {
return nil, fmt.Errorf("failed to create up interface request: %w", err)
}
// Get current flags.
if err = unix.IoctlIfreq(sockFD, unix.SIOCGIFFLAGS, req); err != nil {
return nil, fmt.Errorf("failed to get interface flags: %w", err)
}
flags := req.Uint16() | unix.IFF_UP | unix.IFF_RUNNING
// Set UP flag / broadcast flags.
req.SetUint16(flags)
if err = unix.IoctlIfreq(sockFD, unix.SIOCSIFFLAGS, req); err != nil {
return nil, fmt.Errorf("failed to set interface up: %w", err)
}
return os.NewFile(uintptr(tunFD), "tun"), nil
}

23
stage3/packet.go Normal file
View File

@ -0,0 +1,23 @@
package stage3
import "unsafe"
const headerSize = 9
type packetHeader struct {
SrcIP byte
Counter uint64
}
func (h packetHeader) Marshal(buf []byte) int {
buf = buf[:9]
buf[0] = h.SrcIP
*(*uint64)(unsafe.Pointer(&buf[1])) = h.Counter
return headerSize
}
func (h *packetHeader) Parse(buf []byte) int {
h.SrcIP = buf[0]
h.Counter = *(*uint64)(unsafe.Pointer(&buf[1]))
return headerSize
}

22
stage3/packet_test.go Normal file
View File

@ -0,0 +1,22 @@
package stage3
import (
"reflect"
"testing"
)
func TestPacketHeader(t *testing.T) {
b := make([]byte, 1024)
h := packetHeader{
SrcIP: 8,
Counter: 2354,
}
n := h.Marshal(b)
h2 := packetHeader{}
h2.Parse(b[:n])
if !reflect.DeepEqual(h, h2) {
t.Fatal(h, h2)
}
}

147
stage3/server.go Normal file
View File

@ -0,0 +1,147 @@
package stage3
import (
"fmt"
"io"
"log"
"net"
"net/netip"
"runtime/debug"
)
var (
network = []byte{10, 1, 1, 0}
serverIP = byte(1)
clientIP = byte(2)
port = uint16(5151)
netName = "testnet"
bufferSize = if_mtu * 2
)
func must(err error) {
if err != nil {
panic(err)
}
}
func RunServer() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("%v", r)
debug.PrintStack()
}
}()
iface, err := openInterface(network, serverIP, netName)
must(err)
myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", port))
must(err)
conn, err := net.ListenUDP("udp", myAddr)
must(err)
// Get remoteAddr from a packet.
buf := make([]byte, 8)
_, remoteAddr, err := conn.ReadFromUDPAddrPort(buf)
log.Printf("Got remote addr: %v", remoteAddr)
must(err)
go readFromIFace(iface, conn, serverIP, clientIP, remoteAddr)
readFromConn(iface, conn, clientIP)
}
func readFromIFace(iface io.ReadWriteCloser, conn *net.UDPConn, localIP, remoteIP byte, remoteAddr netip.AddrPort) {
var (
n int
packet = make([]byte, bufferSize)
version byte
ip byte
err error
counter uint64
buf = make([]byte, bufferSize)
)
for {
n, err = iface.Read(packet[:bufferSize])
must(err)
packet = packet[:n]
if len(packet) < 20 {
log.Printf("Dropping small packet: %d", n)
continue
}
packet = packet[:n]
version = packet[0] >> 4
switch version {
case 4:
ip = packet[19]
case 6:
ip = packet[39]
default:
log.Printf("Dropping packet with IP version: %d", version)
continue
}
if ip != remoteIP {
log.Printf("Dropping packet for incorrect IP: %d", ip)
continue
}
h := packetHeader{SrcIP: localIP, Counter: counter}
counter++
buf = buf[:headerSize+len(packet)]
h.Marshal(buf)
copy(buf[headerSize:], packet)
_, err = conn.WriteToUDPAddrPort(buf, remoteAddr)
must(err)
}
}
func readFromConn(iface io.ReadWriteCloser, conn *net.UDPConn, remoteIP byte) {
var (
n int
packet = make([]byte, bufferSize)
err error
counter uint64
run uint64
h packetHeader
)
for {
// We assume that we're only receiving packets from one source.
n, err = conn.Read(packet[:bufferSize])
must(err)
packet = packet[:n]
if len(packet) < headerSize {
fmt.Print("_")
continue
}
h.Parse(packet)
if h.SrcIP != remoteIP {
fmt.Print("?")
continue
}
if h.Counter == counter+1 {
run++
counter = h.Counter
} else if h.Counter > counter+1 {
fmt.Printf("x(%d/%d)", h.Counter-counter+1, run)
run = 0
counter = h.Counter
} else if h.Counter <= counter {
//log.Printf("Skipped late packet: -%d", counter-h.Counter)
//continue
fmt.Print("<")
}
_, err = iface.Write(packet[headerSize:])
must(err)
}
}

1
stage3/startup.go Normal file
View File

@ -0,0 +1 @@
package stage3