Skip to content

Commit 0d3cbfc

Browse files
merge main
2 parents 845da0a + 3799229 commit 0d3cbfc

File tree

29 files changed

+20935
-442
lines changed

29 files changed

+20935
-442
lines changed

asic/src/softnpu/table.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ impl TableOps<Handle> for Table {
265265
}
266266
}
267267
}
268-
("forward", params)
268+
("forward_v6", params)
269269
}
270270
(ROUTER4_LOOKUP_RT, "forward_vlan") => {
271271
let mut params = Vec::new();
@@ -339,7 +339,7 @@ impl TableOps<Handle> for Table {
339339
}
340340
}
341341
}
342-
("forward_vlan", params)
342+
("forward_vlan_v6", params)
343343
}
344344
(ROUTER6_LOOKUP_IDX, "index") => {
345345
let mut params = Vec::new();

dpd-api/src/lib.rs

Lines changed: 147 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use transceiver_controller::{
4646
};
4747

4848
mod v1;
49+
mod v2;
4950

5051
api_versions!([
5152
// WHEN CHANGING THE API (part 1 of 2):
@@ -59,6 +60,8 @@ api_versions!([
5960
// | example for the next person.
6061
// v
6162
// (next_int, IDENT),
63+
(6, CONSOLIDATED_V4_ROUTES),
64+
(5, UPLINK_PORTS),
6265
(4, V4_OVER_V6_ROUTES),
6366
(3, ATTACHED_SUBNETS),
6467
(2, DUAL_STACK_NAT_WORKFLOW),
@@ -377,6 +380,34 @@ pub trait DpdApi {
377380
#[endpoint {
378381
method = POST,
379382
path = "/route/ipv4",
383+
versions = ..VERSION_CONSOLIDATED_V4_ROUTES
384+
}]
385+
async fn route_ipv4_add_v1(
386+
rqctx: RequestContext<Self::Context>,
387+
update: TypedBody<v2::Ipv4RouteUpdate>,
388+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
389+
let route = update.into_inner();
390+
Self::route_ipv4_add(
391+
rqctx,
392+
TypedBody::from(Ipv4RouteUpdate {
393+
cidr: route.cidr,
394+
target: RouteTarget::V4(route.target),
395+
replace: route.replace,
396+
}),
397+
)
398+
.await
399+
}
400+
401+
/**
402+
* Route an IPv4 subnet to a link and a nexthop gateway (IPv4 or IPv6).
403+
*
404+
* This call can be used to create a new single-path route or to add new targets
405+
* to a multipath route.
406+
*/
407+
#[endpoint {
408+
method = POST,
409+
path = "/route/ipv4",
410+
versions = VERSION_CONSOLIDATED_V4_ROUTES..
380411
}]
381412
async fn route_ipv4_add(
382413
rqctx: RequestContext<Self::Context>,
@@ -392,12 +423,23 @@ pub trait DpdApi {
392423
#[endpoint {
393424
method = POST,
394425
path = "/route/ipv4-over-ipv6",
395-
versions = VERSION_V4_OVER_V6_ROUTES..,
426+
versions = VERSION_V4_OVER_V6_ROUTES..VERSION_CONSOLIDATED_V4_ROUTES,
396427
}]
397428
async fn route_ipv4_over_ipv6_add(
398429
rqctx: RequestContext<Self::Context>,
399430
update: TypedBody<Ipv4OverIpv6RouteUpdate>,
400-
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
431+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
432+
let route = update.into_inner();
433+
Self::route_ipv4_add(
434+
rqctx,
435+
TypedBody::from(Ipv4RouteUpdate {
436+
cidr: route.cidr,
437+
target: RouteTarget::V6(route.target),
438+
replace: route.replace,
439+
}),
440+
)
441+
.await
442+
}
401443

402444
/**
403445
* Route an IPv4 subnet to a link and a nexthop gateway.
@@ -408,6 +450,34 @@ pub trait DpdApi {
408450
#[endpoint {
409451
method = PUT,
410452
path = "/route/ipv4",
453+
versions = ..VERSION_CONSOLIDATED_V4_ROUTES
454+
}]
455+
async fn route_ipv4_set_v1(
456+
rqctx: RequestContext<Self::Context>,
457+
update: TypedBody<v2::Ipv4RouteUpdate>,
458+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
459+
let route = update.into_inner();
460+
Self::route_ipv4_set(
461+
rqctx,
462+
TypedBody::from(Ipv4RouteUpdate {
463+
cidr: route.cidr,
464+
target: RouteTarget::V4(route.target),
465+
replace: route.replace,
466+
}),
467+
)
468+
.await
469+
}
470+
471+
/**
472+
* Route an IPv4 subnet to a link and a nexthop gateway (IPv4 or IPv6).
473+
*
474+
* This call can be used to create a new single-path route or to replace any
475+
* existing routes with a new single-path route.
476+
*/
477+
#[endpoint {
478+
method = PUT,
479+
path = "/route/ipv4",
480+
versions = VERSION_CONSOLIDATED_V4_ROUTES..
411481
}]
412482
async fn route_ipv4_set(
413483
rqctx: RequestContext<Self::Context>,
@@ -423,12 +493,23 @@ pub trait DpdApi {
423493
#[endpoint {
424494
method = PUT,
425495
path = "/route/ipv4-over-ipv6",
426-
versions = VERSION_V4_OVER_V6_ROUTES..,
496+
versions = VERSION_V4_OVER_V6_ROUTES..VERSION_CONSOLIDATED_V4_ROUTES,
427497
}]
428498
async fn route_ipv4_over_ipv6_set(
429499
rqctx: RequestContext<Self::Context>,
430500
update: TypedBody<Ipv4OverIpv6RouteUpdate>,
431-
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
501+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
502+
let route = update.into_inner();
503+
Self::route_ipv4_set(
504+
rqctx,
505+
TypedBody::from(Ipv4RouteUpdate {
506+
cidr: route.cidr,
507+
target: RouteTarget::V6(route.target),
508+
replace: route.replace,
509+
}),
510+
)
511+
.await
512+
}
432513

433514
/**
434515
* Remove all targets for the given subnet
@@ -448,6 +529,32 @@ pub trait DpdApi {
448529
#[endpoint {
449530
method = DELETE,
450531
path = "/route/ipv4/{cidr}/{port_id}/{link_id}/{tgt_ip}",
532+
versions = ..VERSION_CONSOLIDATED_V4_ROUTES
533+
}]
534+
async fn route_ipv4_delete_target_v1(
535+
rqctx: RequestContext<Self::Context>,
536+
path: Path<v2::RouteTargetIpv4Path>,
537+
) -> Result<HttpResponseDeleted, HttpError> {
538+
let p = path.into_inner();
539+
Self::route_ipv4_delete_target(
540+
rqctx,
541+
Path::from(RouteTargetIpv4Path {
542+
cidr: p.cidr,
543+
port_id: p.port_id,
544+
link_id: p.link_id,
545+
tgt_ip: IpAddr::V4(p.tgt_ip),
546+
}),
547+
)
548+
.await
549+
}
550+
551+
/**
552+
* Remove a single target for the given IPv4 subnet (IPv4 or IPv6 next hop)
553+
*/
554+
#[endpoint {
555+
method = DELETE,
556+
path = "/route/ipv4/{cidr}/{port_id}/{link_id}/{tgt_ip}",
557+
versions = VERSION_CONSOLIDATED_V4_ROUTES..
451558
}]
452559
async fn route_ipv4_delete_target(
453560
rqctx: RequestContext<Self::Context>,
@@ -988,21 +1095,50 @@ pub trait DpdApi {
9881095
#[endpoint {
9891096
method = GET,
9901097
path = "/ports/{port_id}/links/{link_id}/nat_only",
1098+
versions = ..VERSION_UPLINK_PORTS
9911099
}]
9921100
async fn link_nat_only_get(
9931101
rqctx: RequestContext<Self::Context>,
9941102
path: Path<LinkPath>,
1103+
) -> Result<HttpResponseOk<bool>, HttpError> {
1104+
Self::link_uplink_get(rqctx, path).await
1105+
}
1106+
1107+
/// Return whether a port is intended to carry uplink traffic
1108+
#[endpoint {
1109+
method = GET,
1110+
path = "/ports/{port_id}/links/{link_id}/uplink",
1111+
versions = VERSION_UPLINK_PORTS..
1112+
}]
1113+
async fn link_uplink_get(
1114+
rqctx: RequestContext<Self::Context>,
1115+
path: Path<LinkPath>,
9951116
) -> Result<HttpResponseOk<bool>, HttpError>;
9961117

9971118
/// Set whether a port is configured to use drop non-nat traffic
9981119
#[endpoint {
9991120
method = PUT,
10001121
path = "/ports/{port_id}/links/{link_id}/nat_only",
1122+
versions = ..VERSION_UPLINK_PORTS
10011123
}]
10021124
async fn link_nat_only_set(
10031125
rqctx: RequestContext<Self::Context>,
10041126
path: Path<LinkPath>,
10051127
body: TypedBody<bool>,
1128+
) -> Result<HttpResponseUpdatedNoContent, HttpError> {
1129+
Self::link_uplink_set(rqctx, path, body).await
1130+
}
1131+
1132+
/// Set whether a port is intended to carry uplink traffic
1133+
#[endpoint {
1134+
method = PUT,
1135+
path = "/ports/{port_id}/links/{link_id}/uplink",
1136+
versions = VERSION_UPLINK_PORTS..
1137+
}]
1138+
async fn link_uplink_set(
1139+
rqctx: RequestContext<Self::Context>,
1140+
path: Path<LinkPath>,
1141+
body: TypedBody<bool>,
10061142
) -> Result<HttpResponseUpdatedNoContent, HttpError>;
10071143

10081144
/// Get the event history for the given link.
@@ -2083,15 +2219,15 @@ impl TryFrom<RouteTarget> for Ipv6Route {
20832219
}
20842220
}
20852221

2086-
/// Represents a new or replacement mapping of a subnet to a single IPv4
2087-
/// RouteTarget nexthop target.
2222+
/// Represents a new or replacement mapping of an IPv4 subnet to a single
2223+
/// RouteTarget nexthop target, which may be either IPv4 or IPv6.
20882224
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
20892225
pub struct Ipv4RouteUpdate {
20902226
/// Traffic destined for any address within the CIDR block is routed using
20912227
/// this information.
20922228
pub cidr: Ipv4Net,
20932229
/// A single Route associated with this CIDR
2094-
pub target: Ipv4Route,
2230+
pub target: RouteTarget,
20952231
/// Should this route replace any existing route? If a route exists and
20962232
/// this parameter is false, then the call will fail.
20972233
pub replace: bool,
@@ -2198,7 +2334,8 @@ pub struct SubnetPath {
21982334
pub subnet: IpNet,
21992335
}
22002336

2201-
/// Represents a single subnet->target route entry
2337+
/// Represents a single subnet->target route entry with an IPv4 or IPv6
2338+
/// next hop.
22022339
#[derive(Deserialize, Serialize, JsonSchema)]
22032340
pub struct RouteTargetIpv4Path {
22042341
/// The subnet being routed
@@ -2207,8 +2344,8 @@ pub struct RouteTargetIpv4Path {
22072344
pub port_id: PortId,
22082345
/// The link to which packets should be sent
22092346
pub link_id: LinkId,
2210-
/// The next hop in the IPv4 route
2211-
pub tgt_ip: Ipv4Addr,
2347+
/// The next hop in the route (IPv4 or IPv6)
2348+
pub tgt_ip: IpAddr,
22122349
}
22132350

22142351
#[derive(Deserialize, Serialize, JsonSchema)]

dpd-api/src/v2.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/
4+
//
5+
// Copyright 2026 Oxide Computer Company
6+
7+
use std::net::Ipv4Addr;
8+
9+
use common::ports::PortId;
10+
use dpd_types::link::LinkId;
11+
use dpd_types::route::Ipv4Route;
12+
use oxnet::Ipv4Net;
13+
use schemars::JsonSchema;
14+
use serde::{Deserialize, Serialize};
15+
16+
/// Represents a new or replacement mapping of a subnet to a single IPv4
17+
/// RouteTarget nexthop target.
18+
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
19+
pub struct Ipv4RouteUpdate {
20+
/// Traffic destined for any address within the CIDR block is routed using
21+
/// this information.
22+
pub cidr: Ipv4Net,
23+
/// A single Route associated with this CIDR
24+
pub target: Ipv4Route,
25+
/// Should this route replace any existing route? If a route exists and
26+
/// this parameter is false, then the call will fail.
27+
pub replace: bool,
28+
}
29+
30+
/// Represents a single subnet->target route entry
31+
#[derive(Deserialize, Serialize, JsonSchema)]
32+
pub struct RouteTargetIpv4Path {
33+
/// The subnet being routed
34+
pub cidr: Ipv4Net,
35+
/// The switch port to which packets should be sent
36+
pub port_id: PortId,
37+
/// The link to which packets should be sent
38+
pub link_id: LinkId,
39+
/// The next hop in the IPv4 route
40+
pub tgt_ip: Ipv4Addr,
41+
}

dpd-client/tests/integration_tests/attached_subnet.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ async fn test_egress(switch: &Switch, test: &ExternalTest) -> TestResult {
116116
tag: switch.client.inner().tag.clone(),
117117
};
118118
switch.client.link_ipv6_create(&port_id, &link_id, &entry).await.unwrap();
119+
switch.set_uplink(test.uplink_port, true).await;
119120

120121
// populate the ndp/arp table with the upstream router's mac and IP.
121122
if test.upstream_router_ip.parse::<Ipv4Addr>().is_ok() {

dpd-client/tests/integration_tests/common.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,15 @@ impl Switch {
537537
Ok(())
538538
}
539539

540+
pub async fn set_uplink(&self, phys_port: PhysPort, uplink: bool) {
541+
let (port_id, link_id) = self.link_id(phys_port).unwrap();
542+
self.client
543+
.link_uplink_set(&port_id, &link_id, uplink)
544+
.await
545+
.unwrap()
546+
.into_inner()
547+
}
548+
540549
pub fn tofino_port(&self, phys_port: PhysPort) -> u16 {
541550
let idx: usize = phys_port.into();
542551
if phys_port == NO_PORT {
@@ -1178,13 +1187,13 @@ async fn set_route_ipv4_common(
11781187
let (port_id, link_id) = switch.link_id(phys_port).unwrap();
11791188
let route = types::Ipv4RouteUpdate {
11801189
cidr,
1181-
target: types::Ipv4Route {
1190+
target: types::RouteTarget::V4(types::Ipv4Route {
11821191
port_id: port_id.clone(),
11831192
link_id: link_id.clone(),
11841193
tgt_ip,
11851194
tag: switch.client.inner().tag.clone(),
11861195
vlan_id,
1187-
},
1196+
}),
11881197
replace: false,
11891198
};
11901199
switch
@@ -1231,20 +1240,20 @@ async fn set_route_ipv4_over_ipv6_common(
12311240
let cidr = subnet.parse::<Ipv4Net>()?;
12321241
let tgt_ip: Ipv6Addr = gw.parse()?;
12331242
let (port_id, link_id) = switch.link_id(phys_port).unwrap();
1234-
let route = types::Ipv4OverIpv6RouteUpdate {
1243+
let route = types::Ipv4RouteUpdate {
12351244
cidr,
1236-
target: types::Ipv6Route {
1245+
target: types::RouteTarget::V6(types::Ipv6Route {
12371246
port_id: port_id.clone(),
12381247
link_id: link_id.clone(),
12391248
tgt_ip,
12401249
tag: switch.client.inner().tag.clone(),
12411250
vlan_id,
1242-
},
1251+
}),
12431252
replace: false,
12441253
};
12451254
switch
12461255
.client
1247-
.route_ipv4_over_ipv6_set(&route)
1256+
.route_ipv4_set(&route)
12481257
.await
12491258
.expect("Failed to add IPv4 route entry");
12501259

0 commit comments

Comments
 (0)