WIP
This commit is contained in:
@@ -147,6 +147,7 @@ func (a *API) Network_List() ([]*Network, error) {
|
|||||||
func (a *API) Peer_CreateNew(p *Peer) error {
|
func (a *API) Peer_CreateNew(p *Peer) error {
|
||||||
p.Version = idgen.NextID(0)
|
p.Version = idgen.NextID(0)
|
||||||
p.WGPubKey = []byte{}
|
p.WGPubKey = []byte{}
|
||||||
|
p.SignPubKey = []byte{}
|
||||||
p.APIKey = idgen.NewToken()
|
p.APIKey = idgen.NewToken()
|
||||||
|
|
||||||
return db.Peer_Insert(a.db, p)
|
return db.Peer_Insert(a.db, p)
|
||||||
@@ -158,6 +159,7 @@ func (a *API) Peer_Init(peer *Peer, args m.PeerInitArgs) error {
|
|||||||
|
|
||||||
peer.Version = idgen.NextID(0)
|
peer.Version = idgen.NextID(0)
|
||||||
peer.WGPubKey = args.WGPubKey
|
peer.WGPubKey = args.WGPubKey
|
||||||
|
peer.SignPubKey = args.SignPubKey
|
||||||
|
|
||||||
return db.Peer_UpdateFull(a.db, peer)
|
return db.Peer_UpdateFull(a.db, peer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -339,19 +339,20 @@ func Network_List(
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
NetworkID int64
|
NetworkID int64
|
||||||
PeerIP byte
|
PeerIP byte
|
||||||
Version int64
|
Version int64
|
||||||
APIKey string
|
APIKey string
|
||||||
Name string
|
Name string
|
||||||
Addr4 []byte
|
Addr4 []byte
|
||||||
Addr6 []byte
|
Addr6 []byte
|
||||||
Port uint16
|
Port uint16
|
||||||
Relay bool
|
Relay bool
|
||||||
WGPubKey []byte
|
WGPubKey []byte
|
||||||
|
SignPubKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
const Peer_SelectQuery = "SELECT NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey FROM peers"
|
const Peer_SelectQuery = "SELECT NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey FROM peers"
|
||||||
|
|
||||||
func Peer_Insert(
|
func Peer_Insert(
|
||||||
tx TX,
|
tx TX,
|
||||||
@@ -362,7 +363,7 @@ func Peer_Insert(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.Exec("INSERT INTO peers(NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey) VALUES(?,?,?,?,?,?,?,?,?,?)", row.NetworkID, row.PeerIP, row.Version, row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey)
|
_, err = tx.Exec("INSERT INTO peers(NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey) VALUES(?,?,?,?,?,?,?,?,?,?,?)", row.NetworkID, row.PeerIP, row.Version, row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey, row.SignPubKey)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +404,7 @@ func Peer_UpdateFull(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,Addr4=?,Addr6=?,Port=?,Relay=?,WGPubKey=? WHERE NetworkID=? AND PeerIP=?", row.Version, row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey, row.NetworkID, row.PeerIP)
|
result, err := tx.Exec("UPDATE peers SET Version=?,APIKey=?,Name=?,Addr4=?,Addr6=?,Port=?,Relay=?,WGPubKey=?,SignPubKey=? WHERE NetworkID=? AND PeerIP=?", row.Version, row.APIKey, row.Name, row.Addr4, row.Addr6, row.Port, row.Relay, row.WGPubKey, row.SignPubKey, row.NetworkID, row.PeerIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -455,8 +456,8 @@ func Peer_Get(
|
|||||||
err error,
|
err error,
|
||||||
) {
|
) {
|
||||||
row = &Peer{}
|
row = &Peer{}
|
||||||
r := tx.QueryRow("SELECT NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey FROM peers WHERE NetworkID=? AND PeerIP=?", NetworkID, PeerIP)
|
r := tx.QueryRow("SELECT NetworkID,PeerIP,Version,APIKey,Name,Addr4,Addr6,Port,Relay,WGPubKey,SignPubKey FROM peers WHERE NetworkID=? AND PeerIP=?", NetworkID, PeerIP)
|
||||||
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey); err != nil {
|
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey); err != nil {
|
||||||
row = nil
|
row = nil
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -472,7 +473,7 @@ func Peer_GetWhere(
|
|||||||
) {
|
) {
|
||||||
row = &Peer{}
|
row = &Peer{}
|
||||||
r := tx.QueryRow(query, args...)
|
r := tx.QueryRow(query, args...)
|
||||||
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey); err != nil {
|
if err = r.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey); err != nil {
|
||||||
row = nil
|
row = nil
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -494,7 +495,7 @@ func Peer_Iterate(
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
row := &Peer{}
|
row := &Peer{}
|
||||||
err := rows.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey)
|
err := rows.Scan(&row.NetworkID, &row.PeerIP, &row.Version, &row.APIKey, &row.Name, &row.Addr4, &row.Addr6, &row.Port, &row.Relay, &row.WGPubKey, &row.SignPubKey)
|
||||||
if !yield(row, err) {
|
if !yield(row, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,5 +19,6 @@ TABLE peers OF Peer (
|
|||||||
Addr6 []byte,
|
Addr6 []byte,
|
||||||
Port uint16,
|
Port uint16,
|
||||||
Relay bool,
|
Relay bool,
|
||||||
WGPubKey []byte NoUpdate
|
WGPubKey []byte NoUpdate,
|
||||||
|
SignPubKey []byte NoUpdate
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ CREATE TABLE peers (
|
|||||||
Port INTEGER NOT NULL,
|
Port INTEGER NOT NULL,
|
||||||
Relay INTEGER NOT NULL DEFAULT 0, -- Boolean if peer will forward packets.
|
Relay INTEGER NOT NULL DEFAULT 0, -- Boolean if peer will forward packets.
|
||||||
WGPubKey BLOB NOT NULL,
|
WGPubKey BLOB NOT NULL,
|
||||||
|
SignPubKey BLOB NOT NULL
|
||||||
PRIMARY KEY(NetworkID, PeerIP)
|
PRIMARY KEY(NetworkID, PeerIP)
|
||||||
) WITHOUT ROWID;
|
) WITHOUT ROWID;
|
||||||
|
|||||||
@@ -313,6 +313,15 @@ func (a *App) _peerInit(peer *api.Peer, w http.ResponseWriter, r *http.Request)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(args.WGPubKey) != 32 {
|
||||||
|
http.Error(w, "invalid WGPubKey", http.StatusBadRequest)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(args.SignPubKey) != 32 {
|
||||||
|
http.Error(w, "invalid SignPubKey", http.StatusBadRequest)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
net, err := a.api.Network_Get(peer.NetworkID)
|
net, err := a.api.Network_Get(peer.NetworkID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -353,14 +362,15 @@ func (a *App) peersArray(networkID int64) (peers [256]*m.Peer, err error) {
|
|||||||
for _, p := range l {
|
for _, p := range l {
|
||||||
if len(p.WGPubKey) != 0 {
|
if len(p.WGPubKey) != 0 {
|
||||||
peers[p.PeerIP] = &m.Peer{
|
peers[p.PeerIP] = &m.Peer{
|
||||||
PeerIP: p.PeerIP,
|
PeerIP: p.PeerIP,
|
||||||
Version: p.Version,
|
Version: p.Version,
|
||||||
Name: p.Name,
|
Name: p.Name,
|
||||||
Addr4: p.Addr4,
|
Addr4: p.Addr4,
|
||||||
Addr6: p.Addr6,
|
Addr6: p.Addr6,
|
||||||
Port: p.Port,
|
Port: p.Port,
|
||||||
Relay: p.Relay,
|
Relay: p.Relay,
|
||||||
WGPubKey: p.WGPubKey,
|
WGPubKey: p.WGPubKey,
|
||||||
|
SignPubKey: p.SignPubKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
package m
|
package m
|
||||||
|
|
||||||
type PeerInitArgs struct {
|
type PeerInitArgs struct {
|
||||||
WGPubKey []byte
|
WGPubKey []byte
|
||||||
|
SignPubKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type PeerInitResp struct {
|
type PeerInitResp struct {
|
||||||
@@ -18,8 +19,9 @@ type Peer struct {
|
|||||||
Addr4 []byte
|
Addr4 []byte
|
||||||
Addr6 []byte
|
Addr6 []byte
|
||||||
Port uint16
|
Port uint16
|
||||||
Relay bool
|
Relay bool
|
||||||
WGPubKey []byte
|
WGPubKey []byte
|
||||||
|
SignPubKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetworkState struct {
|
type NetworkState struct {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ type HubPeer struct {
|
|||||||
IsPublic bool
|
IsPublic bool
|
||||||
EndpointV4 netip.AddrPort // zero if none
|
EndpointV4 netip.AddrPort // zero if none
|
||||||
EndpointV6 netip.AddrPort // zero if none
|
EndpointV6 netip.AddrPort // zero if none
|
||||||
|
SignPubKey [32]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type PingEvent struct {
|
type PingEvent struct {
|
||||||
@@ -36,10 +37,10 @@ type PingEvent struct {
|
|||||||
ping control.Ping
|
ping control.Ping
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MulticastEvent carries a raw signed beacon for verification in the event loop.
|
||||||
type MulticastEvent struct {
|
type MulticastEvent struct {
|
||||||
pubKey wgtypes.Key
|
signed []byte // nacl/sign signed beacon (64-byte sig || 35-byte payload)
|
||||||
vpnIP netip.Addr
|
src netip.Addr // physical LAN source address
|
||||||
endpoint netip.AddrPort
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// App is the peer application. All mutable state lives here and is
|
// App is the peer application. All mutable state lives here and is
|
||||||
|
|||||||
@@ -71,8 +71,14 @@ func (hp *HubPoller) poll() {
|
|||||||
log.Printf("[HubPoller] fetch: %v", err)
|
log.Printf("[HubPoller] fetch: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
log.Printf("[HubPoller] unexpected status %d", resp.StatusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
_ = resp.Body.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[HubPoller] read body: %v", err)
|
log.Printf("[HubPoller] read body: %v", err)
|
||||||
return
|
return
|
||||||
@@ -93,7 +99,7 @@ func (hp *HubPoller) apply(state m.NetworkState) {
|
|||||||
netAddr := hp.vpnNet.Addr().As4()
|
netAddr := hp.vpnNet.Addr().As4()
|
||||||
|
|
||||||
for _, p := range state.Peers {
|
for _, p := range state.Peers {
|
||||||
if p == nil || len(p.WGPubKey) != wgtypes.KeyLen {
|
if p == nil || len(p.WGPubKey) != wgtypes.KeyLen || len(p.SignPubKey) != 32 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +144,8 @@ func hubPeerFrom(pubKey wgtypes.Key, vpnIP netip.Addr, p *m.Peer) HubPeer {
|
|||||||
ep6 = netip.AddrPortFrom(addr, p.Port)
|
ep6 = netip.AddrPortFrom(addr, p.Port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var signPubKey [32]byte
|
||||||
|
copy(signPubKey[:], p.SignPubKey)
|
||||||
return HubPeer{
|
return HubPeer{
|
||||||
PubKey: pubKey,
|
PubKey: pubKey,
|
||||||
VPNIP: vpnIP,
|
VPNIP: vpnIP,
|
||||||
@@ -145,5 +153,6 @@ func hubPeerFrom(pubKey wgtypes.Key, vpnIP netip.Addr, p *m.Peer) HubPeer {
|
|||||||
IsPublic: ep4.IsValid() || ep6.IsValid(),
|
IsPublic: ep4.IsValid() || ep6.IsValid(),
|
||||||
EndpointV4: ep4,
|
EndpointV4: ep4,
|
||||||
EndpointV6: ep6,
|
EndpointV6: ep6,
|
||||||
|
SignPubKey: signPubKey,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
peer/init.go
31
peer/init.go
@@ -2,6 +2,7 @@ package peer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/nacl/sign"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
"vppn/m"
|
"vppn/m"
|
||||||
@@ -19,6 +21,7 @@ import (
|
|||||||
// loaded on every subsequent run.
|
// loaded on every subsequent run.
|
||||||
type LocalState struct {
|
type LocalState struct {
|
||||||
PrivKey wgtypes.Key
|
PrivKey wgtypes.Key
|
||||||
|
SignKey [64]byte // nacl/sign Ed25519 private key
|
||||||
VPNIP netip.Addr
|
VPNIP netip.Addr
|
||||||
VPNNet netip.Prefix
|
VPNNet netip.Prefix
|
||||||
WGPort uint16
|
WGPort uint16
|
||||||
@@ -28,7 +31,8 @@ type LocalState struct {
|
|||||||
|
|
||||||
// localStateJSON is the on-disk representation.
|
// localStateJSON is the on-disk representation.
|
||||||
type localStateJSON struct {
|
type localStateJSON struct {
|
||||||
PrivKey string `json:"priv_key"` // standard base64
|
PrivKey string `json:"priv_key"` // standard base64
|
||||||
|
SignKey string `json:"sign_key"` // standard base64
|
||||||
VPNIP netip.Addr `json:"vpn_ip"`
|
VPNIP netip.Addr `json:"vpn_ip"`
|
||||||
VPNNet netip.Prefix `json:"vpn_net"`
|
VPNNet netip.Prefix `json:"vpn_net"`
|
||||||
WGPort uint16 `json:"wg_port"`
|
WGPort uint16 `json:"wg_port"`
|
||||||
@@ -60,8 +64,17 @@ func LoadOrInit(statePath, hubURL, apiKey string) (LocalState, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func initFromHub(hubURL, apiKey string, privKey wgtypes.Key) (LocalState, error) {
|
func initFromHub(hubURL, apiKey string, privKey wgtypes.Key) (LocalState, error) {
|
||||||
pubKey := privKey.PublicKey()
|
wgPubKey := privKey.PublicKey()
|
||||||
body, _ := json.Marshal(m.PeerInitArgs{WGPubKey: pubKey[:]})
|
|
||||||
|
signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return LocalState{}, fmt.Errorf("generate sign key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, _ := json.Marshal(m.PeerInitArgs{
|
||||||
|
WGPubKey: wgPubKey[:],
|
||||||
|
SignPubKey: signPubKey[:],
|
||||||
|
})
|
||||||
|
|
||||||
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 {
|
||||||
@@ -105,6 +118,7 @@ func initFromHub(hubURL, apiKey string, privKey wgtypes.Key) (LocalState, error)
|
|||||||
|
|
||||||
return LocalState{
|
return LocalState{
|
||||||
PrivKey: privKey,
|
PrivKey: privKey,
|
||||||
|
SignKey: *signPrivKey,
|
||||||
VPNIP: vpnIP,
|
VPNIP: vpnIP,
|
||||||
VPNNet: vpnNet,
|
VPNNet: vpnNet,
|
||||||
WGPort: wgPort,
|
WGPort: wgPort,
|
||||||
@@ -126,8 +140,18 @@ func parseLocalState(data []byte) (LocalState, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return LocalState{}, fmt.Errorf("invalid key: %w", err)
|
return LocalState{}, fmt.Errorf("invalid key: %w", err)
|
||||||
}
|
}
|
||||||
|
signKeyBytes, err := base64.StdEncoding.DecodeString(j.SignKey)
|
||||||
|
if err != nil {
|
||||||
|
return LocalState{}, fmt.Errorf("decode sign key: %w", err)
|
||||||
|
}
|
||||||
|
if len(signKeyBytes) != 64 {
|
||||||
|
return LocalState{}, fmt.Errorf("invalid sign key length: %d", len(signKeyBytes))
|
||||||
|
}
|
||||||
|
var signKey [64]byte
|
||||||
|
copy(signKey[:], signKeyBytes)
|
||||||
return LocalState{
|
return LocalState{
|
||||||
PrivKey: key,
|
PrivKey: key,
|
||||||
|
SignKey: signKey,
|
||||||
VPNIP: j.VPNIP,
|
VPNIP: j.VPNIP,
|
||||||
VPNNet: j.VPNNet,
|
VPNNet: j.VPNNet,
|
||||||
WGPort: j.WGPort,
|
WGPort: j.WGPort,
|
||||||
@@ -139,6 +163,7 @@ func parseLocalState(data []byte) (LocalState, error) {
|
|||||||
func saveLocalState(path string, s LocalState) error {
|
func saveLocalState(path string, s LocalState) error {
|
||||||
j := localStateJSON{
|
j := localStateJSON{
|
||||||
PrivKey: base64.StdEncoding.EncodeToString(s.PrivKey[:]),
|
PrivKey: base64.StdEncoding.EncodeToString(s.PrivKey[:]),
|
||||||
|
SignKey: base64.StdEncoding.EncodeToString(s.SignKey[:]),
|
||||||
VPNIP: s.VPNIP,
|
VPNIP: s.VPNIP,
|
||||||
VPNNet: s.VPNNet,
|
VPNNet: s.VPNNet,
|
||||||
WGPort: s.WGPort,
|
WGPort: s.WGPort,
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/nacl/sign"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
mcBeaconLen = 35 // 1 VPN IP byte + 32 WG pubkey + 2 WG listen port
|
mcBeaconLen = 35 // 1 VPN IP byte + 32 WG pubkey + 2 WG listen port
|
||||||
|
mcSignedBeaconLen = sign.Overhead + mcBeaconLen // 64-byte nacl/sign prefix + payload
|
||||||
mcBroadcastInterval = 32 * time.Second
|
mcBroadcastInterval = 32 * time.Second
|
||||||
mcErrorRetryInterval = 16 * time.Second
|
mcErrorRetryInterval = 16 * time.Second
|
||||||
)
|
)
|
||||||
@@ -21,18 +23,23 @@ var mcAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(
|
|||||||
netip.AddrFrom4([4]byte{224, 0, 0, 157}),
|
netip.AddrFrom4([4]byte{224, 0, 0, 157}),
|
||||||
4560))
|
4560))
|
||||||
|
|
||||||
// RunMCWriter broadcasts a beacon on the local multicast group every
|
// RunMCWriter broadcasts a signed beacon on the local multicast group every
|
||||||
// mcBroadcastInterval so that LAN peers can discover our WireGuard endpoint.
|
// mcBroadcastInterval so that LAN peers can discover our WireGuard endpoint.
|
||||||
func RunMCWriter(selfVPNIP netip.Addr, pubKey wgtypes.Key, wgPort uint16) {
|
func RunMCWriter(selfVPNIP netip.Addr, pubKey wgtypes.Key, wgPort uint16, signKey *[64]byte) {
|
||||||
conn, err := net.ListenMulticastUDP("udp", nil, mcAddr)
|
conn, err := net.ListenMulticastUDP("udp", nil, mcAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[MCWriter] bind: %v", err)
|
log.Printf("[MCWriter] bind: %v", err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
beacon := buildBeacon(selfVPNIP, pubKey, wgPort)
|
payload := buildBeacon(selfVPNIP, pubKey, wgPort)
|
||||||
|
signed := sign.Sign(nil, payload, signKey)
|
||||||
|
|
||||||
|
if _, err := conn.WriteToUDP(signed, mcAddr); err != nil {
|
||||||
|
log.Printf("[MCWriter] write: %v", err)
|
||||||
|
}
|
||||||
for range time.Tick(mcBroadcastInterval) {
|
for range time.Tick(mcBroadcastInterval) {
|
||||||
if _, err := conn.WriteToUDP(beacon, mcAddr); err != nil {
|
if _, err := conn.WriteToUDP(signed, mcAddr); err != nil {
|
||||||
log.Printf("[MCWriter] write: %v", err)
|
log.Printf("[MCWriter] write: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +71,7 @@ func runMCReaderInner(vpnNet netip.Prefix, selfVPNIP netip.Addr, ch chan<- Multi
|
|||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
buf := make([]byte, 64)
|
buf := make([]byte, mcSignedBeaconLen+1) // +1 to detect oversized packets
|
||||||
netAddr := vpnNet.Addr().As4()
|
netAddr := vpnNet.Addr().As4()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -73,29 +80,25 @@ func runMCReaderInner(vpnNet netip.Prefix, selfVPNIP netip.Addr, ch chan<- Multi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("read: %w", err)
|
return fmt.Errorf("read: %w", err)
|
||||||
}
|
}
|
||||||
if n != mcBeaconLen {
|
if n != mcSignedBeaconLen {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Peek at VPN IP byte (first byte of payload after 64-byte sig prefix)
|
||||||
|
// to skip our own beacon before verifying.
|
||||||
octets := netAddr
|
octets := netAddr
|
||||||
octets[3] = buf[0]
|
octets[3] = buf[sign.Overhead]
|
||||||
vpnIP := netip.AddrFrom4(octets)
|
vpnIP := netip.AddrFrom4(octets)
|
||||||
if vpnIP == selfVPNIP {
|
if vpnIP == selfVPNIP {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pubKey, err := wgtypes.NewKey(buf[1:33])
|
signed := make([]byte, mcSignedBeaconLen)
|
||||||
if err != nil {
|
copy(signed, buf[:mcSignedBeaconLen])
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
wgPort := binary.BigEndian.Uint16(buf[33:35])
|
|
||||||
endpoint := netip.AddrPortFrom(src.Addr().Unmap(), wgPort)
|
|
||||||
|
|
||||||
ch <- MulticastEvent{
|
ch <- MulticastEvent{
|
||||||
pubKey: pubKey,
|
signed: signed,
|
||||||
vpnIP: vpnIP,
|
src: src.Addr().Unmap(),
|
||||||
endpoint: endpoint,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func New(
|
|||||||
|
|
||||||
go cc.run(pingCh)
|
go cc.run(pingCh)
|
||||||
go poller.Run()
|
go poller.Run()
|
||||||
go RunMCWriter(state.VPNIP, state.PrivKey.PublicKey(), state.WGPort)
|
go RunMCWriter(state.VPNIP, state.PrivKey.PublicKey(), state.WGPort, &state.SignKey)
|
||||||
if !state.IsPublic {
|
if !state.IsPublic {
|
||||||
go RunMCReader(state.VPNNet, state.VPNIP, multicastCh)
|
go RunMCReader(state.VPNNet, state.VPNIP, multicastCh)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,14 +15,15 @@ func (a *App) onAddPeer(p HubPeer) {
|
|||||||
a.onRemovePeer(p.PubKey)
|
a.onRemovePeer(p.PubKey)
|
||||||
|
|
||||||
peer := &Peer{
|
peer := &Peer{
|
||||||
wgPeer: wgtypes.Peer{PublicKey: p.PubKey},
|
wgPeer: wgtypes.Peer{PublicKey: p.PubKey},
|
||||||
VPNIP: p.VPNIP,
|
VPNIP: p.VPNIP,
|
||||||
IsRelay: p.IsRelay,
|
IsRelay: p.IsRelay,
|
||||||
IsPublic: p.IsPublic,
|
IsPublic: p.IsPublic,
|
||||||
Endpoint4: p.EndpointV4,
|
Endpoint4: p.EndpointV4,
|
||||||
Endpoint6: p.EndpointV6,
|
Endpoint6: p.EndpointV6,
|
||||||
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),
|
||||||
|
SignPubKey: p.SignPubKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint := peer.PreferredEndpoint()
|
endpoint := peer.PreferredEndpoint()
|
||||||
|
|||||||
@@ -1,13 +1,29 @@
|
|||||||
package peer
|
package peer
|
||||||
|
|
||||||
import "net/netip"
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/nacl/sign"
|
||||||
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
)
|
||||||
|
|
||||||
func (a *App) onMulticastDiscovery(e MulticastEvent) {
|
func (a *App) onMulticastDiscovery(e MulticastEvent) {
|
||||||
if a.isPublic {
|
if a.isPublic {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
peer, ok := a.peersByKey[e.pubKey]
|
// Peek at the VPN IP byte to find the sender peer before verifying.
|
||||||
|
// nacl/sign prepends a 64-byte signature, so payload starts at offset sign.Overhead.
|
||||||
|
if len(e.signed) != mcSignedBeaconLen {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
netAddr := a.vpnNet.Addr().As4()
|
||||||
|
octets := netAddr
|
||||||
|
octets[3] = e.signed[sign.Overhead]
|
||||||
|
vpnIP := netip.AddrFrom4(octets)
|
||||||
|
|
||||||
|
peer, ok := a.peersByIP[vpnIP]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -16,11 +32,28 @@ func (a *App) onMulticastDiscovery(e MulticastEvent) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payload, ok := sign.Open(nil, e.signed, &peer.SignPubKey)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// payload: [1 VPN IP byte][32 WG pubkey][2 WG port]
|
||||||
|
wgPubKey, err := wgtypes.NewKey(payload[1:33])
|
||||||
|
if err != nil || wgPubKey != peer.PubKey() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wgPort := binary.BigEndian.Uint16(payload[33:35])
|
||||||
|
endpoint := netip.AddrPortFrom(e.src, wgPort)
|
||||||
|
if !endpoint.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var v4, v6 netip.AddrPort
|
var v4, v6 netip.AddrPort
|
||||||
if e.endpoint.Addr().Is4() {
|
if e.src.Is4() {
|
||||||
v4 = e.endpoint
|
v4 = endpoint
|
||||||
} else {
|
} else {
|
||||||
v6 = e.endpoint
|
v6 = endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
a.addProbe(peer, v4, v6)
|
a.addProbe(peer, v4, v6)
|
||||||
|
|||||||
@@ -19,14 +19,15 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Peer struct {
|
type Peer struct {
|
||||||
wgPeer wgtypes.Peer
|
wgPeer wgtypes.Peer
|
||||||
VPNIP netip.Addr // VPN IP address.
|
VPNIP netip.Addr // VPN IP address.
|
||||||
IsRelay bool // Peer is a relay.
|
IsRelay bool // Peer is a relay.
|
||||||
IsPublic bool // Peer has a public IP.
|
IsPublic bool // Peer has a public IP.
|
||||||
Endpoint4 netip.AddrPort // Reported IPv4 endpoint.
|
Endpoint4 netip.AddrPort // Reported IPv4 endpoint.
|
||||||
Endpoint6 netip.AddrPort // Reported IPv6 endpoint.
|
Endpoint6 netip.AddrPort // Reported IPv6 endpoint.
|
||||||
RTT time.Duration // Round-trip time.
|
RTT time.Duration // Round-trip time.
|
||||||
Role control.Role // Client initiates pings; server responds.
|
Role control.Role // Client initiates pings; server responds.
|
||||||
|
SignPubKey [32]byte // nacl/sign public key for verifying multicast beacons.
|
||||||
}
|
}
|
||||||
|
|
||||||
// PubKey is the wireguard public key.
|
// PubKey is the wireguard public key.
|
||||||
|
|||||||
@@ -173,9 +173,12 @@ func (d *Device) RemovePeer(pubKey wgtypes.Key) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableForwarding enables IPv4 forwarding on the interface, required for
|
// EnableForwarding enables IPv4 forwarding globally and on the interface,
|
||||||
// relay peers that forward traffic between VPN peers.
|
// required for relay peers that forward traffic between VPN peers.
|
||||||
func (d *Device) EnableForwarding() error {
|
func (d *Device) EnableForwarding() error {
|
||||||
|
if err := os.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
path := fmt.Sprintf("/proc/sys/net/ipv4/conf/%s/forwarding", d.name)
|
path := fmt.Sprintf("/proc/sys/net/ipv4/conf/%s/forwarding", d.name)
|
||||||
return os.WriteFile(path, []byte("1\n"), 0644)
|
return os.WriteFile(path, []byte("1\n"), 0644)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user