In industrial IoT deployments with OpenWRT devices, we often face a scenario where hundreds of devices share the same OpenVPN certificate (with duplicate-cn
enabled) for simplified management. This creates a roadblock when trying to assign static IPs through traditional client-config-dir
methods.
First, configure your OpenVPN server to use a pool with sequential IPs:
server 172.16.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
client-config-dir ccd
duplicate-cn
We'll use the --ipaddr
hack in the client's custom up script. Create /etc/openvpn/client-ip.sh
:
#!/bin/sh
# Extract device ID from hostname (e.g., "sensor-006" → 6)
DEVICE_ID=$(hostname | grep -o '[0-9]\+$')
REQUESTED_IP="172.16.0.$DEVICE_ID"
# Create temp config
echo "ifconfig-push $REQUESTED_IP 255.255.255.0" > /tmp/vpn_ip_$USER.ovpn
# Connect with custom config
exec /usr/sbin/openvpn --script-security 2 \
--config "$1" \
--ipaddr "$REQUESTED_IP" \
--up /usr/local/bin/ip-enforcer
Create /usr/local/bin/ip-enforcer
:
#!/bin/bash
ping -c 1 -W 2 $4 &>/dev/null
if [ $? -eq 0 ]; then
logger "OpenVPN IP conflict detected for $4"
exit 1
fi
exit 0
For LuCI-based configuration, modify your VPN interface:
config interface 'vpn'
option proto 'openvpn'
option config '/etc/openvpn/client.conf'
option up_script '/etc/openvpn/client-ip.sh'
option enabled '1'
Add this to your OpenVPN server's client-connect script to validate IP requests:
#!/bin/bash
REQUESTED_IP=$(env | grep '^ipaddr=' | cut -d= -f2)
# Validate IP format and range
if [[ $REQUESTED_IP =~ ^172\.16\.0\.([0-9]{1,3})$ ]]; then
DEVICE_ID=${BASH_REMATCH[1]}
if [ $DEVICE_ID -lt 1 ] || [ $DEVICE_ID -gt 254 ]; then
echo "Invalid device ID range" >&2
exit 1
fi
else
echo "Invalid IP format" >&2
exit 1
fi
When managing multiple IoT devices (like OpenWRT routers) in a VPN network, organizations often prefer using a single client certificate with duplicate-cn
enabled for simplified certificate management. However, this creates a roadblock for assigning static IPs through traditional methods like client-config-dir
and ifconfig-push
.
In industrial IoT deployments, maintaining consistent IP-to-device mapping (where device ID matches last IP octet) is crucial for:
- Predictable network troubleshooting
- Automated configuration management
- Device-specific firewall rules
While clients can't directly request IPs in OpenVPN, we can implement a workaround using client-specific configuration files with unique common names:
# /etc/openvpn/server.conf
client-config-dir /etc/openvpn/ccd
duplicate-cn
Then create CCD files named after your device IDs:
# /etc/openvpn/ccd/device6
ifconfig-push 172.16.0.6 172.16.0.1
For dynamic environments, use this bash script to generate CCD files:
#!/bin/bash
BASE_IP="172.16.0"
for i in {1..50}; do
echo "ifconfig-push ${BASE_IP}.${i} ${BASE_IP}.1" > "/etc/openvpn/ccd/device$i"
done
On OpenWRT devices, modify the client config to include device identification:
# /etc/config/openvpn
config openvpn 'client'
option config '/etc/openvpn/client.conf'
option enabled '1'
option device_id '6' # Matches CCD filename
Check assigned IPs with:
openvpn --status /var/run/openvpn.status 10
cat /var/run/openvpn.status
Common issues to watch for:
- CCD file permissions (must be readable by OpenVPN user)
- IP address conflicts in the VPN subnet
- Missing
duplicate-cn
directive in server config
For more dynamic assignments, implement a client-connect
script that:
#!/bin/bash
# /etc/openvpn/client-connect.sh
DEVICE_ID=$(echo $common_name | sed 's/device//')
echo "ifconfig-push 172.16.0.$DEVICE_ID 172.16.0.1" > $1
exit 0
Configure in server.conf:
script-security 2
client-connect /etc/openvpn/client-connect.sh