In this post I'll show how to make a Systemd service for easy addition and removal of local IP addresses.
Problem
The service may be useful when working with emulators such as Android Emulator, when one needs to configure network communication between the emulated environment and a local server. For example, the problem with Android Emulator is that it is isolated from the host's private network, as it has its own virtualised address space.
But it somehow manages to get access to addresses of the interfaces configured on the host. And we are going to exploit this feature.
I have tried different ways, and eventually came to making a Systemd service.
NetworkManager
We can easily overcome the problem by creating a virtual (VLAN) connection for an existing wired connection with the help of a popular network manager such as NetworkManager. For example, NetworkManager's interface allows to create a VLAN connection based on real wired or wireless connections and assign different IP addresses to it. However, for some reason it didn't work for my Wi-Fi connection, and wired connection was not available. So I was forced to search for another solution.
Systemd service
The following solution is based on the ip
tool from the iproute2 package.
Actually we can add address to an interface with a single command without the need for a Systemd service or something, e.g.:
ip address add 10.100.100.10/24 dev wlp3s0 label wlp3s0:1or
ip address change 10.100.100.10/24 dev wlp3s0 label wlp3s0:1where
10.100.100.10/24
specifies IP address and the network (24-bit mask, particularly), wlp3s0
points to existing wireless interface, and wlp3s0:1
is a tag for the address being added (will appear as separate interface in the output of ifconfig
). Removal is just as easy:
ip address del 10.100.100.10/24 dev wlp3s0
But I don't like the idea of typing the commands over and over. So I created the following script and saved it as /usr/local/sbin/ip-alias
:
#!/bin/bash - # Configures extra IP addresses for a network interface. # # Configuration files are located at /usr/local/etc/ip-alias.IFNAME.conf # where IFNAME is the network interface name. # # Ruslan Osmanov 2017 # Prints an error message err() { printf 'Error: %s\n' "$1" >&2 } # Prints an warning message warn() { printf 'Warning: %s\n' "$1" >&2 } # Prints an error message $1 (if any), then exits die() { [ $# -gt 0 ] && err "$1" exit 1 } # Prints usage info and exits with status $1 usage() { printf 'Usage: %s IFNAME [add|remove]\n' "$0" exit $1 } # Adds all addresses specified in the configuration file for device $1 add() { local device="$1" local address local new_device # Use the command group '(...)' in order to prevent modifying variables in # the global scope when 'source'ing the configuration file ( load_config "$device" || die "failed to read configuration file" [ ! -v addresses -o ${#addresses[*]} -eq 0 ] && \ die "required configuration 'addresses' is invalid/missing" for index in "${!addresses[@]}" ; do address="${addresses[$index]}" label="${device}:${index}" printf 'adding %s for %s as %s\n' "$address" "$device" "$label" # We might used ip address add instead, but it complicates consecutive calls to the script /bin/ip address change "$address" dev "$device" label "$label" [ $? -eq 0 ] || die "failed to add $address for $label" done ) } # Removes address $1 for device $2 remove_address() { local address="$1" local device="$2" printf 'removing address %s for device %s\n' "$address" "$device" /bin/ip address del "$address" dev "$device" if [ $? -ne 0 ]; then err "Failed to remove device $device" return 1 fi } # Removes all addresses specified in the configuration file for device $1 remove() { local device="$1" local address # Use the command group '(...)' in order to prevent modifying variables in # the global scope when 'source'ing the configuration file ( load_config "$device" || die "failed to read configuration file" [ ! -v addresses -o ${#addresses[*]} -eq 0 ] && \ die "required configuration 'addresses' is invalid/missing" for index in "${!addresses[@]}" ; do address="${addresses[$index]}" remove_address "$address" "$device" done ) } # Loads configuration file for device $1 load_config() { local filename="/usr/local/etc/ip-alias.${1}.conf" printf 'loading configuration from %s\n' "$filename" source "$filename" } if [ $# -lt 1 ]; then err "Invalid number of arguments" usage 1 fi device="$1" [ $# -gt 1 ] && action="$2" : ${action:='add'} case "$action" in add) add "$device" ;; remove) remove "$device" ;; *) die "action $action didn't match anything" esac
Configuration file is just a shell script with declaration of addresses
array variable. For example, /usr/local/etc/ip-alias.wlp3s0.conf
file might look like the following:
addresses=("10.100.100.10/24" "10.100.101.10/24")
Having this script we don't need to remember the syntax of the ip
command. All we need is to pass an interface name and an action name to the command:
/usr/local/sbin/ip-alias wlp3s0 add /usr/local/sbin/ip-alias wlp3s0 remove
But we can do better with the help of an init system. For example, we can create /etc/systemd/system/ip-alias@.service
Systemd service file with the following content:
[Unit] Description=Setup IP aliases for %i network interface After=network.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/local/sbin/ip-alias %i add ExecStop=/usr/local/sbin/ip-alias %i remove [Install] WantedBy=multi-user.target
If you are using NetworkManager, you may want to add it as dependency:
[Unit] Description=Setup IP aliases for %i network interface Wants=NetworkManager.service NetworkManager-wait-online.service After=network.target NetworkManager.service NetworkManager-wait-online.service BindsTo=NetworkManager.service NetworkManager-wait-online.service PartOf=NetworkManager.service NetworkManager-wait-online.service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/local/sbin/ip-alias %i add ExecStop=/usr/local/sbin/ip-alias %i remove ExecReload=/usr/local/sbin/ip-alias %i remove ExecReload=/usr/local/sbin/ip-alias %i add [Install] WantedBy=multi-user.target
# systemctl enable NetworkManager-wait-online.service
Then we can enable, start and stop the service for specific network interface(s), e.g.:
# systemctl enable ip-alias@wlp3s0 # systemctl start ip-alias@wlp3s0 # systemctl stop ip-alias@wlp3s0
With this service we don't need to remember anything, except the use of systemctl
.
Sample ifconfig
output when the service is running:
wlp3s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 10.10.0.102 netmask 255.255.255.0 broadcast 10.10.0.255 inet6 fe80::2ac2:ddff:fec7:16d5 prefixlen 64 scopeid 0x20<link> ether 28:c2:dd:c7:16:d5 txqueuelen 1000 (Ethernet) RX packets 173600 bytes 166406125 (158.6 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 96319 bytes 13015624 (12.4 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 wlp3s0:0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 10.100.100.10 netmask 255.255.255.0 broadcast 0.0.0.0 ether 28:c2:dd:c7:16:d5 txqueuelen 1000 (Ethernet) wlp3s0:1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 10.100.101.10 netmask 255.255.255.0 broadcast 0.0.0.0 ether 28:c2:dd:c7:16:d5 txqueuelen 1000 (Ethernet)