[Openvpn-devel,v2] dco: fix source IP selection

Message ID 20250201062437.4059652-1-dqfext@gmail.com
State New
Headers show
Series [Openvpn-devel,v2] dco: fix source IP selection | expand

Commit Message

Qingfang Deng Feb. 1, 2025, 6:24 a.m. UTC
When multihome option is enabled, OpenVPN passes ipi_addr to DCO, which
is always 0.0.0.0. It should use ipi_spec_dst instead.
When local option is present, OpenVPN does not pass it to DCO.
As a result, Linux may pick a different IP as the source IP, breaking
the connection.

Signed-off-by: Qingfang Deng <dqfext@gmail.com>
---
v2: fix code style reported by CI
Discussions: https://github.com/OpenVPN/openvpn/pull/668

 src/openvpn/dco.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

Comments

Gert Doering Feb. 1, 2025, 7:54 a.m. UTC | #1
Hi,

On Sat, Feb 01, 2025 at 02:24:37PM +0800, Qingfang Deng wrote:
>  #endif
> +    if (sock->bind_local && sock->info.lsa->bind_local) {
> +        localaddr = sock->info.lsa->bind_local->ai_addr;
> +    }
> +

This is still not correct style - the opening "{" needs to go on its own
line.  Also see below on whether this is necessary at all.

>      int ret = dco_new_peer(&c->c1.tuntap->dco, multi->peer_id,
> -                           c->c2.link_sockets[0]->sd, NULL, remoteaddr, NULL, NULL);
> +                           c->c2.link_sockets[0]->sd, localaddr, remoteaddr, NULL, NULL);
>      if (ret < 0)
>      {
>          return ret;
> @@ -550,7 +555,7 @@ dco_multi_get_localaddr(struct multi_context *m, struct multi_instance *mi,
>          {
>              struct sockaddr_in *sock_in4 = (struct sockaddr_in *)local;
>  #if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)
> -            sock_in4->sin_addr = actual->pi.in4.ipi_addr;
> +            sock_in4->sin_addr = actual->pi.in4.ipi_spec_dst;
>  #elif defined(IP_RECVDSTADDR)
>              sock_in4->sin_addr = actual->pi.in4;

Well spotted.

>  #else
> @@ -616,10 +621,15 @@ dco_multi_add_new_peer(struct multi_context *m, struct multi_instance *mi)
>          vpn_addr6 = &c->c2.push_ifconfig_ipv6_local;
>      }
>  
> +    struct link_socket *ls = c->c2.link_sockets[0];
>      if (dco_multi_get_localaddr(m, mi, &local))
>      {
>          localaddr = (struct sockaddr *)&local;
>      }
> +    else if (ls->bind_local && ls->info.lsa->bind_local)
> +    {
> +        localaddr = ls->info.lsa->bind_local->ai_addr;
> +    }

Not sure about that.  If the socket is bound, the kernel knows about
the binding (because we pass the socket to the kernel).  You're sure this
is needed?

gert
Antonio Quartulli Feb. 10, 2025, 9:19 a.m. UTC | #2
Hi,

Thanks a lot for bringing this problem to our attention.

On 01/02/2025 08:54, Gert Doering wrote:
>> @@ -616,10 +621,15 @@ dco_multi_add_new_peer(struct multi_context *m, struct multi_instance *mi)
>>           vpn_addr6 = &c->c2.push_ifconfig_ipv6_local;
>>       }
>>   
>> +    struct link_socket *ls = c->c2.link_sockets[0];
>>       if (dco_multi_get_localaddr(m, mi, &local))
>>       {
>>           localaddr = (struct sockaddr *)&local;
>>       }
>> +    else if (ls->bind_local && ls->info.lsa->bind_local)
>> +    {
>> +        localaddr = ls->info.lsa->bind_local->ai_addr;
>> +    }
> 
> Not sure about that.  If the socket is bound, the kernel knows about
> the binding (because we pass the socket to the kernel).  You're sure this
> is needed?

As already questioned by Gert, I can confirm that this is not expected, 
because DCO should automatically pick any source address that was bound 
to the socket.

This said, DCO seems to not be doing this and therefore the bound source 
address is not respected.

Therefore this is a bug that needs fixing in DCO and should not be 
worked-around by userspace.

Hence, I'd suggest to drop the hunk about and rather fix the issue in DCO.
while looking at this patch, I have already identified where DCO needs 
adjustments, therefore it's likely that the next DCO revision will have 
this fixed.

Regards,

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

Patch

diff --git a/src/openvpn/dco.c b/src/openvpn/dco.c
index b5a21369..eef49ade 100644
--- a/src/openvpn/dco.c
+++ b/src/openvpn/dco.c
@@ -493,6 +493,7 @@  dco_p2p_add_new_peer(struct context *c)
     ASSERT(sock->info.connection_established);
 
     struct sockaddr *remoteaddr = &sock->info.lsa->actual.dest.addr.sa;
+    struct sockaddr *localaddr = NULL;
     struct tls_multi *multi = c->c2.tls_multi;
 #ifdef TARGET_FREEBSD
     /* In Linux in P2P mode the kernel automatically removes an existing peer
@@ -503,8 +504,12 @@  dco_p2p_add_new_peer(struct context *c)
         c->c2.tls_multi->dco_peer_id = -1;
     }
 #endif
+    if (sock->bind_local && sock->info.lsa->bind_local) {
+        localaddr = sock->info.lsa->bind_local->ai_addr;
+    }
+
     int ret = dco_new_peer(&c->c1.tuntap->dco, multi->peer_id,
-                           c->c2.link_sockets[0]->sd, NULL, remoteaddr, NULL, NULL);
+                           c->c2.link_sockets[0]->sd, localaddr, remoteaddr, NULL, NULL);
     if (ret < 0)
     {
         return ret;
@@ -550,7 +555,7 @@  dco_multi_get_localaddr(struct multi_context *m, struct multi_instance *mi,
         {
             struct sockaddr_in *sock_in4 = (struct sockaddr_in *)local;
 #if defined(HAVE_IN_PKTINFO) && defined(HAVE_IPI_SPEC_DST)
-            sock_in4->sin_addr = actual->pi.in4.ipi_addr;
+            sock_in4->sin_addr = actual->pi.in4.ipi_spec_dst;
 #elif defined(IP_RECVDSTADDR)
             sock_in4->sin_addr = actual->pi.in4;
 #else
@@ -616,10 +621,15 @@  dco_multi_add_new_peer(struct multi_context *m, struct multi_instance *mi)
         vpn_addr6 = &c->c2.push_ifconfig_ipv6_local;
     }
 
+    struct link_socket *ls = c->c2.link_sockets[0];
     if (dco_multi_get_localaddr(m, mi, &local))
     {
         localaddr = (struct sockaddr *)&local;
     }
+    else if (ls->bind_local && ls->info.lsa->bind_local)
+    {
+        localaddr = ls->info.lsa->bind_local->ai_addr;
+    }
 
     int ret = dco_new_peer(&c->c1.tuntap->dco, peer_id, sd, localaddr,
                            remoteaddr, vpn_addr4, vpn_addr6);