When a peer changes its UDP endpoint, the DCO module emits a
notification to userpace. The message is parsed and the relevant
information are extracted in order to process the floating operation.
Note that we preserve IPv4-mapped IPv6 addresses in userspace when
receiving a pure IPv4 address from the module, otherwise openvpn
wouldn't be able to retrieve the multi_instance using the transport
address hash table lookup.
It may happen that a netlink notification gets lost, causing us to skip
a float step. If the peer then floats back to its previous address,
userspace closes the only valid instance while trying to process the
float, leading to a segfault. To prevent this, we ignore float attempts
to an address already taken by a peer with the same peer ID.
Change-Id: I33e9272b4196c7634db2fb33a75ae4261660867f
Signed-off-by: Ralf Lici <ralf@mandelbit.com>
Acked-by: Gert Doering <gert@greenie.muc.de>
Acked-by: Antonio Quartulli <antonio@mandelbit.com>
Acked-by: Lev Stipakov <lstipakov@gmail.com>
Message-Id: <20250718122230.14008-1-gert@greenie.muc.de>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg32210.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>
| ... | ... |
@@ -768,6 +768,44 @@ nla_put_failure: |
| 768 | 768 |
return ret; |
| 769 | 769 |
} |
| 770 | 770 |
|
| 771 |
+static bool |
|
| 772 |
+ovpn_parse_float_addr(struct nlattr **attrs, struct sockaddr *out) |
|
| 773 |
+{
|
|
| 774 |
+ if (!attrs[OVPN_A_PEER_REMOTE_PORT]) |
|
| 775 |
+ {
|
|
| 776 |
+ msg(D_DCO, "ovpn-dco: no remote port in PEER_FLOAT_NTF message"); |
|
| 777 |
+ return false; |
|
| 778 |
+ } |
|
| 779 |
+ |
|
| 780 |
+ if (attrs[OVPN_A_PEER_REMOTE_IPV4]) |
|
| 781 |
+ {
|
|
| 782 |
+ struct sockaddr_in *addr4 = (struct sockaddr_in *)out; |
|
| 783 |
+ CLEAR(*addr4); |
|
| 784 |
+ addr4->sin_family = AF_INET; |
|
| 785 |
+ addr4->sin_port = nla_get_u16(attrs[OVPN_A_PEER_REMOTE_PORT]); |
|
| 786 |
+ addr4->sin_addr.s_addr = nla_get_u32(attrs[OVPN_A_PEER_REMOTE_IPV4]); |
|
| 787 |
+ return true; |
|
| 788 |
+ } |
|
| 789 |
+ else if (attrs[OVPN_A_PEER_REMOTE_IPV6] |
|
| 790 |
+ && nla_len(attrs[OVPN_A_PEER_REMOTE_IPV6]) == sizeof(struct in6_addr)) |
|
| 791 |
+ {
|
|
| 792 |
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)out; |
|
| 793 |
+ CLEAR(*addr6); |
|
| 794 |
+ addr6->sin6_family = AF_INET6; |
|
| 795 |
+ addr6->sin6_port = nla_get_u16(attrs[OVPN_A_PEER_REMOTE_PORT]); |
|
| 796 |
+ memcpy(&addr6->sin6_addr, nla_data(attrs[OVPN_A_PEER_REMOTE_IPV6]), |
|
| 797 |
+ sizeof(addr6->sin6_addr)); |
|
| 798 |
+ if (attrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]) |
|
| 799 |
+ {
|
|
| 800 |
+ addr6->sin6_scope_id = nla_get_u32(attrs[OVPN_A_PEER_REMOTE_IPV6_SCOPE_ID]); |
|
| 801 |
+ } |
|
| 802 |
+ return true; |
|
| 803 |
+ } |
|
| 804 |
+ |
|
| 805 |
+ msg(D_DCO, "ovpn-dco: no valid remote IP address in PEER_FLOAT_NTF message"); |
|
| 806 |
+ return false; |
|
| 807 |
+} |
|
| 808 |
+ |
|
| 771 | 809 |
/* This function parses any netlink message sent by ovpn-dco to userspace */ |
| 772 | 810 |
static int |
| 773 | 811 |
ovpn_handle_msg(struct nl_msg *msg, void *arg) |
| ... | ... |
@@ -856,6 +894,45 @@ ovpn_handle_msg(struct nl_msg *msg, void *arg) |
| 856 | 856 |
break; |
| 857 | 857 |
} |
| 858 | 858 |
|
| 859 |
+ case OVPN_CMD_PEER_FLOAT_NTF: |
|
| 860 |
+ {
|
|
| 861 |
+ if (!attrs[OVPN_A_PEER]) |
|
| 862 |
+ {
|
|
| 863 |
+ msg(D_DCO, "ovpn-dco: no peer in PEER_FLOAT_NTF message"); |
|
| 864 |
+ return NL_STOP; |
|
| 865 |
+ } |
|
| 866 |
+ |
|
| 867 |
+ struct nlattr *fp_attrs[OVPN_A_PEER_MAX + 1]; |
|
| 868 |
+ if (nla_parse_nested(fp_attrs, OVPN_A_PEER_MAX, attrs[OVPN_A_PEER], |
|
| 869 |
+ NULL)) |
|
| 870 |
+ {
|
|
| 871 |
+ msg(D_DCO, "ovpn-dco: can't parse peer in PEER_FLOAT_NTF messsage"); |
|
| 872 |
+ return NL_STOP; |
|
| 873 |
+ } |
|
| 874 |
+ |
|
| 875 |
+ if (!fp_attrs[OVPN_A_PEER_ID]) |
|
| 876 |
+ {
|
|
| 877 |
+ msg(D_DCO, "ovpn-dco: no peer-id in PEER_FLOAT_NTF message"); |
|
| 878 |
+ return NL_STOP; |
|
| 879 |
+ } |
|
| 880 |
+ uint32_t peerid = nla_get_u32(fp_attrs[OVPN_A_PEER_ID]); |
|
| 881 |
+ |
|
| 882 |
+ if (!ovpn_parse_float_addr(fp_attrs, (struct sockaddr *)&dco->dco_float_peer_ss)) |
|
| 883 |
+ {
|
|
| 884 |
+ return NL_STOP; |
|
| 885 |
+ } |
|
| 886 |
+ |
|
| 887 |
+ struct gc_arena gc = gc_new(); |
|
| 888 |
+ msg(D_DCO_DEBUG, |
|
| 889 |
+ "ovpn-dco: received CMD_PEER_FLOAT_NTF, ifindex: %u, peer-id %u, address: %s", |
|
| 890 |
+ ifindex, peerid, print_sockaddr((struct sockaddr *)&dco->dco_float_peer_ss, &gc)); |
|
| 891 |
+ dco->dco_message_peer_id = (int)peerid; |
|
| 892 |
+ dco->dco_message_type = OVPN_CMD_PEER_FLOAT_NTF; |
|
| 893 |
+ |
|
| 894 |
+ gc_free(&gc); |
|
| 895 |
+ break; |
|
| 896 |
+ } |
|
| 897 |
+ |
|
| 859 | 898 |
case OVPN_CMD_KEY_SWAP_NTF: |
| 860 | 899 |
{
|
| 861 | 900 |
if (!attrs[OVPN_A_KEYCONF]) |
| ... | ... |
@@ -34,6 +34,7 @@ |
| 34 | 34 |
/* Defines to avoid mismatching with other platforms */ |
| 35 | 35 |
#define OVPN_CMD_DEL_PEER OVPN_CMD_PEER_DEL_NTF |
| 36 | 36 |
#define OVPN_CMD_SWAP_KEYS OVPN_CMD_KEY_SWAP_NTF |
| 37 |
+#define OVPN_CMD_FLOAT_PEER OVPN_CMD_PEER_FLOAT_NTF |
|
| 37 | 38 |
|
| 38 | 39 |
typedef enum ovpn_key_slot dco_key_slot_t; |
| 39 | 40 |
typedef enum ovpn_cipher_alg dco_cipher_t; |
| ... | ... |
@@ -75,6 +76,7 @@ typedef struct |
| 75 | 75 |
int dco_message_peer_id; |
| 76 | 76 |
int dco_message_key_id; |
| 77 | 77 |
int dco_del_peer_reason; |
| 78 |
+ struct sockaddr_storage dco_float_peer_ss; |
|
| 78 | 79 |
uint64_t dco_read_bytes; |
| 79 | 80 |
uint64_t dco_write_bytes; |
| 80 | 81 |
} dco_context_t; |
| ... | ... |
@@ -663,6 +663,7 @@ dco_handle_overlapped_success(dco_context_t *dco, bool queued) |
| 663 | 663 |
dco->dco_message_peer_id = dco->notif_buf.PeerId; |
| 664 | 664 |
dco->dco_message_type = dco->notif_buf.Cmd; |
| 665 | 665 |
dco->dco_del_peer_reason = dco->notif_buf.DelPeerReason; |
| 666 |
+ dco->dco_float_peer_ss = dco->notif_buf.FloatAddress; |
|
| 666 | 667 |
} |
| 667 | 668 |
else |
| 668 | 669 |
{
|
| ... | ... |
@@ -1243,6 +1243,41 @@ process_incoming_link(struct context *c, struct link_socket *sock) |
| 1243 | 1243 |
perf_pop(); |
| 1244 | 1244 |
} |
| 1245 | 1245 |
|
| 1246 |
+void |
|
| 1247 |
+extract_dco_float_peer_addr(const sa_family_t socket_family, |
|
| 1248 |
+ struct openvpn_sockaddr *out_osaddr, |
|
| 1249 |
+ const struct sockaddr *float_sa) |
|
| 1250 |
+{
|
|
| 1251 |
+ if (float_sa->sa_family == AF_INET) |
|
| 1252 |
+ {
|
|
| 1253 |
+ struct sockaddr_in *float4 = (struct sockaddr_in *)float_sa; |
|
| 1254 |
+ /* DCO treats IPv4-mapped IPv6 addresses as pure IPv4. However, on a |
|
| 1255 |
+ * dual-stack socket, we need to preserve the mapping otherwise openvpn |
|
| 1256 |
+ * will not be able to find the peer by its transport address. |
|
| 1257 |
+ */ |
|
| 1258 |
+ if (socket_family == AF_INET6) |
|
| 1259 |
+ {
|
|
| 1260 |
+ out_osaddr->addr.in6.sin6_family = AF_INET6; |
|
| 1261 |
+ out_osaddr->addr.in6.sin6_port = float4->sin_port; |
|
| 1262 |
+ |
|
| 1263 |
+ memset(&out_osaddr->addr.in6.sin6_addr.s6_addr, 0, 10); |
|
| 1264 |
+ out_osaddr->addr.in6.sin6_addr.s6_addr[10] = 0xff; |
|
| 1265 |
+ out_osaddr->addr.in6.sin6_addr.s6_addr[11] = 0xff; |
|
| 1266 |
+ memcpy(&out_osaddr->addr.in6.sin6_addr.s6_addr[12], |
|
| 1267 |
+ &float4->sin_addr.s_addr, sizeof(in_addr_t)); |
|
| 1268 |
+ } |
|
| 1269 |
+ else |
|
| 1270 |
+ {
|
|
| 1271 |
+ memcpy(&out_osaddr->addr.in4, float4, sizeof(struct sockaddr_in)); |
|
| 1272 |
+ } |
|
| 1273 |
+ } |
|
| 1274 |
+ else |
|
| 1275 |
+ {
|
|
| 1276 |
+ struct sockaddr_in6 *float6 = (struct sockaddr_in6 *)float_sa; |
|
| 1277 |
+ memcpy(&out_osaddr->addr.in6, float6, sizeof(struct sockaddr_in6)); |
|
| 1278 |
+ } |
|
| 1279 |
+} |
|
| 1280 |
+ |
|
| 1246 | 1281 |
static void |
| 1247 | 1282 |
process_incoming_dco(struct context *c) |
| 1248 | 1283 |
{
|
| ... | ... |
@@ -196,6 +196,21 @@ bool process_incoming_link_part1(struct context *c, struct link_socket_info *lsi |
| 196 | 196 |
void process_incoming_link_part2(struct context *c, struct link_socket_info *lsi, const uint8_t *orig_buf); |
| 197 | 197 |
|
| 198 | 198 |
/** |
| 199 |
+ * Transfers \c float_sa data extracted from an incoming DCO |
|
| 200 |
+ * PEER_FLOAT_NTF to \c out_osaddr for later processing. |
|
| 201 |
+ * |
|
| 202 |
+ * @param socket_family - The address family of the socket |
|
| 203 |
+ * @param out_osaddr - openvpn_sockaddr struct that will be filled the new |
|
| 204 |
+ * address data |
|
| 205 |
+ * @param float_sa - The sockaddr struct containing the data received from the |
|
| 206 |
+ * DCO notification |
|
| 207 |
+ */ |
|
| 208 |
+void |
|
| 209 |
+extract_dco_float_peer_addr(sa_family_t socket_family, |
|
| 210 |
+ struct openvpn_sockaddr *out_osaddr, |
|
| 211 |
+ const struct sockaddr *float_sa); |
|
| 212 |
+ |
|
| 213 |
+/** |
|
| 199 | 214 |
* Write a packet to the external network interface. |
| 200 | 215 |
* @ingroup external_multiplexer |
| 201 | 216 |
* |
| ... | ... |
@@ -3251,6 +3251,18 @@ multi_process_float(struct multi_context *m, struct multi_instance *mi, |
| 3251 | 3251 |
goto done; |
| 3252 | 3252 |
} |
| 3253 | 3253 |
|
| 3254 |
+ /* It doesn't make sense to let a peer float to the address it already |
|
| 3255 |
+ * has, so we disallow it. This can happen if a DCO netlink notification |
|
| 3256 |
+ * gets lost and we miss a floating step. |
|
| 3257 |
+ */ |
|
| 3258 |
+ if (m1->peer_id == m2->peer_id) |
|
| 3259 |
+ {
|
|
| 3260 |
+ msg(M_WARN, "disallowing peer %" PRIu32 " (%s) from floating to " |
|
| 3261 |
+ "its own address (%s)", |
|
| 3262 |
+ m1->peer_id, tls_common_name(mi->context.c2.tls_multi, false), |
|
| 3263 |
+ mroute_addr_print(&mi->real, &gc)); |
|
| 3264 |
+ goto done; |
|
| 3265 |
+ } |
|
| 3254 | 3266 |
msg(D_MULTI_MEDIUM, "closing instance %s", multi_instance_string(ex_mi, false, &gc)); |
| 3255 | 3267 |
multi_close_instance(m, ex_mi, false); |
| 3256 | 3268 |
} |
| ... | ... |
@@ -3384,6 +3396,17 @@ multi_process_incoming_dco(struct multi_context *m) |
| 3384 | 3384 |
{
|
| 3385 | 3385 |
process_incoming_del_peer(m, mi, dco); |
| 3386 | 3386 |
} |
| 3387 |
+#if defined(TARGET_LINUX) || defined(TARGET_WIN32) |
|
| 3388 |
+ else if (dco->dco_message_type == OVPN_CMD_FLOAT_PEER) |
|
| 3389 |
+ {
|
|
| 3390 |
+ ASSERT(mi->context.c2.link_sockets[0]); |
|
| 3391 |
+ extract_dco_float_peer_addr(mi->context.c2.link_sockets[0]->info.af, |
|
| 3392 |
+ &m->top.c2.from.dest, |
|
| 3393 |
+ (struct sockaddr *)&dco->dco_float_peer_ss); |
|
| 3394 |
+ multi_process_float(m, mi, mi->context.c2.link_sockets[0]); |
|
| 3395 |
+ CLEAR(dco->dco_float_peer_ss); |
|
| 3396 |
+ } |
|
| 3397 |
+#endif /* if defined(TARGET_LINUX) || defined(TARGET_WIN32) */ |
|
| 3387 | 3398 |
else if (dco->dco_message_type == OVPN_CMD_SWAP_KEYS) |
| 3388 | 3399 |
{
|
| 3389 | 3400 |
tls_session_soft_reset(mi->context.c2.tls_multi); |
| ... | ... |
@@ -322,7 +322,7 @@ bool multi_process_post(struct multi_context *m, struct multi_instance *mi, cons |
| 322 | 322 |
/** |
| 323 | 323 |
* Process an incoming DCO message (from kernel space). |
| 324 | 324 |
* |
| 325 |
- * @param m - The single \c multi_context structur.e |
|
| 325 |
+ * @param m - The single \c multi_context structure. |
|
| 326 | 326 |
* |
| 327 | 327 |
* @return |
| 328 | 328 |
* - True, if the message was received correctly. |
| ... | ... |
@@ -149,7 +149,8 @@ typedef struct _OVPN_MP_START_VPN {
|
| 149 | 149 |
|
| 150 | 150 |
typedef enum {
|
| 151 | 151 |
OVPN_CMD_DEL_PEER, |
| 152 |
- OVPN_CMD_SWAP_KEYS |
|
| 152 |
+ OVPN_CMD_SWAP_KEYS, |
|
| 153 |
+ OVPN_CMD_FLOAT_PEER |
|
| 153 | 154 |
} OVPN_NOTIFY_CMD; |
| 154 | 155 |
|
| 155 | 156 |
typedef enum {
|
| ... | ... |
@@ -164,6 +165,7 @@ typedef struct _OVPN_NOTIFY_EVENT {
|
| 164 | 164 |
OVPN_NOTIFY_CMD Cmd; |
| 165 | 165 |
int PeerId; |
| 166 | 166 |
OVPN_DEL_PEER_REASON DelPeerReason; |
| 167 |
+ struct sockaddr_storage FloatAddress; |
|
| 167 | 168 |
} OVPN_NOTIFY_EVENT, * POVPN_NOTIFY_EVENT; |
| 168 | 169 |
|
| 169 | 170 |
typedef struct _OVPN_MP_DEL_PEER {
|