Marcus Penate 141ac1c9dd Initial commit: DHCP whitelist service for direct link connections
Features:
- Docker-based DHCP server with MAC address whitelisting
- Binds to specific ethernet interface only
- NO DNS/gateway advertised (direct link only, not a router)
- Configurable network parameters (subnet, DHCP range, lease times)
- Systemd service integration for Arch/Manjaro
- Test environment with isolated network (172.20.0.0/24)
- Auto-configuration script to detect network settings
- Complete Makefile with management targets

Security:
- Only responds to whitelisted MAC addresses
- deny unknown-clients configuration
- Runs in Docker container for isolation

Configuration:
- Copy .example files to create your config
- interface.conf: Network interface to bind to
- whitelist.conf: Allowed MAC addresses
- network.conf: Network parameters (optional)
2025-08-27 20:46:29 -04:00

176 lines
5.6 KiB
Bash
Executable File

#!/bin/bash
set -e
INTERFACE_FILE="/config/interface.conf"
WHITELIST_FILE="/config/whitelist.conf"
NETWORK_FILE="/config/network.conf"
DHCPD_CONF="/etc/dhcp/dhcpd.conf"
if [ ! -f "$INTERFACE_FILE" ]; then
echo "Error: Interface configuration file not found: $INTERFACE_FILE"
exit 1
fi
if [ ! -f "$WHITELIST_FILE" ]; then
echo "Error: Whitelist configuration file not found: $WHITELIST_FILE"
exit 1
fi
INTERFACE=$(cat "$INTERFACE_FILE" | tr -d '\n\r ')
echo "Using interface: $INTERFACE"
# Wait for interface to be available
for i in {1..30}; do
if ip link show "$INTERFACE" &>/dev/null; then
break
fi
echo "Waiting for interface $INTERFACE..."
sleep 1
done
if ! ip link show "$INTERFACE" &>/dev/null; then
echo "Error: Interface $INTERFACE not found"
exit 1
fi
# Load network configuration if available
if [ -f "$NETWORK_FILE" ]; then
echo "Loading network configuration from $NETWORK_FILE"
source "$NETWORK_FILE"
else
echo "No network configuration file found, using defaults"
fi
# Set defaults for unset variables
RANGE_START_OFFSET=${RANGE_START_OFFSET:-10}
RANGE_END_OFFSET=${RANGE_END_OFFSET:-100}
LEASE_TIME=${LEASE_TIME:-43200}
MAX_LEASE_TIME=${MAX_LEASE_TIME:-86400}
# Get interface IP and network info
INTERFACE_IP=$(ip -4 addr show "$INTERFACE" | grep "inet " | awk '{print $2}' | cut -d'/' -f1)
INTERFACE_CIDR=$(ip -4 addr show "$INTERFACE" | grep "inet " | awk '{print $2}' | cut -d'/' -f2)
# Check if subnet is manually configured
if [ -n "$SUBNET" ] && [ -n "$NETMASK" ]; then
echo "Using manual network configuration: $SUBNET/$NETMASK"
INTERFACE_NETWORK="$SUBNET"
# Calculate broadcast from subnet and netmask
if [ "$NETMASK" = "255.255.255.0" ]; then
BROADCAST="${SUBNET%.*}.255"
elif [ "$NETMASK" = "255.255.0.0" ]; then
BROADCAST="${SUBNET%.*.*}.255.255"
elif [ "$NETMASK" = "255.0.0.0" ]; then
BROADCAST="${SUBNET%.*.*.*}.255.255.255"
else
BROADCAST="${SUBNET%.*}.255"
fi
else
echo "Auto-detecting network configuration from interface"
# For test environment, handle Docker's internal network properly
if [ "$INTERFACE" = "eth0" ] && [ -n "$INTERFACE_IP" ]; then
# Extract network base from the actual IP
INTERFACE_NETWORK=$(echo "$INTERFACE_IP" | sed 's/\.[0-9]*$/\.0/')
else
# Fallback for physical interfaces
INTERFACE_NETWORK=$(ip -4 addr show "$INTERFACE" | grep "inet " | awk '{print $2}' | cut -d'/' -f1 | sed 's/\.[0-9]*$/\.0/')
fi
# Convert CIDR to netmask and calculate broadcast
case $INTERFACE_CIDR in
24)
NETMASK="255.255.255.0"
BROADCAST="${INTERFACE_NETWORK%.*}.255"
;;
16)
NETMASK="255.255.0.0"
BROADCAST="${INTERFACE_NETWORK%.*.*}.255.255"
;;
8)
NETMASK="255.0.0.0"
BROADCAST="${INTERFACE_NETWORK%.*.*.*}.255.255.255"
;;
*)
NETMASK="255.255.255.0"
BROADCAST="${INTERFACE_NETWORK%.*}.255"
;;
esac
fi
echo "Interface IP: $INTERFACE_IP"
echo "Network: $INTERFACE_NETWORK/$NETMASK"
echo "DHCP Range: ${INTERFACE_NETWORK%.*}.$RANGE_START_OFFSET - ${INTERFACE_NETWORK%.*}.$RANGE_END_OFFSET"
echo "Lease Time: $LEASE_TIME seconds"
# Generate dhcpd.conf from template
cp /etc/dhcp/dhcpd.conf.template "$DHCPD_CONF"
# Generate host entries for whitelisted MACs
HOST_ENTRIES=""
HOST_NUM=1
echo "Reading whitelist from $WHITELIST_FILE"
# Check if file exists and is readable
if [ ! -f "$WHITELIST_FILE" ]; then
echo "ERROR: Whitelist file does not exist!"
exit 1
fi
if [ ! -r "$WHITELIST_FILE" ]; then
echo "ERROR: Whitelist file is not readable!"
exit 1
fi
echo "File size: $(stat -c%s "$WHITELIST_FILE") bytes"
echo "File contents (hex dump):"
hexdump -C "$WHITELIST_FILE" | head -5
echo "File contents (raw):"
cat "$WHITELIST_FILE"
echo ""
echo "--- Processing MACs ---"
# Read file line by line without subshell
while IFS= read -r MAC || [ -n "$MAC" ]; do
# Remove any whitespace, carriage returns, and convert to lowercase
ORIG_MAC="$MAC"
MAC=$(echo "$MAC" | tr -d '\r\n\t ' | tr '[:upper:]' '[:lower:]')
echo "Processing: original='$ORIG_MAC' cleaned='$MAC'"
if [ -n "$MAC" ]; then
# Generate IP based on host number (starting from .10)
LAST_OCTET=$((10 + HOST_NUM - 1))
HOST_IP="${INTERFACE_NETWORK%.*}.$LAST_OCTET"
NEW_ENTRY="
host client$HOST_NUM {
hardware ethernet $MAC;
fixed-address $HOST_IP;
}"
HOST_ENTRIES="${HOST_ENTRIES}${NEW_ENTRY}"
echo "Added whitelist entry: $MAC -> $HOST_IP"
HOST_NUM=$((HOST_NUM + 1))
fi
done < "$WHITELIST_FILE"
if [ -z "$HOST_ENTRIES" ]; then
echo "Warning: No MAC addresses found in whitelist!"
else
echo "Total whitelisted MACs: $((HOST_NUM - 1))"
fi
# Replace placeholders in config
sed -i "s#__SUBNET__#$INTERFACE_NETWORK#g" "$DHCPD_CONF"
sed -i "s#__NETMASK__#$NETMASK#g" "$DHCPD_CONF"
sed -i "s#__RANGE_START__#${INTERFACE_NETWORK%.*}.$RANGE_START_OFFSET#g" "$DHCPD_CONF"
sed -i "s#__RANGE_END__#${INTERFACE_NETWORK%.*}.$RANGE_END_OFFSET#g" "$DHCPD_CONF"
sed -i "s#__BROADCAST__#$BROADCAST#g" "$DHCPD_CONF"
sed -i "s#__LEASE_TIME__#$LEASE_TIME#g" "$DHCPD_CONF"
sed -i "s#__MAX_LEASE_TIME__#$MAX_LEASE_TIME#g" "$DHCPD_CONF"
# Use a different approach for multiline HOST_ENTRIES
awk -v entries="$HOST_ENTRIES" '{gsub(/__HOST_ENTRIES__/, entries); print}' "$DHCPD_CONF" > "$DHCPD_CONF.tmp" && mv "$DHCPD_CONF.tmp" "$DHCPD_CONF"
echo "Starting DHCP server on interface $INTERFACE..."
echo "Configuration:"
cat "$DHCPD_CONF"
# Start DHCP server
exec dhcpd -4 -f -d -cf "$DHCPD_CONF" "$INTERFACE"