I have a mdns service discovery that uses the following code for initialization
void mdnssd_init(struct in_addr host, bool compliant) {
int sock;
int res;
struct ip_mreq mreq;
struct sockaddr_in addr;
sock = socket(AF_INET, SOCK_DGRAM, 0);
char param = 32;
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*) ¶m, sizeof(param));
int enable = 1
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &enable, sizeof(enable));
param = 1;
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, (void*) ¶m, sizeof(param));
#ifndef _WIN32
if (compliant) {
enable = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (void*)&enable, sizeof(enable));
}
#endif
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
/*
* Sending *from * 5353 indicates that we do compliant mDNS query. If we chose
* random ports, the ttl will be much shorter.
*/
if (compliant) addr.sin_port = htons(MDNS_PORT);
// Windows must bind this socket to a specific address, others must not (it's *must*)
#ifdef _WIN32
addr.sin_addr.s_addr = host.s_addr;
#else
addr.sin_addr.s_addr = INADDR_ANY;
#endif
socklen_t addrlen = sizeof(addr);
res = bind(sock, (struct sockaddr *) &addr, addrlen);
if (res < 0) return;
// set outgoing interface for multicast (it's optional, INADDR_ANY could be used)
setsockopt (sock, IPPROTO_IP, IP_MULTICAST_IF, (void*) &host.s_addr, sizeof(host.s_addr));
// set multicast groups we are interested by to receive such packets
memset(&mreq, 0, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(MDNS_MULTICAST_ADDRESS);
mreq.imr_interface.s_addr = host.s_addr; // optional, INADDR_ANY can be used
setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*) &mreq, sizeof(mreq));
}
It works on all platforms (Linux, Windows, Solaris, FreeBSD, MacOS) but I really don't understand why I need that binding difference between Linux and Windows.
On Linux, if I bind the socket to the address I want to use to send/receive, multicast traffic is not received. Note that queries that require unicast response are properly answered. I understand that some settings here are optional and INADDR_ANY can be used, letting the OS select what interface to send request and receive response (and it works).
On the contrary, on Windows, if the socket is not bound to a specific address, but set to INADDR_ANY, then no multicast traffic is received. Same, queries requiring unicast responses are received.
So it's very puzzling to me that not both options work. I should be able to bind the socket to the address that will be used for sending/receiving - or not. It should work either way, no?
Related
I have a client/server LWIP program that works correctly with unicast communication however I want to use multicast features so I used IGMP library did the following:
1- in lwipopts.h:
#define LWIP_IGMP 1 //allowed IGMP
2- in ethernetif.c:
netif->flags |= NETIF_FLAG_IGMP; //in low_level_init function
3-in my source file (for both client and server projects):
implemented the following code:
void recCallBack (void)
{
printf("connected"); //BREAK_POINT
}
static void UDP_Multicast_init(void *arg)
{
struct ip4_addr ipgroup, localIP;
struct udp_pcb *g_udppcb;
char msg[] = "hello";
struct pbuf* p;
p = pbuf_alloc(PBUF_TRANSPORT,sizeof(msg),PBUF_RAM);
memcpy (p->payload, msg, sizeof(msg));
IP4_ADDR(&ipgroup, 224, 0, 1, 129 ); //Multicast IP address.
IP4_ADDR(&localIP, 192, 168, 1, 2); //Interface IP address
#if LWIP_IGMP
s8_t iret = igmp_joingroup((ip4_addr_t *)(&localIP),(ip4_addr_t *)(&ipgroup));
#endif
g_udppcb =( struct udp_pcb*) udp_new();
udp_bind(g_udppcb, &localIP, 319); //to allow receiving multicast
udp_recv(g_udppcb, recCallBack,NULL); //recCallBack is the callback function that will be called every time you receive multicast
udp_sendto(g_udppcb,p,&ipgroup,319); //send a multicast packet
}
void telnet_shell_init(void)
{
sys_thread_new("TELNET", UDP_Multicast_init, NULL, DEFAULT_THREAD_STACKSIZE, osPriorityAboveNormal);
}
The result: all the mentioned code steps are executed successfully in both projects (client and server) but I'm not receiving any multicast messages (or maybe not even sending)!
I added a "BREAK_POINT" in the callback function but I never reached it. Can you help me? either by suggesting a solution or at least a way to track the problem... I'm using STM32F746 Nucleo board with LWIP, FreeRTOS libraries generated by cubeMX.
Thank you.
<<< Edit >>>
After more investigations I found out that the problem is in the reception of the multi-cast frames which should be enabled during the MAC initialization. Although the following code did not work for me, it was helpful to others so here it is:
4- in the stm32f7xx_hal_eth.c (ETH_MACDMAConfig function):
macinit.PromiscuousMode = ETH_PROMISCUOUS_MODE_ENABLE;
macinit.MulticastFramesFilter = ETH_MULTICASTFRAMESFILTER_NONE;
My multicast testing was finished successfully with STM32F407 and CubeMX version 4.25.0.
The Kalkhouri's question was helpful.
I share my working code here.
Following code must be included same as Kalkhouri did.
#define LWIP_IGMP 1
macinit.MulticastFramesFilter = ETH_MULTICASTFRAMESFILTER_NONE;
netif->flags |= NETIF_FLAG_IGMP;
I used socket API of LWIP rather than low level function.
#include "lwip/opt.h"
#include "lwip/dhcp.h"
#include "lwip/netif.h"
#include "lwip/tcpip.h"
#include "lwip/sockets.h"
int Bind(int sock, uint16_t port)
{
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(port);
if (bind(sock, (struct sockaddr *) &serv_addr, (socklen_t)sizeof(serv_addr)) < 0)
return -1;
return 0;
}
int JoinGroup(int sock, const char* join_ip, const char* local_ip)
{
ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(join_ip);
mreq.imr_interface.s_addr = inet_addr(local_ip);
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) < 0)
return -1;
return 0;
}
void MulticastStart()
{
int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
Bind(sock, 5000);
JoinGroup(sock, "224.1.1.1", "192.168.10.123");
// Now you can do recvfrom() in RTOS task.
........
}
Note: You should use this code under the RTOS support.
Is it possible to filter packets using BPF on datagram socket?
No error occures when I try to attach a filter, but I don't receive any packet.
I compiled a filter using libpcap, and the filter works with tcpdump.
Here is shortened version of my code:
static const char filter[] = "udp[8] == 0x00";
int sock = socket(AF_INET, SOCK_DGRAM, 0);
pcap_t *pcap = pcap_open_dead(DLT_RAW, 1024);
struct bpf_program bpf_prog;
pcap_compile(pcap, &bpf_prog, filter, 0, PCAP_NETMASK_UNKNOWN);
struct sock_fprog linux_bpf = {
.len = bpf_prog.bf_len,
.filter = (struct sock_filter *) bpf_prog.bf_insns,
};
setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &linux_bpf, sizeof(linux_bpf));
My machine is ubuntu 12.04 x86.
well, after some tests and trials, it is possible.
however, libpcap does not support it directly.
what should be done is open a pcap handler specifying ethernet data type, and then access the bytes in the udp packet as if you access the ethernet packet.
the filter offsets start from the beginning of the packet, but the 'packet' depends on the layer you opened the socket for.
if one opens socket with SOCK_DGRAM, the bpf instruction ldb 0 will load the first byte of the udp header. so when accessing ether[0] in the filter libpcap will compile it to ldb 0 which is what we want.
so, the corrected code should be something like this:
static const char filter[] = "ether[8] == 0x00";
int sock = socket(AF_INET, SOCK_DGRAM, 0);
pcap_t *pcap = pcap_open_dead(DLT_EN10MB, 1024);
struct bpf_program bpf_prog;
pcap_compile(pcap, &bpf_prog, filter, 0, PCAP_NETMASK_UNKNOWN);
struct sock_fprog linux_bpf = {
.len = bpf_prog.bf_len,
.filter = (struct sock_filter *) bpf_prog.bf_insns,
};
setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &linux_bpf, sizeof(linux_bpf));
How can I set (and replace the existing) default network route from a C program? I'd like to do it without shell commands if possible (this is a low memory embedded system). Also can you set the default route without specifying the gateway IP address? In my application I want to make either ppp0 or eth0 the default route, depending on whether the cable is plugged into eth0 or not.
Thanks,
Fred
You can make IOCTL calls to set the default route from a C program.
void main()
{
int sockfd;
struct rtentry rt;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1)
{
perror("socket creation failed\n");
return;
}
struct sockaddr_in *sockinfo = (struct sockaddr_in *)&rt.rt_gateway;
sockinfo->sin_family = AF_INET;
sockinfo->sin_addr.s_addr = inet_addr("Your Address");
sockinfo = (struct sockaddr_in *)&rt.rt_dst;
sockinfo->sin_family = AF_INET;
sockinfo->sin_addr.s_addr = INADDR_ANY;
sockinfo = (struct sockaddr_in *)&rt.rt_genmask;
sockinfo->sin_family = AF_INET;
sockinfo->sin_addr.s_addr = INADDR_ANY;
rt.rt_flags = RTF_UP | RTF_GATEWAY;
rt.rt_dev = "eth0";
if(ioctl(sockfd, SIOCADDRT, &rt) < 0 )
perror("ioctl");
return;
}
You could strace the route command you are wanting to mimic. This gives you the relevant syscalls useful to change routing.
You may be interested by the proc(5) interface, e.g. its /proc/net/route pseudo-file.
See also ip(7).
The constant INADDR_ANY is the so-called IPv4 wildcard address. The
wildcard IP address is useful for applications that bind Internet
domain sockets on multihomed hosts. If an application on a multihomed
host binds a socket to just one of its host’s IP addresses, then that
socket can receive only UDP datagrams or TCP connection requests sent
to that IP address. However, we normally want an application on a
multihomed host to be able to receive datagrams or connection requests
that specify any of the host’s IP addresses, and binding the socket to
the wildcard IP address makes this possible.
struct sockaddr_in server_address;
int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&server_address, 0, sizeof(struct sockaddr_in));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY); // here is my quesion
server_address.sin_port = htons(9734);
bind(server_sockfd, (struct sockaddr*)&server_address, sizeof(server_address));
Question>
If we bind the socket to a specific IP address, then the socket can only receive UPD/TCP requests sent sent to that IP address.
As I show in the above code, now the socket server_sockfd is bound with INADDR_ANY.
I just feel confused here b/c if the socket can receive any request on the internet, how it can still work well. There are tons of requests of UDP/TCP on internet, if the socket responses to everybody,
, how can it still work?
// updated code for client side //
int
main(int argc, char *argv[])
{
struct sockaddr_in6 svaddr;
int sfd, j;
size_t msgLen;
ssize_t numBytes;
char resp[BUF_SIZE];
if (argc < 3 || strcmp(argv[1], "--help") == 0)
usageErr("%s host-address msg...\n", argv[0]);
/* Create a datagram socket; send to an address in the IPv6 somain */
sfd = socket(AF_INET6, SOCK_DGRAM, 0); /* Create client socket */
if (sfd == -1)
errExit("socket");
memset(&svaddr, 0, sizeof(struct sockaddr_in6));
svaddr.sin6_family = AF_INET6;
svaddr.sin6_port = htons(PORT_NUM);
if (inet_pton(AF_INET6, argv[1], &svaddr.sin6_addr) <= 0)
fatal("inet_pton failed for address '%s'", argv[1]);
/* Send messages to server; echo responses on stdout */
for (j = 2; j < argc; j++) {
msgLen = strlen(argv[j]);
if (sendto(sfd, argv[j], msgLen, 0, (struct sockaddr *) &svaddr,
sizeof(struct sockaddr_in6)) != msgLen)
fatal("sendto");
numBytes = recvfrom(sfd, resp, BUF_SIZE, 0, NULL, NULL);
if (numBytes == -1)
errExit("recvfrom");
printf("Response %d: %.*s\n", j - 1, (int) numBytes, resp);
}
exit(EXIT_SUCCESS);
}
// updated for server side code
int
main(int argc, char *argv[])
{
struct sockaddr_in6 svaddr, claddr;
int sfd, j;
ssize_t numBytes;
socklen_t len;
char buf[BUF_SIZE];
char claddrStr[INET6_ADDRSTRLEN];
/* Create a datagram socket bound to an address in the IPv6 somain */
sfd = socket(AF_INET6, SOCK_DGRAM, 0);
if (sfd == -1)
errExit("socket");
memset(&svaddr, 0, sizeof(struct sockaddr_in6));
svaddr.sin6_family = AF_INET6;
svaddr.sin6_addr = in6addr_any; /* Wildcard address */
svaddr.sin6_port = htons(PORT_NUM);
if (bind(sfd, (struct sockaddr *) &svaddr,
sizeof(struct sockaddr_in6)) == -1)
errExit("bind");
/* Receive messages, convert to uppercase, and return to client */
for (;;) {
len = sizeof(struct sockaddr_in6);
numBytes = recvfrom(sfd, buf, BUF_SIZE, 0,
(struct sockaddr *) &claddr, &len);
if (numBytes == -1)
errExit("recvfrom");
/* Display address of client that sent the message */
if (inet_ntop(AF_INET6, &claddr.sin6_addr, claddrStr,
INET6_ADDRSTRLEN) == NULL)
printf("Couldn't convert client address to string\n");
else
printf("Server received %ld bytes from (%s, %u)\n",
(long) numBytes, claddrStr, ntohs(claddr.sin6_port));
for (j = 0; j < numBytes; j++)
buf[j] = toupper((unsigned char) buf[j]);
if (sendto(sfd, buf, numBytes, 0, (struct sockaddr *) &claddr, len) !=
numBytes)
fatal("sendto");
}
}
// updated for how to run this server/client programs.
$ ./server_program &
[1] 31047
$ ./client_program ::1 ciao // Send to server on local host
Server received 4 bytes from (::1, 32770)
Response 1: CIAO
It doesn't get requests for every IP address on the internet(a), it gets requests for every IP address that it services. For example, it may have multiple NICs, each with a separate IP address or it may have a single NIC capable of managing multiple IP addresses (it may even have multiple NICs, each capable of handling multiple IP addresses.
The key snippet to look at is:
... we normally want an application on a multi-homed host to be able to receive datagrams or connection requests that specify any of the host’s IP addresses (my italics).
In other words, you may have a multi-homed set-up where your machine services 10.0.0.15 and 10.0.0.16. Using INADDR_ANY will allow you to pick up traffic for both those addresses, without picking up requests for 10.0.0.17 which may be the machine on the other end of the bench (or other side of the planet).
The following table, with the top row being request destinations and the left column being the address you're listening on, shows whether you'll be given a request (Y) or not (N):
Request to> 10.0.0.15 10.0.0.16 10.0.0.17
Bind to: *-------------------------------
10.0.0.15 | Y N N
10.0.0.16 | N Y N
INADDR_ANY | Y Y N
(a) It doesn't even see the vast majority of requests on the net. The vast majority don't even make it to your nearest router (or probably even your ISP). Even those that do make it to your nearest router, your particular machine might not see if they're destined for another machine on the local segment (promiscuous mode notwithstanding).
I'm trying to receive a multicast data from specific network interface on CentOS 5.5
sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(1234);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sd, (sockaddr*)&addr, sizeof(sockaddr_in));
setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 5);
But I'm receiving packets from all interfaces.
What wrong?
First, check if any of your calls fail, socket,bind,setsockopt in this case. Printing an error message with the perror() function will help you diagnose problems.
However, for receiving multicast datagrams you might need to specify the ip address of the interface when you join a multicast group using the IP_ADD_MEMBERSHIP socket option
Something like
setsockopt (sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
where the mreq struct is
struct ip_mreq
{
struct in_addr imr_multiaddr; /* IP multicast address of group */
struct in_addr imr_interface; /* local IP address of interface */
};
More info here.