Compare commits
	
		
			20 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | a0b5058544 | ||
|  | 232681fac6 | ||
|  | 6e7a2456b2 | ||
|  | 970490b17b | ||
|  | 2bdd76e689 | ||
|  | d495ba9be7 | ||
|  | 5b194581b5 | ||
|  | 55f63043ee | ||
|  | 36172bf310 | ||
| 2549e1ae08 | |||
| a6b849c6e3 | |||
|  | 9637550109 | ||
|  | b1e85733a2 | ||
|  | f0076939d5 | ||
|  | 5d97cccb98 | ||
|  | bbf5202d30 | ||
|  | 8407fd5b48 | ||
|  | f47a8245b4 | ||
|  | b8d814f636 | ||
|  | 6117a0afe4 | 
							
								
								
									
										49
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,41 +1,8 @@ | |||||||
| # vppn: Virtual Pretty Private Network | # vppn: Virtual Potentially Private Network | ||||||
|  |  | ||||||
| ## Roadmap | ## TODO | ||||||
|  |  | ||||||
| * Use probe and relayed-probe packets vs ping/pong. | * Add `-force-init` argument to `node` main? | ||||||
| * Rename Mediator -> Relay |  | ||||||
| * Use default port 456 |  | ||||||
| * Remove signing key from hub |  | ||||||
| * Peer: UDP hole-punching |  | ||||||
| * Peer: local peer discovery - part of RoutingProcessor |  | ||||||
| * Peer: update hub w/ latest port on startup |  | ||||||
|  |  | ||||||
| ## Learnings |  | ||||||
|  |  | ||||||
| * Encryption / decryption is 20x faster than signing/opening. |  | ||||||
| * Allowing out-of order packets is massively important for throughput with TCP |  | ||||||
|  |  | ||||||
| ## Principles |  | ||||||
|  |  | ||||||
| * Creates an IPv4/24 network with a maximum of 254 peers. (1-254) |  | ||||||
| * Simple setup: via setup link from the hub. |  | ||||||
| * Each peer has full network state replicated from the hub. |  | ||||||
|  |  | ||||||
| ## Routing |  | ||||||
|  |  | ||||||
| * Routing is different for public vs non-public peers |  | ||||||
|   * Public: routes are initialized via incoming ping requests |  | ||||||
|   * NonPub: routes are initialized via incoming ping responses |  | ||||||
|  |  | ||||||
| A non-public peer needs to maintain connections with every public peer. |  | ||||||
|  |  | ||||||
| * Sending: |  | ||||||
|   * Public: send to address |  | ||||||
|   * Non-public: send to a mediator |  | ||||||
|  |  | ||||||
| * Pings: |  | ||||||
|   * Servers don't need to ping |  | ||||||
|   * Clients need to ping all public and local peers to keep connections open |  | ||||||
|  |  | ||||||
| ## Hub Server Configuration | ## Hub Server Configuration | ||||||
|  |  | ||||||
| @@ -58,15 +25,12 @@ scp hub user@<remote>:~/ | |||||||
| Create systemd file in `/etc/systemd/system/hub.service | Create systemd file in `/etc/systemd/system/hub.service | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| Description=hub |  | ||||||
| Requires=network.target |  | ||||||
|  |  | ||||||
| [Service] | [Service] | ||||||
| AmbientCapabilities=CAP_NET_BIND_SERVICE | AmbientCapabilities=CAP_NET_BIND_SERVICE | ||||||
| Type=simple | Type=simple | ||||||
| User=user | User=user | ||||||
| WorkingDirectory=/home/user/ | WorkingDirectory=/home/user/ | ||||||
| ExecStart=/home/user/hub -listen <addr>:https -secure=true -root-dir=/home/user | ExecStart=/home/user/hub -listen <addr>:https -root-dir=/home/user | ||||||
| Restart=always | Restart=always | ||||||
| RestartSec=8 | RestartSec=8 | ||||||
| TimeoutStopSec=24 | TimeoutStopSec=24 | ||||||
| @@ -98,15 +62,12 @@ Create systemd file in `/etc/systemd/system/vppn.service`. | |||||||
|  |  | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| Description=vppn |  | ||||||
| Requires=network.target |  | ||||||
|  |  | ||||||
| [Service] | [Service] | ||||||
| AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN | AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN | ||||||
| Type=simple | Type=simple | ||||||
| User=user | User=user | ||||||
| WorkingDirectory=/home/user/ | WorkingDirectory=/home/user/ | ||||||
| ExecStart=/home/user/vppn -name vppn | ExecStart=/home/user/vppn -name vppn -hub-address https://my.hub -api-key 1234567890 | ||||||
| Restart=always | Restart=always | ||||||
| RestartSec=8 | RestartSec=8 | ||||||
| TimeoutStopSec=24 | TimeoutStopSec=24 | ||||||
|   | |||||||
| @@ -1,79 +0,0 @@ | |||||||
| package aestests |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"crypto/aes" |  | ||||||
| 	"crypto/cipher" |  | ||||||
| 	"crypto/rand" |  | ||||||
| 	"log" |  | ||||||
| 	"testing" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func must(err error) { |  | ||||||
| 	if err != nil { |  | ||||||
| 		panic(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestAES(t *testing.T) { |  | ||||||
| 	key := make([]byte, 32) |  | ||||||
| 	rand.Read(key) |  | ||||||
|  |  | ||||||
| 	block, err := aes.NewCipher(key) |  | ||||||
| 	must(err) |  | ||||||
|  |  | ||||||
| 	aesgcm, err := cipher.NewGCM(block) |  | ||||||
| 	must(err) |  | ||||||
|  |  | ||||||
| 	log.Print(aesgcm.NonceSize()) |  | ||||||
| 	log.Print(aesgcm.Overhead()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func BenchmarkSeal(b *testing.B) { |  | ||||||
| 	key := make([]byte, 32) |  | ||||||
| 	rand.Read(key) |  | ||||||
|  |  | ||||||
| 	block, err := aes.NewCipher(key) |  | ||||||
| 	must(err) |  | ||||||
|  |  | ||||||
| 	cryptor, err := cipher.NewGCM(block) |  | ||||||
| 	must(err) |  | ||||||
|  |  | ||||||
| 	nonce := make([]byte, 12) |  | ||||||
| 	rand.Read(nonce) |  | ||||||
|  |  | ||||||
| 	data := make([]byte, 1400) |  | ||||||
| 	rand.Read(data) |  | ||||||
|  |  | ||||||
| 	out := make([]byte, 1500) |  | ||||||
| 	b.ResetTimer() |  | ||||||
| 	for i := 0; i < b.N; i++ { |  | ||||||
| 		out = cryptor.Seal(out[:0], nonce, data, nil) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func BenchmarkOpen(b *testing.B) { |  | ||||||
| 	key := make([]byte, 32) |  | ||||||
| 	rand.Read(key) |  | ||||||
|  |  | ||||||
| 	block, err := aes.NewCipher(key) |  | ||||||
| 	must(err) |  | ||||||
|  |  | ||||||
| 	cryptor, err := cipher.NewGCM(block) |  | ||||||
| 	must(err) |  | ||||||
|  |  | ||||||
| 	nonce := make([]byte, 12) |  | ||||||
| 	rand.Read(nonce) |  | ||||||
|  |  | ||||||
| 	data := make([]byte, 1400) |  | ||||||
| 	rand.Read(data) |  | ||||||
|  |  | ||||||
| 	sealed := make([]byte, 1500) |  | ||||||
| 	sealed = cryptor.Seal(sealed[:0], nonce, data, nil) |  | ||||||
|  |  | ||||||
| 	dec := make([]byte, 1500) |  | ||||||
|  |  | ||||||
| 	b.ResetTimer() |  | ||||||
| 	for i := 0; i < b.N; i++ { |  | ||||||
| 		dec, err = cryptor.Open(dec[:0], nonce, sealed, nil) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| go build |  | ||||||
| sudo setcap cap_net_admin+iep vppn |  | ||||||
| sudo setcap cap_net_bind_service+iep vppn |  | ||||||
							
								
								
									
										3
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
									
									
									
									
								
							| @@ -5,13 +5,12 @@ go 1.23.2 | |||||||
| require ( | require ( | ||||||
| 	git.crumpington.com/lib/go v0.8.1 | 	git.crumpington.com/lib/go v0.8.1 | ||||||
| 	git.crumpington.com/lib/webutil v0.0.7 | 	git.crumpington.com/lib/webutil v0.0.7 | ||||||
| 	github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 |  | ||||||
| 	golang.org/x/crypto v0.29.0 | 	golang.org/x/crypto v0.29.0 | ||||||
|  | 	golang.org/x/sys v0.27.0 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/mattn/go-sqlite3 v1.14.24 // indirect | 	github.com/mattn/go-sqlite3 v1.14.24 // indirect | ||||||
| 	golang.org/x/net v0.31.0 // indirect | 	golang.org/x/net v0.31.0 // indirect | ||||||
| 	golang.org/x/sys v0.27.0 // indirect |  | ||||||
| 	golang.org/x/text v0.20.0 // indirect | 	golang.org/x/text v0.20.0 // indirect | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							| @@ -4,8 +4,6 @@ git.crumpington.com/lib/webutil v0.0.7 h1:1RG9CpuXYalT0NPj8fvxjOLV566LqL37APvAdA | |||||||
| git.crumpington.com/lib/webutil v0.0.7/go.mod h1:efIEiuK1uqFIhI/dlsWUHMsC5bXcEbJEjmdluRoFPPQ= | git.crumpington.com/lib/webutil v0.0.7/go.mod h1:efIEiuK1uqFIhI/dlsWUHMsC5bXcEbJEjmdluRoFPPQ= | ||||||
| github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= | github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= | ||||||
| github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= | github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= | ||||||
| github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= |  | ||||||
| github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= |  | ||||||
| golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= | golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= | ||||||
| golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= | golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= | ||||||
| golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= | golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package api | package api | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"crypto/rand" |  | ||||||
| 	"database/sql" | 	"database/sql" | ||||||
| 	"embed" | 	"embed" | ||||||
| 	"errors" | 	"errors" | ||||||
| @@ -14,7 +13,6 @@ import ( | |||||||
| 	"git.crumpington.com/lib/go/idgen" | 	"git.crumpington.com/lib/go/idgen" | ||||||
| 	"git.crumpington.com/lib/go/sqliteutil" | 	"git.crumpington.com/lib/go/sqliteutil" | ||||||
| 	"golang.org/x/crypto/bcrypt" | 	"golang.org/x/crypto/bcrypt" | ||||||
| 	"golang.org/x/crypto/nacl/box" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| //go:embed migrations | //go:embed migrations | ||||||
| @@ -23,7 +21,6 @@ var migrations embed.FS | |||||||
| type API struct { | type API struct { | ||||||
| 	db   *sql.DB | 	db   *sql.DB | ||||||
| 	lock sync.Mutex | 	lock sync.Mutex | ||||||
| 	peerIntents map[string]PeerCreateArgs |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(dbPath string) (*API, error) { | func New(dbPath string) (*API, error) { | ||||||
| @@ -38,7 +35,6 @@ func New(dbPath string) (*API, error) { | |||||||
|  |  | ||||||
| 	a := &API{ | 	a := &API{ | ||||||
| 		db: sqlDB, | 		db: sqlDB, | ||||||
| 		peerIntents: map[string]PeerCreateArgs{}, |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return a, a.ensurePassword() | 	return a, a.ensurePassword() | ||||||
| @@ -141,78 +137,24 @@ func (a *API) Session_SignIn(s *Session, pwd string) error { | |||||||
| 	return db.Session_SetSignedIn(a.db, s.SessionID) | 	return db.Session_SetSignedIn(a.db, s.SessionID) | ||||||
| } | } | ||||||
|  |  | ||||||
| type PeerCreateArgs struct { | func (a *API) Peer_CreateNew(p *Peer) error { | ||||||
| 	Name     string | 	p.Version = idgen.NextID(0) | ||||||
| 	PublicIP []byte | 	p.PubKey = []byte{} | ||||||
| 	Port     uint16 | 	p.PubSignKey = []byte{} | ||||||
| 	Relay    bool | 	p.APIKey = idgen.NewToken() | ||||||
|  |  | ||||||
|  | 	return db.Peer_Insert(a.db, p) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Create the intention to add a peer. The returned code is used to complete | func (a *API) Peer_Init(peer *Peer, args m.PeerInitArgs) (*m.PeerConfig, error) { | ||||||
| // the peer creation. The code is valid for 5 minutes. |  | ||||||
| func (a *API) Peer_CreateIntent(args PeerCreateArgs) string { |  | ||||||
| 	a.lock.Lock() | 	a.lock.Lock() | ||||||
| 	defer a.lock.Unlock() | 	defer a.lock.Unlock() | ||||||
|  |  | ||||||
| 	code := idgen.NewToken() | 	peer.Version = idgen.NextID(0) | ||||||
| 	a.peerIntents[code] = args | 	peer.PubKey = args.EncPubKey | ||||||
|  | 	peer.PubSignKey = args.PubSignKey | ||||||
|  |  | ||||||
| 	go func() { | 	if err := db.Peer_UpdateFull(a.db, peer); err != nil { | ||||||
| 		time.Sleep(5 * time.Minute) |  | ||||||
| 		a.lock.Lock() |  | ||||||
| 		defer a.lock.Unlock() |  | ||||||
| 		delete(a.peerIntents, code) |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	return code |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { |  | ||||||
| 	a.lock.Lock() |  | ||||||
| 	defer a.lock.Unlock() |  | ||||||
|  |  | ||||||
| 	args, ok := a.peerIntents[creationCode] |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil, ErrNotAuthorized |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	delete(a.peerIntents, creationCode) |  | ||||||
|  |  | ||||||
| 	encPubKey, encPrivKey, err := box.GenerateKey(rand.Reader) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Get peer IP. |  | ||||||
| 	peerIP := byte(0) |  | ||||||
|  |  | ||||||
| 	for i := byte(1); i < 255; i++ { |  | ||||||
| 		exists, err := db.Peer_Exists(a.db, i) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		if !exists { |  | ||||||
| 			peerIP = i |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if peerIP == 0 { |  | ||||||
| 		return nil, ErrNoIPAvailable |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	peer := &Peer{ |  | ||||||
| 		PeerIP:   peerIP, |  | ||||||
| 		Version:  idgen.NextID(0), |  | ||||||
| 		APIKey:   idgen.NewToken(), |  | ||||||
| 		Name:     args.Name, |  | ||||||
| 		PublicIP: args.PublicIP, |  | ||||||
| 		Port:     args.Port, |  | ||||||
| 		Relay:    args.Relay, |  | ||||||
| 		PubKey:   encPubKey[:], |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err := db.Peer_Insert(a.db, peer); err != nil { |  | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -220,14 +162,10 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { | |||||||
|  |  | ||||||
| 	return &m.PeerConfig{ | 	return &m.PeerConfig{ | ||||||
| 		PeerIP:   peer.PeerIP, | 		PeerIP:   peer.PeerIP, | ||||||
| 		HubAddress: conf.HubAddress, |  | ||||||
| 		APIKey:     peer.APIKey, |  | ||||||
| 		Network:  conf.VPNNetwork, | 		Network:  conf.VPNNetwork, | ||||||
| 		PublicIP: peer.PublicIP, | 		PublicIP: peer.PublicIP, | ||||||
| 		Port:     peer.Port, | 		Port:     peer.Port, | ||||||
| 		Relay:    peer.Relay, | 		Relay:    peer.Relay, | ||||||
| 		PubKey:     encPubKey[:], |  | ||||||
| 		PrivKey:    encPrivKey[:], |  | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -315,9 +315,10 @@ type Peer struct { | |||||||
| 	Port       uint16 | 	Port       uint16 | ||||||
| 	Relay      bool | 	Relay      bool | ||||||
| 	PubKey     []byte | 	PubKey     []byte | ||||||
|  | 	PubSignKey []byte | ||||||
| } | } | ||||||
|  |  | ||||||
| const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers" | const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers" | ||||||
|  |  | ||||||
| func Peer_Insert( | func Peer_Insert( | ||||||
| 	tx TX, | 	tx TX, | ||||||
| @@ -328,7 +329,7 @@ func Peer_Insert( | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	_, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey) VALUES(?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey) | 	_, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey) VALUES(?,?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -369,7 +370,7 @@ func Peer_UpdateFull( | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PeerIP) | 	result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=?,PubSignKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey, row.PeerIP) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -419,8 +420,8 @@ func Peer_Get( | |||||||
| 	err error, | 	err error, | ||||||
| ) { | ) { | ||||||
| 	row = &Peer{} | 	row = &Peer{} | ||||||
| 	r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers WHERE PeerIP=?", PeerIP) | 	r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers WHERE PeerIP=?", PeerIP) | ||||||
| 	err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) | 	err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -434,7 +435,7 @@ func Peer_GetWhere( | |||||||
| ) { | ) { | ||||||
| 	row = &Peer{} | 	row = &Peer{} | ||||||
| 	r := tx.QueryRow(query, args...) | 	r := tx.QueryRow(query, args...) | ||||||
| 	err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) | 	err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -454,7 +455,7 @@ func Peer_Iterate( | |||||||
| 		defer rows.Close() | 		defer rows.Close() | ||||||
| 		for rows.Next() { | 		for rows.Next() { | ||||||
| 			row := &Peer{} | 			row := &Peer{} | ||||||
| 			err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) | 			err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) | ||||||
| 			if !yield(row, err) { | 			if !yield(row, err) { | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -21,5 +21,6 @@ TABLE peers OF Peer ( | |||||||
|   PublicIP   []byte, |   PublicIP   []byte, | ||||||
|   Port       uint16, |   Port       uint16, | ||||||
|   Relay      bool, |   Relay      bool, | ||||||
|   PubKey     []byte NoUpdate |   PubKey     []byte NoUpdate, | ||||||
|  |   PubSignKey []byte NoUpdate | ||||||
| ); | ); | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								hub/api/migrations/2024-12-27-signing-keys.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								hub/api/migrations/2024-12-27-signing-keys.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | ALTER TABLE peers ADD COLUMN PubSignKey BLOB NOT NULL DEFAULT ''; | ||||||
| @@ -1 +0,0 @@ | |||||||
| package api |  | ||||||
| @@ -19,14 +19,14 @@ var templateFS embed.FS | |||||||
| type Config struct { | type Config struct { | ||||||
| 	RootDir    string | 	RootDir    string | ||||||
| 	ListenAddr string | 	ListenAddr string | ||||||
| 	Secure     bool | 	Insecure   bool | ||||||
| } | } | ||||||
|  |  | ||||||
| type App struct { | type App struct { | ||||||
| 	api      *api.API | 	api      *api.API | ||||||
| 	mux      *http.ServeMux | 	mux      *http.ServeMux | ||||||
| 	tmpl     map[string]*template.Template | 	tmpl     map[string]*template.Template | ||||||
| 	secure bool | 	insecure bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewApp(conf Config) (*App, error) { | func NewApp(conf Config) (*App, error) { | ||||||
| @@ -39,7 +39,7 @@ func NewApp(conf Config) (*App, error) { | |||||||
| 		api:      api, | 		api:      api, | ||||||
| 		mux:      http.NewServeMux(), | 		mux:      http.NewServeMux(), | ||||||
| 		tmpl:     webutil.ParseTemplateSet(templateFuncs, templateFS), | 		tmpl:     webutil.ParseTemplateSet(templateFuncs, templateFS), | ||||||
| 		secure: conf.Secure, | 		insecure: conf.Insecure, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	app.registerRoutes() | 	app.registerRoutes() | ||||||
|   | |||||||
| @@ -17,8 +17,9 @@ func (a *App) setCookie(w http.ResponseWriter, name, value string) { | |||||||
| 		Name:     name, | 		Name:     name, | ||||||
| 		Value:    value, | 		Value:    value, | ||||||
| 		Path:     "/", | 		Path:     "/", | ||||||
| 		Secure:   a.secure, | 		Secure:   !a.insecure, | ||||||
| 		SameSite: http.SameSiteStrictMode, | 		SameSite: http.SameSiteStrictMode, | ||||||
|  | 		HttpOnly: true, | ||||||
| 		MaxAge:   86400 * 365 * 10, | 		MaxAge:   86400 * 365 * 10, | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -65,13 +65,26 @@ func (app *App) handleSignedIn(pattern string, fn handlerFunc) { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| type peerHandlerFunc func(w http.ResponseWriter, r *http.Request) error | type peerHandlerFunc func(p *api.Peer, w http.ResponseWriter, r *http.Request) error | ||||||
|  |  | ||||||
| func (app *App) handlePeer(pattern string, fn peerHandlerFunc) { | func (app *App) handlePeer(pattern string, fn peerHandlerFunc) { | ||||||
| 	wrapped := func(w http.ResponseWriter, r *http.Request) { | 	wrapped := func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 		_, apiKey, ok := r.BasicAuth() | ||||||
|  | 		if !ok { | ||||||
|  | 			http.Error(w, "Not authorized", http.StatusUnauthorized) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		peer, err := app.api.Peer_GetByAPIKey(apiKey) | ||||||
|  | 		if err != nil { | ||||||
|  | 			http.Error(w, "Not authorized", http.StatusUnauthorized) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		r.ParseForm() | 		r.ParseForm() | ||||||
| 		if err := fn(w, r); err != nil { | 		if err := fn(peer, w, r); err != nil { | ||||||
| 			http.Error(w, err.Error(), http.StatusInternalServerError) | 			http.Error(w, err.Error(), http.StatusInternalServerError) | ||||||
|  | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package hub | package hub | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"encoding/json" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| @@ -54,11 +55,18 @@ func (a *App) _adminSignOutSubmit(s *api.Session, w http.ResponseWriter, r *http | |||||||
| } | } | ||||||
|  |  | ||||||
| func (a *App) _adminConfig(s *api.Session, w http.ResponseWriter, r *http.Request) error { | func (a *App) _adminConfig(s *api.Session, w http.ResponseWriter, r *http.Request) error { | ||||||
|  | 	peers, err := a.api.Peer_List() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return a.render("/admin-config.html", w, struct { | 	return a.render("/admin-config.html", w, struct { | ||||||
| 		Session *api.Session | 		Session *api.Session | ||||||
|  | 		Peers   []*api.Peer | ||||||
| 		Config  *api.Config | 		Config  *api.Config | ||||||
| 	}{ | 	}{ | ||||||
| 		s, | 		s, | ||||||
|  | 		peers, | ||||||
| 		a.api.Config_Get(), | 		a.api.Config_Get(), | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| @@ -142,21 +150,6 @@ func (a *App) _adminPasswordSubmit(s *api.Session, w http.ResponseWriter, r *htt | |||||||
| 	return a.redirect(w, r, "/admin/config/") | 	return a.redirect(w, r, "/admin/config/") | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *App) _adminPeerList(s *api.Session, w http.ResponseWriter, r *http.Request) error { |  | ||||||
| 	peers, err := a.api.Peer_List() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return a.render("/admin-peer-list.html", w, struct { |  | ||||||
| 		Session *api.Session |  | ||||||
| 		Peers   []*api.Peer |  | ||||||
| 	}{ |  | ||||||
| 		s, |  | ||||||
| 		peers, |  | ||||||
| 	}) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (a *App) _adminHosts(s *api.Session, w http.ResponseWriter, r *http.Request) error { | func (a *App) _adminHosts(s *api.Session, w http.ResponseWriter, r *http.Request) error { | ||||||
| 	conf := a.api.Config_Get() | 	conf := a.api.Config_Get() | ||||||
|  |  | ||||||
| @@ -187,36 +180,26 @@ func (a *App) _adminPeerCreate(s *api.Session, w http.ResponseWriter, r *http.Re | |||||||
| func (a *App) _adminPeerCreateSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { | func (a *App) _adminPeerCreateSubmit(s *api.Session, w http.ResponseWriter, r *http.Request) error { | ||||||
| 	var ipStr string | 	var ipStr string | ||||||
|  |  | ||||||
| 	args := api.PeerCreateArgs{} | 	p := &api.Peer{} | ||||||
| 	err := webutil.NewFormScanner(r.Form). | 	err := webutil.NewFormScanner(r.Form). | ||||||
| 		Scan("Name", &args.Name). | 		Scan("IP", &p.PeerIP). | ||||||
|  | 		Scan("Name", &p.Name). | ||||||
| 		Scan("PublicIP", &ipStr). | 		Scan("PublicIP", &ipStr). | ||||||
| 		Scan("Port", &args.Port). | 		Scan("Port", &p.Port). | ||||||
| 		Scan("Relay", &args.Relay). | 		Scan("Relay", &p.Relay). | ||||||
| 		Error() | 		Error() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if args.PublicIP, err = stringToIP(ipStr); err != nil { | 	if p.PublicIP, err = stringToIP(ipStr); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	code := a.api.Peer_CreateIntent(args) | 	if err := a.api.Peer_CreateNew(p); err != nil { | ||||||
| 	return a.redirect(w, r, "/admin/peer/intent-created/?Code=%s", code) | 		return err | ||||||
| 	} | 	} | ||||||
|  | 	return a.redirect(w, r, "/admin/peer/view/?PeerIP=%d", p.PeerIP) | ||||||
| func (a *App) _adminPeerIntentCreated(s *api.Session, w http.ResponseWriter, r *http.Request) error { |  | ||||||
| 	code := r.FormValue("Code") |  | ||||||
| 	if code == "" { |  | ||||||
| 		return errors.New("missing Code") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return a.render("/admin-peer-intent.html", w, struct { |  | ||||||
| 		Session    *api.Session |  | ||||||
| 		HubAddress string |  | ||||||
| 		Code       string |  | ||||||
| 	}{s, a.api.Config_Get().HubAddress, code}) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *App) _adminPeerView(s *api.Session, w http.ResponseWriter, r *http.Request) error { | func (a *App) _adminPeerView(s *api.Session, w http.ResponseWriter, r *http.Request) error { | ||||||
| @@ -323,9 +306,13 @@ func (a *App) _adminPeerDeleteSubmit(s *api.Session, w http.ResponseWriter, r *h | |||||||
| 	return a.redirect(w, r, "/admin/peer/list/") | 	return a.redirect(w, r, "/admin/peer/list/") | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *App) _peerCreate(w http.ResponseWriter, r *http.Request) error { | func (a *App) _peerInit(peer *api.Peer, w http.ResponseWriter, r *http.Request) error { | ||||||
| 	code := r.FormValue("Code") | 	args := m.PeerInitArgs{} | ||||||
| 	conf, err := a.api.Peer_Create(code) | 	if err := json.NewDecoder(r.Body).Decode(&args); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	conf, err := a.api.Peer_Init(peer, args) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -333,33 +320,16 @@ func (a *App) _peerCreate(w http.ResponseWriter, r *http.Request) error { | |||||||
| 	return a.sendJSON(w, conf) | 	return a.sendJSON(w, conf) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *App) _peerFetchState(w http.ResponseWriter, r *http.Request) error { | func (a *App) _peerFetchState(peer *api.Peer, w http.ResponseWriter, r *http.Request) error { | ||||||
| 	_, apiKey, ok := r.BasicAuth() |  | ||||||
| 	if !ok { |  | ||||||
| 		return api.ErrNotAuthorized |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	peer, err := a.api.Peer_GetByAPIKey(apiKey) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	peers, err := a.api.Peer_List() | 	peers, err := a.api.Peer_List() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	conf := a.api.Config_Get() | 	state := m.NetworkState{} | ||||||
|  |  | ||||||
| 	state := m.NetworkState{ |  | ||||||
| 		HubAddress: conf.HubAddress, |  | ||||||
| 		Network:    conf.VPNNetwork, |  | ||||||
| 		PeerIP:     peer.PeerIP, |  | ||||||
| 		PublicIP:   peer.PublicIP, |  | ||||||
| 		Port:       peer.Port, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, p := range peers { | 	for _, p := range peers { | ||||||
|  | 		if len(p.PubKey) != 0 { | ||||||
| 			state.Peers[p.PeerIP] = &m.Peer{ | 			state.Peers[p.PeerIP] = &m.Peer{ | ||||||
| 				PeerIP:     p.PeerIP, | 				PeerIP:     p.PeerIP, | ||||||
| 				Version:    p.Version, | 				Version:    p.Version, | ||||||
| @@ -368,6 +338,8 @@ func (a *App) _peerFetchState(w http.ResponseWriter, r *http.Request) error { | |||||||
| 				Port:       p.Port, | 				Port:       p.Port, | ||||||
| 				Relay:      p.Relay, | 				Relay:      p.Relay, | ||||||
| 				PubKey:     p.PubKey, | 				PubKey:     p.PubKey, | ||||||
|  | 				PubSignKey: p.PubSignKey, | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ func Main() { | |||||||
| 	conf := Config{} | 	conf := Config{} | ||||||
| 	flag.StringVar(&conf.RootDir, "root-dir", "", "[REQUIRED] Root directory.") | 	flag.StringVar(&conf.RootDir, "root-dir", "", "[REQUIRED] Root directory.") | ||||||
| 	flag.StringVar(&conf.ListenAddr, "listen", "", "[REQUIRED] Listen address.") | 	flag.StringVar(&conf.ListenAddr, "listen", "", "[REQUIRED] Listen address.") | ||||||
| 	flag.BoolVar(&conf.Secure, "secure", false, "Use secure cookies.") | 	flag.BoolVar(&conf.Insecure, "insecure", false, "Don't use secure cookies.") | ||||||
|  |  | ||||||
| 	flag.Parse() | 	flag.Parse() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,17 +16,15 @@ func (a *App) registerRoutes() { | |||||||
| 	a.handleSignedIn("POST /admin/sign-out/", a._adminSignOutSubmit) | 	a.handleSignedIn("POST /admin/sign-out/", a._adminSignOutSubmit) | ||||||
| 	a.handleSignedIn("GET  /admin/password/edit/", a._adminPasswordEdit) | 	a.handleSignedIn("GET  /admin/password/edit/", a._adminPasswordEdit) | ||||||
| 	a.handleSignedIn("POST /admin/password/edit/", a._adminPasswordSubmit) | 	a.handleSignedIn("POST /admin/password/edit/", a._adminPasswordSubmit) | ||||||
| 	a.handleSignedIn("GET  /admin/peer/list/", a._adminPeerList) |  | ||||||
| 	a.handleSignedIn("GET  /admin/peer/hosts/", a._adminHosts) | 	a.handleSignedIn("GET  /admin/peer/hosts/", a._adminHosts) | ||||||
| 	a.handleSignedIn("GET  /admin/peer/create/", a._adminPeerCreate) | 	a.handleSignedIn("GET  /admin/peer/create/", a._adminPeerCreate) | ||||||
| 	a.handleSignedIn("POST /admin/peer/create/", a._adminPeerCreateSubmit) | 	a.handleSignedIn("POST /admin/peer/create/", a._adminPeerCreateSubmit) | ||||||
| 	a.handleSignedIn("GET  /admin/peer/intent-created/", a._adminPeerIntentCreated) |  | ||||||
| 	a.handleSignedIn("GET  /admin/peer/view/", a._adminPeerView) | 	a.handleSignedIn("GET  /admin/peer/view/", a._adminPeerView) | ||||||
| 	a.handleSignedIn("GET  /admin/peer/edit/", a._adminPeerEdit) | 	a.handleSignedIn("GET  /admin/peer/edit/", a._adminPeerEdit) | ||||||
| 	a.handleSignedIn("POST /admin/peer/edit/", a._adminPeerEditSubmit) | 	a.handleSignedIn("POST /admin/peer/edit/", a._adminPeerEditSubmit) | ||||||
| 	a.handleSignedIn("GET  /admin/peer/delete/", a._adminPeerDelete) | 	a.handleSignedIn("GET  /admin/peer/delete/", a._adminPeerDelete) | ||||||
| 	a.handleSignedIn("POST /admin/peer/delete/", a._adminPeerDeleteSubmit) | 	a.handleSignedIn("POST /admin/peer/delete/", a._adminPeerDeleteSubmit) | ||||||
|  |  | ||||||
| 	a.handlePeer("GET /peer/create/", a._peerCreate) | 	a.handlePeer("POST /peer/init/", a._peerInit) | ||||||
| 	a.handlePeer("GET /peer/fetch-state/", a._peerFetchState) | 	a.handlePeer("GET /peer/fetch-state/", a._peerFetchState) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,3 @@ | |||||||
| body { |  | ||||||
|     max-width: 1920px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .def-list tr td:first-child { | .def-list tr td:first-child { | ||||||
|     text-align:right; |     text-align:right; | ||||||
|     width:1%; |     width:1%; | ||||||
|   | |||||||
| @@ -16,4 +16,42 @@ | |||||||
|     <td>{{ipToString .Config.VPNNetwork}}</td> |     <td>{{ipToString .Config.VPNNetwork}}</td> | ||||||
|   </tr> |   </tr> | ||||||
| </table> | </table> | ||||||
|  |  | ||||||
|  | <h2>Peers</h2> | ||||||
|  |  | ||||||
|  | <p> | ||||||
|  |   <a href="/admin/peer/create/">Add Peer</a> / | ||||||
|  |   <a href="/admin/peer/hosts/">Hosts</a> | ||||||
|  | </p> | ||||||
|  |  | ||||||
|  | {{if .Peers -}} | ||||||
|  | <table> | ||||||
|  |   <thead> | ||||||
|  |     <tr> | ||||||
|  |       <th>PeerIP</th> | ||||||
|  |       <th>Name</th> | ||||||
|  |       <th>Public IP</th> | ||||||
|  |       <th>Port</th> | ||||||
|  |       <th>Relay</th> | ||||||
|  |     </tr> | ||||||
|  |   </thead> | ||||||
|  |   <tbody> | ||||||
|  |   {{range .Peers -}} | ||||||
|  |   <tr> | ||||||
|  |     <td> | ||||||
|  |       <a href="/admin/peer/view/?PeerIP={{.PeerIP}}"> | ||||||
|  |         {{.PeerIP}} | ||||||
|  |       </a> | ||||||
|  |     </td> | ||||||
|  |     <td>{{.Name}}</td> | ||||||
|  |     <td>{{ipToString .PublicIP}}</td> | ||||||
|  |     <td>{{.Port}}</td> | ||||||
|  |     <td>{{if .Relay}}T{{else}}F{{end}}</td> | ||||||
|  |   </tr> | ||||||
|  |   </tbody> | ||||||
|  |   {{- end}} | ||||||
|  | </table> | ||||||
|  | {{- else}} | ||||||
|  | <p>No peers.</p> | ||||||
|  | {{- end}} | ||||||
| {{- end}} | {{- end}} | ||||||
|   | |||||||
| @@ -3,6 +3,10 @@ | |||||||
|  |  | ||||||
| <form method="POST"> | <form method="POST"> | ||||||
|   <input type="hidden" name="CSRF" value="{{.Session.CSRF}}"> |   <input type="hidden" name="CSRF" value="{{.Session.CSRF}}"> | ||||||
|  |   <p> | ||||||
|  |     <label>IP</label><br> | ||||||
|  |     <input type="number" name="IP" min="1" max="255" value="0"> | ||||||
|  |   </p> | ||||||
|   <p> |   <p> | ||||||
|     <label>Name</label><br> |     <label>Name</label><br> | ||||||
|     <input type="text" name="Name"> |     <input type="text" name="Name"> | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								hub/templates/admin-peer-init.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								hub/templates/admin-peer-init.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | {{define "body" -}} | ||||||
|  | <h2>Initialize Peer</h2> | ||||||
|  |  | ||||||
|  | <p> | ||||||
|  |   Configure the peer with the following URL: | ||||||
|  | </p> | ||||||
|  | <pre> | ||||||
|  |   {{.HubAddress}}/peer/init/?Code={{.Code}} | ||||||
|  | </pre> | ||||||
|  | <p> | ||||||
|  |   <a href="/admin/config/">Done</a> | ||||||
|  | </p> | ||||||
|  | {{- end}} | ||||||
| @@ -8,6 +8,6 @@ | |||||||
|   {{.HubAddress}}/peer/create/?Code={{.Code}} |   {{.HubAddress}}/peer/create/?Code={{.Code}} | ||||||
| </pre> | </pre> | ||||||
| <p> | <p> | ||||||
|   <a href="/admin/peer/list/">Done</a> |   <a href="/admin/config/">Done</a> | ||||||
| </p> | </p> | ||||||
| {{- end}} | {{- end}} | ||||||
|   | |||||||
| @@ -1,40 +0,0 @@ | |||||||
| {{define "body" -}} |  | ||||||
| <h2>Peers</h2> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
|   <a href="/admin/peer/create/">Add Peer</a> / |  | ||||||
|   <a href="/admin/peer/hosts/">Hosts</a> |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| {{if .Peers -}} |  | ||||||
| <table> |  | ||||||
|   <thead> |  | ||||||
|     <tr> |  | ||||||
|       <th>PeerIP</th> |  | ||||||
|       <th>Name</th> |  | ||||||
|       <th>Public IP</th> |  | ||||||
|       <th>Port</th> |  | ||||||
|       <th>Relay</th> |  | ||||||
|     </tr> |  | ||||||
|   </thead> |  | ||||||
|   <tbody> |  | ||||||
|   {{range .Peers -}} |  | ||||||
|   <tr> |  | ||||||
|     <td> |  | ||||||
|       <a href="/admin/peer/view/?PeerIP={{.PeerIP}}"> |  | ||||||
|         {{.PeerIP}} |  | ||||||
|       </a> |  | ||||||
|     </td> |  | ||||||
|     <td>{{.Name}}</td> |  | ||||||
|     <td>{{ipToString .PublicIP}}</td> |  | ||||||
|     <td>{{.Port}}</td> |  | ||||||
|     <td>{{if .Relay}}T{{else}}F{{end}}</td> |  | ||||||
|   </tr> |  | ||||||
|   </tbody> |  | ||||||
|   {{- end}} |  | ||||||
| </table> |  | ||||||
| {{- else}} |  | ||||||
| <p>No peers.</p> |  | ||||||
| {{- end}} |  | ||||||
|  |  | ||||||
| {{- end}} |  | ||||||
| @@ -10,8 +10,7 @@ | |||||||
|     <h1>VPPN</h1> |     <h1>VPPN</h1> | ||||||
|     <nav> |     <nav> | ||||||
|       {{if .Session.SignedIn -}} |       {{if .Session.SignedIn -}} | ||||||
|       <a href="/admin/config/">Config</a> / |       <a href="/admin/config/">Home</a> / | ||||||
|       <a href="/admin/peer/list/">Peers</a> / |  | ||||||
|       <a href="/admin/sign-out/">Sign out</a> |       <a href="/admin/sign-out/">Sign out</a> | ||||||
|       {{- end}} |       {{- end}} | ||||||
|     </nav> |     </nav> | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| package hub |  | ||||||
							
								
								
									
										19
									
								
								m/models.go
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								m/models.go
									
									
									
									
									
								
							| @@ -1,16 +1,17 @@ | |||||||
| // The package `m` contains models shared between the hub and peer programs. | // The package `m` contains models shared between the hub and peer programs. | ||||||
| package m | package m | ||||||
|  |  | ||||||
|  | type PeerInitArgs struct { | ||||||
|  | 	EncPubKey  []byte | ||||||
|  | 	PubSignKey []byte | ||||||
|  | } | ||||||
|  |  | ||||||
| type PeerConfig struct { | type PeerConfig struct { | ||||||
| 	PeerIP   byte | 	PeerIP   byte | ||||||
| 	HubAddress string |  | ||||||
| 	Network  []byte | 	Network  []byte | ||||||
| 	APIKey     string |  | ||||||
| 	PublicIP []byte | 	PublicIP []byte | ||||||
| 	Port     uint16 | 	Port     uint16 | ||||||
| 	Relay    bool | 	Relay    bool | ||||||
| 	PubKey     []byte |  | ||||||
| 	PrivKey    []byte |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type Peer struct { | type Peer struct { | ||||||
| @@ -21,17 +22,9 @@ type Peer struct { | |||||||
| 	Port       uint16 | 	Port       uint16 | ||||||
| 	Relay      bool | 	Relay      bool | ||||||
| 	PubKey     []byte | 	PubKey     []byte | ||||||
|  | 	PubSignKey []byte | ||||||
| } | } | ||||||
|  |  | ||||||
| type NetworkState struct { | type NetworkState struct { | ||||||
| 	HubAddress string |  | ||||||
|  |  | ||||||
| 	// The requester's data: |  | ||||||
| 	Network  []byte |  | ||||||
| 	PeerIP   byte |  | ||||||
| 	PublicIP []byte |  | ||||||
| 	Port     uint16 |  | ||||||
|  |  | ||||||
| 	// All peer data. |  | ||||||
| 	Peers [256]*Peer | 	Peers [256]*Peer | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,69 +3,69 @@ package node | |||||||
| import ( | import ( | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/netip" | 	"net/netip" | ||||||
|  | 	"runtime/debug" | ||||||
|  | 	"sort" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func addrDiscoveryServer() { | type pubAddrStore struct { | ||||||
| 	var ( | 	lastSeen map[netip.AddrPort]time.Time | ||||||
| 		buf1 = make([]byte, bufferSize) | 	addrList []netip.AddrPort | ||||||
| 		buf2 = make([]byte, bufferSize) |  | ||||||
| 	) |  | ||||||
|  |  | ||||||
| 	for { |  | ||||||
| 		pkt := <-discoveryPackets |  | ||||||
|  |  | ||||||
| 		p, ok := pkt.Payload.(addrDiscoveryPacket) |  | ||||||
| 		if !ok { |  | ||||||
| 			continue |  | ||||||
| } | } | ||||||
|  |  | ||||||
| 		route := routingTable[pkt.SrcIP].Load() | func newPubAddrStore() *pubAddrStore { | ||||||
| 		if route == nil || !route.RemoteAddr.IsValid() { | 	return &pubAddrStore{ | ||||||
| 			continue | 		lastSeen: map[netip.AddrPort]time.Time{}, | ||||||
| 		} | 		addrList: make([]netip.AddrPort, 0, 32), | ||||||
|  |  | ||||||
| 		_sendControlPacket(addrDiscoveryPacket{ |  | ||||||
| 			TraceID: p.TraceID, |  | ||||||
| 			ToAddr:  pkt.SrcAddr, |  | ||||||
| 		}, *route, buf1, buf2) |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func addrDiscoveryClient() { | func (store *pubAddrStore) Store(add netip.AddrPort) { | ||||||
| 	var ( | 	if localPub { | ||||||
| 		checkInterval = 8 * time.Second | 		log.Printf("OOPS: Local pub but storage attempt: %s", debug.Stack()) | ||||||
| 		timer         = time.NewTimer(4 * time.Second) | 		return | ||||||
|  |  | ||||||
| 		buf1 = make([]byte, bufferSize) |  | ||||||
| 		buf2 = make([]byte, bufferSize) |  | ||||||
|  |  | ||||||
| 		addrPacket addrDiscoveryPacket |  | ||||||
| 		lAddr      netip.AddrPort |  | ||||||
| 	) |  | ||||||
|  |  | ||||||
| 	for { |  | ||||||
| 		select { |  | ||||||
| 		case pkt := <-discoveryPackets: |  | ||||||
| 			p, ok := pkt.Payload.(addrDiscoveryPacket) |  | ||||||
| 			if !ok || p.TraceID != addrPacket.TraceID || !p.ToAddr.IsValid() || p.ToAddr == lAddr { |  | ||||||
| 				continue |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 			log.Printf("Discovered local address: %v", p.ToAddr) | 	if !add.IsValid() { | ||||||
| 			lAddr = p.ToAddr | 		return | ||||||
| 			localAddr.Store(&p.ToAddr) |  | ||||||
|  |  | ||||||
| 		case <-timer.C: |  | ||||||
| 			timer.Reset(checkInterval) |  | ||||||
|  |  | ||||||
| 			route := getRelayRoute() |  | ||||||
| 			if route == nil { |  | ||||||
| 				continue |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 			addrPacket.TraceID = newTraceID() | 	if _, exists := store.lastSeen[add]; !exists { | ||||||
| 			_sendControlPacket(addrPacket, *route, buf1, buf2) | 		store.addrList = append(store.addrList, add) | ||||||
|  | 	} | ||||||
|  | 	store.lastSeen[add] = time.Now() | ||||||
|  | 	store.sort() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (store *pubAddrStore) Get() (addrs [8]netip.AddrPort) { | ||||||
|  | 	if localPub { | ||||||
|  | 		addrs[0] = localAddr | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	copy(addrs[:], store.addrList) | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (store *pubAddrStore) Clean() { | ||||||
|  | 	if localPub { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for ip, lastSeen := range store.lastSeen { | ||||||
|  | 		if time.Since(lastSeen) > timeoutInterval { | ||||||
|  | 			delete(store.lastSeen, ip) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | 	store.addrList = store.addrList[:0] | ||||||
|  | 	for ip := range store.lastSeen { | ||||||
|  | 		store.addrList = append(store.addrList, ip) | ||||||
|  | 	} | ||||||
|  | 	store.sort() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (store *pubAddrStore) sort() { | ||||||
|  | 	sort.Slice(store.addrList, func(i, j int) bool { | ||||||
|  | 		return store.lastSeen[store.addrList[j]].Before(store.lastSeen[store.addrList[i]]) | ||||||
|  | 	}) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										29
									
								
								node/addrdiscovery_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								node/addrdiscovery_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | package node | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/netip" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestPubAddrStore(t *testing.T) { | ||||||
|  | 	s := newPubAddrStore() | ||||||
|  |  | ||||||
|  | 	l := []netip.AddrPort{ | ||||||
|  | 		netip.AddrPortFrom(netip.AddrFrom4([4]byte{0, 1, 2, 3}), 20), | ||||||
|  | 		netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 1, 2, 3}), 21), | ||||||
|  | 		netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 1, 2, 3}), 22), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for i := range l { | ||||||
|  | 		s.Store(l[i]) | ||||||
|  | 		time.Sleep(time.Millisecond) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.Clean() | ||||||
|  |  | ||||||
|  | 	l2 := s.Get() | ||||||
|  | 	if l2[0] != l[2] || l2[1] != l[1] || l2[2] != l[0] { | ||||||
|  | 		t.Fatal(l, l2) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -6,7 +6,6 @@ import ( | |||||||
| 	"crypto/rand" | 	"crypto/rand" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // TODO: Use [32]byte for simplicity everywhere. |  | ||||||
| type dataCipher struct { | type dataCipher struct { | ||||||
| 	key  [32]byte | 	key  [32]byte | ||||||
| 	aead cipher.AEAD | 	aead cipher.AEAD | ||||||
| @@ -20,7 +19,6 @@ func newDataCipher() *dataCipher { | |||||||
| 	return newDataCipherFromKey(key) | 	return newDataCipherFromKey(key) | ||||||
| } | } | ||||||
|  |  | ||||||
| // key must be 32 bytes. |  | ||||||
| func newDataCipherFromKey(key [32]byte) *dataCipher { | func newDataCipherFromKey(key [32]byte) *dataCipher { | ||||||
| 	block, err := aes.NewCipher(key[:]) | 	block, err := aes.NewCipher(key[:]) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								node/cipher-discovery.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								node/cipher-discovery.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | package node | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | func signData(privKey *[64]byte, h header, data, out []byte) []byte { | ||||||
|  | 	out = out[:headerSize] | ||||||
|  | 	h.Marshal(out) | ||||||
|  | 	return sign.Sign(out, data, privKey) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func openData(pubKey *[32]byte, signed, out []byte) (data []byte, ok bool) { | ||||||
|  | 	return sign.Open(out[:0], signed[headerSize:], pubKey) | ||||||
|  | } | ||||||
|  | */ | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| go build |  | ||||||
| sudo setcap cap_net_admin+iep ./client |  | ||||||
| ./client 144.76.78.93 |  | ||||||
| @@ -1,15 +0,0 @@ | |||||||
| 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(os.Args[1]) |  | ||||||
| 	n.RunClient() |  | ||||||
| } |  | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| #!/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" |  | ||||||
| @@ -1,8 +0,0 @@ | |||||||
| package main |  | ||||||
|  |  | ||||||
| import "vppn/node" |  | ||||||
|  |  | ||||||
| func main() { |  | ||||||
| 	n := node.NewTmpNodeServer() |  | ||||||
| 	n.RunServer() |  | ||||||
| } |  | ||||||
							
								
								
									
										11
									
								
								node/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								node/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | package node | ||||||
|  |  | ||||||
|  | import "vppn/m" | ||||||
|  |  | ||||||
|  | type localConfig struct { | ||||||
|  | 	m.PeerConfig | ||||||
|  | 	PubKey      []byte | ||||||
|  | 	PrivKey     []byte | ||||||
|  | 	PubSignKey  []byte | ||||||
|  | 	PrivSignKey []byte | ||||||
|  | } | ||||||
| @@ -5,7 +5,6 @@ import ( | |||||||
| 	"log" | 	"log" | ||||||
| 	"net" | 	"net" | ||||||
| 	"net/netip" | 	"net/netip" | ||||||
| 	"runtime/debug" |  | ||||||
| 	"sync" | 	"sync" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -21,10 +20,12 @@ func newConnWriter(conn *net.UDPConn) *connWriter { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (w *connWriter) WriteTo(packet []byte, addr netip.AddrPort) { | func (w *connWriter) WriteTo(packet []byte, addr netip.AddrPort) { | ||||||
|  | 	// Even though a conn is safe for concurrent use, it turns out that a mutex | ||||||
|  | 	// in Go is more fair when there's contention. Without this lock, control | ||||||
|  | 	// packets may fail to be sent in a timely manner causing timeouts. | ||||||
| 	w.lock.Lock() | 	w.lock.Lock() | ||||||
| 	if _, err := w.conn.WriteToUDPAddrPort(packet, addr); err != nil { | 	if _, err := w.conn.WriteToUDPAddrPort(packet, addr); err != nil { | ||||||
| 		debug.PrintStack() | 		log.Printf("Failed to write to UDP port: %v", err) | ||||||
| 		log.Fatalf("Failed to write to UDP port: %v", err) |  | ||||||
| 	} | 	} | ||||||
| 	w.lock.Unlock() | 	w.lock.Unlock() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -56,7 +56,7 @@ func storeJson(x any, outPath string) error { | |||||||
| 	return os.Rename(tmpPath, outPath) | 	return os.Rename(tmpPath, outPath) | ||||||
| } | } | ||||||
|  |  | ||||||
| func storePeerConfig(netName string, pc m.PeerConfig) error { | func storePeerConfig(netName string, pc localConfig) error { | ||||||
| 	return storeJson(pc, peerConfigPath(netName)) | 	return storeJson(pc, peerConfigPath(netName)) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -73,7 +73,7 @@ func loadJson(dataPath string, ptr any) error { | |||||||
| 	return json.Unmarshal(data, ptr) | 	return json.Unmarshal(data, ptr) | ||||||
| } | } | ||||||
|  |  | ||||||
| func loadPeerConfig(netName string) (pc m.PeerConfig, err error) { | func loadPeerConfig(netName string) (pc localConfig, err error) { | ||||||
| 	return pc, loadJson(peerConfigPath(netName), &pc) | 	return pc, loadJson(peerConfigPath(netName), &pc) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package node | package node | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"net/netip" |  | ||||||
| 	"sync/atomic" | 	"sync/atomic" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -12,13 +11,6 @@ func getRelayRoute() *peerRoute { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func getLocalAddr() netip.AddrPort { |  | ||||||
| 	if a := localAddr.Load(); a != nil { |  | ||||||
| 		return *a |  | ||||||
| 	} |  | ||||||
| 	return netip.AddrPort{} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func _sendControlPacket(pkt interface{ Marshal([]byte) []byte }, route peerRoute, buf1, buf2 []byte) { | func _sendControlPacket(pkt interface{ Marshal([]byte) []byte }, route peerRoute, buf1, buf2 []byte) { | ||||||
| 	buf := pkt.Marshal(buf2) | 	buf := pkt.Marshal(buf2) | ||||||
| 	h := header{ | 	h := header{ | ||||||
|   | |||||||
| @@ -1,10 +1,11 @@ | |||||||
| package node | package node | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"net" | ||||||
| 	"net/netip" | 	"net/netip" | ||||||
|  | 	"net/url" | ||||||
| 	"sync/atomic" | 	"sync/atomic" | ||||||
| 	"time" | 	"time" | ||||||
| 	"vppn/m" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @@ -13,6 +14,12 @@ const ( | |||||||
| 	if_queue_len          = 2048 | 	if_queue_len          = 2048 | ||||||
| 	controlCipherOverhead = 16 | 	controlCipherOverhead = 16 | ||||||
| 	dataCipherOverhead    = 16 | 	dataCipherOverhead    = 16 | ||||||
|  | 	signOverhead          = 64 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	multicastIP   = netip.AddrFrom4([4]byte{224, 0, 0, 157}) | ||||||
|  | 	multicastAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(multicastIP, 4560)) | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type peerRoute struct { | type peerRoute struct { | ||||||
| @@ -20,17 +27,23 @@ type peerRoute struct { | |||||||
| 	Up            bool // True if data can be sent on the route. | 	Up            bool // True if data can be sent on the route. | ||||||
| 	Relay         bool // True if the peer is a relay. | 	Relay         bool // True if the peer is a relay. | ||||||
| 	Direct        bool // True if this is a direct connection. | 	Direct        bool // True if this is a direct connection. | ||||||
|  | 	PubSignKey    []byte | ||||||
| 	ControlCipher *controlCipher | 	ControlCipher *controlCipher | ||||||
| 	DataCipher    *dataCipher | 	DataCipher    *dataCipher | ||||||
| 	RemoteAddr    netip.AddrPort // Remote address if directly connected. | 	RemoteAddr    netip.AddrPort // Remote address if directly connected. | ||||||
| } | } | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
|  | 	hubURL *url.URL | ||||||
|  | 	apiKey string | ||||||
|  |  | ||||||
| 	// Configuration for this peer. | 	// Configuration for this peer. | ||||||
| 	netName     string | 	netName     string | ||||||
| 	localIP     byte | 	localIP     byte | ||||||
| 	localPub    bool | 	localPub    bool | ||||||
| 	privateKey []byte | 	localAddr   netip.AddrPort | ||||||
|  | 	privKey     []byte | ||||||
|  | 	privSignKey []byte | ||||||
|  |  | ||||||
| 	// Shared interface for writing. | 	// Shared interface for writing. | ||||||
| 	_iface *ifWriter | 	_iface *ifWriter | ||||||
| @@ -54,21 +67,8 @@ var ( | |||||||
| 		return | 		return | ||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
| 	// Channels for incoming control packets. | 	// Messages for the supervisor. | ||||||
| 	controlPackets [256]chan controlPacket = func() (out [256]chan controlPacket) { | 	messages = make(chan any, 1024) | ||||||
| 		for i := range out { |  | ||||||
| 			out[i] = make(chan controlPacket, 256) |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	// Channels for incoming peer updates from the hub. |  | ||||||
| 	peerUpdates [256]chan *m.Peer = func() (out [256]chan *m.Peer) { |  | ||||||
| 		for i := range out { |  | ||||||
| 			out[i] = make(chan *m.Peer) |  | ||||||
| 		} |  | ||||||
| 		return |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	// Global routing table. | 	// Global routing table. | ||||||
| 	routingTable [256]*atomic.Pointer[peerRoute] = func() (out [256]*atomic.Pointer[peerRoute]) { | 	routingTable [256]*atomic.Pointer[peerRoute] = func() (out [256]*atomic.Pointer[peerRoute]) { | ||||||
| @@ -80,7 +80,7 @@ var ( | |||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
| 	// Managed by the relayManager. | 	// Managed by the relayManager. | ||||||
| 	discoveryPackets chan controlPacket | 	relayIP = &atomic.Pointer[byte]{} | ||||||
| 	localAddr        *atomic.Pointer[netip.AddrPort] // May be nil. |  | ||||||
| 	relayIP          *atomic.Pointer[byte]           // May be nil. | 	publicAddrs = newPubAddrStore() | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -13,23 +13,25 @@ const ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| type header struct { | type header struct { | ||||||
|  | 	Version  byte | ||||||
| 	StreamID byte | 	StreamID byte | ||||||
| 	Counter  uint64 // Init with time.Now().Unix << 30 to ensure monotonic. |  | ||||||
| 	SourceIP byte | 	SourceIP byte | ||||||
| 	DestIP   byte | 	DestIP   byte | ||||||
|  | 	Counter  uint64 // Init with time.Now().Unix << 30 to ensure monotonic. | ||||||
| } | } | ||||||
|  |  | ||||||
| func (h *header) Parse(b []byte) { | func (h *header) Parse(b []byte) { | ||||||
| 	h.StreamID = b[0] | 	h.Version = b[0] | ||||||
| 	h.Counter = *(*uint64)(unsafe.Pointer(&b[1])) | 	h.StreamID = b[1] | ||||||
| 	h.SourceIP = b[9] | 	h.SourceIP = b[2] | ||||||
| 	h.DestIP = b[10] | 	h.DestIP = b[3] | ||||||
|  | 	h.Counter = *(*uint64)(unsafe.Pointer(&b[4])) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (h *header) Marshal(buf []byte) { | func (h *header) Marshal(buf []byte) { | ||||||
| 	buf[0] = h.StreamID | 	buf[0] = h.Version | ||||||
| 	*(*uint64)(unsafe.Pointer(&buf[1])) = h.Counter | 	buf[1] = h.StreamID | ||||||
| 	buf[9] = h.SourceIP | 	buf[2] = h.SourceIP | ||||||
| 	buf[10] = h.DestIP | 	buf[3] = h.DestIP | ||||||
| 	buf[11] = 0 | 	*(*uint64)(unsafe.Pointer(&buf[4])) = h.Counter | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ import ( | |||||||
| 	"io" | 	"io" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" |  | ||||||
| 	"time" | 	"time" | ||||||
| 	"vppn/m" | 	"vppn/m" | ||||||
| ) | ) | ||||||
| @@ -16,21 +15,18 @@ type hubPoller struct { | |||||||
| 	versions [256]int64 | 	versions [256]int64 | ||||||
| } | } | ||||||
|  |  | ||||||
| func newHubPoller(conf m.PeerConfig) *hubPoller { | func newHubPoller() *hubPoller { | ||||||
| 	u, err := url.Parse(conf.HubAddress) | 	u := *hubURL | ||||||
| 	if err != nil { |  | ||||||
| 		log.Fatalf("Failed to parse hub address %s: %v", conf.HubAddress, err) |  | ||||||
| 	} |  | ||||||
| 	u.Path = "/peer/fetch-state/" | 	u.Path = "/peer/fetch-state/" | ||||||
|  |  | ||||||
| 	client := &http.Client{Timeout: 8 * time.Second} | 	client := &http.Client{Timeout: 8 * time.Second} | ||||||
|  |  | ||||||
| 	req := &http.Request{ | 	req := &http.Request{ | ||||||
| 		Method: http.MethodGet, | 		Method: http.MethodGet, | ||||||
| 		URL:    u, | 		URL:    &u, | ||||||
| 		Header: http.Header{}, | 		Header: http.Header{}, | ||||||
| 	} | 	} | ||||||
| 	req.SetBasicAuth("", conf.APIKey) | 	req.SetBasicAuth("", apiKey) | ||||||
|  |  | ||||||
| 	return &hubPoller{ | 	return &hubPoller{ | ||||||
| 		client: client, | 		client: client, | ||||||
| @@ -71,7 +67,7 @@ func (hp *hubPoller) pollHub() { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := json.Unmarshal(body, &state); err != nil { | 	if err := json.Unmarshal(body, &state); err != nil { | ||||||
| 		log.Printf("Failed to unmarshal response from hub: %v", err) | 		log.Printf("Failed to unmarshal response from hub: %v\n%s", err, body) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -85,10 +81,12 @@ func (hp *hubPoller) pollHub() { | |||||||
| func (hp *hubPoller) applyNetworkState(state m.NetworkState) { | func (hp *hubPoller) applyNetworkState(state m.NetworkState) { | ||||||
| 	for i, peer := range state.Peers { | 	for i, peer := range state.Peers { | ||||||
| 		if i != int(localIP) { | 		if i != int(localIP) { | ||||||
| 			if peer != nil && peer.Version != hp.versions[i] { | 			if peer == nil || peer.Version != hp.versions[i] { | ||||||
| 				peerUpdates[i] <- state.Peers[i] | 				messages <- peerUpdateMsg{PeerIP: byte(i), Peer: state.Peers[i]} | ||||||
|  | 				if peer != nil { | ||||||
| 					hp.versions[i] = peer.Version | 					hp.versions[i] = peer.Version | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										97
									
								
								node/localdiscovery.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								node/localdiscovery.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | package node | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"net" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"golang.org/x/crypto/nacl/sign" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func localDiscovery() { | ||||||
|  | 	conn, err := net.ListenMulticastUDP("udp", nil, multicastAddr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("Failed to bind to multicast address: %v", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go sendLocalDiscovery(conn) | ||||||
|  | 	go recvLocalDiscovery(conn) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func sendLocalDiscovery(conn *net.UDPConn) { | ||||||
|  | 	var ( | ||||||
|  | 		buf1 = make([]byte, bufferSize) | ||||||
|  | 		buf2 = make([]byte, bufferSize) | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	for range time.Tick(16 * time.Second) { | ||||||
|  | 		signed := buildLocalDiscoveryPacket(buf1, buf2) | ||||||
|  | 		if _, err := conn.WriteToUDP(signed, multicastAddr); err != nil { | ||||||
|  | 			log.Printf("Failed to write multicast UDP packet: %v", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func recvLocalDiscovery(conn *net.UDPConn) { | ||||||
|  | 	var ( | ||||||
|  | 		raw = make([]byte, bufferSize) | ||||||
|  | 		buf = make([]byte, bufferSize) | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		n, remoteAddr, err := conn.ReadFromUDPAddrPort(raw[:bufferSize]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalf("Failed to read from UDP port (multicast): %v", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		raw = raw[:n] | ||||||
|  | 		h, ok := openLocalDiscoveryPacket(raw, buf) | ||||||
|  | 		if !ok { | ||||||
|  | 			log.Printf("Failed to open discovery packet?") | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		msg := controlMsg[localDiscoveryPacket]{ | ||||||
|  | 			SrcIP:   h.SourceIP, | ||||||
|  | 			SrcAddr: remoteAddr, | ||||||
|  | 			Packet:  localDiscoveryPacket{}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case messages <- msg: | ||||||
|  | 		default: | ||||||
|  | 			log.Printf("Dropping local discovery message.") | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func buildLocalDiscoveryPacket(buf1, buf2 []byte) []byte { | ||||||
|  | 	h := header{ | ||||||
|  | 		StreamID: controlStreamID, | ||||||
|  | 		Counter:  0, | ||||||
|  | 		SourceIP: localIP, | ||||||
|  | 		DestIP:   255, | ||||||
|  | 	} | ||||||
|  | 	out := buf1[:headerSize] | ||||||
|  | 	h.Marshal(out) | ||||||
|  | 	return sign.Sign(buf2[:0], out, (*[64]byte)(privSignKey)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func openLocalDiscoveryPacket(raw, buf []byte) (h header, ok bool) { | ||||||
|  | 	if len(raw) != headerSize+signOverhead { | ||||||
|  | 		ok = false | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	h.Parse(raw[signOverhead:]) | ||||||
|  | 	route := routingTable[h.SourceIP].Load() | ||||||
|  | 	if route == nil || route.PubSignKey == nil { | ||||||
|  | 		log.Printf("Missing signing key: %d", h.SourceIP) | ||||||
|  | 		ok = false | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, ok = sign.Open(buf[:0], raw, (*[32]byte)(route.PubSignKey)) | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								node/localdiscovery_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								node/localdiscovery_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | package node | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"golang.org/x/crypto/nacl/sign" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestLocalDiscoveryPacketSigning(t *testing.T) { | ||||||
|  | 	localIP = 32 | ||||||
|  |  | ||||||
|  | 	var ( | ||||||
|  | 		buf1                      = make([]byte, bufferSize) | ||||||
|  | 		buf2                      = make([]byte, bufferSize) | ||||||
|  | 		pubSignKey, privSigKey, _ = sign.GenerateKey(rand.Reader) | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	privSignKey = privSigKey[:] | ||||||
|  | 	route := routingTable[localIP].Load() | ||||||
|  | 	route.IP = byte(localIP) | ||||||
|  | 	route.PubSignKey = pubSignKey[0:32] | ||||||
|  | 	routingTable[localIP].Store(route) | ||||||
|  |  | ||||||
|  | 	out := buildLocalDiscoveryPacket(buf1, buf2) | ||||||
|  |  | ||||||
|  | 	h, ok := openLocalDiscoveryPacket(bytes.Clone(out), buf1) | ||||||
|  | 	if !ok { | ||||||
|  | 		t.Fatal(h, ok) | ||||||
|  | 	} | ||||||
|  | 	if h.StreamID != controlStreamID || h.SourceIP != localIP || h.DestIP != 255 { | ||||||
|  | 		t.Fatal(h) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										182
									
								
								node/main.go
									
									
									
									
									
								
							
							
						
						
									
										182
									
								
								node/main.go
									
									
									
									
									
								
							| @@ -1,6 +1,8 @@ | |||||||
| package node | package node | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"crypto/rand" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| @@ -9,10 +11,14 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/netip" | 	"net/netip" | ||||||
|  | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
| 	"runtime/debug" | 	"runtime/debug" | ||||||
| 	"sync/atomic" | 	"time" | ||||||
| 	"vppn/m" | 	"vppn/m" | ||||||
|  |  | ||||||
|  | 	"golang.org/x/crypto/nacl/box" | ||||||
|  | 	"golang.org/x/crypto/nacl/sign" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func panicHandler() { | func panicHandler() { | ||||||
| @@ -24,39 +30,61 @@ func panicHandler() { | |||||||
| func Main() { | func Main() { | ||||||
| 	defer panicHandler() | 	defer panicHandler() | ||||||
|  |  | ||||||
| 	var ( | 	var hubAddress string | ||||||
| 		initURL  string |  | ||||||
| 		listenIP string |  | ||||||
| 		port     int |  | ||||||
| 	) |  | ||||||
|  |  | ||||||
| 	flag.StringVar(&netName, "name", "", "[REQUIRED] The network name.") | 	flag.StringVar(&netName, "name", "", "[REQUIRED] The network name.") | ||||||
| 	flag.StringVar(&initURL, "init-url", "", "Initializes peer from the hub URL.") | 	flag.StringVar(&hubAddress, "hub-address", "", "[REQUIRED] The hub address.") | ||||||
| 	flag.StringVar(&listenIP, "listen-ip", "", "IP address to listen on.") | 	flag.StringVar(&apiKey, "api-key", "", "[REQUIRED] The node's API key.") | ||||||
| 	flag.IntVar(&port, "port", 0, "Port to listen on.") |  | ||||||
| 	flag.Parse() | 	flag.Parse() | ||||||
|  |  | ||||||
| 	if netName == "" { | 	if netName == "" || hubAddress == "" || apiKey == "" { | ||||||
| 		flag.Usage() | 		flag.Usage() | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if initURL != "" { | 	var err error | ||||||
| 		mainInit(initURL) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	main(listenIP, uint16(port)) | 	hubURL, err = url.Parse(hubAddress) | ||||||
| } |  | ||||||
|  |  | ||||||
| func mainInit(initURL string) { |  | ||||||
| 	if _, err := loadPeerConfig(netName); err == nil { |  | ||||||
| 		log.Fatalf("Network is already initialized.") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	resp, err := http.Get(initURL) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Fatalf("Failed to fetch data from hub: %v", err) | 		log.Fatalf("Failed to parse hub address: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	main() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func initPeerWithHub() { | ||||||
|  | 	encPubKey, encPrivKey, err := box.GenerateKey(rand.Reader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Failed to generate encryption keys: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Failed to generate signing keys: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	initURL := *hubURL | ||||||
|  | 	initURL.Path = "/peer/init/" | ||||||
|  |  | ||||||
|  | 	args := m.PeerInitArgs{ | ||||||
|  | 		EncPubKey:  encPubKey[:], | ||||||
|  | 		PubSignKey: signPubKey[:], | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	buf := &bytes.Buffer{} | ||||||
|  | 	if err := json.NewEncoder(buf).Encode(args); err != nil { | ||||||
|  | 		log.Fatalf("Failed to encode init args: %v", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	req, err := http.NewRequest(http.MethodPost, initURL.String(), buf) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Failed to construct request: %v", err) | ||||||
|  | 	} | ||||||
|  | 	req.SetBasicAuth("", apiKey) | ||||||
|  |  | ||||||
|  | 	resp, err := http.DefaultClient.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Fatalf("Failed to init with hub: %v", err) | ||||||
| 	} | 	} | ||||||
| 	defer resp.Body.Close() | 	defer resp.Body.Close() | ||||||
|  |  | ||||||
| @@ -65,11 +93,16 @@ func mainInit(initURL string) { | |||||||
| 		log.Fatalf("Failed to read response body: %v", err) | 		log.Fatalf("Failed to read response body: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	peerConfig := m.PeerConfig{} | 	peerConfig := localConfig{} | ||||||
| 	if err := json.Unmarshal(data, &peerConfig); err != nil { | 	if err := json.Unmarshal(data, &peerConfig.PeerConfig); err != nil { | ||||||
| 		log.Fatalf("Failed to parse configuration: %v", err) | 		log.Fatalf("Failed to parse configuration: %v\n%s", err, data) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	peerConfig.PubKey = encPubKey[:] | ||||||
|  | 	peerConfig.PrivKey = encPrivKey[:] | ||||||
|  | 	peerConfig.PubSignKey = signPubKey[:] | ||||||
|  | 	peerConfig.PrivSignKey = signPrivKey[:] | ||||||
|  |  | ||||||
| 	if err := storePeerConfig(netName, peerConfig); err != nil { | 	if err := storePeerConfig(netName, peerConfig); err != nil { | ||||||
| 		log.Fatalf("Failed to store configuration: %v", err) | 		log.Fatalf("Failed to store configuration: %v", err) | ||||||
| 	} | 	} | ||||||
| @@ -79,20 +112,25 @@ func mainInit(initURL string) { | |||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
| func main(listenIP string, port uint16) { | func main() { | ||||||
| 	config, err := loadPeerConfig(netName) | 	config, err := loadPeerConfig(netName) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("Failed to load configuration: %v", err) | ||||||
|  | 		log.Printf("Initializing...") | ||||||
|  | 		initPeerWithHub() | ||||||
|  |  | ||||||
|  | 		config, err = loadPeerConfig(netName) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			log.Fatalf("Failed to load configuration: %v", err) | 			log.Fatalf("Failed to load configuration: %v", err) | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 	port = determinePort(config.Port, port) |  | ||||||
|  |  | ||||||
| 	iface, err := openInterface(config.Network, config.PeerIP, netName) | 	iface, err := openInterface(config.Network, config.PeerIP, netName) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Fatalf("Failed to open interface: %v", err) | 		log.Fatalf("Failed to open interface: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", listenIP, port)) | 	myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", config.Port)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Fatalf("Failed to resolve UDP address: %v", err) | 		log.Fatalf("Failed to resolve UDP address: %v", err) | ||||||
| 	} | 	} | ||||||
| @@ -102,54 +140,45 @@ func main(listenIP string, port uint16) { | |||||||
| 		log.Fatalf("Failed to open UDP port: %v", err) | 		log.Fatalf("Failed to open UDP port: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	conn.SetReadBuffer(1024 * 1024 * 8) | ||||||
|  | 	conn.SetWriteBuffer(1024 * 1024 * 8) | ||||||
|  |  | ||||||
| 	// Intialize globals. | 	// Intialize globals. | ||||||
| 	_iface = newIFWriter(iface) | 	_iface = newIFWriter(iface) | ||||||
| 	_conn = newConnWriter(conn) | 	_conn = newConnWriter(conn) | ||||||
|  |  | ||||||
| 	localIP = config.PeerIP | 	localIP = config.PeerIP | ||||||
| 	discoveryPackets = make(chan controlPacket, 256) |  | ||||||
| 	localAddr = &atomic.Pointer[netip.AddrPort]{} |  | ||||||
| 	relayIP = &atomic.Pointer[byte]{} |  | ||||||
|  |  | ||||||
| 	ip, ok := netip.AddrFromSlice(config.PublicIP) | 	ip, ok := netip.AddrFromSlice(config.PublicIP) | ||||||
| 	if ok { | 	if ok { | ||||||
| 		localPub = true | 		localPub = true | ||||||
| 		addr := netip.AddrPortFrom(ip, config.Port) | 		localAddr = netip.AddrPortFrom(ip, config.Port) | ||||||
| 		localAddr.Store(&addr) |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	privateKey = config.PrivKey | 	privKey = config.PrivKey | ||||||
|  | 	privSignKey = config.PrivSignKey | ||||||
|  |  | ||||||
| 	// Start supervisors. | 	if !localPub { | ||||||
| 	for i := range 256 { |  | ||||||
| 		go newPeerSupervisor(i).Run() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if localPub { |  | ||||||
| 		go addrDiscoveryServer() |  | ||||||
| 	} else { |  | ||||||
| 		go addrDiscoveryClient() |  | ||||||
| 		go relayManager() | 		go relayManager() | ||||||
|  | 		go localDiscovery() | ||||||
| 	} | 	} | ||||||
| 	go newHubPoller(config).Run() |  | ||||||
|  | 	go func() { | ||||||
|  | 		for range time.Tick(pingInterval) { | ||||||
|  | 			messages <- pingTimerMsg{} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	go startPeerSuper() | ||||||
|  |  | ||||||
|  | 	go newHubPoller().Run() | ||||||
| 	go readFromConn(conn) | 	go readFromConn(conn) | ||||||
|  |  | ||||||
| 	readFromIFace(iface) | 	readFromIFace(iface) | ||||||
| } | } | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
| func determinePort(confPort, portFromCommandLine uint16) uint16 { |  | ||||||
| 	if portFromCommandLine != 0 { |  | ||||||
| 		return portFromCommandLine |  | ||||||
| 	} |  | ||||||
| 	if confPort != 0 { |  | ||||||
| 		return confPort |  | ||||||
| 	} |  | ||||||
| 	return 456 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| func readFromConn(conn *net.UDPConn) { | func readFromConn(conn *net.UDPConn) { | ||||||
|  |  | ||||||
| 	defer panicHandler() | 	defer panicHandler() | ||||||
| @@ -200,59 +229,44 @@ func handleControlPacket(addr netip.AddrPort, h header, data, decBuf []byte) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if h.DestIP != localIP { | 	if h.DestIP != localIP { | ||||||
| 		log.Printf("Incorrect destination IP on control packet: %d != %d", h.DestIP, localIP) | 		log.Printf("Incorrect destination IP on control packet: %#v", h) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	out, ok := route.ControlCipher.Decrypt(data, decBuf) | 	out, ok := route.ControlCipher.Decrypt(data, decBuf) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		//log.Printf("Failed to decrypt control packet.") | 		log.Printf("Failed to decrypt control packet.") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(out) == 0 { | 	if len(out) == 0 { | ||||||
| 		//log.Printf("Empty control packet from: %d", h.SourceIP) | 		log.Printf("Empty control packet from: %d", h.SourceIP) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if dupChecks[h.SourceIP].IsDup(h.Counter) { | 	if dupChecks[h.SourceIP].IsDup(h.Counter) { | ||||||
| 		//log.Printf("[%03d] Duplicate control packet: %d", h.SourceIP, h.Counter) | 		log.Printf("[%03d] Duplicate control packet: %d", h.SourceIP, h.Counter) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	pkt := controlPacket{ | 	msg, err := parseControlMsg(h.SourceIP, addr, out) | ||||||
| 		SrcIP:   h.SourceIP, | 	if err != nil { | ||||||
| 		SrcAddr: addr, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if err := pkt.ParsePayload(out); err != nil { |  | ||||||
| 		log.Printf("Failed to parse control packet: %v", err) | 		log.Printf("Failed to parse control packet: %v", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	switch pkt.Payload.(type) { |  | ||||||
|  |  | ||||||
| 	case addrDiscoveryPacket: |  | ||||||
| 	select { | 	select { | ||||||
| 		case discoveryPackets <- pkt: | 	case messages <- msg: | ||||||
| 		default: |  | ||||||
| 			log.Printf("Dropping discovery packet.") |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 	default: |  | ||||||
| 		select { |  | ||||||
| 		case controlPackets[h.SourceIP] <- pkt: |  | ||||||
| 	default: | 	default: | ||||||
| 		log.Printf("Dropping control packet.") | 		log.Printf("Dropping control packet.") | ||||||
| 	} | 	} | ||||||
| 	} |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func handleDataPacket(h header, data []byte, decBuf []byte) { | func handleDataPacket(h header, data []byte, decBuf []byte) { | ||||||
| 	route := routingTable[h.SourceIP].Load() | 	route := routingTable[h.SourceIP].Load() | ||||||
| 	if !route.Up { | 	if !route.Up { | ||||||
| 		//log.Printf("Not connected (recv).") | 		log.Printf("Not connected (recv).") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -263,7 +277,7 @@ func handleDataPacket(h header, data []byte, decBuf []byte) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if dupChecks[h.SourceIP].IsDup(h.Counter) { | 	if dupChecks[h.SourceIP].IsDup(h.Counter) { | ||||||
| 		//log.Printf("[%03d] Duplicate data packet: %d", h.SourceIP, h.Counter) | 		log.Printf("[%03d] Duplicate data packet: %d", h.SourceIP, h.Counter) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -274,7 +288,7 @@ func handleDataPacket(h header, data []byte, decBuf []byte) { | |||||||
|  |  | ||||||
| 	destRoute := routingTable[h.DestIP].Load() | 	destRoute := routingTable[h.DestIP].Load() | ||||||
| 	if !destRoute.Up { | 	if !destRoute.Up { | ||||||
| 		log.Printf("Not connected (relay): %v", destRoute) | 		log.Printf("Not connected (relay): %d", destRoute.IP) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										59
									
								
								node/messages.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								node/messages.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | package node | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"net/netip" | ||||||
|  | 	"vppn/m" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type controlMsg[T any] struct { | ||||||
|  | 	SrcIP   byte | ||||||
|  | 	SrcAddr netip.AddrPort | ||||||
|  | 	Packet  T | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func parseControlMsg(srcIP byte, srcAddr netip.AddrPort, buf []byte) (any, error) { | ||||||
|  | 	switch buf[0] { | ||||||
|  |  | ||||||
|  | 	case packetTypeSyn: | ||||||
|  | 		packet, err := parseSynPacket(buf) | ||||||
|  | 		return controlMsg[synPacket]{ | ||||||
|  | 			SrcIP:   srcIP, | ||||||
|  | 			SrcAddr: srcAddr, | ||||||
|  | 			Packet:  packet, | ||||||
|  | 		}, err | ||||||
|  |  | ||||||
|  | 	case packetTypeAck: | ||||||
|  | 		packet, err := parseAckPacket(buf) | ||||||
|  | 		return controlMsg[ackPacket]{ | ||||||
|  | 			SrcIP:   srcIP, | ||||||
|  | 			SrcAddr: srcAddr, | ||||||
|  | 			Packet:  packet, | ||||||
|  | 		}, err | ||||||
|  |  | ||||||
|  | 	case packetTypeProbe: | ||||||
|  | 		packet, err := parseProbePacket(buf) | ||||||
|  | 		return controlMsg[probePacket]{ | ||||||
|  | 			SrcIP:   srcIP, | ||||||
|  | 			SrcAddr: srcAddr, | ||||||
|  | 			Packet:  packet, | ||||||
|  | 		}, err | ||||||
|  |  | ||||||
|  | 	default: | ||||||
|  | 		return nil, errUnknownPacketType | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type peerUpdateMsg struct { | ||||||
|  | 	PeerIP byte | ||||||
|  | 	Peer   *m.Peer | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type pingTimerMsg struct{} | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
| @@ -63,12 +63,20 @@ func (w *binWriter) Int64(x int64) *binWriter { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (w *binWriter) AddrPort(addrPort netip.AddrPort) *binWriter { | func (w *binWriter) AddrPort(addrPort netip.AddrPort) *binWriter { | ||||||
|  | 	w.Bool(addrPort.IsValid()) | ||||||
| 	addr := addrPort.Addr().As16() | 	addr := addrPort.Addr().As16() | ||||||
| 	copy(w.b[w.i:w.i+16], addr[:]) | 	copy(w.b[w.i:w.i+16], addr[:]) | ||||||
| 	w.i += 16 | 	w.i += 16 | ||||||
| 	return w.Uint16(addrPort.Port()) | 	return w.Uint16(addrPort.Port()) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (w *binWriter) AddrPortArray(l [8]netip.AddrPort) *binWriter { | ||||||
|  | 	for _, addrPort := range l { | ||||||
|  | 		w.AddrPort(addrPort) | ||||||
|  | 	} | ||||||
|  | 	return w | ||||||
|  | } | ||||||
|  |  | ||||||
| func (w *binWriter) Build() []byte { | func (w *binWriter) Build() []byte { | ||||||
| 	return w.b[:w.i] | 	return w.b[:w.i] | ||||||
| } | } | ||||||
| @@ -146,15 +154,34 @@ func (r *binReader) Int64(x *int64) *binReader { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (r *binReader) AddrPort(x *netip.AddrPort) *binReader { | func (r *binReader) AddrPort(x *netip.AddrPort) *binReader { | ||||||
| 	if !r.hasBytes(18) { | 	if !r.hasBytes(19) { | ||||||
| 		return r | 		return r | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	var ( | ||||||
|  | 		valid bool | ||||||
|  | 		port  uint16 | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	r.Bool(&valid) | ||||||
| 	addr := netip.AddrFrom16(([16]byte)(r.b[r.i : r.i+16])).Unmap() | 	addr := netip.AddrFrom16(([16]byte)(r.b[r.i : r.i+16])).Unmap() | ||||||
| 	r.i += 16 | 	r.i += 16 | ||||||
|  |  | ||||||
| 	var port uint16 |  | ||||||
| 	r.Uint16(&port) | 	r.Uint16(&port) | ||||||
|  |  | ||||||
|  | 	if valid { | ||||||
| 		*x = netip.AddrPortFrom(addr, port) | 		*x = netip.AddrPortFrom(addr, port) | ||||||
|  | 	} else { | ||||||
|  | 		*x = netip.AddrPort{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return r | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (r *binReader) AddrPortArray(x *[8]netip.AddrPort) *binReader { | ||||||
|  | 	for i := range x { | ||||||
|  | 		r.AddrPort(&x[i]) | ||||||
|  | 	} | ||||||
| 	return r | 	return r | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,15 +12,30 @@ func TestBinWriteRead(t *testing.T) { | |||||||
| 	type Item struct { | 	type Item struct { | ||||||
| 		Type     byte | 		Type     byte | ||||||
| 		TraceID  uint64 | 		TraceID  uint64 | ||||||
|  | 		Addrs    [8]netip.AddrPort | ||||||
| 		DestAddr netip.AddrPort | 		DestAddr netip.AddrPort | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	in := Item{1, 2, netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 22)} | 	in := Item{ | ||||||
|  | 		1, | ||||||
|  | 		2, | ||||||
|  | 		[8]netip.AddrPort{}, | ||||||
|  | 		netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 22), | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	in.Addrs[0] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{0, 1, 2, 3}), 20) | ||||||
|  | 	in.Addrs[2] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 3, 4, 5}), 22) | ||||||
|  | 	in.Addrs[3] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 3, 4, 3}), 23) | ||||||
|  | 	in.Addrs[4] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 3, 4, 4}), 24) | ||||||
|  | 	in.Addrs[5] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 3, 4, 5}), 25) | ||||||
|  | 	in.Addrs[6] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{2, 3, 4, 6}), 26) | ||||||
|  | 	in.Addrs[7] = netip.AddrPortFrom(netip.AddrFrom4([4]byte{7, 8, 9, 7}), 27) | ||||||
|  |  | ||||||
| 	buf = newBinWriter(buf). | 	buf = newBinWriter(buf). | ||||||
| 		Byte(in.Type). | 		Byte(in.Type). | ||||||
| 		Uint64(in.TraceID). | 		Uint64(in.TraceID). | ||||||
| 		AddrPort(in.DestAddr). | 		AddrPort(in.DestAddr). | ||||||
|  | 		AddrPortArray(in.Addrs). | ||||||
| 		Build() | 		Build() | ||||||
|  |  | ||||||
| 	out := Item{} | 	out := Item{} | ||||||
| @@ -29,6 +44,7 @@ func TestBinWriteRead(t *testing.T) { | |||||||
| 		Byte(&out.Type). | 		Byte(&out.Type). | ||||||
| 		Uint64(&out.TraceID). | 		Uint64(&out.TraceID). | ||||||
| 		AddrPort(&out.DestAddr). | 		AddrPort(&out.DestAddr). | ||||||
|  | 		AddrPortArray(&out.Addrs). | ||||||
| 		Error() | 		Error() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
|   | |||||||
| @@ -20,35 +20,11 @@ const ( | |||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
| type controlPacket struct { |  | ||||||
| 	SrcIP   byte |  | ||||||
| 	SrcAddr netip.AddrPort |  | ||||||
| 	Payload any |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p *controlPacket) ParsePayload(buf []byte) (err error) { |  | ||||||
| 	switch buf[0] { |  | ||||||
| 	case packetTypeSyn: |  | ||||||
| 		p.Payload, err = parseSynPacket(buf) |  | ||||||
| 	case packetTypeSynAck: |  | ||||||
| 		p.Payload, err = parseSynAckPacket(buf) |  | ||||||
| 	case packetTypeProbe: |  | ||||||
| 		p.Payload, err = parseProbePacket(buf) |  | ||||||
| 	case packetTypeAddrDiscovery: |  | ||||||
| 		p.Payload, err = parseAddrDiscoveryPacket(buf) |  | ||||||
| 	default: |  | ||||||
| 		return errUnknownPacketType |  | ||||||
| 	} |  | ||||||
| 	return err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| type synPacket struct { | type synPacket struct { | ||||||
| 	TraceID       uint64   // TraceID to match response w/ request. | 	TraceID       uint64   // TraceID to match response w/ request. | ||||||
| 	SharedKey     [32]byte // Our shared key. | 	SharedKey     [32]byte // Our shared key. | ||||||
| 	Direct        bool | 	Direct        bool | ||||||
| 	FromAddr  netip.AddrPort // The client's sending address. | 	PossibleAddrs [8]netip.AddrPort // Possible public addresses of the sender. | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p synPacket) Marshal(buf []byte) []byte { | func (p synPacket) Marshal(buf []byte) []byte { | ||||||
| @@ -57,7 +33,14 @@ func (p synPacket) Marshal(buf []byte) []byte { | |||||||
| 		Uint64(p.TraceID). | 		Uint64(p.TraceID). | ||||||
| 		SharedKey(p.SharedKey). | 		SharedKey(p.SharedKey). | ||||||
| 		Bool(p.Direct). | 		Bool(p.Direct). | ||||||
| 		AddrPort(p.FromAddr). | 		AddrPort(p.PossibleAddrs[0]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[1]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[2]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[3]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[4]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[5]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[6]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[7]). | ||||||
| 		Build() | 		Build() | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -66,53 +49,55 @@ func parseSynPacket(buf []byte) (p synPacket, err error) { | |||||||
| 		Uint64(&p.TraceID). | 		Uint64(&p.TraceID). | ||||||
| 		SharedKey(&p.SharedKey). | 		SharedKey(&p.SharedKey). | ||||||
| 		Bool(&p.Direct). | 		Bool(&p.Direct). | ||||||
| 		AddrPort(&p.FromAddr). | 		AddrPort(&p.PossibleAddrs[0]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[1]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[2]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[3]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[4]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[5]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[6]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[7]). | ||||||
| 		Error() | 		Error() | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
| type synAckPacket struct { | type ackPacket struct { | ||||||
| 	TraceID  uint64 |  | ||||||
| 	FromAddr netip.AddrPort |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (p synAckPacket) Marshal(buf []byte) []byte { |  | ||||||
| 	return newBinWriter(buf). |  | ||||||
| 		Byte(packetTypeSynAck). |  | ||||||
| 		Uint64(p.TraceID). |  | ||||||
| 		AddrPort(p.FromAddr). |  | ||||||
| 		Build() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func parseSynAckPacket(buf []byte) (p synAckPacket, err error) { |  | ||||||
| 	err = newBinReader(buf[1:]). |  | ||||||
| 		Uint64(&p.TraceID). |  | ||||||
| 		AddrPort(&p.FromAddr). |  | ||||||
| 		Error() |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| type addrDiscoveryPacket struct { |  | ||||||
| 	TraceID       uint64 | 	TraceID       uint64 | ||||||
| 	ToAddr        netip.AddrPort | 	ToAddr        netip.AddrPort | ||||||
|  | 	PossibleAddrs [8]netip.AddrPort // Possible public addresses of the sender. | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p addrDiscoveryPacket) Marshal(buf []byte) []byte { | func (p ackPacket) Marshal(buf []byte) []byte { | ||||||
| 	return newBinWriter(buf). | 	return newBinWriter(buf). | ||||||
| 		Byte(packetTypeAddrDiscovery). | 		Byte(packetTypeAck). | ||||||
| 		Uint64(p.TraceID). | 		Uint64(p.TraceID). | ||||||
| 		AddrPort(p.ToAddr). | 		AddrPort(p.ToAddr). | ||||||
|  | 		AddrPort(p.PossibleAddrs[0]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[1]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[2]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[3]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[4]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[5]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[6]). | ||||||
|  | 		AddrPort(p.PossibleAddrs[7]). | ||||||
| 		Build() | 		Build() | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func parseAddrDiscoveryPacket(buf []byte) (p addrDiscoveryPacket, err error) { | func parseAckPacket(buf []byte) (p ackPacket, err error) { | ||||||
| 	err = newBinReader(buf[1:]). | 	err = newBinReader(buf[1:]). | ||||||
| 		Uint64(&p.TraceID). | 		Uint64(&p.TraceID). | ||||||
| 		AddrPort(&p.ToAddr). | 		AddrPort(&p.ToAddr). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[0]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[1]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[2]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[3]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[4]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[5]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[6]). | ||||||
|  | 		AddrPort(&p.PossibleAddrs[7]). | ||||||
| 		Error() | 		Error() | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| @@ -138,3 +123,7 @@ func parseProbePacket(buf []byte) (p probePacket, err error) { | |||||||
| 		Error() | 		Error() | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type localDiscoveryPacket struct{} | ||||||
|   | |||||||
| @@ -10,7 +10,6 @@ import ( | |||||||
| func TestPacketSyn(t *testing.T) { | func TestPacketSyn(t *testing.T) { | ||||||
| 	in := synPacket{ | 	in := synPacket{ | ||||||
| 		TraceID:  newTraceID(), | 		TraceID:  newTraceID(), | ||||||
| 		RelayIP:  4, |  | ||||||
| 		FromAddr: netip.AddrPortFrom(netip.AddrFrom4([4]byte{4, 5, 6, 7}), 22), | 		FromAddr: netip.AddrPortFrom(netip.AddrFrom4([4]byte{4, 5, 6, 7}), 22), | ||||||
| 	} | 	} | ||||||
| 	rand.Read(in.SharedKey[:]) | 	rand.Read(in.SharedKey[:]) | ||||||
| @@ -26,12 +25,12 @@ func TestPacketSyn(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestPacketSynAck(t *testing.T) { | func TestPacketSynAck(t *testing.T) { | ||||||
| 	in := synAckPacket{ | 	in := ackPacket{ | ||||||
| 		TraceID:  newTraceID(), | 		TraceID:  newTraceID(), | ||||||
| 		FromAddr: netip.AddrPortFrom(netip.AddrFrom4([4]byte{4, 5, 6, 7}), 22), | 		FromAddr: netip.AddrPortFrom(netip.AddrFrom4([4]byte{4, 5, 6, 7}), 22), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	out, err := parseSynAckPacket(in.Marshal(make([]byte, bufferSize))) | 	out, err := parseAckPacket(in.Marshal(make([]byte, bufferSize))) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,331 +0,0 @@ | |||||||
| package node |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"log" |  | ||||||
| 	"net/netip" |  | ||||||
| 	"sync/atomic" |  | ||||||
| 	"time" |  | ||||||
| 	"vppn/m" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	pingInterval    = 8 * time.Second |  | ||||||
| 	timeoutInterval = 25 * time.Second |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| type peerSupervisor struct { |  | ||||||
| 	// The purpose of this state machine is to manage this published data. |  | ||||||
| 	published *atomic.Pointer[peerRoute] |  | ||||||
| 	staged    peerRoute // Local copy of shared data. See publish(). |  | ||||||
|  |  | ||||||
| 	// Immutable data. |  | ||||||
| 	remoteIP byte // Remote VPN IP. |  | ||||||
|  |  | ||||||
| 	// Mutable peer data. |  | ||||||
| 	peer      *m.Peer |  | ||||||
| 	remotePub bool |  | ||||||
|  |  | ||||||
| 	// Incoming events. |  | ||||||
| 	peerUpdates    chan *m.Peer |  | ||||||
| 	controlPackets chan controlPacket |  | ||||||
|  |  | ||||||
| 	// Buffers for sending control packets. |  | ||||||
| 	buf1 []byte |  | ||||||
| 	buf2 []byte |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func newPeerSupervisor(i int) *peerSupervisor { |  | ||||||
| 	return &peerSupervisor{ |  | ||||||
| 		published:      routingTable[i], |  | ||||||
| 		remoteIP:       byte(i), |  | ||||||
| 		peerUpdates:    peerUpdates[i], |  | ||||||
| 		controlPackets: controlPackets[i], |  | ||||||
| 		buf1:           make([]byte, bufferSize), |  | ||||||
| 		buf2:           make([]byte, bufferSize), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| type stateFunc func() stateFunc |  | ||||||
|  |  | ||||||
| func (s *peerSupervisor) Run() { |  | ||||||
| 	state := s.noPeer |  | ||||||
| 	for { |  | ||||||
| 		state = state() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| func (s *peerSupervisor) sendControlPacket(pkt interface{ Marshal([]byte) []byte }) { |  | ||||||
| 	_sendControlPacket(pkt, s.staged, s.buf1, s.buf2) |  | ||||||
| 	time.Sleep(500 * time.Millisecond) // Rate limit packets. |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *peerSupervisor) sendControlPacketTo( |  | ||||||
| 	pkt interface{ Marshal([]byte) []byte }, |  | ||||||
| 	addr netip.AddrPort, |  | ||||||
| ) { |  | ||||||
| 	if !addr.IsValid() { |  | ||||||
| 		s.logf("ERROR: Attepted to send packet to invalid address: %v", addr) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	route := s.staged |  | ||||||
| 	route.Direct = true |  | ||||||
| 	route.RemoteAddr = addr |  | ||||||
| 	_sendControlPacket(pkt, route, s.buf1, s.buf2) |  | ||||||
| 	time.Sleep(500 * time.Millisecond) // Rate limit packets. |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| func (s *peerSupervisor) logf(msg string, args ...any) { |  | ||||||
| 	log.Printf(fmt.Sprintf("[%03d] ", s.remoteIP)+msg, args...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| func (s *peerSupervisor) publish() { |  | ||||||
| 	data := s.staged |  | ||||||
| 	s.published.Store(&data) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| func (s *peerSupervisor) noPeer() stateFunc { |  | ||||||
| 	return s.peerUpdate(<-s.peerUpdates) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| func (s *peerSupervisor) peerUpdate(peer *m.Peer) stateFunc { |  | ||||||
| 	return func() stateFunc { return s._peerUpdate(peer) } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *peerSupervisor) _peerUpdate(peer *m.Peer) stateFunc { |  | ||||||
| 	defer s.publish() |  | ||||||
|  |  | ||||||
| 	s.peer = peer |  | ||||||
| 	s.staged = peerRoute{} |  | ||||||
|  |  | ||||||
| 	if s.peer == nil { |  | ||||||
| 		return s.noPeer |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	s.staged.IP = s.remoteIP |  | ||||||
| 	s.staged.ControlCipher = newControlCipher(privateKey, peer.PubKey) |  | ||||||
| 	s.staged.DataCipher = newDataCipher() |  | ||||||
|  |  | ||||||
| 	if ip, isValid := netip.AddrFromSlice(peer.PublicIP); isValid { |  | ||||||
| 		s.remotePub = true |  | ||||||
| 		s.staged.Relay = peer.Relay |  | ||||||
| 		s.staged.Direct = true |  | ||||||
| 		s.staged.RemoteAddr = netip.AddrPortFrom(ip, peer.Port) |  | ||||||
| 	} else if localPub { |  | ||||||
| 		s.staged.Direct = true |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if s.remotePub == localPub { |  | ||||||
| 		if localIP < s.remoteIP { |  | ||||||
| 			return s.server |  | ||||||
| 		} |  | ||||||
| 		return s.client |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if s.remotePub { |  | ||||||
| 		return s.client |  | ||||||
| 	} |  | ||||||
| 	return s.server |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| func (s *peerSupervisor) server() stateFunc { |  | ||||||
| 	logf := func(format string, args ...any) { s.logf("SERVER "+format, args...) } |  | ||||||
|  |  | ||||||
| 	logf("DOWN") |  | ||||||
|  |  | ||||||
| 	var ( |  | ||||||
| 		syn          synPacket |  | ||||||
| 		timeoutTimer = time.NewTimer(timeoutInterval) |  | ||||||
| 	) |  | ||||||
|  |  | ||||||
| 	for { |  | ||||||
| 		select { |  | ||||||
| 		case peer := <-s.peerUpdates: |  | ||||||
| 			return s.peerUpdate(peer) |  | ||||||
|  |  | ||||||
| 		case pkt := <-s.controlPackets: |  | ||||||
| 			switch p := pkt.Payload.(type) { |  | ||||||
|  |  | ||||||
| 			case synPacket: |  | ||||||
| 				// Before we can respond to this packet, we need to make sure the |  | ||||||
| 				// route is setup properly. |  | ||||||
| 				// |  | ||||||
| 				// The client will update the syn's TraceID whenever there's a change. |  | ||||||
| 				// The server will follow the client's request. |  | ||||||
| 				if p.TraceID != syn.TraceID || !s.staged.Up { |  | ||||||
| 					if p.Direct { |  | ||||||
| 						logf("UP - Direct") |  | ||||||
| 					} else { |  | ||||||
| 						logf("UP - Relayed") |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					syn = p |  | ||||||
| 					s.staged.Up = true |  | ||||||
| 					s.staged.Direct = syn.Direct |  | ||||||
| 					s.staged.DataCipher = newDataCipherFromKey(syn.SharedKey) |  | ||||||
| 					s.staged.RemoteAddr = pkt.SrcAddr |  | ||||||
|  |  | ||||||
| 					s.publish() |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				// We should always respond. |  | ||||||
| 				ack := synAckPacket{ |  | ||||||
| 					TraceID:  syn.TraceID, |  | ||||||
| 					FromAddr: getLocalAddr(), |  | ||||||
| 				} |  | ||||||
| 				s.sendControlPacket(ack) |  | ||||||
|  |  | ||||||
| 				if s.staged.Direct { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				if !syn.FromAddr.IsValid() { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				probe := probePacket{TraceID: newTraceID()} |  | ||||||
| 				s.sendControlPacketTo(probe, syn.FromAddr) |  | ||||||
|  |  | ||||||
| 			case probePacket: |  | ||||||
| 				if pkt.SrcAddr.IsValid() { |  | ||||||
| 					s.sendControlPacketTo(probePacket{TraceID: p.TraceID}, pkt.SrcAddr) |  | ||||||
| 				} else { |  | ||||||
| 					logf("Invalid probe address") |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 		case <-timeoutTimer.C: |  | ||||||
| 			logf("Connection timeout") |  | ||||||
| 			s.staged.Up = false |  | ||||||
| 			s.publish() |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- |  | ||||||
|  |  | ||||||
| func (s *peerSupervisor) client() stateFunc { |  | ||||||
| 	logf := func(format string, args ...any) { s.logf("CLIENT "+format, args...) } |  | ||||||
|  |  | ||||||
| 	logf("DOWN") |  | ||||||
|  |  | ||||||
| 	var ( |  | ||||||
| 		syn = synPacket{ |  | ||||||
| 			TraceID:   newTraceID(), |  | ||||||
| 			SharedKey: s.staged.DataCipher.Key(), |  | ||||||
| 			Direct:    s.staged.Direct, |  | ||||||
| 			FromAddr:  getLocalAddr(), |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		ack synAckPacket |  | ||||||
|  |  | ||||||
| 		probe     probePacket |  | ||||||
| 		probeAddr netip.AddrPort |  | ||||||
|  |  | ||||||
| 		lAddr netip.AddrPort |  | ||||||
|  |  | ||||||
| 		timeoutTimer = time.NewTimer(timeoutInterval) |  | ||||||
| 		pingTimer    = time.NewTimer(pingInterval) |  | ||||||
| 	) |  | ||||||
|  |  | ||||||
| 	defer timeoutTimer.Stop() |  | ||||||
| 	defer pingTimer.Stop() |  | ||||||
|  |  | ||||||
| 	s.sendControlPacket(syn) |  | ||||||
|  |  | ||||||
| 	for { |  | ||||||
| 		select { |  | ||||||
|  |  | ||||||
| 		case peer := <-s.peerUpdates: |  | ||||||
| 			return s.peerUpdate(peer) |  | ||||||
|  |  | ||||||
| 		case pkt := <-s.controlPackets: |  | ||||||
| 			switch p := pkt.Payload.(type) { |  | ||||||
|  |  | ||||||
| 			case synAckPacket: |  | ||||||
| 				if p.TraceID != syn.TraceID { |  | ||||||
| 					continue // Hmm... |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				ack = p |  | ||||||
| 				timeoutTimer.Reset(timeoutInterval) |  | ||||||
|  |  | ||||||
| 				if !s.staged.Up { |  | ||||||
| 					if s.staged.Direct { |  | ||||||
| 						logf("UP - Direct") |  | ||||||
| 					} else { |  | ||||||
| 						logf("UP - Relayed") |  | ||||||
| 					} |  | ||||||
|  |  | ||||||
| 					s.staged.Up = true |  | ||||||
| 					s.publish() |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 			case probePacket: |  | ||||||
| 				if s.staged.Direct { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				if p.TraceID != probe.TraceID { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				// Upgrade connection. |  | ||||||
|  |  | ||||||
| 				logf("UP - Direct") |  | ||||||
| 				s.staged.Direct = true |  | ||||||
| 				s.staged.RemoteAddr = probeAddr |  | ||||||
| 				s.publish() |  | ||||||
|  |  | ||||||
| 				syn.TraceID = newTraceID() |  | ||||||
| 				syn.Direct = true |  | ||||||
| 				syn.FromAddr = getLocalAddr() |  | ||||||
| 				s.sendControlPacket(syn) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 		case <-pingTimer.C: |  | ||||||
| 			// Send syn. |  | ||||||
|  |  | ||||||
| 			syn.FromAddr = getLocalAddr() |  | ||||||
| 			if syn.FromAddr != lAddr { |  | ||||||
| 				syn.TraceID = newTraceID() |  | ||||||
| 				lAddr = syn.FromAddr |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			s.sendControlPacket(syn) |  | ||||||
|  |  | ||||||
| 			pingTimer.Reset(pingInterval) |  | ||||||
|  |  | ||||||
| 			if s.staged.Direct { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if !ack.FromAddr.IsValid() { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			probe = probePacket{TraceID: newTraceID()} |  | ||||||
| 			probeAddr = ack.FromAddr |  | ||||||
|  |  | ||||||
| 			s.sendControlPacketTo(probe, ack.FromAddr) |  | ||||||
|  |  | ||||||
| 		case <-timeoutTimer.C: |  | ||||||
| 			logf("Connection timeout") |  | ||||||
| 			return s.peerUpdate(s.peer) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										417
									
								
								node/supervisor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								node/supervisor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,417 @@ | |||||||
|  | package node | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"net/netip" | ||||||
|  | 	"strings" | ||||||
|  | 	"sync/atomic" | ||||||
|  | 	"time" | ||||||
|  | 	"vppn/m" | ||||||
|  |  | ||||||
|  | 	"git.crumpington.com/lib/go/ratelimiter" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	pingInterval    = 8 * time.Second | ||||||
|  | 	timeoutInterval = 30 * time.Second | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | func startPeerSuper() { | ||||||
|  | 	peers := [256]peerState{} | ||||||
|  | 	for i := range peers { | ||||||
|  | 		data := &peerStateData{ | ||||||
|  | 			published: routingTable[i], | ||||||
|  | 			remoteIP:  byte(i), | ||||||
|  | 			buf1:      make([]byte, bufferSize), | ||||||
|  | 			buf2:      make([]byte, bufferSize), | ||||||
|  | 			limiter: ratelimiter.New(ratelimiter.Config{ | ||||||
|  | 				FillPeriod:   20 * time.Millisecond, | ||||||
|  | 				MaxWaitCount: 1, | ||||||
|  | 			}), | ||||||
|  | 		} | ||||||
|  | 		peers[i] = data.OnPeerUpdate(nil) | ||||||
|  | 	} | ||||||
|  | 	go runPeerSuper(peers) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func runPeerSuper(peers [256]peerState) { | ||||||
|  | 	for raw := range messages { | ||||||
|  | 		switch msg := raw.(type) { | ||||||
|  |  | ||||||
|  | 		case peerUpdateMsg: | ||||||
|  | 			peers[msg.PeerIP] = peers[msg.PeerIP].OnPeerUpdate(msg.Peer) | ||||||
|  |  | ||||||
|  | 		case controlMsg[synPacket]: | ||||||
|  | 			peers[msg.SrcIP].OnSyn(msg) | ||||||
|  |  | ||||||
|  | 		case controlMsg[ackPacket]: | ||||||
|  | 			peers[msg.SrcIP].OnAck(msg) | ||||||
|  |  | ||||||
|  | 		case controlMsg[probePacket]: | ||||||
|  | 			peers[msg.SrcIP].OnProbe(msg) | ||||||
|  |  | ||||||
|  | 		case controlMsg[localDiscoveryPacket]: | ||||||
|  | 			peers[msg.SrcIP].OnLocalDiscovery(msg) | ||||||
|  |  | ||||||
|  | 		case pingTimerMsg: | ||||||
|  | 			publicAddrs.Clean() | ||||||
|  | 			for i := range peers { | ||||||
|  | 				if newState := peers[i].OnPingTimer(); newState != nil { | ||||||
|  | 					peers[i] = newState | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		default: | ||||||
|  | 			log.Printf("WARNING: unknown message type: %+v", msg) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type peerState interface { | ||||||
|  | 	OnPeerUpdate(*m.Peer) peerState | ||||||
|  | 	OnSyn(controlMsg[synPacket]) | ||||||
|  | 	OnAck(controlMsg[ackPacket]) | ||||||
|  | 	OnProbe(controlMsg[probePacket]) | ||||||
|  | 	OnLocalDiscovery(controlMsg[localDiscoveryPacket]) | ||||||
|  | 	OnPingTimer() peerState | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type peerStateData struct { | ||||||
|  | 	// The purpose of this state machine is to manage this published data. | ||||||
|  | 	published *atomic.Pointer[peerRoute] | ||||||
|  | 	staged    peerRoute // Local copy of shared data. See publish(). | ||||||
|  |  | ||||||
|  | 	// Immutable data. | ||||||
|  | 	remoteIP byte // Remote VPN IP. | ||||||
|  |  | ||||||
|  | 	// Mutable peer data. | ||||||
|  | 	peer      *m.Peer | ||||||
|  | 	remotePub bool | ||||||
|  |  | ||||||
|  | 	// Buffers for sending control packets. | ||||||
|  | 	buf1 []byte | ||||||
|  | 	buf2 []byte | ||||||
|  |  | ||||||
|  | 	// For logging. Set per-state. | ||||||
|  | 	client bool | ||||||
|  |  | ||||||
|  | 	// We rate limit per remote endpoint because if we don't we tend to lose | ||||||
|  | 	// packets. | ||||||
|  | 	limiter *ratelimiter.Limiter | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | func (s *peerStateData) sendControlPacket(pkt interface{ Marshal([]byte) []byte }) { | ||||||
|  | 	s._sendControlPacket(pkt, s.staged) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *peerStateData) sendControlPacketTo(pkt interface{ Marshal([]byte) []byte }, addr netip.AddrPort) { | ||||||
|  | 	if !addr.IsValid() { | ||||||
|  | 		s.logf("ERROR: Attepted to send packet to invalid address: %v", addr) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	route := s.staged | ||||||
|  | 	route.Direct = true | ||||||
|  | 	route.RemoteAddr = addr | ||||||
|  | 	s._sendControlPacket(pkt, route) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *peerStateData) _sendControlPacket(pkt interface{ Marshal([]byte) []byte }, route peerRoute) { | ||||||
|  | 	if err := s.limiter.Limit(); err != nil { | ||||||
|  | 		s.logf("Not sending control packet: rate limited.") // Shouldn't happen. | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	_sendControlPacket(pkt, route, s.buf1, s.buf2) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | func (s *peerStateData) publish() { | ||||||
|  | 	data := s.staged | ||||||
|  | 	s.published.Store(&data) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *peerStateData) logf(format string, args ...any) { | ||||||
|  | 	b := strings.Builder{} | ||||||
|  | 	b.WriteString(fmt.Sprintf("%30s: ", s.peer.Name)) | ||||||
|  |  | ||||||
|  | 	if s.client { | ||||||
|  | 		b.WriteString("CLIENT | ") | ||||||
|  | 	} else { | ||||||
|  | 		b.WriteString("SERVER | ") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if s.staged.Direct { | ||||||
|  | 		b.WriteString("DIRECT  | ") | ||||||
|  | 	} else { | ||||||
|  | 		b.WriteString("RELAYED | ") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if s.staged.Up { | ||||||
|  | 		b.WriteString("UP   | ") | ||||||
|  | 	} else { | ||||||
|  | 		b.WriteString("DOWN | ") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	log.Printf(b.String()+format, args...) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | func (s *peerStateData) OnPeerUpdate(peer *m.Peer) peerState { | ||||||
|  | 	defer s.publish() | ||||||
|  |  | ||||||
|  | 	if peer == nil { | ||||||
|  | 		return enterStateDisconnected(s) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.peer = peer | ||||||
|  | 	s.staged = peerRoute{ | ||||||
|  | 		IP:            s.remoteIP, | ||||||
|  | 		PubSignKey:    peer.PubSignKey, | ||||||
|  | 		ControlCipher: newControlCipher(privKey, peer.PubKey), | ||||||
|  | 		DataCipher:    newDataCipher(), | ||||||
|  | 	} | ||||||
|  | 	s.remotePub = false | ||||||
|  |  | ||||||
|  | 	if ip, isValid := netip.AddrFromSlice(peer.PublicIP); isValid { | ||||||
|  | 		s.remotePub = true | ||||||
|  | 		s.staged.Relay = peer.Relay | ||||||
|  | 		s.staged.Direct = true | ||||||
|  | 		s.staged.RemoteAddr = netip.AddrPortFrom(ip, peer.Port) | ||||||
|  | 	} else if localPub { | ||||||
|  | 		s.staged.Direct = true | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if s.remotePub == localPub { | ||||||
|  | 		if localIP < s.remoteIP { | ||||||
|  | 			return enterStateServer(s) | ||||||
|  | 		} | ||||||
|  | 		return enterStateClient(s) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if s.remotePub { | ||||||
|  | 		return enterStateClient(s) | ||||||
|  | 	} | ||||||
|  | 	return enterStateServer(s) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type stateDisconnected struct { | ||||||
|  | 	*peerStateData | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func enterStateDisconnected(s *peerStateData) peerState { | ||||||
|  | 	s.peer = nil | ||||||
|  | 	s.staged = peerRoute{} | ||||||
|  | 	s.publish() | ||||||
|  | 	return &stateDisconnected{s} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *stateDisconnected) OnSyn(controlMsg[synPacket])                       {} | ||||||
|  | func (s *stateDisconnected) OnAck(controlMsg[ackPacket])                       {} | ||||||
|  | func (s *stateDisconnected) OnProbe(controlMsg[probePacket])                   {} | ||||||
|  | func (s *stateDisconnected) OnLocalDiscovery(controlMsg[localDiscoveryPacket]) {} | ||||||
|  |  | ||||||
|  | func (s *stateDisconnected) OnPingTimer() peerState { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type stateServer struct { | ||||||
|  | 	*stateDisconnected | ||||||
|  | 	lastSeen   time.Time | ||||||
|  | 	synTraceID uint64 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func enterStateServer(s *peerStateData) peerState { | ||||||
|  | 	s.client = false | ||||||
|  | 	return &stateServer{stateDisconnected: &stateDisconnected{s}} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *stateServer) OnSyn(msg controlMsg[synPacket]) { | ||||||
|  | 	s.lastSeen = time.Now() | ||||||
|  | 	p := msg.Packet | ||||||
|  |  | ||||||
|  | 	// Before we can respond to this packet, we need to make sure the | ||||||
|  | 	// route is setup properly. | ||||||
|  | 	// | ||||||
|  | 	// The client will update the syn's TraceID whenever there's a change. | ||||||
|  | 	// The server will follow the client's request. | ||||||
|  | 	if p.TraceID != s.synTraceID || !s.staged.Up { | ||||||
|  | 		s.synTraceID = p.TraceID | ||||||
|  | 		s.staged.Up = true | ||||||
|  | 		s.staged.Direct = p.Direct | ||||||
|  | 		s.staged.DataCipher = newDataCipherFromKey(p.SharedKey) | ||||||
|  | 		s.staged.RemoteAddr = msg.SrcAddr | ||||||
|  | 		s.publish() | ||||||
|  | 		s.logf("Got syn.") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Always respond. | ||||||
|  | 	ack := ackPacket{ | ||||||
|  | 		TraceID:       p.TraceID, | ||||||
|  | 		ToAddr:        s.staged.RemoteAddr, | ||||||
|  | 		PossibleAddrs: publicAddrs.Get(), | ||||||
|  | 	} | ||||||
|  | 	s.sendControlPacket(ack) | ||||||
|  |  | ||||||
|  | 	if s.staged.Direct { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Not direct => send probes. | ||||||
|  | 	for _, addr := range p.PossibleAddrs { | ||||||
|  | 		if !addr.IsValid() { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		s.sendControlPacketTo(probePacket{TraceID: newTraceID()}, addr) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *stateServer) OnProbe(msg controlMsg[probePacket]) { | ||||||
|  | 	if !msg.SrcAddr.IsValid() { | ||||||
|  | 		s.logf("Invalid probe address.") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	s.sendControlPacketTo(probePacket{TraceID: msg.Packet.TraceID}, msg.SrcAddr) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *stateServer) OnPingTimer() peerState { | ||||||
|  | 	if time.Since(s.lastSeen) > timeoutInterval && s.staged.Up { | ||||||
|  | 		s.staged.Up = false | ||||||
|  | 		s.publish() | ||||||
|  | 		s.logf("Connection timeout.") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type stateClient struct { | ||||||
|  | 	*stateDisconnected | ||||||
|  |  | ||||||
|  | 	lastSeen time.Time | ||||||
|  | 	syn      synPacket | ||||||
|  | 	ack      ackPacket | ||||||
|  |  | ||||||
|  | 	probes             map[uint64]netip.AddrPort | ||||||
|  | 	localDiscoveryAddr netip.AddrPort | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func enterStateClient(s *peerStateData) peerState { | ||||||
|  | 	s.client = true | ||||||
|  | 	ss := &stateClient{ | ||||||
|  | 		stateDisconnected: &stateDisconnected{s}, | ||||||
|  | 		probes:            map[uint64]netip.AddrPort{}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ss.syn = synPacket{ | ||||||
|  | 		TraceID:       newTraceID(), | ||||||
|  | 		SharedKey:     s.staged.DataCipher.Key(), | ||||||
|  | 		Direct:        s.staged.Direct, | ||||||
|  | 		PossibleAddrs: publicAddrs.Get(), | ||||||
|  | 	} | ||||||
|  | 	ss.sendControlPacket(ss.syn) | ||||||
|  |  | ||||||
|  | 	return ss | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *stateClient) sendProbeTo(addr netip.AddrPort) { | ||||||
|  | 	probe := probePacket{TraceID: newTraceID()} | ||||||
|  | 	s.probes[probe.TraceID] = addr | ||||||
|  | 	s.sendControlPacketTo(probe, addr) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *stateClient) OnAck(msg controlMsg[ackPacket]) { | ||||||
|  | 	if msg.Packet.TraceID != s.syn.TraceID { | ||||||
|  | 		s.logf("Ack has incorrect trace ID") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.ack = msg.Packet | ||||||
|  | 	s.lastSeen = time.Now() | ||||||
|  |  | ||||||
|  | 	if !s.staged.Up { | ||||||
|  | 		s.staged.Up = true | ||||||
|  | 		s.logf("Got ack.") | ||||||
|  | 		s.publish() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Store possible public address if we're not a public node. | ||||||
|  | 	if !localPub && s.remotePub { | ||||||
|  | 		publicAddrs.Store(msg.Packet.ToAddr) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *stateClient) OnProbe(msg controlMsg[probePacket]) { | ||||||
|  | 	if s.staged.Direct { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	addr, ok := s.probes[msg.Packet.TraceID] | ||||||
|  | 	if !ok { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.staged.RemoteAddr = addr | ||||||
|  | 	s.staged.Direct = true | ||||||
|  | 	s.publish() | ||||||
|  |  | ||||||
|  | 	s.syn.TraceID = newTraceID() | ||||||
|  | 	s.syn.Direct = true | ||||||
|  | 	s.syn.PossibleAddrs = [8]netip.AddrPort{} | ||||||
|  | 	s.sendControlPacket(s.syn) | ||||||
|  |  | ||||||
|  | 	s.logf("Established direct connection to %s.", s.staged.RemoteAddr.String()) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *stateClient) OnLocalDiscovery(msg controlMsg[localDiscoveryPacket]) { | ||||||
|  | 	if s.staged.Direct { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// The source port will be the multicast port, so we'll have to | ||||||
|  | 	// construct the correct address using the peer's listed port. | ||||||
|  | 	s.localDiscoveryAddr = netip.AddrPortFrom(msg.SrcAddr.Addr(), s.peer.Port) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *stateClient) OnPingTimer() peerState { | ||||||
|  | 	if time.Since(s.lastSeen) > timeoutInterval { | ||||||
|  | 		if s.staged.Up { | ||||||
|  | 			s.logf("Connection timeout.") | ||||||
|  | 		} | ||||||
|  | 		return s.OnPeerUpdate(s.peer) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s.sendControlPacket(s.syn) | ||||||
|  |  | ||||||
|  | 	if s.staged.Direct { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	clear(s.probes) | ||||||
|  | 	for _, addr := range s.ack.PossibleAddrs { | ||||||
|  | 		if !addr.IsValid() { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		s.sendProbeTo(addr) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if s.localDiscoveryAddr.IsValid() { | ||||||
|  | 		s.sendProbeTo(s.localDiscoveryAddr) | ||||||
|  | 		s.localDiscoveryAddr = netip.AddrPort{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @@ -1 +0,0 @@ | |||||||
| ## Stage1: Point-to-point Tunnel w/ no Encryption |  | ||||||
| @@ -1,32 +0,0 @@ | |||||||
| 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) |  | ||||||
| } |  | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| #!/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" |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| 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]) |  | ||||||
| } |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| go build |  | ||||||
| sudo setcap cap_net_admin+iep server |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| 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]) |  | ||||||
| } |  | ||||||
| @@ -1,142 +0,0 @@ | |||||||
| 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
									
									
									
									
									
								
							
							
						
						
									
										109
									
								
								stage1/server.go
									
									
									
									
									
								
							| @@ -1,109 +0,0 @@ | |||||||
| 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 +0,0 @@ | |||||||
| package stage1 |  | ||||||
| @@ -1,4 +0,0 @@ | |||||||
| ## Stage2: |  | ||||||
|  |  | ||||||
| * Point-to-point Tunnel w/ no Encryption |  | ||||||
| * Server gets client's addr from first packet |  | ||||||
| @@ -1,35 +0,0 @@ | |||||||
| 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) |  | ||||||
| } |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| go build |  | ||||||
| sudo setcap cap_net_admin+iep ./client |  | ||||||
| ./client 144.76.78.93 |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| 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]) |  | ||||||
| } |  | ||||||
| @@ -1,6 +0,0 @@ | |||||||
| #!/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" |  | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| package main |  | ||||||
|  |  | ||||||
| import "vppn/stage2" |  | ||||||
|  |  | ||||||
| func main() { |  | ||||||
| 	stage2.RunServer() |  | ||||||
| } |  | ||||||
| @@ -1,142 +0,0 @@ | |||||||
| 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
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								stage2/server.go
									
									
									
									
									
								
							| @@ -1,112 +0,0 @@ | |||||||
| 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 +0,0 @@ | |||||||
| package stage2 |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| ## 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. |  | ||||||
| @@ -1,35 +0,0 @@ | |||||||
| 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) |  | ||||||
| } |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
|  |  | ||||||
| go build |  | ||||||
| sudo setcap cap_net_admin+iep ./client |  | ||||||
| ./client 144.76.78.93 |  | ||||||
| @@ -1,14 +0,0 @@ | |||||||
| 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]) |  | ||||||
| } |  | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| #!/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" |  | ||||||
| @@ -1,7 +0,0 @@ | |||||||
| package main |  | ||||||
|  |  | ||||||
| import "vppn/stage3" |  | ||||||
|  |  | ||||||
| func main() { |  | ||||||
| 	stage3.RunServer() |  | ||||||
| } |  | ||||||
| @@ -1,142 +0,0 @@ | |||||||
| 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 |  | ||||||
| } |  | ||||||
| @@ -1,23 +0,0 @@ | |||||||
| 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 |  | ||||||
| } |  | ||||||
| @@ -1,22 +0,0 @@ | |||||||
| 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
									
									
									
									
									
								
							
							
						
						
									
										147
									
								
								stage3/server.go
									
									
									
									
									
								
							| @@ -1,147 +0,0 @@ | |||||||
| 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 +0,0 @@ | |||||||
| package stage3 |  | ||||||
		Reference in New Issue
	
	Block a user