wip
This commit is contained in:
32
peer/crypto.go
Normal file
32
peer/crypto.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
"golang.org/x/crypto/nacl/sign"
|
||||
)
|
||||
|
||||
func encryptPacket(sharedKey, nonce, packet, out []byte) []byte {
|
||||
out = box.SealAfterPrecomputation(out[:0], packet, (*[24]byte)(nonce), (*[32]byte)(sharedKey))
|
||||
return append(out, nonce...)
|
||||
}
|
||||
|
||||
func decryptPacket(sharedKey, packet, out []byte) (decrypted []byte, ok bool) {
|
||||
cut := len(packet) - NONCE_SIZE
|
||||
decrypted, ok = box.OpenAfterPrecomputation(out[:0], packet[:cut], (*[24]byte)(packet[cut:]), (*[32]byte)(sharedKey))
|
||||
return decrypted, ok
|
||||
}
|
||||
|
||||
// Signed packet should be encrypted with the encryptPacket function first.
|
||||
func signPacket(privKey, packet, out []byte) []byte {
|
||||
return sign.Sign(out[:0], packet, (*[64]byte)(privKey))
|
||||
}
|
||||
|
||||
func openPacket(pubKey, packet, out []byte) (encPacket []byte, ok bool) {
|
||||
return sign.Open(out[:0], packet, (*[32]byte)(pubKey))
|
||||
}
|
||||
|
||||
func computeSharedKey(peerPubKey, privKey []byte) []byte {
|
||||
shared := [32]byte{}
|
||||
box.Precompute(&shared, (*[32]byte)(peerPubKey), (*[32]byte)(privKey))
|
||||
return shared[:]
|
||||
}
|
||||
129
peer/crypto_test.go
Normal file
129
peer/crypto_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
"golang.org/x/crypto/nacl/sign"
|
||||
)
|
||||
|
||||
func TestEncryptDecryptPacket(t *testing.T) {
|
||||
pubKey1, privKey1, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pubKey2, privKey2, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sharedEncKey := [32]byte{}
|
||||
box.Precompute(&sharedEncKey, pubKey2, privKey1)
|
||||
|
||||
sharedDecKey := [32]byte{}
|
||||
box.Precompute(&sharedDecKey, pubKey1, privKey2)
|
||||
|
||||
original := make([]byte, MTU)
|
||||
rand.Read(original)
|
||||
|
||||
nonce := make([]byte, NONCE_SIZE)
|
||||
rand.Read(nonce)
|
||||
|
||||
encrypted := make([]byte, BUFFER_SIZE)
|
||||
encrypted = encryptPacket(sharedEncKey[:], nonce, original, encrypted)
|
||||
|
||||
decrypted := make([]byte, MTU)
|
||||
var ok bool
|
||||
decrypted, ok = decryptPacket(sharedDecKey[:], encrypted, decrypted)
|
||||
if !ok {
|
||||
t.Fatal(ok)
|
||||
}
|
||||
|
||||
if !bytes.Equal(original, decrypted) {
|
||||
t.Fatal("mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncryptPacket(b *testing.B) {
|
||||
_, privKey1, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
pubKey2, _, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
sharedEncKey := [32]byte{}
|
||||
box.Precompute(&sharedEncKey, pubKey2, privKey1)
|
||||
|
||||
original := make([]byte, MTU)
|
||||
rand.Read(original)
|
||||
|
||||
nonce := make([]byte, NONCE_SIZE)
|
||||
rand.Read(nonce)
|
||||
|
||||
encrypted := make([]byte, BUFFER_SIZE)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
encrypted = encryptPacket(sharedEncKey[:], nonce, original, encrypted)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecryptPacket(b *testing.B) {
|
||||
pubKey1, privKey1, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
pubKey2, privKey2, err := box.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
sharedEncKey := [32]byte{}
|
||||
box.Precompute(&sharedEncKey, pubKey2, privKey1)
|
||||
|
||||
sharedDecKey := [32]byte{}
|
||||
box.Precompute(&sharedDecKey, pubKey1, privKey2)
|
||||
|
||||
original := make([]byte, MTU)
|
||||
rand.Read(original)
|
||||
|
||||
nonce := make([]byte, NONCE_SIZE)
|
||||
rand.Read(nonce)
|
||||
|
||||
encrypted := make([]byte, BUFFER_SIZE)
|
||||
encrypted = encryptPacket(sharedEncKey[:], nonce, original, encrypted)
|
||||
|
||||
decrypted := make([]byte, MTU)
|
||||
for i := 0; i < b.N; i++ {
|
||||
decrypted, _ = decryptPacket(sharedDecKey[:], encrypted, decrypted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignOpenPacket(t *testing.T) {
|
||||
pubKey, privKey, err := sign.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
packet := make([]byte, MTU)
|
||||
|
||||
rand.Read(packet)
|
||||
|
||||
signedPacket := signPacket(privKey[:], packet, make([]byte, BUFFER_SIZE))
|
||||
|
||||
encPacket, ok := openPacket(pubKey[:], signedPacket, make([]byte, BUFFER_SIZE))
|
||||
if !ok {
|
||||
t.Fatal(ok)
|
||||
}
|
||||
|
||||
if !bytes.Equal(encPacket, packet) {
|
||||
t.Fatal("not equal")
|
||||
}
|
||||
}
|
||||
82
peer/files.go
Normal file
82
peer/files.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"vppn/m"
|
||||
)
|
||||
|
||||
func configDir(netName string) string {
|
||||
d, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get user home directory: %v", err)
|
||||
}
|
||||
return filepath.Join(d, ".vppn", netName)
|
||||
}
|
||||
|
||||
func peerConfigPath(netName string) string {
|
||||
return filepath.Join(configDir(netName), "peer-config.json")
|
||||
}
|
||||
|
||||
func peerStatePath(netName string) string {
|
||||
return filepath.Join(configDir(netName), "peer-state.json")
|
||||
}
|
||||
|
||||
func storeJson(x any, outPath string) error {
|
||||
outDir := filepath.Dir(outPath)
|
||||
_ = os.MkdirAll(outDir, 0700)
|
||||
|
||||
tmpPath := outPath + ".tmp"
|
||||
buf, err := json.Marshal(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Create(tmpPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := f.Write(buf); err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Sync(); err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Rename(tmpPath, outPath)
|
||||
}
|
||||
|
||||
func storePeerConfig(netName string, pc m.PeerConfig) error {
|
||||
return storeJson(pc, peerConfigPath(netName))
|
||||
}
|
||||
|
||||
func storePeerState(netName string, ps m.NetworkState) error {
|
||||
return storeJson(ps, peerStatePath(netName))
|
||||
}
|
||||
|
||||
func loadJson(dataPath string, ptr any) error {
|
||||
data, err := os.ReadFile(dataPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, ptr)
|
||||
}
|
||||
|
||||
func loadPeerConfig(netName string) (pc m.PeerConfig, err error) {
|
||||
return pc, loadJson(peerConfigPath(netName), &pc)
|
||||
}
|
||||
|
||||
func loadPeerState(netName string) (ps m.NetworkState, err error) {
|
||||
return ps, loadJson(peerStatePath(netName), &ps)
|
||||
}
|
||||
16
peer/globals.go
Normal file
16
peer/globals.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package peer
|
||||
|
||||
const (
|
||||
DEFAULT_PORT = 515
|
||||
NONCE_SIZE = 24
|
||||
KEY_SIZE = 32
|
||||
SIG_SIZE = 64
|
||||
MTU = 1408
|
||||
BUFFER_SIZE = MTU + NONCE_SIZE + SIG_SIZE
|
||||
|
||||
STREAM_DATA = 0
|
||||
STREAM_ROUTING = 1 // Routing queries and responses.
|
||||
|
||||
// Basic packet types
|
||||
PACKET_TYPE_DATA = 0
|
||||
)
|
||||
86
peer/main.go
Normal file
86
peer/main.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"vppn/m"
|
||||
|
||||
_ "net/http/pprof"
|
||||
)
|
||||
|
||||
func Main() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
log.Printf("Serving on localhost:6060...")
|
||||
log.Println(http.ListenAndServe("localhost:6060", nil))
|
||||
}()
|
||||
|
||||
var (
|
||||
netName string
|
||||
initURL string
|
||||
listenIP string
|
||||
port int
|
||||
)
|
||||
|
||||
flag.StringVar(&netName, "name", "", "[REQUIRED] The network name.")
|
||||
flag.StringVar(&initURL, "init-url", "", "Initializes peer from the hub URL.")
|
||||
flag.StringVar(&listenIP, "listen-ip", "", "IP address to listen on.")
|
||||
flag.IntVar(&port, "port", 0, "Port to listen on.")
|
||||
flag.Parse()
|
||||
|
||||
if netName == "" {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if initURL != "" {
|
||||
mainInit(netName, initURL)
|
||||
return
|
||||
}
|
||||
|
||||
peer, err := NewPeer(netName, listenIP, uint16(port))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create peer: %v", err)
|
||||
}
|
||||
|
||||
peer.Run()
|
||||
}
|
||||
|
||||
func mainInit(netName, initURL string) {
|
||||
if _, err := loadPeerConfig(netName); err == nil {
|
||||
log.Fatalf("Network is already initialized.")
|
||||
}
|
||||
|
||||
resp, err := http.Get(initURL)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to fetch data from hub: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read response body: %v", err)
|
||||
}
|
||||
|
||||
peerConfig := m.PeerConfig{}
|
||||
if err := json.Unmarshal(data, &peerConfig); err != nil {
|
||||
log.Fatalf("Failed to parse configuration: %v", err)
|
||||
}
|
||||
|
||||
if err := storePeerConfig(netName, peerConfig); err != nil {
|
||||
log.Fatalf("Failed to store configuration: %v", err)
|
||||
}
|
||||
|
||||
log.Print("Initialization successful.")
|
||||
}
|
||||
26
peer/nonce.go
Normal file
26
peer/nonce.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package peer
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func ParseNonceBytes(nb []byte, nonce *Nonce) {
|
||||
nonce.Counter = *(*uint64)(unsafe.Pointer(&nb[0]))
|
||||
nonce.SourceIP = nb[8]
|
||||
nonce.ViaIP = nb[9]
|
||||
nonce.DestIP = nb[10]
|
||||
nonce.StreamID = nb[11]
|
||||
nonce.PacketType = nb[12]
|
||||
}
|
||||
|
||||
func MarshalNonce(nonce Nonce, buf []byte) {
|
||||
*(*uint64)(unsafe.Pointer(&buf[0])) = nonce.Counter
|
||||
buf[8] = nonce.SourceIP
|
||||
buf[9] = nonce.ViaIP
|
||||
buf[10] = nonce.DestIP
|
||||
buf[11] = nonce.StreamID
|
||||
buf[12] = nonce.PacketType
|
||||
clear(buf[13:])
|
||||
}
|
||||
|
||||
func CounterTimestamp(counter uint64) int64 {
|
||||
return int64(counter >> 30)
|
||||
}
|
||||
25
peer/nonce_test.go
Normal file
25
peer/nonce_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMarshalParseNonce(t *testing.T) {
|
||||
nIn := Nonce{
|
||||
Counter: 3212,
|
||||
SourceIP: 34,
|
||||
ViaIP: 20,
|
||||
DestIP: 200,
|
||||
StreamID: 4,
|
||||
PacketType: 44,
|
||||
}
|
||||
|
||||
buf := make([]byte, NONCE_SIZE)
|
||||
MarshalNonce(nIn, buf)
|
||||
|
||||
nOut := Nonce{}
|
||||
ParseNonceBytes(buf, &nOut)
|
||||
if nIn != nOut {
|
||||
t.Fatal(nIn, nOut)
|
||||
}
|
||||
}
|
||||
114
peer/peer-ifreader.go
Normal file
114
peer/peer-ifreader.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime/debug"
|
||||
"vppn/fasttime"
|
||||
)
|
||||
|
||||
func (peer *Peer) ifReader() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
n int
|
||||
destIP byte
|
||||
router = peer.router
|
||||
route *route
|
||||
iface = peer.iface
|
||||
nonce = Nonce{
|
||||
SourceIP: peer.ip,
|
||||
PacketType: PACKET_TYPE_DATA,
|
||||
StreamID: STREAM_DATA,
|
||||
}
|
||||
err error
|
||||
now uint64
|
||||
counterTS uint64
|
||||
counter uint64
|
||||
packet = make([]byte, BUFFER_SIZE)
|
||||
encrypted = make([]byte, BUFFER_SIZE)
|
||||
nonceBuf = make([]byte, NONCE_SIZE)
|
||||
toSend []byte
|
||||
signingKey = peer.signPrivKey
|
||||
|
||||
reqPool = make(chan udpWriteReq, 1024)
|
||||
writeChan = make(chan udpWriteReq, 1024)
|
||||
)
|
||||
|
||||
for range cap(reqPool) {
|
||||
reqPool <- udpWriteReq{Packet: make([]byte, BUFFER_SIZE)}
|
||||
}
|
||||
|
||||
go udpWriter(writeChan, peer.conn, reqPool)
|
||||
|
||||
for {
|
||||
n, err = iface.Read(packet[:BUFFER_SIZE])
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read from interface: %v", err)
|
||||
}
|
||||
|
||||
if n < 20 {
|
||||
log.Printf("Dropping small packet: %d", n)
|
||||
continue
|
||||
}
|
||||
|
||||
packet = packet[:n]
|
||||
|
||||
destIP = packet[19]
|
||||
route = router.GetRoute(destIP)
|
||||
if route == nil {
|
||||
log.Printf("Dropping packet for non-existent IP: %d", destIP)
|
||||
continue
|
||||
}
|
||||
|
||||
now = uint64(fasttime.Now())
|
||||
if counterTS < now {
|
||||
counterTS = now
|
||||
counter = now << 30
|
||||
}
|
||||
|
||||
counter++
|
||||
|
||||
nonce.Counter = counter
|
||||
nonce.ViaIP = route.ViaIP
|
||||
nonce.DestIP = destIP
|
||||
|
||||
MarshalNonce(nonce, nonceBuf)
|
||||
|
||||
encrypted = encryptPacket(route.EncSharedKey, nonceBuf, packet, encrypted)
|
||||
|
||||
if route.ViaIP != 0 {
|
||||
toSend = signPacket(signingKey, encrypted, packet)
|
||||
} else {
|
||||
toSend = encrypted
|
||||
}
|
||||
|
||||
req := <-reqPool
|
||||
req.Addr = route.Addr
|
||||
req.Packet = req.Packet[:len(toSend)]
|
||||
copy(req.Packet, toSend)
|
||||
|
||||
writeChan <- req
|
||||
}
|
||||
}
|
||||
|
||||
type udpWriteReq struct {
|
||||
Addr netip.AddrPort
|
||||
Packet []byte
|
||||
}
|
||||
|
||||
func udpWriter(in chan udpWriteReq, conn *net.UDPConn, reqPool chan udpWriteReq) {
|
||||
var err error
|
||||
for req := range in {
|
||||
if _, err = conn.WriteToUDPAddrPort(req.Packet, req.Addr); err != nil {
|
||||
log.Fatalf("Failed to write UDP packet: %v", err)
|
||||
}
|
||||
reqPool <- req
|
||||
}
|
||||
}
|
||||
158
peer/peer-netreader.go
Normal file
158
peer/peer-netreader.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"runtime/debug"
|
||||
"vppn/fasttime"
|
||||
)
|
||||
|
||||
func (peer *Peer) netReader() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("stacktrace from panic: \n" + string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
var (
|
||||
n int
|
||||
//srcAddr *net.UDPAddr
|
||||
nonce Nonce
|
||||
packet = make([]byte, BUFFER_SIZE)
|
||||
decrypted = make([]byte, BUFFER_SIZE)
|
||||
toWrite []byte
|
||||
route *route
|
||||
ok bool
|
||||
err error
|
||||
conn = peer.conn
|
||||
ip = peer.ip
|
||||
counters = [2][256]uint64{} // Counter by stream and IP.
|
||||
|
||||
ifaceChan = make(chan []byte, 1024)
|
||||
reqPool = make(chan []byte, 1024)
|
||||
)
|
||||
|
||||
for range cap(reqPool) {
|
||||
reqPool <- make([]byte, BUFFER_SIZE)
|
||||
}
|
||||
|
||||
go ifWriter(ifaceChan, peer.iface, reqPool)
|
||||
|
||||
NEXT_PACKET:
|
||||
|
||||
n, _, err = conn.ReadFromUDPAddrPort(packet[:BUFFER_SIZE])
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read UDP packet: %v", err)
|
||||
}
|
||||
|
||||
if n < NONCE_SIZE {
|
||||
log.Printf("Dropping short UDP packet: %d", n)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
packet = packet[:n]
|
||||
|
||||
ParseNonceBytes(packet[n-NONCE_SIZE:], &nonce)
|
||||
|
||||
// Drop after 8 seconds.
|
||||
if CounterTimestamp(nonce.Counter) < fasttime.Now()-8 {
|
||||
log.Printf("Dropping old packet: %d", CounterTimestamp(nonce.Counter))
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
if nonce.StreamID > 1 {
|
||||
log.Printf("Dropping invalid stream ID: %v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
// Check source counter.
|
||||
if nonce.Counter <= counters[nonce.StreamID][nonce.SourceIP] {
|
||||
log.Printf("Dropping packet with bad counter: %v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
counters[nonce.StreamID][nonce.SourceIP] = nonce.Counter
|
||||
|
||||
route = peer.router.GetRoute(nonce.SourceIP)
|
||||
if route == nil {
|
||||
log.Printf("Dropping packet without route: %v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
switch ip {
|
||||
case nonce.DestIP:
|
||||
goto DECRYPT
|
||||
case nonce.ViaIP:
|
||||
goto VALIDATE_SIGNATURE
|
||||
default:
|
||||
log.Printf("Bad packet: %v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
DECRYPT:
|
||||
|
||||
decrypted, ok = decryptPacket(route.EncSharedKey, packet, decrypted)
|
||||
if !ok {
|
||||
log.Printf("Failed to decrypt packet: %v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
switch nonce.StreamID {
|
||||
case STREAM_DATA:
|
||||
goto WRITE_IFACE_DATA
|
||||
case STREAM_ROUTING:
|
||||
goto WRITE_ROUTING_PACKET
|
||||
default:
|
||||
log.Printf("Invalid stream ID: %d", nonce.StreamID)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
WRITE_IFACE_DATA:
|
||||
|
||||
//toWrite = toWrite[:len(decrypted)]
|
||||
//copy(toWrite, decrypted)
|
||||
|
||||
toWrite = <-reqPool
|
||||
ifaceChan <- append(toWrite[:0], decrypted...)
|
||||
|
||||
goto NEXT_PACKET
|
||||
|
||||
WRITE_ROUTING_PACKET:
|
||||
|
||||
//peer.processRoutingPacket(decrypted)
|
||||
//peer.routeManager.ProcessPacket(decrypted)
|
||||
|
||||
goto NEXT_PACKET
|
||||
|
||||
VALIDATE_SIGNATURE:
|
||||
|
||||
// We don't forward twice.
|
||||
if route.Mediator {
|
||||
log.Printf("Dropping double-forward packet: %v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
decrypted, ok = openPacket(route.SignPubKey, packet, decrypted)
|
||||
if !ok {
|
||||
log.Printf("Failed to open signed packet: %v", nonce)
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
if _, err = conn.WriteToUDPAddrPort(decrypted, route.Addr); err != nil {
|
||||
log.Fatalf("Failed to forward packet: %v", err)
|
||||
}
|
||||
|
||||
goto NEXT_PACKET
|
||||
}
|
||||
|
||||
func ifWriter(in chan []byte, iface io.ReadWriteCloser, out chan []byte) {
|
||||
var err error
|
||||
for packet := range in {
|
||||
if _, err = iface.Write(packet); err != nil {
|
||||
log.Printf("Size: %d", len(packet))
|
||||
log.Fatalf("Failed to write to interface: %v", err)
|
||||
}
|
||||
out <- packet
|
||||
}
|
||||
}
|
||||
67
peer/peer.go
Normal file
67
peer/peer.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Peer struct {
|
||||
// Immutable data.
|
||||
ip byte // Last byte of IPv4 address.
|
||||
hubAddr string
|
||||
apiKey string
|
||||
|
||||
encPubKey []byte
|
||||
encPrivKey []byte
|
||||
signPubKey []byte
|
||||
signPrivKey []byte
|
||||
conn *net.UDPConn
|
||||
iface io.ReadWriteCloser
|
||||
|
||||
router *Router
|
||||
}
|
||||
|
||||
func NewPeer(netName, listenIP string, port uint16) (*Peer, error) {
|
||||
conf, err := loadPeerConfig(netName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer := &Peer{
|
||||
ip: conf.PeerIP,
|
||||
hubAddr: conf.HubAddress,
|
||||
apiKey: conf.APIKey,
|
||||
encPubKey: conf.EncPubKey,
|
||||
encPrivKey: conf.EncPrivKey,
|
||||
signPubKey: conf.SignPubKey,
|
||||
signPrivKey: conf.SignPrivKey,
|
||||
}
|
||||
|
||||
peer.router = NewRouter(conf)
|
||||
|
||||
port = determinePort(conf.Port, port)
|
||||
|
||||
conn, err := openUDPConn(listenIP, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peer.conn = conn
|
||||
|
||||
peer.iface, err = openInterface(conf.Network, conf.PeerIP, netName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
func (p *Peer) Run() {
|
||||
go p.netReader()
|
||||
p.ifReader()
|
||||
}
|
||||
18
peer/router-ping.go
Normal file
18
peer/router-ping.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package peer
|
||||
|
||||
type RoutingPingReq struct {
|
||||
PeerIP byte
|
||||
Type byte // 0 => local, 1 => direct, 2 => Via
|
||||
Addr []byte
|
||||
Port int
|
||||
SentAt int64 // unix milli
|
||||
}
|
||||
|
||||
type RoutingPingResp struct {
|
||||
PeerIP byte
|
||||
Type byte // 0 => local, 1 => direct, 2 => Via
|
||||
Addr []byte
|
||||
Port int
|
||||
SentAt int64
|
||||
RecvdAt int64
|
||||
}
|
||||
123
peer/router.go
Normal file
123
peer/router.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"vppn/m"
|
||||
)
|
||||
|
||||
var zeroAddrPort netip.AddrPort
|
||||
|
||||
type routeInfo struct {
|
||||
Up bool
|
||||
Route route
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
conf m.PeerConfig
|
||||
routes [256]*atomic.Pointer[routeInfo]
|
||||
}
|
||||
|
||||
func NewRouter(conf m.PeerConfig) *Router {
|
||||
rm := &Router{
|
||||
conf: conf,
|
||||
}
|
||||
|
||||
for i := range rm.routes {
|
||||
rm.routes[i] = &atomic.Pointer[routeInfo]{}
|
||||
rm.routes[i].Store(&routeInfo{})
|
||||
}
|
||||
|
||||
go rm.pollHub()
|
||||
return rm
|
||||
}
|
||||
|
||||
func (rm *Router) GetRoute(ip byte) *route {
|
||||
if route := rm.routes[ip].Load(); route != nil && route.Up {
|
||||
return &route.Route
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rm *Router) pollHub() {
|
||||
u, err := url.Parse(rm.conf.HubAddress)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse hub address %s: %v", rm.conf.HubAddress, err)
|
||||
}
|
||||
u.Path = "/peer/fetch-state/"
|
||||
|
||||
client := &http.Client{Timeout: 8 * time.Second}
|
||||
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: u,
|
||||
Header: http.Header{},
|
||||
}
|
||||
req.SetBasicAuth("", rm.conf.APIKey)
|
||||
|
||||
rm._pollHub(client, req)
|
||||
|
||||
for range time.Tick(time.Minute) {
|
||||
rm._pollHub(client, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (rm *Router) _pollHub(client *http.Client, req *http.Request) {
|
||||
var state m.NetworkState
|
||||
|
||||
log.Printf("Fetching peer state from %s...", rm.conf.HubAddress)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch peer state: %v", err)
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
if err != nil {
|
||||
log.Printf("Failed to read body: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &state); err != nil {
|
||||
log.Printf("Failed to unmarshal response from hub: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for i, peer := range state.Peers {
|
||||
if peer == nil {
|
||||
continue
|
||||
}
|
||||
route := rm.routes[i].Load()
|
||||
rm.routes[i].Store(rm.updateRoute(route, peer))
|
||||
}
|
||||
}
|
||||
|
||||
func (rm *Router) updateRoute(routePtr *routeInfo, peer *m.Peer) *routeInfo {
|
||||
if peer == nil {
|
||||
return &routeInfo{}
|
||||
}
|
||||
|
||||
route := *routePtr
|
||||
|
||||
addr, ok := netip.AddrFromSlice(peer.IP)
|
||||
if !ok {
|
||||
return &routeInfo{}
|
||||
}
|
||||
|
||||
route.Up = true
|
||||
route.Route.Addr = netip.AddrPortFrom(addr, peer.Port)
|
||||
route.Route.Mediator = peer.Mediator
|
||||
route.Route.ViaIP = 0
|
||||
if len(route.Route.SignPubKey) == 0 {
|
||||
route.Route.SignPubKey = peer.SignPubKey
|
||||
route.Route.EncSharedKey = computeSharedKey(peer.EncPubKey, rm.conf.EncPrivKey)
|
||||
}
|
||||
|
||||
return &route
|
||||
}
|
||||
156
peer/startup.go
Normal file
156
peer/startup.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func determinePort(confPort, portFromCommandLine uint16) uint16 {
|
||||
if portFromCommandLine != 0 {
|
||||
return portFromCommandLine
|
||||
}
|
||||
if confPort != 0 {
|
||||
return confPort
|
||||
}
|
||||
return DEFAULT_PORT
|
||||
}
|
||||
|
||||
func openUDPConn(listenIP string, port uint16) (*net.UDPConn, error) {
|
||||
myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", listenIP, port))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct UDP address: %w", err)
|
||||
}
|
||||
|
||||
return net.ListenUDP("udp", myAddr)
|
||||
}
|
||||
|
||||
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(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(1000)
|
||||
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
|
||||
}
|
||||
22
peer/types.go
Normal file
22
peer/types.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
type route struct {
|
||||
Mediator bool // Route is via a mediator.
|
||||
SignPubKey []byte
|
||||
EncSharedKey []byte // Shared key for encoding / decoding packets.
|
||||
Addr netip.AddrPort // Address to send to.
|
||||
ViaIP byte // If != 0, this is a forwarding address.
|
||||
}
|
||||
|
||||
type Nonce struct {
|
||||
Counter uint64
|
||||
SourceIP byte
|
||||
ViaIP byte
|
||||
DestIP byte
|
||||
StreamID byte // The stream, see STREAM_* constants
|
||||
PacketType byte // The packet type. See PACKET_* constants.
|
||||
}
|
||||
Reference in New Issue
Block a user