if you open an ICMP raw socket and then bind to an address (like you do with the -S option to BSD ping or -I to linux ping) it appears that you do not get ICMP unreachables if any are sent to you.
compare the call to __raw_v4_lookup in icmp.c icmp_unreach() to that in raw.c raw_v4_input() to see what i mean.
i've included test code (unreach_send.c, unreach_listen.c) so that you can test it yourself, and a patch that fixes the kernel. the send needs to be run on a seperate machine to trigger the address comparison error.
patch is against linux-2.4.19
--
Matthew Luckie
kluckie@ihug.co.nz
--- icmp.c.orig Tue Nov 19 13:10:30 2002 +++ icmp.c Tue Nov 19 13:24:53 2002 @@ -16,6 +16,8 @@ * Other than that this module is a complete rewrite. * * Fixes: + * Matthew Luckie : fix call to __raw_v4_lookup in + * icmp_unreach * Clemens Fruhwirth : introduce global icmp rate limiting * with icmp type masking ability instead * of broken per type icmp timeouts. @@ -649,8 +651,8 @@ read_lock(&raw_v4_lock); if ((raw_sk = raw_v4_htable[hash]) != NULL) { - while ((raw_sk = __raw_v4_lookup(raw_sk, protocol, iph->daddr, - iph->saddr, skb->dev->ifindex)) != NULL) { + while ((raw_sk = __raw_v4_lookup(raw_sk, protocol, iph->saddr, + iph->daddr, skb->dev->ifindex)) != NULL) { raw_err(raw_sk, skb, info); raw_sk = raw_sk->next; iph = (struct iphdr *)skb->data;
#include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/unistd.h> #include <netinet/in.h> #include <errno.h> int main(int argc, char *argv[]) { fd_set rfds; int nfds; int bound; int unbound; struct sockaddr_in sin; char *bind_addr = "192.168.1.1"; u_char buf[2048]; size_t bufsize; socklen_t len; bzero(&sin, sizeof(sin)); sin.sin_family = AF_INET; inet_pton(AF_INET, bind_addr, &sin.sin_addr); bound = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); unbound = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if(bound == -1 || unbound == -1) { printf("could not open sockets\n"); return -1; } if(bind(bound, (struct sockaddr *)&sin, sizeof(sin)) == -1) { printf("could not bind to %s, errno %d\n", bind_addr, errno); return -1; } while(1) { FD_ZERO(&rfds); FD_SET(bound, &rfds); FD_SET(unbound, &rfds); if(bound < unbound) nfds = unbound; else nfds = bound; if(select(nfds+1, &rfds, NULL, NULL, NULL) < 1) { return -1; } if(FD_ISSET(bound, &rfds)) { printf("got something on the bound socket\n"); recvfrom(bound, buf, bufsize, 0, (struct sockaddr *)&sin, &len); } if(FD_ISSET(unbound, &rfds)) { printf("got something on the unbound socket\n"); recvfrom(unbound, buf, bufsize, 0, (struct sockaddr *)&sin, &len); } } return 0; }
#include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/unistd.h> #include <netinet/in.h> #include <errno.h> #define ICMP_DEST_UNREACH 3 /* Destination Unreachable */ #define ICMP_HOST_UNREACH 1 /* Host Unreachable */ struct icmphdr { u_int8_t type; u_int8_t code; u_int16_t checksum; u_int16_t id; u_int16_t sequence; }; int main(int argc, char *argv[]) { fd_set rfds; int nfds; int sock; struct sockaddr_in sin; char *dest_addr = "192.168.1.1"; u_char buf[sizeof(struct icmphdr)]; size_t bufsize; struct icmphdr *icmp; socklen_t sa_len = sizeof(struct sockaddr_in); int len = sizeof(buf); bzero(&sin, sizeof(sin)); inet_pton(AF_INET, dest_addr, &sin.sin_addr); sin.sin_family = AF_INET; sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if(sock == -1) { printf("could not open socket: errno %d\n", errno); return -1; } icmp = (struct icmphdr *)buf; bzero(buf, sizeof(buf)); icmp->type = ICMP_DEST_UNREACH; icmp->code = ICMP_HOST_UNREACH; icmp->checksum = 0; if(sendto(sock, buf, len, 0, (struct sockaddr *)&sin, sa_len) == -1) { printf("could not sendto, errno %d\n", errno); return -1; } return 0; }