221 lines
5.4 KiB
Go
221 lines
5.4 KiB
Go
package peer
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/netip"
|
|
"net/url"
|
|
"os"
|
|
"vppn/m"
|
|
|
|
"git.crumpington.com/lib/go/flock"
|
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
|
)
|
|
|
|
type peerMain struct {
|
|
Globals
|
|
netName string
|
|
holePunch *HolePunch
|
|
controlServer *ControlServer
|
|
endpointReporter *EndpointReporter // non-nil on relay peers only
|
|
hubPoller *HubPoller
|
|
lockFile *os.File
|
|
}
|
|
|
|
func newPeerMain(args mainArgs) *peerMain {
|
|
logf := func(s string, args ...any) {
|
|
log.Printf("[Main] "+s, args...)
|
|
}
|
|
|
|
if err := os.MkdirAll(configDir(args.NetName), 0700); err != nil {
|
|
log.Fatalf("Failed to create config directory: %v", err)
|
|
}
|
|
|
|
lockFile, err := flock.TryLock(lockFilePath(args.NetName))
|
|
if err != nil {
|
|
log.Fatalf("Failed to open lock file: %v", err)
|
|
}
|
|
if lockFile == nil {
|
|
log.Fatalf("Failed to obtain file lock.")
|
|
}
|
|
|
|
config, err := loadPeerConfig(args.NetName)
|
|
if err != nil {
|
|
logf("Failed to load configuration: %v", err)
|
|
logf("Initializing...")
|
|
initPeerWithHub(args)
|
|
|
|
config, err = loadPeerConfig(args.NetName)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load configuration: %v", err)
|
|
}
|
|
}
|
|
|
|
state, err := loadNetworkState(args.NetName)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load network state: %v", err)
|
|
}
|
|
|
|
wgPrivKey, err := wgtypes.ParseKey(config.WGPrivKey)
|
|
if err != nil {
|
|
log.Fatalf("Failed to parse WireGuard private key: %v", err)
|
|
}
|
|
|
|
localPeer := state.Peers[config.LocalPeerIP]
|
|
var listenPort int
|
|
if localPeer != nil {
|
|
listenPort = int(localPeer.Port1)
|
|
}
|
|
|
|
vpnIP := netip.AddrFrom4([4]byte{
|
|
config.Network[0],
|
|
config.Network[1],
|
|
config.Network[2],
|
|
config.LocalPeerIP,
|
|
})
|
|
|
|
wgClient, err := createWGDevice(args.NetName, wgPrivKey, listenPort, vpnIP, config.Network)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create WireGuard device: %v", err)
|
|
}
|
|
|
|
for _, p := range state.Peers {
|
|
if p == nil || !p.Relay || p.PeerIP == config.LocalPeerIP {
|
|
continue
|
|
}
|
|
if len(p.WGPubKey) != wgtypes.KeyLen || len(p.PublicIP1) == 0 || p.Port1 == 0 {
|
|
continue
|
|
}
|
|
relayPubKey, err := wgtypes.NewKey(p.WGPubKey)
|
|
if err != nil {
|
|
logf("Invalid relay WG key: %v", err)
|
|
continue
|
|
}
|
|
relayIP, ok := netip.AddrFromSlice(p.PublicIP1)
|
|
if !ok {
|
|
continue
|
|
}
|
|
relayEndpoint := netip.AddrPortFrom(relayIP.Unmap(), p.Port1)
|
|
if err := applyBaseConfig(wgClient, args.NetName, relayPubKey, relayEndpoint, config.Network); err != nil {
|
|
logf("Failed to apply relay base config: %v", err)
|
|
}
|
|
break
|
|
}
|
|
|
|
g := NewGlobals(config, netip.AddrPort{})
|
|
g.WGPrivKey = wgPrivKey
|
|
g.WGClient = wgClient
|
|
g.WGDevName = args.NetName
|
|
|
|
holePunch := NewHolePunch(g)
|
|
|
|
controlServer, err := NewControlServer(g, holePunch, args.NetName)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create control server: %v", err)
|
|
}
|
|
|
|
var endpointReporter *EndpointReporter
|
|
if localPeer != nil && localPeer.Relay {
|
|
if err := enableForwarding(args.NetName); err != nil {
|
|
log.Fatalf("Failed to enable IP forwarding: %v", err)
|
|
}
|
|
endpointReporter = NewEndpointReporter(g, controlServer, args.NetName)
|
|
}
|
|
|
|
hubPoller, err := NewHubPoller(g, holePunch, args.NetName, args.HubAddress, args.APIKey)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create hub poller: %v", err)
|
|
}
|
|
|
|
go runStatusServer(g, statusSocketPath(args.NetName))
|
|
|
|
return &peerMain{
|
|
Globals: g,
|
|
netName: args.NetName,
|
|
holePunch: holePunch,
|
|
controlServer: controlServer,
|
|
endpointReporter: endpointReporter,
|
|
hubPoller: hubPoller,
|
|
lockFile: lockFile,
|
|
}
|
|
}
|
|
|
|
func (p *peerMain) Run() {
|
|
go p.controlServer.Run()
|
|
if p.endpointReporter != nil {
|
|
go p.endpointReporter.Run()
|
|
}
|
|
go RunMCWriter(p.Globals)
|
|
go RunMCReader(p.Globals, p.holePunch, p.netName)
|
|
go p.hubPoller.Run()
|
|
select {}
|
|
}
|
|
|
|
func initPeerWithHub(args mainArgs) {
|
|
privKey := generateWGKey()
|
|
pubKey := privKey.PublicKey()
|
|
|
|
initURL, err := url.Parse(args.HubAddress)
|
|
if err != nil {
|
|
log.Fatalf("Failed to parse hub URL: %v", err)
|
|
}
|
|
initURL.Path = "/peer/init/"
|
|
|
|
initArgs := m.PeerInitArgs{
|
|
WGPubKey: pubKey[:],
|
|
}
|
|
|
|
buf := &bytes.Buffer{}
|
|
if err := json.NewEncoder(buf).Encode(initArgs); err != nil {
|
|
log.Fatalf("Failed to encode init args: %v", err)
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodPost, initURL.String(), buf)
|
|
if err != nil {
|
|
log.Fatalf("Failed to construct request: %v", err)
|
|
}
|
|
req.SetBasicAuth("", args.APIKey)
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
log.Fatalf("Failed to init with hub: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Fatalf("Failed to read response body: %v", err)
|
|
}
|
|
|
|
if resp.StatusCode == http.StatusConflict {
|
|
log.Fatalf("WireGuard key already registered (HTTP 409). Delete and re-create the peer to re-register.")
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
log.Fatalf("Hub returned unexpected status %d: %s", resp.StatusCode, data)
|
|
}
|
|
|
|
initResp := m.PeerInitResp{}
|
|
if err := json.Unmarshal(data, &initResp); err != nil {
|
|
log.Fatalf("Failed to parse configuration: %v\n%s", err, data)
|
|
}
|
|
|
|
config := LocalConfig{
|
|
LocalPeerIP: initResp.PeerIP,
|
|
Network: initResp.Network,
|
|
WGPrivKey: privKey.String(),
|
|
}
|
|
|
|
if err := storeNetworkState(args.NetName, initResp.NetworkState); err != nil {
|
|
log.Fatalf("Failed to store network state: %v", err)
|
|
}
|
|
|
|
if err := storePeerConfig(args.NetName, config); err != nil {
|
|
log.Fatalf("Failed to store configuration: %v", err)
|
|
}
|
|
|
|
log.Print("Initialization successful.")
|
|
}
|