13 Commits

Author SHA1 Message Date
jdl
55f63043ee Cleanup 2025-01-04 13:52:50 +01:00
jdl
36172bf310 Cleanup 2025-01-04 13:36:55 +01:00
2549e1ae08 Update README.md 2025-01-04 12:30:12 +00:00
a6b849c6e3 fewer-routines (#2)
Co-authored-by: jdl <jdl@desktop>
Reviewed-on: #2
2025-01-04 12:28:40 +00:00
jdl
9637550109 Cleanup 2025-01-02 07:43:42 +01:00
jdl
b1e85733a2 Cleanup 2025-01-02 07:42:13 +01:00
jdl
f0076939d5 Cleanup 2025-01-02 07:42:00 +01:00
jdl
5d97cccb98 WIP: cleanup. Local peer discovery working. 2024-12-30 15:38:53 +01:00
jdl
bbf5202d30 WIP: cleanup. Local peer discovery working. 2024-12-30 15:38:08 +01:00
jdl
8407fd5b48 WIP - cleanup / local discovery 2024-12-30 09:26:48 +01:00
jdl
f47a8245b4 Small bug fixes. 2024-12-25 10:39:42 +01:00
jdl
b8d814f636 Cleanup 2024-12-24 19:41:07 +01:00
jdl
6117a0afe4 Removing unnecessary files. 2024-12-24 19:38:47 +01:00
72 changed files with 953 additions and 1967 deletions

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -1,5 +0,0 @@
#!/bin/bash
go build
sudo setcap cap_net_admin+iep vppn
sudo setcap cap_net_bind_service+iep vppn

View File

@@ -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
} }

View File

@@ -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
} }

View File

@@ -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
); );

View File

@@ -0,0 +1 @@
ALTER TABLE peers ADD COLUMN PubSignKey BLOB NOT NULL DEFAULT '';

View File

@@ -1 +0,0 @@
package api

View File

@@ -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()

View File

@@ -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,
}) })
} }

View File

@@ -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
} }
} }

View File

@@ -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,
}
} }
} }

View File

@@ -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()

View File

@@ -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)
} }

View File

@@ -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}}

View File

@@ -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">

View 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}}

View File

@@ -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}}

View File

@@ -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}}

View File

@@ -3,6 +3,7 @@
<p> <p>
<a href="/admin/peer/edit/?PeerIP={{.Peer.PeerIP}}">Edit</a> / <a href="/admin/peer/edit/?PeerIP={{.Peer.PeerIP}}">Edit</a> /
<a href="/admin/peer/init/?PeerIP={{.Peer.PeerIP}}">Initialize</a> /
<a href="/admin/peer/delete/?PeerIP={{.Peer.PeerIP}}">Delete</a> <a href="/admin/peer/delete/?PeerIP={{.Peer.PeerIP}}">Delete</a>
</p> </p>

View File

@@ -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>

View File

@@ -1 +0,0 @@
package hub

View File

@@ -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
} }

View File

@@ -13,21 +13,17 @@ func addrDiscoveryServer() {
) )
for { for {
pkt := <-discoveryPackets msg := <-discoveryMessages
p := msg.Packet
p, ok := pkt.Payload.(addrDiscoveryPacket) route := routingTable[msg.SrcIP].Load()
if !ok {
continue
}
route := routingTable[pkt.SrcIP].Load()
if route == nil || !route.RemoteAddr.IsValid() { if route == nil || !route.RemoteAddr.IsValid() {
continue continue
} }
_sendControlPacket(addrDiscoveryPacket{ _sendControlPacket(addrDiscoveryPacket{
TraceID: p.TraceID, TraceID: p.TraceID,
ToAddr: pkt.SrcAddr, ToAddr: msg.SrcAddr,
}, *route, buf1, buf2) }, *route, buf1, buf2)
} }
} }
@@ -46,9 +42,9 @@ func addrDiscoveryClient() {
for { for {
select { select {
case pkt := <-discoveryPackets: case msg := <-discoveryMessages:
p, ok := pkt.Payload.(addrDiscoveryPacket) p := msg.Packet
if !ok || p.TraceID != addrPacket.TraceID || !p.ToAddr.IsValid() || p.ToAddr == lAddr { if p.TraceID != addrPacket.TraceID || !p.ToAddr.IsValid() || p.ToAddr == lAddr {
continue continue
} }

View File

@@ -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
View 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)
}
*/

View File

@@ -1,5 +0,0 @@
#!/bin/bash
go build
sudo setcap cap_net_admin+iep ./client
./client 144.76.78.93

View File

@@ -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()
}

View File

@@ -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"

View File

@@ -1,8 +0,0 @@
package main
import "vppn/node"
func main() {
n := node.NewTmpNodeServer()
n.RunServer()
}

11
node/config.go Normal file
View File

@@ -0,0 +1,11 @@
package node
import "vppn/m"
type localConfig struct {
m.PeerConfig
PubKey []byte
PrivKey []byte
PubSignKey []byte
PrivSignKey []byte
}

View File

@@ -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()
} }

View File

@@ -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)
} }

View File

@@ -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,22 @@ 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 privKey []byte
privSignKey []byte
// Shared interface for writing. // Shared interface for writing.
_iface *ifWriter _iface *ifWriter
@@ -54,21 +66,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, 512)
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]) {
@@ -79,8 +78,10 @@ var (
return return
}() }()
// Managed by the addrDiscovery* functions.
discoveryMessages = make(chan controlMsg[addrDiscoveryPacket], 256)
// Managed by the relayManager. // Managed by the relayManager.
discoveryPackets chan controlPacket localAddr = &atomic.Pointer[netip.AddrPort]{}
localAddr *atomic.Pointer[netip.AddrPort] // May be nil. relayIP = &atomic.Pointer[byte]{}
relayIP *atomic.Pointer[byte] // May be nil.
) )

View File

@@ -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
View 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(32 * 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
}

View 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)
}
}

View File

@@ -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,14 +140,14 @@ 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 {
@@ -118,38 +156,32 @@ func main(listenIP string, port uint16) {
localAddr.Store(&addr) localAddr.Store(&addr)
} }
privateKey = config.PrivKey privKey = config.PrivKey
privSignKey = config.PrivSignKey
// Start supervisors.
for i := range 256 {
go newPeerSupervisor(i).Run()
}
if localPub { if localPub {
go addrDiscoveryServer() go addrDiscoveryServer()
} else { } else {
go addrDiscoveryClient() 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()
@@ -206,53 +238,43 @@ func handleControlPacket(addr netip.AddrPort, h header, data, decBuf []byte) {
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) { if dm, ok := msg.(controlMsg[addrDiscoveryPacket]); ok {
discoveryMessages <- dm
case addrDiscoveryPacket: return
select {
case discoveryPackets <- pkt:
default:
log.Printf("Dropping discovery packet.")
} }
default:
select { select {
case controlPackets[h.SourceIP] <- pkt: case messages <- msg:
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 +285,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 +296,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
} }

67
node/messages.go Normal file
View File

@@ -0,0 +1,67 @@
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 packetTypeSynAck:
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
case packetTypeAddrDiscovery:
packet, err := parseAddrDiscoveryPacket(buf)
return controlMsg[addrDiscoveryPacket]{
SrcIP: srcIP,
SrcAddr: srcAddr,
Packet: packet,
}, err
default:
return nil, errUnknownPacketType
}
}
// ----------------------------------------------------------------------------
type peerUpdateMsg struct {
PeerIP byte
Peer *m.Peer
}
// ----------------------------------------------------------------------------
type pingTimerMsg struct{}
// ----------------------------------------------------------------------------

View File

@@ -20,30 +20,6 @@ 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.
@@ -73,12 +49,12 @@ func parseSynPacket(buf []byte) (p synPacket, err error) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
type synAckPacket struct { type ackPacket struct {
TraceID uint64 TraceID uint64
FromAddr netip.AddrPort FromAddr netip.AddrPort
} }
func (p synAckPacket) Marshal(buf []byte) []byte { func (p ackPacket) Marshal(buf []byte) []byte {
return newBinWriter(buf). return newBinWriter(buf).
Byte(packetTypeSynAck). Byte(packetTypeSynAck).
Uint64(p.TraceID). Uint64(p.TraceID).
@@ -86,7 +62,7 @@ func (p synAckPacket) Marshal(buf []byte) []byte {
Build() Build()
} }
func parseSynAckPacket(buf []byte) (p synAckPacket, 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.FromAddr). AddrPort(&p.FromAddr).
@@ -138,3 +114,7 @@ func parseProbePacket(buf []byte) (p probePacket, err error) {
Error() Error()
return return
} }
// ----------------------------------------------------------------------------
type localDiscoveryPacket struct{}

View File

@@ -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)
} }

View File

@@ -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)
}
}
}

395
node/supervisor.go Normal file
View File

@@ -0,0 +1,395 @@
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 = 25 * 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: 50 * 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:
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
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.IP = s.remoteIP
s.staged.PubSignKey = peer.PubSignKey
s.staged.ControlCipher = newControlCipher(privKey, 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 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,
FromAddr: getLocalAddr(),
}
s.sendControlPacket(ack)
if !s.staged.Direct && p.FromAddr.IsValid() {
s.sendControlPacketTo(probePacket{TraceID: newTraceID()}, p.FromAddr)
}
}
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
probeTraceID uint64
probeAddr netip.AddrPort
localProbeTraceID uint64
localProbeAddr netip.AddrPort
}
func enterStateClient(s *peerStateData) peerState {
s.client = true
ss := &stateClient{stateDisconnected: &stateDisconnected{s}}
ss.syn = synPacket{
TraceID: newTraceID(),
SharedKey: s.staged.DataCipher.Key(),
Direct: s.staged.Direct,
FromAddr: getLocalAddr(),
}
ss.sendSyn()
return ss
}
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()
} else {
}
}
func (s *stateClient) OnProbe(msg controlMsg[probePacket]) {
if s.staged.Direct {
return
}
switch msg.Packet.TraceID {
case s.probeTraceID:
s.staged.RemoteAddr = s.probeAddr
case s.localProbeTraceID:
s.staged.RemoteAddr = s.localProbeAddr
default:
return
}
s.staged.Direct = true
s.publish()
s.syn.TraceID = newTraceID()
s.syn.Direct = true
s.syn.FromAddr = getLocalAddr()
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
}
// Send probe.
//
// The source port will be the multicast port, so we'll have to
// construct the correct address using the peer's listed port.
s.localProbeTraceID = newTraceID()
s.localProbeAddr = netip.AddrPortFrom(msg.SrcAddr.Addr(), s.peer.Port)
s.sendControlPacketTo(probePacket{TraceID: s.localProbeTraceID}, s.localProbeAddr)
}
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.sendSyn()
if !s.staged.Direct && s.ack.FromAddr.IsValid() {
s.probeTraceID = newTraceID()
s.probeAddr = s.ack.FromAddr
s.sendControlPacketTo(probePacket{TraceID: s.probeTraceID}, s.probeAddr)
}
return nil
}
func (s *stateClient) sendSyn() {
localAddr := getLocalAddr()
if localAddr != s.syn.FromAddr {
s.syn.TraceID = newTraceID()
s.syn.FromAddr = localAddr
}
s.sendControlPacket(s.syn)
}

View File

@@ -1 +0,0 @@
## Stage1: Point-to-point Tunnel w/ no Encryption

View File

@@ -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)
}

View File

@@ -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"

View File

@@ -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])
}

View File

@@ -1,4 +0,0 @@
#!/bin/bash
go build
sudo setcap cap_net_admin+iep server

View File

@@ -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])
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -1 +0,0 @@
package stage1

View File

@@ -1,4 +0,0 @@
## Stage2:
* Point-to-point Tunnel w/ no Encryption
* Server gets client's addr from first packet

View File

@@ -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)
}

View File

@@ -1,5 +0,0 @@
#!/bin/bash
go build
sudo setcap cap_net_admin+iep ./client
./client 144.76.78.93

View File

@@ -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])
}

View File

@@ -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"

View File

@@ -1,7 +0,0 @@
package main
import "vppn/stage2"
func main() {
stage2.RunServer()
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -1 +0,0 @@
package stage2

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -1,5 +0,0 @@
#!/bin/bash
go build
sudo setcap cap_net_admin+iep ./client
./client 144.76.78.93

View File

@@ -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])
}

View File

@@ -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"

View File

@@ -1,7 +0,0 @@
package main
import "vppn/stage3"
func main() {
stage3.RunServer()
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -1 +0,0 @@
package stage3