The branch main has been updated by markj:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=05b5d56c540335ed17acf843810901338bf862d5

commit 05b5d56c540335ed17acf843810901338bf862d5
Author:     Mark Johnston <ma...@freebsd.org>
AuthorDate: 2025-07-25 13:15:02 +0000
Commit:     Mark Johnston <ma...@freebsd.org>
CommitDate: 2025-07-25 17:39:01 +0000

    if_ovpn: Support multihomed server configurations
    
    In UDP server mode, openvpn implements the "multihome" option, which
    makes it avoid binding to an address.  Instead, the server socket is
    bound to INADDR_ANY.
    
    Today, when configuring a new peer and setting the source address,
    sockaddr() returns the wildcard address, so the source address is
    implicitly determined by the output interface.  This doesn't work as one
    would want if the WAN interface has multiple addresses and clients
    connect to non-primary addresses.
    
    Make multihome mode work properly: use the local address supplied by
    openvpn in preference to that of the socket.  We still fetch the port
    number out of the socket.
    
    PR:             273664
    Reviewed by:    kp
    MFC after:      1 month
    Sponsored by:   Stormshield
    Sponsored by:   Klara, Inc.
    Differential Revision:  https://reviews.freebsd.org/D51498
---
 sys/net/if_ovpn.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 46 insertions(+), 7 deletions(-)

diff --git a/sys/net/if_ovpn.c b/sys/net/if_ovpn.c
index d3ee1654ce82..853a0556a080 100644
--- a/sys/net/if_ovpn.c
+++ b/sys/net/if_ovpn.c
@@ -320,6 +320,25 @@ ovpn_get_port(const struct sockaddr_storage *s)
        }
 }
 
+static void
+ovpn_set_port(struct sockaddr_storage *s, unsigned short port)
+{
+       switch (s->ss_family) {
+       case AF_INET: {
+               struct sockaddr_in *in = (struct sockaddr_in *)s;
+               in->sin_port = port;
+               break;
+       }
+       case AF_INET6: {
+               struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)s;
+               in6->sin6_port = port;
+               break;
+       }
+       default:
+               panic("Unsupported address family %d", s->ss_family);
+       }
+}
+
 static int
 ovpn_nvlist_to_sockaddr(const nvlist_t *nvl, struct sockaddr_storage *sa)
 {
@@ -333,13 +352,14 @@ ovpn_nvlist_to_sockaddr(const nvlist_t *nvl, struct 
sockaddr_storage *sa)
                return (EINVAL);
 
        af = nvlist_get_number(nvl, "af");
-
        switch (af) {
 #ifdef INET
        case AF_INET: {
                struct sockaddr_in *in = (struct sockaddr_in *)sa;
                size_t len;
                const void *addr = nvlist_get_binary(nvl, "address", &len);
+
+               memset(in, 0, sizeof(*in));
                in->sin_family = af;
                in->sin_len = sizeof(*in);
                if (len != sizeof(in->sin_addr))
@@ -355,6 +375,8 @@ ovpn_nvlist_to_sockaddr(const nvlist_t *nvl, struct 
sockaddr_storage *sa)
                struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)sa;
                size_t len;
                const void *addr = nvlist_get_binary(nvl, "address", &len);
+
+               memset(in6, 0, sizeof(*in6));
                in6->sin6_family = af;
                in6->sin6_len = sizeof(*in6);
                if (len != sizeof(in6->sin6_addr))
@@ -475,7 +497,7 @@ ovpn_new_peer(struct ifnet *ifp, const nvlist_t *nvl)
 #ifdef INET6
        struct epoch_tracker et;
 #endif
-       struct sockaddr_storage remote;
+       struct sockaddr_storage local, remote;
        struct ovpn_kpeer *peer = NULL;
        struct file *fp = NULL;
        struct ovpn_softc *sc = ifp->if_softc;
@@ -544,20 +566,37 @@ ovpn_new_peer(struct ifnet *ifp, const nvlist_t *nvl)
        callout_init_rm(&peer->ping_send, &sc->lock, CALLOUT_SHAREDLOCK);
        callout_init_rm(&peer->ping_rcv, &sc->lock, 0);
 
-       peer->local.ss_len = sizeof(peer->local);
-       ret = sosockaddr(so, (struct sockaddr *)&peer->local);
-       if (ret)
+       memset(&local, 0, sizeof(local));
+       local.ss_len = sizeof(local);
+       ret = sosockaddr(so, (struct sockaddr *)&local);
+       if (ret != 0)
                goto error;
+       if (nvlist_exists_nvlist(nvl, "local")) {
+               struct sockaddr_storage local1;
 
-       if (ovpn_get_port(&peer->local) == 0) {
+               ret = ovpn_nvlist_to_sockaddr(nvlist_get_nvlist(nvl, "local"),
+                   &local1);
+               if (ret != 0)
+                       goto error;
+
+               /*
+                * openvpn doesn't provide a port here when in multihome mode,
+                * just steal the one the socket is bound to.
+                */
+               if (ovpn_get_port(&local1) == 0)
+                       ovpn_set_port(&local1, ovpn_get_port(&local));
+               memcpy(&local, &local1, sizeof(local1));
+       }
+       if (ovpn_get_port(&local) == 0) {
                ret = EINVAL;
                goto error;
        }
-       if (peer->local.ss_family != remote.ss_family) {
+       if (local.ss_family != remote.ss_family) {
                ret = EINVAL;
                goto error;
        }
 
+       memcpy(&peer->local, &local, sizeof(local));
        memcpy(&peer->remote, &remote, sizeof(remote));
 
 #ifdef INET6

Reply via email to