7.8 KiB
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:
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 <pid> 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:
- Host PID namespace (
--pid host) — to know the PID of the target container (obtained via Docker SDKContainerInspect().State.Pid) - Host network namespace (
--network host) — so the program can also manage host-level iptables chains (DOCKER-USER, FORWARD, POSTROUTING) - NET_ADMIN capability — iptables manipulation requires this Linux capability
- SYS_ADMIN capability —
nsenterneeds 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
{
"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:
- Enable IP forwarding — writes
1to/proc/sys/net/ipv4/ip_forward - Ensure Docker networks — creates bridge networks with specified subnet/gateway
- Connect containers — attaches containers to networks with assigned static IPs
- 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:
nsenter -t $(docker inspect --format {{.State.Pid}} $NAME) -n -- /sbin/iptables-legacy -t nat -I PREROUTING ...
The Go implementation does the same:
// 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", "--",
"/sbin/iptables-legacy", "-t", "nat", "-I", "PREROUTING", ...)
-t <pid>— 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
# 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 \
network-go