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)
This commit is contained in:
commit
141ac1c9dd
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@ -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
|
18
Dockerfile
Normal file
18
Dockerfile
Normal file
@ -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"]
|
78
Makefile
Normal file
78
Makefile
Normal file
@ -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."
|
136
README.md
Normal file
136
README.md
Normal file
@ -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
|
57
claude.md
Normal file
57
claude.md
Normal file
@ -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
|
3
config/interface.conf.example
Normal file
3
config/interface.conf.example
Normal file
@ -0,0 +1,3 @@
|
||||
# Example: Ethernet interface name
|
||||
# Find your interface with: ip link show
|
||||
eth0
|
24
config/network.conf.example
Normal file
24
config/network.conf.example
Normal file
@ -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
|
5
config/whitelist.conf.example
Normal file
5
config/whitelist.conf.example
Normal file
@ -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
|
18
dhcp/dhcpd.conf.template
Normal file
18
dhcp/dhcpd.conf.template
Normal file
@ -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__
|
176
dhcp/entrypoint.sh
Executable file
176
dhcp/entrypoint.sh
Executable file
@ -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"
|
45
docker-compose.test.yml
Normal file
45
docker-compose.test.yml
Normal file
@ -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'"]
|
17
docker-compose.yml
Normal file
17
docker-compose.yml
Normal file
@ -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
|
155
scripts/autoconfig.sh
Executable file
155
scripts/autoconfig.sh
Executable file
@ -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"
|
134
scripts/install-service.sh
Executable file
134
scripts/install-service.sh
Executable file
@ -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"
|
29
systemd/dhcp-whitelist.service
Normal file
29
systemd/dhcp-whitelist.service
Normal file
@ -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
|
7
test/test-client/Dockerfile
Normal file
7
test/test-client/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM alpine:3.19
|
||||
|
||||
RUN apk add --no-cache \
|
||||
iproute2 \
|
||||
iputils
|
||||
|
||||
CMD ["/bin/sh"]
|
1
test/test-config/interface.conf
Normal file
1
test/test-config/interface.conf
Normal file
@ -0,0 +1 @@
|
||||
eth0
|
20
test/test-config/network.conf
Normal file
20
test/test-config/network.conf
Normal file
@ -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
|
1
test/test-config/whitelist.conf
Normal file
1
test/test-config/whitelist.conf
Normal file
@ -0,0 +1 @@
|
||||
02:42:ac:11:00:02
|
Loading…
x
Reference in New Issue
Block a user