DHCP, NAT and port forwarding

How a device gets its address, and how a router shares one public address with a whole network.

A12.6Networks and the webA level30 min

Do this lesson in the simulator

The last lesson left two questions open. When a phone joins a network, how does it get an IP address, a subnet mask and a router to use, without anyone typing them in? And if every device at home has a private address that the Internet will not route, how does a web page ever reach it? The answers are three jobs your home router does all day: DHCP, NAT and port forwarding.

DHCP

The dynamic host configuration protocol (DHCP) gives a device its network settings automatically when it joins a network. A DHCP server, usually built into the router, keeps a pool of addresses it may hand out. The exchange has four messages, remembered as DORA:

Step Message From What it says
1 Discover new device, broadcast "I have no address. Is there a DHCP server?" The device has no IP address yet, so it broadcasts to every device on the network.
2 Offer DHCP server "You can have 192.168.4.37, with these settings." The server picks a free address from its pool.
3 Request device, broadcast "I would like 192.168.4.37." Broadcast, so any other server that made an offer knows it was not chosen.
4 Acknowledge DHCP server "192.168.4.37 is yours for 24 hours." The address is now leased and the device configures itself.

Along with the IP address, the server supplies the subnet mask, the default gateway (the router's address) and the addresses of DNS servers. The address is given on a lease: for a fixed time, after which it must be renewed or it returns to the pool. A device normally asks to renew when half the lease has passed.

Why use DHCP rather than configuring every device by hand?

  • It is automatic: a visitor's laptop joins with no help from a technician.
  • It avoids conflicts: the server never hands the same address to two devices, which a person typing addresses might do.
  • It reuses addresses: a device that leaves stops renewing, and its address goes back into the pool for someone else. That matters when there are fewer addresses than devices that might ever visit.
  • Settings can be changed in one place: a new DNS server is given to every device at its next renewal.

Servers and printers, which other devices must always find at the same address, are usually given a static address instead, or a DHCP reservation that always gives that MAC address the same IP address.

pool = ["192.168.4.37", "192.168.4.38", "192.168.4.39"]
leases = {}                                   # MAC address -> IP address

def dhcp_request(mac):
    if mac in leases:                         # a returning device keeps its address
        return leases[mac]
    if not pool:
        return None                           # no addresses left
    address = pool.pop(0)
    leases[mac] = address
    return address

def dhcp_release(mac):
    pool.append(leases.pop(mac))

for mac in ["3C:61:05:1A:9F:02", "A4:CF:12:00:4B:7E", "3C:61:05:1A:9F:02"]:
    print(mac, "gets", dhcp_request(mac))
dhcp_release("A4:CF:12:00:4B:7E")
print("pool now:", pool)

Run this in the simulator

NAT

Network address translation (NAT) lets many devices with private addresses share one public address. The router has two addresses: a private one on the local network (say 192.168.4.1) and a public one on the Internet (say 198.51.100.20).

When a device sends a packet out to the Internet, the router:

  1. replaces the packet's private source IP address with its own public address;
  2. also replaces the source port with a port of its own choosing, so that two devices using the same port can still be told apart;
  3. records the change in its translation table.

When a reply comes back, it is addressed to the router's public address and that port. The router looks the port up in the table, rewrites the destination back to the private address and port, and forwards it inside.

Private socket Public socket Remote socket
192.168.4.23:49152 198.51.100.20:50000 203.0.113.80:443
192.168.4.37:49152 198.51.100.20:50001 203.0.113.80:443

Both devices chose client port 49152, but the router gave them different public ports, so the replies go to the right one.

NAT is used because:

  • it conserves public IPv4 addresses: a whole home or school needs just one;
  • it adds security: the private addresses are hidden from the Internet, and a packet arriving from outside that matches no entry in the table has nowhere to go, so it is dropped.

Port forwarding

That second point is a problem when you want outside computers to reach a device inside: a web server, a game server, or a camera you check from your phone. They cannot address its private IP address, and a connection they start has no NAT table entry.

Port forwarding is a fixed rule on the router: packets arriving at the public address on a chosen port are always forwarded to a particular private address and port. For example, a rule that forwards public port 8080 to 192.168.4.50:80 means a client connecting to 198.51.100.20:8080 reaches the web server on 192.168.4.50. Only the forwarded port is exposed; every other device and port stays hidden.

Task: get an address

The Router on this mat is a DHCP server that talks over the radio in a simplified form of DORA. Messages are words separated by single spaces.

  1. Send DISCOVER.
  2. The Router replies OFFER <address> <mask> <gateway> <lease seconds>. Print offered <address>.
  3. Send REQUEST <address>, using the address from the offer.
  4. The Router replies ACK <address> <mask> <gateway> <lease seconds>. From the ACK, print address: <address>, then gateway: <gateway>, then lease: <hours> hours, where hours is the lease in seconds divided by 3600 as a whole number.

Check messages() every 0.1 s while you wait for each reply. Read every value from the replies; the robot does not drive.

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

send("DISCOVER")
wait(1)
print(messages())

Task: the NAT table

Simulate the router's NAT table. The router's public address is 198.51.100.20, and it has one port forwarding rule: public port 8080 goes to 192.168.4.50 port 80.

outgoing is a list of private sockets, each a tuple (private IP, private port), sending packets out in that order. incoming is a list of public port numbers that reply packets arrive at, in that order.

For each outgoing socket: if it has no entry yet, give it the next free public port, starting at 50000 and going up by 1. Print out <private IP>:<private port> -> 198.51.100.20:<public port>.

Then for each incoming port: if it is in the NAT table, print in 198.51.100.20:<port> -> <private IP>:<private port>; otherwise, if it has a port forwarding rule, print the same form using the rule; otherwise print in 198.51.100.20:<port> dropped. That is 7 lines. The robot does not drive.

# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()

PUBLIC_IP = "198.51.100.20"
forwarding = {8080: ("192.168.4.50", 80)}
next_port = 50000

outgoing = [("192.168.4.23", 49152), ("192.168.4.37", 49152), ("192.168.4.23", 49153)]
incoming = [50001, 8080, 50009, 50000]

Challenges

  1. Add lease times to the DHCP cell: give each lease an expiry time and return expired addresses to the pool.
  2. Send the same outgoing socket twice in the NAT task. Check it keeps its public port.
  3. Why does NAT get in the way of two home computers trying to connect directly to each other, peer to peer?