While developing a small DHCP client, I managed to send broadcasts on a specific interface by binding to IFADDR_ANY and setting SO_BROADCAST and SO_BINDTODEVICE, and I can see replies from the DHCP server.
Unfortunately, unless that interface has an address configured, my application doesn't receive any packets. That makes writing a DHCP client rather pointless.
QUESTION: How can I receive IPv4 UDP broadcasts on an interface that has not assigned an address?
I'm aware of RAW sockets, but I'd like to avoid using them.
If sending was impossible, I would just accept that no UDP is possible unless addresses are configured, but as sending works fine and just receiving does not, I'm wondering if I'm missing something.
Here's minimal code:
#define _DEFAULT_SOURCE
#include <sys/types.h>
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
if(argc < 2) {
return 0;
}
struct sockaddr_in src = { .sin_family=AF_INET, .sin_addr.s_addr=INADDR_ANY, .sin_port=htons(68) };
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
const int on=1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
struct ifreq ifr;
memset(&ifr, '\0', sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), argv[1]);
ioctl(fd, SIOCGIFINDEX, &ifr);
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr));
bind(fd, (struct sockaddr *)&src, sizeof(src));
char buf[512];
ssize_t res = recvfrom(fd, buf, sizeof(buf), 0, NULL, 0);
printf("res=%zi\n", res);
return 0;
}
I run this through "sudo strace", and it stops in recvmsg() forever, even if I can watch broadcast UDP on port 68 in wireshark.
I solved it. "reverse path filtering" was activated, causing all IPv4 packets which would take a different return path (or which have no route) to be dropped.
If no address is configured for that interface, every packet gets dropped.
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
did the trick.
I've been a Unix programmer for a decent amount of time, and I know pretty well the system's socket API, I use it mostly for networking.
The thing is that I'm currently trying to create a cross-platform software, and so I began to learn how to compile my source code into windows executable files.
I've created a startup() function which does the simplest thing: connect to a server (of which IP is given), and return -1 on failure. On Linux my code runs smooth, but on Windows(7) I get the same error on some machines - "Connect()" function failed, errno: "Result too large" which (by what I understood) means it could no find any listening server on that IP, but on others it runs well.
Here is the code (win version):
#define _WIN32_WINNT 0x0501
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <windows.h>
#include <winsock.h>
#define ADDR "127.127.127.127"
#define PORT 8752
int startup(struct sockaddr_in sin)
{
int sockfd, soaddr;
WSADATA wsaData;
if (WSAStartup(MAKEWORD(1,1), &wsaData) != 0) {
fprintf(stderr, "WSAStartup failed.\n");
exit(1);
}
if((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
fprintf(stderr, "socket:%s\n", strerror(errno));
return(-1);
}
soaddr = sizeof(sin);
if(connect(sockfd, (struct sockaddr*)&sin, soaddr) == -1)
{
fprintf(stderr, "connect:%s\n", strerror(errno));
return(-1);
}
return sockfd;
}
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(ADDR);
sockfd = startup(sin);
// Code continues
return(0);
}
note:ADDR has been changed for privacy reasons, but there is a working server on that machine
So, why exactly am I getting this error ?
WinSock does not use errno, it uses WSAGetLastError() instead.
WinSock does not use file descriptors for sockets, it uses actual kernel objects. On Windows, you need to use the SOCKET handle type, not int, for your socket variables (or at least type-cast SOCKET values to (u)intptr_t and then type-cast back to SOCKET when calling WinSock functions).
You can use the Win32 API FormatMessage() function to get a human readable string for a WinSock error code.
#define _WIN32_WINNT 0x0501
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <windows.h>
#include <winsock.h>
#define ADDR "127.127.127.127"
#define PORT 8752
__declspec(thread) char errmsg[256];
char* sockerr(int errcode)
{
DWORD len = FormatMessageA(FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errcode, 0, errmsg, 255, NULL);
if (len != 0)
errmsg[len] = 0;
else
sprintf(errmsg, "error %d", errcode);
return errmsg;
}
int startup()
{
WSADATA wsaData;
int ret = WSAStartup(MAKEWORD(1,1), &wsaData);
if (ret != 0) {
fprintf(stderr, "WSAStartup:%s\n", sockerr(ret));
return(ret);
}
return(0);
}
void cleanup()
{
WSACleanup();
}
SOCKET connectToServer(struct sockaddr_in sin)
{
SOCKET sockfd;
int soaddr;
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd == INVALID_SOCKET)
{
fprintf(stderr, "socket:%s\n", sockerr(WSAGetLastError()));
return(INVALID_SOCKET);
}
soaddr = sizeof(sin);
if (connect(sockfd, (struct sockaddr*)&sin, soaddr) == -1)
{
fprintf(stderr, "connect:%s\n", sockerr(WSAGetLastError()));
closesocket(sockfd);
return(INVALID_SOCKET);
}
return(sockfd);
}
int main(int argc, char *argv[])
{
SOCKET sockfd;
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(ADDR);
if (startup() != 0) {
exit(1);
}
sockfd = connectToServer(sin);
if (sockfd != INVALID_SOCKET)
{
// ...
closesocket(sockfd);
}
cleanup();
return(0);
}
You will have to take these differences into consideration when writing cross-platform socket code.
According to your code you are running client socket in windows machine.You need to load runtime libraries for windows as
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
You can refer to Windows Client Socket Source. Hope this works for you.
I know that this is obviously elementary question and I know that there are many tutorials and ready-to-go examples but I must missing something. I am trying to send for example text (char *) via UDP socket to other machine in local network. So far I tried some tutorials like http://gafferongames.com/networking-for-game-programmers/sending-and-receiving-packets/ and so on but I always get error in bind() function with errno "Cannot assign requested address".
I just have some data in char array and I want to push them via network to another host. Could someone please point me to the right direction? Do I need socket server or client? Do I need to bind the socket to some interface?
This is my playground:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <errno.h>
int handle;
int init_socket()
{
handle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (handle <= 0)
{
printf("failed to create socket\n");
return 1;
}
printf("sockets successfully initialized\n");
return 0;
}
int main ()
{
unsigned short port = 30000;
char * data = "hovno";
init_socket();
struct sockaddr_in address;
memset((char *) &address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("192.168.11.129"); // this is address of host which I want to send the socket
address.sin_port = htons(port);
printf("handle: %d\n", handle); // prints number greater than 0 so I assume handle is initialized properly
if (bind(handle, (const struct sockaddr*) &address, sizeof(struct sockaddr_in)) < 0)
{
printf("failed to bind socket (%s)\n", strerror(errno)); // Cannot assign requested address
return 1;
}
int nonBlocking = 1;
if (fcntl(handle, F_SETFL, O_NONBLOCK, nonBlocking) == -1)
{
printf("failed to set non-blocking\n");
return 2;
}
int sent_bytes = sendto(handle, data, strlen(data), 0, (const struct sockaddr*) &address, sizeof(struct sockaddr_in));
if (sent_bytes != strlen(data))
{
printf("failed to send packet\n");
return 3;
}
return 0;
}
bind is called for the local address (one you intend to recv packets to). The IP address must be a local IP address of the machine, or (most frequently) INADDR_ANY.
Normally you don't have to use bind on the client side at all. The system will pick a suitable free port for you automatically.
To specify the remote address for a UDP socket, use sendto, not send.
If you search Google for udp client c code, one of the first results is this one. You can see that the networking part is basically just two calls, socket and sendto.
i try to get the mac address of an interface i want to work with.
I use this code to do so, but i get always the error message "Inappropriate ioctl for device
"
I already tried using a different socket, ie AF_INET with SOCK_DGRAM (though i need the raw socket for later usage) without any difference.
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if_ether.h>
#include <net/if.h>
int main()
{
char if_name[] = "eth0";
char mac[ETH_ALEN];
struct ifreq ifr;
int sock;
if(sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)) < 0)
{
perror("SOCKET");
return 1;
}
// get mac address of our interface
memset(&ifr, 0, sizeof(struct ifreq));
memcpy(ifr.ifr_name, if_name, 4);
if(ioctl(sock, SIOCGIFHWADDR, &ifr) == -1)
{
perror("SIOCGIFHWADDR");
return 1;
}
memcpy(mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
printf("%x.%x.%x.%x.%x.%x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
The problem should be pretty obvious if you turned on more warnings:
if(sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)) < 0)
The above assigns the result of the comparison to sock, and of course it's not the valid socket.
Instead you need to use parentheses to avoid the operator precedence problem:
if((sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP))) < 0)
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):
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device)) requires root privileges, why? (setting other options works as user)
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.
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.
NOTE: SO_BINDTODEVICE requires elevated permissions:
run the executable with full root permission
after building the executable you can use sudo setcap to grant the executable permission to use this specific socket option then you can run the executable without root permission and the executable has permission to use the SO_BINDTODEVICE feature (via earlier call to setcap).
setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 4);
Above line of code is enough to receive messages from eth0 interface only.
I tested this on Linux.
NOTE: It won't work if there is a bridge interface controlling actual interfaces.
Best regards,
Santosh.
OK, I've looked into it a little more. SO_BINDTODEVICE was considered "near obsolete" back in 1999, and is root-only due to some unspecified "security implications" (I couldn't find out exactly what).
However, you should be able to get the behaviour you want by binding to INADDR_ANY and setting the IP_PKTINFO socketopt. This will pass an extra message on the socket that contains a pktinfo structure describing the incoming packet. This structure includes the index of the interface that the packet came in on:
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 */
};
The ipi_ifindex matches with the ifr_ifindex from the struct ifreq returned by the netdevice ioctls like SIOCGIFCONF. So you should be able to use that to ignore packets received on interfaces other than the one you're interested in.
Doco for IP_PKTINFO is in ip(7) and for the interface ioctls in netdevice(7).
Before Linux 3.8, this socket option could be set, but could not retrieved with getsockopt(). Since Linux 3.8, it is readable. The optlen argument should contain the buffer size available to receive the device name and is recommended to be IFNAMSZ bytes. The real device name length is reported back in the optlen argument.
The problem I ran into seems to be that receiving broadcasts from a specific interface is handled differently by Linux, Windows,...
http://www.developerweb.net/forum/showthread.php?t=5722
I now decided to solve the problem (little documentation and bad portability) by changing the TCP/IP stack of the microcontroller. It will no longer send answers to the broadcast address but instead take the IP/MAC from the incoming UDP packet as the destination IP/MAC. Then I can (on the pc side) simply bind the socket to the IP of eth1.
Cheers,
Michael
Just lookup the IP address of the interface you're interested in with getifaddrs(), and bind your socket to that IP address with bind(). If you enable SO_BROADCAST on the socket you'll then only get broadcasts recieved on that interface.
Or indeed you could skip the getifaddrs() part and just directly bind() to 192.168.7.1 if you like.
I can confirm that sending multicast to specific interface works also like this. See the sample codes below.
However I can't get listener.c program working if the interface is set by SO_BINDTODEVICE to my secondary interface eth4.
I used completely different machine to send the multicast packets and the listener works from interface eth3, not from interface eth4. However, tcpdump shows the packets in both interfaces (sudo tcpdump -i eth4 |grep UDP).
These are modifications to Antony Courtney's sample code:
sender.c and listener.c:
/*
* sender.c -- multicasts "hello, world!" to a multicast group once a second
*
* Antony Courtney, 25/11/94
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <net/if.h>
#define HELLO_PORT 12345
#define HELLO_GROUP "225.0.0.37"
main(int argc, char *argv[])
{
struct sockaddr_in addr;
int fd, cnt;
struct ip_mreq mreq;
char *message="Hello, World!";
char com[1000];
/* create what looks like an ordinary UDP socket */
if ((fd=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
perror("socket");
exit(1);
}
/* set up destination address */
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=inet_addr(HELLO_GROUP);
addr.sin_port=htons(HELLO_PORT);
u_char ttl=7;
setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth4");
ioctl(fd, SIOCGIFINDEX, &ifr);
printf("[[%d]]\n", ifr.ifr_ifindex );
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(struct ifreq));
inet_ntop(AF_INET, &(addr), com, INET_ADDRSTRLEN);
printf("addr=%s\n", com );
/* now just sendto() our destination! */
while (1) {
if (sendto(fd,message,strlen(message),0,(struct sockaddr *) &addr,
sizeof(addr)) < 0) {
perror("sendto");
exit(1);
}
sleep(1);
}
}
listener.c :
/*
* listener.c -- joins a multicast group and echoes all data it receives from
* the group to its stdout...
*
* Antony Courtney, 25/11/94
* Modified by: Frédéric Bastien (25/03/04)
* to compile without warning and work correctly
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <net/if.h>
#define HELLO_PORT 12345
#define HELLO_GROUP "225.0.0.37"
#define MSGBUFSIZE 256
main(int argc, char *argv[])
{
struct sockaddr_in addr;
int fd, nbytes,addrlen;
struct ip_mreq mreq;
char msgbuf[MSGBUFSIZE];
u_int yes=1; /*** MODIFICATION TO ORIGINAL */
/* create what looks like an ordinary UDP socket */
if ((fd=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
perror("socket");
exit(1);
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(struct ifreq));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth4");
ioctl(fd, SIOCGIFINDEX, &ifr);
printf("[[%d]]\n", ifr.ifr_ifindex );
if( setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(struct ifreq)) < 0 )
{
perror("SO_BINDTODEVICE");
exit(1);
}
/**** MODIFICATION TO ORIGINAL */
/* allow multiple sockets to use the same PORT number */
if (setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)) < 0) {
perror("Reusing ADDR failed");
exit(1);
}
/*** END OF MODIFICATION TO ORIGINAL */
/* set up destination address */
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=htonl(INADDR_ANY); /* N.B.: differs from sender */
addr.sin_port=htons(HELLO_PORT);
/* bind to receive address */
if (bind(fd,(struct sockaddr *) &addr,sizeof(addr)) < 0) {
perror("bind");
exit(1);
}
/*
ifr.ifr_flags = IFF_UP | IFF_ALLMULTI | IFF_MULTICAST;
ioctl(fd, SIOCSIFFLAGS, &ifr );
*/
/* use setsockopt() to request that the kernel join a multicast group */
mreq.imr_multiaddr.s_addr=inet_addr(HELLO_GROUP);
mreq.imr_interface.s_addr=htonl(INADDR_ANY);
if (setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0) {
perror("setsockopt");
exit(1);
}
/* now just enter a read-print loop */
while (1) {
addrlen=sizeof(addr);
if ((nbytes=recvfrom(fd,msgbuf,MSGBUFSIZE,0,
(struct sockaddr *) &addr,&addrlen)) < 0) {
perror("recvfrom");
exit(1);
}
msgbuf[nbytes]='\0';
puts(msgbuf);
}
}
If you are unable to receive multicast packets on the secondary interface, it could well be reverse path filtering that is blocking them. This filters out received packets if those packets would not go out on the interface they are coming in on.
To disable this feature, use the following:
sudo -i
echo 2 > /proc/sys/net/ipv4/conf/eth1/rp_filter
echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
exit
The answer to question 2 seems to be that getsockopt is just not supported for the SO_BINDTODEVICE option. In the Linux kernel source (2.6.27) the option is only handled in the sock_setsockopt function of linux-2.6.27.25-0.1/net/core/sock.c
For question 3 it seems, lots of people recommend the "UNIX network programming" book by W. Richard Stevens.
I looked through the socket options pages of the google book online version - the SO_BINDTODEVICE option is not listed in table 7.1 and 7.2 :-(
...maybe because this option is Linux only?
setsocketopt needs device index, not name. Furthermore you should use struct ifreq to pass the index:
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth3");
ioctl(s, SIOCGIFINDEX, &ifr)
setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(ifr));
I've solved a similar problem by adding the following to /etc/sudoers (or in a file in /etc/sudoers.d):
myuser myhost=(root) NOPASSWD: /usr/bin/fping
Then instead of using fping directory, use sudo fping.