On 20 Aug 2022, at 16:33, Gert Doering wrote:
- iroute installation works for the easy cases (--route in server.conf,
   --iroute with a more-specific of that in ccd/).  It does not work
   for the nasty cases (--route and --iroute with same netbits).

   I will send a followup e-mail with more details on this soon.

*This* is the bit I want to cover separately, because this e-mail is
going to be long :-)

So, here's my test cases, to see if I could get DCO to misbehave.

Server config has:

# grep ^route server.conf
route 10.114.200.0 255.255.255.0
route-ipv6 fd00:abcd:114:200::/64
route 10.114.201.0 255.255.255.0
route-ipv6 fd00:abcd:114:201::/64

("pull two /24 and two /64 towards the tun interface")

There is one particular client which has many addresses configured on
its loopback

lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
        inet 10.114.200.74 netmask 0xffffffff
        inet 10.114.201.74 netmask 0xffffffff
        inet 10.114.202.74 netmask 0xffffffff
        inet6 fd00:abcd:114:200::74 prefixlen 128
        inet6 fd00:abcd:114:201::74 prefixlen 128
        inet6 fd00:abcd:114:202::74 prefixlen 128

... and when that client connects, the expectation is that *other* clients
can ping these 6 addresses (note it's 200, 201, 202 for v4 and v6).

Here's the ccd file for this client:

# cat /root/t_server/tun-udp-p2mp/ccd/freebsd-74-amd64
# Host iroute
iroute 10.114.200.74 255.255.255.255
iroute-ipv6 fd00:abcd:114:200::74/128
# Subnet-iroute
iroute 10.114.201.0 255.255.255.0
iroute-ipv6 fd00:abcd:114:201::/64
# iroute-without-route
# 10.114.202, fd00:abcd:114:202::/64
iroute 10.114.202.74 255.255.255.255
iroute-ipv6 fd00:abcd:114:202::74/128


So the "200" routes are "install a host route for IPv4 or IPv6 that is
part of an aggregate".  This works fine.

The "202" routes are "install a host route for IPv4 or IPv6 where there is no covering --route" - this is basically something OpenVPN could not
do before DCO (update host routes on-demand on client connect), but is
something I've long waited for :-) - this also works fine.

Now, the 201 routes are "there whole network specified in --route is
routed to a single client".  This is not such an unusual setup in
cases like "there is a central office OpenVPN server, and one of the
branch offices wants it 192.168.77.0/24 reachable from the other
clients".  This *fails*.

For IPv4, we end up with "netstat -rn" looking like this:

10.114.201.0/24    10.114.2.2         UGS        tun1
10.114.201.0/24    10.114.2.8         UGS        tun1

and "route get" telling us the the first one is preferred

$ route get 10.114.201.74
   route to: 10.114.201.74
destination: 10.114.201.0
       mask: 255.255.255.0
    gateway: 10.114.2.2
        fib: 0
  interface: tun1

so packets loop... (10.114.2.2 is not an address that belongs to an
actual client, but "the tun interface peer") the client is 10.114.2.8.

$ traceroute 10.114.201.74
traceroute to 10.114.201.74 (10.114.201.74), 64 hops max, 40 byte packets
 1  10.114.2.2 (10.114.2.2)  1.706 ms  1.240 ms  0.921 ms
 2  localhost (127.0.0.1)  0.865 ms  0.809 ms  0.807 ms
 3  10.114.2.2 (10.114.2.2)  1.603 ms  1.605 ms  1.595 ms
 4  localhost (127.0.0.1)  1.533 ms  1.383 ms  1.768 ms


For IPv6, things look different - route installation fails

2022-08-20 15:47:24 freebsd-74-amd64/2001:608:0:814::f000:3 /sbin/route -6 add -net fd00:abcd:114:201::/64 fd00:abcd:114:2::1006 -fib 0 add net fd00:abcd:114:201::/64: gateway fd00:abcd:114:2::1006 fib 0: route already in table

so, the iroute never makes it to kernel, "netstat -rn" remains at

fd00:abcd:114:201::/64 link#14 US tun1

and packets loop as well

$ traceroute6 fd00:abcd:114:201::74
traceroute6 to fd00:abcd:114:201::74 (fd00:abcd:114:201::74) from fd00:abcd:114:2::1, 64 hops max, 28 byte packets
 1  fd00:abcd:114:2::1000  1.749 ms  1.246 ms  0.930 ms
 2  fd00:abcd:114:2::1  0.836 ms  1.225 ms  1.067 ms
 3  fd00:abcd:114:2::1000  1.555 ms  1.631 ms  1.575 ms
 4  fd00:abcd:114:2::1  1.472 ms  1.531 ms  1.677 ms



So, what we do on *Linux* to handle this, is to work with route metrics (10.220.* = "same instances, different host, so different address range")

linux$ ip route |grep tun1
10.220.200.0/24 via 10.220.2.2 dev tun1 metric 200
10.220.200.74 via 10.220.2.8 dev tun1 metric 100
10.220.201.0/24 via 10.220.2.8 dev tun1 metric 100
10.220.201.0/24 via 10.220.2.2 dev tun1 metric 200
10.220.202.74 via 10.220.2.8 dev tun1 metric 100
linux$ ip -6 route |grep tun1
fd00:abcd:220:200::74 via fd00:abcd:220:2::1006 dev tun1 metric 100 pref medium
fd00:abcd:220:200::/64 dev tun1 metric 200 pref medium
fd00:abcd:220:201::/64 via fd00:abcd:220:2::1006 dev tun1 metric 100 pref medium
fd00:abcd:220:201::/64 dev tun1 metric 200 pref medium
fd00:abcd:220:202::74 via fd00:abcd:220:2::1006 dev tun1 metric 100 pref medium

so there's always 5 routes in the active table - 2 "--route" routes
(according to server.conf) with *metric 200*, and then 3 "--iroute" routes
(from ccd/) with metric DCO_IROUTE_METRIC = 100.

Linux handles this as would a "Cisco router" do - if there are two
identical routes with different metric, the lower-metric route is
used, and the other one is ignored.  So this works very nicely.


Now, back to FreeBSD.

  - our code does not try to set metrics on FreeBSD
  - my reading of route(8) does not show me anything in that direction
    (metric, preference, administrative distance, ...)
  - I do not understand FreeBSD internals well enough to know whether
    it can be done at all, or not.

It’s rather poorly (i.e. not) documented, but it is possible to set a route metric. It’s called a ‘weight’, and not actually mentioned in the man page. Or shown in netstat output.

But:

        $ sudo route add 172.16.2.0/24 10.0.2.1
        add net 172.16.2.0: gateway 10.0.2.1
        $ sudo route add 172.16.2.0/24 -weight 2 10.0.2.254
        add net 172.16.2.0: gateway 10.0.2.254

So we can add multiple routes for the same network, as long as they have a different gateway.

The usual `netstat -rn` doesn’t show the weight. You have to use the json/xml output to get it:

        $ netstat -rn --libxo json | jq
        …
              {
                "destination": "172.16.2.0/24",
                "gateway": "10.0.2.1",
                "flags": "UGS",
                "flags_pretty": [
                  "up",
                  "gateway",
                  "static"
                ],
                "weight": 1,
                "interface-name": "bnxt0"
              },
              {
                "destination": "172.16.2.0/24",
                "gateway": "10.0.2.254",
                "flags": "UGS",
                "flags_pretty": [
                  "up",
                  "gateway",
                  "static"
                ],
                "weight": 2,
                "interface-name": "bnxt0"
              },

A higher weight gives a higher priority, so that’s the opposite of what Linux does with metric, I believe.

If it can not be done, we need to document this as "one of the issues
with FreeBSD DCO", and it can of course be easily workarounded in most
cases

  --route 10.114.201.0/24
    --iroute 10.114.201.0/25
    --iroute 10.114.201.128/25

will get the same thing done "when client is online route the whole /25
down there", but it's harder to get into people's heads :-)  (adding
code to auto-split such a prefix is technically possible but would be
quite a complication, so I'd rather solve this "with documentation").

So, what shall we do?

If I’m understanding everything correctly it should be relatively simple to extend networking_freebsd.c to also set a weight for each route it installs. We’d have to convert the metric into a weight, which I think we could do as `weight = RT_MAX_WEIGHT - metric` (RT_MAX_WEIGHT is 16777215 /* 3 bytes */).

Can you try this?

diff --git a/src/openvpn/networking_freebsd.c b/src/openvpn/networking_freebsd.c
        index 0633dce7..e79af2bf 100644
        --- a/src/openvpn/networking_freebsd.c
        +++ b/src/openvpn/networking_freebsd.c
        @@ -10,6 +10,8 @@

         #if defined(TARGET_FREEBSD)

        +#include <net/route.h>
        +
         static int
         net_route_v4(const char *op, const in_addr_t *dst, int prefixlen,
                      const in_addr_t *gw, const char *iface, uint32_t table,
@@ -23,12 +25,13 @@ net_route_v4(const char *op, const in_addr_t *dst, int prefixlen,
             _dst = ntohl(*dst);
             _gw = ntohl(*gw);

        -    argv_printf(&argv, "%s %s -net %s/%d %s -fib %d",
        +    argv_printf(&argv, "%s %s -net %s/%d %s -fib %d -weight %d",
                         ROUTE_PATH, op,
                         inet_ntop(AF_INET, &_dst, buf1, sizeof(buf1)),
                         prefixlen,
                         inet_ntop(AF_INET, &_gw, buf2, sizeof(buf2)),
        -                table);
        +                table,
        +                RT_MAX_WEIGHT - metric);

             argv_msg(M_INFO, &argv);
             status = openvpn_execve_check(&argv, NULL, 0,
@@ -48,12 +51,13 @@ net_route_v6(const char *op, const struct in6_addr *dst,
             struct argv argv = argv_new();
             bool status;

        -    argv_printf(&argv, "%s -6 %s -net %s/%d %s -fib %d",
        +    argv_printf(&argv, "%s -6 %s -net %s/%d %s -fib %d -weight %d",
                         ROUTE_PATH, op,
                         inet_ntop(AF_INET6, dst, buf1, sizeof(buf1)),
                         prefixlen,
                         inet_ntop(AF_INET6, gw, buf2, sizeof(buf2)),
        -                table);
        +                table,
        +                RT_MAX_WEIGHT - metric);

             argv_msg(M_INFO, &argv);
             status = openvpn_execve_check(&argv, NULL, 0,

Kristof
_______________________________________________
Openvpn-devel mailing list
Openvpn-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to