# 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 <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:

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", "--",
    "/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

```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