| /* | |
| * rarpd.c RARP daemon. | |
| * | |
| * This program is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU General Public License | |
| * as published by the Free Software Foundation; either version | |
| * 2 of the License, or (at your option) any later version. | |
| * | |
| * Authors: Alexey Kuznetsov, <[email protected]> | |
| */ | |
| #include <stdio.h> | |
| #include <syslog.h> | |
| #include <dirent.h> | |
| #include <malloc.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <stdlib.h> | |
| #include <netdb.h> | |
| #include <arpa/inet.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/poll.h> | |
| #include <sys/errno.h> | |
| #include <sys/fcntl.h> | |
| #include <sys/socket.h> | |
| #include <sys/signal.h> | |
| #include <linux/if.h> | |
| #include <linux/if_arp.h> | |
| #include <netinet/in.h> | |
| #include <linux/if_packet.h> | |
| #include <linux/filter.h> | |
| int do_reload = 1; | |
| int debug; | |
| int verbose; | |
| int ifidx; | |
| int allow_offlink; | |
| int only_ethers; | |
| int all_ifaces; | |
| int listen_arp; | |
| char *ifname; | |
| char *tftp_dir = "/etc/tftpboot"; | |
| extern int ether_ntohost(char *name, unsigned char *ea); | |
| void usage(void) __attribute__((noreturn)); | |
| struct iflink | |
| { | |
| struct iflink *next; | |
| int index; | |
| int hatype; | |
| unsigned char lladdr[16]; | |
| char name[IFNAMSIZ]; | |
| struct ifaddr *ifa_list; | |
| } *ifl_list; | |
| struct ifaddr | |
| { | |
| struct ifaddr *next; | |
| __u32 prefix; | |
| __u32 mask; | |
| __u32 local; | |
| }; | |
| struct rarp_map | |
| { | |
| struct rarp_map *next; | |
| int ifindex; | |
| int arp_type; | |
| int lladdr_len; | |
| unsigned char lladdr[16]; | |
| __u32 ipaddr; | |
| } *rarp_db; | |
| void usage() | |
| { | |
| fprintf(stderr, "Usage: rarpd [ -dveaA ] [ -b tftpdir ] [ interface]\n"); | |
| exit(1); | |
| } | |
| void load_db(void) | |
| { | |
| } | |
| void load_if(void) | |
| { | |
| int fd; | |
| struct ifreq *ifrp, *ifend; | |
| struct iflink *ifl; | |
| struct ifaddr *ifa; | |
| struct ifconf ifc; | |
| struct ifreq ibuf[256]; | |
| if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { | |
| syslog(LOG_ERR, "socket: %m"); | |
| return; | |
| } | |
| ifc.ifc_len = sizeof ibuf; | |
| ifc.ifc_buf = (caddr_t)ibuf; | |
| if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) < 0 || | |
| ifc.ifc_len < (int)sizeof(struct ifreq)) { | |
| syslog(LOG_ERR, "SIOCGIFCONF: %m"); | |
| close(fd); | |
| return; | |
| } | |
| while ((ifl = ifl_list) != NULL) { | |
| while ((ifa = ifl->ifa_list) != NULL) { | |
| ifl->ifa_list = ifa->next; | |
| free(ifa); | |
| } | |
| ifl_list = ifl->next; | |
| free(ifl); | |
| } | |
| ifend = (struct ifreq *)((char *)ibuf + ifc.ifc_len); | |
| for (ifrp = ibuf; ifrp < ifend; ifrp++) { | |
| __u32 addr; | |
| __u32 mask; | |
| __u32 prefix; | |
| if (ifrp->ifr_addr.sa_family != AF_INET) | |
| continue; | |
| addr = ((struct sockaddr_in*)&ifrp->ifr_addr)->sin_addr.s_addr; | |
| if (addr == 0) | |
| continue; | |
| if (ioctl(fd, SIOCGIFINDEX, ifrp)) { | |
| syslog(LOG_ERR, "ioctl(SIOCGIFNAME): %m"); | |
| continue; | |
| } | |
| if (ifidx && ifrp->ifr_ifindex != ifidx) | |
| continue; | |
| for (ifl = ifl_list; ifl; ifl = ifl->next) | |
| if (ifl->index == ifrp->ifr_ifindex) | |
| break; | |
| if (ifl == NULL) { | |
| char *p; | |
| int index = ifrp->ifr_ifindex; | |
| if (ioctl(fd, SIOCGIFHWADDR, ifrp)) { | |
| syslog(LOG_ERR, "ioctl(SIOCGIFHWADDR): %m"); | |
| continue; | |
| } | |
| ifl = (struct iflink*)malloc(sizeof(*ifl)); | |
| if (ifl == NULL) | |
| continue; | |
| memset(ifl, 0, sizeof(*ifl)); | |
| ifl->next = ifl_list; | |
| ifl_list = ifl; | |
| ifl->index = index; | |
| ifl->hatype = ifrp->ifr_hwaddr.sa_family; | |
| memcpy(ifl->lladdr, ifrp->ifr_hwaddr.sa_data, 14); | |
| strncpy(ifl->name, ifrp->ifr_name, IFNAMSIZ); | |
| p = strchr(ifl->name, ':'); | |
| if (p) | |
| *p = 0; | |
| if (verbose) | |
| syslog(LOG_INFO, "link %s", ifl->name); | |
| } | |
| if (ioctl(fd, SIOCGIFNETMASK, ifrp)) { | |
| syslog(LOG_ERR, "ioctl(SIOCGIFMASK): %m"); | |
| continue; | |
| } | |
| mask = ((struct sockaddr_in*)&ifrp->ifr_netmask)->sin_addr.s_addr; | |
| if (ioctl(fd, SIOCGIFDSTADDR, ifrp)) { | |
| syslog(LOG_ERR, "ioctl(SIOCGIFDSTADDR): %m"); | |
| continue; | |
| } | |
| prefix = ((struct sockaddr_in*)&ifrp->ifr_dstaddr)->sin_addr.s_addr; | |
| for (ifa = ifl->ifa_list; ifa; ifa = ifa->next) { | |
| if (ifa->local == addr && | |
| ifa->prefix == prefix && | |
| ifa->mask == mask) | |
| break; | |
| } | |
| if (ifa == NULL) { | |
| if (mask == 0 || prefix == 0) | |
| continue; | |
| ifa = (struct ifaddr*)malloc(sizeof(*ifa)); | |
| memset(ifa, 0, sizeof(*ifa)); | |
| ifa->local = addr; | |
| ifa->prefix = prefix; | |
| ifa->mask = mask; | |
| ifa->next = ifl->ifa_list; | |
| ifl->ifa_list = ifa; | |
| if (verbose) { | |
| int i; | |
| __u32 m = ~0U; | |
| for (i=32; i>=0; i--) { | |
| if (htonl(m) == mask) | |
| break; | |
| m <<= 1; | |
| } | |
| if (addr == prefix) { | |
| syslog(LOG_INFO, " addr %s/%d on %s\n", | |
| inet_ntoa(*(struct in_addr*)&addr), i, ifl->name); | |
| } else { | |
| char tmpa[64]; | |
| sprintf(tmpa, "%s", inet_ntoa(*(struct in_addr*)&addr)); | |
| syslog(LOG_INFO, " addr %s %s/%d on %s\n", tmpa, | |
| inet_ntoa(*(struct in_addr*)&prefix), i, ifl->name); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void configure(void) | |
| { | |
| load_if(); | |
| load_db(); | |
| } | |
| int bootable(__u32 addr) | |
| { | |
| struct dirent *dent; | |
| DIR *d; | |
| char name[9]; | |
| sprintf(name, "%08X", (__u32)ntohl(addr)); | |
| d = opendir(tftp_dir); | |
| if (d == NULL) { | |
| syslog(LOG_ERR, "opendir: %m"); | |
| return 0; | |
| } | |
| while ((dent = readdir(d)) != NULL) { | |
| if (strncmp(dent->d_name, name, 8) == 0) | |
| break; | |
| } | |
| closedir(d); | |
| return dent != NULL; | |
| } | |
| struct ifaddr *select_ipaddr(int ifindex, __u32 *sel_addr, __u32 **alist) | |
| { | |
| struct iflink *ifl; | |
| struct ifaddr *ifa; | |
| int retry = 0; | |
| int i; | |
| retry: | |
| for (ifl=ifl_list; ifl; ifl=ifl->next) | |
| if (ifl->index == ifindex) | |
| break; | |
| if (ifl == NULL && !retry) { | |
| retry++; | |
| load_if(); | |
| goto retry; | |
| } | |
| if (ifl == NULL) | |
| return NULL; | |
| for (i=0; alist[i]; i++) { | |
| __u32 addr = *(alist[i]); | |
| for (ifa=ifl->ifa_list; ifa; ifa=ifa->next) { | |
| if (!((ifa->prefix^addr)&ifa->mask)) { | |
| *sel_addr = addr; | |
| return ifa; | |
| } | |
| } | |
| if (ifa == NULL && retry==0) { | |
| retry++; | |
| load_if(); | |
| goto retry; | |
| } | |
| } | |
| if (i==1 && allow_offlink) { | |
| *sel_addr = *(alist[0]); | |
| return ifl->ifa_list; | |
| } | |
| syslog(LOG_ERR, "Off-link request on %s", ifl->name); | |
| return NULL; | |
| } | |
| struct rarp_map *rarp_lookup(int ifindex, int hatype, | |
| int halen, unsigned char *lladdr) | |
| { | |
| struct rarp_map *r; | |
| for (r=rarp_db; r; r=r->next) { | |
| if (r->arp_type != hatype && r->arp_type != -1) | |
| continue; | |
| if (r->lladdr_len != halen) | |
| continue; | |
| if (r->ifindex != ifindex && r->ifindex != 0) | |
| continue; | |
| if (memcmp(r->lladdr, lladdr, halen) == 0) | |
| break; | |
| } | |
| if (r == NULL) { | |
| if (hatype == ARPHRD_ETHER && halen == 6) { | |
| struct ifaddr *ifa; | |
| struct hostent *hp; | |
| char ename[256]; | |
| static struct rarp_map emap = { | |
| NULL, | |
| 0, | |
| ARPHRD_ETHER, | |
| 6, | |
| }; | |
| if (ether_ntohost(ename, lladdr) != 0 || | |
| (hp = gethostbyname(ename)) == NULL) { | |
| if (verbose) | |
| syslog(LOG_INFO, "not found in /etc/ethers"); | |
| return NULL; | |
| } | |
| if (hp->h_addrtype != AF_INET) { | |
| syslog(LOG_ERR, "no IP address"); | |
| return NULL; | |
| } | |
| ifa = select_ipaddr(ifindex, &emap.ipaddr, (__u32 **)hp->h_addr_list); | |
| if (ifa) { | |
| memcpy(emap.lladdr, lladdr, 6); | |
| if (only_ethers || bootable(emap.ipaddr)) | |
| return &emap; | |
| if (verbose) | |
| syslog(LOG_INFO, "not bootable"); | |
| } | |
| } | |
| } | |
| return r; | |
| } | |
| static int load_arp_bpflet(int fd) | |
| { | |
| static struct sock_filter insns[] = { | |
| BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 6), | |
| BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ARPOP_RREQUEST, 0, 1), | |
| BPF_STMT(BPF_RET|BPF_K, 1024), | |
| BPF_STMT(BPF_RET|BPF_K, 0), | |
| }; | |
| static struct sock_fprog filter = { | |
| sizeof insns / sizeof(insns[0]), | |
| insns | |
| }; | |
| return setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)); | |
| } | |
| int put_mylladdr(unsigned char **ptr_p, int ifindex, int alen) | |
| { | |
| struct iflink *ifl; | |
| for (ifl=ifl_list; ifl; ifl = ifl->next) | |
| if (ifl->index == ifindex) | |
| break; | |
| if (ifl==NULL) | |
| return -1; | |
| memcpy(*ptr_p, ifl->lladdr, alen); | |
| *ptr_p += alen; | |
| return 0; | |
| } | |
| int put_myipaddr(unsigned char **ptr_p, int ifindex, __u32 hisipaddr) | |
| { | |
| __u32 laddr = 0; | |
| struct iflink *ifl; | |
| struct ifaddr *ifa; | |
| for (ifl=ifl_list; ifl; ifl = ifl->next) | |
| if (ifl->index == ifindex) | |
| break; | |
| if (ifl==NULL) | |
| return -1; | |
| for (ifa=ifl->ifa_list; ifa; ifa=ifa->next) { | |
| if (!((ifa->prefix^hisipaddr)&ifa->mask)) { | |
| laddr = ifa->local; | |
| break; | |
| } | |
| } | |
| memcpy(*ptr_p, &laddr, 4); | |
| *ptr_p += 4; | |
| return 0; | |
| } | |
| void arp_advise(int ifindex, unsigned char *lladdr, int lllen, __u32 ipaddr) | |
| { | |
| int fd; | |
| struct arpreq req; | |
| struct sockaddr_in *sin; | |
| struct iflink *ifl; | |
| for (ifl=ifl_list; ifl; ifl = ifl->next) | |
| if (ifl->index == ifindex) | |
| break; | |
| if (ifl == NULL) | |
| return; | |
| fd = socket(AF_INET, SOCK_DGRAM, 0); | |
| memset(&req, 0, sizeof(req)); | |
| req.arp_flags = ATF_COM; | |
| sin = (struct sockaddr_in *)&req.arp_pa; | |
| sin->sin_family = AF_INET; | |
| sin->sin_addr.s_addr = ipaddr; | |
| req.arp_ha.sa_family = ifl->hatype; | |
| memcpy(req.arp_ha.sa_data, lladdr, lllen); | |
| memcpy(req.arp_dev, ifl->name, IFNAMSIZ); | |
| if (ioctl(fd, SIOCSARP, &req)) | |
| syslog(LOG_ERR, "SIOCSARP: %m"); | |
| close(fd); | |
| } | |
| void serve_it(int fd) | |
| { | |
| unsigned char buf[1024]; | |
| struct sockaddr_ll sll; | |
| socklen_t sll_len = sizeof(sll); | |
| struct arphdr *a = (struct arphdr*)buf; | |
| struct rarp_map *rmap; | |
| unsigned char *ptr; | |
| int n; | |
| n = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr*)&sll, &sll_len); | |
| if (n<0) { | |
| if (errno != EINTR && errno != EAGAIN) | |
| syslog(LOG_ERR, "recvfrom: %m"); | |
| return; | |
| } | |
| /* Do not accept packets for other hosts and our own ones */ | |
| if (sll.sll_pkttype != PACKET_BROADCAST && | |
| sll.sll_pkttype != PACKET_MULTICAST && | |
| sll.sll_pkttype != PACKET_HOST) | |
| return; | |
| if (ifidx && sll.sll_ifindex != ifidx) | |
| return; | |
| if (n<sizeof(*a)) { | |
| syslog(LOG_ERR, "truncated arp packet; len=%d", n); | |
| return; | |
| } | |
| /* Accept only RARP requests */ | |
| if (a->ar_op != htons(ARPOP_RREQUEST)) | |
| return; | |
| if (verbose) { | |
| int i; | |
| char tmpbuf[16*3]; | |
| char *ptr = tmpbuf; | |
| for (i=0; i<sll.sll_halen; i++) { | |
| if (i) { | |
| sprintf(ptr, ":%02x", sll.sll_addr[i]); | |
| ptr++; | |
| } else | |
| sprintf(ptr, "%02x", sll.sll_addr[i]); | |
| ptr += 2; | |
| } | |
| syslog(LOG_INFO, "RARP request from %s on if%d", tmpbuf, sll.sll_ifindex); | |
| } | |
| /* Sanity checks */ | |
| /* 1. IP only -> pln==4 */ | |
| if (a->ar_pln != 4) { | |
| syslog(LOG_ERR, "interesting rarp_req plen=%d", a->ar_pln); | |
| return; | |
| } | |
| /* 2. ARP protocol must be IP */ | |
| if (a->ar_pro != htons(ETH_P_IP)) { | |
| syslog(LOG_ERR, "rarp protocol is not IP %04x", ntohs(a->ar_pro)); | |
| return; | |
| } | |
| /* 3. ARP types must match */ | |
| if (htons(sll.sll_hatype) != a->ar_hrd) { | |
| switch (sll.sll_hatype) { | |
| case ARPHRD_FDDI: | |
| if (a->ar_hrd == htons(ARPHRD_ETHER) || | |
| a->ar_hrd == htons(ARPHRD_IEEE802)) | |
| break; | |
| default: | |
| syslog(LOG_ERR, "rarp htype mismatch"); | |
| return; | |
| } | |
| } | |
| /* 3. LL address lengths must be equal */ | |
| if (a->ar_hln != sll.sll_halen) { | |
| syslog(LOG_ERR, "rarp hlen mismatch"); | |
| return; | |
| } | |
| /* 4. Check packet length */ | |
| if (sizeof(*a) + 2*4 + 2*a->ar_hln > n) { | |
| syslog(LOG_ERR, "truncated rarp request; len=%d", n); | |
| return; | |
| } | |
| /* 5. Silly check: if this guy set different source | |
| addresses in MAC header and in ARP, he is insane | |
| */ | |
| if (memcmp(sll.sll_addr, a+1, sll.sll_halen)) { | |
| syslog(LOG_ERR, "this guy set different his lladdrs in arp and header"); | |
| return; | |
| } | |
| /* End of sanity checks */ | |
| /* Lookup requested target in our database */ | |
| rmap = rarp_lookup(sll.sll_ifindex, sll.sll_hatype, | |
| sll.sll_halen, (unsigned char*)(a+1) + sll.sll_halen + 4); | |
| if (rmap == NULL) | |
| return; | |
| /* Prepare reply. It is almost ready, we only | |
| replace ARP packet type, put our lladdr and | |
| IP address to source fileds, | |
| and fill target IP address. | |
| */ | |
| a->ar_op = htons(ARPOP_RREPLY); | |
| ptr = (unsigned char*)(a+1); | |
| if (put_mylladdr(&ptr, sll.sll_ifindex, rmap->lladdr_len)) | |
| return; | |
| if (put_myipaddr(&ptr, sll.sll_ifindex, rmap->ipaddr)) | |
| return; | |
| /* It is already filled */ | |
| ptr += rmap->lladdr_len; | |
| memcpy(ptr, &rmap->ipaddr, 4); | |
| ptr += 4; | |
| /* Update our ARP cache. Probably, this guy | |
| will not able to make ARP (if it is broken) | |
| */ | |
| arp_advise(sll.sll_ifindex, rmap->lladdr, rmap->lladdr_len, rmap->ipaddr); | |
| /* Sendto is blocking, but with 5sec timeout */ | |
| alarm(5); | |
| sendto(fd, buf, ptr - buf, 0, (struct sockaddr*)&sll, sizeof(sll)); | |
| alarm(0); | |
| } | |
| void catch_signal(int sig, void (*handler)(int)) | |
| { | |
| struct sigaction sa; | |
| memset(&sa, 0, sizeof(sa)); | |
| sa.sa_handler = handler; | |
| #ifdef SA_INTERRUPT | |
| sa.sa_flags = SA_INTERRUPT; | |
| #endif | |
| sigaction(sig, &sa, NULL); | |
| } | |
| void sig_alarm(int signo) | |
| { | |
| } | |
| void sig_hup(int signo) | |
| { | |
| do_reload = 1; | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| struct pollfd pset[2]; | |
| int psize; | |
| int opt; | |
| opterr = 0; | |
| while ((opt = getopt(argc, argv, "aAb:dvoe")) != EOF) { | |
| switch (opt) { | |
| case 'a': | |
| ++all_ifaces; | |
| break; | |
| case 'A': | |
| ++listen_arp; | |
| break; | |
| case 'd': | |
| ++debug; | |
| break; | |
| case 'v': | |
| ++verbose; | |
| break; | |
| case 'o': | |
| ++allow_offlink; | |
| break; | |
| case 'e': | |
| ++only_ethers; | |
| break; | |
| case 'b': | |
| tftp_dir = optarg; | |
| break; | |
| default: | |
| usage(); | |
| } | |
| } | |
| if (argc > optind) { | |
| if (argc > optind+1) | |
| usage(); | |
| ifname = argv[optind]; | |
| } | |
| psize = 1; | |
| pset[0].fd = socket(PF_PACKET, SOCK_DGRAM, 0); | |
| if (ifname) { | |
| struct ifreq ifr; | |
| memset(&ifr, 0, sizeof(ifr)); | |
| strncpy(ifr.ifr_name, ifname, IFNAMSIZ); | |
| if (ioctl(pset[0].fd, SIOCGIFINDEX, &ifr)) { | |
| perror("ioctl(SIOCGIFINDEX)"); | |
| usage(); | |
| } | |
| ifidx = ifr.ifr_ifindex; | |
| } | |
| pset[1].fd = -1; | |
| if (listen_arp) { | |
| pset[1].fd = socket(PF_PACKET, SOCK_DGRAM, 0); | |
| if (pset[1].fd >= 0) { | |
| load_arp_bpflet(pset[1].fd); | |
| psize = 1; | |
| } | |
| } | |
| if (pset[1].fd >= 0) { | |
| struct sockaddr_ll sll; | |
| memset(&sll, 0, sizeof(sll)); | |
| sll.sll_family = AF_PACKET; | |
| sll.sll_protocol = htons(ETH_P_ARP); | |
| sll.sll_ifindex = all_ifaces ? 0 : ifidx; | |
| if (bind(pset[1].fd, (struct sockaddr*)&sll, sizeof(sll)) < 0) { | |
| close(pset[1].fd); | |
| pset[1].fd = -1; | |
| psize = 1; | |
| } | |
| } | |
| if (pset[0].fd >= 0) { | |
| struct sockaddr_ll sll; | |
| memset(&sll, 0, sizeof(sll)); | |
| sll.sll_family = AF_PACKET; | |
| sll.sll_protocol = htons(ETH_P_RARP); | |
| sll.sll_ifindex = all_ifaces ? 0 : ifidx; | |
| if (bind(pset[0].fd, (struct sockaddr*)&sll, sizeof(sll)) < 0) { | |
| close(pset[0].fd); | |
| pset[0].fd = -1; | |
| } | |
| } | |
| if (pset[0].fd < 0) { | |
| pset[0] = pset[1]; | |
| psize--; | |
| } | |
| if (psize == 0) { | |
| fprintf(stderr, "failed to bind any socket. Aborting.\n"); | |
| exit(1); | |
| } | |
| if (!debug) { | |
| int fd; | |
| pid_t pid = fork(); | |
| if (pid > 0) | |
| exit(0); | |
| else if (pid == -1) { | |
| perror("rarpd: fork"); | |
| exit(1); | |
| } | |
| if (chdir("/") < 0) { | |
| perror("rarpd: chdir"); | |
| exit(1); | |
| } | |
| fd = open("/dev/null", O_RDWR); | |
| if (fd >= 0) { | |
| dup2(fd, 0); | |
| dup2(fd, 1); | |
| dup2(fd, 2); | |
| if (fd > 2) | |
| close(fd); | |
| } | |
| setsid(); | |
| } | |
| openlog("rarpd", LOG_PID | LOG_CONS, LOG_DAEMON); | |
| catch_signal(SIGALRM, sig_alarm); | |
| catch_signal(SIGHUP, sig_hup); | |
| for (;;) { | |
| int i; | |
| if (do_reload) { | |
| configure(); | |
| do_reload = 0; | |
| } | |
| #define EVENTS (POLLIN|POLLPRI|POLLERR|POLLHUP) | |
| pset[0].events = EVENTS; | |
| pset[0].revents = 0; | |
| pset[1].events = EVENTS; | |
| pset[1].revents = 0; | |
| i = poll(pset, psize, -1); | |
| if (i <= 0) { | |
| if (errno != EINTR && i<0) { | |
| syslog(LOG_ERR, "poll returned some crap: %m\n"); | |
| sleep(10); | |
| } | |
| continue; | |
| } | |
| for (i=0; i<psize; i++) { | |
| if (pset[i].revents&EVENTS) | |
| serve_it(pset[i].fd); | |
| } | |
| } | |
| } |