Skip to content

Commit 43c3daa

Browse files
committed
Add data-exchange network policy and multi-network stream fix
- Fix SYN-ACK/RST source address for multi-network connections: use pkt.Dst instead of d.Addr() so responses match the network- specific address the connection was opened with - Fix policy runner bootstrap: always refresh peers from registry on startup, not just for policies with cycle rules - Add admin-token registry paths for pilotctl network join --node-id, member-tags set, and policy set - Add data-exchange-policy.json: service-node gated connectivity with text messaging (port 1000) open and file transfer (port 1001) restricted to service-tagged nodes - Add integration test (6 subtests) covering connect, dial, datagram policy enforcement
1 parent 12be2cb commit 43c3daa

5 files changed

Lines changed: 377 additions & 21 deletions

File tree

cmd/pilotctl/main.go

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4384,15 +4384,30 @@ func cmdNetworkList() {
43844384

43854385
func cmdNetworkJoin(args []string) {
43864386
if len(args) < 1 {
4387-
fatalCode("invalid_argument", "usage: pilotctl network join <network_id> [--token TOKEN]")
4387+
fatalCode("invalid_argument", "usage: pilotctl network join <network_id> [--token TOKEN] [--node-id N]")
43884388
}
43894389
netID := parseUint16(args[0], "network_id")
4390-
token := ""
4391-
for i := 1; i < len(args)-1; i++ {
4392-
if args[i] == "--token" {
4393-
token = args[i+1]
4394-
break
4390+
flags, _ := parseFlags(args[1:])
4391+
token := flagString(flags, "token", "")
4392+
nodeIDStr := flagString(flags, "node-id", "")
4393+
4394+
// Admin path: --node-id joins a remote node directly via registry
4395+
if nodeIDStr != "" {
4396+
nodeID := parseNodeID(nodeIDStr)
4397+
adminToken := requireAdminToken()
4398+
rc := connectRegistry()
4399+
defer rc.Close()
4400+
4401+
result, err := rc.JoinNetwork(nodeID, netID, token, 0, adminToken)
4402+
if err != nil {
4403+
fatalCode("connection_failed", "network join: %v", err)
43954404
}
4405+
if jsonOutput {
4406+
output(result)
4407+
} else {
4408+
fmt.Printf("joined node %d to network %d\n", nodeID, netID)
4409+
}
4410+
return
43964411
}
43974412

43984413
d := connectDriver()
@@ -5303,7 +5318,11 @@ func cmdPolicySet(args []string) {
53035318
reg := connectRegistry()
53045319
defer reg.Close()
53055320

5306-
_, err = reg.SetExprPolicy(netID, policyJSON, flagString(flags, "admin-token", ""))
5321+
adminToken := flagString(flags, "admin-token", "")
5322+
if adminToken == "" {
5323+
adminToken = getAdminToken()
5324+
}
5325+
_, err = reg.SetExprPolicy(netID, policyJSON, adminToken)
53075326
if err != nil {
53085327
fatalCode("connection_failed", "set policy on registry: %v", err)
53095328
}
@@ -5488,6 +5507,23 @@ func cmdMemberTagsSet(args []string) {
54885507

54895508
tags := strings.Split(tagsStr, ",")
54905509

5510+
// If admin token is available, go directly to registry (no daemon needed)
5511+
if adminToken := getAdminToken(); adminToken != "" {
5512+
rc := connectRegistry()
5513+
defer rc.Close()
5514+
5515+
result, err := rc.SetMemberTags(netID, uint32(nid), tags, adminToken)
5516+
if err != nil {
5517+
fatalCode("connection_failed", "member-tags set: %v", err)
5518+
}
5519+
if jsonOutput {
5520+
output(result)
5521+
return
5522+
}
5523+
fmt.Printf("Member tags set for node %d in network %d: %s\n", uint32(nid), netID, strings.Join(tags, ", "))
5524+
return
5525+
}
5526+
54915527
d := connectDriver()
54925528
defer d.Close()
54935529

data-exchange-policy.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"version": 1,
3+
"rules": [
4+
{
5+
"name": "service-connect",
6+
"on": "connect",
7+
"match": "has_tag(local_tags, \"service\") || has_tag(peer_tags, \"service\")",
8+
"actions": [{"type": "allow"}]
9+
},
10+
{
11+
"name": "service-dial",
12+
"on": "dial",
13+
"match": "has_tag(local_tags, \"service\") || has_tag(peer_tags, \"service\")",
14+
"actions": [{"type": "allow"}]
15+
},
16+
{
17+
"name": "block-peer-connect",
18+
"on": "connect",
19+
"match": "true",
20+
"actions": [{"type": "deny"}]
21+
},
22+
{
23+
"name": "block-peer-dial",
24+
"on": "dial",
25+
"match": "true",
26+
"actions": [{"type": "deny"}]
27+
},
28+
{
29+
"name": "allow-text",
30+
"on": "datagram",
31+
"match": "port == 1000",
32+
"actions": [{"type": "allow"}]
33+
},
34+
{
35+
"name": "allow-service-files",
36+
"on": "datagram",
37+
"match": "port == 1001 && ((direction == \"out\" && has_tag(local_tags, \"service\")) || (direction == \"in\" && has_tag(peer_tags, \"service\")))",
38+
"actions": [{"type": "allow"}]
39+
},
40+
{
41+
"name": "deny-other-data",
42+
"on": "datagram",
43+
"match": "true",
44+
"actions": [{"type": "deny"}]
45+
}
46+
]
47+
}

pkg/daemon/daemon.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,7 +1323,7 @@ func (d *Daemon) handleStreamPacket(pkt *protocol.Packet) {
13231323
Version: protocol.Version,
13241324
Flags: protocol.FlagSYN | protocol.FlagACK,
13251325
Protocol: protocol.ProtoStream,
1326-
Src: d.Addr(),
1326+
Src: pkt.Dst,
13271327
Dst: pkt.Src,
13281328
SrcPort: pkt.DstPort,
13291329
DstPort: pkt.SrcPort,
@@ -1396,7 +1396,11 @@ func (d *Daemon) handleStreamPacket(pkt *protocol.Packet) {
13961396

13971397
conn := d.ports.NewConnection(pkt.DstPort, pkt.Src, pkt.SrcPort)
13981398
conn.Mu.Lock()
1399-
conn.LocalAddr = d.Addr()
1399+
// Use the destination address from the SYN as our local address.
1400+
// This ensures the correct network-specific address is used for
1401+
// multi-network connections (e.g. 1:0001.0000.0003 instead of
1402+
// the primary 0:0000.0000.0003).
1403+
conn.LocalAddr = pkt.Dst
14001404
conn.State = StateSynReceived
14011405
conn.RecvAck = pkt.Seq + 1
14021406
conn.ExpectedSeq = pkt.Seq + 1 // first data segment after SYN
@@ -1417,7 +1421,7 @@ func (d *Daemon) handleStreamPacket(pkt *protocol.Packet) {
14171421
Version: protocol.Version,
14181422
Flags: protocol.FlagSYN | protocol.FlagACK,
14191423
Protocol: protocol.ProtoStream,
1420-
Src: d.Addr(),
1424+
Src: pkt.Dst,
14211425
Dst: pkt.Src,
14221426
SrcPort: pkt.DstPort,
14231427
DstPort: pkt.SrcPort,
@@ -1727,7 +1731,7 @@ func (d *Daemon) sendRST(orig *protocol.Packet) {
17271731
Version: protocol.Version,
17281732
Flags: protocol.FlagRST,
17291733
Protocol: protocol.ProtoStream,
1730-
Src: d.Addr(),
1734+
Src: orig.Dst,
17311735
Dst: orig.Src,
17321736
SrcPort: orig.DstPort,
17331737
DstPort: orig.SrcPort,

pkg/daemon/policy_runner.go

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -353,16 +353,11 @@ func (pr *PolicyRunner) executeWebhook(d policy.Directive) {
353353
func (pr *PolicyRunner) cycleLoop() {
354354
defer close(pr.done)
355355

356-
// Bootstrap if no peers and the policy has cycle rules
357-
if pr.compiled.HasRulesFor(policy.EventCycle) {
358-
pr.mu.RLock()
359-
needBootstrap := len(pr.peers) == 0
360-
pr.mu.RUnlock()
361-
if needBootstrap {
362-
if err := pr.bootstrap(); err != nil {
363-
slog.Warn("policy: bootstrap failed", "network_id", pr.netID, "err", err)
364-
}
365-
}
356+
// Always bootstrap from registry to refresh peer list and tags.
357+
// Persisted state preserves scores/history, but membership and tags
358+
// may have changed since last run.
359+
if err := pr.bootstrap(); err != nil {
360+
slog.Warn("policy: bootstrap failed", "network_id", pr.netID, "err", err)
366361
}
367362

368363
cycleStr, _ := pr.compiled.CycleDuration()

0 commit comments

Comments
 (0)