Browse code

Fix best gateway selection over netlink

Netlink route request with NLM_F_DUMP flag set means to return
all entries matching criteria passed in message content -
matching supplied family & dst address in our case.
So, gateway from the first ipv4 route was always used.

On kernels earlier than 2.6.38 default routes are the last ones,
so arbitrary host/net route w/o gateway is likely be returned as
first, causing gateway to be invalid or empty.
After refactoring in 2.6.38 kernel default routes are on top, so
the problem with older kernels was hidden.

Fix this behavior by selecting first 0.0.0.0/0 if dst was not set
or empty. For IPv6, no behavior is changed - request ::/128 route,
so just clarify the sizes via netlink route api.

Tested on 5.4.0, 4.1.51, 2.6.36 and 2.6.22 kernels.

Signed-off-by: Vladislav Grishenko <themiron@yandex-team.ru>
Acked-by: Antonio Quartulli <a@unstable.cc>
Message-Id: <20200908123625.23179-1-themiron@yandex-team.ru>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg20900.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
(cherry picked from commit 505d5ad8fadcdc56bae07f4b95c05acd93a47c24)

Vladislav Grishenko authored on 2020/09/08 21:36:25
Showing 2 changed files
... ...
@@ -11,8 +11,11 @@ Standalone Debug Options
11 11
      --show-gateway
12 12
      --show-gateway IPv6-target
13 13
 
14
-  If an IPv6 target address is passed as argument, the IPv6 route for this
15
-  host is reported.
14
+  For IPv6 this queries the route towards ::/128, or the specified IPv6
15
+  target address if passed as argument.
16
+  For IPv4 on Linux, Windows, MacOS and BSD it looks for a 0.0.0.0/0 route.
17
+  If there are more specific routes, the result will not always be matching
18
+  the route of the IPv4 packets to the VPN gateway.
16 19
 
17 20
 
18 21
 Advanced Expert Options
... ...
@@ -345,6 +345,13 @@ sitnl_send(struct nlmsghdr *payload, pid_t peer, unsigned int groups,
345 345
  *               continue;
346 346
  *           }
347 347
  */
348
+
349
+            if (h->nlmsg_type == NLMSG_DONE)
350
+            {
351
+                ret = 0;
352
+                goto out;
353
+            }
354
+
348 355
             if (h->nlmsg_type == NLMSG_ERROR)
349 356
             {
350 357
                 err = (struct nlmsgerr *)NLMSG_DATA(h);
... ...
@@ -360,7 +367,11 @@ sitnl_send(struct nlmsghdr *payload, pid_t peer, unsigned int groups,
360 360
                         ret = 0;
361 361
                         if (cb)
362 362
                         {
363
-                            ret = cb(h, arg_cb);
363
+                            int r = cb(h, arg_cb);
364
+                            if (r <= 0)
365
+                            {
366
+                                ret = r;
367
+                            }
364 368
                         }
365 369
                     }
366 370
                     else
... ...
@@ -375,8 +386,12 @@ sitnl_send(struct nlmsghdr *payload, pid_t peer, unsigned int groups,
375 375
 
376 376
             if (cb)
377 377
             {
378
-                ret = cb(h, arg_cb);
379
-                goto out;
378
+                int r = cb(h, arg_cb);
379
+                if (r <= 0)
380
+                {
381
+                    ret = r;
382
+                    goto out;
383
+                }
380 384
             }
381 385
             else
382 386
             {
... ...
@@ -410,6 +425,7 @@ typedef struct {
410 410
     int addr_size;
411 411
     inet_address_t gw;
412 412
     char iface[IFNAMSIZ];
413
+    bool default_only;
413 414
 } route_res_t;
414 415
 
415 416
 static int
... ...
@@ -421,6 +437,12 @@ sitnl_route_save(struct nlmsghdr *n, void *arg)
421 421
     int len = n->nlmsg_len - NLMSG_LENGTH(sizeof(*r));
422 422
     unsigned int ifindex = 0;
423 423
 
424
+    /* filter-out non-zero dst prefixes */
425
+    if (res->default_only && r->rtm_dst_len != 0)
426
+    {
427
+        return 1;
428
+    }
429
+
424 430
     while (RTA_OK(rta, len))
425 431
     {
426 432
         switch (rta->rta_type)
... ...
@@ -477,11 +499,25 @@ sitnl_route_best_gw(sa_family_t af_family, const inet_address_t *dst,
477 477
     {
478 478
         case AF_INET:
479 479
             res.addr_size = sizeof(in_addr_t);
480
-            req.n.nlmsg_flags |= NLM_F_DUMP;
480
+            /*
481
+             * kernel can't return 0.0.0.0/8 host route, dump all
482
+             * the routes and filter for 0.0.0.0/0 in cb()
483
+             */
484
+            if (!dst || !dst->ipv4)
485
+            {
486
+                req.n.nlmsg_flags |= NLM_F_DUMP;
487
+                res.default_only = true;
488
+            }
489
+            else
490
+            {
491
+                req.r.rtm_dst_len = 32;
492
+            }
481 493
             break;
482 494
 
483 495
         case AF_INET6:
484 496
             res.addr_size = sizeof(struct in6_addr);
497
+            /* kernel can return ::/128 host route */
498
+            req.r.rtm_dst_len = 128;
485 499
             break;
486 500
 
487 501
         default: