WIP - cleanup / local discovery
This commit is contained in:
		
							
								
								
									
										128
									
								
								hub/api/api.go
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								hub/api/api.go
									
									
									
									
									
								
							| @@ -15,6 +15,7 @@ import ( | |||||||
| 	"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/box" | ||||||
|  | 	"golang.org/x/crypto/nacl/sign" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| //go:embed migrations | //go:embed migrations | ||||||
| @@ -24,6 +25,7 @@ type API struct { | |||||||
| 	db          *sql.DB | 	db          *sql.DB | ||||||
| 	lock        sync.Mutex | 	lock        sync.Mutex | ||||||
| 	peerIntents map[string]PeerCreateArgs | 	peerIntents map[string]PeerCreateArgs | ||||||
|  | 	initIntents map[string]byte // Map from intent key to peer IP | ||||||
| } | } | ||||||
|  |  | ||||||
| func New(dbPath string) (*API, error) { | func New(dbPath string) (*API, error) { | ||||||
| @@ -39,6 +41,7 @@ func New(dbPath string) (*API, error) { | |||||||
| 	a := &API{ | 	a := &API{ | ||||||
| 		db:          sqlDB, | 		db:          sqlDB, | ||||||
| 		peerIntents: map[string]PeerCreateArgs{}, | 		peerIntents: map[string]PeerCreateArgs{}, | ||||||
|  | 		initIntents: map[string]byte{}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return a, a.ensurePassword() | 	return a, a.ensurePassword() | ||||||
| @@ -141,6 +144,16 @@ 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) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (a *API) Peer_CreateNew(p *Peer) error { | ||||||
|  | 	p.Version = idgen.NextID(0) | ||||||
|  | 	p.PubKey = []byte{} | ||||||
|  | 	p.PubSignKey = []byte{} | ||||||
|  | 	p.APIKey = idgen.NewToken() | ||||||
|  |  | ||||||
|  | 	return db.Peer_Insert(a.db, p) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TODO: Remove | ||||||
| type PeerCreateArgs struct { | type PeerCreateArgs struct { | ||||||
| 	Name     string | 	Name     string | ||||||
| 	PublicIP []byte | 	PublicIP []byte | ||||||
| @@ -148,6 +161,7 @@ type PeerCreateArgs struct { | |||||||
| 	Relay    bool | 	Relay    bool | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TODO: Remove | ||||||
| // Create the intention to add a peer. The returned code is used to complete | // Create the intention to add a peer. The returned code is used to complete | ||||||
| // the peer creation. The code is valid for 5 minutes. | // the peer creation. The code is valid for 5 minutes. | ||||||
| func (a *API) Peer_CreateIntent(args PeerCreateArgs) string { | func (a *API) Peer_CreateIntent(args PeerCreateArgs) string { | ||||||
| @@ -167,6 +181,78 @@ func (a *API) Peer_CreateIntent(args PeerCreateArgs) string { | |||||||
| 	return code | 	return code | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Create the intention to initialize a peer. The returned code is used to | ||||||
|  | // complete the peer initialization. The code is valid for 5 minutes. | ||||||
|  | func (a *API) Peer_CreateInitIntent(peerIP byte) string { | ||||||
|  | 	a.lock.Lock() | ||||||
|  | 	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.APIKey = idgen.NewToken() | ||||||
|  | 	peer.PubKey = encPubKey[:] | ||||||
|  | 	peer.PubSignKey = signPubKey[:] | ||||||
|  |  | ||||||
|  | 	if err := db.Peer_UpdateFull(a.db, peer); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	conf := a.Config_Get() | ||||||
|  |  | ||||||
|  | 	return &m.PeerConfig{ | ||||||
|  | 		PeerIP:      peer.PeerIP, | ||||||
|  | 		HubAddress:  conf.HubAddress, | ||||||
|  | 		APIKey:      peer.APIKey, | ||||||
|  | 		Network:     conf.VPNNetwork, | ||||||
|  | 		PublicIP:    peer.PublicIP, | ||||||
|  | 		Port:        peer.Port, | ||||||
|  | 		Relay:       peer.Relay, | ||||||
|  | 		PubKey:      encPubKey[:], | ||||||
|  | 		PrivKey:     encPrivKey[:], | ||||||
|  | 		PubSignKey:  signPubKey[:], | ||||||
|  | 		PrivSignKey: signPrivKey[:], | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TODO: Remove | ||||||
| func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { | func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { | ||||||
| 	a.lock.Lock() | 	a.lock.Lock() | ||||||
| 	defer a.lock.Unlock() | 	defer a.lock.Unlock() | ||||||
| @@ -183,6 +269,11 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Get peer IP. | 	// Get peer IP. | ||||||
| 	peerIP := byte(0) | 	peerIP := byte(0) | ||||||
|  |  | ||||||
| @@ -202,14 +293,15 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	peer := &Peer{ | 	peer := &Peer{ | ||||||
| 		PeerIP:   peerIP, | 		PeerIP:     peerIP, | ||||||
| 		Version:  idgen.NextID(0), | 		Version:    idgen.NextID(0), | ||||||
| 		APIKey:   idgen.NewToken(), | 		APIKey:     idgen.NewToken(), | ||||||
| 		Name:     args.Name, | 		Name:       args.Name, | ||||||
| 		PublicIP: args.PublicIP, | 		PublicIP:   args.PublicIP, | ||||||
| 		Port:     args.Port, | 		Port:       args.Port, | ||||||
| 		Relay:    args.Relay, | 		Relay:      args.Relay, | ||||||
| 		PubKey:   encPubKey[:], | 		PubKey:     encPubKey[:], | ||||||
|  | 		PubSignKey: signPubKey[:], | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := db.Peer_Insert(a.db, peer); err != nil { | 	if err := db.Peer_Insert(a.db, peer); err != nil { | ||||||
| @@ -219,15 +311,17 @@ func (a *API) Peer_Create(creationCode string) (*m.PeerConfig, error) { | |||||||
| 	conf := a.Config_Get() | 	conf := a.Config_Get() | ||||||
|  |  | ||||||
| 	return &m.PeerConfig{ | 	return &m.PeerConfig{ | ||||||
| 		PeerIP:     peer.PeerIP, | 		PeerIP:      peer.PeerIP, | ||||||
| 		HubAddress: conf.HubAddress, | 		HubAddress:  conf.HubAddress, | ||||||
| 		APIKey:     peer.APIKey, | 		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[:], | 		PubKey:      encPubKey[:], | ||||||
| 		PrivKey:    encPrivKey[:], | 		PrivKey:     encPrivKey[:], | ||||||
|  | 		PubSignKey:  signPubKey[:], | ||||||
|  | 		PrivSignKey: signPrivKey[:], | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -307,17 +307,18 @@ func Session_List( | |||||||
| // ---------------------------------------------------------------------------- | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
| type Peer struct { | type Peer struct { | ||||||
| 	PeerIP   byte | 	PeerIP     byte | ||||||
| 	Version  int64 | 	Version    int64 | ||||||
| 	APIKey   string | 	APIKey     string | ||||||
| 	Name     string | 	Name       string | ||||||
| 	PublicIP []byte | 	PublicIP   []byte | ||||||
| 	Port     uint16 | 	Port       uint16 | ||||||
| 	Relay    bool | 	Relay      bool | ||||||
| 	PubKey   []byte | 	PubKey     []byte | ||||||
|  | 	PubSignKey []byte | ||||||
| } | } | ||||||
|  |  | ||||||
| const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers" | const Peer_SelectQuery = "SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers" | ||||||
|  |  | ||||||
| func Peer_Insert( | func Peer_Insert( | ||||||
| 	tx TX, | 	tx TX, | ||||||
| @@ -328,7 +329,7 @@ func Peer_Insert( | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	_, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey) VALUES(?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey) | 	_, err = tx.Exec("INSERT INTO peers(PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey) VALUES(?,?,?,?,?,?,?,?,?)", row.PeerIP, row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -369,7 +370,7 @@ func Peer_UpdateFull( | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PeerIP) | 	result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,PublicIP=?,Port=?,Relay=?,PubKey=?,PubSignKey=? WHERE PeerIP=?", row.Version, row.APIKey, row.Name, row.PublicIP, row.Port, row.Relay, row.PubKey, row.PubSignKey, row.PeerIP) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -419,8 +420,8 @@ func Peer_Get( | |||||||
| 	err error, | 	err error, | ||||||
| ) { | ) { | ||||||
| 	row = &Peer{} | 	row = &Peer{} | ||||||
| 	r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey FROM peers WHERE PeerIP=?", PeerIP) | 	r := tx.QueryRow("SELECT PeerIP,Version,APIKey,Name,PublicIP,Port,Relay,PubKey,PubSignKey FROM peers WHERE PeerIP=?", PeerIP) | ||||||
| 	err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) | 	err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -434,7 +435,7 @@ func Peer_GetWhere( | |||||||
| ) { | ) { | ||||||
| 	row = &Peer{} | 	row = &Peer{} | ||||||
| 	r := tx.QueryRow(query, args...) | 	r := tx.QueryRow(query, args...) | ||||||
| 	err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) | 	err = r.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -454,7 +455,7 @@ func Peer_Iterate( | |||||||
| 		defer rows.Close() | 		defer rows.Close() | ||||||
| 		for rows.Next() { | 		for rows.Next() { | ||||||
| 			row := &Peer{} | 			row := &Peer{} | ||||||
| 			err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey) | 			err := rows.Scan(&row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.PublicIP, &row.Port, &row.Relay, &row.PubKey, &row.PubSignKey) | ||||||
| 			if !yield(row, err) { | 			if !yield(row, err) { | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -21,5 +21,6 @@ TABLE peers OF Peer ( | |||||||
|   PublicIP   []byte, |   PublicIP   []byte, | ||||||
|   Port       uint16, |   Port       uint16, | ||||||
|   Relay      bool, |   Relay      bool, | ||||||
|   PubKey     []byte NoUpdate |   PubKey     []byte NoUpdate, | ||||||
|  |   PubSignKey []byte NoUpdate | ||||||
| ); | ); | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								hub/api/migrations/2024-12-27-signing-keys.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								hub/api/migrations/2024-12-27-signing-keys.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | ALTER TABLE peers ADD COLUMN PubSignKey BLOB NOT NULL DEFAULT ''; | ||||||
| @@ -54,11 +54,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 +149,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,25 +179,45 @@ 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) _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, cod) | ||||||
|  |  | ||||||
|  | 	return a.render("/admin-peer-init.html", w, struct { | ||||||
|  | 		Session    *api.Session | ||||||
|  | 		HubAddress string | ||||||
|  | 		Code       string | ||||||
|  | 	}{s, a.api.Config_Get().HubAddress, code}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TODO: Remove | ||||||
| func (a *App) _adminPeerIntentCreated(s *api.Session, w http.ResponseWriter, r *http.Request) error { | func (a *App) _adminPeerIntentCreated(s *api.Session, w http.ResponseWriter, r *http.Request) error { | ||||||
| 	code := r.FormValue("Code") | 	code := r.FormValue("Code") | ||||||
| 	if code == "" { | 	if code == "" { | ||||||
| @@ -323,6 +335,17 @@ 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 { | ||||||
|  | 	code := r.FormValue("Code") | ||||||
|  | 	conf, err := a.api.Peer_Init(code) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return a.sendJSON(w, conf) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TODO: Remove | ||||||
| func (a *App) _peerCreate(w http.ResponseWriter, r *http.Request) error { | func (a *App) _peerCreate(w http.ResponseWriter, r *http.Request) error { | ||||||
| 	code := r.FormValue("Code") | 	code := r.FormValue("Code") | ||||||
| 	conf, err := a.api.Peer_Create(code) | 	conf, err := a.api.Peer_Create(code) | ||||||
| @@ -360,14 +383,16 @@ func (a *App) _peerFetchState(w http.ResponseWriter, r *http.Request) error { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, p := range peers { | 	for _, p := range peers { | ||||||
| 		state.Peers[p.PeerIP] = &m.Peer{ | 		if len(p.PubKey) != 0 { | ||||||
| 			PeerIP:   p.PeerIP, | 			state.Peers[p.PeerIP] = &m.Peer{ | ||||||
| 			Version:  p.Version, | 				PeerIP:   p.PeerIP, | ||||||
| 			Name:     p.Name, | 				Version:  p.Version, | ||||||
| 			PublicIP: p.PublicIP, | 				Name:     p.Name, | ||||||
| 			Port:     p.Port, | 				PublicIP: p.PublicIP, | ||||||
| 			Relay:    p.Relay, | 				Port:     p.Port, | ||||||
| 			PubKey:   p.PubKey, | 				Relay:    p.Relay, | ||||||
|  | 				PubKey:   p.PubKey, | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,10 +16,11 @@ 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/init/", a._adminPeerInit) | ||||||
|  | 	// TODO: Remove | ||||||
| 	a.handleSignedIn("GET  /admin/peer/intent-created/", a._adminPeerIntentCreated) | 	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) | ||||||
| @@ -27,6 +28,7 @@ func (a *App) registerRoutes() { | |||||||
| 	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("GET /peer/create/", a._peerCreate) // TODO: Remove | ||||||
|  | 	a.handlePeer("GET /peer/init/", a._peerInit) | ||||||
| 	a.handlePeer("GET /peer/fetch-state/", a._peerFetchState) | 	a.handlePeer("GET /peer/fetch-state/", a._peerFetchState) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -16,4 +16,42 @@ | |||||||
|     <td>{{ipToString .Config.VPNNetwork}}</td> |     <td>{{ipToString .Config.VPNNetwork}}</td> | ||||||
|   </tr> |   </tr> | ||||||
| </table> | </table> | ||||||
|  |  | ||||||
|  | <h2>Peers</h2> | ||||||
|  |  | ||||||
|  | <p> | ||||||
|  |   <a href="/admin/peer/create/">Add Peer</a> / | ||||||
|  |   <a href="/admin/peer/hosts/">Hosts</a> | ||||||
|  | </p> | ||||||
|  |  | ||||||
|  | {{if .Peers -}} | ||||||
|  | <table> | ||||||
|  |   <thead> | ||||||
|  |     <tr> | ||||||
|  |       <th>PeerIP</th> | ||||||
|  |       <th>Name</th> | ||||||
|  |       <th>Public IP</th> | ||||||
|  |       <th>Port</th> | ||||||
|  |       <th>Relay</th> | ||||||
|  |     </tr> | ||||||
|  |   </thead> | ||||||
|  |   <tbody> | ||||||
|  |   {{range .Peers -}} | ||||||
|  |   <tr> | ||||||
|  |     <td> | ||||||
|  |       <a href="/admin/peer/view/?PeerIP={{.PeerIP}}"> | ||||||
|  |         {{.PeerIP}} | ||||||
|  |       </a> | ||||||
|  |     </td> | ||||||
|  |     <td>{{.Name}}</td> | ||||||
|  |     <td>{{ipToString .PublicIP}}</td> | ||||||
|  |     <td>{{.Port}}</td> | ||||||
|  |     <td>{{if .Relay}}T{{else}}F{{end}}</td> | ||||||
|  |   </tr> | ||||||
|  |   </tbody> | ||||||
|  |   {{- end}} | ||||||
|  | </table> | ||||||
|  | {{- else}} | ||||||
|  | <p>No peers.</p> | ||||||
|  | {{- end}} | ||||||
| {{- end}} | {{- end}} | ||||||
|   | |||||||
| @@ -3,6 +3,10 @@ | |||||||
|  |  | ||||||
| <form method="POST"> | <form method="POST"> | ||||||
|   <input type="hidden" name="CSRF" value="{{.Session.CSRF}}"> |   <input type="hidden" name="CSRF" value="{{.Session.CSRF}}"> | ||||||
|  |   <p> | ||||||
|  |     <label>IP</label><br> | ||||||
|  |     <input type="number" name="IP" min="1" max="255" value="0"> | ||||||
|  |   </p> | ||||||
|   <p> |   <p> | ||||||
|     <label>Name</label><br> |     <label>Name</label><br> | ||||||
|     <input type="text" name="Name"> |     <input type="text" name="Name"> | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								hub/templates/admin-peer-init.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								hub/templates/admin-peer-init.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | {{define "body" -}} | ||||||
|  | <h2>Initialize Peer</h2> | ||||||
|  |  | ||||||
|  | <p> | ||||||
|  |   Configure the peer with the following URL: | ||||||
|  | </p> | ||||||
|  | <pre> | ||||||
|  |   {{.HubAddress}}/peer/init/?Code={{.Code}} | ||||||
|  | </pre> | ||||||
|  | <p> | ||||||
|  |   <a href="/admin/config/">Done</a> | ||||||
|  | </p> | ||||||
|  | {{- end}} | ||||||
| @@ -8,6 +8,6 @@ | |||||||
|   {{.HubAddress}}/peer/create/?Code={{.Code}} |   {{.HubAddress}}/peer/create/?Code={{.Code}} | ||||||
| </pre> | </pre> | ||||||
| <p> | <p> | ||||||
|   <a href="/admin/peer/list/">Done</a> |   <a href="/admin/config/">Done</a> | ||||||
| </p> | </p> | ||||||
| {{- end}} | {{- end}} | ||||||
|   | |||||||
| @@ -1,40 +0,0 @@ | |||||||
| {{define "body" -}} |  | ||||||
| <h2>Peers</h2> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
|   <a href="/admin/peer/create/">Add Peer</a> / |  | ||||||
|   <a href="/admin/peer/hosts/">Hosts</a> |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| {{if .Peers -}} |  | ||||||
| <table> |  | ||||||
|   <thead> |  | ||||||
|     <tr> |  | ||||||
|       <th>PeerIP</th> |  | ||||||
|       <th>Name</th> |  | ||||||
|       <th>Public IP</th> |  | ||||||
|       <th>Port</th> |  | ||||||
|       <th>Relay</th> |  | ||||||
|     </tr> |  | ||||||
|   </thead> |  | ||||||
|   <tbody> |  | ||||||
|   {{range .Peers -}} |  | ||||||
|   <tr> |  | ||||||
|     <td> |  | ||||||
|       <a href="/admin/peer/view/?PeerIP={{.PeerIP}}"> |  | ||||||
|         {{.PeerIP}} |  | ||||||
|       </a> |  | ||||||
|     </td> |  | ||||||
|     <td>{{.Name}}</td> |  | ||||||
|     <td>{{ipToString .PublicIP}}</td> |  | ||||||
|     <td>{{.Port}}</td> |  | ||||||
|     <td>{{if .Relay}}T{{else}}F{{end}}</td> |  | ||||||
|   </tr> |  | ||||||
|   </tbody> |  | ||||||
|   {{- end}} |  | ||||||
| </table> |  | ||||||
| {{- else}} |  | ||||||
| <p>No peers.</p> |  | ||||||
| {{- end}} |  | ||||||
|  |  | ||||||
| {{- end}} |  | ||||||
| @@ -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> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,8 +10,6 @@ | |||||||
|     <h1>VPPN</h1> |     <h1>VPPN</h1> | ||||||
|     <nav> |     <nav> | ||||||
|       {{if .Session.SignedIn -}} |       {{if .Session.SignedIn -}} | ||||||
|       <a href="/admin/config/">Config</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> | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								m/models.go
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								m/models.go
									
									
									
									
									
								
							| @@ -2,25 +2,28 @@ | |||||||
| package m | package m | ||||||
|  |  | ||||||
| type PeerConfig struct { | type PeerConfig struct { | ||||||
|  | 	PeerIP      byte | ||||||
|  | 	HubAddress  string | ||||||
|  | 	Network     []byte | ||||||
|  | 	APIKey      string | ||||||
|  | 	PublicIP    []byte | ||||||
|  | 	Port        uint16 | ||||||
|  | 	Relay       bool | ||||||
|  | 	PubKey      []byte | ||||||
|  | 	PrivKey     []byte | ||||||
|  | 	PubSignKey  []byte | ||||||
|  | 	PrivSignKey []byte | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type Peer struct { | ||||||
| 	PeerIP     byte | 	PeerIP     byte | ||||||
| 	HubAddress string | 	Version    int64 | ||||||
| 	Network    []byte | 	Name       string | ||||||
| 	APIKey     string |  | ||||||
| 	PublicIP   []byte | 	PublicIP   []byte | ||||||
| 	Port       uint16 | 	Port       uint16 | ||||||
| 	Relay      bool | 	Relay      bool | ||||||
| 	PubKey     []byte | 	PubKey     []byte | ||||||
| 	PrivKey    []byte | 	PubSignKey []byte | ||||||
| } |  | ||||||
|  |  | ||||||
| type Peer struct { |  | ||||||
| 	PeerIP   byte |  | ||||||
| 	Version  int64 |  | ||||||
| 	Name     string |  | ||||||
| 	PublicIP []byte |  | ||||||
| 	Port     uint16 |  | ||||||
| 	Relay    bool |  | ||||||
| 	PubKey   []byte |  | ||||||
| } | } | ||||||
|  |  | ||||||
| type NetworkState struct { | type NetworkState struct { | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ import ( | |||||||
| 	"crypto/rand" | 	"crypto/rand" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // TODO: Use [32]byte for simplicity everywhere. |  | ||||||
| type dataCipher struct { | type dataCipher struct { | ||||||
| 	key  [32]byte | 	key  [32]byte | ||||||
| 	aead cipher.AEAD | 	aead cipher.AEAD | ||||||
| @@ -20,7 +19,6 @@ func newDataCipher() *dataCipher { | |||||||
| 	return newDataCipherFromKey(key) | 	return newDataCipherFromKey(key) | ||||||
| } | } | ||||||
|  |  | ||||||
| // key must be 32 bytes. |  | ||||||
| func newDataCipherFromKey(key [32]byte) *dataCipher { | func newDataCipherFromKey(key [32]byte) *dataCipher { | ||||||
| 	block, err := aes.NewCipher(key[:]) | 	block, err := aes.NewCipher(key[:]) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								node/cipher-discovery.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								node/cipher-discovery.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | package node | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | func signData(privKey *[64]byte, h header, data, out []byte) []byte { | ||||||
|  | 	out = out[:headerSize] | ||||||
|  | 	h.Marshal(out) | ||||||
|  | 	return sign.Sign(out, data, privKey) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func openData(pubKey *[32]byte, signed, out []byte) (data []byte, ok bool) { | ||||||
|  | 	return sign.Open(out[:0], signed[headerSize:], pubKey) | ||||||
|  | } | ||||||
|  | */ | ||||||
| @@ -25,7 +25,7 @@ func (w *connWriter) WriteTo(packet []byte, addr netip.AddrPort) { | |||||||
| 	// packets may fail to be sent in a timely manner causing timeouts. | 	// 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 { | ||||||
| 		log.Fatalf("Failed to write to UDP port: %v", err) | 		log.Printf("Failed to write to UDP port: %v", err) | ||||||
| 	} | 	} | ||||||
| 	w.lock.Unlock() | 	w.lock.Unlock() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ 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. | ||||||
| @@ -27,10 +28,11 @@ type peerRoute struct { | |||||||
|  |  | ||||||
| var ( | var ( | ||||||
| 	// 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 | ||||||
| @@ -80,7 +82,7 @@ var ( | |||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
| 	// Managed by the relayManager. | 	// Managed by the relayManager. | ||||||
| 	discoveryPackets chan controlPacket | 	discoveryPackets = make(chan controlPacket, 256) | ||||||
| 	localAddr        *atomic.Pointer[netip.AddrPort] // May be nil. | 	localAddr        = &atomic.Pointer[netip.AddrPort]{} | ||||||
| 	relayIP          *atomic.Pointer[byte]           // May be nil. | 	relayIP          = &atomic.Pointer[byte]{} | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										75
									
								
								node/localbroadcaster.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								node/localbroadcaster.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | package node | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"log" | ||||||
|  | 	"net" | ||||||
|  | 	"net/netip" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func localBroadcaster() { | ||||||
|  | 	var ( | ||||||
|  | 		buf1 = make([]byte, bufferSize) | ||||||
|  | 		buf2 = make([]byte, bufferSize) | ||||||
|  | 	) | ||||||
|  | 	time.Sleep(4 * time.Second) | ||||||
|  | 	doBroadcast(buf1, buf2) | ||||||
|  | 	for range time.Tick(32 * time.Second) { | ||||||
|  | 		doBroadcast(buf1, buf2) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func doBroadcast(buf1, buf2 []byte) { | ||||||
|  | 	ifaces, err := net.Interfaces() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("Failed to list interfaces: %v", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, iface := range ifaces { | ||||||
|  | 		if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagRunning == 0 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if iface.Flags&net.FlagPointToPoint != 0 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if iface.Flags&net.FlagBroadcast == 0 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		addrs, err := iface.Addrs() | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Printf("Failed to get interface addresses: %v", err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for _, addr := range addrs { | ||||||
|  | 			ipNet, ok := addr.(*net.IPNet) | ||||||
|  | 			if !ok { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			ip4 := ipNet.IP.To4() | ||||||
|  | 			if ip4 == nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			ip, ok := lastAddr(ipNet) | ||||||
|  | 			if !ok { | ||||||
|  | 				log.Printf("Failed to find broadcast address: %v", ipNet) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			log.Printf("Broadcasting on address: %v", ip) | ||||||
|  | 			//addr := netip.AddrPortFrom(ip, 456) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // works when the n is a prefix, otherwise... | ||||||
|  | func lastAddr(n *net.IPNet) (netip.Addr, bool) { | ||||||
|  | 	ip := make(net.IP, len(n.IP.To4())) | ||||||
|  | 	binary.BigEndian.PutUint32(ip, binary.BigEndian.Uint32(n.IP.To4())| | ||||||
|  | 		^binary.BigEndian.Uint32(net.IP(n.Mask).To4())) | ||||||
|  | 	return netip.AddrFromSlice(ip) | ||||||
|  | } | ||||||
							
								
								
									
										101
									
								
								node/localdiscovery.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								node/localdiscovery.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | |||||||
|  | package node | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"log" | ||||||
|  | 	"net" | ||||||
|  | 	"net/netip" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	"golang.org/x/crypto/nacl/sign" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	signOverhead  = 64 | ||||||
|  | 	multicastIP   = netip.AddrFrom4([4]byte{224, 0, 0, 157}) | ||||||
|  | 	multicastAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(multicastIP, 4560)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func localDiscovery() { | ||||||
|  | 	conn, err := net.ListenMulticastUDP("udp", nil, multicastAddr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Printf("Failed to bind to multicast address: %v", err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	go sendLocalDiscovery(conn) | ||||||
|  | 	go recvLocalDiscovery(conn) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func sendLocalDiscovery(conn *net.UDPConn) { | ||||||
|  | 	var ( | ||||||
|  | 		buf1 = make([]byte, bufferSize) | ||||||
|  | 		buf2 = make([]byte, bufferSize) | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	for range time.Tick(16 * time.Second) { | ||||||
|  | 		signed := buildLocalDiscoveryPacket(buf1, buf2) | ||||||
|  | 		if _, err := conn.WriteToUDP(signed, multicastAddr); err != nil { | ||||||
|  | 			log.Printf("Failed to write multicast UDP packet: %v", err) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func recvLocalDiscovery(conn *net.UDPConn) { | ||||||
|  | 	var ( | ||||||
|  | 		raw = make([]byte, bufferSize) | ||||||
|  | 		buf = make([]byte, bufferSize) | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		n, remoteAddr, err := conn.ReadFromUDPAddrPort(raw[:bufferSize]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalf("Failed to read from UDP port (multicast): %v", err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		raw = raw[:n] | ||||||
|  | 		h, ok := openLocalDiscoveryPacket(raw, buf) | ||||||
|  | 		if !ok { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		pkt := controlPacket{ | ||||||
|  | 			SrcIP:   h.SourceIP, | ||||||
|  | 			SrcAddr: remoteAddr, | ||||||
|  | 			Payload: localDiscoveryPacket{}, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case controlPackets[h.SourceIP] <- pkt: | ||||||
|  | 		default: | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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 { | ||||||
|  | 		ok = false | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, ok = sign.Open(buf[:0], raw, (*[32]byte)(route.PubSignKey)) | ||||||
|  | 	return | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								node/localdiscovery_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								node/localdiscovery_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | package node | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"crypto/rand" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"golang.org/x/crypto/nacl/sign" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestLocalDiscoveryPacketSigning(t *testing.T) { | ||||||
|  | 	localIP = 32 | ||||||
|  |  | ||||||
|  | 	var ( | ||||||
|  | 		buf1                      = make([]byte, bufferSize) | ||||||
|  | 		buf2                      = make([]byte, bufferSize) | ||||||
|  | 		pubSignKey, privSigKey, _ = sign.GenerateKey(rand.Reader) | ||||||
|  | 	) | ||||||
|  |  | ||||||
|  | 	privSignKey = privSigKey[:] | ||||||
|  | 	route := routingTable[localIP].Load() | ||||||
|  | 	route.IP = byte(localIP) | ||||||
|  | 	route.PubSignKey = pubSignKey[0:32] | ||||||
|  | 	routingTable[localIP].Store(route) | ||||||
|  |  | ||||||
|  | 	out := buildLocalDiscoveryPacket(buf1, buf2) | ||||||
|  |  | ||||||
|  | 	h, ok := openLocalDiscoveryPacket(bytes.Clone(out), buf1) | ||||||
|  | 	if !ok { | ||||||
|  | 		t.Fatal(h, ok) | ||||||
|  | 	} | ||||||
|  | 	if h.StreamID != controlStreamID || h.SourceIP != localIP || h.DestIP != 255 { | ||||||
|  | 		t.Fatal(h) | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								node/main.go
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								node/main.go
									
									
									
									
									
								
							| @@ -11,7 +11,6 @@ import ( | |||||||
| 	"net/netip" | 	"net/netip" | ||||||
| 	"os" | 	"os" | ||||||
| 	"runtime/debug" | 	"runtime/debug" | ||||||
| 	"sync/atomic" |  | ||||||
| 	"vppn/m" | 	"vppn/m" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -50,10 +49,6 @@ func Main() { | |||||||
| } | } | ||||||
|  |  | ||||||
| func mainInit(initURL string) { | func mainInit(initURL string) { | ||||||
| 	if _, err := loadPeerConfig(netName); err == nil { |  | ||||||
| 		log.Fatalf("Network is already initialized.") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	resp, err := http.Get(initURL) | 	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 fetch data from hub: %v", err) | ||||||
| @@ -102,14 +97,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,7 +113,8 @@ func main(listenIP string, port uint16) { | |||||||
| 		localAddr.Store(&addr) | 		localAddr.Store(&addr) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	privateKey = config.PrivKey | 	privKey = config.PrivKey | ||||||
|  | 	privSignKey = config.PrivSignKey | ||||||
|  |  | ||||||
| 	// Start supervisors. | 	// Start supervisors. | ||||||
| 	for i := range 256 { | 	for i := range 256 { | ||||||
| @@ -130,7 +126,9 @@ func main(listenIP string, port uint16) { | |||||||
| 	} else { | 	} else { | ||||||
| 		go addrDiscoveryClient() | 		go addrDiscoveryClient() | ||||||
| 		go relayManager() | 		go relayManager() | ||||||
|  | 		go localDiscovery() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	go newHubPoller(config).Run() | 	go newHubPoller(config).Run() | ||||||
| 	go readFromConn(conn) | 	go readFromConn(conn) | ||||||
| 	readFromIFace(iface) | 	readFromIFace(iface) | ||||||
| @@ -206,17 +204,17 @@ 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 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -252,7 +250,7 @@ func handleControlPacket(addr netip.AddrPort, h header, data, decBuf []byte) { | |||||||
| 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 +261,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 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								node/messages.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								node/messages.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | package node | ||||||
| @@ -138,3 +138,7 @@ func parseProbePacket(buf []byte) (p probePacket, err error) { | |||||||
| 		Error() | 		Error() | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ---------------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | type localDiscoveryPacket struct{} | ||||||
|   | |||||||
| @@ -10,7 +10,6 @@ import ( | |||||||
| func TestPacketSyn(t *testing.T) { | func TestPacketSyn(t *testing.T) { | ||||||
| 	in := synPacket{ | 	in := synPacket{ | ||||||
| 		TraceID:  newTraceID(), | 		TraceID:  newTraceID(), | ||||||
| 		RelayIP:  4, |  | ||||||
| 		FromAddr: netip.AddrPortFrom(netip.AddrFrom4([4]byte{4, 5, 6, 7}), 22), | 		FromAddr: netip.AddrPortFrom(netip.AddrFrom4([4]byte{4, 5, 6, 7}), 22), | ||||||
| 	} | 	} | ||||||
| 	rand.Read(in.SharedKey[:]) | 	rand.Read(in.SharedKey[:]) | ||||||
|   | |||||||
| @@ -115,7 +115,8 @@ func (s *peerSupervisor) _peerUpdate(peer *m.Peer) stateFunc { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	s.staged.IP = s.remoteIP | 	s.staged.IP = s.remoteIP | ||||||
| 	s.staged.ControlCipher = newControlCipher(privateKey, peer.PubKey) | 	s.staged.ControlCipher = newControlCipher(privKey, peer.PubKey) | ||||||
|  | 	s.staged.PubSignKey = peer.PubSignKey | ||||||
| 	s.staged.DataCipher = newDataCipher() | 	s.staged.DataCipher = newDataCipher() | ||||||
|  |  | ||||||
| 	if ip, isValid := netip.AddrFromSlice(peer.PublicIP); isValid { | 	if ip, isValid := netip.AddrFromSlice(peer.PublicIP); isValid { | ||||||
| @@ -241,7 +242,7 @@ func (s *peerSupervisor) client() stateFunc { | |||||||
| 		probe     probePacket | 		probe     probePacket | ||||||
| 		probeAddr netip.AddrPort | 		probeAddr netip.AddrPort | ||||||
|  |  | ||||||
| 		lAddr netip.AddrPort | 		remoteAddr netip.AddrPort | ||||||
|  |  | ||||||
| 		timeoutTimer = time.NewTimer(timeoutInterval) | 		timeoutTimer = time.NewTimer(timeoutInterval) | ||||||
| 		pingTimer    = time.NewTimer(pingInterval) | 		pingTimer    = time.NewTimer(pingInterval) | ||||||
| @@ -306,9 +307,9 @@ func (s *peerSupervisor) client() stateFunc { | |||||||
| 			// Send syn. | 			// Send syn. | ||||||
|  |  | ||||||
| 			syn.FromAddr = getLocalAddr() | 			syn.FromAddr = getLocalAddr() | ||||||
| 			if syn.FromAddr != lAddr { | 			if syn.FromAddr != remoteAddr { | ||||||
| 				syn.TraceID = newTraceID() | 				syn.TraceID = newTraceID() | ||||||
| 				lAddr = syn.FromAddr | 				remoteAddr = syn.FromAddr | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			s.sendControlPacket(syn) | 			s.sendControlPacket(syn) | ||||||
| @@ -319,6 +320,9 @@ func (s *peerSupervisor) client() stateFunc { | |||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// TODO: Check if we have local address. | ||||||
|  | 			// TODO: Send local probe | ||||||
|  |  | ||||||
| 			if !ack.FromAddr.IsValid() { | 			if !ack.FromAddr.IsValid() { | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								node/signing.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								node/signing.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | package node | ||||||
		Reference in New Issue
	
	Block a user