Skip site navigation (1)Skip section navigation (2)


| raw e-mail | index | archive | help
URL: https://cgit.FreeBSD.org/src/commit/?id=9c52600a5a150117b4396df3b868cf2516e1674c

commit 9c52600a5a150117b4396df3b868cf2516e1674c
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2025-07-22 12:50:52 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2025-07-28 14:36:54 +0000

    if_ovpn: support floating clients
    
    If a client changes its IP address notify userspace of this.
    
    The UDP filtering function supplies the remote IP address, so we check if the
    address changed there. If so, we tag the packet with the new address. Once the
    packet is decrypted (and as part of that, has had its signature checked) we
    can commit to the address change. Take the write lock and notify userspace of
    the change.
    
    Reviewed by:    markj
    MFC after:      3 weeks
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D51468
---
 sys/net/if_ovpn.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 sys/net/if_ovpn.h |   1 +
 sys/sys/mbuf.h    |   1 +
 3 files changed, 194 insertions(+), 1 deletion(-)

diff --git a/sys/net/if_ovpn.c b/sys/net/if_ovpn.c
index 853a0556a080..fdbf70465338 100644
--- a/sys/net/if_ovpn.c
+++ b/sys/net/if_ovpn.c
@@ -131,6 +131,9 @@ struct ovpn_notification {
 	/* Delete notification */
 	enum ovpn_del_reason	del_reason;
 	struct ovpn_peer_counters	counters;
+
+	/* Float notification */
+	struct sockaddr_storage	address;
 };
 
 struct ovpn_softc;
@@ -195,6 +198,10 @@ struct ovpn_softc {
 	struct epoch_context	 epoch_ctx;
 };
 
+struct ovpn_mtag {
+	struct sockaddr_storage	 addr;
+};
+
 static struct ovpn_kpeer *ovpn_find_peer(struct ovpn_softc *, uint32_t);
 static bool ovpn_udp_input(struct mbuf *, int, struct inpcb *,
     const struct sockaddr *, void *);
@@ -206,6 +213,8 @@ static void ovpn_free_kkey_dir(struct ovpn_kkey_dir *);
 static bool ovpn_check_replay(struct ovpn_kkey_dir *, uint32_t);
 static int ovpn_peer_compare(const struct ovpn_kpeer *,
     const struct ovpn_kpeer *);
+static bool ovpn_sockaddr_compare(const struct sockaddr *,
+    const struct sockaddr *);
 
 static RB_PROTOTYPE(ovpn_kpeers, ovpn_kpeer, tree, ovpn_peer_compare);
 static RB_GENERATE(ovpn_kpeers, ovpn_kpeer, tree, ovpn_peer_compare);
@@ -283,6 +292,43 @@ ovpn_peer_compare(const struct ovpn_kpeer *a, const struct ovpn_kpeer *b)
 	return (a->peerid - b->peerid);
 }
 
+static bool
+ovpn_sockaddr_compare(const struct sockaddr *a,
+    const struct sockaddr *b)
+{
+	if (a->sa_family != b->sa_family)
+		return (false);
+	MPASS(a->sa_len == b->sa_len);
+
+	switch (a->sa_family) {
+	case AF_INET: {
+		const struct sockaddr_in *a4, *b4;
+
+		a4 = (const struct sockaddr_in *)a;
+		b4 = (const struct sockaddr_in *)b;
+
+		if (a4->sin_port != b4->sin_port)
+			return (false);
+
+		return (a4->sin_addr.s_addr == b4->sin_addr.s_addr);
+	}
+	case AF_INET6: {
+		const struct sockaddr_in6 *a6, *b6;
+
+		a6 = (const struct sockaddr_in6 *)a;
+		b6 = (const struct sockaddr_in6 *)b;
+
+		if (a6->sin6_port != b6->sin6_port)
+			return (false);
+
+		return (memcmp(&a6->sin6_addr, &b6->sin6_addr,
+		    sizeof(a6->sin6_addr)) == 0);
+	}
+	default:
+		panic("Unknown address family %d", a->sa_family);
+	}
+}
+
 static struct ovpn_kpeer *
 ovpn_find_peer(struct ovpn_softc *sc, uint32_t peerid)
 {
@@ -394,6 +440,44 @@ ovpn_nvlist_to_sockaddr(const nvlist_t *nvl, struct sockaddr_storage *sa)
 	return (0);
 }
 
+static int
+ovpn_add_sockaddr(nvlist_t *parent, const char *name, const struct sockaddr *s)
+{
+	nvlist_t *nvl;
+
+	nvl = nvlist_create(0);
+	if (nvl == NULL)
+		return (ENOMEM);
+
+	nvlist_add_number(nvl, "af", s->sa_family);
+
+	switch (s->sa_family) {
+	case AF_INET: {
+		const struct sockaddr_in *s4 = (const struct sockaddr_in *)s;
+
+		nvlist_add_number(nvl, "port", s4->sin_port);
+		nvlist_add_binary(nvl, "address", &s4->sin_addr,
+		    sizeof(s4->sin_addr));
+		break;
+	}
+	case AF_INET6: {
+		const struct sockaddr_in6 *s6 = (const struct sockaddr_in6 *)s;
+
+		nvlist_add_number(nvl, "port", s6->sin6_port);
+		nvlist_add_binary(nvl, "address", &s6->sin6_addr,
+		    sizeof(s6->sin6_addr));
+		break;
+	}
+	default:
+		nvlist_destroy(nvl);
+		return (EINVAL);
+	}
+
+	nvlist_move_nvlist(parent, name, nvl);
+
+	return (0);
+}
+
 static void
 ovpn_notify_del_peer(struct ovpn_softc *sc, struct ovpn_kpeer *peer)
 {
@@ -446,6 +530,33 @@ ovpn_notify_key_rotation(struct ovpn_softc *sc, struct ovpn_kpeer *peer)
 	}
 }
 
+static int
+ovpn_notify_float(struct ovpn_softc *sc, uint32_t peerid,
+    const struct sockaddr_storage *remote)
+{
+	struct ovpn_notification *n;
+
+	n = malloc(sizeof(*n), M_OVPN, M_NOWAIT | M_ZERO);
+	if (n == NULL)
+		return (ENOMEM);
+
+	n->peerid = peerid;
+	n->type = OVPN_NOTIF_FLOAT;
+	memcpy(&n->address, remote, sizeof(n->address));
+
+	if (buf_ring_enqueue(sc->notifring, n) != 0) {
+		free(n, M_OVPN);
+		return (ENOMEM);
+	} else if (sc->so != NULL) {
+		/* Wake up userspace */
+		sc->so->so_error = EAGAIN;
+		sorwakeup(sc->so);
+		sowwakeup(sc->so);
+	}
+
+	return (0);
+}
+
 static void
 ovpn_peer_release_ref(struct ovpn_kpeer *peer, bool locked)
 {
@@ -1377,12 +1488,36 @@ opvn_get_pkt(struct ovpn_softc *sc, nvlist_t **onvl)
 	}
 	nvlist_add_number(nvl, "peerid", n->peerid);
 	nvlist_add_number(nvl, "notification", n->type);
-	if (n->type == OVPN_NOTIF_DEL_PEER) {
+	switch (n->type) {
+	case OVPN_NOTIF_DEL_PEER: {
 		nvlist_add_number(nvl, "del_reason", n->del_reason);
 
 		/* No error handling, because we want to send the notification
 		 * even if we can't attach the counters. */
 		ovpn_notif_add_counters(nvl, n);
+		break;
+	}
+	case OVPN_NOTIF_FLOAT: {
+		int ret;
+
+		ret = ovpn_add_sockaddr(nvl, "address",
+		    (struct sockaddr *)&n->address);
+
+		if (ret) {
+			/*
+			 * Try to re-enqueue the notification. Maybe we'll
+			 * have better luck next time. No error handling,
+			 * because if we fail to re-enqueue there's nothing we can do.
+			 */
+			(void)ovpn_notify_float(sc, n->peerid, &n->address);
+			nvlist_destroy(nvl);
+			free(n, M_OVPN);
+			return (ret);
+		}
+		break;
+	}
+	default:
+		break;
 	}
 	free(n, M_OVPN);
 
@@ -1538,6 +1673,7 @@ ovpn_finish_rx(struct ovpn_softc *sc, struct mbuf *m,
     struct rm_priotracker *_ovpn_lock_trackerp)
 {
 	uint32_t af;
+	struct m_tag *mtag;
 
 	OVPN_RASSERT(sc);
 	NET_EPOCH_ASSERT();
@@ -1556,6 +1692,38 @@ ovpn_finish_rx(struct ovpn_softc *sc, struct mbuf *m,
 
 	OVPN_RUNLOCK(sc);
 
+	/* Check if the peer changed to a new source address. */
+	mtag = m_tag_find(m, PACKET_TAG_OVPN, NULL);
+	if (mtag != NULL) {
+		struct ovpn_mtag *ot = (struct ovpn_mtag *)(mtag + 1);
+
+		OVPN_WLOCK(sc);
+
+		/*
+		 * Check the address against the peer's remote again, because we may race
+		 * against ourselves (i.e. we may have tagged multiple packets to indicate we
+		 * floated).
+		 */
+		if (ovpn_sockaddr_compare((struct sockaddr *)&ot->addr,
+		    (struct sockaddr *)&peer->remote)) {
+			OVPN_WUNLOCK(sc);
+			goto skip_float;
+		}
+
+		/* And notify userspace. */
+		if (ovpn_notify_float(sc, peer->peerid, &ot->addr) == 0) {
+			/*
+			 * Update the 'remote' for this peer, but only if
+			 * we've actually enqueued the notification.
+			 * Otherwise we can try again later.
+			 */
+			memcpy(&peer->remote, &ot->addr, sizeof(peer->remote));
+		}
+
+		OVPN_WUNLOCK(sc);
+	}
+
+skip_float:
 	OVPN_COUNTER_ADD(sc, received_data_pkts, 1);
 	OVPN_COUNTER_ADD(sc, tunnel_bytes_received, m->m_pkthdr.len);
 	OVPN_PEER_COUNTER_ADD(peer, pkt_in, 1);
@@ -2318,6 +2486,29 @@ ovpn_udp_input(struct mbuf *m, int off, struct inpcb *inp,
 		return (true);
 	}
 
+	/*
+	 * If we got this from a different address than we expected tag the packet.
+	 * We'll deal with notifiying userspace later, after we've decrypted and
+	 * verified.
+	 */
+	if (! ovpn_sockaddr_compare((struct sockaddr *)&peer->remote, sa)) {
+		struct m_tag *mt;
+		struct ovpn_mtag *ot;
+
+		MPASS(sa->sa_len <= sizeof(ot->addr));
+		mt = m_tag_get(PACKET_TAG_OVPN, sizeof(*ot), M_NOWAIT);
+		/*
+		 * If we fail to allocate here we'll just try again on the next
+		 * packet.
+		 */
+		if (mt != NULL) {
+			ot = (struct ovpn_mtag *)(mt + 1);
+			memcpy(&ot->addr, sa, sa->sa_len);
+
+			m_tag_prepend(m, mt);
+		}
+	}
+
 	if (key->decrypt->cipher == OVPN_CIPHER_ALG_NONE) {
 		/* Now remove the outer headers */
 		m_adj_decap(m, sizeof(struct udphdr) + ohdrlen);
diff --git a/sys/net/if_ovpn.h b/sys/net/if_ovpn.h
index 2d6b8c1e7eff..2a24c35788a9 100644
--- a/sys/net/if_ovpn.h
+++ b/sys/net/if_ovpn.h
@@ -37,6 +37,7 @@
 enum ovpn_notif_type {
 	OVPN_NOTIF_DEL_PEER,
 	OVPN_NOTIF_ROTATE_KEY,
+	OVPN_NOTIF_FLOAT,
 };
 
 enum ovpn_del_reason {
diff --git a/sys/sys/mbuf.h b/sys/sys/mbuf.h
index c75094aea450..304bd019c9fc 100644
--- a/sys/sys/mbuf.h
+++ b/sys/sys/mbuf.h
@@ -1391,6 +1391,7 @@ extern bool		mb_use_ext_pgs;	/* Use ext_pgs for sendfile */
 #define	PACKET_TAG_PF_REASSEMBLED		31
 #define	PACKET_TAG_IPSEC_ACCEL_OUT		32  /* IPSEC accel out */
 #define	PACKET_TAG_IPSEC_ACCEL_IN		33  /* IPSEC accel in */
+#define	PACKET_TAG_OVPN				34 /* if_ovpn */
 
 /* Specific cookies and tags. */
 



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?>