# network-go — Docker Network & Firewall Manager A Go-based replacement for the `firewall/firewall-add` shell script. It watches `/etc/user/config/networks.json` for changes and reconciles Docker networks, container connections, and iptables firewall rules — all via the Docker SDK and `nsenter` (no Docker CLI calls). ## Project Structure ``` network-go/ ├── main.go Entry point: watches config, orchestrates reconciliation ├── config/ │ └── config.go Parses /etc/user/config/networks.json into typed structs ├── docker/ │ └── docker.go Docker SDK wrapper: network create, container connect, │ container PID for nsenter, route management ├── firewall/ │ └── firewall.go Orchestrator: translates policies → iptables rules ├── iptables/ │ └── iptables.go Manages iptables CLI: PREROUTING DNAT, POSTROUTING MASQUERADE, │ FORWARD ACCEPT, nsenter for container network namespaces ├── resolver/ │ └── resolver.go Resolves names → IPs using networks.json config only ├── watcher/ │ └── watcher.go Periodic file change detection via MD5 hash polling ├── go.mod / go.sum Module definition with Docker SDK dependency └── .gitignore ``` ## Docker Run — Required Environment & Volumes This program is designed to run inside a Docker container with the following setup: ```bash docker run -d \ --name network-go \ --network host \ --cap-add NET_ADMIN \ --cap-add SYS_ADMIN \ --pid host \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /etc/user/config:/etc/user/config \ -v /proc/sys/net/ipv4/ip_forward:/proc/sys/net/ipv4/ip_forward \ network-go ``` ### Required Volumes | Mount | Purpose | |---|---| | `/var/run/docker.sock:/var/run/docker.sock` | Docker SDK communication (create networks, inspect containers, get PIDs for nsenter) | | `/etc/user/config:/etc/user/config` | Access to `networks.json` configuration file | ### Required Flags | Flag | Purpose | |---|---| | `--network host` | Run in the host network namespace so iptables rules apply to the host | | `--pid host` | Access host PIDs for `nsenter -t ` into container network namespaces | | `--cap-add NET_ADMIN` | Required to manipulate iptables rules | | `--cap-add SYS_ADMIN` | Required for `nsenter` to enter other containers' namespaces | ### Environment Variables | Variable | Default | Description | |---|---|---| | `NETWORKS_CONFIG_PATH` | `/etc/user/config/networks.json` | Path to the configuration file (inside the container) | | `WATCH_PERIOD_SECONDS` | `30` | Polling interval in seconds for config file changes | | `DEBUG` | `false` | Enable debug output (`1` or `true`) | | `DOCKER_HOST` | (empty = `/var/run/docker.sock`) | Docker daemon socket — automatically used by Docker SDK | ### Why `--network host` and `--pid host`? The program uses `nsenter` to enter other containers' network namespaces and insert iptables rules. This requires: 1. **Host PID namespace** (`--pid host`) — to know the PID of the target container (obtained via Docker SDK `ContainerInspect().State.Pid`) 2. **Host network namespace** (`--network host`) — so the program can also manage host-level iptables chains (DOCKER-USER, FORWARD, POSTROUTING) 3. **NET_ADMIN capability** — iptables manipulation requires this Linux capability 4. **SYS_ADMIN capability** — `nsenter` needs this to switch namespaces Without these, the program cannot: - List/manage host iptables rules - Insert PREROUTING/POSTROUTING rules inside other containers - Add routes to container network namespaces ## Configuration — `/etc/user/config/networks.json` ```json { "networks": { "smarthost-loadbalancer": { "network_name": "smarthost-loadbalancer", "subnet": "172.18.103.0/24", "gateway": "172.18.103.1" }, "smarthost_backend-1": { "network_name": "smarthost_backend-1", "subnet": "172.18.104.0/24", "gateway": "172.18.104.1" } }, "ips": { "172.18.103.2": { "ip": "172.18.103.2", "container_name": "smarthostloadbalancer", "selector": "smarthostloadbalancer", "service_name": "smarthost-proxy" }, "172.18.104.2": { "ip": "172.18.104.2", "container_name": "smarthostbackend-1", "selector": "smarthostbackend-1", "service_name": "smarthost-proxy" } }, "policies": [ { "service_name": "smarthost-proxy", "container_name": "smarthost_loadbalancer", "selector": "smarthostloadbalancer", "from": "publicbackend", "port": 80, "proto": "tcp" }, { "service_name": "smarthost-proxy", "container_name": "smarthost_loadbalancer", "selector": "smarthostloadbalancer", "name": "wireguardproxy", "iface": "wg0", "nat": "dnat", "to": "smarthostloadbalancer", "port": 80, "proto": "tcp" } ] } ``` ## How Reconciliation Works When the config file changes, `Orchestrator.ReconcileAll()` runs: 1. **Enable IP forwarding** — writes `1` to `/proc/sys/net/ipv4/ip_forward` 2. **Ensure Docker networks** — creates bridge networks with specified subnet/gateway 3. **Connect containers** — attaches containers to networks with assigned static IPs 4. **Apply firewall policies** — processes each policy entry into iptables rules: ### Policy Types | Policy Pattern | Action | Target | |---|---|---| | `from` field present | FORWARD ACCEPT rule | Host chain: DOCKER-USER (iptables-legacy) or FORWARD | | `nat: "dnat"` with `iface` | PREROUTING DNAT on interface | Host PREROUTING chain | | `nat: "dnat"` with `selector` | PREROUTING DNAT inside container | Container namespace via nsenter | ### nsenter Implementation The shell script uses: ```bash nsenter -t $(docker inspect --format {{.State.Pid}} $NAME) -n -- /sbin/iptables-legacy -t nat -I PREROUTING ... ``` The Go implementation does the same: ```go // 1. Get container PID via Docker SDK pid, _ := dockerClient.GetContainerPID(ctx, containerName) // 2. Execute iptables inside container namespace via nsenter exec.Command("nsenter", "-t", fmt.Sprintf("%d", pid), "-n", "--", "/usr/sbin/iptables-legacy", "-t", "nat", "-I", "PREROUTING", ...) ``` - `-t ` — target the container's PID - `-n` — enter the network namespace only - `--` — separator, then the iptables command ## Packages ### config `NetworksConfig`, `NetworkConfig`, `IPConfig`, `PolicyConfig` structs. Helpers: `IsIP`, `ToCIDR`, `NetworkPrefix`, `ParseCIDR`, `Load`. ### docker Docker SDK wrapper: `EnsureNetwork`, `ConnectContainer`, `DisconnectContainer`, `GetContainerPID`, `AddRouteInContainer`, `WaitForContainerRunning`, `InspectContainer`. ### resolver Name→IP resolution using `networks.json` config only. Looks up by `container_name` and `selector` fields in the `ips` section, with prefix fallback matching. ### iptables Auto-detects `iptables-legacy` vs `iptables`. Manages: - PREROUTING DNAT (host and container via nsenter) - POSTROUTING MASQUERADE (host and container) - FORWARD/DOCKER-USER ACCEPT with ESTABLISHED,RELATED - Rule deletion by line-number + pattern matching ### firewall `Orchestrator` ties all packages together. `ReconcileAll()` runs the full cycle. Policy→rule mapping: `from` → FORWARD ACCEPT, `nat: dnat` → PREROUTING DNAT. ### watcher `FileWatcher` polls a file via MD5 hash comparison. `Start`/`Stop` lifecycle. ## Build & Run ```bash # Build cd network-go go build -o network-go . # Run locally (requires Docker socket access) ./network-go # Build and run in Docker docker build -t network-go . docker run -d \ --network host \ --pid host \ --cap-add NET_ADMIN \ --cap-add SYS_ADMIN \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /etc/user/config:/etc/user/config \ -e WATCH_PERIOD_SECONDS=30 \ -e DEBUG=false \ --name network-go \ safebox/network-go