qbone: Optionally set initcwnd on routes.
If --qbone_route_init_cwnd is non-zero, we will specify initcwnd on
bonnet-managed routes on the QBONE table; otherwise, we will continue the
present behavior of leaving this value unspecified.
Protected by FLAGS_qbone_route_init_cwnd.
PiperOrigin-RevId: 463645823
diff --git a/quiche/quic/qbone/bonnet/tun_device_controller.cc b/quiche/quic/qbone/bonnet/tun_device_controller.cc
index 80c1909..b6e3d76 100644
--- a/quiche/quic/qbone/bonnet/tun_device_controller.cc
+++ b/quiche/quic/qbone/bonnet/tun_device_controller.cc
@@ -15,6 +15,11 @@
"qbone interface to the qbone table. This is unnecessary in "
"environments with no other ipv6 route.");
+ABSL_FLAG(int, qbone_route_init_cwnd,
+ quic::NetlinkInterface::kUnspecifiedInitCwnd,
+ "If non-zero, will add initcwnd to QBONE routing rules. Setting "
+ "a value below 10 is dangerous and not recommended.");
+
namespace quic {
bool TunDeviceController::UpdateAddress(const IpRange& desired_range) {
@@ -82,7 +87,8 @@
rule.table == QboneConstants::kQboneRouteTableId) {
if (!netlink_->ChangeRoute(NetlinkInterface::Verb::kRemove, rule.table,
rule.destination_subnet, rule.scope,
- rule.preferred_source, rule.out_interface)) {
+ rule.preferred_source, rule.out_interface,
+ rule.init_cwnd)) {
QUIC_LOG(ERROR) << "Unable to remove old route to <"
<< rule.destination_subnet.ToString() << ">";
return false;
@@ -102,8 +108,8 @@
for (const auto& route : routes) {
if (!netlink_->ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId, route,
- RT_SCOPE_LINK, desired_address,
- link_info.index)) {
+ RT_SCOPE_LINK, desired_address, link_info.index,
+ absl::GetFlag(FLAGS_qbone_route_init_cwnd))) {
QUIC_LOG(ERROR) << "Unable to add route <" << route.ToString() << ">";
return false;
}
diff --git a/quiche/quic/qbone/bonnet/tun_device_controller_test.cc b/quiche/quic/qbone/bonnet/tun_device_controller_test.cc
index 62cb863..59863fe 100644
--- a/quiche/quic/qbone/bonnet/tun_device_controller_test.cc
+++ b/quiche/quic/qbone/bonnet/tun_device_controller_test.cc
@@ -13,6 +13,7 @@
#include "quiche/quic/qbone/qbone_constants.h"
ABSL_DECLARE_FLAG(bool, qbone_tun_device_replace_default_routing_rules);
+ABSL_DECLARE_FLAG(int, qbone_route_init_cwnd);
namespace quic::test {
namespace {
@@ -130,6 +131,7 @@
NetlinkInterface::RoutingRule matching_route;
matching_route.table = QboneConstants::kQboneRouteTableId;
matching_route.out_interface = kIfindex;
+ matching_route.init_cwnd = NetlinkInterface::kUnspecifiedInitCwnd;
for (int i = 0; i < num_matching_routes; i++) {
routing_rules->push_back(matching_route);
}
@@ -141,9 +143,10 @@
return true;
}));
- EXPECT_CALL(netlink_, ChangeRoute(NetlinkInterface::Verb::kRemove,
- QboneConstants::kQboneRouteTableId, _, _, _,
- kIfindex))
+ EXPECT_CALL(netlink_,
+ ChangeRoute(NetlinkInterface::Verb::kRemove,
+ QboneConstants::kQboneRouteTableId, _, _, _, kIfindex,
+ NetlinkInterface::kUnspecifiedInitCwnd))
.Times(num_matching_routes)
.WillRepeatedly(Return(true));
@@ -157,7 +160,7 @@
EXPECT_CALL(netlink_,
ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
- IpRangeEq(link_local_range_), _, _, kIfindex))
+ IpRangeEq(link_local_range_), _, _, kIfindex, _))
.WillOnce(Return(true));
EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {}));
@@ -170,9 +173,11 @@
EXPECT_CALL(netlink_, GetRuleInfo(_)).WillOnce(Return(true));
+ absl::SetFlag(&FLAGS_qbone_route_init_cwnd, 32);
EXPECT_CALL(netlink_, ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
- IpRangeEq(kIpRange), _, _, kIfindex))
+ IpRangeEq(kIpRange), _, _, kIfindex,
+ absl::GetFlag(FLAGS_qbone_route_init_cwnd)))
.Times(2)
.WillRepeatedly(Return(true))
.RetiresOnSaturation();
@@ -185,7 +190,7 @@
EXPECT_CALL(netlink_,
ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
- IpRangeEq(link_local_range_), _, _, kIfindex))
+ IpRangeEq(link_local_range_), _, _, kIfindex, _))
.WillOnce(Return(true));
EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {kIpRange, kIpRange}));
@@ -206,7 +211,7 @@
EXPECT_CALL(netlink_,
ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
- IpRangeEq(link_local_range_), _, _, kIfindex))
+ IpRangeEq(link_local_range_), _, _, kIfindex, _))
.WillOnce(Return(true));
EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {}));
@@ -220,7 +225,7 @@
EXPECT_CALL(netlink_, ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
- IpRangeEq(kIpRange), _, _, kIfindex))
+ IpRangeEq(kIpRange), _, _, kIfindex, _))
.Times(2)
.WillRepeatedly(Return(true))
.RetiresOnSaturation();
@@ -228,7 +233,7 @@
EXPECT_CALL(netlink_,
ChangeRoute(NetlinkInterface::Verb::kReplace,
QboneConstants::kQboneRouteTableId,
- IpRangeEq(link_local_range_), _, _, kIfindex))
+ IpRangeEq(link_local_range_), _, _, kIfindex, _))
.WillOnce(Return(true));
EXPECT_TRUE(controller_.UpdateRoutes(kIpRange, {kIpRange, kIpRange}));
diff --git a/quiche/quic/qbone/platform/mock_netlink.h b/quiche/quic/qbone/platform/mock_netlink.h
index 6e33d9c..72e3b66 100644
--- a/quiche/quic/qbone/platform/mock_netlink.h
+++ b/quiche/quic/qbone/platform/mock_netlink.h
@@ -25,7 +25,8 @@
MOCK_METHOD(bool, GetRouteInfo, (std::vector<RoutingRule>*), (override));
MOCK_METHOD(bool, ChangeRoute,
- (Verb, uint32_t, const IpRange&, uint8_t, QuicIpAddress, int32_t),
+ (Verb, uint32_t, const IpRange&, uint8_t, QuicIpAddress, int32_t,
+ uint32_t),
(override));
MOCK_METHOD(bool, GetRuleInfo, (std::vector<IpRule>*), (override));
diff --git a/quiche/quic/qbone/platform/netlink.cc b/quiche/quic/qbone/platform/netlink.cc
index b40ec38..0f20576 100644
--- a/quiche/quic/qbone/platform/netlink.cc
+++ b/quiche/quic/qbone/platform/netlink.cc
@@ -422,6 +422,7 @@
Netlink::RoutingRule rule;
rule.scope = route->rtm_scope;
rule.table = route->rtm_table;
+ rule.init_cwnd = Netlink::kUnspecifiedInitCwnd;
struct rtattr* rta;
for (rta = RTM_RTA(route); RTA_OK(rta, payload_length);
@@ -448,6 +449,25 @@
rule.out_interface = *reinterpret_cast<int*>(RTA_DATA(rta));
break;
}
+ case RTA_METRICS: {
+ struct rtattr* rtax;
+ int rta_payload_length = RTA_PAYLOAD(rta);
+ for (rtax = reinterpret_cast<struct rtattr*>(RTA_DATA(rta));
+ RTA_OK(rtax, rta_payload_length);
+ rtax = RTA_NEXT(rtax, rta_payload_length)) {
+ switch (rtax->rta_type) {
+ case RTAX_INITCWND: {
+ rule.init_cwnd = *reinterpret_cast<uint32_t*>(RTA_DATA(rtax));
+ break;
+ }
+ default: {
+ QUIC_VLOG(2) << absl::StrCat(
+ "Uninteresting RTA_METRICS attribute: ", rtax->rta_type);
+ }
+ }
+ }
+ break;
+ }
default: {
QUIC_VLOG(2) << absl::StrCat("Uninteresting attribute: ",
rta->rta_type);
@@ -489,7 +509,7 @@
bool Netlink::ChangeRoute(Netlink::Verb verb, uint32_t table,
const IpRange& destination_subnet, uint8_t scope,
QuicIpAddress preferred_source,
- int32_t interface_index) {
+ int32_t interface_index, uint32_t init_cwnd) {
if (!destination_subnet.prefix().IsInitialized()) {
return false;
}
@@ -551,6 +571,15 @@
message.AppendAttribute(RTA_TABLE, &table, sizeof(table));
+ if (init_cwnd != kUnspecifiedInitCwnd) {
+ char data[RTA_LENGTH(sizeof(uint32_t))];
+ struct rtattr* rta = reinterpret_cast<struct rtattr*>(data);
+ rta->rta_type = RTAX_INITCWND;
+ rta->rta_len = sizeof(data);
+ *reinterpret_cast<uint32_t*>(RTA_DATA(rta)) = init_cwnd;
+ message.AppendAttribute(RTA_METRICS, data, sizeof(data));
+ }
+
// RTA_OIF is the target interface for this rule.
message.AppendAttribute(RTA_OIF, &interface_index, sizeof(interface_index));
// The actual destination subnet must be truncated of all the tailing zeros.
diff --git a/quiche/quic/qbone/platform/netlink.h b/quiche/quic/qbone/platform/netlink.h
index 45709c7..857b8bf 100644
--- a/quiche/quic/qbone/platform/netlink.h
+++ b/quiche/quic/qbone/platform/netlink.h
@@ -74,6 +74,8 @@
//
// preferred_source can be !IsInitialized(), in which case it will be omitted.
//
+ // init_cwnd will be left unspecified if set to 0.
+ //
// For Verb::kRemove, rule matching is done by (destination_subnet, scope,
// preferred_source, interface_index). Return true if a matching rule is
// found. interface_index can be 0 for wilecard.
@@ -86,8 +88,8 @@
// matching rule is found, a new entry will be created.
bool ChangeRoute(Netlink::Verb verb, uint32_t table,
const IpRange& destination_subnet, uint8_t scope,
- QuicIpAddress preferred_source,
- int32_t interface_index) override;
+ QuicIpAddress preferred_source, int32_t interface_index,
+ uint32_t init_cwnd) override;
// Returns the set of all rules in the routing policy database.
bool GetRuleInfo(std::vector<Netlink::IpRule>* ip_rules) override;
diff --git a/quiche/quic/qbone/platform/netlink_interface.h b/quiche/quic/qbone/platform/netlink_interface.h
index 4bbde21..c4fc42c 100644
--- a/quiche/quic/qbone/platform/netlink_interface.h
+++ b/quiche/quic/qbone/platform/netlink_interface.h
@@ -73,6 +73,8 @@
uint8_t prefix_length, uint8_t ifa_flags, uint8_t ifa_scope,
const std::vector<struct rtattr*>& additional_attributes) = 0;
+ static constexpr uint32_t kUnspecifiedInitCwnd = 0;
+
// Routing rule reported back from GetRouteInfo.
struct RoutingRule {
uint32_t table;
@@ -80,6 +82,7 @@
QuicIpAddress preferred_source;
uint8_t scope;
int out_interface;
+ uint32_t init_cwnd; // kUnspecifiedInitCwnd if unspecified
};
struct IpRule {
@@ -109,7 +112,7 @@
virtual bool ChangeRoute(Verb verb, uint32_t table,
const IpRange& destination_subnet, uint8_t scope,
QuicIpAddress preferred_source,
- int32_t interface_index) = 0;
+ int32_t interface_index, uint32_t init_cwnd) = 0;
// Returns the set of all rules in the routing policy database.
virtual bool GetRuleInfo(std::vector<IpRule>* ip_rules) = 0;
diff --git a/quiche/quic/qbone/platform/netlink_test.cc b/quiche/quic/qbone/platform/netlink_test.cc
index 9bd91b0..b0ea1f0 100644
--- a/quiche/quic/qbone/platform/netlink_test.cc
+++ b/quiche/quic/qbone/platform/netlink_test.cc
@@ -204,7 +204,8 @@
unsigned char destination_length, unsigned char source_length,
unsigned char tos, unsigned char table, unsigned char protocol,
unsigned char scope, unsigned char type, unsigned int flags,
- QuicIpAddress destination, int interface_index) {
+ QuicIpAddress destination, int interface_index,
+ int init_cwnd) {
auto* msg = reinterpret_cast<struct rtmsg*>(NLMSG_DATA(nlm));
msg->rtm_family = family;
msg->rtm_dst_len = destination_length;
@@ -223,6 +224,16 @@
// Add egress interface
AddRTA(nlm, RTA_OIF, &interface_index, sizeof(interface_index));
+
+ // Add initcwnd
+ if (init_cwnd > 0) {
+ char data[RTA_LENGTH(sizeof(uint32_t))];
+ struct rtattr* rta = reinterpret_cast<struct rtattr*>(data);
+ rta->rta_len = sizeof(data);
+ rta->rta_type = RTA_METRICS;
+ *reinterpret_cast<uint32_t*>(RTA_DATA(rta)) = init_cwnd;
+ AddRTA(nlm, RTA_METRICS, data, sizeof(data));
+ }
}
TEST_F(NetlinkTest, GetLinkInfoWorks) {
@@ -474,7 +485,7 @@
buf, nullptr, RTM_NEWROUTE, seq);
CreateRtmsg(netlink_message, AF_INET6, 48, 0, 0,
RT_TABLE_MAIN, RTPROT_STATIC, RT_SCOPE_LINK,
- RTN_UNICAST, 0, destination, 7);
+ RTN_UNICAST, 0, destination, 7, 0);
ret += NLMSG_ALIGN(netlink_message->nlmsg_len);
netlink_message = CreateNetlinkMessage(
@@ -494,6 +505,7 @@
routing_rules[0].destination_subnet.ToString());
EXPECT_FALSE(routing_rules[0].preferred_source.IsInitialized());
EXPECT_EQ(7, routing_rules[0].out_interface);
+ EXPECT_EQ(0, routing_rules[0].init_cwnd);
}
TEST_F(NetlinkTest, ChangeRouteAdd) {
@@ -504,6 +516,7 @@
IpRange subnet;
subnet.FromString("ff80:dead:beef::/48");
int egress_interface_index = 7;
+ uint32_t init_cwnd = 32;
ExpectNetlinkPacket(
RTM_NEWROUTE, NLM_F_ACK | NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL,
[](void* buf, size_t len, int seq) {
@@ -516,8 +529,8 @@
netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
return netlink_message->nlmsg_len;
},
- [preferred_ip, subnet, egress_interface_index](const void* buf,
- size_t len) {
+ [preferred_ip, subnet, egress_interface_index, init_cwnd](const void* buf,
+ size_t len) {
auto* netlink_message = reinterpret_cast<const struct nlmsghdr*>(buf);
auto* rtm =
reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(netlink_message));
@@ -575,16 +588,25 @@
QboneConstants::kQboneRouteTableId);
break;
}
+ case RTA_METRICS: {
+ struct rtattr* rtax =
+ reinterpret_cast<struct rtattr*>(RTA_DATA(rta));
+ ASSERT_EQ(rtax->rta_type, RTAX_INITCWND);
+ ASSERT_EQ(rtax->rta_len, RTA_LENGTH(sizeof(uint32_t)));
+ ASSERT_EQ(*reinterpret_cast<uint32_t*>(RTA_DATA(rtax)),
+ init_cwnd);
+ break;
+ }
default:
EXPECT_TRUE(false) << "Seeing rtattr that should not be sent";
}
++num_rta;
}
- EXPECT_EQ(5, num_rta);
+ EXPECT_EQ(6, num_rta);
});
EXPECT_TRUE(netlink->ChangeRoute(
Netlink::Verb::kAdd, QboneConstants::kQboneRouteTableId, subnet,
- RT_SCOPE_LINK, preferred_ip, egress_interface_index));
+ RT_SCOPE_LINK, preferred_ip, egress_interface_index, init_cwnd));
}
TEST_F(NetlinkTest, ChangeRouteRemove) {
@@ -666,7 +688,8 @@
});
EXPECT_TRUE(netlink->ChangeRoute(
Netlink::Verb::kRemove, QboneConstants::kQboneRouteTableId, subnet,
- RT_SCOPE_LINK, preferred_ip, egress_interface_index));
+ RT_SCOPE_LINK, preferred_ip, egress_interface_index,
+ Netlink::kUnspecifiedInitCwnd));
}
TEST_F(NetlinkTest, ChangeRouteReplace) {
@@ -757,7 +780,8 @@
});
EXPECT_TRUE(netlink->ChangeRoute(
Netlink::Verb::kReplace, QboneConstants::kQboneRouteTableId, subnet,
- RT_SCOPE_LINK, preferred_ip, egress_interface_index));
+ RT_SCOPE_LINK, preferred_ip, egress_interface_index,
+ Netlink::kUnspecifiedInitCwnd));
}
} // namespace