All of lore.kernel.org
 help / color / mirror / Atom feed
* The SO_BINDTODEVICE was set to the desired interface, but packets are received from all interfaces.
@ 2018-05-07 10:19 Damir Mansurov
  2018-05-07 12:41 ` Paolo Abeni
  2018-05-07 16:14 ` Ben Greear
  0 siblings, 2 replies; 6+ messages in thread
From: Damir Mansurov @ 2018-05-07 10:19 UTC (permalink / raw)
  To: netdev; +Cc: Konstantin Ushakov, Alexandra N. Kossovsky, Andrey Dmitrov

[-- Attachment #1: Type: text/plain, Size: 1871 bytes --]


Greetings,

After successful call of the setsockopt(SO_BINDTODEVICE) function to set 
data reception from only one interface, the data is still received from 
all interfaces. Function setsockopt() returns 0 but then recv() receives 
data from all available network interfaces.

The problem is reproducible on linux kernels 4.14 - 4.16, but it does 
not on linux kernels 4.4, 4.13.

I have written C-code to reproduce this issue (see attached files 
b2d_send.c and b2d_recv.c). See below explanation of tested configuration.


         PC-1                              PC-2
  -------------------               -------------------
  | b2d_send        |               | b2d_recv        |
  |                 |               |                 |
  |           ------|               |------           |
  |          | eth0 |---------------| eth0 |          |
  |           ------|               |------           |
  |                 |               |                 |
  |           ------|               |------           |
  |          | eth1 |---------------| eth1 |          |
  |           ------|               |------           |
  |                 |               |                 |
  -------------------               -------------------

Steps:
1. Copy b2d_recv.c to PC-2, compile it ("gcc -o b2d_recv b2d_recv.c") 
and run "./b2d_recv eth0 23777" to get derived data only from eth0 
interface. Port number in this example is 23777 only for sample.

2. Copy b2d_send.c to PC-1, compile it ("gcc -o b2d_send b2d_send.c") 
and run "./b2d_send ip1 ip2 23777" where ip1 and ip2 are ip addresses of 
interfaces eth0 and eth1 of PC-2.

3. Result:
- b2d_recv prints out data from eth0 and eth1 on linux kernels from 4.14 
up to 4.16.
- b2d_recv prints out data from only eth0 on linux kernels below 4.14.


******************
Thanks,
Damir Mansurov
dnman@oktetlabs.ru

[-- Attachment #2: b2d_recv.c --]
[-- Type: text/x-csrc, Size: 3108 bytes --]

/*
 * Receive udp packets from desired interface
 *
 * This tool is used to check that option SO_BINDTODEVICE works correctly
 * setsockop(SO_BINDTODEVICE)
 * Use together with b2d_send.c
 *
 * 1. Start b2d_recv on receiver PC
 * 2. Start b2d_send on sender PC
 * 3. Check that packets are received only from the selected interface
 *
 * usage:   ./b2d_recv interface port
 * example: ./b2d_recv eth0 23777
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>

#define RX_BUFF_SIZE 1024

/* create recv socket, if error occured exit(EXIT_FAILURE) */
int create_recv_sock(const char * str_interface, const char * str_port,
                     struct sockaddr_in * sock_addr);

int
main(int argc, char *argv[])
{
    struct sockaddr_in sa_recv;

    if (argc != 3)
    {
        printf("usage: %s interface port\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int sfd_recv  = create_recv_sock(argv[1], argv[2], &sa_recv);

    char rx_buff[RX_BUFF_SIZE] = {0};
    struct sockaddr_in src_addr;
    socklen_t addr_len;
    memset(&src_addr, 0, sizeof(struct sockaddr_in));

    while (1)
    {
        ssize_t res = recvfrom(sfd_recv, rx_buff, RX_BUFF_SIZE - 1, 0,
                       (struct sockaddr *)&src_addr, &addr_len);
        if (res < 0)
        {
            perror("recvfrom");
            exit(EXIT_FAILURE);
        }

        char buf[256];
        const char * x = inet_ntop(src_addr.sin_family, &src_addr.sin_addr,
                                   buf, addr_len);
        if (x == NULL)
        {
            perror("inet_ntop");
            exit(EXIT_FAILURE);
        }
        printf("recv %ld bytes from %s:%u: \"%s\"\n",
                res, buf, ntohs(src_addr.sin_port), rx_buff);
    }

    exit(EXIT_SUCCESS);
}


int
create_recv_sock(const char * str_interface, const char * str_port,
                       struct sockaddr_in * sock_addr)
{
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    memset(sock_addr, 0, sizeof(struct sockaddr_in));
    sock_addr->sin_family = AF_INET;
    sock_addr->sin_addr.s_addr = htonl(INADDR_ANY);
    sock_addr->sin_port = htons((uint16_t)atoi(str_port));

    int res = bind(sockfd, (struct sockaddr*)sock_addr,
                      sizeof(struct sockaddr_in));

    if (res < 0)
    {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    if (strlen(str_interface) == 0)
    {
        puts("Data will be received from all interfaces");
    }
    else
    {
        res = setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
                         str_interface, strlen(str_interface) + 1);
        if (res < 0)
        {
            perror("setsockopt");
            exit(EXIT_FAILURE);
        }
        else
        {
            printf("success bind to device \"%s\"\n", str_interface);
        }
    }
    printf("recv port %s\n", str_port);
    return sockfd;

}/* create_recv_socket() */



[-- Attachment #3: b2d_send.c --]
[-- Type: text/x-csrc, Size: 2501 bytes --]

/*
 * Send udp packets from two various interfaces,
 *
 * This tool used to check correctly work option
 * setsockopt(SO_BINDTODEVICE)
 * Use together with b2d_recv.c
 * Detailed description in file b2d_recv.c
 *
 * usage    ./b2d_send ip1 ip2 port
 * example: ./b2d_send 192.168.44.2 192.168.45.2 23777
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>

#define RX_BUFF_SIZE 1024

/* create sender socket, if error occured exit(EXIT_FAILURE) */
int create_sender_sock(const char * str_ip, const char * str_port,
                       struct sockaddr_in * sock_addr);

int
main(int argc, char *argv[])
{
    struct sockaddr_in sa_sender1, sa_sender2;

    if (argc != 4)
    {
        printf("usage: %s ip1 ip2 port\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int sfd_sender1 = create_sender_sock(argv[1], argv[3], &sa_sender1);
    int sfd_sender2 = create_sender_sock(argv[2], argv[3], &sa_sender2);

    char tx_buff1[] = "Data from first socket";
    char tx_buff2[] = "Data from second socket";

    ssize_t res = send(sfd_sender1, tx_buff1, strlen(tx_buff1) + 1, 0);
    if (res < 0)
    {
        perror("sender1");
        exit(EXIT_FAILURE);
    }
    printf("success send %ld bytes to %s:%s \"%s\"\n",
            res, argv[1], argv[3], tx_buff1);

    res = send(sfd_sender2, tx_buff2, strlen(tx_buff2) + 1, 0);
    if (res < 0)
    {
        perror("sender2");
        exit(EXIT_FAILURE);
    }
    printf("success send %ld bytes to %s:%s \"%s\"\n",
            res, argv[2], argv[3], tx_buff2);

    exit(EXIT_SUCCESS);

}/* main() */


int
create_sender_sock(const char * str_ip, const char * str_port,
                   struct sockaddr_in * sock_addr)
{
    int res;
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    memset(sock_addr, 0, sizeof(struct sockaddr_in));
    sock_addr->sin_family = AF_INET;
    if (inet_pton(AF_INET, str_ip, &(sock_addr->sin_addr)) != 1)
    {
        perror("Bad ip_add");
        exit(EXIT_FAILURE);
    }
    sock_addr->sin_port = htons((uint16_t)atoi(str_port));

    res = connect(sockfd, (struct sockaddr*)sock_addr,
                  sizeof(struct sockaddr_in));
    if (res < 0)
    {
        perror("connect");
        exit(EXIT_FAILURE);
    }

    return sockfd;

}/* create_sender_socket() */



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: The SO_BINDTODEVICE was set to the desired interface, but packets are received from all interfaces.
  2018-05-07 10:19 The SO_BINDTODEVICE was set to the desired interface, but packets are received from all interfaces Damir Mansurov
@ 2018-05-07 12:41 ` Paolo Abeni
  2018-05-07 15:23   ` David Ahern
  2018-05-08 22:48   ` David Ahern
  2018-05-07 16:14 ` Ben Greear
  1 sibling, 2 replies; 6+ messages in thread
From: Paolo Abeni @ 2018-05-07 12:41 UTC (permalink / raw)
  To: Damir Mansurov, netdev, David Ahern
  Cc: Konstantin Ushakov, Alexandra N. Kossovsky, Andrey Dmitrov

Hi,
On Mon, 2018-05-07 at 13:19 +0300, Damir Mansurov wrote:
> After successful call of the setsockopt(SO_BINDTODEVICE) function to set 
> data reception from only one interface, the data is still received from 
> all interfaces. Function setsockopt() returns 0 but then recv() receives 
> data from all available network interfaces.
> 
> The problem is reproducible on linux kernels 4.14 - 4.16, but it does 
> not on linux kernels 4.4, 4.13.

I think that the cause is commit:

commit fb74c27735f0a34e76dbf1972084e984ad2ea145
Author: David Ahern <dsahern@gmail.com>
Date:   Mon Aug 7 08:44:16 2017 -0700

    net: ipv4: add second dif to udp socket lookups

Something like the following should fix, but I'm unsure it preserves
the intended semathics for 'sdif'. David, can you please have a look?
Thanks!

Paolo
---
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index dd3102a37ef9..0d593d5c33cf 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -401,9 +401,9 @@ static int compute_score(struct sock *sk, struct net *net,
 		bool dev_match = (sk->sk_bound_dev_if == dif ||
 				  sk->sk_bound_dev_if == sdif);
 
-		if (exact_dif && !dev_match)
+		if (!dev_match)
 			return -1;
-		if (sk->sk_bound_dev_if && dev_match)
+		if (sk->sk_bound_dev_if)
 			score += 4;
 	}
 

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: The SO_BINDTODEVICE was set to the desired interface, but packets are received from all interfaces.
  2018-05-07 12:41 ` Paolo Abeni
@ 2018-05-07 15:23   ` David Ahern
  2018-05-08 22:48   ` David Ahern
  1 sibling, 0 replies; 6+ messages in thread
From: David Ahern @ 2018-05-07 15:23 UTC (permalink / raw)
  To: Paolo Abeni, Damir Mansurov, netdev
  Cc: Konstantin Ushakov, Alexandra N. Kossovsky, Andrey Dmitrov

On 5/7/18 6:41 AM, Paolo Abeni wrote:
> Hi,
> On Mon, 2018-05-07 at 13:19 +0300, Damir Mansurov wrote:
>> After successful call of the setsockopt(SO_BINDTODEVICE) function to set 
>> data reception from only one interface, the data is still received from 
>> all interfaces. Function setsockopt() returns 0 but then recv() receives 
>> data from all available network interfaces.
>>
>> The problem is reproducible on linux kernels 4.14 - 4.16, but it does 
>> not on linux kernels 4.4, 4.13.
> 
> I think that the cause is commit:
> 
> commit fb74c27735f0a34e76dbf1972084e984ad2ea145
> Author: David Ahern <dsahern@gmail.com>
> Date:   Mon Aug 7 08:44:16 2017 -0700
> 
>     net: ipv4: add second dif to udp socket lookups
> 
> Something like the following should fix, but I'm unsure it preserves
> the intended semathics for 'sdif'. David, can you please have a look?
> Thanks!
> 
> Paolo
> ---
> diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
> index dd3102a37ef9..0d593d5c33cf 100644
> --- a/net/ipv4/udp.c
> +++ b/net/ipv4/udp.c
> @@ -401,9 +401,9 @@ static int compute_score(struct sock *sk, struct net *net,
>  		bool dev_match = (sk->sk_bound_dev_if == dif ||
>  				  sk->sk_bound_dev_if == sdif);
>  
> -		if (exact_dif && !dev_match)
> +		if (!dev_match)
>  			return -1;
> -		if (sk->sk_bound_dev_if && dev_match)
> +		if (sk->sk_bound_dev_if)
>  			score += 4;
>  	}
>  
> 

yes, that does look like a mistake -- no match on sk_bound_dev_if should
fail the lookup.

Let me apply the diff and run my vrf tests to make sure they still work.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: The SO_BINDTODEVICE was set to the desired interface, but packets are received from all interfaces.
  2018-05-07 10:19 The SO_BINDTODEVICE was set to the desired interface, but packets are received from all interfaces Damir Mansurov
  2018-05-07 12:41 ` Paolo Abeni
@ 2018-05-07 16:14 ` Ben Greear
  2018-05-07 21:20   ` David Ahern
  1 sibling, 1 reply; 6+ messages in thread
From: Ben Greear @ 2018-05-07 16:14 UTC (permalink / raw)
  To: Damir Mansurov, netdev
  Cc: Konstantin Ushakov, Alexandra N. Kossovsky, Andrey Dmitrov

On 05/07/2018 03:19 AM, Damir Mansurov wrote:
>
> Greetings,
>
> After successful call of the setsockopt(SO_BINDTODEVICE) function to set data reception from only one interface, the data is still received from all interfaces.
> Function setsockopt() returns 0 but then recv() receives data from all available network interfaces.
>
> The problem is reproducible on linux kernels 4.14 - 4.16, but it does not on linux kernels 4.4, 4.13.
>
> I have written C-code to reproduce this issue (see attached files b2d_send.c and b2d_recv.c). See below explanation of tested configuration.

Hello,

I am not sure if this is your problem or not, but if you are using VRF, then you need
to call SO_BINDTODEVICE before you do the 'normal' bind() call.

Thanks,
Ben

>
>
>         PC-1                              PC-2
>  -------------------               -------------------
>  | b2d_send        |               | b2d_recv        |
>  |                 |               |                 |
>  |           ------|               |------           |
>  |          | eth0 |---------------| eth0 |          |
>  |           ------|               |------           |
>  |                 |               |                 |
>  |           ------|               |------           |
>  |          | eth1 |---------------| eth1 |          |
>  |           ------|               |------           |
>  |                 |               |                 |
>  -------------------               -------------------
>
> Steps:
> 1. Copy b2d_recv.c to PC-2, compile it ("gcc -o b2d_recv b2d_recv.c") and run "./b2d_recv eth0 23777" to get derived data only from eth0 interface. Port number
> in this example is 23777 only for sample.
>
> 2. Copy b2d_send.c to PC-1, compile it ("gcc -o b2d_send b2d_send.c") and run "./b2d_send ip1 ip2 23777" where ip1 and ip2 are ip addresses of interfaces eth0
> and eth1 of PC-2.
>
> 3. Result:
> - b2d_recv prints out data from eth0 and eth1 on linux kernels from 4.14 up to 4.16.
> - b2d_recv prints out data from only eth0 on linux kernels below 4.14.
>
>
> ******************
> Thanks,
> Damir Mansurov
> dnman@oktetlabs.ru


-- 
Ben Greear <greearb@candelatech.com>
Candela Technologies Inc  http://www.candelatech.com

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: The SO_BINDTODEVICE was set to the desired interface, but packets are received from all interfaces.
  2018-05-07 16:14 ` Ben Greear
@ 2018-05-07 21:20   ` David Ahern
  0 siblings, 0 replies; 6+ messages in thread
From: David Ahern @ 2018-05-07 21:20 UTC (permalink / raw)
  To: Ben Greear, Damir Mansurov, netdev
  Cc: Konstantin Ushakov, Alexandra N. Kossovsky, Andrey Dmitrov

On 5/7/18 10:14 AM, Ben Greear wrote:
> On 05/07/2018 03:19 AM, Damir Mansurov wrote:
>>
>> Greetings,
>>
>> After successful call of the setsockopt(SO_BINDTODEVICE) function to
>> set data reception from only one interface, the data is still received
>> from all interfaces.
>> Function setsockopt() returns 0 but then recv() receives data from all
>> available network interfaces.
>>
>> The problem is reproducible on linux kernels 4.14 - 4.16, but it does
>> not on linux kernels 4.4, 4.13.
>>
>> I have written C-code to reproduce this issue (see attached files
>> b2d_send.c and b2d_recv.c). See below explanation of tested
>> configuration.
> 
> Hello,
> 
> I am not sure if this is your problem or not, but if you are using VRF,
> then you need
> to call SO_BINDTODEVICE before you do the 'normal' bind() call.
> 

This is a different problem -- socket lookup is matching when it should not.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: The SO_BINDTODEVICE was set to the desired interface, but packets are received from all interfaces.
  2018-05-07 12:41 ` Paolo Abeni
  2018-05-07 15:23   ` David Ahern
@ 2018-05-08 22:48   ` David Ahern
  1 sibling, 0 replies; 6+ messages in thread
From: David Ahern @ 2018-05-08 22:48 UTC (permalink / raw)
  To: Paolo Abeni, Damir Mansurov, netdev
  Cc: Konstantin Ushakov, Alexandra N. Kossovsky, Andrey Dmitrov

On 5/7/18 6:41 AM, Paolo Abeni wrote:
> Hi,
> On Mon, 2018-05-07 at 13:19 +0300, Damir Mansurov wrote:
>> After successful call of the setsockopt(SO_BINDTODEVICE) function to set 
>> data reception from only one interface, the data is still received from 
>> all interfaces. Function setsockopt() returns 0 but then recv() receives 
>> data from all available network interfaces.
>>
>> The problem is reproducible on linux kernels 4.14 - 4.16, but it does 
>> not on linux kernels 4.4, 4.13.
> 
> I think that the cause is commit:
> 
> commit fb74c27735f0a34e76dbf1972084e984ad2ea145
> Author: David Ahern <dsahern@gmail.com>
> Date:   Mon Aug 7 08:44:16 2017 -0700
> 
>     net: ipv4: add second dif to udp socket lookups
> 
> Something like the following should fix, but I'm unsure it preserves
> the intended semathics for 'sdif'. David, can you please have a look?
> Thanks!
> 
> Paolo
> ---
> diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
> index dd3102a37ef9..0d593d5c33cf 100644
> --- a/net/ipv4/udp.c
> +++ b/net/ipv4/udp.c
> @@ -401,9 +401,9 @@ static int compute_score(struct sock *sk, struct net *net,
>  		bool dev_match = (sk->sk_bound_dev_if == dif ||
>  				  sk->sk_bound_dev_if == sdif);
>  
> -		if (exact_dif && !dev_match)
> +		if (!dev_match)
>  			return -1;
> -		if (sk->sk_bound_dev_if && dev_match)
> +		if (sk->sk_bound_dev_if)
>  			score += 4;
>  	}
>  
> 

The above fixes the reported problem. You should make the same change to
ipv6 as well. Fixes tags:

Fixes: fb74c27735f0a ("net: ipv4: add second dif to udp socket lookups")
Fixes: 1801b570dd2ae ("net: ipv6: add second dif to udp socket lookups")


The change does break a VRF use case, but that case works by accident
given this bug. The use case is a client or server bound to an enslaved
device and trying to communicate locally. In some cases the error is the
ICMP 'Connection refused' getting lost; in other cases the packets don't
make it from one scope to another (eg., VRF based server talking to
device based client). After poking around for a couple of days, I
believe the proper fix for this uses case is beyond the scope of
anything that should be backported to 4.14. So I am fine with the
breakage to what is IMHO a corner case - and there is a reasonable
workaround until I find a proper solution.

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2018-05-08 22:48 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-05-07 10:19 The SO_BINDTODEVICE was set to the desired interface, but packets are received from all interfaces Damir Mansurov
2018-05-07 12:41 ` Paolo Abeni
2018-05-07 15:23   ` David Ahern
2018-05-08 22:48   ` David Ahern
2018-05-07 16:14 ` Ben Greear
2018-05-07 21:20   ` David Ahern

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.