Cleanup - WIP
This commit is contained in:
@@ -130,12 +130,12 @@ 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.Port4)
|
||||
ep4 = netip.AddrPortFrom(addr.Unmap(), p.Port)
|
||||
}
|
||||
}
|
||||
if len(p.Addr6) > 0 {
|
||||
if addr, ok := netip.AddrFromSlice(p.Addr6); ok {
|
||||
ep6 = netip.AddrPortFrom(addr, p.Port6)
|
||||
ep6 = netip.AddrPortFrom(addr, p.Port)
|
||||
}
|
||||
}
|
||||
return HubPeer{
|
||||
|
||||
@@ -116,7 +116,7 @@ func (hp *HubPoller) applyPeerConfig(peer *m.Peer) {
|
||||
if peer == nil || len(peer.WGPubKey) != wgtypes.KeyLen {
|
||||
return
|
||||
}
|
||||
if len(peer.Addr4) == 0 || peer.Port4 == 0 {
|
||||
if len(peer.Addr4) == 0 || peer.Port == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ func (hp *HubPoller) applyPeerConfig(peer *m.Peer) {
|
||||
hp.logf("Invalid public IP for peer %d", peer.PeerIP)
|
||||
return
|
||||
}
|
||||
endpoint := netip.AddrPortFrom(ip.Unmap(), peer.Port4)
|
||||
endpoint := netip.AddrPortFrom(ip.Unmap(), peer.Port)
|
||||
|
||||
if peer.Relay {
|
||||
if err := applyBaseConfig(hp.WGClient, hp.WGDevName, pubKey, endpoint, hp.Network); err != nil {
|
||||
|
||||
156
peer/init.go
Normal file
156
peer/init.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"vppn/m"
|
||||
)
|
||||
|
||||
// LocalState is the persisted identity for this peer, written on first run and
|
||||
// loaded on every subsequent run.
|
||||
type LocalState struct {
|
||||
PrivKey wgtypes.Key
|
||||
VPNIP netip.Addr
|
||||
VPNNet netip.Prefix
|
||||
WGPort uint16
|
||||
IsRelay bool
|
||||
IsPublic bool
|
||||
}
|
||||
|
||||
// localStateJSON is the on-disk representation.
|
||||
type localStateJSON struct {
|
||||
PrivKey string `json:"priv_key"` // standard base64
|
||||
VPNIP netip.Addr `json:"vpn_ip"`
|
||||
VPNNet netip.Prefix `json:"vpn_net"`
|
||||
WGPort uint16 `json:"wg_port"`
|
||||
IsRelay bool `json:"is_relay"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
}
|
||||
|
||||
// LoadOrInit loads LocalState from path, or registers with the hub and creates
|
||||
// the file if it doesn't exist.
|
||||
func LoadOrInit(statePath, hubURL, apiKey string) (LocalState, error) {
|
||||
if data, err := os.ReadFile(statePath); err == nil {
|
||||
return parseLocalState(data)
|
||||
}
|
||||
|
||||
privKey, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return LocalState{}, fmt.Errorf("generate key: %w", err)
|
||||
}
|
||||
|
||||
state, err := initFromHub(hubURL, apiKey, privKey)
|
||||
if err != nil {
|
||||
return LocalState{}, err
|
||||
}
|
||||
|
||||
if err := saveLocalState(statePath, state); err != nil {
|
||||
return LocalState{}, fmt.Errorf("save state: %w", err)
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func initFromHub(hubURL, apiKey string, privKey wgtypes.Key) (LocalState, error) {
|
||||
pubKey := privKey.PublicKey()
|
||||
body, _ := json.Marshal(m.PeerInitArgs{WGPubKey: pubKey[:]})
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, hubURL+"/peer/init/", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return LocalState{}, err
|
||||
}
|
||||
req.SetBasicAuth("", apiKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return LocalState{}, fmt.Errorf("hub init: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return LocalState{}, fmt.Errorf("hub init: HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var r m.PeerInitResp
|
||||
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||
return LocalState{}, fmt.Errorf("hub init decode: %w", err)
|
||||
}
|
||||
|
||||
if len(r.Network) != 4 {
|
||||
return LocalState{}, fmt.Errorf("hub init: invalid network %v", r.Network)
|
||||
}
|
||||
|
||||
netAddr := netip.AddrFrom4([4]byte(r.Network))
|
||||
octets := netAddr.As4()
|
||||
octets[3] = r.PeerIP
|
||||
vpnIP := netip.AddrFrom4(octets)
|
||||
vpnNet := netip.PrefixFrom(netAddr, 24)
|
||||
|
||||
var isRelay, isPublic bool
|
||||
var wgPort uint16
|
||||
if self := r.NetworkState.Peers[r.PeerIP]; self != nil {
|
||||
isRelay = self.Relay
|
||||
isPublic = len(self.Addr4) > 0 || len(self.Addr6) > 0
|
||||
wgPort = self.Port
|
||||
}
|
||||
|
||||
return LocalState{
|
||||
PrivKey: privKey,
|
||||
VPNIP: vpnIP,
|
||||
VPNNet: vpnNet,
|
||||
WGPort: wgPort,
|
||||
IsRelay: isRelay,
|
||||
IsPublic: isPublic,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseLocalState(data []byte) (LocalState, error) {
|
||||
var j localStateJSON
|
||||
if err := json.Unmarshal(data, &j); err != nil {
|
||||
return LocalState{}, fmt.Errorf("parse state: %w", err)
|
||||
}
|
||||
keyBytes, err := base64.StdEncoding.DecodeString(j.PrivKey)
|
||||
if err != nil {
|
||||
return LocalState{}, fmt.Errorf("decode key: %w", err)
|
||||
}
|
||||
key, err := wgtypes.NewKey(keyBytes)
|
||||
if err != nil {
|
||||
return LocalState{}, fmt.Errorf("invalid key: %w", err)
|
||||
}
|
||||
return LocalState{
|
||||
PrivKey: key,
|
||||
VPNIP: j.VPNIP,
|
||||
VPNNet: j.VPNNet,
|
||||
WGPort: j.WGPort,
|
||||
IsRelay: j.IsRelay,
|
||||
IsPublic: j.IsPublic,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func saveLocalState(path string, s LocalState) error {
|
||||
j := localStateJSON{
|
||||
PrivKey: base64.StdEncoding.EncodeToString(s.PrivKey[:]),
|
||||
VPNIP: s.VPNIP,
|
||||
VPNNet: s.VPNNet,
|
||||
WGPort: s.WGPort,
|
||||
IsRelay: s.IsRelay,
|
||||
IsPublic: s.IsPublic,
|
||||
}
|
||||
data, err := json.MarshalIndent(j, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, data, 0600)
|
||||
}
|
||||
85
peer/new.go
Normal file
85
peer/new.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
|
||||
"vppn/peer/wginterface"
|
||||
)
|
||||
|
||||
// New constructs an App, creates the WireGuard interface, and starts the
|
||||
// background goroutines (hub poller, multicast, control conn reader).
|
||||
// The caller should invoke Run() to start the event loop.
|
||||
func New(
|
||||
state LocalState,
|
||||
hubURL, apiKey string,
|
||||
ifaceName string,
|
||||
) (*App, error) {
|
||||
a4 := state.VPNIP.As4()
|
||||
if err := wginterface.Create(ifaceName, a4[:], 24); err != nil {
|
||||
return nil, fmt.Errorf("create WG interface: %w", err)
|
||||
}
|
||||
|
||||
dev, err := wginterface.Open(ifaceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open WG device: %w", err)
|
||||
}
|
||||
|
||||
if err := dev.Configure(state.PrivKey, int(state.WGPort)); err != nil {
|
||||
return nil, fmt.Errorf("configure WG device: %w", err)
|
||||
}
|
||||
|
||||
if state.IsRelay {
|
||||
if err := dev.EnableForwarding(); err != nil {
|
||||
return nil, fmt.Errorf("enable forwarding: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cc, err := newUDPControlConn(state.VPNIP, ControlPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("control conn: %w", err)
|
||||
}
|
||||
|
||||
pingCh := make(chan PingEvent)
|
||||
hubAddCh := make(chan HubPeer)
|
||||
hubRemoveCh := make(chan wgtypes.Key)
|
||||
multicastCh := make(chan MulticastEvent)
|
||||
|
||||
poller, err := NewHubPoller(
|
||||
state.VPNIP, state.VPNNet,
|
||||
hubURL, apiKey,
|
||||
hubAddCh, hubRemoveCh,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hub poller: %w", err)
|
||||
}
|
||||
|
||||
go cc.run(pingCh)
|
||||
go poller.Run()
|
||||
go RunMCWriter(state.VPNIP, state.PrivKey.PublicKey(), state.WGPort)
|
||||
if !state.IsPublic {
|
||||
go RunMCReader(state.VPNNet, state.VPNIP, multicastCh)
|
||||
}
|
||||
|
||||
return &App{
|
||||
vpnIP: state.VPNIP,
|
||||
vpnNet: state.VPNNet,
|
||||
privKey: state.PrivKey,
|
||||
pubKey: state.PrivKey.PublicKey(),
|
||||
isRelay: state.IsRelay,
|
||||
isPublic: state.IsPublic,
|
||||
|
||||
dev: dev,
|
||||
controlConn: cc,
|
||||
|
||||
peersByKey: make(map[wgtypes.Key]*Peer),
|
||||
peersByIP: make(map[netip.Addr]*Peer),
|
||||
|
||||
hubAddCh: hubAddCh,
|
||||
hubRemoveCh: hubRemoveCh,
|
||||
pingCh: pingCh,
|
||||
multicastCh: multicastCh,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user