LPM route table batch installation
I want to: install a routing table of LPM prefixes (e.g. 10.0.x.0/24 → port N) in a single P4Runtime Write RPC, then read it back to verify, and handle the case where one or more updates were rejected by the device.
The pattern
java
import io.github.zhh2001.jp4.P4Switch;
import io.github.zhh2001.jp4.entity.TableEntry;
import io.github.zhh2001.jp4.match.Match;
import io.github.zhh2001.jp4.batch.WriteResult;
import io.github.zhh2001.jp4.batch.UpdateFailure;
try (P4Switch sw = P4Switch.connectAsPrimary("127.0.0.1:50051")
.bindPipeline(p4info, deviceConfig)) {
WriteResult r = sw.batch()
.insert(routeEntry("10.0.1.0/24", 1))
.insert(routeEntry("10.0.2.0/24", 2))
.insert(routeEntry("10.0.3.0/24", 3))
.execute();
System.out.printf("installed %d routes (allSucceeded=%s)%n",
r.submitted(), r.allSucceeded());
if (!r.allSucceeded()) {
for (UpdateFailure f : r.failures()) {
System.err.printf("update[%d] failed: %s %s%n",
f.index(), f.code(), f.message());
}
}
// Read-back verification.
for (TableEntry e : sw.read("MyIngress.backend_lookup").all()) {
Match m = e.match("hdr.ipv4.dstAddr");
int port = e.action().paramInt("port");
System.out.printf(" %s → port %d%n", m, port);
}
}
private static TableEntry routeEntry(String cidr, int port) {
return TableEntry.in("MyIngress.backend_lookup")
.match("hdr.ipv4.dstAddr", Match.lpm(cidr))
.action("MyIngress.forward").param("port", port)
.build();
}Real usage: simple-loadbalancer.
Walkthrough
- Build entries through a helper. The
routeEntry(cidr, port)helper keeps the table name, match field name, and action name confined to one site — call sites read asrouteEntry("10.0.1.0/24", 1). Misspellings fail atsw.insert(...)time with a known-list error message, never reach the device. Match.lpm(cidr)parses CIDR notation. Equivalent tonew Match.Lpm(Ip4.of("10.0.1.0").toBytes(), 24)but reads more naturally for IPv4 routes.sw.batch().insert(...).insert(...).execute()packs multipleUpdates into one Write RPC. The device applies them in order, but P4Runtime does not mandate atomic batches — a failure does not roll back preceding successes.WriteResultcarries per-update results.allSucceeded()is true ifffailures()is empty. EachUpdateFailurecarries the original batch index, the gRPCErrorCode, and the device's message — enough to log, retry, or modify the offending update.- Read-back verification through
sw.read("table_name").all()returns the installed entries in whatever order the device reports them. TheMatchobject'stoString()renders the LPM aslpm 10.0.1.0/24for grep-friendly logs.
Modify and delete patterns
To move a route to a different port:
java
sw.modify(routeEntry("10.0.2.0/24", 4)); // throws if key doesn't existTo remove a route:
java
sw.delete(routeEntry("10.0.2.0/24", 4)); // only match key matters for deleteFor delete, the action half is silently ignored on the wire; pass any value.
See also
- Tables — the full
Matchbuilder surface, including LPM / ternary / range / optional. - P4Runtime spec mapping — how
sw.batch().execute()translates to a singleWriteRPC with multipleUpdates. - Canonical bytestring — why a read-back IP address may have fewer bytes than the written form.
simple-loadbalancerexample — the full runnable program this recipe was extracted from.