15 c sockets linux udp broadcast
我有一台带有两张网卡的电脑.一个(eth0
)用于LAN /互联网,另一个用于与一个微控制器设备进行UDP通信.微控制器具有IP(192.168.7.2)和MAC地址.第二个pc网络适配器(eth1
)有192.168.7.1.
微控制器有一个非常简单的IP堆栈,因此mc发送UDP数据包的最简单方法是广播它们.
在PC方面,我想收到广播 - 但仅限于eth1
.所以我尝试将UDP套接字绑定到eth1
设备.
问题(源代码如下):
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device))
需要root权限,为什么?(设置其他选项作为用户)
getsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, (void *)buffer, &opt_length)
给出"协议不可用".我想通过setsockopt
命令回读我设置的设备.
我在哪里可以找到好消息?我检查了一些Linux编程,网络书籍,但是例如SO_BINDTODEVICE
我只在互联网上找到的选项.
我冗长(肮脏)的测试程序显示了问题.设置和返回SO_RCVTIMEO
和SO_BROADCAST
选项按预期工作.
用户退出时运行代码:
could not set SO_BINDTODEVICE (Operation not permitted)"
Run Code Online (Sandbox Code Playgroud)
使用sudo运行会给出:
SO_BINDTODEVICE set
./mc-test: could not get SO_BINDTODEVICE (Protocol not available)
Run Code Online (Sandbox Code Playgroud)
因此,设置选项似乎有效但读取它是不可能的?
/* 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);
}
Run Code Online (Sandbox Code Playgroud)
aus*_*ton 16
在看到SO_BINDTODEVICE如何实际使用的相互矛盾的答案后,我一直在研究这个问题.一些消息来源声称正确的用法是传入一个struct ifreq
指针,该指针具有通过ioctl获得的设备名称和索引.例如:
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));
Run Code Online (Sandbox Code Playgroud)
凡为Beej的网络教程说,设备名称作为传递一个字符指针.例如:
char *devname = "eth0";
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, devname, strlen(devname));
Run Code Online (Sandbox Code Playgroud)
我已经尝试了这两种方法,但它们都做了所需要的,但我想注意在第一种方法中获得的设备索引是多余的.如果你查看net/core/sock.c中的内核代码,sock_bindtodevice
只需复制设备名称字符串,调用dev_get_by_name_rcu
以获取设备并绑定它.
第一种方法的作用是设备名称是ifreq
结构中的第一个元素,请参阅http://linux.die.net/man/7/netdevice.
好的,我已经多看了一下.SO_BINDTODEVICE在1999年被认为是"接近过时",并且由于一些未指明的"安全隐患"(我无法确切知道是什么)而仅仅是root用户.
但是,您应该能够通过绑定到INADDR_ANY并设置IP_PKTINFO socketopt来获得所需的行为.这将在套接字上传递一条额外消息,该消息包含描述传入数据包的pktinfo结构.此结构包括数据包所在的接口的索引:
struct in_pktinfo {
unsigned int ipi_ifindex; /* Interface index */
struct in_addr ipi_spec_dst; /* Local address */
struct in_addr ipi_addr; /* Header Destination address */
};
Run Code Online (Sandbox Code Playgroud)
ipi_ifindex与netdevice ioctls(如SIOCGIFCONF)返回的struct ifreq中的ifr_ifindex匹配.因此,您应该能够使用它来忽略在您感兴趣的接口之外的接口上接收的数据包.
IP_PKTINFO的Doco位于ip(7)中,而接口ioctls位于netdevice(7)中.
小智 7
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 4);
Run Code Online (Sandbox Code Playgroud)
上面的代码行足以eth0 interface
仅从中接收消息.我在Linux上测试了这个.
注意:如果存在控制实际接口的桥接接口,则无法工作.
此致,Santosh.
在 Linux 3.8 之前,可以设置此套接字选项,但无法使用getsockopt()检索。从 Linux 3.8 开始,它是可读的。该optlen
参数应包含可用于接收设备名称的缓冲区大小,建议为 IFNAMSZ 字节。实际设备名称长度在optlen
参数中报告。
小智 1
问题 2 的答案似乎是 SO_BINDTODEVICE 选项不支持 getsockopt。在 Linux 内核源代码 (2.6.27) 中,该选项仅在 linux-2.6.27.25-0.1/net/core/sock.c 的 sock_setsockopt 函数中处理
对于问题3,似乎很多人推荐W. Richard Stevens的《UNIX网络编程》一书。我浏览了谷歌图书在线版本的套接字选项页面 - 表 7.1 和 7.2 中未列出 SO_BINDTODEVICE 选项:-( ...也许是因为此选项仅适用于 Linux?