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:
xieyao
2026-06-17 05:04:19 +08:00
parent e3147ac06f
commit 807c4cf323
9 changed files with 535 additions and 8 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
web/node_modules/
bin/
.git/
*.md

9
Dockerfile.client Normal file
View 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
View 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
View 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)
}

View File

@@ -6,7 +6,7 @@ database:
auto_migrate: true auto_migrate: true
init_data: true init_data: true
sqlite: sqlite:
path: ./data/zeromesh.db path: /tmp/data/zeromesh.db
mysql: mysql:
host: 127.0.0.1 host: 127.0.0.1
port: 3306 port: 3306

167
docker-compose.yml Normal file
View 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

View File

@@ -5,6 +5,7 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
@@ -62,6 +63,11 @@ func NewAgent(client *Client) *Agent {
return a return a
} }
func (a *Agent) SetIdentity(id *Identity) {
a.identity = id
a.localMAC = generateMAC(a.cfg.NetworkID, id.Address)
}
func (a *Agent) Start() error { func (a *Agent) Start() error {
port := a.cfg.ListenPort port := a.cfg.ListenPort
if port == 0 { if port == 0 {
@@ -109,6 +115,104 @@ func (a *Agent) LocalPort() int {
return a.udpConn.LocalAddr().(*net.UDPAddr).Port 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() { func (a *Agent) udpReadLoop() {
defer a.wg.Done() defer a.wg.Done()
buf := make([]byte, 65535) buf := make([]byte, 65535)
@@ -162,16 +266,17 @@ func (a *Agent) handleHandshake(payload []byte, addr *net.UDPAddr) {
var pubKey [32]byte var pubKey [32]byte
copy(pubKey[:], payload[:32]) copy(pubKey[:], payload[:32])
peerAddr := AddressFromPublicKey(pubKey[:]) peerAddr := AddressFromPublicKey(pubKey[:])
existing := a.peers.getByAddr(peerAddr)
if existing != nil && existing.Connected {
existing.Touch()
return
}
peer := a.peers.upsert(peerAddr, pubKey, addr) peer := a.peers.upsert(peerAddr, pubKey, addr)
peer.Touch() peer.Touch()
a.log.Info("handshake from peer", "addr", peerAddr, "endpoint", addr) a.log.Info("handshake from peer", "addr", peerAddr, "endpoint", addr)
sendKey, recvKey := deriveKeys([]byte(a.cfg.PSK), a.identity.PublicKey, pubKey[:]) sendKey, recvKey := deriveKeys([]byte(a.cfg.PSK), a.identity.PublicKey, pubKey[:])
c, err := chacha20poly1305.New(sendKey[:]) var err error
if err != nil {
return
}
_ = c
peer.sendCipher, err = chacha20poly1305.New(sendKey[:]) peer.sendCipher, err = chacha20poly1305.New(sendKey[:])
if err != nil { if err != nil {
return return
@@ -180,6 +285,7 @@ func (a *Agent) handleHandshake(payload []byte, addr *net.UDPAddr) {
if err != nil { if err != nil {
return return
} }
peer.Connected = true
hello := make([]byte, 32) hello := make([]byte, 32)
copy(hello, a.identity.PublicKey) 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])) binary.BigEndian.PutUint64(nonce[4:], binary.BigEndian.Uint64(payload[:8]))
decrypted, err := peer.recvCipher.Open(nil, nonce, payload[8:], nil) decrypted, err := peer.recvCipher.Open(nil, nonce, payload[8:], nil)
if err != nil { if err != nil {
a.log.Warn("decrypt failed", "addr", peer.addr, "endpoint", addr)
return return
} }
frame := decrypted frame := decrypted
@@ -207,6 +314,8 @@ func (a *Agent) handleData(payload []byte, addr *net.UDPAddr) {
return 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) a.macTable.learn(frame[6:12], peer.addr)
dstMAC := frame[:6] dstMAC := frame[:6]
@@ -214,12 +323,14 @@ func (a *Agent) handleData(payload []byte, addr *net.UDPAddr) {
_ = srcMAC _ = srcMAC
if isLocalMAC(dstMAC, a.localMAC[:]) { if isLocalMAC(dstMAC, a.localMAC[:]) {
a.log.Info("data to tap local")
if a.tapDev != nil { if a.tapDev != nil {
a.tapDev.Write(frame) a.tapDev.Write(frame)
} }
return return
} }
if isBroadcastMAC(dstMAC) { if isBroadcastMAC(dstMAC) {
a.log.Info("data to tap broadcast", "ether", fmt.Sprintf("0x%04x", etherType))
if a.tapDev != nil { if a.tapDev != nil {
a.tapDev.Write(frame) a.tapDev.Write(frame)
} }
@@ -229,12 +340,14 @@ func (a *Agent) handleData(payload []byte, addr *net.UDPAddr) {
dstAddr := a.macTable.lookup(string(dstMAC)) dstAddr := a.macTable.lookup(string(dstMAC))
if dstAddr != nil { if dstAddr != nil {
a.log.Info("data forward unicast", "ether", fmt.Sprintf("0x%04x", etherType))
if p := a.peers.getByAddr(*dstAddr); p != nil { if p := a.peers.getByAddr(*dstAddr); p != nil {
a.sendToPeer(frame, p) a.sendToPeer(frame, p)
} else { } else {
a.broadcastToPeers(frame, peer.addr) a.broadcastToPeers(frame, peer.addr)
} }
} else { } else {
a.log.Info("data forward miss", "ether", fmt.Sprintf("0x%04x", etherType))
a.broadcastToPeers(frame, peer.addr) a.broadcastToPeers(frame, peer.addr)
} }
} }
@@ -257,21 +370,25 @@ func (a *Agent) tapReadLoop() {
if len(frame) < 14 { if len(frame) < 14 {
continue continue
} }
etherType := (uint16(frame[12]) << 8) | uint16(frame[13])
a.macTable.learn(frame[6:12], zeroAddr) a.macTable.learn(frame[6:12], zeroAddr)
dstMAC := frame[:6] dstMAC := frame[:6]
if isBroadcastMAC(dstMAC) { if isBroadcastMAC(dstMAC) {
a.log.Info("tap broadcast", "len", n, "ether", fmt.Sprintf("0x%04x", etherType))
a.broadcastToPeers(frame, zeroAddr) a.broadcastToPeers(frame, zeroAddr)
continue continue
} }
dstAddr := a.macTable.lookup(string(dstMAC)) dstAddr := a.macTable.lookup(string(dstMAC))
if dstAddr != nil { if dstAddr != nil {
a.log.Info("tap unicast", "len", n, "ether", fmt.Sprintf("0x%04x", etherType))
if p := a.peers.getByAddr(*dstAddr); p != nil { if p := a.peers.getByAddr(*dstAddr); p != nil {
a.sendToPeer(frame, p) a.sendToPeer(frame, p)
} else { } else {
a.broadcastToPeers(frame, zeroAddr) a.broadcastToPeers(frame, zeroAddr)
} }
} else { } else {
a.log.Info("tap miss", "len", n, "ether", fmt.Sprintf("0x%04x", etherType), "dst", fmt.Sprintf("%x", dstMAC))
a.broadcastToPeers(frame, zeroAddr) a.broadcastToPeers(frame, zeroAddr)
} }
} }
@@ -281,11 +398,16 @@ func (a *Agent) maintenanceLoop() {
defer a.wg.Done() defer a.wg.Done()
ticker := time.NewTicker(maintenanceTick) ticker := time.NewTicker(maintenanceTick)
defer ticker.Stop() defer ticker.Stop()
var syncCount int
for { for {
select { select {
case <-a.ctx.Done(): case <-a.ctx.Done():
return return
case <-ticker.C: case <-ticker.C:
syncCount++
if syncCount%3 == 0 {
a.SyncPeers()
}
a.peers.cleanDead() a.peers.cleanDead()
a.macTable.clean() a.macTable.clean()
for _, p := range a.peers.all() { 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) { func (a *Agent) sendToPeer(frame []byte, p *peer) {
if p == nil || p.sendCipher == nil { if p == nil || p.sendCipher == nil {
a.log.Warn("sendToPeer: nil peer or cipher")
return return
} }
p.LastSend = time.Now() p.LastSend = time.Now()
@@ -330,6 +453,7 @@ func (a *Agent) sendToPeer(frame []byte, p *peer) {
header := make([]byte, 8) header := make([]byte, 8)
binary.BigEndian.PutUint64(header, binary.BigEndian.Uint64(nonceBuf[4:])) binary.BigEndian.PutUint64(header, binary.BigEndian.Uint64(nonceBuf[4:]))
payload := append(header, encrypted...) payload := append(header, encrypted...)
a.log.Info("send data", "to", p.addr, "len", len(payload))
a.sendPacket(pktData, a.cfg.NetworkID, payload, p.Endpoint) a.sendPacket(pktData, a.cfg.NetworkID, payload, p.Endpoint)
} }

View File

@@ -4,6 +4,7 @@ package sdk
import ( import (
"fmt" "fmt"
"os/exec"
"syscall" "syscall"
"unsafe" "unsafe"
) )
@@ -35,7 +36,6 @@ func openTap(name string, mtu int) (*tapInterface, error) {
} }
ti := &tapInterface{Name: devName, MTU: mtu, fd: fd} ti := &tapInterface{Name: devName, MTU: mtu, fd: fd}
// Set MTU via socket ioctl
s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0) s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0)
if err == nil { if err == nil {
defer syscall.Close(s) defer syscall.Close(s)
@@ -45,7 +45,7 @@ func openTap(name string, mtu int) (*tapInterface, error) {
} }
copy(mtuReq.name[:], []byte(devName)) copy(mtuReq.name[:], []byte(devName))
mtuReq.mtu = int32(mtu) 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 return ti, nil
} }
@@ -63,6 +63,21 @@ func (ti *tapInterface) Close() error {
return syscall.Close(ti.fd) 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 { func indexOfZero(s string) int {
for i, c := range []byte(s) { for i, c := range []byte(s) {
if c == 0 { if c == 0 {

View File

@@ -24,3 +24,7 @@ func (ti *tapInterface) Write(buf []byte) error {
func (ti *tapInterface) Close() error { func (ti *tapInterface) Close() error {
return nil return nil
} }
func (ti *tapInterface) SetIP(cidr string) error {
return fmt.Errorf("TAP device requires Linux")
}