Bug fixes, removed old files.
This commit is contained in:
@@ -22,6 +22,8 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// TODO: Acquire flock on lock file.
|
||||
|
||||
statePath := networkStatePath(*name)
|
||||
|
||||
state, err := peer.LoadOrInit(statePath, *hub, *apiKey)
|
||||
|
||||
@@ -22,10 +22,9 @@
|
||||
<tr>
|
||||
<th>PeerIP</th>
|
||||
<th>Name</th>
|
||||
<th>Public IP 1</th>
|
||||
<th>Port 1</th>
|
||||
<th>Public IP 2</th>
|
||||
<th>Port 2</th>
|
||||
<th>IPv4</th>
|
||||
<th>IPv6</th>
|
||||
<th>Port</th>
|
||||
<th>Relay</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -38,10 +37,9 @@
|
||||
</a>
|
||||
</td>
|
||||
<td>{{.Name}}</td>
|
||||
<td>{{ipToString .PublicIP1}}</td>
|
||||
<td>{{.Port1}}</td>
|
||||
<td>{{ipToString .PublicIP2}}</td>
|
||||
<td>{{.Port2}}</td>
|
||||
<td>{{ipToString .Addr4}}</td>
|
||||
<td>{{ipToString .Addr6}}</td>
|
||||
<td>{{.Port}}</td>
|
||||
<td>{{if .Relay}}T{{else}}F{{end}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -12,20 +12,16 @@
|
||||
<input type="text" name="Name">
|
||||
</p>
|
||||
<p>
|
||||
<label>Public IP 1</label><br>
|
||||
<input type="text" name="PublicIP1">
|
||||
<label>IPv4 Address (optional)</label><br>
|
||||
<input type="text" name="Addr4">
|
||||
</p>
|
||||
<p>
|
||||
<label>Port 1</label><br>
|
||||
<input type="number" name="Port1" value="456">
|
||||
<label>IPv6 Address (optional)</label><br>
|
||||
<input type="text" name="Addr6">
|
||||
</p>
|
||||
<p>
|
||||
<label>Public IP 2 (optional)</label><br>
|
||||
<input type="text" name="PublicIP2">
|
||||
</p>
|
||||
<p>
|
||||
<label>Port 2</label><br>
|
||||
<input type="number" name="Port2" value="0">
|
||||
<label>WireGuard Port</label><br>
|
||||
<input type="number" name="Port" value="51820">
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
|
||||
@@ -12,20 +12,16 @@
|
||||
<input type="text" name="Name" value="{{.Name}}">
|
||||
</p>
|
||||
<p>
|
||||
<label>Public IP 1</label><br>
|
||||
<input type="text" name="PublicIP1" value="{{ipToString .PublicIP1}}">
|
||||
<label>IPv4 Address (optional)</label><br>
|
||||
<input type="text" name="Addr4" value="{{ipToString .Addr4}}">
|
||||
</p>
|
||||
<p>
|
||||
<label>Port 1</label><br>
|
||||
<input type="number" name="Port1" value="{{.Port1}}">
|
||||
<label>IPv6 Address (optional)</label><br>
|
||||
<input type="text" name="Addr6" value="{{ipToString .Addr6}}">
|
||||
</p>
|
||||
<p>
|
||||
<label>Public IP 2 (optional)</label><br>
|
||||
<input type="text" name="PublicIP2" value="{{ipToString .PublicIP2}}">
|
||||
</p>
|
||||
<p>
|
||||
<label>Port 2</label><br>
|
||||
<input type="number" name="Port2" value="{{.Port2}}">
|
||||
<label>WireGuard Port</label><br>
|
||||
<input type="number" name="Port" value="{{.Port}}">
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
|
||||
@@ -8,10 +8,9 @@
|
||||
{{with .Peer -}}
|
||||
<table class="def-list">
|
||||
<tr><td>Peer IP</td><td>{{.PeerIP}}</td></tr>
|
||||
<tr><td>Public IP 1</td><td>{{ipToString .PublicIP1}}</td></tr>
|
||||
<tr><td>Port 1</td><td>{{.Port1}}</td></tr>
|
||||
<tr><td>Public IP 2</td><td>{{ipToString .PublicIP2}}</td></tr>
|
||||
<tr><td>Port 2</td><td>{{.Port2}}</td></tr>
|
||||
<tr><td>IPv4 Address</td><td>{{ipToString .Addr4}}</td></tr>
|
||||
<tr><td>IPv6 Address</td><td>{{ipToString .Addr6}}</td></tr>
|
||||
<tr><td>WireGuard Port</td><td>{{.Port}}</td></tr>
|
||||
<tr><td>Relay</td><td>{{if .Relay}}T{{else}}F{{end}}</td></tr>
|
||||
<tr><td>WG Public Key</td><td>{{wgKeyString .WGPubKey}}</td></tr>
|
||||
</table>
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
//go:build ignore
|
||||
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"vppn/m"
|
||||
)
|
||||
|
||||
type LocalConfig struct {
|
||||
LocalPeerIP byte
|
||||
Network []byte
|
||||
WGPrivKey string
|
||||
}
|
||||
|
||||
func configDir(netName string) string {
|
||||
d, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get user home directory: %v", err)
|
||||
}
|
||||
return filepath.Join(d, ".vppn", netName)
|
||||
}
|
||||
|
||||
func lockFilePath(netName string) string {
|
||||
return filepath.Join(configDir(netName), "__lock__")
|
||||
}
|
||||
|
||||
func peerConfigPath(netName string) string {
|
||||
return filepath.Join(configDir(netName), "config.json")
|
||||
}
|
||||
|
||||
func peerStatePath(netName string) string {
|
||||
return filepath.Join(configDir(netName), "state.json")
|
||||
}
|
||||
|
||||
func statusSocketPath(netName string) string {
|
||||
return filepath.Join(configDir(netName), "status.sock")
|
||||
}
|
||||
|
||||
func storeJson(x any, outPath string) error {
|
||||
outDir := filepath.Dir(outPath)
|
||||
_ = os.MkdirAll(outDir, 0700)
|
||||
|
||||
tmpPath := outPath + ".tmp"
|
||||
buf, err := json.Marshal(x)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(tmpPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := f.Write(buf); err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Sync(); err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Rename(tmpPath, outPath)
|
||||
}
|
||||
|
||||
func storePeerConfig(netName string, pc LocalConfig) error {
|
||||
return storeJson(pc, peerConfigPath(netName))
|
||||
}
|
||||
|
||||
func storeNetworkState(netName string, ps m.NetworkState) error {
|
||||
return storeJson(ps, peerStatePath(netName))
|
||||
}
|
||||
|
||||
func loadJson(dataPath string, ptr any) error {
|
||||
data, err := os.ReadFile(dataPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, ptr)
|
||||
}
|
||||
|
||||
func loadPeerConfig(netName string) (pc LocalConfig, err error) {
|
||||
return pc, loadJson(peerConfigPath(netName), &pc)
|
||||
}
|
||||
|
||||
func loadNetworkState(netName string) (ps m.NetworkState, err error) {
|
||||
return ps, loadJson(peerStatePath(netName), &ps)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
//go:build ignore
|
||||
|
||||
package peer
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
const (
|
||||
broadcastInterval = 16 * time.Second
|
||||
broadcastErrorTimeoutInterval = 8 * time.Second
|
||||
)
|
||||
|
||||
var multicastAddr = net.UDPAddrFromAddrPort(netip.AddrPortFrom(
|
||||
netip.AddrFrom4([4]byte{224, 0, 0, 157}),
|
||||
4560))
|
||||
|
||||
type Globals struct {
|
||||
LocalConfig // Embed, immutable.
|
||||
|
||||
// WireGuard private key, client, and device name. Immutable after init.
|
||||
WGPrivKey wgtypes.Key
|
||||
WGClient *wgctrl.Client
|
||||
WGDevName string
|
||||
|
||||
// Local public address (if available). Immutable.
|
||||
LocalAddr netip.AddrPort
|
||||
LocalAddrValid bool
|
||||
}
|
||||
|
||||
func NewGlobals(localConfig LocalConfig, localAddr netip.AddrPort) (g Globals) {
|
||||
g.LocalConfig = localConfig
|
||||
g.LocalAddr = localAddr
|
||||
g.LocalAddrValid = localAddr.IsValid()
|
||||
return g
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
//go:build ignore
|
||||
|
||||
package peer
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
const (
|
||||
probeWait = 15 * time.Second
|
||||
backoffStart = 5 * time.Minute
|
||||
backoffMax = time.Hour
|
||||
)
|
||||
|
||||
type probeState struct {
|
||||
pubKey wgtypes.Key
|
||||
endpoint netip.AddrPort
|
||||
probing bool
|
||||
direct bool
|
||||
backoff time.Duration
|
||||
backoffAt time.Time
|
||||
}
|
||||
|
||||
type HolePunch struct {
|
||||
Globals
|
||||
mu sync.Mutex
|
||||
peers [256]*probeState
|
||||
}
|
||||
|
||||
func NewHolePunch(g Globals) *HolePunch {
|
||||
return &HolePunch{Globals: g}
|
||||
}
|
||||
|
||||
// OnEndpointLearned is called when a peer's external WG endpoint becomes known,
|
||||
// either from the VPN control channel (MsgMyEndpoint) or from the hub poller.
|
||||
// fromHub=true resets any existing backoff so the hub-reported change is acted
|
||||
// on immediately.
|
||||
func (hp *HolePunch) OnEndpointLearned(peerIP byte, pubKey wgtypes.Key, endpoint netip.AddrPort, fromHub bool) {
|
||||
hp.mu.Lock()
|
||||
defer hp.mu.Unlock()
|
||||
|
||||
ps := hp.peers[peerIP]
|
||||
if ps == nil {
|
||||
ps = &probeState{pubKey: pubKey}
|
||||
hp.peers[peerIP] = ps
|
||||
}
|
||||
|
||||
if fromHub {
|
||||
ps.backoff = 0
|
||||
ps.backoffAt = time.Time{}
|
||||
ps.direct = false
|
||||
ps.pubKey = pubKey
|
||||
}
|
||||
ps.endpoint = endpoint
|
||||
|
||||
if ps.probing || ps.direct {
|
||||
return
|
||||
}
|
||||
if ps.backoff > 0 && time.Now().Before(ps.backoffAt) {
|
||||
return
|
||||
}
|
||||
|
||||
ps.probing = true
|
||||
go hp.runProbe(peerIP)
|
||||
}
|
||||
|
||||
func (hp *HolePunch) runProbe(peerIP byte) {
|
||||
hp.mu.Lock()
|
||||
ps := hp.peers[peerIP]
|
||||
if ps == nil {
|
||||
hp.mu.Unlock()
|
||||
return
|
||||
}
|
||||
pubKey := ps.pubKey
|
||||
endpoint := ps.endpoint
|
||||
hp.mu.Unlock()
|
||||
|
||||
vpnIP := netip.AddrFrom4([4]byte{
|
||||
hp.Network[0], hp.Network[1], hp.Network[2], peerIP,
|
||||
})
|
||||
|
||||
probeStart := time.Now()
|
||||
if err := addProbeEntry(hp.WGClient, hp.WGDevName, pubKey, endpoint); err != nil {
|
||||
log.Printf("[HolePunch] addProbeEntry peer %d: %v", peerIP, err)
|
||||
hp.finishProbe(peerIP, false)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(probeWait)
|
||||
|
||||
_, handshakeTime, err := getPeerEndpoint(hp.WGClient, hp.WGDevName, pubKey)
|
||||
if err == nil && handshakeTime.After(probeStart) {
|
||||
if err := promoteToDirect(hp.WGClient, hp.WGDevName, pubKey, vpnIP); err != nil {
|
||||
log.Printf("[HolePunch] promoteToDirect peer %d: %v", peerIP, err)
|
||||
}
|
||||
hp.finishProbe(peerIP, true)
|
||||
return
|
||||
}
|
||||
|
||||
// Probe failed — remove entry and schedule backoff retry.
|
||||
if err := removePeerEntry(hp.WGClient, hp.WGDevName, pubKey); err != nil {
|
||||
log.Printf("[HolePunch] removePeerEntry peer %d: %v", peerIP, err)
|
||||
}
|
||||
|
||||
hp.mu.Lock()
|
||||
ps = hp.peers[peerIP]
|
||||
var delay time.Duration
|
||||
if ps != nil {
|
||||
if ps.backoff == 0 {
|
||||
ps.backoff = backoffStart
|
||||
} else {
|
||||
ps.backoff = min(ps.backoff*2, backoffMax)
|
||||
}
|
||||
ps.backoffAt = time.Now().Add(ps.backoff)
|
||||
ps.probing = false
|
||||
delay = ps.backoff
|
||||
}
|
||||
hp.mu.Unlock()
|
||||
|
||||
if delay > 0 {
|
||||
go func() {
|
||||
time.Sleep(delay)
|
||||
hp.retryProbe(peerIP)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (hp *HolePunch) finishProbe(peerIP byte, success bool) {
|
||||
hp.mu.Lock()
|
||||
defer hp.mu.Unlock()
|
||||
ps := hp.peers[peerIP]
|
||||
if ps == nil {
|
||||
return
|
||||
}
|
||||
ps.probing = false
|
||||
if success {
|
||||
ps.direct = true
|
||||
ps.backoff = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (hp *HolePunch) retryProbe(peerIP byte) {
|
||||
hp.mu.Lock()
|
||||
ps := hp.peers[peerIP]
|
||||
if ps == nil || ps.probing || ps.direct {
|
||||
hp.mu.Unlock()
|
||||
return
|
||||
}
|
||||
ps.probing = true
|
||||
hp.mu.Unlock()
|
||||
hp.runProbe(peerIP)
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
//go:build ignore
|
||||
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"time"
|
||||
"vppn/m"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
type HubPoller struct {
|
||||
client *http.Client
|
||||
req *http.Request
|
||||
versions [256]int64
|
||||
netName string
|
||||
}
|
||||
|
||||
func NewHubPoller(
|
||||
netName,
|
||||
hubURL,
|
||||
apiKey string,
|
||||
) (*HubPoller, error) {
|
||||
u, err := url.Parse(hubURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Path = "/peer/fetch-state/"
|
||||
|
||||
client := &http.Client{Timeout: 8 * time.Second}
|
||||
|
||||
req := &http.Request{
|
||||
Method: http.MethodGet,
|
||||
URL: u,
|
||||
Header: http.Header{},
|
||||
}
|
||||
req.SetBasicAuth("", apiKey)
|
||||
|
||||
return &HubPoller{
|
||||
Globals: g,
|
||||
holePunch: hp,
|
||||
client: client,
|
||||
req: req,
|
||||
netName: netName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (hp *HubPoller) logf(s string, args ...any) {
|
||||
log.Printf("[HubPoller] "+s, args...)
|
||||
}
|
||||
|
||||
func (hp *HubPoller) Run() {
|
||||
state, err := loadNetworkState(hp.netName)
|
||||
if err != nil {
|
||||
hp.logf("Failed to load network state: %v", err)
|
||||
hp.logf("Polling hub...")
|
||||
hp.pollHub()
|
||||
} else {
|
||||
hp.applyNetworkState(state)
|
||||
}
|
||||
|
||||
for range time.Tick(64 * time.Second) {
|
||||
hp.pollHub()
|
||||
}
|
||||
}
|
||||
|
||||
func (hp *HubPoller) pollHub() {
|
||||
var state m.NetworkState
|
||||
|
||||
resp, err := hp.client.Do(hp.req)
|
||||
if err != nil {
|
||||
hp.logf("Failed to fetch peer state: %v", err)
|
||||
return
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
if err != nil {
|
||||
hp.logf("Failed to read body from hub: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &state); err != nil {
|
||||
hp.logf("Failed to unmarshal response from hub: %v\n%s", err, body)
|
||||
return
|
||||
}
|
||||
|
||||
if err := storeNetworkState(hp.netName, state); err != nil {
|
||||
hp.logf("Failed to store network state: %v", err)
|
||||
}
|
||||
|
||||
hp.applyNetworkState(state)
|
||||
}
|
||||
|
||||
func (hp *HubPoller) applyNetworkState(state m.NetworkState) {
|
||||
for i, peer := range state.Peers {
|
||||
if i == int(hp.LocalPeerIP) {
|
||||
continue
|
||||
}
|
||||
if peer != nil && peer.Version == hp.versions[i] {
|
||||
continue
|
||||
}
|
||||
hp.applyPeerConfig(peer)
|
||||
if peer != nil {
|
||||
hp.versions[i] = peer.Version
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (hp *HubPoller) applyPeerConfig(peer *m.Peer) {
|
||||
if peer == nil || len(peer.WGPubKey) != wgtypes.KeyLen {
|
||||
return
|
||||
}
|
||||
if len(peer.Addr4) == 0 || peer.Port == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
pubKey, err := wgtypes.NewKey(peer.WGPubKey)
|
||||
if err != nil {
|
||||
hp.logf("Invalid WG key for peer %d: %v", peer.PeerIP, err)
|
||||
return
|
||||
}
|
||||
|
||||
ip, ok := netip.AddrFromSlice(peer.Addr4)
|
||||
if !ok {
|
||||
hp.logf("Invalid public IP for peer %d", peer.PeerIP)
|
||||
return
|
||||
}
|
||||
endpoint := netip.AddrPortFrom(ip.Unmap(), peer.Port)
|
||||
|
||||
if peer.Relay {
|
||||
if err := applyBaseConfig(hp.WGClient, hp.WGDevName, pubKey, endpoint, hp.Network); err != nil {
|
||||
hp.logf("Failed to update relay config: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if hp.holePunch != nil {
|
||||
hp.holePunch.OnEndpointLearned(peer.PeerIP, pubKey, endpoint, true)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
//go:build ignore
|
||||
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
func RunMCReader(g Globals, hp *HolePunch, netName string) {
|
||||
for {
|
||||
if err := runMCReaderInner(g, hp, netName); err != nil {
|
||||
log.Printf("[MCReader] %v", err)
|
||||
}
|
||||
time.Sleep(broadcastErrorTimeoutInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func runMCReaderInner(g Globals, hp *HolePunch, netName string) error {
|
||||
conn, err := net.ListenMulticastUDP("udp", nil, multicastAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bind: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
buf := make([]byte, 64)
|
||||
for {
|
||||
conn.SetReadDeadline(time.Now().Add(32 * time.Second))
|
||||
n, src, err := conn.ReadFromUDPAddrPort(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read: %w", err)
|
||||
}
|
||||
if n != beaconLen {
|
||||
continue
|
||||
}
|
||||
handleBeacon(g, hp, netName, buf[:n], src)
|
||||
}
|
||||
}
|
||||
|
||||
func handleBeacon(g Globals, hp *HolePunch, netName string, beacon []byte, src netip.AddrPort) {
|
||||
peerIPByte := beacon[0]
|
||||
if peerIPByte == g.LocalPeerIP {
|
||||
return
|
||||
}
|
||||
|
||||
pubKey, err := wgtypes.NewKey(beacon[1:33])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Skip relay peers: probing would replace their /24 AllowedIPs with empty.
|
||||
if state, err := loadNetworkState(netName); err == nil {
|
||||
if p := state.Peers[peerIPByte]; p != nil && p.Relay {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
wgPort := binary.BigEndian.Uint16(beacon[33:35])
|
||||
endpoint := netip.AddrPortFrom(src.Addr().Unmap(), wgPort)
|
||||
|
||||
hp.OnEndpointLearned(peerIPByte, pubKey, endpoint, false)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
//go:build ignore
|
||||
|
||||
package peer
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const beaconLen = 35 // 1 VPN IP byte + 32 WG pubkey + 2 WG listen port
|
||||
|
||||
func RunMCWriter(g Globals) {
|
||||
conn, err := net.ListenMulticastUDP("udp", nil, multicastAddr)
|
||||
if err != nil {
|
||||
log.Fatalf("[MCWriter] bind: %v", err)
|
||||
}
|
||||
|
||||
for range time.Tick(broadcastInterval) {
|
||||
beacon, err := buildBeacon(g)
|
||||
if err != nil {
|
||||
log.Printf("[MCWriter] build beacon: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Printf("[MCWriter] Broadcasting on %v...", multicastAddr)
|
||||
if _, err := conn.WriteToUDP(beacon, multicastAddr); err != nil {
|
||||
log.Printf("[MCWriter] write: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildBeacon(g Globals) ([]byte, error) {
|
||||
dev, err := g.WGClient.Device(g.WGDevName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get WG device: %w", err)
|
||||
}
|
||||
beacon := make([]byte, beaconLen)
|
||||
beacon[0] = g.LocalPeerIP
|
||||
pubKey := g.WGPrivKey.PublicKey()
|
||||
copy(beacon[1:33], pubKey[:])
|
||||
binary.BigEndian.PutUint16(beacon[33:35], uint16(dev.ListenPort))
|
||||
return beacon, nil
|
||||
}
|
||||
179
peer/wgdev.go
179
peer/wgdev.go
@@ -1,179 +0,0 @@
|
||||
//go:build ignore
|
||||
|
||||
package peer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
func createWGDevice(name string, privKey wgtypes.Key, listenPort int, vpnIP netip.Addr, network []byte) (*wgctrl.Client, error) {
|
||||
if len(network) != 4 {
|
||||
return nil, fmt.Errorf("expected 4-byte network, got %d", len(network))
|
||||
}
|
||||
|
||||
la := netlink.NewLinkAttrs()
|
||||
la.Name = name
|
||||
if err := netlink.LinkAdd(&netlink.GenericLink{LinkAttrs: la, LinkType: "wireguard"}); err != nil {
|
||||
return nil, fmt.Errorf("add wireguard link: %w", err)
|
||||
}
|
||||
|
||||
link, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
_ = destroyWGDevice(name)
|
||||
return nil, fmt.Errorf("get wireguard link: %w", err)
|
||||
}
|
||||
|
||||
a4 := vpnIP.As4()
|
||||
if err := netlink.AddrAdd(link, &netlink.Addr{
|
||||
IPNet: &net.IPNet{
|
||||
IP: net.IP(a4[:]),
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
},
|
||||
}); err != nil {
|
||||
_ = destroyWGDevice(name)
|
||||
return nil, fmt.Errorf("add VPN address: %w", err)
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetUp(link); err != nil {
|
||||
_ = destroyWGDevice(name)
|
||||
return nil, fmt.Errorf("set link up: %w", err)
|
||||
}
|
||||
|
||||
client, err := wgctrl.New()
|
||||
if err != nil {
|
||||
_ = destroyWGDevice(name)
|
||||
return nil, fmt.Errorf("new wgctrl client: %w", err)
|
||||
}
|
||||
|
||||
cfg := wgtypes.Config{
|
||||
PrivateKey: &privKey,
|
||||
ListenPort: &listenPort,
|
||||
}
|
||||
if err := client.ConfigureDevice(name, cfg); err != nil {
|
||||
client.Close()
|
||||
_ = destroyWGDevice(name)
|
||||
return nil, fmt.Errorf("configure wireguard: %w", err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func destroyWGDevice(name string) error {
|
||||
link, err := netlink.LinkByName(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get link %q: %w", name, err)
|
||||
}
|
||||
return netlink.LinkDel(link)
|
||||
}
|
||||
|
||||
// applyBaseConfig adds the relay peer with /24 AllowedIPs, making it the
|
||||
// fallback route for all VPN traffic.
|
||||
func applyBaseConfig(client *wgctrl.Client, devName string, relayPubKey wgtypes.Key, relayEndpoint netip.AddrPort, network []byte) error {
|
||||
if len(network) != 4 {
|
||||
return fmt.Errorf("expected 4-byte network, got %d", len(network))
|
||||
}
|
||||
keepalive := 25 * time.Second
|
||||
cfg := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: relayPubKey,
|
||||
Endpoint: net.UDPAddrFromAddrPort(relayEndpoint),
|
||||
AllowedIPs: []net.IPNet{{
|
||||
IP: net.IP{network[0], network[1], network[2], 0},
|
||||
Mask: net.CIDRMask(24, 32),
|
||||
}},
|
||||
ReplaceAllowedIPs: true,
|
||||
PersistentKeepaliveInterval: &keepalive,
|
||||
}},
|
||||
}
|
||||
return client.ConfigureDevice(devName, cfg)
|
||||
}
|
||||
|
||||
// addProbeEntry adds a peer with no AllowedIPs and a 5s keepalive so WireGuard
|
||||
// attempts handshakes without routing any traffic through it yet.
|
||||
func addProbeEntry(client *wgctrl.Client, devName string, pubKey wgtypes.Key, endpoint netip.AddrPort) error {
|
||||
keepalive := 5 * time.Second
|
||||
cfg := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
Endpoint: net.UDPAddrFromAddrPort(endpoint),
|
||||
AllowedIPs: []net.IPNet{},
|
||||
ReplaceAllowedIPs: true,
|
||||
PersistentKeepaliveInterval: &keepalive,
|
||||
}},
|
||||
}
|
||||
return client.ConfigureDevice(devName, cfg)
|
||||
}
|
||||
|
||||
// addDirectPeer adds a peer with a known endpoint and /32 AllowedIPs in one
|
||||
// step, for use when the hub reports a peer with a stable public endpoint.
|
||||
func addDirectPeer(client *wgctrl.Client, devName string, pubKey wgtypes.Key, endpoint netip.AddrPort, vpnIP netip.Addr) error {
|
||||
a4 := vpnIP.As4()
|
||||
cfg := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
Endpoint: net.UDPAddrFromAddrPort(endpoint),
|
||||
AllowedIPs: []net.IPNet{{
|
||||
IP: net.IP(a4[:]),
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
}},
|
||||
ReplaceAllowedIPs: true,
|
||||
}},
|
||||
}
|
||||
return client.ConfigureDevice(devName, cfg)
|
||||
}
|
||||
|
||||
// promoteToDirect upgrades a probe entry to a /32 AllowedIPs entry, causing
|
||||
// WireGuard to prefer the direct path over the relay's /24 route.
|
||||
func promoteToDirect(client *wgctrl.Client, devName string, pubKey wgtypes.Key, vpnIP netip.Addr) error {
|
||||
a4 := vpnIP.As4()
|
||||
cfg := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
AllowedIPs: []net.IPNet{{
|
||||
IP: net.IP(a4[:]),
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
}},
|
||||
ReplaceAllowedIPs: true,
|
||||
}},
|
||||
}
|
||||
return client.ConfigureDevice(devName, cfg)
|
||||
}
|
||||
|
||||
func removePeerEntry(client *wgctrl.Client, devName string, pubKey wgtypes.Key) error {
|
||||
cfg := wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{{
|
||||
PublicKey: pubKey,
|
||||
Remove: true,
|
||||
}},
|
||||
}
|
||||
return client.ConfigureDevice(devName, cfg)
|
||||
}
|
||||
|
||||
func enableForwarding(ifaceName string) error {
|
||||
path := fmt.Sprintf("/proc/sys/net/ipv4/conf/%s/forwarding", ifaceName)
|
||||
return os.WriteFile(path, []byte("1\n"), 0644)
|
||||
}
|
||||
|
||||
func getPeerEndpoint(client *wgctrl.Client, devName string, pubKey wgtypes.Key) (netip.AddrPort, time.Time, error) {
|
||||
dev, err := client.Device(devName)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, time.Time{}, fmt.Errorf("get device: %w", err)
|
||||
}
|
||||
for _, p := range dev.Peers {
|
||||
if p.PublicKey == pubKey {
|
||||
if p.Endpoint == nil {
|
||||
return netip.AddrPort{}, p.LastHandshakeTime, nil
|
||||
}
|
||||
return p.Endpoint.AddrPort(), p.LastHandshakeTime, nil
|
||||
}
|
||||
}
|
||||
return netip.AddrPort{}, time.Time{}, fmt.Errorf("peer %v not found in device %s", pubKey, devName)
|
||||
}
|
||||
Reference in New Issue
Block a user