From 141ac1c9dd7d8dbee8e0873b9d20af816dc75cfc Mon Sep 17 00:00:00 2001 From: Marcus Penate Date: Wed, 27 Aug 2025 18:55:32 -0400 Subject: [PATCH] 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) --- .gitignore | 18 ++++ Dockerfile | 18 ++++ Makefile | 78 ++++++++++++++ README.md | 136 ++++++++++++++++++++++++ claude.md | 57 +++++++++++ config/interface.conf.example | 3 + config/network.conf.example | 24 +++++ config/whitelist.conf.example | 5 + dhcp/dhcpd.conf.template | 18 ++++ dhcp/entrypoint.sh | 176 ++++++++++++++++++++++++++++++++ docker-compose.test.yml | 45 ++++++++ docker-compose.yml | 17 +++ scripts/autoconfig.sh | 155 ++++++++++++++++++++++++++++ scripts/install-service.sh | 134 ++++++++++++++++++++++++ systemd/dhcp-whitelist.service | 29 ++++++ test/test-client/Dockerfile | 7 ++ test/test-config/interface.conf | 1 + test/test-config/network.conf | 20 ++++ test/test-config/whitelist.conf | 1 + 19 files changed, 942 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 claude.md create mode 100644 config/interface.conf.example create mode 100644 config/network.conf.example create mode 100644 config/whitelist.conf.example create mode 100644 dhcp/dhcpd.conf.template create mode 100755 dhcp/entrypoint.sh create mode 100644 docker-compose.test.yml create mode 100644 docker-compose.yml create mode 100755 scripts/autoconfig.sh create mode 100755 scripts/install-service.sh create mode 100644 systemd/dhcp-whitelist.service create mode 100644 test/test-client/Dockerfile create mode 100644 test/test-config/interface.conf create mode 100644 test/test-config/network.conf create mode 100644 test/test-config/whitelist.conf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86aa06d --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +*.swp +*.swo +.DS_Store +.env +docker-compose.override.yml +*.log +*.pid +/data/ +/tmp/ +__pycache__/ +*.pyc +.vscode/ +.idea/ + +# User configuration files (keep examples only) +config/interface.conf +config/whitelist.conf +config/network.conf \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3a00d11 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM alpine:3.19 + +RUN apk add --no-cache \ + dhcp \ + bash \ + iproute2 + +RUN mkdir -p /var/lib/dhcp /etc/dhcp /run/dhcp + +COPY dhcp/entrypoint.sh /entrypoint.sh +COPY dhcp/dhcpd.conf.template /etc/dhcp/dhcpd.conf.template + +RUN chmod +x /entrypoint.sh && \ + touch /var/lib/dhcp/dhcpd.leases + +EXPOSE 67/udp + +ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1cffd9f --- /dev/null +++ b/Makefile @@ -0,0 +1,78 @@ +.PHONY: up down install service_up service_down test autoconfig clean help + +# Default target +help: + @echo "Available targets:" + @echo " up - Start DHCP server in userland mode" + @echo " down - Stop DHCP server" + @echo " install - Install as systemd service (requires sudo)" + @echo " service_up - Enable and start systemd service" + @echo " service_down - Disable and stop systemd service" + @echo " test - Run test environment with virtual network (172.20.0.0/24)" + @echo " test-clean - Clean up test environment" + @echo " autoconfig - Auto-generate configuration from current network" + @echo " clean - Remove all containers and generated files" + +# Start DHCP server in userland mode +up: + @echo "Building DHCP server image..." + @docker-compose build + @echo "Starting DHCP server..." + @docker-compose up -d + @echo "DHCP server started. Check logs with: docker-compose logs -f" + +# Stop DHCP server +down: + @echo "Stopping DHCP server..." + @docker-compose down + @echo "DHCP server stopped." + +# Install as systemd service (requires sudo) +install: + @echo "Installing systemd service..." + @bash scripts/install-service.sh + @echo "Service installed. Use 'make service_up' to start." + +# Enable and start systemd service +service_up: + @echo "Enabling and starting systemd service..." + @systemctl --user enable dhcp-whitelist.service 2>/dev/null || sudo systemctl enable dhcp-whitelist.service + @systemctl --user start dhcp-whitelist.service 2>/dev/null || sudo systemctl start dhcp-whitelist.service + @echo "Service started. Check status with: systemctl status dhcp-whitelist" + +# Disable and stop systemd service +service_down: + @echo "Stopping and disabling systemd service..." + @systemctl --user stop dhcp-whitelist.service 2>/dev/null || sudo systemctl stop dhcp-whitelist.service + @systemctl --user disable dhcp-whitelist.service 2>/dev/null || sudo systemctl disable dhcp-whitelist.service + @echo "Service stopped." + +# Run test environment +test: + @echo "Building test environment..." + @docker-compose -f docker-compose.test.yml build + @echo "Running tests..." + @docker-compose -f docker-compose.test.yml up --abort-on-container-exit + @$(MAKE) test-clean + @echo "Tests completed." + +# Clean up test environment (always runs, even on test failure) +test-clean: + @echo "Cleaning up test environment..." + @docker-compose -f docker-compose.test.yml down -v --remove-orphans 2>/dev/null || true + @docker network rm service-dhcp-direct-link-only_test-net 2>/dev/null || true + @echo "Test cleanup complete." + +# Auto-generate configuration +autoconfig: + @echo "Auto-generating configuration..." + @bash scripts/autoconfig.sh + @echo "Configuration generated. Review config/ directory before starting." + +# Clean up +clean: + @echo "Cleaning up..." + @docker-compose down 2>/dev/null || true + @$(MAKE) test-clean + @rm -f config/*.tmp + @echo "Cleanup complete." \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d072187 --- /dev/null +++ b/README.md @@ -0,0 +1,136 @@ +# DHCP Direct Link Only Service + +A Docker-based DHCP server that only serves IP addresses to whitelisted MAC addresses on a specific ethernet interface. Designed for secure, controlled network environments where only authorized devices should receive DHCP leases. + +## Features + +- **MAC Address Whitelisting**: Only responds to DHCP requests from pre-authorized MAC addresses +- **Interface Binding**: Binds to a specific ethernet interface only +- **Docker-Based**: Runs in an isolated container environment +- **Systemd Integration**: Can be installed as a system service on Arch/Manjaro +- **Auto-Configuration**: Automatically detects network settings and connected devices +- **Testing Environment**: Includes isolated test environment with virtual networks + +## Requirements + +- Docker +- Docker Compose +- Make +- systemd (for service installation) +- sudo (for service installation) + +## Quick Start + +1. Auto-configure for current network: +```bash +make autoconfig +``` + +2. Start the DHCP server: +```bash +make up +``` + +3. Stop the server: +```bash +make down +``` + +## Configuration + +### Manual Configuration + +Edit the following files in the `config/` directory: + +- `interface.conf`: Specify the ethernet interface to bind to +- `whitelist.conf`: List MAC addresses (one per line) that should be served + +### Auto Configuration + +Run `make autoconfig` to automatically: +- Detect your ethernet interface +- Find connected devices on the network +- Generate configuration files + +## Make Targets + +| Target | Description | Requires sudo | +|--------|-------------|---------------| +| `up` | Start DHCP server in userland mode | No | +| `down` | Stop DHCP server | No | +| `install` | Install as systemd service | Yes | +| `service_up` | Enable and start systemd service | No | +| `service_down` | Disable and stop systemd service | No | +| `test` | Run isolated test environment | No | +| `autoconfig` | Auto-generate configuration | No | +| `clean` | Remove generated files | No | + +## Installation as System Service + +To install and run as a systemd service: + +```bash +# Install the service (requires sudo) +sudo make install + +# Start the service +make service_up + +# Check service status +systemctl status dhcp-whitelist + +# Stop the service +make service_down +``` + +The service configuration will be stored in `/etc/dhcp-whitelist/` and will persist across system reboots. + +## Testing + +Run the test environment with virtual networks: + +```bash +make test +``` + +This creates an isolated Docker network with: +- A DHCP server with test whitelist +- Test clients with different MAC addresses +- Validation of whitelist enforcement + +## Network Configuration + +Default DHCP settings: +- Subnet: 192.168.75.0/24 +- Range: 192.168.75.10 - 192.168.75.100 +- Gateway: 192.168.75.1 +- DNS: 8.8.8.8, 8.8.4.4 +- Lease time: 12 hours + +## Troubleshooting + +### DHCP server not responding +- Check that the interface in `config/interface.conf` is correct +- Verify the MAC address is in `config/whitelist.conf` +- Check Docker logs: `docker-compose logs dhcp-server` + +### Permission denied errors +- Service installation requires sudo: `sudo make install` +- Ensure Docker daemon is running +- Check that your user is in the docker group + +### Service won't start +- Check systemd logs: `journalctl -u dhcp-whitelist -f` +- Verify Docker and docker-compose are installed +- Check configuration files in `/etc/dhcp-whitelist/` + +## Security Considerations + +- This server uses MAC address filtering as the primary security mechanism +- MAC addresses can be spoofed; use additional security measures in production +- The server runs with host networking to access the physical interface +- Consider firewall rules to restrict DHCP traffic further + +## License + +MIT \ No newline at end of file diff --git a/claude.md b/claude.md new file mode 100644 index 0000000..9b0346b --- /dev/null +++ b/claude.md @@ -0,0 +1,57 @@ +# DHCP Direct Link Only Service - Development Notes + +## Project Overview +This project creates a DHCP server that only responds to whitelisted MAC addresses on a specific ethernet interface. It's designed for direct link connections where only authorized devices should receive IP addresses, without any routing or DNS services. + +## Configuration Features +- Binds to specific ethernet interface (configurable) +- MAC address whitelisting (configurable) +- NO DNS servers advertised (direct link only) +- NO gateway/router advertised (not a NAT setup) +- Configurable network parameters (subnet, range, lease times) + +## Architecture Decisions + +### Why Docker? +- Isolation from host system +- Easy deployment and management +- Consistent environment across different systems +- Simple cleanup and removal + +### Why ISC DHCP Server? +- Mature, stable DHCP implementation +- Extensive configuration options +- Good documentation +- Supports MAC address filtering natively + +### Network Mode: Host +The container uses host network mode because: +- DHCP requires direct access to the physical network interface +- DHCP uses raw sockets that don't work well with Docker's bridge networking +- We need to bind to a specific physical interface + +## Implementation Details + +### Whitelist Implementation +The whitelist is implemented using ISC DHCP's host declarations with a deny unknown-clients directive. This ensures only explicitly defined MAC addresses receive leases. + +### Configuration Management +- Local configs in `config/` for development +- System configs in `/etc/dhcp-whitelist/` for production service +- Auto-config script detects network settings automatically + +### Testing Strategy +- Isolated Docker network for testing +- Separate test client containers +- Tests both allowed and denied MAC scenarios + +## Known Limitations +1. MAC addresses can be spoofed - this is not a security solution by itself +2. Requires host network mode which reduces container isolation +3. Only one instance can run per interface + +## Future Enhancements +- Web UI for managing whitelist +- Logging and monitoring +- Multiple interface support +- Integration with network authentication systems \ No newline at end of file diff --git a/config/interface.conf.example b/config/interface.conf.example new file mode 100644 index 0000000..fcdfb38 --- /dev/null +++ b/config/interface.conf.example @@ -0,0 +1,3 @@ +# Example: Ethernet interface name +# Find your interface with: ip link show +eth0 \ No newline at end of file diff --git a/config/network.conf.example b/config/network.conf.example new file mode 100644 index 0000000..e35180e --- /dev/null +++ b/config/network.conf.example @@ -0,0 +1,24 @@ +# Network configuration for DHCP server +# These values override automatic detection + +# Network subnet (leave empty for auto-detection from interface) +# Example: SUBNET=192.168.1.0 +SUBNET= + +# Netmask in dotted notation (leave empty for auto-detection) +# Example: NETMASK=255.255.255.0 +NETMASK= + +# DHCP range start offset from network base (default: 10) +# For example, if network is 192.168.1.0, start will be 192.168.1.10 +RANGE_START_OFFSET=10 + +# DHCP range end offset from network base (default: 100) +# For example, if network is 192.168.1.0, end will be 192.168.1.100 +RANGE_END_OFFSET=100 + +# Lease time in seconds (default: 43200 = 12 hours) +LEASE_TIME=43200 + +# Max lease time in seconds (default: 86400 = 24 hours) +MAX_LEASE_TIME=86400 \ No newline at end of file diff --git a/config/whitelist.conf.example b/config/whitelist.conf.example new file mode 100644 index 0000000..b303dce --- /dev/null +++ b/config/whitelist.conf.example @@ -0,0 +1,5 @@ +# MAC addresses to whitelist (one per line) +# Format: aa:bb:cc:dd:ee:ff +# Example: +# 00:11:22:33:44:55 +# aa:bb:cc:dd:ee:ff \ No newline at end of file diff --git a/dhcp/dhcpd.conf.template b/dhcp/dhcpd.conf.template new file mode 100644 index 0000000..b082f5e --- /dev/null +++ b/dhcp/dhcpd.conf.template @@ -0,0 +1,18 @@ +authoritative; +default-lease-time __LEASE_TIME__; +max-lease-time __MAX_LEASE_TIME__; + +# Deny all clients by default +deny unknown-clients; + +# Network configuration - Direct link only, no routing +subnet __SUBNET__ netmask __NETMASK__ { + range __RANGE_START__ __RANGE_END__; + # No gateway - this is a direct link only + # No DNS - clients should use their own DNS from other interfaces + option subnet-mask __NETMASK__; + option broadcast-address __BROADCAST__; +} + +# Whitelisted MAC addresses +__HOST_ENTRIES__ \ No newline at end of file diff --git a/dhcp/entrypoint.sh b/dhcp/entrypoint.sh new file mode 100755 index 0000000..7d1f221 --- /dev/null +++ b/dhcp/entrypoint.sh @@ -0,0 +1,176 @@ +#!/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" \ No newline at end of file diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..c131fdd --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,45 @@ +version: '3.8' + +networks: + test-net: + driver: bridge + ipam: + config: + - subnet: 172.20.0.0/24 + gateway: 172.20.0.1 + +services: + test-dhcp-server: + build: . + image: dhcp-whitelist:test + container_name: test-dhcp-server + networks: + test-net: + ipv4_address: 172.20.0.2 + volumes: + - ./test/test-config:/config:ro + environment: + - TZ=UTC + cap_add: + - NET_ADMIN + - NET_RAW + + test-client-allowed: + build: ./test/test-client + container_name: test-client-allowed + networks: + test-net: + mac_address: "02:42:ac:11:00:02" + depends_on: + - test-dhcp-server + command: ["/bin/sh", "-c", "sleep 5 && udhcpc -i eth0 -n -q && ip addr show eth0"] + + test-client-denied: + build: ./test/test-client + container_name: test-client-denied + networks: + test-net: + mac_address: "02:42:ac:11:00:99" + depends_on: + - test-dhcp-server + command: ["/bin/sh", "-c", "sleep 5 && timeout 10 udhcpc -i eth0 -n -q || echo 'DHCP request denied as expected'"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..38d028a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +version: '3.8' + +services: + dhcp-server: + build: . + image: dhcp-whitelist:latest + container_name: dhcp-whitelist-server + network_mode: host + privileged: true + volumes: + - ./config:/config:ro + restart: unless-stopped + environment: + - TZ=UTC + cap_add: + - NET_ADMIN + - NET_RAW \ No newline at end of file diff --git a/scripts/autoconfig.sh b/scripts/autoconfig.sh new file mode 100755 index 0000000..35801bb --- /dev/null +++ b/scripts/autoconfig.sh @@ -0,0 +1,155 @@ +#!/bin/bash +set -e + +CONFIG_DIR="./config" +INTERFACE_CONF="${CONFIG_DIR}/interface.conf" +WHITELIST_CONF="${CONFIG_DIR}/whitelist.conf" +NETWORK_CONF="${CONFIG_DIR}/network.conf" + +echo "=== DHCP Whitelist Auto-Configuration ===" +echo + +# Create config directory if it doesn't exist +mkdir -p "${CONFIG_DIR}" + +# Find ethernet interfaces (excluding loopback and virtual interfaces) +echo "Detecting ethernet interfaces..." +INTERFACES=$(ip link show | grep -E "^[0-9]+: en" | awk -F': ' '{print $2}' | head -1) + +if [ -z "$INTERFACES" ]; then + echo "Error: No ethernet interface found" + echo "Please configure manually by editing ${INTERFACE_CONF}" + exit 1 +fi + +echo "Found ethernet interface: $INTERFACES" + +# Get the interface with an IP in 192.168.x.x range (common for local networks) +SELECTED_INTERFACE="" +for IFACE in $INTERFACES; do + IP_INFO=$(ip -4 addr show "$IFACE" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' || true) + if [ -n "$IP_INFO" ]; then + echo " Interface $IFACE has IP: $IP_INFO" + if [[ "$IP_INFO" =~ ^192\.168\. ]] || [[ "$IP_INFO" =~ ^10\. ]] || [[ "$IP_INFO" =~ ^172\. ]]; then + SELECTED_INTERFACE="$IFACE" + break + fi + fi +done + +if [ -z "$SELECTED_INTERFACE" ]; then + # Fall back to first interface with any IP + for IFACE in $INTERFACES; do + if ip -4 addr show "$IFACE" 2>/dev/null | grep -q "inet "; then + SELECTED_INTERFACE="$IFACE" + break + fi + done +fi + +if [ -z "$SELECTED_INTERFACE" ]; then + echo "Error: No ethernet interface with IP address found" + echo "Using first available interface: $INTERFACES" + SELECTED_INTERFACE="$INTERFACES" +fi + +echo +echo "Selected interface: $SELECTED_INTERFACE" +echo "$SELECTED_INTERFACE" > "${INTERFACE_CONF}" + +# Find MAC addresses of devices on the network +echo +echo "Scanning for devices on the network..." + +# Get the network range +NETWORK_INFO=$(ip -4 addr show "$SELECTED_INTERFACE" | grep -oP '(?<=inet\s)\d+(\.\d+){3}/\d+' | head -1) +if [ -z "$NETWORK_INFO" ]; then + echo "Warning: Could not determine network range for $SELECTED_INTERFACE" + echo "Creating empty whitelist file. Please add MAC addresses manually." + touch "${WHITELIST_CONF}" +else + NETWORK=$(echo "$NETWORK_INFO" | cut -d'/' -f1 | sed 's/\.[0-9]*$/\.0/') + NETMASK=$(echo "$NETWORK_INFO" | cut -d'/' -f2) + + echo "Network: ${NETWORK}/${NETMASK}" + + # Try to ping the network to populate ARP table (just a few addresses) + echo "Discovering devices (this may take a moment)..." + BASE_NET=$(echo "$NETWORK" | sed 's/\.0$//') + for i in {1..10}; do + ping -c 1 -W 1 "${BASE_NET}.${i}" >/dev/null 2>&1 & + done + wait + + # Get MAC addresses from ARP table + echo + echo "Found devices:" + MACS=$(ip neigh show dev "$SELECTED_INTERFACE" | grep -E "lladdr" | awk '{print $5}' | sort -u) + + if [ -z "$MACS" ]; then + echo " No devices found in ARP table" + echo " Creating empty whitelist. Please add MAC addresses manually." + touch "${WHITELIST_CONF}" + else + echo "$MACS" | while read -r MAC; do + IP=$(ip neigh show dev "$SELECTED_INTERFACE" | grep "$MAC" | awk '{print $1}') + echo " $MAC (IP: $IP)" + done + + echo + echo "Writing MAC addresses to whitelist..." + echo "$MACS" > "${WHITELIST_CONF}" + echo "Added $(echo "$MACS" | wc -l) MAC address(es) to whitelist" + fi +fi + +echo +echo "=== Configuration Summary ===" +echo "Interface: $(cat "${INTERFACE_CONF}")" +echo "Whitelist entries:" +if [ -f "${WHITELIST_CONF}" ] && [ -s "${WHITELIST_CONF}" ]; then + cat "${WHITELIST_CONF}" | sed 's/^/ /' +else + echo " (empty - please add MAC addresses manually)" +fi + +echo +# Create or update network.conf with defaults (can be customized) +if [ ! -f "${NETWORK_CONF}" ]; then + echo + echo "Creating default network configuration..." + cat > "${NETWORK_CONF}" << EOF +# Network configuration for DHCP server +# These values override automatic detection + +# Network subnet (leave empty for auto-detection from interface) +SUBNET= + +# Netmask in dotted notation (leave empty for auto-detection) +NETMASK= + +# DHCP range start offset from network base (default: 10) +RANGE_START_OFFSET=10 + +# DHCP range end offset from network base (default: 100) +RANGE_END_OFFSET=100 + +# Lease time in seconds (default: 43200 = 12 hours) +LEASE_TIME=43200 + +# Max lease time in seconds (default: 86400 = 24 hours) +MAX_LEASE_TIME=86400 +EOF + echo "Network configuration created with defaults" +else + echo "Network configuration already exists, keeping current settings" +fi + +echo +echo "Configuration files created:" +echo " ${INTERFACE_CONF}" +echo " ${WHITELIST_CONF}" +echo " ${NETWORK_CONF}" +echo +echo "You can now start the DHCP server with: make up" +echo "Or install as a service with: sudo make install" \ No newline at end of file diff --git a/scripts/install-service.sh b/scripts/install-service.sh new file mode 100755 index 0000000..f8b0345 --- /dev/null +++ b/scripts/install-service.sh @@ -0,0 +1,134 @@ +#!/bin/bash +set -e + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + echo "This script must be run with sudo" + echo "Usage: sudo make install" + exit 1 +fi + +SERVICE_NAME="dhcp-whitelist" +SERVICE_FILE="systemd/${SERVICE_NAME}.service" +SYSTEMD_DIR="/etc/systemd/system" +CONFIG_DIR="/etc/dhcp-whitelist" +PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +echo "=== Installing DHCP Whitelist Service ===" +echo + +# Check if Docker is installed +if ! command -v docker &> /dev/null; then + echo "Error: Docker is not installed" + echo "Please install Docker first" + exit 1 +fi + +# Check if docker-compose is installed +if ! command -v docker-compose &> /dev/null; then + echo "Error: docker-compose is not installed" + echo "Please install docker-compose first" + exit 1 +fi + +# Create config directory +echo "Creating configuration directory: ${CONFIG_DIR}" +mkdir -p "${CONFIG_DIR}" + +# Copy configuration files +echo "Copying configuration files..." +if [ -f "${PROJECT_DIR}/config/interface.conf" ]; then + cp "${PROJECT_DIR}/config/interface.conf" "${CONFIG_DIR}/" + echo " Copied interface.conf" +else + echo "Warning: config/interface.conf not found" + echo " Creating default interface.conf" + echo "enp0s13f0u3" > "${CONFIG_DIR}/interface.conf" +fi + +if [ -f "${PROJECT_DIR}/config/whitelist.conf" ]; then + cp "${PROJECT_DIR}/config/whitelist.conf" "${CONFIG_DIR}/" + echo " Copied whitelist.conf" +else + echo "Warning: config/whitelist.conf not found" + echo " Creating empty whitelist.conf" + touch "${CONFIG_DIR}/whitelist.conf" +fi + +# Set proper permissions +chmod 644 "${CONFIG_DIR}"/*.conf +echo "Configuration files installed to: ${CONFIG_DIR}" + +# Create service file from template +echo +echo "Creating systemd service file..." +cat > "${SYSTEMD_DIR}/${SERVICE_NAME}.service" << EOF +[Unit] +Description=DHCP Whitelist Service +After=network.target docker.service +Requires=docker.service + +[Service] +Type=simple +Restart=always +RestartSec=10 +WorkingDirectory=${PROJECT_DIR} +Environment="CONFIG_DIR=${CONFIG_DIR}" + +# Pre-start: Build the image +ExecStartPre=/usr/bin/docker-compose build + +# Start the service +ExecStart=/usr/bin/docker-compose up + +# Stop the service +ExecStop=/usr/bin/docker-compose down + +# Reload config by restarting containers +ExecReload=/usr/bin/docker-compose restart + +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +EOF + +echo "Service file created: ${SYSTEMD_DIR}/${SERVICE_NAME}.service" + +# Create docker-compose override for service mode +echo +echo "Creating docker-compose override for service mode..." +cat > "${PROJECT_DIR}/docker-compose.override.yml" << EOF +version: '3.8' + +services: + dhcp-server: + volumes: + - ${CONFIG_DIR}:/config:ro +EOF + +# Reload systemd +echo +echo "Reloading systemd daemon..." +systemctl daemon-reload + +echo +echo "=== Installation Complete ===" +echo +echo "Configuration files location: ${CONFIG_DIR}" +echo " - ${CONFIG_DIR}/interface.conf" +echo " - ${CONFIG_DIR}/whitelist.conf" +echo +echo "Service management commands:" +echo " Start service: systemctl start ${SERVICE_NAME}" +echo " Stop service: systemctl stop ${SERVICE_NAME}" +echo " Enable on boot: systemctl enable ${SERVICE_NAME}" +echo " Check status: systemctl status ${SERVICE_NAME}" +echo " View logs: journalctl -u ${SERVICE_NAME} -f" +echo +echo "Or use make targets:" +echo " make service_up - Enable and start service" +echo " make service_down - Stop and disable service" +echo +echo "To start the service now, run: make service_up" \ No newline at end of file diff --git a/systemd/dhcp-whitelist.service b/systemd/dhcp-whitelist.service new file mode 100644 index 0000000..f0b7b28 --- /dev/null +++ b/systemd/dhcp-whitelist.service @@ -0,0 +1,29 @@ +[Unit] +Description=DHCP Whitelist Service +After=network.target docker.service +Requires=docker.service + +[Service] +Type=simple +Restart=always +RestartSec=10 +WorkingDirectory=/opt/dhcp-whitelist +Environment="CONFIG_DIR=/etc/dhcp-whitelist" + +# Pre-start: Build the image +ExecStartPre=/usr/bin/docker-compose build + +# Start the service +ExecStart=/usr/bin/docker-compose up + +# Stop the service +ExecStop=/usr/bin/docker-compose down + +# Reload config by restarting containers +ExecReload=/usr/bin/docker-compose restart + +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/test/test-client/Dockerfile b/test/test-client/Dockerfile new file mode 100644 index 0000000..1dbc305 --- /dev/null +++ b/test/test-client/Dockerfile @@ -0,0 +1,7 @@ +FROM alpine:3.19 + +RUN apk add --no-cache \ + iproute2 \ + iputils + +CMD ["/bin/sh"] \ No newline at end of file diff --git a/test/test-config/interface.conf b/test/test-config/interface.conf new file mode 100644 index 0000000..563d95d --- /dev/null +++ b/test/test-config/interface.conf @@ -0,0 +1 @@ +eth0 \ No newline at end of file diff --git a/test/test-config/network.conf b/test/test-config/network.conf new file mode 100644 index 0000000..3fd02f4 --- /dev/null +++ b/test/test-config/network.conf @@ -0,0 +1,20 @@ +# Network configuration for TEST DHCP server +# Uses 172.20.0.0/24 to avoid conflicts with common networks + +# Network subnet (overrides auto-detection) +SUBNET=172.20.0.0 + +# Netmask in dotted notation +NETMASK=255.255.255.0 + +# DHCP range start offset from network base +RANGE_START_OFFSET=10 + +# DHCP range end offset from network base +RANGE_END_OFFSET=100 + +# Lease time in seconds (shorter for testing) +LEASE_TIME=300 + +# Max lease time in seconds +MAX_LEASE_TIME=600 \ No newline at end of file diff --git a/test/test-config/whitelist.conf b/test/test-config/whitelist.conf new file mode 100644 index 0000000..886b8a3 --- /dev/null +++ b/test/test-config/whitelist.conf @@ -0,0 +1 @@ +02:42:ac:11:00:02 \ No newline at end of file