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