home of the madduck/ blog/
Freeing the primary IP address

I have a machine in Singapore and have been asked to switch it to another IP address (don't ask...). Due to numerous reasons, I do not want to reboot the machine, so I am faced with the task of switching the primary IP address of a remote host via SSH. Fun.

In the following remote identifies the remote machine, local the workstation from where the SSH session originates. 1.2.3.150 is the old IP that I have to free, and 1.2.3.200 is the new IP. 1.2.3.129 is the router IP and since it's a /25 network, 1.2.3.128 is the network address.

I'll be using the ip command from the iproute package exclusively. ifconfig must DIE!

The first step of this endeavour is making sure that traffic to your new IP is not going to be caught by the packet filter. This is simple enough:

remote# ip addr add 1.2.3.200/25 dev eth0
local# nc 1.2.3.200 22
SSH-2.0-OpenSSH_4.3p2 Debian-3~bpo.1

We have thus added a secondary address and verified that it can communicate, at least receive packets on port 22. However, since it is a secondary address, it depends on the primary address and will be removed if you remove the primary from the interface. We thus need to get tricky:

remote# ip addr del 1.2.3.200/25 dev eth0
remote# ip addr add 1.2.3.200/32 dev eth0

Note how we're adding the address with a /32 netmask instead. This makes it independent of the current primary address.

Now the fun begins. Close the SSH session and log in to the new IP:

remote# exit
local# ssh root@1.2.3.200

And remove the primary IP; yes, you heard me:

remote# ip route
1.2.3.128/25 dev eth0  proto kernel  scope link  src 1.2.3.150
default via 1.2.3.129 dev eth0
remote# ip addr del 1.2.3.150/25 dev eth0
remote# ip route
default via 1.2.3.129 dev eth0

Gotta love Linux: it saved the default route because it was still reachable from the 1.2.3.200/32 address, and only removed the link-local route.

Note that, at that point, the remote host does not belong to any subnet anymore. It may not be able to reach (or be reached by) other hosts from the subnet in which it resided. However, it is still reachable through its default gateway (1.2.3.129). Therefore, if your path to the host is going throught that gateway, you will not experience any connection loss. Hosts on the same subnet (same physical network) might still reach the machine via the gateway too, depending on the latter's filtering configuration (firewall).

We could stop right here for there is no real reason why this machine should have a link-local route or even know about the network to which it is attached; but we won't... instead, we'll add the new address again (a second time), now with a proper netmask:

remote# ip addr add 1.2.3.200/25 brd 1.2.3.255 dev eth0
remote# ip addr del 1.2.3.200/32 dev eth0
remote# ip route
1.2.3.128/25 dev eth0  proto kernel  scope link  src 1.2.3.200 
default via 1.2.3.129 dev eth0

After modifying /etc/network/interfaces, the deed is done. The next time I do this I might consider a safety net of the form:

remote# echo 'ifdown --force eth0; ifup eth0' | at now + 10 minutes

But that would only have been half as fun.

Of course it goes without saying that the above comes without warranties, if you try it out, you do so at your own risk. :)

NP: Neurosis / The Eye of every Storm

Update: It's been proposed to just run:

remote# ip addr del 1.2.3.150/25 dev eth0; \
  ip addr add 1.2.3.200/25 dev eth0; \
  ip route add default via 1.2.3.128

This may well work, especially if run within a screen session (I've had times when the shell did not execute subsequent commands after being cut off from the controlling tty).

However, this method does not guard against typoes. Or did you spot the typo in the above set of commands?

The method I provided above seems resilient to typos as no command ever affects the current connection.