/* * Author: Shao A Wu. swu001@gmail.com * Release under GPL license. * * This simple program demonstrates a bug in Linux kernel 2.6.x releases (and * possibly 2.4 kernels also). I have tested this code on several 2.6 kernels * (RHEL 4.4, 4.5, 5.0 releases) all sufferring the same bug. On Sun Solaris 10, * however, it always behave correctly. * * This bug also affects select/poll's behavior as demonstrated in CASE2. * * How to reproduce the bug... * * Please make sure the multicast group address and the port number are not * currently in use on your test system. I use group address 239.255.1.1 and * port number 12345 here. Here are commands you can use to verify: * netstat -gn * netstat -an | grep 12345 * If you don't see port 12345 and group address 239.255.1.1 listed, that is * good. * * Test scenarios: * 1. Run one copy of this program, if you see it's running and receiving its * own messages, control-C out of it (because that is how it should behave). * And try again. If you try that a few times, you will see that this program * will hang on recv() function! That is an incorrect behavior. This program * should always receive its own messages. * * 2. Follow scenario #1 until you get it hanging, open another window, and run * a second copy of this program. You will see both copies start running and * can receive each other's messages. That's a correct behavior -- the first * datagram from the second copy takes the first copy of the process out of * hanging state. * * To Compile on Linux: * g++ -g -o test simplemcast.cc * To compile on Sun Solaris 10: * CC -g -o test simplemcast.cc -lsocket -lnsl * * To run the test program: * ./test -port 12345 -grp 239.255.1.1 */ #include #include #include #include #include #ifdef CASE2 #include #endif /* CASE2 */ #include #include using namespace std; int msock(const struct sockaddr *sa_) { const int one = 1; const int sock = socket(AF_INET, SOCK_DGRAM, 0); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); bind(sock, sa_, sizeof(*sa_)); u_char val = (u_char)20; const socklen_t len = sizeof(val); struct group_req req; if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &val, len) < 0) throw runtime_error(string("setsockopt TTL failed: ") + strerror(errno)); val = 1; if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &val, len) < 0) throw runtime_error(string("setsockopt LOOP failed: ") + strerror(errno)); memset(&req, 0, sizeof(req)); req.gr_interface = 0; memcpy(&req.gr_group, sa_, sizeof(*sa_)); if (setsockopt(sock, IPPROTO_IP, MCAST_JOIN_GROUP, &req, sizeof(req)) < 0) throw runtime_error(string("setsockopt JOIN_GROUP failed: ") + strerror(errno)); return sock; } void lookup(struct sockaddr &sa_, const char *grp_, const char *port_) { struct addrinfo *res; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; const int rc = getaddrinfo(grp_, port_, &hints, &res); if (rc != 0) { throw runtime_error(string("ERROR: Can't resolve address: ") + grp_ + " " + port_); } if (res->ai_addrlen > sizeof(sa_)) throw runtime_error("sockaddr too large!"); memcpy(&sa_, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); } void runtest(const char *grp_, const char *port_) { const int buflen = 1024; char rcvbuf[buflen], sndbuf[buflen]; struct sockaddr sa; const pid_t pid = getpid(); sprintf(sndbuf, "this is a test from %d.", pid); lookup(sa, grp_, port_); const int datalen = strlen(sndbuf) + 1; const int sock = msock(&sa); #ifdef CASE2 struct pollfd ps; ps.fd = sock; ps.events = POLLRDNORM; #endif /* CASE2 */ for (int i=0; i < 20; ++i) { const int x = sendto(sock, sndbuf, datalen, 0, &sa, sizeof(sa)); if (x < 0) throw runtime_error(string("sendto failed: ") + strerror(errno)); /* * If multiple copies of this program are running, we'll not * be able to receive all test messages.... but that's not our * test scenario. */ #ifdef CASE2 // use infinite timeout - want to test the hanging bug, remember? if (poll(&ps, 1, -1) == 1) { if (recv(sock, rcvbuf, buflen, 0) > 0) { cout << pid << " RCV: " << rcvbuf << endl; } } #else /* CASE2 */ if (recv(sock, rcvbuf, buflen, 0) > 0) { cout << pid << " RCV: " << rcvbuf << endl; } #endif /* CASE2 */ sleep(1); } } void usage(const char *pname_) { cerr << "usage: " << pname_ << " -port -grp " << endl; exit(-1); } int main(int argc, const char *argv[]) { if (argc < 5) usage(argv[0]); const char *port = 0; const char *grp = 0; for (int i=1; i < argc; i++) { if (strcmp("-port", argv[i]) == 0) { port = argv[++i]; } else if (strcmp("-grp", argv[i]) == 0) { grp = argv[++i]; } } if (!port || !grp) usage(argv[0]); try { runtest(grp, port); } catch (const runtime_error &e_) { cerr << "ERROR: " << e_.what() << endl; } return 0; }