TAP -> TUN, handle multiple peers behind one NAT

This commit is contained in:
2026-07-23 11:41:01 +02:00
parent fabe2614a5
commit 4d710fb72b
3 changed files with 49 additions and 35 deletions

View File

@@ -1,6 +1,6 @@
# baalvpn # baalvpn
A post-quantum VPN for IPv6 A hub-and-spoke VPN for IPv6
## Features ## Features
* **Post-quantum security:** Uses [XWingMLKEM768X25519](https://datatracker.ietf.org/doc/html/draft-connolly-cfrg-xwing-kem/) + [XChaCha20-Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305#XChaCha20-Poly1305_%E2%80%93_extended_nonce_variant) + [HKDF-SHA-256](https://en.wikipedia.org/wiki/HKDF) for end-to-end encryption * **Post-quantum security:** Uses [XWingMLKEM768X25519](https://datatracker.ietf.org/doc/html/draft-connolly-cfrg-xwing-kem/) + [XChaCha20-Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305#XChaCha20-Poly1305_%E2%80%93_extended_nonce_variant) + [HKDF-SHA-256](https://en.wikipedia.org/wiki/HKDF) for end-to-end encryption

View File

@@ -16,6 +16,7 @@ import (
"github.com/songgao/water" "github.com/songgao/water"
) )
// TODO: parse some sort of config+key file
const ( const (
SERVER_IP = "172.20.12.47" SERVER_IP = "172.20.12.47"
IFACE_NAME = "baalvpn" IFACE_NAME = "baalvpn"
@@ -69,13 +70,14 @@ func register() {
} }
pubKey := privKey.PublicKey() pubKey := privKey.PublicKey()
// TODO: this probably should be repeated until we get RESP_REGISTER
send(shared.BuildPkt(shared.REQ_REGISTER, pubKey.Bytes())) send(shared.BuildPkt(shared.REQ_REGISTER, pubKey.Bytes()))
} }
func receivePackets() { func receivePackets() {
for {
buffer := make([]byte, 50000) buffer := make([]byte, 50000)
n, addr, err := conn.ReadFromUDP(buffer) for {
n, addr, err := conn.ReadFromUDP(buffer[:])
if err != nil { if err != nil {
log.Println(err) log.Println(err)
continue continue
@@ -88,7 +90,9 @@ func receivePackets() {
continue continue
} }
go handleIncomingPkt(buffer[:n]) req := make([]byte, n)
copy(req, buffer[:n])
go handleIncomingPkt(req)
} }
} }
@@ -195,6 +199,7 @@ func getOrEstablishSessionKey(ip string) []byte {
} }
// TODO: this definitely shouldnt block the main thread // TODO: this definitely shouldnt block the main thread
// TODO: this should try like 10 times tops since server doesnt respond at all if it doesnt have the pubkey
for { for {
// request pubkey every 50ms // request pubkey every 50ms
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
@@ -233,7 +238,7 @@ func getOrEstablishSessionKey(ip string) []byte {
} }
func setupInterface() { func setupInterface() {
config := water.Config{DeviceType: water.TAP} config := water.Config{DeviceType: water.TUN}
config.Name = IFACE_NAME config.Name = IFACE_NAME
var err error var err error
@@ -261,16 +266,14 @@ func relayPackets() {
} }
pkt = pkt[:n] pkt = pkt[:n]
etherType := binary.BigEndian.Uint16(pkt[12:14]) version := pkt[0] >> 4
payload := pkt[14:] if version != 6 {
if etherType != 0x86dd { // IPv6
continue continue
} }
destIP := net.IP(payload[24:40]).String() destIP := net.IP(pkt[24:40]).String()
if payload[24] == 0xff { // multicast if pkt[24] == 0xff { // multicast
encryptedPkt, err := EncryptSym(multicastKey, pkt) encryptedPkt, err := EncryptSym(multicastKey, pkt)
if err != nil { if err != nil {
panic(err) panic(err)

View File

@@ -1,14 +1,15 @@
// TODO: slice bounds checking // TODO: slice bounds checking
// TODO: authentication // TODO: authentication
// TODO: somehow persist IPs // TODO: somehow persist IPs
// TODO: handle multiple peers behind one NAT
// TODO: key rotation // TODO: key rotation
// TODO: dont trust claimed IP at all
package main package main
import ( import (
"crypto/hpke" "crypto/hpke"
"crypto/rand" "crypto/rand"
"encoding/binary" "encoding/binary"
"encoding/hex"
"log" "log"
"net" "net"
"os" "os"
@@ -19,12 +20,13 @@ import (
type Peer struct { type Peer struct {
RealAddr *net.UDPAddr RealAddr *net.UDPAddr
InternalIP string InternalIP string
PublicKey hpke.PublicKey HexPublicKey string
} }
var ( var (
peers = shared.NewTMap[string, *Peer]() peers = shared.NewTMap[string, *Peer]()
peersByInternal = shared.NewTMap[string, *Peer]() peersByInternal = shared.NewTMap[string, *Peer]()
peersByPubKey = shared.NewTMap[string, *Peer]()
conn *net.UDPConn conn *net.UDPConn
multicastKey = make([]byte, 32) multicastKey = make([]byte, 32)
) )
@@ -54,8 +56,8 @@ func receivePackets() {
} }
defer conn.Close() defer conn.Close()
for {
buffer := make([]byte, 50000) buffer := make([]byte, 50000)
for {
n, addr, err := conn.ReadFromUDP(buffer) n, addr, err := conn.ReadFromUDP(buffer)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@@ -65,14 +67,16 @@ func receivePackets() {
continue continue
} }
go handleReq(buffer[:n], addr) req := make([]byte, n)
copy(req, buffer[:n])
go handleReq(req, addr)
} }
} }
func handleReq(req []byte, addr *net.UDPAddr) { func handleReq(req []byte, addr *net.UDPAddr) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
log.Printf("recovered from panic while handling request from %s: %v\n", addr.IP.String(), r) log.Printf("recovered from panic while handling request from %s: %v\n", addr.String(), r)
} }
}() }()
@@ -96,19 +100,25 @@ func handleReq(req []byte, addr *net.UDPAddr) {
return return
} }
internalIP := randomIP().String() hexPubKey := hex.EncodeToString(pubKey.Bytes())
if destPeer, ok := peers.GetOK(addr.IP.String()); ok { var internalIP string
internalIP = destPeer.InternalIP // TODO: authenticating only by PUBLIC key is obviously a bad idea
destPeer.PublicKey = pubKey if peer, ok := peersByPubKey.GetOK(hexPubKey); ok {
internalIP = peer.InternalIP
peers.Delete(peer.RealAddr.String())
peer.RealAddr = addr
peers.Set(addr.String(), peer)
} else { } else {
internalIP = randomIP().String()
peer := &Peer{ peer := &Peer{
RealAddr: addr, RealAddr: addr,
InternalIP: internalIP, InternalIP: internalIP,
PublicKey: pubKey, HexPublicKey: hexPubKey,
} }
peers.Set(addr.IP.String(), peer) peers.Set(addr.String(), peer)
peersByInternal.Set(internalIP, peer) peersByInternal.Set(internalIP, peer)
peersByPubKey.Set(hexPubKey, peer)
} }
ciphertext, err := hpke.Seal(pubKey, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte("baalvpn"), multicastKey) ciphertext, err := hpke.Seal(pubKey, hpke.HKDFSHA256(), hpke.ChaCha20Poly1305(), []byte("baalvpn"), multicastKey)
@@ -119,7 +129,7 @@ func handleReq(req []byte, addr *net.UDPAddr) {
resp := shared.BuildPkt(shared.RESP_REGISTER, append(net.ParseIP(internalIP), ciphertext...)) resp := shared.BuildPkt(shared.RESP_REGISTER, append(net.ParseIP(internalIP), ciphertext...))
sendTo(addr, resp) sendTo(addr, resp)
case shared.ENC_PKT: case shared.ENC_PKT:
peer := peers.Get(addr.IP.String()) peer := peers.Get(addr.String())
if peer == nil { if peer == nil {
log.Println("data from unregistered peer:", addr.String()) log.Println("data from unregistered peer:", addr.String())
return return
@@ -139,7 +149,7 @@ func handleReq(req []byte, addr *net.UDPAddr) {
log.Println(srcIP + " -/> " + destIP + " (unrecognized IP)") log.Println(srcIP + " -/> " + destIP + " (unrecognized IP)")
} }
case shared.BROADCAST_PKT: case shared.BROADCAST_PKT:
peer := peers.Get(addr.IP.String()) peer := peers.Get(addr.String())
if peer == nil { if peer == nil {
log.Println("data from unregistered peer:", addr.String()) log.Println("data from unregistered peer:", addr.String())
return return
@@ -150,15 +160,16 @@ func handleReq(req []byte, addr *net.UDPAddr) {
case shared.REQ_GET_PUBKEY: case shared.REQ_GET_PUBKEY:
peerIP := net.IP(req[4:20]).String() peerIP := net.IP(req[4:20]).String()
peer := peersByInternal.Get(peerIP) peer := peersByInternal.Get(peerIP)
if peer == nil { if peer != nil {
log.Println("tried to get pubkey of unknown peer") pubKey, err := hex.DecodeString(peer.HexPublicKey)
return if err != nil {
panic(err)
} }
resp := shared.BuildPkt(shared.RESP_GET_PUBKEY, append(net.ParseIP(peerIP), pubKey...))
resp := shared.BuildPkt(shared.RESP_GET_PUBKEY, append(net.ParseIP(peer.InternalIP), peer.PublicKey.Bytes()...))
sendTo(addr, resp) sendTo(addr, resp)
}
case shared.REQ_ESTABLISH, shared.RESP_ESTABLISH: case shared.REQ_ESTABLISH, shared.RESP_ESTABLISH:
peer := peers.Get(addr.IP.String()) peer := peers.Get(addr.String())
if peer == nil { if peer == nil {
log.Println("unregistered peer tried to ESTABLISH") log.Println("unregistered peer tried to ESTABLISH")
return return