Skip to content

Repository files navigation

P4 Programmable NAT / PAT

A software-defined NAT/PAT system that implements network address translation entirely in the P4 data plane, paired with a Python P4Runtime controller that manages the translation state and enforces a stateful firewall. Built for the Programmable Networks module (SCC.333) at Lancaster University.

Overview

The project recreates the behaviour of a home or edge router on two programmable switches. A PAT switch sits between a private home network and the wider internet and performs many-to-one translation, so a whole private subnet can share a single public IP. A second NAT switch performs static one-to-one translation between a pair of server networks.

The split between data plane and control plane is the point of the project. The data plane (the P4 programs) rewrites packets at line rate on the switch. The control plane (controller.py) connects to each switch over gRPC and P4Runtime, installs the compiled forwarding pipeline, manages the match-action table entries, and reacts to any packet the switch cannot translate on its own.

Network features

  • Static NAT (nat.p4, switch s2): a one-to-one mapping that rewrites source and destination IP addresses as traffic crosses the boundary between two networks.
  • Dynamic PAT / NAPT (pat.p4, switch s1): the many-to-one translation a real home router performs. A whole private subnet shares one public IP, with connections kept distinct by rewriting the transport-layer port as well as the IP.
  • Reactive flow installation: the first packet of an unknown outbound flow is punted to the controller, which installs matching forward and reverse rules. Every later packet of that flow is then translated entirely in the data plane with no controller involvement.
  • Port forwarding: internal services can be exposed on a public port, configured at runtime through the REST API.
  • Stateful inbound filtering: an unsolicited inbound TCP connection that matches no translation or port-forward rule is refused by having the controller build and send a TCP RST. This gives the NAT a default-deny posture and makes it act as a security boundary.

How it works

Data plane (P4). Each switch parses Ethernet, IPv4, TCP and UDP, matches packets against its tables (t_nat_src and t_nat_dst on the NAT switch, t_pat_out and t_pat_in on the PAT switch), rewrites the relevant addresses and ports in place, and recomputes the IPv4 and L4 checksums in the egress pipeline. A packet that matches no entry hits the table default action; on the PAT switch that action forwards the packet to the controller (CPU_PORT).

Control plane (Python). controller.py opens a P4Runtime session to each switch, pushes the forwarding pipeline, and runs a packet-processing loop per switch. When a packet arrives it decides whether to install a new PAT translation, apply a configured port-forward rule, or refuse the connection with a TCP RST. The current state can be inspected and changed at runtime through a small Flask REST API.

Topology

A private home network sits behind the PAT switch. Web and cloud server networks sit behind the NAT switch. Linux routers and the two BMv2 switches are wired together to emulate a small internet.

   Home network (private)                           Server networks
   192.168.0.0/24                                    10.0.0.0/16, 10.10.0.0/16
   +------------+                                    +------------+
   | homePC     |      s1 (PAT)        s2 (NAT)       | web1 / web2|
   | tablet     |     +--------+      +--------+      | cloud1     |
   | phone      |--+--| pat.p4 |--+ +-| nat.p4 |--+---|            |
   +------------+  |  +---+----+  | | +---+----+  |   +------------+
              home router |    [ internet ]  |  web/cloud routers
                          |      router      |
                          |  P4Runtime/gRPC  |
                          +--------+---------+
                                   |
                          +--------+--------+
                          |  controller.py  |  (Python control plane
                          |  + Flask REST   |   + REST API on :8080)
                          +-----------------+

REST API

The controller serves a small JSON API on port 8080.

Method and path Purpose
GET /api/status Health check that reports the number of connected switches.
GET /api/switches List the connected switches and their addresses.
GET /api/nat/list Active PAT translations, read directly from the P4 table.
GET /api/port-forward/list Configured port-forwarding rules.
GET /api/port-forward/add/<internal_ip>/<internal_port>/<external_port> Add a port-forwarding rule at runtime.

Components

  • p4src/nat.p4: static one-to-one NAT data-plane program, loaded onto switch s2.
  • p4src/pat.p4: dynamic PAT/NAPT and port forwarding data-plane program, loaded onto s1.
  • controller.py: the P4Runtime control plane and Flask REST API, with a NATController and a PATController built on a shared base class.
  • controller.json: declares which switches to connect to and the P4 artifacts each one uses.
  • mininet/topo.py: the Mininet topology (hosts, Linux routers, switches and links).
  • mininet/flask_ip.py: a tiny HTTP server run on each host that returns the caller's source IP, used to confirm that translation is working.
  • proto/ and util/: the P4Runtime protobuf definitions and client libraries, vendored from the p4lang tutorials (Apache-2.0).

Tech stack

  • P4_16 on the v1model architecture for the data-plane programs.
  • BMv2 and Stratum software switches to run the compiled P4.
  • Mininet for the emulated network topology.
  • P4Runtime, gRPC and Protocol Buffers for the control-plane API.
  • Python 3 with Flask for the REST API and Scapy for building the TCP RST packets.
  • Docker and docker-compose for a reproducible switch and Mininet environment.
  • Make to build the P4 programs and orchestrate the multi-process system.

Running

The build and run flow is driven by make, and three terminals are convenient.

Terminal A builds the P4 programs and starts the network:

make stop        # tear down any previous run
make p4-build    # compile nat.p4 and pat.p4 with p4c (via Docker)
make start       # bring up Mininet and the Stratum switches
make mn-cli      # attach to the Mininet CLI (Ctrl-D detaches, the network keeps running)

Terminal B starts the control plane:

make controller  # connects to the switches and serves the REST API on :8080

Terminal C drives traffic and inspects state. Generate traffic from the Mininet CLI in terminal A, for example homePC curl <server>, then query the API:

curl localhost:8080/api/nat/list
# expose internal 192.168.0.10:80 on public port 9090:
curl localhost:8080/api/port-forward/add/192.168.0.10/80/9090

See the Makefile for the full set of targets. make deps pulls the required Docker images and make clean removes build artifacts.

Scope

This is an educational project. Choices such as the fixed set of NAT mappings, the in-memory port-forward table, and the lack of an ARP responder are appropriate for a coursework demo but would need more work for any real deployment. It is released under the Apache License 2.0 (see LICENSE). The protobuf definitions in proto/ and the client libraries in util/ are vendored from the p4lang tutorials and keep their original Apache-2.0 headers; see NOTICE for details.

About

Software-defined NAT/PAT implemented in the P4 data-plane language, driven by a Python P4Runtime controller with a Flask REST API. Features dynamic port translation, runtime-configurable port forwarding, and a stateful firewall that rejects unsolicited inbound connections with TCP RSTs. Programmable Networks coursework, Lancaster University.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages