feat: Docker Compose E2E test env, agent fixes, TAP MAC config, testclient binary
- Add docker-compose.yml with server + init + 3 clients + tester - Add Dockerfile.server (CGO multi-stage) and Dockerfile.client - Add .dockerignore for efficient builds - Add cmd/testclient/main.go: full E2E test binary using SDK - Fix agent handshake loop: only respond to new handshakes - Add tapInterface.SetMAC() to match agent's generated MAC - Call SetMAC() in agent SetTAPIP to fix ARP resolution - Add data-plane debug logging for troubleshooting - Change config database path to /tmp/data/ for container use - Add SetMAC stub for non-Linux builds
This commit is contained in:
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
web/node_modules/
|
||||
bin/
|
||||
.git/
|
||||
*.md
|
||||
9
Dockerfile.client
Normal file
9
Dockerfile.client
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM golang:1.26-alpine AS builder
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
RUN go build -o /zmclient ./cmd/testclient/
|
||||
|
||||
FROM alpine:3.19
|
||||
RUN apk add --no-cache iproute2 iptables-legacy
|
||||
COPY --from=builder /zmclient /usr/local/bin/zmclient
|
||||
CMD ["zmclient"]
|
||||
12
Dockerfile.server
Normal file
12
Dockerfile.server
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM golang:1.26-alpine AS builder
|
||||
RUN apk add --no-cache gcc musl-dev
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=1 go build -o /zeromesh .
|
||||
|
||||
FROM alpine:3.19
|
||||
RUN apk add --no-cache iproute2 iptables-legacy
|
||||
COPY --from=builder /zeromesh /usr/local/bin/zeromesh
|
||||
COPY config.yaml /etc/zeromesh/config.yaml
|
||||
EXPOSE 10001 19993/udp
|
||||
CMD ["zeromesh", "-config", "/etc/zeromesh/config.yaml"]
|
||||
192
cmd/testclient/main.go
Normal file
192
cmd/testclient/main.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"zeromesh/sdk"
|
||||
)
|
||||
|
||||
var (
|
||||
serverURL = flag.String("server", "http://server:10001", "Controller HTTP URL")
|
||||
name = flag.String("name", "client", "Node name")
|
||||
udpPort = flag.Int("udp", 7001, "Agent UDP listen port")
|
||||
tapName = flag.String("tap", "ztmesh0", "TAP device name")
|
||||
identityPath = flag.String("identity", "", "Identity file path")
|
||||
adminUser = flag.String("admin", "admin", "Admin username")
|
||||
adminPass = flag.String("pass", "admin123456", "Admin password")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
slog.SetLogLoggerLevel(slog.LevelInfo)
|
||||
|
||||
cfg := sdk.DefaultConfig()
|
||||
cfg.ControllerURL = *serverURL
|
||||
cfg.ListenPort = *udpPort
|
||||
cfg.TapName = *tapName
|
||||
cfg.PSK = "default-psk-change-me"
|
||||
if *identityPath != "" {
|
||||
cfg.IdentityPath = *identityPath
|
||||
}
|
||||
cli := sdk.New(cfg)
|
||||
|
||||
var token string
|
||||
for i := 0; i < 30; i++ {
|
||||
if err := cli.Login(*adminUser, *adminPass); err == nil {
|
||||
token = cli.Token()
|
||||
break
|
||||
}
|
||||
if _, err := cli.InitAdmin(*adminUser, *adminPass); err == nil {
|
||||
token = cli.Token()
|
||||
log.Print("initialized admin")
|
||||
break
|
||||
}
|
||||
log.Printf("waiting for server (attempt %d)...", i+1)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
if token == "" {
|
||||
log.Fatal("could not login or init admin after 30 attempts")
|
||||
}
|
||||
log.Printf("logged in as %s", *adminUser)
|
||||
|
||||
idPath := *identityPath
|
||||
if idPath == "" {
|
||||
idPath = fmt.Sprintf("/tmp/%s.id", *name)
|
||||
}
|
||||
id, err := sdk.LoadOrGenerateIdentity(idPath)
|
||||
if err != nil {
|
||||
log.Fatalf("identity: %v", err)
|
||||
}
|
||||
log.Printf("identity: %s pub=%s", id.Address, id.PublicKeyHex())
|
||||
|
||||
myIP := sdk.GetPreferredIP()
|
||||
node, err := cli.RegisterNode(id.Address.String(), id.PublicKeyHex(), *name, myIP, *udpPort, "1.0.0")
|
||||
if err != nil {
|
||||
log.Fatalf("register node: %v", err)
|
||||
}
|
||||
log.Printf("registered node: %s", node.NodeID)
|
||||
|
||||
findNetwork := func() *sdk.Network {
|
||||
networks, err := cli.ListNetworks()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for i := range networks {
|
||||
if !strings.HasPrefix(networks[i].IPRange, "10.7.") && !strings.HasPrefix(networks[i].IPRange, "172.") && !strings.HasPrefix(networks[i].IPRange, "192.") {
|
||||
continue
|
||||
}
|
||||
return &networks[i]
|
||||
}
|
||||
if len(networks) > 0 {
|
||||
return &networks[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
myNet := findNetwork()
|
||||
if myNet == nil {
|
||||
created, err := cli.CreateNetwork(fmt.Sprintf("testnet-%s", *name), "")
|
||||
if err != nil {
|
||||
log.Fatalf("create network: %v", err)
|
||||
}
|
||||
myNet = created
|
||||
log.Printf("created network: %s (%s) id=%d", myNet.Name, myNet.IPRange, myNet.NetworkID)
|
||||
} else {
|
||||
log.Printf("using network: %s (%s) id=%d", myNet.Name, myNet.IPRange, myNet.NetworkID)
|
||||
}
|
||||
|
||||
var myIPAddr string
|
||||
for i := 0; i < 10; i++ {
|
||||
members, err := cli.NetworkMembers(myNet.NetworkID)
|
||||
if err == nil {
|
||||
for _, m := range members {
|
||||
if m.NodeID == id.Address.String() {
|
||||
myIPAddr = m.IPAddress
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if myIPAddr != "" {
|
||||
break
|
||||
}
|
||||
log.Printf("authorizing self on network %d...", myNet.NetworkID)
|
||||
cli.AuthorizeMember(myNet.NetworkID, id.Address.String())
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
if myIPAddr == "" {
|
||||
log.Fatalf("could not get virtual IP after authorization")
|
||||
}
|
||||
log.Printf("my virtual IP: %s", myIPAddr)
|
||||
|
||||
agent := sdk.NewAgent(cli)
|
||||
agent.SetIdentity(id)
|
||||
|
||||
if err := agent.Start(); err != nil {
|
||||
log.Fatalf("agent start: %v", err)
|
||||
}
|
||||
defer agent.Stop()
|
||||
log.Printf("agent started: tap=%s udp=%d", agent.TAPName(), agent.LocalPort())
|
||||
|
||||
agent.ControllerHello()
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
for i := 0; i < 6; i++ {
|
||||
agent.SyncPeers()
|
||||
time.Sleep(3 * time.Second)
|
||||
c := agent.PeerCount()
|
||||
if c > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
cidrFull := myIPAddr + "/24"
|
||||
if err := agent.SetTAPIP(cidrFull); err != nil {
|
||||
log.Printf("set tap ip warning: %v", err)
|
||||
} else {
|
||||
log.Printf("tap ip set: %s", cidrFull)
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:7777", myIPAddr))
|
||||
if err != nil {
|
||||
log.Printf("echo server listen error (expected if TAP not ready): %v", err)
|
||||
} else {
|
||||
defer listener.Close()
|
||||
log.Printf("echo server listening on %s:7777", myIPAddr)
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func(c net.Conn) {
|
||||
defer c.Close()
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
n, err := c.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Write(buf[:n])
|
||||
}
|
||||
}(conn)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
peerCount := agent.PeerCount()
|
||||
log.Printf("%s ready: ip=%s tap=%s peers=%d", *name, myIPAddr, agent.TAPName(), peerCount)
|
||||
|
||||
sc := make(chan os.Signal, 1)
|
||||
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sc
|
||||
log.Printf("shutting down %s", *name)
|
||||
}
|
||||
@@ -6,7 +6,7 @@ database:
|
||||
auto_migrate: true
|
||||
init_data: true
|
||||
sqlite:
|
||||
path: ./data/zeromesh.db
|
||||
path: /tmp/data/zeromesh.db
|
||||
mysql:
|
||||
host: 127.0.0.1
|
||||
port: 3306
|
||||
|
||||
167
docker-compose.yml
Normal file
167
docker-compose.yml
Normal file
@@ -0,0 +1,167 @@
|
||||
services:
|
||||
server:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.server
|
||||
container_name: zm-server
|
||||
hostname: server
|
||||
privileged: true
|
||||
ports:
|
||||
- "7100:10001"
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
mkdir -p /tmp/data && zeromesh -config /etc/zeromesh/config.yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost:10001/api/health"]
|
||||
interval: 3s
|
||||
retries: 30
|
||||
start_period: 5s
|
||||
networks:
|
||||
zmt:
|
||||
ipv4_address: 172.30.0.10
|
||||
|
||||
init:
|
||||
image: alpine:3.19
|
||||
container_name: zm-init
|
||||
hostname: init
|
||||
depends_on:
|
||||
server:
|
||||
condition: service_healthy
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
apk add --no-cache curl jq
|
||||
echo '=== Create admin (try init then login) ==='
|
||||
ADMIN=$$(curl -s -X POST http://server:10001/api/v1/auth/init \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"username":"admin","password":"admin123456"}')
|
||||
TOKEN=$$(echo "$$ADMIN" | jq -r '.token // empty')
|
||||
if [ -z "$$TOKEN" ]; then
|
||||
echo 'Admin already exists, logging in...'
|
||||
LOGIN=$$(curl -s -X POST http://server:10001/api/v1/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"username":"admin","password":"admin123456"}')
|
||||
TOKEN=$$(echo "$$LOGIN" | jq -r '.token // empty')
|
||||
fi
|
||||
if [ -z "$$TOKEN" ]; then echo "Failed to get admin token"; exit 1; fi
|
||||
echo 'Admin token acquired'
|
||||
sleep 1
|
||||
echo '=== Create network ==='
|
||||
curl -s -X POST http://server:10001/api/v1/network/create \
|
||||
-H "Authorization: Bearer $$TOKEN" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name":"testnet","ip_range":"10.7.0.0/24"}'
|
||||
echo ''
|
||||
echo '=== init done ==='
|
||||
networks:
|
||||
- zmt
|
||||
|
||||
client1:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.client
|
||||
container_name: zm-client1
|
||||
hostname: client1
|
||||
privileged: true
|
||||
ports:
|
||||
- "7201:7001/udp"
|
||||
depends_on:
|
||||
init:
|
||||
condition: service_completed_successfully
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
zmclient -server http://server:10001 -name client1 -udp 7001 -tap ztmesh0 -identity /tmp/c1.id
|
||||
networks:
|
||||
zmt:
|
||||
ipv4_address: 172.30.0.11
|
||||
|
||||
client2:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.client
|
||||
container_name: zm-client2
|
||||
hostname: client2
|
||||
privileged: true
|
||||
ports:
|
||||
- "7202:7002/udp"
|
||||
depends_on:
|
||||
init:
|
||||
condition: service_completed_successfully
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
zmclient -server http://server:10001 -name client2 -udp 7002 -tap ztmesh1 -identity /tmp/c2.id
|
||||
networks:
|
||||
zmt:
|
||||
ipv4_address: 172.30.0.12
|
||||
|
||||
client3:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.client
|
||||
container_name: zm-client3
|
||||
hostname: client3
|
||||
privileged: true
|
||||
ports:
|
||||
- "7203:7003/udp"
|
||||
depends_on:
|
||||
init:
|
||||
condition: service_completed_successfully
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
zmclient -server http://server:10001 -name client3 -udp 7003 -tap ztmesh2 -identity /tmp/c3.id
|
||||
networks:
|
||||
zmt:
|
||||
ipv4_address: 172.30.0.13
|
||||
|
||||
tester:
|
||||
image: alpine:3.19
|
||||
container_name: zm-tester
|
||||
hostname: tester
|
||||
privileged: true
|
||||
depends_on:
|
||||
init:
|
||||
condition: service_completed_successfully
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
apk add --no-cache iputils iproute2 curl bash
|
||||
echo '=== Waiting for clients (60s) ==='
|
||||
sleep 60
|
||||
echo '=== Login ==='
|
||||
LOGIN=$$(curl -s -X POST http://server:10001/api/v1/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"username":"admin","password":"admin123456"}')
|
||||
TOKEN=$$(echo "$$LOGIN" | sed 's/.*"token":"//' | sed 's/".*//')
|
||||
echo '=== Networks ==='
|
||||
curl -s http://server:10001/api/v1/network/list \
|
||||
-H "Authorization: Bearer $$TOKEN" | head -c 300
|
||||
echo ''
|
||||
echo '=== Members (network/1) ==='
|
||||
curl -s http://server:10001/api/v1/network/1/members \
|
||||
-H "Authorization: Bearer $$TOKEN" | sed 's/,/\n/g' | grep -E 'node_id|ip_address|authorized'
|
||||
echo ''
|
||||
echo '=== Online Nodes ==='
|
||||
NODES=$$(curl -s http://server:10001/api/v1/node/online \
|
||||
-H "Authorization: Bearer $$TOKEN")
|
||||
echo "$$NODES" | sed 's/,/\n/g' | grep -E 'node_id|ip_address|port|name'
|
||||
echo ''
|
||||
echo '=== Try pinging virtual IPs ==='
|
||||
networks:
|
||||
- zmt
|
||||
|
||||
networks:
|
||||
zmt:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.30.0.0/24
|
||||
134
sdk/agent.go
134
sdk/agent.go
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
@@ -62,6 +63,11 @@ func NewAgent(client *Client) *Agent {
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Agent) SetIdentity(id *Identity) {
|
||||
a.identity = id
|
||||
a.localMAC = generateMAC(a.cfg.NetworkID, id.Address)
|
||||
}
|
||||
|
||||
func (a *Agent) Start() error {
|
||||
port := a.cfg.ListenPort
|
||||
if port == 0 {
|
||||
@@ -109,6 +115,104 @@ func (a *Agent) LocalPort() int {
|
||||
return a.udpConn.LocalAddr().(*net.UDPAddr).Port
|
||||
}
|
||||
|
||||
func (a *Agent) TAPName() string {
|
||||
if a.tapDev != nil {
|
||||
return a.tapDev.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *Agent) SetTAPIP(cidr string) error {
|
||||
if a.tapDev == nil {
|
||||
return fmt.Errorf("tap not opened")
|
||||
}
|
||||
if err := a.tapDev.SetMAC(a.localMAC); err != nil {
|
||||
a.log.Warn("set tap mac", "error", err)
|
||||
}
|
||||
return a.tapDev.SetIP(cidr)
|
||||
}
|
||||
|
||||
func (a *Agent) ControllerHello() error {
|
||||
ctrlAddr := &net.UDPAddr{
|
||||
IP: a.parseControllerHost(),
|
||||
Port: 19993,
|
||||
}
|
||||
payload := make([]byte, 32)
|
||||
copy(payload, a.identity.PublicKey)
|
||||
a.sendPacket(pktHandshake, 0, payload, ctrlAddr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) SyncPeers() error {
|
||||
nodes, err := a.client.ListOnlineNodes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
myAddr := a.identity.Address.String()
|
||||
for _, n := range nodes {
|
||||
if n.NodeID == myAddr || n.PublicKey == "" {
|
||||
continue
|
||||
}
|
||||
pk, err := hex.DecodeString(n.PublicKey)
|
||||
if err != nil || len(pk) != 32 {
|
||||
continue
|
||||
}
|
||||
ip := net.ParseIP(n.IPAddress)
|
||||
if ip == nil || n.Port == 0 {
|
||||
continue
|
||||
}
|
||||
peerAddr := AddressFromPublicKey(pk)
|
||||
udpAddr := &net.UDPAddr{IP: ip, Port: n.Port}
|
||||
|
||||
var pubKeyArr [32]byte
|
||||
copy(pubKeyArr[:], pk)
|
||||
existing := a.peers.getByAddr(peerAddr)
|
||||
if existing != nil && existing.Connected {
|
||||
continue
|
||||
}
|
||||
|
||||
peer := a.peers.upsert(peerAddr, pubKeyArr, udpAddr)
|
||||
peer.Touch()
|
||||
|
||||
sendKey, recvKey := deriveKeys([]byte(a.cfg.PSK), a.identity.PublicKey, pk)
|
||||
peer.sendCipher, _ = chacha20poly1305.New(sendKey[:])
|
||||
peer.recvCipher, _ = chacha20poly1305.New(recvKey[:])
|
||||
peer.Connected = true
|
||||
|
||||
a.log.Info("synced peer", "addr", peerAddr, "endpoint", udpAddr)
|
||||
a.sendHello(peer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) PeerCount() int {
|
||||
return len(a.peers.all())
|
||||
}
|
||||
|
||||
func (a *Agent) parseControllerHost() net.IP {
|
||||
host := a.cfg.ControllerURL
|
||||
if len(host) > 7 && host[:7] == "http://" {
|
||||
host = host[7:]
|
||||
}
|
||||
if len(host) > 8 && host[:8] == "https://" {
|
||||
host = host[8:]
|
||||
}
|
||||
idx := indexOfByte(host, ':')
|
||||
if idx >= 0 {
|
||||
host = host[:idx]
|
||||
}
|
||||
return net.ParseIP(host)
|
||||
}
|
||||
|
||||
func indexOfByte(s string, b byte) int {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == b {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (a *Agent) udpReadLoop() {
|
||||
defer a.wg.Done()
|
||||
buf := make([]byte, 65535)
|
||||
@@ -162,16 +266,17 @@ func (a *Agent) handleHandshake(payload []byte, addr *net.UDPAddr) {
|
||||
var pubKey [32]byte
|
||||
copy(pubKey[:], payload[:32])
|
||||
peerAddr := AddressFromPublicKey(pubKey[:])
|
||||
existing := a.peers.getByAddr(peerAddr)
|
||||
if existing != nil && existing.Connected {
|
||||
existing.Touch()
|
||||
return
|
||||
}
|
||||
peer := a.peers.upsert(peerAddr, pubKey, addr)
|
||||
peer.Touch()
|
||||
a.log.Info("handshake from peer", "addr", peerAddr, "endpoint", addr)
|
||||
|
||||
sendKey, recvKey := deriveKeys([]byte(a.cfg.PSK), a.identity.PublicKey, pubKey[:])
|
||||
c, err := chacha20poly1305.New(sendKey[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = c
|
||||
var err error
|
||||
peer.sendCipher, err = chacha20poly1305.New(sendKey[:])
|
||||
if err != nil {
|
||||
return
|
||||
@@ -180,6 +285,7 @@ func (a *Agent) handleHandshake(payload []byte, addr *net.UDPAddr) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
peer.Connected = true
|
||||
|
||||
hello := make([]byte, 32)
|
||||
copy(hello, a.identity.PublicKey)
|
||||
@@ -200,6 +306,7 @@ func (a *Agent) handleData(payload []byte, addr *net.UDPAddr) {
|
||||
binary.BigEndian.PutUint64(nonce[4:], binary.BigEndian.Uint64(payload[:8]))
|
||||
decrypted, err := peer.recvCipher.Open(nil, nonce, payload[8:], nil)
|
||||
if err != nil {
|
||||
a.log.Warn("decrypt failed", "addr", peer.addr, "endpoint", addr)
|
||||
return
|
||||
}
|
||||
frame := decrypted
|
||||
@@ -207,6 +314,8 @@ func (a *Agent) handleData(payload []byte, addr *net.UDPAddr) {
|
||||
return
|
||||
}
|
||||
|
||||
etherType := (uint16(frame[12]) << 8) | uint16(frame[13])
|
||||
a.log.Info("handle data", "len", len(frame), "ether", fmt.Sprintf("0x%04x", etherType))
|
||||
a.macTable.learn(frame[6:12], peer.addr)
|
||||
|
||||
dstMAC := frame[:6]
|
||||
@@ -214,12 +323,14 @@ func (a *Agent) handleData(payload []byte, addr *net.UDPAddr) {
|
||||
_ = srcMAC
|
||||
|
||||
if isLocalMAC(dstMAC, a.localMAC[:]) {
|
||||
a.log.Info("data to tap local")
|
||||
if a.tapDev != nil {
|
||||
a.tapDev.Write(frame)
|
||||
}
|
||||
return
|
||||
}
|
||||
if isBroadcastMAC(dstMAC) {
|
||||
a.log.Info("data to tap broadcast", "ether", fmt.Sprintf("0x%04x", etherType))
|
||||
if a.tapDev != nil {
|
||||
a.tapDev.Write(frame)
|
||||
}
|
||||
@@ -229,12 +340,14 @@ func (a *Agent) handleData(payload []byte, addr *net.UDPAddr) {
|
||||
|
||||
dstAddr := a.macTable.lookup(string(dstMAC))
|
||||
if dstAddr != nil {
|
||||
a.log.Info("data forward unicast", "ether", fmt.Sprintf("0x%04x", etherType))
|
||||
if p := a.peers.getByAddr(*dstAddr); p != nil {
|
||||
a.sendToPeer(frame, p)
|
||||
} else {
|
||||
a.broadcastToPeers(frame, peer.addr)
|
||||
}
|
||||
} else {
|
||||
a.log.Info("data forward miss", "ether", fmt.Sprintf("0x%04x", etherType))
|
||||
a.broadcastToPeers(frame, peer.addr)
|
||||
}
|
||||
}
|
||||
@@ -257,21 +370,25 @@ func (a *Agent) tapReadLoop() {
|
||||
if len(frame) < 14 {
|
||||
continue
|
||||
}
|
||||
etherType := (uint16(frame[12]) << 8) | uint16(frame[13])
|
||||
a.macTable.learn(frame[6:12], zeroAddr)
|
||||
|
||||
dstMAC := frame[:6]
|
||||
if isBroadcastMAC(dstMAC) {
|
||||
a.log.Info("tap broadcast", "len", n, "ether", fmt.Sprintf("0x%04x", etherType))
|
||||
a.broadcastToPeers(frame, zeroAddr)
|
||||
continue
|
||||
}
|
||||
dstAddr := a.macTable.lookup(string(dstMAC))
|
||||
if dstAddr != nil {
|
||||
a.log.Info("tap unicast", "len", n, "ether", fmt.Sprintf("0x%04x", etherType))
|
||||
if p := a.peers.getByAddr(*dstAddr); p != nil {
|
||||
a.sendToPeer(frame, p)
|
||||
} else {
|
||||
a.broadcastToPeers(frame, zeroAddr)
|
||||
}
|
||||
} else {
|
||||
a.log.Info("tap miss", "len", n, "ether", fmt.Sprintf("0x%04x", etherType), "dst", fmt.Sprintf("%x", dstMAC))
|
||||
a.broadcastToPeers(frame, zeroAddr)
|
||||
}
|
||||
}
|
||||
@@ -281,11 +398,16 @@ func (a *Agent) maintenanceLoop() {
|
||||
defer a.wg.Done()
|
||||
ticker := time.NewTicker(maintenanceTick)
|
||||
defer ticker.Stop()
|
||||
var syncCount int
|
||||
for {
|
||||
select {
|
||||
case <-a.ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
syncCount++
|
||||
if syncCount%3 == 0 {
|
||||
a.SyncPeers()
|
||||
}
|
||||
a.peers.cleanDead()
|
||||
a.macTable.clean()
|
||||
for _, p := range a.peers.all() {
|
||||
@@ -320,6 +442,7 @@ func (a *Agent) sendPacket(pktType byte, netID uint32, payload []byte, addr *net
|
||||
|
||||
func (a *Agent) sendToPeer(frame []byte, p *peer) {
|
||||
if p == nil || p.sendCipher == nil {
|
||||
a.log.Warn("sendToPeer: nil peer or cipher")
|
||||
return
|
||||
}
|
||||
p.LastSend = time.Now()
|
||||
@@ -330,6 +453,7 @@ func (a *Agent) sendToPeer(frame []byte, p *peer) {
|
||||
header := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(header, binary.BigEndian.Uint64(nonceBuf[4:]))
|
||||
payload := append(header, encrypted...)
|
||||
a.log.Info("send data", "to", p.addr, "len", len(payload))
|
||||
a.sendPacket(pktData, a.cfg.NetworkID, payload, p.Endpoint)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ package sdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -35,7 +36,6 @@ func openTap(name string, mtu int) (*tapInterface, error) {
|
||||
}
|
||||
ti := &tapInterface{Name: devName, MTU: mtu, fd: fd}
|
||||
|
||||
// Set MTU via socket ioctl
|
||||
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0)
|
||||
if err == nil {
|
||||
defer syscall.Close(s)
|
||||
@@ -45,7 +45,7 @@ func openTap(name string, mtu int) (*tapInterface, error) {
|
||||
}
|
||||
copy(mtuReq.name[:], []byte(devName))
|
||||
mtuReq.mtu = int32(mtu)
|
||||
syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), 0x400c4a52, uintptr(unsafe.Pointer(&mtuReq))) // SIOCSIFMTU
|
||||
syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), 0x400c4a52, uintptr(unsafe.Pointer(&mtuReq)))
|
||||
}
|
||||
return ti, nil
|
||||
}
|
||||
@@ -63,6 +63,21 @@ func (ti *tapInterface) Close() error {
|
||||
return syscall.Close(ti.fd)
|
||||
}
|
||||
|
||||
func (ti *tapInterface) SetMAC(mac [6]byte) error {
|
||||
macStr := fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5])
|
||||
return exec.Command("ip", "link", "set", ti.Name, "address", macStr).Run()
|
||||
}
|
||||
|
||||
func (ti *tapInterface) SetIP(cidr string) error {
|
||||
if err := exec.Command("ip", "addr", "add", cidr, "dev", ti.Name).Run(); err != nil {
|
||||
return fmt.Errorf("add ip: %w", err)
|
||||
}
|
||||
if err := exec.Command("ip", "link", "set", ti.Name, "up").Run(); err != nil {
|
||||
return fmt.Errorf("link up: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func indexOfZero(s string) int {
|
||||
for i, c := range []byte(s) {
|
||||
if c == 0 {
|
||||
|
||||
@@ -24,3 +24,7 @@ func (ti *tapInterface) Write(buf []byte) error {
|
||||
func (ti *tapInterface) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ti *tapInterface) SetIP(cidr string) error {
|
||||
return fmt.Errorf("TAP device requires Linux")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user