Cleanup - WIP
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"git.crumpington.com/lib/go/webutil"
|
"git.crumpington.com/lib/go/webutil"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) _root(s *api.Session, w http.ResponseWriter, r *http.Request) error {
|
func (a *App) _root(s *api.Session, w http.ResponseWriter, r *http.Request) error {
|
||||||
@@ -366,19 +367,26 @@ func (a *App) peersList(networkID int64) (peers []m.Peer, err error) {
|
|||||||
peers = make([]m.Peer, 0, len(l))
|
peers = make([]m.Peer, 0, len(l))
|
||||||
|
|
||||||
for _, p := range l {
|
for _, p := range l {
|
||||||
if len(p.WGPubKey) != 0 {
|
if len(p.WGPubKey) == 0 {
|
||||||
peers = append(peers, m.Peer{
|
continue
|
||||||
PeerIP: p.PeerIP,
|
|
||||||
Version: p.Version,
|
|
||||||
Name: p.Name,
|
|
||||||
Addr4: p.Addr4,
|
|
||||||
Addr6: p.Addr6,
|
|
||||||
Port: p.Port,
|
|
||||||
Relay: p.Relay,
|
|
||||||
WGPubKey: p.WGPubKey,
|
|
||||||
SignPubKey: p.SignPubKey,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
wgKey, err := wgtypes.NewKey(p.WGPubKey)
|
||||||
|
if err != nil {
|
||||||
|
continue // malformed key; skip rather than serve garbage
|
||||||
|
}
|
||||||
|
var signKey [32]byte
|
||||||
|
copy(signKey[:], p.SignPubKey)
|
||||||
|
peers = append(peers, m.Peer{
|
||||||
|
PeerIP: p.PeerIP,
|
||||||
|
Version: p.Version,
|
||||||
|
Name: p.Name,
|
||||||
|
Addr4: addrFromBytes(p.Addr4),
|
||||||
|
Addr6: addrFromBytes(p.Addr6),
|
||||||
|
Port: p.Port,
|
||||||
|
Relay: p.Relay,
|
||||||
|
WGPubKey: wgKey,
|
||||||
|
SignPubKey: signKey,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return peers, nil
|
return peers, nil
|
||||||
|
|||||||
13
hub/util.go
13
hub/util.go
@@ -38,6 +38,19 @@ func (app *App) sendJSON(w http.ResponseWriter, data any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addrFromBytes parses raw IP bytes (4 or 16) into a netip.Addr, unmapping
|
||||||
|
// IPv4-in-IPv6, returning the zero Addr for empty/invalid input.
|
||||||
|
func addrFromBytes(b []byte) netip.Addr {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return netip.Addr{}
|
||||||
|
}
|
||||||
|
addr, ok := netip.AddrFromSlice(b)
|
||||||
|
if !ok {
|
||||||
|
return netip.Addr{}
|
||||||
|
}
|
||||||
|
return addr.Unmap()
|
||||||
|
}
|
||||||
|
|
||||||
func stringToIP(in string) ([]byte, error) {
|
func stringToIP(in string) ([]byte, error) {
|
||||||
in = strings.TrimSpace(in)
|
in = strings.TrimSpace(in)
|
||||||
if len(in) == 0 {
|
if len(in) == 0 {
|
||||||
|
|||||||
115
m/models.go
115
m/models.go
@@ -1,6 +1,15 @@
|
|||||||
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
)
|
||||||
|
|
||||||
type PeerInitArgs struct {
|
type PeerInitArgs struct {
|
||||||
WGPubKey []byte
|
WGPubKey []byte
|
||||||
SignPubKey []byte
|
SignPubKey []byte
|
||||||
@@ -13,16 +22,114 @@ type PeerInitResp struct {
|
|||||||
NetworkState NetworkState
|
NetworkState NetworkState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Peer is the network membership record for a single peer, exchanged between
|
||||||
|
// the hub and peers. Addr4/Addr6 are the peer's public endpoint addresses (zero
|
||||||
|
// if it has none); Port is its WireGuard listen port, meaningful even for a
|
||||||
|
// non-public peer (it is the peer's own bind/beacon port).
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
PeerIP byte
|
PeerIP byte
|
||||||
Version int64
|
Version int64
|
||||||
Name string
|
Name string
|
||||||
Addr4 []byte
|
Addr4 netip.Addr // zero if none
|
||||||
Addr6 []byte
|
Addr6 netip.Addr // zero if none
|
||||||
Port uint16
|
Port uint16
|
||||||
Relay bool
|
Relay bool
|
||||||
WGPubKey []byte
|
WGPubKey wgtypes.Key
|
||||||
SignPubKey []byte
|
SignPubKey [32]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPublic reports whether the peer advertises at least one reachable endpoint.
|
||||||
|
func (p Peer) IsPublic() bool {
|
||||||
|
return p.Addr4.IsValid() || p.Addr6.IsValid()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint4 returns the IPv4 endpoint (addr+port), or the zero AddrPort if the
|
||||||
|
// peer has no IPv4 address.
|
||||||
|
func (p Peer) Endpoint4() netip.AddrPort {
|
||||||
|
if !p.Addr4.IsValid() {
|
||||||
|
return netip.AddrPort{}
|
||||||
|
}
|
||||||
|
return netip.AddrPortFrom(p.Addr4, p.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint6 returns the IPv6 endpoint (addr+port), or the zero AddrPort if the
|
||||||
|
// peer has no IPv6 address.
|
||||||
|
func (p Peer) Endpoint6() netip.AddrPort {
|
||||||
|
if !p.Addr6.IsValid() {
|
||||||
|
return netip.AddrPort{}
|
||||||
|
}
|
||||||
|
return netip.AddrPortFrom(p.Addr6, p.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreferredEndpoint returns the IPv4 endpoint if present, else IPv6.
|
||||||
|
func (p Peer) PreferredEndpoint() netip.AddrPort {
|
||||||
|
if ep := p.Endpoint4(); ep.IsValid() {
|
||||||
|
return ep
|
||||||
|
}
|
||||||
|
return p.Endpoint6()
|
||||||
|
}
|
||||||
|
|
||||||
|
// peerJSON is the wire representation. netip.Addr fields round-trip as text
|
||||||
|
// strings automatically; only the fixed-size key arrays need base64 (otherwise
|
||||||
|
// encoding/json would emit them as arrays of numbers).
|
||||||
|
type peerJSON struct {
|
||||||
|
PeerIP byte
|
||||||
|
Version int64
|
||||||
|
Name string
|
||||||
|
Addr4 netip.Addr
|
||||||
|
Addr6 netip.Addr
|
||||||
|
Port uint16
|
||||||
|
Relay bool
|
||||||
|
WGPubKey string
|
||||||
|
SignPubKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Peer) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(peerJSON{
|
||||||
|
PeerIP: p.PeerIP,
|
||||||
|
Version: p.Version,
|
||||||
|
Name: p.Name,
|
||||||
|
Addr4: p.Addr4,
|
||||||
|
Addr6: p.Addr6,
|
||||||
|
Port: p.Port,
|
||||||
|
Relay: p.Relay,
|
||||||
|
WGPubKey: base64.StdEncoding.EncodeToString(p.WGPubKey[:]),
|
||||||
|
SignPubKey: base64.StdEncoding.EncodeToString(p.SignPubKey[:]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Peer) UnmarshalJSON(data []byte) error {
|
||||||
|
var j peerJSON
|
||||||
|
if err := json.Unmarshal(data, &j); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wg, err := base64.StdEncoding.DecodeString(j.WGPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decode WGPubKey: %w", err)
|
||||||
|
}
|
||||||
|
key, err := wgtypes.NewKey(wg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid WGPubKey: %w", err)
|
||||||
|
}
|
||||||
|
sign, err := base64.StdEncoding.DecodeString(j.SignPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decode SignPubKey: %w", err)
|
||||||
|
}
|
||||||
|
if len(sign) != 32 {
|
||||||
|
return fmt.Errorf("invalid SignPubKey length: %d", len(sign))
|
||||||
|
}
|
||||||
|
*p = Peer{
|
||||||
|
PeerIP: j.PeerIP,
|
||||||
|
Version: j.Version,
|
||||||
|
Name: j.Name,
|
||||||
|
Addr4: j.Addr4,
|
||||||
|
Addr6: j.Addr6,
|
||||||
|
Port: j.Port,
|
||||||
|
Relay: j.Relay,
|
||||||
|
WGPubKey: key,
|
||||||
|
SignPubKey: [32]byte(sign),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetworkState struct {
|
type NetworkState struct {
|
||||||
|
|||||||
15
peer/app.go
15
peer/app.go
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
|
"vppn/m"
|
||||||
"vppn/peer/control"
|
"vppn/peer/control"
|
||||||
"vppn/peer/wginterface"
|
"vppn/peer/wginterface"
|
||||||
)
|
)
|
||||||
@@ -21,18 +22,6 @@ const (
|
|||||||
TimeoutInterval = 30 * time.Second
|
TimeoutInterval = 30 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// HubPeer is a peer entry as reported by the hub poller.
|
|
||||||
type HubPeer struct {
|
|
||||||
PubKey wgtypes.Key
|
|
||||||
VPNIP netip.Addr
|
|
||||||
Name string
|
|
||||||
IsRelay bool
|
|
||||||
IsPublic bool
|
|
||||||
EndpointV4 netip.AddrPort // zero if none
|
|
||||||
EndpointV6 netip.AddrPort // zero if none
|
|
||||||
SignPubKey [32]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type PingEvent struct {
|
type PingEvent struct {
|
||||||
srcVPNIP netip.Addr
|
srcVPNIP netip.Addr
|
||||||
ping control.Ping
|
ping control.Ping
|
||||||
@@ -70,7 +59,7 @@ type App struct {
|
|||||||
selfV6 netip.AddrPort
|
selfV6 netip.AddrPort
|
||||||
|
|
||||||
// Event channels fed by background goroutines
|
// Event channels fed by background goroutines
|
||||||
hubAddCh <-chan HubPeer
|
hubAddCh <-chan m.Peer
|
||||||
hubRemoveCh <-chan wgtypes.Key
|
hubRemoveCh <-chan wgtypes.Key
|
||||||
pingCh <-chan PingEvent
|
pingCh <-chan PingEvent
|
||||||
multicastCh <-chan MulticastEvent
|
multicastCh <-chan MulticastEvent
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
|
"vppn/m"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addRelayPeer adds a public relay peer and marks it Up so it satisfies
|
// addRelayPeer adds a public relay peer and marks it Up so it satisfies
|
||||||
@@ -13,12 +15,13 @@ import (
|
|||||||
func addRelayPeer(t *testing.T, a *App, vpnIP string, ep netip.AddrPort) *Peer {
|
func addRelayPeer(t *testing.T, a *App, vpnIP string, ep netip.AddrPort) *Peer {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
key := mustKey(t)
|
key := mustKey(t)
|
||||||
a.onAddPeer(HubPeer{
|
ip := netip.MustParseAddr(vpnIP)
|
||||||
PubKey: key,
|
a.onAddPeer(m.Peer{
|
||||||
VPNIP: netip.MustParseAddr(vpnIP),
|
WGPubKey: key,
|
||||||
IsPublic: true,
|
PeerIP: ip.As4()[3],
|
||||||
IsRelay: true,
|
Addr4: ep.Addr(),
|
||||||
EndpointV4: ep,
|
Port: ep.Port(),
|
||||||
|
Relay: true,
|
||||||
})
|
})
|
||||||
p := a.peersByKey[key]
|
p := a.peersByKey[key]
|
||||||
p.wgPeer.LastHandshakeTime = time.Now()
|
p.wgPeer.LastHandshakeTime = time.Now()
|
||||||
@@ -48,7 +51,7 @@ func newTestApp(t *testing.T, vpnIP string, isPublic, isRelay bool) (*App, *fake
|
|||||||
controlConn: cc,
|
controlConn: cc,
|
||||||
peersByKey: make(map[wgtypes.Key]*Peer),
|
peersByKey: make(map[wgtypes.Key]*Peer),
|
||||||
peersByIP: make(map[netip.Addr]*Peer),
|
peersByIP: make(map[netip.Addr]*Peer),
|
||||||
hubAddCh: make(chan HubPeer),
|
hubAddCh: make(chan m.Peer),
|
||||||
hubRemoveCh: make(chan wgtypes.Key),
|
hubRemoveCh: make(chan wgtypes.Key),
|
||||||
pingCh: make(chan PingEvent),
|
pingCh: make(chan PingEvent),
|
||||||
multicastCh: make(chan MulticastEvent),
|
multicastCh: make(chan MulticastEvent),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type HubPoller struct {
|
|||||||
hubURL string
|
hubURL string
|
||||||
apiKey string
|
apiKey string
|
||||||
statePath string // where the network state cache is persisted
|
statePath string // where the network state cache is persisted
|
||||||
addCh chan<- HubPeer
|
addCh chan<- m.Peer
|
||||||
removeCh chan<- wgtypes.Key
|
removeCh chan<- wgtypes.Key
|
||||||
known map[wgtypes.Key]int64 // pubKey → last seen version
|
known map[wgtypes.Key]int64 // pubKey → last seen version
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ func NewHubPoller(
|
|||||||
vpnNet netip.Prefix,
|
vpnNet netip.Prefix,
|
||||||
hubURL, apiKey string,
|
hubURL, apiKey string,
|
||||||
statePath string,
|
statePath string,
|
||||||
addCh chan<- HubPeer,
|
addCh chan<- m.Peer,
|
||||||
removeCh chan<- wgtypes.Key,
|
removeCh chan<- wgtypes.Key,
|
||||||
) (*HubPoller, error) {
|
) (*HubPoller, error) {
|
||||||
u, err := url.Parse(hubURL)
|
u, err := url.Parse(hubURL)
|
||||||
@@ -119,12 +119,7 @@ func (hp *HubPoller) apply(state m.NetworkState) (changed bool) {
|
|||||||
netAddr := hp.vpnNet.Addr().As4()
|
netAddr := hp.vpnNet.Addr().As4()
|
||||||
|
|
||||||
for _, p := range state.Peers {
|
for _, p := range state.Peers {
|
||||||
if len(p.WGPubKey) != wgtypes.KeyLen || len(p.SignPubKey) != 32 {
|
if p.WGPubKey == (wgtypes.Key{}) {
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pubKey, err := wgtypes.NewKey(p.WGPubKey)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,13 +130,13 @@ func (hp *HubPoller) apply(state m.NetworkState) (changed bool) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
seen[pubKey] = struct{}{}
|
seen[p.WGPubKey] = struct{}{}
|
||||||
|
|
||||||
if v, ok := hp.known[pubKey]; ok && v == p.Version {
|
if v, ok := hp.known[p.WGPubKey]; ok && v == p.Version {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
hp.known[pubKey] = p.Version
|
hp.known[p.WGPubKey] = p.Version
|
||||||
hp.addCh <- hubPeerFrom(pubKey, vpnIP, p)
|
hp.addCh <- p
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,30 +150,3 @@ func (hp *HubPoller) apply(state m.NetworkState) (changed bool) {
|
|||||||
|
|
||||||
return changed
|
return changed
|
||||||
}
|
}
|
||||||
|
|
||||||
func hubPeerFrom(pubKey wgtypes.Key, vpnIP netip.Addr, p m.Peer) HubPeer {
|
|
||||||
var ep4, ep6 netip.AddrPort
|
|
||||||
if len(p.Addr4) > 0 {
|
|
||||||
if addr, ok := netip.AddrFromSlice(p.Addr4); ok {
|
|
||||||
ep4 = netip.AddrPortFrom(addr.Unmap(), p.Port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p.Addr6) > 0 {
|
|
||||||
if addr, ok := netip.AddrFromSlice(p.Addr6); ok {
|
|
||||||
ep6 = netip.AddrPortFrom(addr.Unmap(), p.Port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var signPubKey [32]byte
|
|
||||||
copy(signPubKey[:], p.SignPubKey)
|
|
||||||
return HubPeer{
|
|
||||||
PubKey: pubKey,
|
|
||||||
VPNIP: vpnIP,
|
|
||||||
Name: p.Name,
|
|
||||||
IsRelay: p.Relay,
|
|
||||||
IsPublic: ep4.IsValid() || ep6.IsValid(),
|
|
||||||
EndpointV4: ep4,
|
|
||||||
EndpointV6: ep6,
|
|
||||||
SignPubKey: signPubKey,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
"vppn/m"
|
"vppn/m"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testPoller(t *testing.T) (*HubPoller, chan HubPeer, chan wgtypes.Key) {
|
func testPoller(t *testing.T) (*HubPoller, chan m.Peer, chan wgtypes.Key) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
addCh := make(chan HubPeer, 8)
|
addCh := make(chan m.Peer, 8)
|
||||||
removeCh := make(chan wgtypes.Key, 8)
|
removeCh := make(chan wgtypes.Key, 8)
|
||||||
hp := &HubPoller{
|
hp := &HubPoller{
|
||||||
selfVPNIP: netip.MustParseAddr("10.0.0.1"),
|
selfVPNIP: netip.MustParseAddr("10.0.0.1"),
|
||||||
@@ -25,10 +25,9 @@ func testPoller(t *testing.T) (*HubPoller, chan HubPeer, chan wgtypes.Key) {
|
|||||||
|
|
||||||
func stateWith(key wgtypes.Key, peerIP byte, version int64) m.NetworkState {
|
func stateWith(key wgtypes.Key, peerIP byte, version int64) m.NetworkState {
|
||||||
return m.NetworkState{Peers: []m.Peer{{
|
return m.NetworkState{Peers: []m.Peer{{
|
||||||
PeerIP: peerIP,
|
PeerIP: peerIP,
|
||||||
Version: version,
|
Version: version,
|
||||||
WGPubKey: key[:],
|
WGPubKey: key,
|
||||||
SignPubKey: make([]byte, 32),
|
|
||||||
}}}
|
}}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +41,7 @@ func TestApply_EmitsAddsAndReportsChange(t *testing.T) {
|
|||||||
if len(addCh) != 1 {
|
if len(addCh) != 1 {
|
||||||
t.Fatalf("expected 1 add, got %d", len(addCh))
|
t.Fatalf("expected 1 add, got %d", len(addCh))
|
||||||
}
|
}
|
||||||
if got := <-addCh; got.PubKey != key {
|
if got := <-addCh; got.WGPubKey != key {
|
||||||
t.Errorf("add pubkey mismatch")
|
t.Errorf("add pubkey mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
peer/init.go
27
peer/init.go
@@ -78,10 +78,13 @@ func initFromHub(hubURL, apiKey string, privKey wgtypes.Key) (LocalState, error)
|
|||||||
return LocalState{}, fmt.Errorf("generate sign key: %w", err)
|
return LocalState{}, fmt.Errorf("generate sign key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := json.Marshal(m.PeerInitArgs{
|
body, err := json.Marshal(m.PeerInitArgs{
|
||||||
WGPubKey: wgPubKey[:],
|
WGPubKey: wgPubKey[:],
|
||||||
SignPubKey: signPubKey[:],
|
SignPubKey: signPubKey[:],
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return LocalState{}, fmt.Errorf("json error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPost, hubURL+"/peer/init/", bytes.NewReader(body))
|
req, err := http.NewRequest(http.MethodPost, hubURL+"/peer/init/", bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -126,31 +129,15 @@ func initFromHub(hubURL, apiKey string, privKey wgtypes.Key) (LocalState, error)
|
|||||||
return LocalState{}, fmt.Errorf("hub init: no peer for own IP: %d", r.PeerIP)
|
return LocalState{}, fmt.Errorf("hub init: no peer for own IP: %d", r.PeerIP)
|
||||||
}
|
}
|
||||||
|
|
||||||
var isRelay, public bool
|
public := self.IsPublic()
|
||||||
var wgPort uint16
|
|
||||||
|
|
||||||
var ep4, ep6 netip.AddrPort
|
|
||||||
if len(self.Addr4) > 0 {
|
|
||||||
if addr, ok := netip.AddrFromSlice(self.Addr4); ok {
|
|
||||||
ep4 = netip.AddrPortFrom(addr.Unmap(), self.Port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(self.Addr6) > 0 {
|
|
||||||
if addr, ok := netip.AddrFromSlice(self.Addr6); ok {
|
|
||||||
ep6 = netip.AddrPortFrom(addr.Unmap(), self.Port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public = ep4.IsValid() || ep6.IsValid()
|
|
||||||
isRelay = self.Relay && public
|
|
||||||
wgPort = self.Port
|
|
||||||
|
|
||||||
return LocalState{
|
return LocalState{
|
||||||
PrivKey: privKey,
|
PrivKey: privKey,
|
||||||
SignKey: *signPrivKey,
|
SignKey: *signPrivKey,
|
||||||
VPNIP: vpnIP,
|
VPNIP: vpnIP,
|
||||||
VPNNet: vpnNet,
|
VPNNet: vpnNet,
|
||||||
WGPort: wgPort,
|
WGPort: self.Port,
|
||||||
IsRelay: isRelay,
|
IsRelay: self.Relay && public,
|
||||||
IsPublic: public,
|
IsPublic: public,
|
||||||
LocalDomain: r.LocalDomain,
|
LocalDomain: r.LocalDomain,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package peer
|
package peer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -11,25 +12,27 @@ import (
|
|||||||
func TestNetworkState_RoundTrip(t *testing.T) {
|
func TestNetworkState_RoundTrip(t *testing.T) {
|
||||||
path := filepath.Join(t.TempDir(), "network.json")
|
path := filepath.Join(t.TempDir(), "network.json")
|
||||||
|
|
||||||
|
var sign1 [32]byte
|
||||||
|
copy(sign1[:], []byte("0123456789abcdef0123456789abcdef"))
|
||||||
|
|
||||||
state := m.NetworkState{Peers: []m.Peer{
|
state := m.NetworkState{Peers: []m.Peer{
|
||||||
{
|
{
|
||||||
PeerIP: 1,
|
PeerIP: 1,
|
||||||
Version: 7,
|
Version: 7,
|
||||||
Name: "hub",
|
Name: "hub",
|
||||||
Addr4: []byte{10, 11, 12, 1},
|
Addr4: netip.MustParseAddr("10.11.12.1"),
|
||||||
Port: 51820,
|
Port: 51820,
|
||||||
Relay: true,
|
Relay: true,
|
||||||
WGPubKey: make([]byte, 32),
|
WGPubKey: mustKey(t),
|
||||||
SignPubKey: make([]byte, 32),
|
SignPubKey: sign1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
PeerIP: 10,
|
PeerIP: 10,
|
||||||
Version: 3,
|
Version: 3,
|
||||||
Name: "laptop",
|
Name: "laptop",
|
||||||
Addr4: []byte{10, 11, 12, 10},
|
Addr4: netip.MustParseAddr("10.11.12.10"),
|
||||||
Port: 51820,
|
Port: 51820,
|
||||||
WGPubKey: []byte("0123456789abcdef0123456789abcdef"),
|
WGPubKey: mustKey(t),
|
||||||
SignPubKey: []byte("fedcba9876543210fedcba9876543210"),
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
|
"vppn/m"
|
||||||
"vppn/peer/wginterface"
|
"vppn/peer/wginterface"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ func New(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pingCh := make(chan PingEvent)
|
pingCh := make(chan PingEvent)
|
||||||
hubAddCh := make(chan HubPeer)
|
hubAddCh := make(chan m.Peer)
|
||||||
hubRemoveCh := make(chan wgtypes.Key)
|
hubRemoveCh := make(chan wgtypes.Key)
|
||||||
multicastCh := make(chan MulticastEvent)
|
multicastCh := make(chan MulticastEvent)
|
||||||
|
|
||||||
|
|||||||
@@ -8,33 +8,31 @@ import (
|
|||||||
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
|
"vppn/m"
|
||||||
"vppn/peer/control"
|
"vppn/peer/control"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (a *App) onAddPeer(p HubPeer) {
|
func (a *App) onAddPeer(p m.Peer) {
|
||||||
a.onRemovePeer(p.PubKey)
|
a.onRemovePeer(p.WGPubKey)
|
||||||
|
|
||||||
|
octets := a.vpnNet.Addr().As4()
|
||||||
|
octets[3] = p.PeerIP
|
||||||
|
vpnIP := netip.AddrFrom4(octets)
|
||||||
|
|
||||||
peer := &Peer{
|
peer := &Peer{
|
||||||
wgPeer: wgtypes.Peer{PublicKey: p.PubKey},
|
wgPeer: wgtypes.Peer{PublicKey: p.WGPubKey},
|
||||||
VPNIP: p.VPNIP,
|
VPNIP: vpnIP,
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
IsRelay: p.IsRelay,
|
IsRelay: p.Relay,
|
||||||
IsPublic: p.IsPublic,
|
IsPublic: p.IsPublic(),
|
||||||
Endpoint4: p.EndpointV4,
|
Endpoint4: p.Endpoint4(),
|
||||||
Endpoint6: p.EndpointV6,
|
Endpoint6: p.Endpoint6(),
|
||||||
RTT: time.Duration(math.MaxInt64) * time.Nanosecond,
|
RTT: time.Duration(math.MaxInt64) * time.Nanosecond,
|
||||||
Role: roleFor(a.isPublic, a.vpnIP, p),
|
Role: roleFor(a.isPublic, a.vpnIP, p.IsPublic(), vpnIP),
|
||||||
SignPubKey: p.SignPubKey,
|
SignPubKey: p.SignPubKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint := peer.PreferredEndpoint()
|
a.peersByKey[p.WGPubKey] = peer
|
||||||
if peer.IsPublic && !endpoint.IsValid() {
|
|
||||||
// The peer is misconfigured.
|
|
||||||
// TODO: Log here.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
a.peersByKey[p.PubKey] = peer
|
|
||||||
a.peersByIP[peer.VPNIP] = peer
|
a.peersByIP[peer.VPNIP] = peer
|
||||||
defer a.updateHosts()
|
defer a.updateHosts()
|
||||||
|
|
||||||
@@ -50,7 +48,7 @@ func (a *App) onAddPeer(p HubPeer) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
a.devAddDirect(peer, endpoint)
|
a.devAddDirect(peer, peer.PreferredEndpoint())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) onRemovePeer(key wgtypes.Key) {
|
func (a *App) onRemovePeer(key wgtypes.Key) {
|
||||||
@@ -104,12 +102,12 @@ func preferredEndpoint(v4, v6 netip.AddrPort) netip.AddrPort {
|
|||||||
return v6
|
return v6
|
||||||
}
|
}
|
||||||
|
|
||||||
func roleFor(selfIsPublic bool, selfIP netip.Addr, p HubPeer) control.Role {
|
func roleFor(selfIsPublic bool, selfIP netip.Addr, peerIsPublic bool, peerVPNIP netip.Addr) control.Role {
|
||||||
if !selfIsPublic && p.IsPublic {
|
if !selfIsPublic && peerIsPublic {
|
||||||
return control.Client
|
return control.Client
|
||||||
}
|
}
|
||||||
if selfIsPublic && !p.IsPublic {
|
if selfIsPublic && !peerIsPublic {
|
||||||
return control.Server
|
return control.Server
|
||||||
}
|
}
|
||||||
return control.RoleFor(selfIP, p.VPNIP)
|
return control.RoleFor(selfIP, peerVPNIP)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
|
"vppn/m"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustKey(t *testing.T) wgtypes.Key {
|
func mustKey(t *testing.T) wgtypes.Key {
|
||||||
@@ -25,13 +27,13 @@ func TestOnAddPeer(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
setup func(a *App, key wgtypes.Key)
|
setup func(a *App, key wgtypes.Key)
|
||||||
peer func(key wgtypes.Key) HubPeer
|
peer func(key wgtypes.Key) m.Peer
|
||||||
check func(t *testing.T, a *App, dev *fakeWGDevice, key wgtypes.Key)
|
check func(t *testing.T, a *App, dev *fakeWGDevice, key wgtypes.Key)
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "non-public peer registered in WG via AddPeer",
|
name: "non-public peer registered in WG via AddPeer",
|
||||||
peer: func(k wgtypes.Key) HubPeer {
|
peer: func(k wgtypes.Key) m.Peer {
|
||||||
return HubPeer{PubKey: k, VPNIP: peerVPNIP}
|
return m.Peer{WGPubKey: k, PeerIP: 2}
|
||||||
},
|
},
|
||||||
check: func(t *testing.T, a *App, dev *fakeWGDevice, key wgtypes.Key) {
|
check: func(t *testing.T, a *App, dev *fakeWGDevice, key wgtypes.Key) {
|
||||||
p := a.peersByKey[key]
|
p := a.peersByKey[key]
|
||||||
@@ -49,8 +51,8 @@ func TestOnAddPeer(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "public peer with endpoint registered via AddDirect",
|
name: "public peer with endpoint registered via AddDirect",
|
||||||
peer: func(k wgtypes.Key) HubPeer {
|
peer: func(k wgtypes.Key) m.Peer {
|
||||||
return HubPeer{PubKey: k, VPNIP: peerVPNIP, IsPublic: true, EndpointV4: ep1}
|
return m.Peer{WGPubKey: k, PeerIP: 2, Addr4: ep1.Addr(), Port: ep1.Port()}
|
||||||
},
|
},
|
||||||
check: func(t *testing.T, a *App, dev *fakeWGDevice, key wgtypes.Key) {
|
check: func(t *testing.T, a *App, dev *fakeWGDevice, key wgtypes.Key) {
|
||||||
p := a.peersByKey[key]
|
p := a.peersByKey[key]
|
||||||
@@ -60,28 +62,13 @@ func TestOnAddPeer(t *testing.T) {
|
|||||||
dev.AssertAddDirect(t, 0, p.PubKey(), ep1, p.VPNIP)
|
dev.AssertAddDirect(t, 0, p.PubKey(), ep1, p.VPNIP)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "public peer with no endpoint is dropped",
|
|
||||||
peer: func(k wgtypes.Key) HubPeer {
|
|
||||||
return HubPeer{PubKey: k, VPNIP: peerVPNIP, IsPublic: true}
|
|
||||||
},
|
|
||||||
check: func(t *testing.T, a *App, dev *fakeWGDevice, key wgtypes.Key) {
|
|
||||||
if a.peersByKey[key] != nil {
|
|
||||||
t.Fatal("peer should not be in peersByKey")
|
|
||||||
}
|
|
||||||
if a.peersByIP[peerVPNIP] != nil {
|
|
||||||
t.Fatal("peer should not be in peersByIP")
|
|
||||||
}
|
|
||||||
dev.AssertNoCalls(t)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "re-add removes old WG entry before adding new one",
|
name: "re-add removes old WG entry before adding new one",
|
||||||
setup: func(a *App, key wgtypes.Key) {
|
setup: func(a *App, key wgtypes.Key) {
|
||||||
a.onAddPeer(HubPeer{PubKey: key, VPNIP: peerVPNIP, IsPublic: true, EndpointV4: ep1})
|
a.onAddPeer(m.Peer{WGPubKey: key, PeerIP: 2, Addr4: ep1.Addr(), Port: ep1.Port()})
|
||||||
},
|
},
|
||||||
peer: func(k wgtypes.Key) HubPeer {
|
peer: func(k wgtypes.Key) m.Peer {
|
||||||
return HubPeer{PubKey: k, VPNIP: peerVPNIP, IsPublic: true, EndpointV4: ep2}
|
return m.Peer{WGPubKey: k, PeerIP: 2, Addr4: ep2.Addr(), Port: ep2.Port()}
|
||||||
},
|
},
|
||||||
check: func(t *testing.T, a *App, dev *fakeWGDevice, key wgtypes.Key) {
|
check: func(t *testing.T, a *App, dev *fakeWGDevice, key wgtypes.Key) {
|
||||||
if len(dev.Calls) != 2 {
|
if len(dev.Calls) != 2 {
|
||||||
@@ -135,7 +122,7 @@ func TestOnRemovePeer(t *testing.T) {
|
|||||||
name: "StateRelayed peer removed from maps with RemovePeer",
|
name: "StateRelayed peer removed from maps with RemovePeer",
|
||||||
setup: func(t *testing.T, a *App) wgtypes.Key {
|
setup: func(t *testing.T, a *App) wgtypes.Key {
|
||||||
key := mustKey(t)
|
key := mustKey(t)
|
||||||
a.onAddPeer(HubPeer{PubKey: key, VPNIP: netip.MustParseAddr("10.0.0.2")})
|
a.onAddPeer(m.Peer{WGPubKey: key, PeerIP: 2})
|
||||||
return key
|
return key
|
||||||
},
|
},
|
||||||
check: func(t *testing.T, a *App, dev *fakeWGDevice) {
|
check: func(t *testing.T, a *App, dev *fakeWGDevice) {
|
||||||
@@ -152,7 +139,7 @@ func TestOnRemovePeer(t *testing.T) {
|
|||||||
name: "StateDirect peer removed from maps with RemovePeer",
|
name: "StateDirect peer removed from maps with RemovePeer",
|
||||||
setup: func(t *testing.T, a *App) wgtypes.Key {
|
setup: func(t *testing.T, a *App) wgtypes.Key {
|
||||||
key := mustKey(t)
|
key := mustKey(t)
|
||||||
a.onAddPeer(HubPeer{PubKey: key, VPNIP: netip.MustParseAddr("10.0.0.2"), IsPublic: true, EndpointV4: ep1})
|
a.onAddPeer(m.Peer{WGPubKey: key, PeerIP: 2, Addr4: ep1.Addr(), Port: ep1.Port()})
|
||||||
return key
|
return key
|
||||||
},
|
},
|
||||||
check: func(t *testing.T, a *App, dev *fakeWGDevice) {
|
check: func(t *testing.T, a *App, dev *fakeWGDevice) {
|
||||||
|
|||||||
Reference in New Issue
Block a user