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
A post-quantum VPN for IPv6
A hub-and-spoke VPN for IPv6
## 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

View File

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

View File

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