与Linux的SO_BINDTODEVICE套接字选项的问题 [英] Problems with SO_BINDTODEVICE Linux socket option

查看:3444
本文介绍了与Linux的SO_BINDTODEVICE套接字选项的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个网卡的PC。一(的eth0 )是局域网/互联网和其他的UDP通讯与一个微控制器设备。单片机有一个IP(192.168.7.2)和MAC地址。第二个PC网络适配器(的eth1 )有192.168.7.1。

该微控制器有一个非常简单的IP堆栈,因此对于MC发送UDP包的最简单方法是广播他们。

在PC端我想收到广播节目 - 但只是从的eth1 。所以,我试着将UDP套接字绑定到的eth1 设备。

中存在的问题(来源$ C ​​$ C以下):


  1. 的setsockopt(袜子,SOL_SOCKET,SO_BINDTODEVICE,设备的sizeof(设备))需要root权限,为什么呢? (设置其他选项可以作为用户)


  2. 的getsockopt(袜子,SOL_SOCKET,SO_BINDTODEVICE,(无效*)缓冲区,和放大器; opt_length)赋予协议不可用。我想读回我通过的setsockopt 命令设置的设备。


  3. 在哪里可以找到好的信息?我检查了一些Linux编程,网络书,但例如 SO_BINDTODEVICE 选项我只是在互联网上找到。


我的长篇(脏)的测试程序显示的问题。设置并取回了 SO_RCVTIMEO SO_BROADCAST 选项按预期工作。

运行code作为用户与退出:

无法设置SO_BINDTODEVICE(不允许的操作)

使用sudo运行提供了:

SO_BINDTODEVICE集
./mc-test:不能得到SO_BINDTODEVICE(协议不可用)

所以,设置选项似乎工作但回读是不可能的?

  / * SO_BINDTODEVICE测试* /#包括LT&; SYS / types.h中>
#包括LT&; SYS / socket.h中>
#包括LT&; netinet / in.h中>
#包括LT&; ARPA / inet.h>
#包括LT&;&netdb.h中GT;
#包括LT&;&stdio.h中GT;
#包括LT&;&unistd.h中GT;
#包括LT&;&string.h中GT;
#包括LT&;&stdlib.h中GT;
#包括LT&; SYS / time.h中>
#包括LT&;&errno.h中GT;#定义MC_IP192.168.7.2
#定义MC_PORT(54321)
#定义MY_PORT(54321)
#定义MY_DEVICE的eth1#定义BUFFERSIZE(1000)/ *全局变量* /
INT袜子;
结构SOCKADDR_IN MC_addr;
结构SOCKADDR_IN my_addr;
字符缓冲区[BUFFERSIZE]INT主(INT ARGC,CHAR *的argv [])
{
  unsigned int类型echolen,clientlen;
  INT RC,N;
  炭opt_buffer [1000];
  结构protoent * udp_protoent;
  timeval结构receive_timeout;
  INT optval的;
  socklen_t的opt_length;  / *创建UDP套接字* /
  如果((袜子=插座(AF_INET,SOCK_DGRAM,IPPROTO_UDP))小于0)
  {
    的printf(%S:无法创建UDP套接字(%S)\\ n
        的argv [0],字符串错误(错误));
    出口(EXIT_FAILURE);
  }
  的printf(UDP套接字创建\\ n);  / *设置recvfrom的超时值* /
  receive_timeout.tv_sec = 5;
  receive_timeout.tv_usec = 0;
  RC = setsockopt的(袜子,SOL_SOCKET,SO_RCVTIMEO,&安培; receive_timeout,
                的sizeof(receive_timeout));
  如果(RC!= 0)
  {
     的printf(%S:无法设置SO_RCVTIMEO(%S)\\ n
        的argv [0],字符串错误(错误));
     出口(EXIT_FAILURE);
  }
  的printf(超时设置为\\ n时间[S]:%d个\\ n时间[毫秒]:%d个\\ N,receive_timeout.tv_sec,receive_timeout.tv_usec);
  / *验证recvfrom的超时值* /
  RC =的getsockopt(袜子,SOL_SOCKET,SO_RCVTIMEO,&安培; receive_timeout,&安培; opt_length);
  如果(RC!= 0)
  {
     的printf(%S:无法获取套接字选项(%S)\\ n
        的argv [0],字符串错误(错误));
     出口(EXIT_FAILURE);
  }
  的printf(超时值\\ n时间[S]:%d个\\ n时间[毫秒]:%d个\\ N,receive_timeout.tv_sec,receive_timeout.tv_usec);  / *允许广播消息套接字* /
  INT真= 1;
  RC = setsockopt的(袜子,SOL_SOCKET,SO_BROADCAST,&安培;真,的sizeof(真));
  如果(RC!= 0)
  {
     的printf(%S:无法设置SO_BROADCAST(%S)\\ n
        的argv [0],字符串错误(错误));
     出口(EXIT_FAILURE);
  }
  的printf(设置SO_BROADCAST \\ n);
  / *验证SO_BROADCAST设置* /
  RC =的getsockopt(袜子,SOL_SOCKET,SO_BROADCAST,&安培; optval的,和放大器; opt_length);
  如果(optval的!= 0)
  {
    的printf(SO_BROADCAST启用\\ n);
  }  / *套接字绑定到一个网络设备* /
  为const char设备[] = MY_DEVICE;
  RC = setsockopt的(袜子,SOL_SOCKET,SO_BINDTODEVICE,设备的sizeof(设备));
  如果(RC!= 0)
  {
     的printf(%S:无法设置SO_BINDTODEVICE(%S)\\ n
        的argv [0],字符串错误(错误));
     出口(EXIT_FAILURE);
  }
  的printf(SO_BINDTODEVICE集\\ n);
  / *验证SO_BINDTODEVICE设置* /
  RC =的getsockopt(袜子,SOL_SOCKET,SO_BINDTODEVICE,(无效*)缓冲区,和放大器; opt_length);
  如果(RC!= 0)
  {
     的printf(%S:不能得到SO_BINDTODEVICE(%S)\\ n
        的argv [0],字符串错误(错误));
     出口(EXIT_FAILURE);
  }
  如果(RC == 0)
  {
    的printf(SO_BINDTODEVICE为:%S \\ n,缓冲区);
  }
  / *构建服务器SOCKADDR_IN结构* /
  memset的(安培; MC_addr,0,sizeof的(MC_addr)); / *清除结构* /
  MC_addr.sin_family = AF_INET; / *互联网/ IP * /
  MC_addr.sin_addr.s_addr = inet_addr(MC_IP); /* IP地址 */
  MC_addr.sin_port = htons(MC_PORT); /* 服务器端口 */  / *绑定自己的端口* /
  my_addr.sin_family = AF_INET;
  my_addr.sin_addr.s_addr = INADDR_ANY; / * INADDR_ANY所有本地地址* /
  my_addr.sin_port = htons(MY_PORT);
  RC =绑定(袜子,(结构sockaddr *)及my_addr,sizeof的(my_addr));
  如果(RC℃,)
  {
     的printf(%S:无法绑定端口(%S)\\ n
        的argv [0],字符串错误(错误));
     出口(EXIT_FAILURE);
  }
  的printf(端口进行绑定\\ n);  / *找出MC * /
  缓冲器[0] =(char)的1;
  缓冲[1] =(char)的0;
  SEND_DATA(缓冲液,2);
  的printf(发送命令:%d个\\ N,(字符)的缓冲区[0]);  RC = receive_data(缓冲液);
  的printf(%d字节收到\\ n,RC);
  缓冲[RC] =(char)的0; / *字符串结束符* /
  的printf(%d个 - %S \\ n,(INT)(char)的缓冲[0],&安培;缓冲[1]);  关闭(袜子);
  的printf(插座关闭\\ n);  出口(0);
}/ *发送数据到MC ******************************************* ********** /
/ *缓冲区指向要发送的字节* /
/ * buf_length是字节数来发送* /
/ *返回0永诺* /
INT SEND_DATA(字符*缓冲区,诠释buf_length)
{
  INT RC;  RC = SENDTO(袜子,缓冲器,buf_length,0,
                 (结构sockaddr *)及MC_addr,
                 的sizeof(MC_addr));
  如果(RC℃,)
  {
    的printf(无法发送数据\\ n);
    关闭(袜子);
    出口(EXIT_FAILURE);
  }
  返回(0);
}/ *从MC接收数据******************************************* ********** /
/ *缓冲区指向所接收的数据*存储器/
/ *最大BUFFERSIZE字节可以接收* /
/ *返回接收的字节数* /
INT receive_data(字符*缓冲区)
{
  INT RC,MC_addr_length;  MC_addr_length = sizeof的(MC_addr);
  RC = recvfrom的(袜子,缓冲器,BUFFERSIZE,0,
                 (结构sockaddr *)及MC_addr,
                 &安培; MC_addr_length);
  如果(RC℃,)
  {
    的printf(无法接收数据\\ n);
    关闭(袜子);
    出口(EXIT_FAILURE);
  }
  回报(RC);
}


解决方案

我已经看到矛盾的答案SO_BINDTODEVICE是如何实际使用后,寻找到这一段时间。 一些消息来源声称,正确的用法是在一个<$ C通$ C>结构的ifreq 指针,它具有通过ioctl获取的设备名称和索引。例如:

 结构的ifreq IFR;
memset的(安培; IFR,0,sizeof的(结构的ifreq));
的snprintf(ifr.ifr_name,sizeof的(ifr.ifr_name),eth0的);
的ioctl(FD,SIOCGIFINDEX,&安培; IFR);
setsockopt的(FD,SOL_SOCKET,SO_BINDTODEVICE,(无效*)及IFR,的sizeof(结构的ifreq));

在哪里为 Beej的网络教程说,设备名称作为传递一个字符指针。例如:

 的char * DEVNAME =eth0的;
setsockopt的(FD,SOL_SOCKET,SO_BINDTODEVICE,DEVNAME,strlen的(DEVNAME));

我已经尝试这两种方法,它们都做需要什么,但我想要注意,在第一种方法中获得的设备索引是多余的。如果你看一下网/核心/袜子。 ç, sock_bindtodevice 只是复制设备名称字符串,调用 dev_get_by_name_rcu 来获取设备,并结合其。

这是第一种方法的工作原理的原因是设备名称是在的ifreq 结构的第一个元素,看的 http://linux.die.net/man/7/netdevice

I have a PC with two network cards. One (eth0) is for LAN/internet and the other for UDP communication with one microcontroller device. The microcontroller has an IP (192.168.7.2) and a MAC address. The second pc network adapter (eth1) has 192.168.7.1.

The microcontroller has a very simple IP stack, so the easiest way for the mc to send UDP packets is to broadcast them.

On the PC side I'd like to receive the broadcasts - but only from eth1. So I try to bind the UDP socket to the eth1 device.

The problems (source code below):

  1. setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device)) requires root privileges, why? (setting other options works as user)

  2. getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length) gives "Protocol not available". I would like to read back the device I set via setsockopt command.

  3. Where can I find good info? I checked some Linux-programming, network books, but for example the SO_BINDTODEVICE option I've only found on the internet.

My lengthy (dirty) test program shows the problems. Setting and getting back the SO_RCVTIMEO and SO_BROADCAST options works as expected.

Running the code as user exits with:

could not set SO_BINDTODEVICE (Operation not permitted)"

Running with sudo gives:

SO_BINDTODEVICE set
./mc-test: could not get SO_BINDTODEVICE (Protocol not available)

So, setting the option seems to work but reading it back is not possible?

/* SO_BINDTODEVICE test */ 

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

#define MC_IP "192.168.7.2"
#define MC_PORT (54321)
#define MY_PORT (54321)
#define MY_DEVICE "eth1"

#define BUFFERSIZE (1000)

/* global variables */
int sock;
struct sockaddr_in MC_addr;
struct sockaddr_in my_addr;
char buffer[BUFFERSIZE];

int main(int argc, char *argv[]) 
{
  unsigned int echolen, clientlen;
  int rc, n;
  char opt_buffer[1000];
  struct protoent *udp_protoent;
  struct timeval receive_timeout;
  int optval;
  socklen_t opt_length;

  /* Create the UDP socket */
  if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) 
  {
    printf ("%s: failed to create UDP socket (%s) \n",
        argv[0], strerror(errno));
    exit (EXIT_FAILURE);
  }
  printf ("UDP socket created\n");

  /* set the recvfrom timeout value */
  receive_timeout.tv_sec = 5;
  receive_timeout.tv_usec = 0;
  rc=setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &receive_timeout,
                sizeof(receive_timeout));
  if (rc != 0) 
  {
     printf ("%s: could not set SO_RCVTIMEO (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("set timeout to\ntime [s]: %d\ntime [ms]: %d\n", receive_timeout.tv_sec, receive_timeout.tv_usec);
  /* verify the recvfrom timeout value */
  rc=getsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &receive_timeout, &opt_length);
  if (rc != 0) 
  {
     printf ("%s: could not get socket options (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("timeout value\ntime [s]: %d\ntime [ms]: %d\n", receive_timeout.tv_sec, receive_timeout.tv_usec);

  /* allow broadcast messages for the socket */
  int true = 1;
  rc=setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &true, sizeof(true));
  if (rc != 0) 
  {
     printf ("%s: could not set SO_BROADCAST (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("set SO_BROADCAST\n");
  /* verify SO_BROADCAST setting */
  rc=getsockopt(sock, SOL_SOCKET, SO_BROADCAST, &optval, &opt_length);
  if (optval != 0) 
  {
    printf("SO_BROADCAST is enabled\n");
  }

  /* bind the socket to one network device */
  const char device[] = MY_DEVICE;
  rc=setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device));
  if (rc != 0) 
  {
     printf ("%s: could not set SO_BINDTODEVICE (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("SO_BINDTODEVICE set\n");
  /* verify SO_BINDTODEVICE setting */
  rc = getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length);
  if (rc != 0) 
  {
     printf ("%s: could not get SO_BINDTODEVICE (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  if (rc == 0) 
  {
    printf("SO_BINDTODEVICE is: %s\n", buffer);
  }


  /* Construct the server sockaddr_in structure */
  memset(&MC_addr, 0, sizeof(MC_addr));     /* Clear struct */
  MC_addr.sin_family = AF_INET;         /* Internet/IP */
  MC_addr.sin_addr.s_addr = inet_addr(MC_IP);   /* IP address */
  MC_addr.sin_port = htons(MC_PORT);        /* server port */

  /* bind my own Port */
  my_addr.sin_family = AF_INET;
  my_addr.sin_addr.s_addr = INADDR_ANY; /* INADDR_ANY all local addresses */
  my_addr.sin_port = htons(MY_PORT);
  rc = bind (sock, (struct sockaddr *) &my_addr, sizeof(my_addr));
  if (rc < 0) 
  {
     printf ("%s: could not bind port (%s)\n",
        argv[0], strerror(errno));
     exit (EXIT_FAILURE);
  }
  printf ("port bound\n");

  /* identify mc */
  buffer[0] = (char)1;
  buffer[1] = (char)0;
  send_data (buffer, 2);  
  printf ("sent command: %d\n", (char)buffer[0]);

  rc=receive_data(buffer);
  printf ("%d bytes received\n", rc);
  buffer[rc] = (char)0; /* string end symbol */
  printf ("%d - %s\n", (int)(char)buffer[0], &buffer[1]);

  close(sock);
  printf ("socket closed\n");

  exit(0);
}

/* send data to the MC *****************************************************/
/* buffer points to the bytes to send */
/* buf_length is the number of bytes to send */
/* returns allways 0 */
int send_data( char *buffer, int buf_length )
{
  int rc;

  rc = sendto (sock, buffer, buf_length, 0,
                 (struct sockaddr *) &MC_addr,
                 sizeof(MC_addr));
  if (rc < 0) 
  {
    printf ("could not send data\n");
    close (sock);
    exit (EXIT_FAILURE);
  }
  return(0);
}

/* receive data from the MC *****************************************************/
/* buffer points to the memory for the received data */
/* max BUFFERSIZE bytes can be received */
/* returns number of bytes received */
int receive_data(char *buffer)
{
  int rc, MC_addr_length;

  MC_addr_length = sizeof(MC_addr);
  rc = recvfrom (sock, buffer, BUFFERSIZE, 0,
                 (struct sockaddr *) &MC_addr,
                 &MC_addr_length);
  if (rc < 0) 
  {
    printf ("could not receive data\n");
    close (sock);
    exit (EXIT_FAILURE);
  }
  return(rc);
}

解决方案

I have been looking into this for a while after seeing conflicting answers to how SO_BINDTODEVICE is actually used. Some sources claim that the correct usage is to pass in a struct ifreq pointer, which has the device name and index obtained via an ioctl. For example:

struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth0");
ioctl(fd, SIOCGIFINDEX, &ifr);
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,  (void*)&ifr, sizeof(struct ifreq));

Where as Beej's networking tutorial says to pass the device name as a char pointer. For example:

char *devname = "eth0";
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, devname, strlen(devname));

I have tried both of these methods and they both do what is required, but I wanted to note that the device index obtained in the first method is superfluous. If you look at the kernel code in net/core/sock.c, sock_bindtodevice just copies the device name string, calls dev_get_by_name_rcu to get the device and binds to it.

The reason that the first approach works is that the device name is the first element in the ifreq structure, see http://linux.die.net/man/7/netdevice.

这篇关于与Linux的SO_BINDTODEVICE套接字选项的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆