WIP
This commit is contained in:
@@ -44,7 +44,8 @@ func main() {
|
|||||||
log.Fatalf("init: %v", err)
|
log.Fatalf("init: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app, err := peer.New(state, *hub, apiKey, *name)
|
ifaceName := strings.TrimSuffix(state.LocalDomain, ".local")
|
||||||
|
app, err := peer.New(state, *hub, apiKey, ifaceName, state.LocalDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("start: %v", err)
|
log.Fatalf("start: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,9 @@ func Peer_Validate(p *Peer) error {
|
|||||||
return ErrInvalidPort
|
return ErrInvalidPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(p.Name) == 0 {
|
||||||
|
return ErrInvalidPeerName
|
||||||
|
}
|
||||||
for _, c := range p.Name {
|
for _, c := range p.Name {
|
||||||
if c >= 'a' && c <= 'z' {
|
if c >= 'a' && c <= 'z' {
|
||||||
continue
|
continue
|
||||||
@@ -102,7 +105,7 @@ func Peer_Validate(p *Peer) error {
|
|||||||
if c >= '0' && c <= '9' {
|
if c >= '0' && c <= '9' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c == '.' || c == '-' || c == '_' {
|
if c == '-' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return ErrInvalidPeerName
|
return ErrInvalidPeerName
|
||||||
|
|||||||
@@ -332,8 +332,9 @@ func (a *App) _peerInit(peer *api.Peer, w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
resp := m.PeerInitResp{
|
resp := m.PeerInitResp{
|
||||||
PeerIP: peer.PeerIP,
|
PeerIP: peer.PeerIP,
|
||||||
Network: net.Network,
|
Network: net.Network,
|
||||||
|
LocalDomain: net.LocalDomain,
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.NetworkState.Peers, err = a.peersArray(net.NetworkID)
|
resp.NetworkState.Peers, err = a.peersArray(net.NetworkID)
|
||||||
@@ -345,19 +346,11 @@ func (a *App) _peerInit(peer *api.Peer, w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) _peerFetchState(peer *api.Peer, w http.ResponseWriter, r *http.Request) error {
|
func (a *App) _peerFetchState(peer *api.Peer, w http.ResponseWriter, r *http.Request) error {
|
||||||
net, err := a.api.Network_Get(peer.NetworkID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
peers, err := a.peersArray(peer.NetworkID)
|
peers, err := a.peersArray(peer.NetworkID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return a.sendJSON(w, m.NetworkState{
|
return a.sendJSON(w, m.NetworkState{Peers: peers})
|
||||||
LocalDomain: net.LocalDomain,
|
|
||||||
Peers: peers,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) peersArray(networkID int64) (peers [256]*m.Peer, err error) {
|
func (a *App) peersArray(networkID int64) (peers [256]*m.Peer, err error) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type PeerInitArgs struct {
|
|||||||
type PeerInitResp struct {
|
type PeerInitResp struct {
|
||||||
PeerIP byte
|
PeerIP byte
|
||||||
Network []byte
|
Network []byte
|
||||||
|
LocalDomain string
|
||||||
NetworkState NetworkState
|
NetworkState NetworkState
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +26,5 @@ type Peer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NetworkState struct {
|
type NetworkState struct {
|
||||||
LocalDomain string
|
Peers [256]*Peer
|
||||||
Peers [256]*Peer
|
|
||||||
}
|
}
|
||||||
|
|||||||
20
peer/app.go
20
peer/app.go
@@ -25,6 +25,7 @@ const (
|
|||||||
type HubPeer struct {
|
type HubPeer struct {
|
||||||
PubKey wgtypes.Key
|
PubKey wgtypes.Key
|
||||||
VPNIP netip.Addr
|
VPNIP netip.Addr
|
||||||
|
Name string
|
||||||
IsRelay bool
|
IsRelay bool
|
||||||
IsPublic bool
|
IsPublic bool
|
||||||
EndpointV4 netip.AddrPort // zero if none
|
EndpointV4 netip.AddrPort // zero if none
|
||||||
@@ -47,12 +48,13 @@ type MulticastEvent struct {
|
|||||||
// accessed only from the Run goroutine.
|
// accessed only from the Run goroutine.
|
||||||
type App struct {
|
type App struct {
|
||||||
// Identity
|
// Identity
|
||||||
vpnIP netip.Addr
|
vpnIP netip.Addr
|
||||||
vpnNet netip.Prefix
|
vpnNet netip.Prefix
|
||||||
privKey wgtypes.Key
|
privKey wgtypes.Key
|
||||||
pubKey wgtypes.Key
|
pubKey wgtypes.Key
|
||||||
isRelay bool
|
isRelay bool
|
||||||
isPublic bool
|
isPublic bool
|
||||||
|
localDomain string
|
||||||
|
|
||||||
// Infrastructure
|
// Infrastructure
|
||||||
dev WGDevice
|
dev WGDevice
|
||||||
@@ -76,6 +78,11 @@ type App struct {
|
|||||||
|
|
||||||
// Run is the main event loop. It runs until SIGTERM/SIGINT.
|
// Run is the main event loop. It runs until SIGTERM/SIGINT.
|
||||||
func (a *App) Run() error {
|
func (a *App) Run() error {
|
||||||
|
// Establish a clean hosts section before the first poll lands, clearing
|
||||||
|
// any stale entries left by a prior run (e.g. crash, or peers removed
|
||||||
|
// while we were down).
|
||||||
|
a.updateHosts()
|
||||||
|
|
||||||
ticker := time.NewTicker(PingInterval)
|
ticker := time.NewTicker(PingInterval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@@ -102,5 +109,6 @@ func (a *App) Run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) onShutdown() error {
|
func (a *App) onShutdown() error {
|
||||||
|
// TODO: removeHosts() ?
|
||||||
return wginterface.Delete(a.dev.Name())
|
return wginterface.Delete(a.dev.Name())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,30 +38,36 @@ func (a *App) devPeers() []wgtypes.Peer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) devAddPeer(p *Peer) {
|
func (a *App) devAddPeer(p *Peer) {
|
||||||
|
log.Printf("RELAYED: %s - %s ", p.Name, p.VPNIP.String())
|
||||||
devRetry(p.VPNIP, "AddPeer", func() error { return a.dev.AddPeer(p.PubKey()) })
|
devRetry(p.VPNIP, "AddPeer", func() error { return a.dev.AddPeer(p.PubKey()) })
|
||||||
p.State = StateRelayed
|
p.State = StateRelayed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) devAddDirect(p *Peer, endpoint netip.AddrPort) {
|
func (a *App) devAddDirect(p *Peer, endpoint netip.AddrPort) {
|
||||||
|
log.Printf("DIRECT: %s - %s @ %s", p.Name, p.VPNIP.String(), endpoint.String())
|
||||||
devRetry(p.VPNIP, "AddDirect", func() error { return a.dev.AddDirect(p.PubKey(), endpoint, p.VPNIP) })
|
devRetry(p.VPNIP, "AddDirect", func() error { return a.dev.AddDirect(p.PubKey(), endpoint, p.VPNIP) })
|
||||||
p.State = StateDirect
|
p.State = StateDirect
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) devSetRelay(p *Peer, endpoint netip.AddrPort) {
|
func (a *App) devSetRelay(p *Peer, endpoint netip.AddrPort) {
|
||||||
|
log.Printf("RELAY: %s - %s @ %s", p.Name, p.VPNIP.String(), endpoint.String())
|
||||||
devRetry(p.VPNIP, "SetRelay", func() error { return a.dev.SetRelay(p.PubKey(), endpoint, a.vpnNet) })
|
devRetry(p.VPNIP, "SetRelay", func() error { return a.dev.SetRelay(p.PubKey(), endpoint, a.vpnNet) })
|
||||||
p.State = StateDirect
|
p.State = StateDirect
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) devPromote(p *Peer) {
|
func (a *App) devPromote(p *Peer) {
|
||||||
|
log.Printf("PROMOTED: %s - %s @ %s", p.Name, p.VPNIP.String(), p.WGEndpoint().String())
|
||||||
devRetry(p.VPNIP, "Promote", func() error { return a.dev.Promote(p.PubKey(), p.VPNIP) })
|
devRetry(p.VPNIP, "Promote", func() error { return a.dev.Promote(p.PubKey(), p.VPNIP) })
|
||||||
p.State = StateDirect
|
p.State = StateDirect
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) devAddProbe(p *Peer, endpoint netip.AddrPort) {
|
func (a *App) devAddProbe(p *Peer, endpoint netip.AddrPort) {
|
||||||
|
log.Printf("PROBE: %s - %s @ %s", p.Name, p.VPNIP.String(), endpoint.String())
|
||||||
devRetry(p.VPNIP, "AddProbe", func() error { return a.dev.AddProbe(p.PubKey(), endpoint) })
|
devRetry(p.VPNIP, "AddProbe", func() error { return a.dev.AddProbe(p.PubKey(), endpoint) })
|
||||||
p.State = StateProbing
|
p.State = StateProbing
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) devRemove(p *Peer) {
|
func (a *App) devRemove(p *Peer) {
|
||||||
|
log.Printf("REMOVED: %s - %s", p.Name, p.VPNIP.String())
|
||||||
devRetry(p.VPNIP, "RemovePeer", func() error { return a.dev.RemovePeer(p.PubKey()) })
|
devRetry(p.VPNIP, "RemovePeer", func() error { return a.dev.RemovePeer(p.PubKey()) })
|
||||||
}
|
}
|
||||||
|
|||||||
121
peer/hosts.go
Normal file
121
peer/hosts.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package peer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"git.crumpington.com/lib/go/flock"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hostsFile = "/etc/hosts"
|
||||||
|
hostsBegin = "# BEGIN vppn "
|
||||||
|
hostsEnd = "# END vppn "
|
||||||
|
)
|
||||||
|
|
||||||
|
// updateHosts rewrites the managed vppn section in /etc/hosts using the
|
||||||
|
// current peersByIP map. Peers without a Name are skipped.
|
||||||
|
func (a *App) updateHosts() {
|
||||||
|
if a.localDomain == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := updateHosts(hostsFile, a.localDomain, a.peersByIP); err != nil {
|
||||||
|
log.Printf("Failed to update hosts file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateHosts(hostsPath, localDomain string, peers map[netip.Addr]*Peer) error {
|
||||||
|
lockFile, err := flock.Lock(hostsPath + ".vppn.lock")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer lockFile.Close()
|
||||||
|
|
||||||
|
begin := hostsBegin + localDomain
|
||||||
|
end := hostsEnd + localDomain
|
||||||
|
|
||||||
|
info, err := os.Stat(hostsPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := os.ReadFile(hostsPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := string(raw)
|
||||||
|
|
||||||
|
before := strings.TrimSpace(data)
|
||||||
|
after := ""
|
||||||
|
|
||||||
|
if idxBegin := strings.Index(data, begin); idxBegin != -1 {
|
||||||
|
idxEnd := strings.Index(data, end)
|
||||||
|
if idxEnd != -1 {
|
||||||
|
after = strings.TrimSpace(data[idxEnd+len(end):])
|
||||||
|
}
|
||||||
|
before = strings.TrimSpace(data[:idxBegin])
|
||||||
|
}
|
||||||
|
|
||||||
|
b := strings.Builder{}
|
||||||
|
b.WriteString(before)
|
||||||
|
b.WriteRune('\n')
|
||||||
|
b.WriteString(after)
|
||||||
|
b.WriteRune('\n')
|
||||||
|
b.WriteRune('\n')
|
||||||
|
|
||||||
|
b.WriteString(begin)
|
||||||
|
b.WriteRune('\n')
|
||||||
|
|
||||||
|
// Collect entries so we can sort by IP for stable output. Pad the IP
|
||||||
|
// column to the width of the widest possible address ("255.255.255.255")
|
||||||
|
// for readability.
|
||||||
|
type entry struct {
|
||||||
|
ip netip.Addr
|
||||||
|
host string
|
||||||
|
}
|
||||||
|
var entries []entry
|
||||||
|
for ip, p := range peers {
|
||||||
|
if p.Name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entries = append(entries, entry{ip: ip, host: p.Name + "." + localDomain})
|
||||||
|
}
|
||||||
|
sort.Slice(entries, func(i, j int) bool {
|
||||||
|
return entries[i].ip.Less(entries[j].ip)
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, e := range entries {
|
||||||
|
b.WriteString(fmt.Sprintf("%-15s %s\n", e.ip.String(), e.host))
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString(end)
|
||||||
|
b.WriteRune('\n')
|
||||||
|
|
||||||
|
// Write to a temp file in the same directory, then rename over the
|
||||||
|
// original so readers never observe a partial file. Preserve the
|
||||||
|
// original's mode and ownership, since rename replaces the inode.
|
||||||
|
tmpPath := hostsPath + ".vppn.tmp"
|
||||||
|
if err := os.WriteFile(tmpPath, []byte(b.String()), info.Mode().Perm()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if st, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||||
|
if err := os.Chown(tmpPath, int(st.Uid), int(st.Gid)); err != nil {
|
||||||
|
os.Remove(tmpPath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(tmpPath, hostsPath); err != nil {
|
||||||
|
os.Remove(tmpPath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
167
peer/hosts_test.go
Normal file
167
peer/hosts_test.go
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
package peer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// writeTempHosts creates a temp hosts file with the given content and returns
|
||||||
|
// its path.
|
||||||
|
func writeTempHosts(t *testing.T, content string) string {
|
||||||
|
t.Helper()
|
||||||
|
path := filepath.Join(t.TempDir(), "hosts")
|
||||||
|
if err := os.WriteFile(path, []byte(content), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// readManagedSection returns the lines between the begin/end markers for the
|
||||||
|
// given localDomain, plus everything outside the section ("outside").
|
||||||
|
func readManagedSection(t *testing.T, path, localDomain string) (inside, outside []string) {
|
||||||
|
t.Helper()
|
||||||
|
raw, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
begin := hostsBegin + localDomain
|
||||||
|
end := hostsEnd + localDomain
|
||||||
|
|
||||||
|
inSection := false
|
||||||
|
for _, line := range strings.Split(string(raw), "\n") {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(line, begin):
|
||||||
|
inSection = true
|
||||||
|
case strings.HasPrefix(line, end):
|
||||||
|
inSection = false
|
||||||
|
case inSection:
|
||||||
|
if f := strings.Join(strings.Fields(line), " "); f != "" {
|
||||||
|
inside = append(inside, f)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if f := strings.Join(strings.Fields(line), " "); f != "" {
|
||||||
|
outside = append(outside, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inside, outside
|
||||||
|
}
|
||||||
|
|
||||||
|
func peer(name string) *Peer {
|
||||||
|
return &Peer{Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateHosts_AddsSection(t *testing.T) {
|
||||||
|
path := writeTempHosts(t, "127.0.0.1 localhost\n")
|
||||||
|
|
||||||
|
peers := map[netip.Addr]*Peer{
|
||||||
|
netip.MustParseAddr("10.11.12.1"): peer("hub"),
|
||||||
|
netip.MustParseAddr("10.11.12.10"): peer("laptop"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updateHosts(path, "mynet.local", peers); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inside, outside := readManagedSection(t, path, "mynet.local")
|
||||||
|
|
||||||
|
sort.Strings(inside)
|
||||||
|
want := []string{
|
||||||
|
"10.11.12.1 hub.mynet.local",
|
||||||
|
"10.11.12.10 laptop.mynet.local",
|
||||||
|
}
|
||||||
|
if strings.Join(inside, "\n") != strings.Join(want, "\n") {
|
||||||
|
t.Errorf("managed section = %v, want %v", inside, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !contains(outside, "127.0.0.1 localhost") {
|
||||||
|
t.Errorf("original content lost; outside = %v", outside)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateHosts_ReplacesExistingSection(t *testing.T) {
|
||||||
|
path := writeTempHosts(t, "127.0.0.1 localhost\n")
|
||||||
|
|
||||||
|
// First write.
|
||||||
|
first := map[netip.Addr]*Peer{
|
||||||
|
netip.MustParseAddr("10.11.12.1"): peer("hub"),
|
||||||
|
}
|
||||||
|
if err := updateHosts(path, "mynet.local", first); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second write with a different set of peers.
|
||||||
|
second := map[netip.Addr]*Peer{
|
||||||
|
netip.MustParseAddr("10.11.12.20"): peer("phone"),
|
||||||
|
}
|
||||||
|
if err := updateHosts(path, "mynet.local", second); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inside, outside := readManagedSection(t, path, "mynet.local")
|
||||||
|
|
||||||
|
if len(inside) != 1 || inside[0] != "10.11.12.20 phone.mynet.local" {
|
||||||
|
t.Errorf("section not replaced; inside = %v", inside)
|
||||||
|
}
|
||||||
|
if contains(inside, "10.11.12.1 hub.mynet.local") {
|
||||||
|
t.Errorf("stale entry remained; inside = %v", inside)
|
||||||
|
}
|
||||||
|
if !contains(outside, "127.0.0.1 localhost") {
|
||||||
|
t.Errorf("original content lost; outside = %v", outside)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateHosts_SkipsEmptyNames(t *testing.T) {
|
||||||
|
path := writeTempHosts(t, "127.0.0.1 localhost\n")
|
||||||
|
|
||||||
|
peers := map[netip.Addr]*Peer{
|
||||||
|
netip.MustParseAddr("10.11.12.1"): peer("hub"),
|
||||||
|
netip.MustParseAddr("10.11.12.99"): peer(""), // no name
|
||||||
|
}
|
||||||
|
if err := updateHosts(path, "mynet.local", peers); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inside, _ := readManagedSection(t, path, "mynet.local")
|
||||||
|
if len(inside) != 1 || inside[0] != "10.11.12.1 hub.mynet.local" {
|
||||||
|
t.Errorf("expected only named peer; inside = %v", inside)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateHosts_Idempotent(t *testing.T) {
|
||||||
|
path := writeTempHosts(t, "127.0.0.1 localhost\n")
|
||||||
|
|
||||||
|
peers := map[netip.Addr]*Peer{
|
||||||
|
netip.MustParseAddr("10.11.12.1"): peer("hub"),
|
||||||
|
}
|
||||||
|
if err := updateHosts(path, "mynet.local", peers); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
first, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := updateHosts(path, "mynet.local", peers); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
second, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(first) != string(second) {
|
||||||
|
t.Errorf("repeated update changed file:\nfirst:\n%s\nsecond:\n%s", first, second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(ss []string, s string) bool {
|
||||||
|
for _, x := range ss {
|
||||||
|
if x == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -149,6 +149,7 @@ func hubPeerFrom(pubKey wgtypes.Key, vpnIP netip.Addr, p *m.Peer) HubPeer {
|
|||||||
return HubPeer{
|
return HubPeer{
|
||||||
PubKey: pubKey,
|
PubKey: pubKey,
|
||||||
VPNIP: vpnIP,
|
VPNIP: vpnIP,
|
||||||
|
Name: p.Name,
|
||||||
IsRelay: p.Relay,
|
IsRelay: p.Relay,
|
||||||
IsPublic: ep4.IsValid() || ep6.IsValid(),
|
IsPublic: ep4.IsValid() || ep6.IsValid(),
|
||||||
EndpointV4: ep4,
|
EndpointV4: ep4,
|
||||||
|
|||||||
75
peer/init.go
75
peer/init.go
@@ -20,24 +20,26 @@ import (
|
|||||||
// LocalState is the persisted identity for this peer, written on first run and
|
// LocalState is the persisted identity for this peer, written on first run and
|
||||||
// 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
|
SignKey [64]byte // nacl/sign Ed25519 private key
|
||||||
VPNIP netip.Addr
|
VPNIP netip.Addr
|
||||||
VPNNet netip.Prefix
|
VPNNet netip.Prefix
|
||||||
WGPort uint16
|
WGPort uint16
|
||||||
IsRelay bool
|
IsRelay bool
|
||||||
IsPublic bool
|
IsPublic bool
|
||||||
|
LocalDomain string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
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"`
|
||||||
IsRelay bool `json:"is_relay"`
|
IsRelay bool `json:"is_relay"`
|
||||||
IsPublic bool `json:"is_public"`
|
IsPublic bool `json:"is_public"`
|
||||||
|
LocalDomain string `json:"local_domain"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadOrInit loads LocalState from path, or registers with the hub and creates
|
// LoadOrInit loads LocalState from path, or registers with the hub and creates
|
||||||
@@ -117,13 +119,14 @@ func initFromHub(hubURL, apiKey string, privKey wgtypes.Key) (LocalState, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return LocalState{
|
return LocalState{
|
||||||
PrivKey: privKey,
|
PrivKey: privKey,
|
||||||
SignKey: *signPrivKey,
|
SignKey: *signPrivKey,
|
||||||
VPNIP: vpnIP,
|
VPNIP: vpnIP,
|
||||||
VPNNet: vpnNet,
|
VPNNet: vpnNet,
|
||||||
WGPort: wgPort,
|
WGPort: wgPort,
|
||||||
IsRelay: isRelay,
|
IsRelay: isRelay,
|
||||||
IsPublic: isPublic,
|
IsPublic: isPublic,
|
||||||
|
LocalDomain: r.LocalDomain,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,25 +153,27 @@ func parseLocalState(data []byte) (LocalState, error) {
|
|||||||
var signKey [64]byte
|
var signKey [64]byte
|
||||||
copy(signKey[:], signKeyBytes)
|
copy(signKey[:], signKeyBytes)
|
||||||
return LocalState{
|
return LocalState{
|
||||||
PrivKey: key,
|
PrivKey: key,
|
||||||
SignKey: signKey,
|
SignKey: signKey,
|
||||||
VPNIP: j.VPNIP,
|
VPNIP: j.VPNIP,
|
||||||
VPNNet: j.VPNNet,
|
VPNNet: j.VPNNet,
|
||||||
WGPort: j.WGPort,
|
WGPort: j.WGPort,
|
||||||
IsRelay: j.IsRelay,
|
IsRelay: j.IsRelay,
|
||||||
IsPublic: j.IsPublic,
|
IsPublic: j.IsPublic,
|
||||||
|
LocalDomain: j.LocalDomain,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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[:]),
|
SignKey: base64.StdEncoding.EncodeToString(s.SignKey[:]),
|
||||||
VPNIP: s.VPNIP,
|
VPNIP: s.VPNIP,
|
||||||
VPNNet: s.VPNNet,
|
VPNNet: s.VPNNet,
|
||||||
WGPort: s.WGPort,
|
WGPort: s.WGPort,
|
||||||
IsRelay: s.IsRelay,
|
IsRelay: s.IsRelay,
|
||||||
IsPublic: s.IsPublic,
|
IsPublic: s.IsPublic,
|
||||||
|
LocalDomain: s.LocalDomain,
|
||||||
}
|
}
|
||||||
data, err := json.MarshalIndent(j, "", " ")
|
data, err := json.MarshalIndent(j, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
14
peer/new.go
14
peer/new.go
@@ -16,6 +16,7 @@ func New(
|
|||||||
state LocalState,
|
state LocalState,
|
||||||
hubURL, apiKey string,
|
hubURL, apiKey string,
|
||||||
ifaceName string,
|
ifaceName string,
|
||||||
|
localDomain string,
|
||||||
) (*App, error) {
|
) (*App, error) {
|
||||||
|
|
||||||
a4 := state.VPNIP.As4()
|
a4 := state.VPNIP.As4()
|
||||||
@@ -65,12 +66,13 @@ func New(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &App{
|
return &App{
|
||||||
vpnIP: state.VPNIP,
|
vpnIP: state.VPNIP,
|
||||||
vpnNet: state.VPNNet,
|
vpnNet: state.VPNNet,
|
||||||
privKey: state.PrivKey,
|
privKey: state.PrivKey,
|
||||||
pubKey: state.PrivKey.PublicKey(),
|
pubKey: state.PrivKey.PublicKey(),
|
||||||
isRelay: state.IsRelay,
|
isRelay: state.IsRelay,
|
||||||
isPublic: state.IsPublic,
|
isPublic: state.IsPublic,
|
||||||
|
localDomain: localDomain,
|
||||||
|
|
||||||
dev: dev,
|
dev: dev,
|
||||||
controlConn: cc,
|
controlConn: cc,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ func (a *App) onAddPeer(p HubPeer) {
|
|||||||
peer := &Peer{
|
peer := &Peer{
|
||||||
wgPeer: wgtypes.Peer{PublicKey: p.PubKey},
|
wgPeer: wgtypes.Peer{PublicKey: p.PubKey},
|
||||||
VPNIP: p.VPNIP,
|
VPNIP: p.VPNIP,
|
||||||
|
Name: p.Name,
|
||||||
IsRelay: p.IsRelay,
|
IsRelay: p.IsRelay,
|
||||||
IsPublic: p.IsPublic,
|
IsPublic: p.IsPublic,
|
||||||
Endpoint4: p.EndpointV4,
|
Endpoint4: p.EndpointV4,
|
||||||
@@ -35,6 +36,7 @@ func (a *App) onAddPeer(p HubPeer) {
|
|||||||
|
|
||||||
a.peersByKey[p.PubKey] = peer
|
a.peersByKey[p.PubKey] = peer
|
||||||
a.peersByIP[peer.VPNIP] = peer
|
a.peersByIP[peer.VPNIP] = peer
|
||||||
|
defer a.updateHosts()
|
||||||
|
|
||||||
if !peer.IsPublic {
|
if !peer.IsPublic {
|
||||||
if a.isPublic {
|
if a.isPublic {
|
||||||
@@ -59,6 +61,7 @@ func (a *App) onRemovePeer(key wgtypes.Key) {
|
|||||||
a.devRemove(peer)
|
a.devRemove(peer)
|
||||||
delete(a.peersByKey, key)
|
delete(a.peersByKey, key)
|
||||||
delete(a.peersByIP, peer.VPNIP)
|
delete(a.peersByIP, peer.VPNIP)
|
||||||
|
a.updateHosts()
|
||||||
|
|
||||||
if peer == a.relay {
|
if peer == a.relay {
|
||||||
a.relay = nil
|
a.relay = nil
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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.
|
||||||
|
Name string // Human-readable DNS label.
|
||||||
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user