Cleanup
This commit is contained in:
		
							
								
								
									
										39
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,41 +1,8 @@ | |||||||
| # vppn: Virtual Pretty Private Network | # vppn: Virtual Pretty 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 | ||||||
|  |  | ||||||
| @@ -106,7 +73,7 @@ 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,7 +1,6 @@ | |||||||
| package api | package api | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"crypto/rand" |  | ||||||
| 	"database/sql" | 	"database/sql" | ||||||
| 	"embed" | 	"embed" | ||||||
| 	"errors" | 	"errors" | ||||||
| @@ -14,8 +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" |  | ||||||
| 	"golang.org/x/crypto/nacl/sign" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| //go:embed migrations | //go:embed migrations | ||||||
| @@ -24,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 | ||||||
| 	initIntents map[string]byte // Map from intent key to peer IP |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(dbPath string) (*API, error) { | func New(dbPath string) (*API, error) { | ||||||
| @@ -39,7 +35,6 @@ func New(dbPath string) (*API, error) { | |||||||
|  |  | ||||||
| 	a := &API{ | 	a := &API{ | ||||||
| 		db: sqlDB, | 		db: sqlDB, | ||||||
| 		initIntents: map[string]byte{}, |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return a, a.ensurePassword() | 	return a, a.ensurePassword() | ||||||
| @@ -151,55 +146,13 @@ func (a *API) Peer_CreateNew(p *Peer) error { | |||||||
| 	return db.Peer_Insert(a.db, p) | 	return db.Peer_Insert(a.db, p) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Create the intention to initialize a peer. The returned code is used to | func (a *API) Peer_Init(peer *Peer, args m.PeerInitArgs) (*m.PeerConfig, error) { | ||||||
| // complete the peer initialization. The code is valid for 5 minutes. |  | ||||||
| func (a *API) Peer_CreateInitIntent(peerIP byte) string { |  | ||||||
| 	a.lock.Lock() | 	a.lock.Lock() | ||||||
| 	defer a.lock.Unlock() | 	defer a.lock.Unlock() | ||||||
|  |  | ||||||
| 	code := idgen.NewToken() |  | ||||||
| 	a.initIntents[code] = peerIP |  | ||||||
|  |  | ||||||
| 	go func() { |  | ||||||
| 		time.Sleep(5 * time.Minute) |  | ||||||
| 		a.lock.Lock() |  | ||||||
| 		defer a.lock.Unlock() |  | ||||||
| 		delete(a.initIntents, code) |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	return code |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (a *API) Peer_Init(initCode string) (*m.PeerConfig, error) { |  | ||||||
| 	a.lock.Lock() |  | ||||||
| 	defer a.lock.Unlock() |  | ||||||
|  |  | ||||||
| 	ip, ok := a.initIntents[initCode] |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil, ErrNotAuthorized |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	peer, err := a.Peer_Get(ip) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	delete(a.initIntents, initCode) |  | ||||||
|  |  | ||||||
| 	encPubKey, encPrivKey, err := box.GenerateKey(rand.Reader) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	peer.Version = idgen.NextID(0) | 	peer.Version = idgen.NextID(0) | ||||||
| 	peer.APIKey = idgen.NewToken() | 	peer.PubKey = args.EncPubKey | ||||||
| 	peer.PubKey = encPubKey[:] | 	peer.PubSignKey = args.PubSignKey | ||||||
| 	peer.PubSignKey = signPubKey[:] |  | ||||||
|  |  | ||||||
| 	if err := db.Peer_UpdateFull(a.db, peer); err != nil { | 	if err := db.Peer_UpdateFull(a.db, peer); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -209,16 +162,10 @@ func (a *API) Peer_Init(initCode 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[:], |  | ||||||
| 		PubSignKey:  signPubKey[:], |  | ||||||
| 		PrivSignKey: signPrivKey[:], |  | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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" | ||||||
| @@ -201,22 +202,6 @@ func (a *App) _adminPeerCreateSubmit(s *api.Session, w http.ResponseWriter, r *h | |||||||
| 	return a.redirect(w, r, "/admin/peer/view/?PeerIP=%d", p.PeerIP) | 	return a.redirect(w, r, "/admin/peer/view/?PeerIP=%d", p.PeerIP) | ||||||
| } | } | ||||||
|  |  | ||||||
| func (a *App) _adminPeerInit(s *api.Session, w http.ResponseWriter, r *http.Request) error { |  | ||||||
| 	var peerIP byte |  | ||||||
| 	err := webutil.NewFormScanner(r.Form).Scan("PeerIP", &peerIP).Error() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	code := a.api.Peer_CreateInitIntent(peerIP) |  | ||||||
| 	log.Printf("Got code: %v / %v", peerIP, code) |  | ||||||
|  |  | ||||||
| 	return a.render("/admin-peer-init.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 { | ||||||
| 	var peerIP byte | 	var peerIP byte | ||||||
| 	err := webutil.NewFormScanner(r.Form).Scan("PeerIP", &peerIP).Error() | 	err := webutil.NewFormScanner(r.Form).Scan("PeerIP", &peerIP).Error() | ||||||
| @@ -321,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) _peerInit(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_Init(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 | ||||||
| 	} | 	} | ||||||
| @@ -331,31 +320,13 @@ func (a *App) _peerInit(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 { | 		if len(p.PubKey) != 0 { | ||||||
|   | |||||||
| @@ -19,13 +19,12 @@ func (a *App) registerRoutes() { | |||||||
| 	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/init/", a._adminPeerInit) |  | ||||||
| 	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/init/", a._peerInit) | 	a.handlePeer("POST /peer/init/", a._peerInit) | ||||||
| 	a.handlePeer("GET /peer/fetch-state/", a._peerFetchState) | 	a.handlePeer("GET /peer/fetch-state/", a._peerFetchState) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -10,6 +10,7 @@ | |||||||
|     <h1>VPPN</h1> |     <h1>VPPN</h1> | ||||||
|     <nav> |     <nav> | ||||||
|       {{if .Session.SignedIn -}} |       {{if .Session.SignedIn -}} | ||||||
|  |       <a href="/admin/config/">Home</a> / | ||||||
|       <a href="/admin/sign-out/">Sign out</a> |       <a href="/admin/sign-out/">Sign out</a> | ||||||
|       {{- end}} |       {{- end}} | ||||||
|     </nav> |     </nav> | ||||||
|   | |||||||
							
								
								
									
										20
									
								
								m/models.go
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								m/models.go
									
									
									
									
									
								
							| @@ -1,18 +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 |  | ||||||
| 	PubSignKey  []byte |  | ||||||
| 	PrivSignKey []byte |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type Peer struct { | type Peer struct { | ||||||
| @@ -27,14 +26,5 @@ type Peer struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| 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 | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package node | |||||||
| import ( | import ( | ||||||
| 	"net" | 	"net" | ||||||
| 	"net/netip" | 	"net/netip" | ||||||
|  | 	"net/url" | ||||||
| 	"sync/atomic" | 	"sync/atomic" | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| @@ -33,6 +34,9 @@ type peerRoute struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
|  | 	hubURL *url.URL | ||||||
|  | 	apiKey string | ||||||
|  |  | ||||||
| 	// Configuration for this peer. | 	// Configuration for this peer. | ||||||
| 	netName     string | 	netName     string | ||||||
| 	localIP     byte | 	localIP     byte | ||||||
|   | |||||||
| @@ -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 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										94
									
								
								node/main.go
									
									
									
									
									
								
							
							
						
						
									
										94
									
								
								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" | ||||||
| 	"time" | 	"time" | ||||||
| 	"vppn/m" | 	"vppn/m" | ||||||
|  |  | ||||||
|  | 	"golang.org/x/crypto/nacl/box" | ||||||
|  | 	"golang.org/x/crypto/nacl/sign" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func panicHandler() { | func panicHandler() { | ||||||
| @@ -24,33 +30,61 @@ func panicHandler() { | |||||||
| func Main() { | func Main() { | ||||||
| 	defer panicHandler() | 	defer panicHandler() | ||||||
|  |  | ||||||
| 	var ( | 	var hubAddress string | ||||||
| 		initURL  string |  | ||||||
| 		listenIP string |  | ||||||
| 	) |  | ||||||
|  |  | ||||||
| 	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.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) | 	hubURL, err = url.Parse(hubAddress) | ||||||
| } |  | ||||||
|  |  | ||||||
| func mainInit(initURL string) { |  | ||||||
| 	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() | ||||||
|  |  | ||||||
| @@ -59,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) | ||||||
| 	} | 	} | ||||||
| @@ -73,18 +112,25 @@ func mainInit(initURL string) { | |||||||
|  |  | ||||||
| // ---------------------------------------------------------------------------- | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
| func main(listenIP string) { | 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) | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	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, config.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) | ||||||
| 	} | 	} | ||||||
| @@ -137,7 +183,7 @@ func main(listenIP string) { | |||||||
| 		} | 		} | ||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
| 	go newHubPoller(config).Run() | 	go newHubPoller().Run() | ||||||
| 	go readFromConn(conn) | 	go readFromConn(conn) | ||||||
| 	readFromIFace(iface) | 	readFromIFace(iface) | ||||||
| } | } | ||||||
| @@ -258,7 +304,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 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -214,7 +214,7 @@ func (s *peerSupervisor) server() stateFunc { | |||||||
| 			s.sendControlPacketTo(probePacket{TraceID: msg.Packet.TraceID}, msg.SrcAddr) | 			s.sendControlPacketTo(probePacket{TraceID: msg.Packet.TraceID}, msg.SrcAddr) | ||||||
|  |  | ||||||
| 		case pingTimerMsg: | 		case pingTimerMsg: | ||||||
| 			if time.Since(lastSeen) > timeoutInterval { | 			if time.Since(lastSeen) > timeoutInterval && s.staged.Up { | ||||||
| 				logf("Connection timeout") | 				logf("Connection timeout") | ||||||
| 				s.staged.Up = false | 				s.staged.Up = false | ||||||
| 				s.publish() | 				s.publish() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user