This commit is contained in:
jdl 2025-09-15 04:07:56 +02:00
parent 75c7c2d3d9
commit e458e43d83
11 changed files with 261 additions and 14 deletions

View File

@ -65,9 +65,10 @@ AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
Type=simple Type=simple
User=user User=user
WorkingDirectory=/home/user/ WorkingDirectory=/home/user/
ExecStart=/home/user/vppn -name mynetwork -hub-address https://my.hub -api-key 1234567890 ExecStart=/home/user/vppn run my_net_name https://my.hub my_api_key
Restart=always Restart=always
RestartSec=8 RestartSec=8
TimeoutStopSec=24
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@ -7,5 +7,5 @@ import (
func main() { func main() {
log.SetFlags(0) log.SetFlags(0)
peer.Main() peer.Main2()
} }

2
go.mod
View File

@ -1,6 +1,6 @@
module vppn module vppn
go 1.24.1 go 1.25.1
require ( require (
git.crumpington.com/lib/go v0.9.0 git.crumpington.com/lib/go v0.9.0

View File

@ -29,6 +29,10 @@ func configDir(netName string) string {
return filepath.Join(d, ".vppn", netName) return filepath.Join(d, ".vppn", netName)
} }
func lockFilePath(netName string) string {
return filepath.Join(configDir(netName), "__lock__")
}
func peerConfigPath(netName string) string { func peerConfigPath(netName string) string {
return filepath.Join(configDir(netName), "config.json") return filepath.Join(configDir(netName), "config.json")
} }
@ -41,6 +45,10 @@ func startupCountPath(netName string) string {
return filepath.Join(configDir(netName), "startup_count.json") return filepath.Join(configDir(netName), "startup_count.json")
} }
func statusSocketPath(netName string) string {
return filepath.Join(configDir(netName), "status.sock")
}
func storeJson(x any, outPath string) error { func storeJson(x any, outPath string) error {
outDir := filepath.Dir(outPath) outDir := filepath.Dir(outPath)
_ = os.MkdirAll(outDir, 0700) _ = os.MkdirAll(outDir, 0700)

View File

@ -19,7 +19,7 @@ const (
controlCipherOverhead = 16 controlCipherOverhead = 16
dataCipherOverhead = 16 dataCipherOverhead = 16
signOverhead = 64 signingOverhead = 64
pingInterval = 8 * time.Second pingInterval = 8 * time.Second
timeoutInterval = 30 * time.Second timeoutInterval = 30 * time.Second

153
peer/main2.go Normal file
View File

@ -0,0 +1,153 @@
package peer
import (
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"os"
"strings"
)
// Usage:
//
// vppn netName run
// vppn netName status
func Main2() {
printUsage := func() {
fmt.Fprintf(os.Stderr, `%s COMMAND [ARGUMENTS...]
Available commands:
run
status
`, os.Args[0])
os.Exit(1)
}
if len(os.Args) < 2 {
printUsage()
}
command := os.Args[1]
switch command {
case "run":
main_run()
case "status":
main_status()
default:
printUsage()
}
}
// ----------------------------------------------------------------------------
type mainArgs struct {
NetName string
HubAddress string
APIKey string
}
func main_run() {
printUsage := func() {
fmt.Fprintf(os.Stderr, `Usage: %s run NETWORK_NAME HUB_ADDRESS API_KEY
NETWORK_NAME
Unique name of the network interface created. The network name
shouldn't change between invocations of the application.
HUB_ADDRESS
The address of the hub server. This should also contain the scheme, for
example https://hub.domain.com/.
API_KEY
The API key assigned to this peer by the hub.
`, os.Args[0])
os.Exit(1)
}
if len(os.Args) != 5 {
printUsage()
}
args := mainArgs{
NetName: os.Args[2],
HubAddress: os.Args[3],
APIKey: os.Args[4],
}
newPeerMain(args).Run()
}
// ----------------------------------------------------------------------------
func main_status() {
printUsage := func() {
fmt.Fprintf(os.Stderr, `Usage: %s status NETWORK_NAME
NETWORK_NAME
Unique name of the network interface created.
`, os.Args[0])
os.Exit(1)
}
if len(os.Args) != 3 {
printUsage()
}
netName := os.Args[2]
client := http.Client{
Transport: &http.Transport{
Dial: func(_, _ string) (net.Conn, error) {
return net.Dial("unix", statusSocketPath(netName))
},
},
}
getURL := "http://unix" + statusSocketPath(netName)
resp, err := client.Get(getURL)
if err != nil {
log.Fatalf("Failed to get response: %v", err)
}
report := StatusReport{}
if err := json.NewDecoder(resp.Body).Decode(&report); err != nil {
log.Fatalf("Failed to decode status report: %v", err)
}
b := strings.Builder{}
for _, status := range report.Remotes {
b.WriteString(fmt.Sprintf("%3d ", status.PeerIP))
if status.Up {
b.WriteString("UP ")
} else {
b.WriteString("DOWN ")
}
if status.Relay && status.Direct {
b.WriteString("RELAY ")
} else if status.Server {
b.WriteString("SERVER ")
} else {
b.WriteString("CLIENT ")
}
if status.Direct {
b.WriteString("DIRECT ")
} else {
b.WriteString("RELAYED ")
}
b.WriteString(fmt.Sprintf("%45s ", status.DirectAddr))
b.WriteString(status.Name)
b.WriteString("\n")
}
fmt.Print(b.String())
}

View File

@ -15,16 +15,16 @@ func createLocalDiscoveryPacket(localIP byte, signingKey []byte) []byte {
} }
buf := make([]byte, headerSize) buf := make([]byte, headerSize)
h.Marshal(buf) h.Marshal(buf)
out := make([]byte, headerSize+signOverhead) out := make([]byte, headerSize+signingOverhead)
return sign.Sign(out[:0], buf, (*[64]byte)(signingKey)) return sign.Sign(out[:0], buf, (*[64]byte)(signingKey))
} }
func headerFromLocalDiscoveryPacket(pkt []byte) (h Header, ok bool) { func headerFromLocalDiscoveryPacket(pkt []byte) (h Header, ok bool) {
if len(pkt) != headerSize+signOverhead { if len(pkt) != headerSize+signingOverhead {
return return
} }
h.Parse(pkt[signOverhead:]) h.Parse(pkt[signingOverhead:])
ok = true ok = true
return return
} }

View File

@ -13,6 +13,8 @@ import (
"net/url" "net/url"
"os" "os"
"vppn/m" "vppn/m"
"git.crumpington.com/lib/go/flock"
) )
type peerMain struct { type peerMain struct {
@ -20,12 +22,7 @@ type peerMain struct {
ifReader *IFReader ifReader *IFReader
connReader *ConnReader connReader *ConnReader
hubPoller *HubPoller hubPoller *HubPoller
} lockFile *os.File
type mainArgs struct {
NetName string
HubAddress string
APIKey string
} }
func newPeerMain(args mainArgs) *peerMain { func newPeerMain(args mainArgs) *peerMain {
@ -33,6 +30,14 @@ func newPeerMain(args mainArgs) *peerMain {
log.Printf("[Main] "+s, args...) log.Printf("[Main] "+s, args...)
} }
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) config, err := loadPeerConfig(args.NetName)
if err != nil { if err != nil {
logf("Failed to load configuration: %v", err) logf("Failed to load configuration: %v", err)
@ -100,11 +105,15 @@ func newPeerMain(args mainArgs) *peerMain {
log.Fatalf("Failed to create hub poller: %v", err) log.Fatalf("Failed to create hub poller: %v", err)
} }
// Start status server.
go runStatusServer(g, statusSocketPath(args.NetName))
return &peerMain{ return &peerMain{
Globals: g, Globals: g,
ifReader: NewIFReader(g), ifReader: NewIFReader(g),
connReader: NewConnReader(g, conn), connReader: NewConnReader(g, conn),
hubPoller: hubPoller, hubPoller: hubPoller,
lockFile: lockFile,
} }
} }

View File

@ -106,6 +106,25 @@ func (r *Remote) encryptControl(conf remoteConfig, packet []byte) []byte {
return conf.ControlCipher.Encrypt(h, packet, packet[len(packet):cap(packet)]) return conf.ControlCipher.Encrypt(h, packet, packet[len(packet):cap(packet)])
} }
func (r *Remote) Status() (RemoteStatus, bool) {
conf := r.conf()
if conf.Peer == nil {
return RemoteStatus{}, false
}
return RemoteStatus{
PeerIP: conf.Peer.PeerIP,
Up: conf.Up,
Name: conf.Peer.Name,
PublicIP: conf.Peer.PublicIP,
Port: conf.Peer.Port,
Relay: conf.Peer.Relay,
Server: conf.Server,
Direct: conf.Direct,
DirectAddr: conf.DirectAddr,
}, true
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// SendDataTo sends a data packet to the remote, called by the IFReader. // SendDataTo sends a data packet to the remote, called by the IFReader.

View File

@ -171,7 +171,7 @@ func (r *remoteFSM) stateServer_onSyn(msg controlMsg[packetSyn]) {
conf.DirectAddr = msg.SrcAddr conf.DirectAddr = msg.SrcAddr
// Update data cipher if the key has changed. // Update data cipher if the key has changed.
if !conf.DataCipher.HasKey(p.SharedKey) { if conf.DataCipher == nil || !conf.DataCipher.HasKey(p.SharedKey) {
conf.DataCipher = newDataCipherFromKey(p.SharedKey) conf.DataCipher = newDataCipherFromKey(p.SharedKey)
} }

57
peer/statusserver.go Normal file
View File

@ -0,0 +1,57 @@
package peer
import (
"encoding/json"
"log"
"net"
"net/http"
"net/netip"
"os"
)
type StatusReport struct {
Remotes []RemoteStatus
}
type RemoteStatus struct {
PeerIP byte
Up bool
Name string
PublicIP []byte
Port uint16
Relay bool
Server bool
Direct bool
DirectAddr netip.AddrPort
}
func runStatusServer(g Globals, socketPath string) {
_ = os.RemoveAll(socketPath)
handler := func(w http.ResponseWriter, r *http.Request) {
report := StatusReport{
Remotes: make([]RemoteStatus, 0, 255),
}
for i := range g.RemotePeers {
remote := g.RemotePeers[i].Load()
status, ok := remote.Status()
if !ok {
continue
}
report.Remotes = append(report.Remotes, status)
}
json.NewEncoder(w).Encode(report)
}
server := http.Server{
Handler: http.HandlerFunc(handler),
}
unixListener, err := net.Listen("unix", socketPath)
if err != nil {
log.Fatalf("Failed to bind to unix socket: %v", err)
}
if err := server.Serve(unixListener); err != nil {
log.Fatalf("Failed to serve on unix socket: %v", err)
}
}