Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/entrypoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ func Bootstrap(centralPath, nodePath, logPath string, verbose bool, opts state.N
if logPath != "" {
nodeCfg.LogPath = logPath
}
if opts.AdvertiseExitNodeSet {
nodeCfg.AdvertiseExitNode = opts.AdvertiseExitNode
}
if opts.ExitNodeSet {
nodeCfg.ExitNode = opts.ExitNode
}

state.ExpandCentralConfig(centralCfg)
if err = state.CentralConfigValidator(centralCfg); err != nil {
Expand Down
14 changes: 14 additions & 0 deletions core/nylon.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ type Nylon struct {
PingBuf *ttlcache.Cache[uint64, EpPing]
PeerMap atomic.Pointer[map[state.NyPublicKey]state.NodeId]

// NodeIdMap maps NodeId<->binary node id, refreshed on every central
// config apply. Read on the dataplane to encode and decode unicast
// packet headers without referencing the live CentralCfg.
NodeIdMap atomic.Pointer[state.NodeIdMap]

// ExitFilter holds the immutable per-packet state needed by the exit
// filter, snapshotted on every config apply. The filter reads only
// this pointer, never the live CentralCfg or LocalCfg, since access
// to those off the dispatch goroutine would otherwise require locks.
ExitFilter atomic.Pointer[ExitFilterSnapshot]

router struct {
LastStarvationRequest time.Time
IO map[state.NodeId]*IOPending
Expand Down Expand Up @@ -176,6 +187,9 @@ func (n *Nylon) Init() error {
if err != nil {
return err
}
if err := n.refreshNodeBindings(); err != nil {
return err
}

n.PingBuf = ttlcache.New[uint64, EpPing](
ttlcache.WithTTL[uint64, EpPing](5*time.Second),
Expand Down
4 changes: 4 additions & 0 deletions core/nylon_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ func (n *Nylon) ApplyCentralConfig(cfg *state.CentralCfg) (ApplyResult, error) {
n.reconcileAdvertisedPrefixes(next)
n.CentralCfg = *next

if err := n.refreshNodeBindings(); err != nil {
return ApplyRejected, err
}

if err := n.SyncWireGuard(); err != nil {
return ApplyRejected, err
}
Expand Down
82 changes: 82 additions & 0 deletions core/nylon_tc.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,53 @@ const (
func (n *Nylon) InstallTC() {
t := n.Trace

// exit-encap filter: outbound IP packets that fall off our routing
// table get wrapped in a NyUnicast / exit packet bound for the
// configured exit node. Installed before the generic forwarder so
// that locally-originated traffic to "the internet" gets captured
// here first. Reads only the atomic ExitFilter snapshot, never the
// live LocalCfg or CentralCfg.
n.Device.InstallFilter(func(dev *device.Device, packet *device.TCElement) (device.TCAction, error) {
snap := n.ExitFilter.Load()
if snap == nil || snap.ExitNodeBin == state.InvalidNodeIdBin {
return device.TcPass, nil
}
if packet.Incoming() || !packet.Validate() {
return device.TcPass, nil
}
ver := packet.GetIPVersion()
if ver != 4 && ver != 6 {
return device.TcPass, nil
}
dst := packet.GetDst()
if _, ok := n.router.ForwardTable.Load().Lookup(dst); ok {
return device.TcPass, nil // overlay route exists; keep normal routing
}
if state.IsDefaultLocalExcludedAddr(dst) {
return device.TcDrop, nil
}
entry, ok := snap.NodeForward[snap.ExitNodeBin]
if !ok || entry.Peer == nil {
if n.DBG_trace_tc {
t.Submit(fmt.Sprintf("ExitDrop: %v -> %v, exit %s, reason no_route\n", packet.GetSrc(), dst, snap.ExitNode))
}
return device.TcDrop, nil
}
src := packet.GetSrc()
if err := wrapExitPacket(packet, snap.ExitNodeBin, snap.LocalIdBin); err != nil {
if n.DBG_trace_tc {
t.Submit(fmt.Sprintf("ExitDrop: %v -> %v, exit %s, reason %v\n", src, dst, snap.ExitNode, err))
}
return device.TcDrop, nil
}
packet.ToPeer = entry.Peer
packet.Priority = device.TcMediumPriority
if n.DBG_trace_tc {
t.Submit(fmt.Sprintf("ExitEncap: %v -> %v, exit %s via %s\n", src, dst, snap.ExitNode, entry.Nh))
}
return device.TcForward, nil
})

if n.DBG_trace_tc {
n.Device.InstallFilter(func(dev *device.Device, packet *device.TCElement) (device.TCAction, error) {
if packet.Validate() { // make sure it's an IP packet
Expand Down Expand Up @@ -128,6 +175,41 @@ func (n *Nylon) InstallTC() {
}
return device.TcPass, nil
})

// handle incoming NyUnicast packets. Installed last so that under
// reverse-installation evaluation it runs first; this ensures
// inbound exit-transit packets get re-forwarded before any of the
// IP-routing filters above ever see them.
n.Device.InstallFilter(func(dev *device.Device, packet *device.TCElement) (device.TCAction, error) {
if !packet.Incoming() || packet.GetIPVersion() != NyUnicastProtoId {
return device.TcPass, nil
}
snap := n.ExitFilter.Load()
if snap == nil {
return device.TcDrop, nil
}
payload := packet.Payload()
h, err := parseNyUnicastHeader(payload)
if err != nil {
if n.DBG_trace_tc {
t.Submit(fmt.Sprintf("ExitDrop: malformed header: %v\n", err))
}
return device.TcDrop, nil
}
switch h.subtype {
case NyUnicastSubtypeExit:
action, err := n.handleExitPacket(packet, snap, h)
if err != nil && n.DBG_trace_tc {
t.Submit(fmt.Sprintf("ExitDrop: reason %v\n", err))
}
return action, err
default:
if n.DBG_trace_tc {
t.Submit(fmt.Sprintf("ExitDrop: unknown unicast subtype %d\n", h.subtype))
}
return device.TcDrop, nil
}
})
}

func (n *Nylon) SendNylon(pkt *protocol.Ny, endpoint conn.Endpoint, peer *device.Peer) error {
Expand Down
Loading
Loading