Skip to content

Instantly share code, notes, and snippets.

@Palatis
Last active January 22, 2026 16:45
Show Gist options
  • Select an option

  • Save Palatis/d8947cb416be61355cfcc4981a4b0ad8 to your computer and use it in GitHub Desktop.

Select an option

Save Palatis/d8947cb416be61355cfcc4981a4b0ad8 to your computer and use it in GitHub Desktop.
OpenWrt podman use br-lan
[engine]
hooks_dir = [
"/etc/containers/oci/hooks.d/",
]
{
"version": "1.0.0",
"hook": {
"path": "/root/bin/podman-lan-bridge-hook.sh"
},
"when": {
"annotations": {
"io.podman.annotations.lan-bridge": "true"
}
},
"stages": [ "prestart", "poststart", "poststop" ]
}
#!/usr/bin/env bash
LOG="/dev/null"
#LOG="/tmp/podman-hook.log"
PIDFILE_DIR="/var/run/podman/dhcp"
echo "=== Hook called at $(date) ===" >> "$LOG"
# Read all input
input=$(cat)
echo "Stdin: $input" >> "$LOG"
CONTAINER_ID=$(echo "$input" | sed -n 's/.*"id":"\([^"]*\)".*/\1/p')
CONTAINER_PID=$(echo "$input" | sed -n 's/.*"pid":\([0-9]*\).*/\1/p')
STATUS=$(echo "$input" | sed -n 's/.*"status":"\([^"]*\)".*/\1/p')
echo "Container ID: $CONTAINER_ID" >> "$LOG"
echo "Container PID: $CONTAINER_PID" >> "$LOG"
echo "Status: $STATUS" >> "$LOG"
DHCP4_PIDFILE="$PIDFILE_DIR/$CONTAINER_ID.udhcpc.pid"
DHCP6_PIDFILE="$PIDFILE_DIR/$CONTAINER_ID.odhcp6c.pid"
# Determine stage based on status
if [ "$STATUS" = "created" ] && [ -n "$CONTAINER_PID" ]; then
STAGE="prestart"
elif [ "$STATUS" = "running" ]; then
STAGE="poststart"
elif [ "$STATUS" = "stopped" ]; then
STAGE="poststop"
else
echo "Unknown stage, exiting" >> "$LOG"
exit 0
fi
echo "Stage: $STAGE" >> "$LOG"
case "$STAGE" in
prestart)
mkdir -p $PIDFILE_DIR
# IPv6
nsenter -t "$CONTAINER_PID" -a sysctl -w net.ipv6.conf.all.accept_ra=2 >> "$LOG" 2>&1
nsenter -t "$CONTAINER_PID" -a sysctl -w net.ipv6.conf.default.accept_ra=2 >> "$LOG" 2>&1
nsenter -t "$CONTAINER_PID" -a sysctl -w net.ipv6.conf.eth0.accept_ra=2 >> "$LOG" 2>&1
nsenter -t "$CONTAINER_PID" -a sysctl -w net.ipv6.conf.eth0.autoconf=1 >> "$LOG" 2>&1
;;
poststart)
# Hostname
CONTAINER_HOSTNAME=$(nsenter -t $CONTAINER_PID -u cat /proc/sys/kernel/hostname)
[ -z "$CONTAINER_HOSTNAME" ] && CONTAINER_HOSTNAME="ctr-${CONTAINER_ID:0:10}"
echo "Container Hostname: $CONTAINER_HOSTNAME" >> "$LOG"
# IPv4
nsenter -t $CONTAINER_PID -m umount /etc/resolv.conf >> "$LOG"
nsenter -t $CONTAINER_PID -a udhcpc -f -R -S -i eth0 -x hostname:$CONTAINER_HOSTNAME 2>&1 >> "$LOG" &
UDHCPC_PID=$!
echo "$UDHCPC_PID" > "$DHCP4_PIDFILE"
echo "udhcpc pid: ${UDHCPC_PID}" >> "$LOG"
# IPv6
DUID="00048e3c${CONTAINER_ID:0:26}"
nsenter -t "$CONTAINER_PID" -n -u odhcp6c -s /root/bin/dhcpv6.simple.script -c "$DUID" -t120 eth0 2>&1 >> "$LOG" &
ODHCP6C_PID=$!
echo "$ODHCP6C_PID" > "$DHCP6_PIDFILE"
echo "odhcp6c pid: ${ODHCP6C_PID}" >> "$LOG"
;;
poststop)
echo "Stopping DHCP client..." >> "$LOG"
# kill IPv4 udhcpc
[ -f "$DHCP4_PIDFILE" ] && kill $(cat "$DHCP4_PIDFILE") 2>/dev/null
rm -f "$DHCP4_PIDFILE"
# kill IPv6 odhcpc6
[ -f "$DHCP6_PIDFILE" ] && kill $(cat "$DHCP6_PIDFILE") 2>/dev/null
rm -f "$DHCP6_PIDFILE"
echo "Cleanup complete" >> "$LOG"
;;
esac
echo "=== Hook finished ===" >> "$LOG"
exit 0
#!/bin/sh
# Minimal odhcp6c script for container (IPv6 only)
# Works with RA_ADDRESSES / ADDRESSES
IF="$1"
ACTION="$2"
case "$ACTION" in
ra-updated|add|bound)
# assign addresses from ADDRESSES
for addr in $ADDRESSES; do
ip_addr=$(echo "$addr" | cut -d',' -f1)
[ -n "$ip_addr" ] && ip -6 addr add "$ip_addr" dev "$IF"
done
# set default routes from RA_ROUTES
for route in $RA_ROUTES; do
dst=$(echo "$route" | cut -d',' -f1)
gw=$(echo "$route" | cut -d',' -f2)
[ -n "$dst" ] && [ -n "$gw" ] && ip -6 route add "$dst" via "$gw" dev "$IF" || true
done
# update resolv.conf from RDNSS
if [ -n "$RDNSS" ]; then
echo > /etc/resolv.conf
for dns in $RDNSS; do
echo "nameserver $dns" >> /etc/resolv.conf
done
fi
;;
del|debound)
# remove assigned addresses
for addr in $ADDRESSES; do
ip_addr=$(echo "$addr" | cut -d',' -f1)
[ -n "$ip_addr" ] && ip -6 addr del "$ip_addr" dev "$IF"
done
;;
esac
exit 0
# podman network create --disable-dns -d bridge --ipv6 --interface-name br-lan --help --ipam-driver dhcp br-lan
# podman inspect br-lan
[
{
"name": "br-lan",
"id": "...",
"driver": "bridge",
"network_interface": "br-lan",
"created": "2026-01-20T20:37:44.707081309+08:00",
"ipv6_enabled": true,
"internal": false,
"dns_enabled": false,
"ipam_options": {
"driver": "dhcp"
}
}
]
# podman run -d --name samba --network=br-lan \
--annotation io.podman.annotations.lan-bridge=true \
-h samba \
--mac-address "11:22:33:44:55:66" \
ghcr.io/servercontainers/samba:smbd-wsdd2-latest
@Palatis
Copy link
Author

Palatis commented Jan 21, 2026

Issues

  • i was going to use podman inspect to obtain the hostname, as it might be more reliable. however can't use that in a hook script because inside hooks the container is in a limbo state and podman inspect just hang forever (kill -9 to stop it).
  • hostname can only be obtained during poststart phase. it's not available during prestart, the command just retrieve router's hostname instead container's hostname during prestart.
  • cannot just podman exec $CONTAINER_ID udhcpc, container don't have the permission to modify network settings.
  • was going to use logger so things logs into syslog, but dunno why a simple logger hello doesn't write anything to logread.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment