commit 8a8e05fb95f946337b16c0122ba702295eb87c1f Author: Johannes Kimmel Date: Mon Sep 18 20:26:28 2023 +0200 auto-l3: init diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a20c2b1 --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ +# Optional: Static IP of the vxlan endpoint to use for the container +AUTO_L3_ADDRESS= + +# Router IPv6 within the network +AUTO_L3_IPV6=2001:db8:0:: + +# Address pool start address +AUTO_L3_PREFIX=2001:db8:1:: + +# Address pool size +AUTO_L3_PREFIX_LEN=48 + +# Delegated prefix length +AUTO_L3_DELEGATED_LEN=62 + +# Babel rxcost for peers +AUTO_L3_RXCOST_PEERING=96 + +# Babel rxcost for clients +AUTO_L3_RXCOST_VXLAN=4096 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d23bcb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/wireguard +.env diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..6492e4f --- /dev/null +++ b/Containerfile @@ -0,0 +1,16 @@ +FROM ghcr.io/void-linux/void-musl-busybox AS auto-l3-bootstrap +RUN xbps-install -Suy xbps; \ + xbps-install -uy shadow; + +FROM auto-l3-bootstrap AS auto-l3 +ENV SVDIR=/auto-l3/sv +STOPSIGNAL SIGHUP +COPY auto-l3 /auto-l3 +RUN xbps-install -y runit-void gettext iptables-nft iproute2 wireguard-tools bird kea; \ + xbps-alternatives -g iptables -s iptables-nft; \ + echo "200 auto-l3" >> /etc/iproute2/rt_tables; \ + echo "1 auto-l3-wireguard" >> /etc/iproute2/group; \ + mkdir -p -m 700 /auto-l3/wireguard + +ENTRYPOINT ["/auto-l3/entrypoint.sh"] +CMD ["runsvdir", "-P", "/auto-l3/sv"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2dd4cc --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# auto-l3 + +## Configuration + +### Environment Variables + +First, copy the example environment file [`.env.example`](<.env.example>) to `.env`. + +```bash +cp .env.example .env +``` + +`.env` is a special file that is read automatically by `docker-compose` or `podman-compose`. + +### Network + +**TODO** + +> - `macvlan` or `ipvlan` recommended +> - Set `AUTO_L3_ADDRESS` to a static IPv6 address, that is reachable for vxlan clients + +#### Wireguard Peers + +Wireguard configuration files can be placed into the [`wireguard/`]() folder. See `man 8 wg` for the format. +Files should be named like `babel-$peer.conf` to be automatically used for babel peering. + +```bash +cp wireguard/skel wireguard/babel-peer1.conf +``` + +## Build + +```bash +docker-compose build +``` +```bash +podman-compose build +``` + +## Run + +Use `docker-compose up` or `podman-compose up` to start the container diff --git a/auto-l3/entrypoint.sh b/auto-l3/entrypoint.sh new file mode 100755 index 0000000..5a8a7de --- /dev/null +++ b/auto-l3/entrypoint.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +set -ex + +: ${AUTO_L3_IPV6:?} +: ${AUTO_L3_PREFIX:?} +: ${AUTO_L3_PREFIX_LEN:?} +: ${AUTO_L3_DELEGATED_LEN:?} +: ${AUTO_L3_RXCOST_PEERING:?} +: ${AUTO_L3_RXCOST_VXLAN:?} + +ip -6 rule add table local prio 1000 +ip -4 rule add table local prio 1000 +ip -6 rule del table local prio 0 +ip -4 rule del table local prio 0 + +ip link add dev auto-l3 up type vrf table auto-l3 +ip address add "${AUTO_L3_IPV6}" dev auto-l3 + +ip link add dev vxlan0 up mtu 1412 master auto-l3 up \ + type vxlan id 243 dev eth0 local "::" dstport 4789 srcport 4789 4790 ageing 30 + +ip -6 rule del l3mdev +ip -4 rule del l3mdev +ip -6 rule add l3mdev prio 0 +ip -4 rule add l3mdev prio 0 +ip -6 rule add l3mdev prio 100 unreachable +ip -4 rule add l3mdev prio 100 unreachable + +ip -c -6 rule +ip -c -4 rule +ip -c link + +mkdir -p /run/runit + +exec "$@" diff --git a/auto-l3/sv/bird/run b/auto-l3/sv/bird/run new file mode 100755 index 0000000..a81c0a5 --- /dev/null +++ b/auto-l3/sv/bird/run @@ -0,0 +1,14 @@ +#!/bin/sh +exec 2>&1 + +SHELL_FORMAT=' +${AUTO_L3_PREFIX}: +${AUTO_L3_PREFIX_LEN}: +${AUTO_L3_DELEGATED_LEN}: +${AUTO_L3_RXCOST_PEERING}: +${AUTO_L3_RXCOST_VXLAN} +' + +envsubst "${SHELL_FORMAT}" < /auto-l3/templates/bird.conf > /tmp/auto-l3-bird.conf + +exec bird -f -u _bird -g _bird -c /tmp/auto-l3-bird.conf diff --git a/auto-l3/sv/bird/supervise b/auto-l3/sv/bird/supervise new file mode 120000 index 0000000..d1ba0a1 --- /dev/null +++ b/auto-l3/sv/bird/supervise @@ -0,0 +1 @@ +/run/runit/supervise.bird \ No newline at end of file diff --git a/auto-l3/sv/kea-dhcpv6/run b/auto-l3/sv/kea-dhcpv6/run new file mode 100755 index 0000000..93a12cb --- /dev/null +++ b/auto-l3/sv/kea-dhcpv6/run @@ -0,0 +1,13 @@ +#!/bin/sh +exec 2>&1 + +SHELL_FORMAT=' +${AUTO_L3_PREFIX}: +${AUTO_L3_PREFIX_LEN}: +${AUTO_L3_DELEGATED_LEN}: +' + +envsubst "${SHELL_FORMAT}" < /auto-l3/templates/kea.conf > /tmp/auto-l3-kea.conf + +mkdir -p /run/kea /var/lib/kea +exec kea-dhcp6 -c /tmp/auto-l3-kea.conf diff --git a/auto-l3/sv/kea-dhcpv6/supervise b/auto-l3/sv/kea-dhcpv6/supervise new file mode 120000 index 0000000..c355234 --- /dev/null +++ b/auto-l3/sv/kea-dhcpv6/supervise @@ -0,0 +1 @@ +/run/runit/supervise.kea-dhcp6 \ No newline at end of file diff --git a/auto-l3/sv/vxmon/run b/auto-l3/sv/vxmon/run new file mode 100755 index 0000000..fbe664d --- /dev/null +++ b/auto-l3/sv/vxmon/run @@ -0,0 +1,53 @@ +#!/bin/sh + +DEV=${1:-vxlan0} + +handle_add() { + mac="${1:?}" + dev="${2:?}" + dst="${3:?}" + + echo "bridge fdb append 00:00:00:00:00:00 dst $dst dev $dev" + bridge fdb append 00:00:00:00:00:00 dst "$dst" dev "$dev" +} + +handle_cleanup() { + mac="${1:?}" + dev="${2:?}" + dst="${3:?}" + + if ! bridge fdb show dev "$dev" | grep -v 00:00:00:00:00:00 | grep "$dst"; then + bridge fdb del 00:00:00:00:00:00 dst "$dst" dev "$dev" + fi +} + +handle_entry() { + cmd=handle_add + + if [ "$1" = "Deleted" ]; then + shift + cmd=handle_cleanup + fi + + mac="${1:?}" + dev="${3:?}" + + if [ "$dev" != "$DEV" ]; then + return + fi + + if [ "$mac" = "00:00:00:00:00:00" ]; then + echo "skipping" "$@" + return + fi + + $cmd "$mac" "$3" "$5" +} + +main() { + bridge monitor fdb | while read args; do + handle_entry $args + done +} + +main diff --git a/auto-l3/sv/vxmon/supervise b/auto-l3/sv/vxmon/supervise new file mode 120000 index 0000000..2f1721b --- /dev/null +++ b/auto-l3/sv/vxmon/supervise @@ -0,0 +1 @@ +/run/runit/supervise.vxmon \ No newline at end of file diff --git a/auto-l3/sv/wireguard/finish b/auto-l3/sv/wireguard/finish new file mode 100755 index 0000000..1de1f92 --- /dev/null +++ b/auto-l3/sv/wireguard/finish @@ -0,0 +1,5 @@ +#!/bin/sh + +set -ex + +ip link delete group auto-l3-wireguard diff --git a/auto-l3/sv/wireguard/run b/auto-l3/sv/wireguard/run new file mode 100755 index 0000000..b763564 --- /dev/null +++ b/auto-l3/sv/wireguard/run @@ -0,0 +1,22 @@ +#!/bin/sh + +set -ex + +for CONF in /auto-l3/wireguard/*.conf; do + [ -r "$CONF" ] || continue + + IFACE=$(basename "$CONF" .conf) + ip link add dev "$IFACE" \ + mtu 1412 \ + master auto-l3 \ + group auto-l3-wireguard \ + type wireguard + wg setconf "$IFACE" "$CONF" + LL=$(wg show "$IFACE" public-key | base64 -d | hexdump -vn8 -e'3/2 "%04x:" "%04x" 1 "\n"') + ip address add "fe80::${LL}/64" dev "$IFACE" + ip link set "$IFACE" up +done + +ip -c link show group auto-l3-wireguard + +exec chpst -b wireguard pause diff --git a/auto-l3/sv/wireguard/supervise b/auto-l3/sv/wireguard/supervise new file mode 120000 index 0000000..0a84c62 --- /dev/null +++ b/auto-l3/sv/wireguard/supervise @@ -0,0 +1 @@ +/run/runit/supervise.wireguard \ No newline at end of file diff --git a/auto-l3/templates/bird.conf b/auto-l3/templates/bird.conf new file mode 100644 index 0000000..ada113e --- /dev/null +++ b/auto-l3/templates/bird.conf @@ -0,0 +1,48 @@ +router id 0.0.0.1; + +ipv6 sadr table auto_l3_v6; + +protocol device {}; + +protocol direct { + ipv6 sadr { + table auto_l3_v6; + }; + interface "auto-l3"; +} + +protocol kernel { + kernel table 200; + netlink rx buffer 16777216; + ipv6 sadr { + table auto_l3_v6; + # export everything but the unreachable static route + # the correct route is set during boot + export where (source != RTS_STATIC); + }; +} + +protocol babel { + ipv6 sadr { + table auto_l3_v6; + import all; + export where source ~ [ RTS_DEVICE, RTS_BABEL, RTS_STATIC ]; + }; + + randomize router id on; + + interface "babel*" { rxcost ${AUTO_L3_RXCOST_PEERING}; }; +} + +protocol babel { + ipv6 sadr { + table auto_l3_v6; + import keep filtered on; + import where net ~ ${AUTO_L3_PREFIX}/${AUTO_L3_PREFIX_LEN} from ::/0 && net.len >= ${AUTO_L3_DELEGATED_LEN}; + export all; + }; + + randomize router id on; + + interface "vxlan0" { rxcost ${AUTO_L3_RXCOST_VXLAN}; }; +} diff --git a/auto-l3/templates/kea.conf b/auto-l3/templates/kea.conf new file mode 100644 index 0000000..3f694c4 --- /dev/null +++ b/auto-l3/templates/kea.conf @@ -0,0 +1,228 @@ +// This is a basic configuration for the Kea DHCPv6 server. Subnet declarations +// are mostly commented out and no interfaces are listed. Therefore, the servers +// will not listen or respond to any queries. +// The basic configuration must be extended to specify interfaces on which +// the servers should listen. There are a number of example options defined. +// These probably don't make any sense in your network. Make sure you at least +// update the following, before running this example in your network: +// - change the network interface names +// - change the subnets to match your actual network +// - change the option values to match your network +// +// This is just a very basic configuration. Kea comes with large suite (over 30) +// of configuration examples and extensive Kea User's Guide. Please refer to +// those materials to get better understanding of what this software is able to +// do. Comments in this configuration file sometimes refer to sections for more +// details. These are section numbers in Kea User's Guide. The version matching +// your software should come with your Kea package, but it is also available +// in ISC's Knowledgebase (https://kea.readthedocs.io; the direct link for +// the stable version is https://kea.readthedocs.io/). +// +// This configuration file contains only DHCPv6 server's configuration. +// If configurations for other Kea services are also included in this file they +// are ignored by the DHCPv6 server. +{ + +// DHCPv6 configuration starts here. This section will be read by DHCPv6 server +// and will be ignored by other components. +"Dhcp6": { + // Add names of your network interfaces to listen on. + "interfaces-config": { + // You typically want to put specific interface names here, e.g. eth0 + // but you can also specify unicast addresses (e.g. eth0/2001:db8::1) if + // you want your server to handle unicast traffic in addition to + // multicast. (DHCPv6 is a multicast based protocol). + "interfaces": [ "vxlan0" ], + "service-sockets-max-retries": 50, + "service-sockets-retry-wait-time": 1000 + }, + + // Use Memfile lease database backend to store leases in a CSV file. + // Depending on how Kea was compiled, it may also support SQL databases + // (MySQL and/or PostgreSQL). Those database backends require more + // parameters, like name, host and possibly user and password. + // There are dedicated examples for each backend. See Section 8.2.2 "Lease + // Storage" for details. + "lease-database": { + // Memfile is the simplest and easiest backend to use. It's an in-memory + // C++ database that stores its state in CSV file. + "type": "memfile", + "lfc-interval": 3600, + "name": "/tmp/dhcp6.leases" + }, + + // These parameters govern global timers. Addresses will be assigned with + // preferred and valid lifetimes being 3000 and 4000, respectively. Client + // is told to start renewing after 1000 seconds. If the server does not + // respond after 2000 seconds since the lease was granted, a client is + // supposed to start REBIND procedure (emergency renewal that allows + // switching to a different server). + "renew-timer": 1000, + "rebind-timer": 2000, + "preferred-lifetime": 3000, + "valid-lifetime": 4000, + + // These are global options. They are going to be sent when a client requests + // them, unless overwritten with values in more specific scopes. The scope + // hierarchy is: + // - global + // - subnet + // - class + // - host + // + // Not all of those options make sense. Please configure only those that + // are actually useful in your network. + // + // For a complete list of options currently supported by Kea, see + // Section 8.2.9 "Standard DHCPv6 Options". Kea also supports + // vendor options (see Section 7.2.10) and allows users to define their + // own custom options (see Section 7.2.9). + "option-data": [ + // When specifying options, you typically need to specify + // one of (name or code) and data. The full option specification + // covers name, code, space, csv-format and data. + // space defaults to "dhcp6" which is usually correct, unless you + // use encapsulate options. csv-format defaults to "true", so + // this is also correct, unless you want to specify the whole + // option value as long hex string. For example, to specify + // domain-name-servers you could do this: + // { + // "name": "dns-servers", + // "code": 23, + // "csv-format": "true", + // "space": "dhcp6", + // "data": "2001:db8:2::45, 2001:db8:2::100" + // } + // but it's a lot of writing, so it's easier to do this instead: + { + "name": "dns-servers", + "data": "fd43:5602:29bd:ffff:1:1:1:1" + } + ], + + // Another thing possible here are hooks. Kea supports a powerful mechanism + // that allows loading external libraries that can extract information and + // even influence how the server processes packets. Those libraries include + // additional forensic logging capabilities, ability to reserve hosts in + // more flexible ways, and even add extra commands. For a list of available + // hook libraries, see https://gitlab.isc.org/isc-projects/kea/wikis/Hooks-available. + // "hooks-libraries": [ + // { + // // Forensic Logging library generates forensic type of audit trail + // // of all devices serviced by Kea, including their identifiers + // // (like MAC address), their location in the network, times + // // when they were active etc. + // "library": "/usr/lib64/kea/hooks/libdhcp_legal_log.so", + // "parameters": { + // "path": "/var/lib/kea", + // "base-name": "kea-forensic6" + // } + // }, + // { + // // Flexible identifier (flex-id). Kea software provides a way to + // // handle host reservations that include addresses, prefixes, + // // options, client classes and other features. The reservation can + // // be based on hardware address, DUID, circuit-id or client-id in + // // DHCPv4 and using hardware address or DUID in DHCPv6. However, + // // there are sometimes scenario where the reservation is more + // // complex, e.g. uses other options that mentioned above, uses part + // // of specific options or perhaps even a combination of several + // // options and fields to uniquely identify a client. Those scenarios + // // are addressed by the Flexible Identifiers hook application. + // "library": "/usr/lib64/kea/hooks/libdhcp_flex_id.so", + // "parameters": { + // "identifier-expression": "relay6[0].option[37].hex" + // } + // } + // ], + + // Below an example of a simple IPv6 subnet declaration. Uncomment to enable + // it. This is a list, denoted with [ ], of structures, each denoted with + // { }. Each structure describes a single subnet and may have several + // parameters. One of those parameters is "pools" that is also a list of + // structures. + "subnet6": [ + { + "id": 1, + "interface": "vxlan0", + // This defines the whole subnet. Kea will use this information to + // determine where the clients are connected. This is the whole + // subnet in your network. This is mandatory parameter for each + // subnet. + "subnet": "fe80::/64", + + // Pools define the actual part of your subnet that is governed + // by Kea. Technically this is optional parameter, but it's + // almost always needed for DHCP to do its job. If you omit it, + // clients won't be able to get addresses, unless there are + // host reservations defined for them. + "pools": [ { "pool": "fe80::8000:0:0:0/64" } ], + + // Kea supports prefix delegation (PD). This mechanism delegates + // whole prefixes, instead of single addresses. You need to specify + // a prefix and then size of the delegated prefixes that it will + // be split into. This example below tells Kea to use + // 2001:db8:1::/56 prefix as pool and split it into /64 prefixes. + // This will give you 256 (2^(64-56)) prefixes. + "pd-pools": [ + { + "prefix": "${AUTO_L3_PREFIX}", + "prefix-len": ${AUTO_L3_PREFIX_LEN}, + "delegated-len": ${AUTO_L3_DELEGATED_LEN} + + // Kea also supports excluded prefixes. This advanced option + // is explained in Section 9.2.9. Please make sure your + // excluded prefix matches the pool it is defined in. + // "excluded-prefix": "2001:db8:8:0:80::", + // "excluded-prefix-len": 72 + } + ] + } + ], + + // Logging configuration starts here. Kea uses different loggers to log various + // activities. For details (e.g. names of loggers), see Chapter 18. + "loggers": [ + { + // This specifies the logging for kea-dhcp6 logger, i.e. all logs + // generated by Kea DHCPv6 server. + "name": "kea-dhcp6", + "output_options": [ + { + // Specifies the output file. There are several special values + // supported: + // - stdout (prints on standard output) + // - stderr (prints on standard error) + // - syslog (logs to syslog) + // - syslog:name (logs to syslog using specified name) + // Any other value is considered a name of the file + "output": "stdout" + + // Shorter log pattern suitable for use with systemd, + // avoids redundant information + // "pattern": "%-5p %m\n", + + // This governs whether the log output is flushed to disk after + // every write. + // "flush": false, + + // This specifies the maximum size of the file before it is + // rotated. + // "maxsize": 1048576, + + // This specifies the maximum number of rotated files to keep. + // "maxver": 8 + } + ], + // This specifies the severity of log messages to keep. Supported values + // are: FATAL, ERROR, WARN, INFO, DEBUG + "severity": "INFO", + + // If DEBUG level is specified, this value is used. 0 is least verbose, + // 99 is most verbose. Be cautious, Kea can generate lots and lots + // of logs if told to do so. + "debuglevel": 99 + } + ] +} +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..eef84f2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,29 @@ +--- +services: + auto-l3: + image: auto-l3 + build: + context: . + target: auto-l3 + restart: always + cap_add: + # required by bird + - CAP_NET_ADMIN + - CAP_NET_BROADCAST + - CAP_NET_RAW + - CAP_NET_BIND_SERVICE + sysctls: + - net.ipv6.conf.all.forwarding=1 + networks: + auto-l3: + ipv6_address: ${AUTO_L3_ADDRESS} + volumes: + - ./auto-l3:/auto-l3 + - ./wireguard:/auto-l3/wireguard + # Use .env file to set environment variables + # see .env.example + env_file: .env + +networks: + auto-l3: + external: true diff --git a/wireguard/skel b/wireguard/skel new file mode 100644 index 0000000..f10b53d --- /dev/null +++ b/wireguard/skel @@ -0,0 +1,11 @@ +# see `man 8 wg` for additional options + +[interface] +PrivateKey = +# optional +# ListenPort = + +[Peer] +PublicKey = +Endpoint = # [2001:db8::]:1234 +AllowedIPs = ::/0