# Uncloud L4


Natively [Uncloud](https://uncloud.run) doesn't do L4 forwarding. This is an [open
issue](https://github.com/psviderski/uncloud/issues/108) as we are figuring out how to actually do this. But
it turns out you can already do this with the current code - well almost.

Buckle up, because there is quite some stuff you may need:

1. A patched Uncloud, see [this PR](https://github.com/psviderski/uncloud/pull/333) for that.
2. A custom [Caddy](https://caddyserver.com) build, with the [L4](https://github.com/mholt/caddy-l4) plugin.
3. A DNS server, I use [atomdns](https://atomdns.miek.nl) of course.

# Caddy

With the following Dockerfile you create a custom caddy image that we will use.

```Dockerfile
FROM caddy:2.11.2-builder AS builder
RUN xcaddy build \
    --with github.com/mholt/caddy-l4

FROM caddy:2.11.2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
```

The compose.yaml is remarkably short, but we also need a Caddyfile, both are listed here, the Caddyfile first,
this tells caddy to forward the DNS traffic to the internal address of the DNS server.

```Caddyfile
{
        layer4 {
                udp/:53 {
                        route {
                                proxy udp/atomdns.internal:53
                        }
                }
                tcp/:53 {
                        route {
                                proxy tcp/atomdns.internal:53
                        }
                }
        }
}
```

And the compose.yaml:

```yaml
services:
  caddy:
    image: registry.science.ru.nl/cncz/sys/image/caddy:latest
    command: caddy run -c /config/Caddyfile
    environment:
      CADDY_ADMIN: unix//run/caddy/admin.sock
    volumes:
      - /var/lib/uncloud/caddy:/data
      - /var/lib/uncloud/caddy:/config
      - /run/uncloud/caddy:/run/caddy
    x-ports:
      - 80:80@host
      - 443:443@host
      - 443:443/udp@host
      - enp1s0:53:53@host
      - enp1s0:53:53/udp@host
    x-caddy: Caddyfile
    deploy:
      mode: global
```

Where I want to highlight `enp1s0:53:53@host` and `enp1s0:53:53/udp@host` which is what the pull requests
adds. As the interface name is shared on all machines, the caddy service can still be globally deployed. If I
were to bind on all interfaces (the default for Uncloud) I trample on `uncloudd` _own_ use of the port 53.

If you are in the directory where you saved the above files you can just run `uc deploy -y` and have caddy
running.

# atomdns

We use this Dockerfile to build atomdns ourselves.

```Dockerfile
ARG BUILD_IMAGE=golang:latest
ARG BASE=alpine:latest

FROM --platform=$BUILDPLATFORM ${BUILD_IMAGE} AS build
WORKDIR /tmp
RUN git clone --depth 1 https://codeberg.org/miekg/dns
WORKDIR /tmp/dns/cmd/atomdns
RUN go mod download
RUN CGO_ENABLED=0 go build

FROM ${BASE}
COPY --from=build /tmp/dns/cmd/atomdns/atomdns /atomdns
WORKDIR /
EXPOSE 53 53/udp 443
ENTRYPOINT ["/atomdns"]
```

The compose file a longer, and includes the Conffile for atomdns to use:

```yaml
# This compose file configures a atomdns that can be used as the cluster-dns' server.
services:
  atomdns:
    image: registry.science.ru.nl/cncz/sys/image/atomdns:latest
    pull_policy: always
    configs:
      - source: atomdns_config
        target: /etc/Conffile
        mode: 0444
    command:
      - /etc/Conffile
    x-ports:
      - ctrl.u.science.ru.nl:443/https

    deploy:
      replicas: 1

    volumes:
      - atomdns_data:/var/lib/atomdns

volumes:
  atomdns_data:
    labels:
      nl.ru.science.backup: "kopia"

configs:
  atomdns_config:
    content: |
      {
          log
          root /var/lib/atomdns
          dns {
              addr [::]:53
          }
      }

      u.science.ru.nl {
          uncloud u.science.ru.nl.db {
              addr [::]:443
          }
          dbsqlite u.science.ru.nl.db
      }

      # add example.org for debugging purposes
      example.org {
        whoami
        log
      }
```

Also deploy that. Note because the database (sqlite) isn't shared, this can only run on a single machine. Also
the example.org zone is there to help in debugging.

When running the above you can send DNS queries to your cluster and they will get answers

```
% uc ls
NAME            MODE         REPLICAS   IMAGE                                                  ENDPOINTS
atomdns         replicated   1          registry.science.ru.nl/cncz/sys/image/atomdns:latest   https://ctrl.u.science.ru.nl → :443
caddy           global       2          registry.science.ru.nl/cncz/sys/image/caddy:latest     (custom Caddy config)
debug           replicated   1          registry.science.ru.nl/cncz/sys/image/debug:latest
```

And `dig @uncloud1.vm.science.ru.nl a whoami.example.org` returns an answer.

