TAP -> TUN, handle multiple peers behind one NAT
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user