Skip to content
Merged
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
10 changes: 3 additions & 7 deletions .github/workflows/go-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ on:
jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

Expand All @@ -24,9 +23,6 @@ jobs:
- name: Build
run: go build

- name: Run unit tests with coverage
run: bash unit-test-coverage.sh
- name: Run integration tests
run: go test -v -tags=integration ./...
- name: Run smoke tests
run: bash smoke/smoke-test.sh
- name: Run all tests
run: go test -v -tags=router_test,integration,e2e ./...

36 changes: 36 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
FROM golang:1.25.4 AS builder
WORKDIR /src

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o /nylon .

FROM scratch

# Copy binary from builder
COPY --from=builder /nylon /usr/local/bin/nylon

WORKDIR /app/config

ENTRYPOINT ["/usr/local/bin/nylon", "run"]

FROM ubuntu:latest AS debug

ARG DEBIAN_FRONTEND=noninteractive

RUN apt-get update && \
apt-get install -y --no-install-recommends \
iputils-ping \
iperf3 \
curl \
iproute2 \
net-tools \
tcpdump \
dnsutils \
netcat-openbsd && \
rm -rf /var/lib/apt/lists/*

COPY --from=builder /nylon /usr/local/bin/nylon

WORKDIR /app/config

ENTRYPOINT ["/usr/local/bin/nylon", "run", "-v", "-w", "--dbg-trace-tc"]
1 change: 1 addition & 0 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func init() {
runCmd.Flags().BoolVarP(&state.DBG_log_repo_updates, "dbg-repo", "", false, "Outputs repo updates to the console")
runCmd.Flags().BoolVarP(&state.DBG_debug, "dbg-perf", "", false, "Enables performance debugging server on port 6060")
runCmd.Flags().BoolVarP(&state.DBG_trace, "dbg-trace", "", false, "Enables trace to trace.out")
runCmd.Flags().BoolVarP(&state.DBG_trace_tc, "dbg-trace-tc", "", false, "Enables logging of packet routing")
runCmd.Flags().StringP("config", "c", DefaultConfigPath, "Path to the config file")
runCmd.Flags().StringP("node", "n", DefaultNodeConfigPath, "Path to the node config file")
runCmd.Flags().StringP("log", "l", "", "Path to the log file (overrides config)")
Expand Down
19 changes: 19 additions & 0 deletions core/ipc.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,25 @@ func HandleNylonIPCGet(s *state.State, rw *bufio.ReadWriter) error {
}
slices.Sort(rt)
sb.WriteString(strings.Join(rt, "\n") + "\n")

// print forward table
sb.WriteString("\n\nForward Table:\n")
rt = make([]string, 0)
for prefix, route := range Get[*NylonRouter](s).ForwardTable.All() {
rt = append(rt, fmt.Sprintf(" - %s via %s", prefix, route.Nh))
}
slices.Sort(rt)
sb.WriteString(strings.Join(rt, "\n") + "\n")

// print exit table
sb.WriteString("\n\nExit Table:\n")
rt = make([]string, 0)
for prefix, route := range Get[*NylonRouter](s).ExitTable.All() {
rt = append(rt, fmt.Sprintf(" - %s via %s", prefix, route.Nh))
}
slices.Sort(rt)
sb.WriteString(strings.Join(rt, "\n") + "\n")

sb.WriteRune(0)
_, err = rw.WriteString(sb.String())
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion core/nylon.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (n *Nylon) Init(s *state.State) error {
}
stNeigh := &state.Neighbour{
Id: peer,
Routes: make(map[state.Source]state.NeighRoute),
Routes: make(map[netip.Prefix]state.NeighRoute),
Eps: make([]state.Endpoint, 0),
}
cfg := s.GetRouter(peer)
Expand Down
33 changes: 32 additions & 1 deletion core/nylon_tc.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package core

import (
"net/netip"

"github.com/encodeous/nylon/polyamide/conn"
"github.com/encodeous/nylon/polyamide/device"
"github.com/encodeous/nylon/protocol"
Expand All @@ -17,6 +19,27 @@ const (
func (n *Nylon) InstallTC(s *state.State) {
r := Get[*NylonRouter](s)

if state.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
peer := packet.FromPeer
if peer == nil {
peer = packet.ToPeer
}
src := packet.GetSrc()
dst := packet.GetDst()
if src.IsValid() &&
dst.IsValid() &&
peer != nil &&
src != netip.IPv4Unspecified() && src != netip.IPv6Unspecified() &&
dst != netip.IPv4Unspecified() && dst != netip.IPv6Unspecified() {
dev.Log.Verbosef("Unhandled TC packet: %v -> %v, peer %s", packet.GetSrc(), packet.GetDst(), peer)
}
}
return device.TcPass, nil
})
}

// bounce back packets if using system routing
if n.env.UseSystemRouting {
n.Device.InstallFilter(func(dev *device.Device, packet *device.TCElement) (device.TCAction, error) {
Expand All @@ -32,7 +55,9 @@ func (n *Nylon) InstallTC(s *state.State) {
entry, ok := r.ForwardTable.Lookup(packet.GetDst())
if ok && !packet.Incoming() {
packet.ToPeer = entry.Peer
//dev.Log.Verbosef("Fwd packet: %v -> %v, via %s", packet.GetSrc(), packet.GetDst(), entry.Nh)
if state.DBG_trace_tc {
dev.Log.Verbosef("Fwd packet: %v -> %v, via %s", packet.GetSrc(), packet.GetDst(), entry.Nh)
}
return device.TcForward, nil
}
return device.TcPass, nil
Expand All @@ -43,6 +68,9 @@ func (n *Nylon) InstallTC(s *state.State) {
entry, ok := r.ForwardTable.Lookup(packet.GetDst())
if ok {
packet.ToPeer = entry.Peer
if state.DBG_trace_tc {
dev.Log.Verbosef("Fwd packet: %v -> %v, via %s", packet.GetSrc(), packet.GetDst(), entry.Nh)
}
return device.TcForward, nil
}
return device.TcPass, nil
Expand All @@ -58,6 +86,9 @@ func (n *Nylon) InstallTC(s *state.State) {
packet.DecrementTTL()
}
if ttl == 0 {
if state.DBG_trace_tc {
dev.Log.Verbosef("TTL Expired: %v -> %v, via %s", packet.GetSrc(), packet.GetDst())
}
return device.TcBounce, nil
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/nylon_wireguard.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func (n *Nylon) cleanupWireGuard(s *state.State) error {
}
}
// run pre-down commands
for _, cmd := range s.PreUp {
for _, cmd := range s.PreDown {
err := ExecSplit(s.Log, cmd)
if err != nil {
s.Log.Error("failed to run pre-down command", "err", err)
Expand Down
2 changes: 1 addition & 1 deletion core/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ func (r *NylonRouter) Init(s *state.State) error {
func (r *NylonRouter) ComputeSysRouteTable() []netip.Prefix {
prefixes := make([]netip.Prefix, 0)
selectedSelf := make(map[netip.Prefix]struct{})
for entry, v := range r.ForwardTable.All() {
for entry, v := range r.Routes {
prefixes = append(prefixes, entry)
if v.Nh == r.Id {
selectedSelf[entry] = struct{}{}
Expand Down
16 changes: 7 additions & 9 deletions core/router_algo.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func RunGC(s *state.RouterState, r Router) {
for src := range s.Sources {
found := false
for _, neigh := range s.Neighbours {
if _, ok := neigh.Routes[src]; ok {
if nSrc, ok := neigh.Routes[src.Prefix]; ok && nSrc.Source == src {
found = true
break
}
Expand Down Expand Up @@ -289,7 +289,7 @@ func HandleNeighbourUpdate(s *state.RouterState, r Router, neighId state.NodeId,

n := s.GetNeighbour(neighId)

_, ok := n.Routes[adv.Source]
_, ok := n.Routes[adv.Prefix]

if adv.Metric == state.INF {
r.SendAckRetract(neighId, adv.Source.Prefix)
Expand Down Expand Up @@ -317,7 +317,7 @@ func HandleNeighbourUpdate(s *state.RouterState, r Router, neighId state.NodeId,
// metric carried by the update.

// create the route
n.Routes[adv.Source] = state.NeighRoute{
n.Routes[adv.Prefix] = state.NeighRoute{
PubRoute: adv,
ExpireAt: time.Now().Add(state.RouteExpiryTime),
}
Expand Down Expand Up @@ -366,13 +366,13 @@ func HandleNeighbourUpdate(s *state.RouterState, r Router, neighId state.NodeId,
// update (possibly a retraction) MUST be sent in a timely manner as
// described in Section 3.7.2.

nr := n.Routes[adv.Source]
nr := n.Routes[adv.Prefix]
nr.PubRoute = adv

if adv.Metric != state.INF {
nr.ExpireAt = time.Now().Add(state.RouteExpiryTime)
}
n.Routes[adv.Source] = nr
n.Routes[adv.Prefix] = nr
}
}

Expand Down Expand Up @@ -502,9 +502,7 @@ func ComputeRoutes(s *state.RouterState, r Router) {
}

// enumerate through neighbour advertisements
for S, adv := range neigh.Routes {
prefix := S.Prefix

for prefix, adv := range neigh.Routes {
// Cost(A, B) + Cost(S, B)
totalCost := AddMetric(CAB, adv.Metric)

Expand Down Expand Up @@ -568,7 +566,7 @@ func ComputeRoutes(s *state.RouterState, r Router) {

for prefix, newRoute := range newTable {
oldRoute, exists := s.Routes[prefix]
if !exists {
if !exists || oldRoute.Metric == state.INF {
r.TableInsertRoute(prefix, newRoute)
r.Log(RouteChanged, "inserted", "prefix", prefix, "new", newRoute)
} else if oldRoute.Nh != newRoute.Nh {
Expand Down
2 changes: 1 addition & 1 deletion core/router_harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func MakeNeighbours(ids ...state.NodeId) []*state.Neighbour {
for _, id := range ids {
neighs = append(neighs, &state.Neighbour{
Id: id,
Routes: make(map[state.Source]state.NeighRoute),
Routes: make(map[netip.Prefix]state.NeighRoute),
})
}
return neighs
Expand Down
1 change: 1 addition & 0 deletions e2e/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
runs
88 changes: 88 additions & 0 deletions e2e/connectivity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//go:build e2e

package e2e

import (
"testing"
"time"

"github.com/encodeous/nylon/state"
)

func TestConnectivity(t *testing.T) {
if testing.Short() {
t.Skip("skipping e2e test in short mode")
}
t.Parallel()

// Use a specific subnet for this test to avoid conflicts
h := NewHarness(t)

// Generate keys
node1Key := state.GenerateKey()
node2Key := state.GenerateKey()
node3Key := state.GenerateKey()

// IPs in the docker network
node1IP := GetIP(h.Subnet, 10)
node2IP := GetIP(h.Subnet, 11)
node3IP := GetIP(h.Subnet, 12)

// Internal Nylon IPs
node1NylonIP := "10.0.0.1"
node2NylonIP := "10.0.0.2"
node3NylonIP := "10.0.0.3"

// Create config directory for this test run
configDir := h.SetupTestDir()

// 1. Create Central Config
central := state.CentralCfg{
Routers: []state.RouterCfg{
SimpleRouter("node1", node1Key.Pubkey(), node1NylonIP, ""),
SimpleRouter("node2", node2Key.Pubkey(), node2NylonIP, node2IP),
SimpleRouter("node3", node3Key.Pubkey(), node3NylonIP, ""),
},
Graph: []string{
"node1, node2",
"node2, node3",
},
Timestamp: time.Now().UnixNano(),
}

centralPath := h.WriteConfig(configDir, "central.yaml", central)

// 2. Create Node Configs
node1Cfg := SimpleLocal("node1", node1Key)
node1Path := h.WriteConfig(configDir, "node1.yaml", node1Cfg)

node2Cfg := SimpleLocal("node2", node2Key)
node2Path := h.WriteConfig(configDir, "node2.yaml", node2Cfg)

node3Cfg := SimpleLocal("node3", node3Key)
node3Path := h.WriteConfig(configDir, "node3.yaml", node3Cfg)

// 4. Start Containers in Parallel
h.StartNodes(
NodeSpec{Name: "node1", IP: node1IP, CentralConfigPath: centralPath, NodeConfigPath: node1Path},
NodeSpec{Name: "node2", IP: node2IP, CentralConfigPath: centralPath, NodeConfigPath: node2Path},
NodeSpec{Name: "node3", IP: node3IP, CentralConfigPath: centralPath, NodeConfigPath: node3Path},
)

// 5. Wait for convergence
t.Log("Waiting for convergence...")
h.WaitForLog("node3", "installing new route prefix=10.0.0.1/32")
h.WaitForLog("node1", "installing new route prefix=10.0.0.2/31")

// 6. Test Connectivity
// Ping from node1 to node2's Nylon IP
t.Logf("Pinging %s from node1...", node2NylonIP)
stdout, stderr, err := h.Exec("node3", []string{"ping", "-c", "3", node1NylonIP})
if err != nil {
h.PrintLogs("node1")
h.PrintLogs("node2")
h.PrintLogs("node3")
t.Fatalf("Ping failed: %v\nStdout: %s\nStderr: %s", err, stdout, stderr)
}
t.Logf("Ping output:\n%s", stdout)
}
Loading