#define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FRAME_MIN_LEN 64 void parse_mac(const char* str, uint8_t* mac) { char* s = strdup(str); char* octet = strtok(s, ":"); int i = 0; while (octet != NULL && i < 6) { mac[i++] = strtol(octet, NULL, 16); octet = strtok(NULL, ":"); } free(s); } void print_mac(uint8_t* mac) { printf("%02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } void output_mac(uint8_t* mac) { printf("%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } typedef struct { char* if_name; socklen_t if_len; uint8_t host[ETH_ALEN]; uint8_t host_ip[4]; int fd; // raw socket int if_idx; int if_mtu; } linkinterface_t; // because C has retarded array types typedef struct { uint8_t mac[ETH_ALEN]; } __attribute__((packed)) mac_t; typedef struct { union { uint32_t addr; uint8_t octets[4]; }; } __attribute__((packed)) ip4addr_t; typedef struct { uint16_t id; void* data; size_t datalen; } frame_t; frame_t* frame_new(size_t datalen) { frame_t* frame = (frame_t*)malloc(sizeof(frame_t)); frame->id = (uint16_t)rand(); frame->data = malloc(datalen); frame->datalen = datalen; memset(frame->data, '\0', frame->datalen); return frame; } frame_t* frame_full(linkinterface_t* link) { return frame_new(link->if_mtu); } void frame_free(frame_t* frame) { free(frame->data); free(frame); } linkinterface_t* link_open(const char* if_name) { linkinterface_t* link = (linkinterface_t*)malloc(sizeof(linkinterface_t)); link->if_name = strdup(if_name); link->if_len = strlen(link->if_name); link->fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (link->fd < 0) { goto _bad0; } setsockopt(link->fd, SOL_SOCKET, SO_BINDTODEVICE, link->if_name, link->if_len); // get interface index and MAC struct ifreq netlink; memset(&netlink, '\0', sizeof(netlink)); strncpy(netlink.ifr_ifrn.ifrn_name, link->if_name, IFNAMSIZ-1); if (ioctl(link->fd, SIOCGIFINDEX, &netlink) < 0) { goto _bad1; } link->if_idx = netlink.ifr_ifru.ifru_ivalue; // index memset(&netlink, '\0', sizeof(netlink)); strncpy(netlink.ifr_ifrn.ifrn_name, link->if_name, IFNAMSIZ-1); if (ioctl(link->fd, SIOCGIFHWADDR, &netlink) < 0) { goto _bad1; } memcpy(link->host, netlink.ifr_ifru.ifru_hwaddr.sa_data, ETH_ALEN); // MAC // get interface IP address memset(&netlink, '\0', sizeof(netlink)); strncpy(netlink.ifr_ifrn.ifrn_name, link->if_name, IFNAMSIZ-1); netlink.ifr_ifru.ifru_addr.sa_family = PF_INET; if (ioctl(link->fd, SIOCGIFADDR, &netlink) < 0) { goto _bad1; } memcpy(link->host_ip, &((struct sockaddr_in*)&netlink.ifr_ifru.ifru_addr)->sin_addr, 4); // get interface MTU memset(&netlink, '\0', sizeof(netlink)); strncpy(netlink.ifr_ifrn.ifrn_name, link->if_name, IFNAMSIZ-1); if (ioctl(link->fd, SIOCGIFMTU, &netlink) < 0) { goto _bad1; } link->if_mtu = netlink.ifr_ifru.ifru_mtu; return link; _bad1: close(link->fd); _bad0: free(link->if_name); free(link); return NULL; } void link_free(linkinterface_t* link) { close(link->fd); free(link->if_name); free(link); } ssize_t link_send(linkinterface_t* link, const mac_t* dstAddr, uint16_t type, frame_t* frame) { // ETHER FRAME MUST NOT BE LESS THAN 64 (60) size_t oldlen = frame->datalen; frame->datalen = MAX(60, frame->datalen + sizeof(struct ether_header)); // add space for ether header and shift user data frame->data = realloc(frame->data, frame->datalen); memmove((uint8_t*)frame->data + sizeof(struct ether_header), frame->data, oldlen); struct ether_header* ether = (struct ether_header*)frame->data; memcpy(ether->ether_shost, link->host, ETH_ALEN); memcpy(ether->ether_dhost, dstAddr->mac, ETH_ALEN); ether->ether_type = htons(type); struct sockaddr_ll ll_addr = {0}; ll_addr.sll_family = PF_PACKET; ll_addr.sll_ifindex = link->if_idx; ll_addr.sll_halen = ETH_ALEN; memcpy(ll_addr.sll_addr, dstAddr, ETH_ALEN); size_t sent = sendto(link->fd, frame->data, frame->datalen, 0, (const struct sockaddr*)&ll_addr, sizeof(ll_addr)); return sent; } size_t link_recv_any_from(linkinterface_t* link, const mac_t* srcAddrs, unsigned addrNum, uint16_t type, frame_t* frame, unsigned timeoutSec, mac_t* matchAddr) { uint64_t deadline = time(NULL) + timeoutSec; uint16_t want_type = htons(type); do { ssize_t rd = recv(link->fd, frame->data, frame->datalen, 0); if (rd < 0) return -1; if (time(NULL) > deadline) return 0; // TIMEOUT struct ether_header* ether = (struct ether_header*)frame->data; unsigned matches = 0; for (unsigned i = 0; i < addrNum; i++) { if (!memcmp(ether->ether_shost, srcAddrs[i].mac, ETH_ALEN)) { matches = 1; if (matchAddr) { memcpy(matchAddr->mac, ether->ether_shost, ETH_ALEN); } break; } } if (!matches) continue; if (memcmp(ether->ether_dhost, link->host, ETH_ALEN)) { continue; // not our host } if (ether->ether_type != want_type) { continue; // not wanted type } // shift back ether header and realloc frame->datalen = rd - sizeof(struct ether_header); memmove(frame->data, (const uint8_t*)frame->data + sizeof(struct ether_header), frame->datalen); frame->data = realloc(frame->data, frame->datalen); return frame->datalen; } while (1); } uint16_t checksum(const uint8_t* data, size_t len) { uint32_t sum = 0; for (size_t i = 0; i < len / 2; i++) { sum += *((const uint16_t*)data + i); sum = (sum >> 16) + (sum & 0xFFFF); } return ~((uint16_t)sum); } ssize_t ip_send(linkinterface_t* link, const mac_t* dstAddr, const ip4addr_t dstIp, uint8_t proto, frame_t* frame) { // shift data to add space for IP header size_t oldlen = frame->datalen; frame->datalen = sizeof(struct ip) + frame->datalen; frame->data = realloc(frame->data, frame->datalen); memmove((uint8_t*)frame->data + sizeof(struct ip), frame->data, oldlen); // create IP packet struct ip* ip = (struct ip*)frame->data; ip->ip_v = 4; ip->ip_hl = sizeof(struct ip) / 4; ip->ip_tos = 0; ip->ip_len = htons(frame->datalen); ip->ip_id = htons(frame->id); ip->ip_off = 0; ip->ip_ttl = 64; ip->ip_p = proto; ip->ip_sum = 0; memcpy(&ip->ip_src, link->host_ip, 4); memcpy(&ip->ip_dst, dstIp.octets, 4); // calculate header checksum ip->ip_sum = checksum((const uint8_t*)frame->data, sizeof(struct ip)); return link_send(link, dstAddr, ETHERTYPE_IP, frame); } ssize_t icmp_direct_broadcast(linkinterface_t* link, const mac_t* dstAddr, uint16_t seq) { size_t hdrlen = sizeof(struct icmphdr); const size_t payloadlen = 20; frame_t* frame = frame_new(hdrlen + payloadlen); struct icmphdr* icmp = (struct icmphdr*)frame->data; icmp->type = ICMP_ECHO; icmp->code = 0; icmp->checksum = 0; icmp->un.echo.id = htons(frame->id); icmp->un.echo.sequence = htons(seq); uint8_t* payload = (uint8_t*)frame->data + hdrlen; for (unsigned i = 0; i < payloadlen; i++) { payload[i] = rand() % 256; } icmp->checksum = checksum((const uint8_t*)frame->data, hdrlen + payloadlen); const ip4addr_t ip_broadcast = { .addr = 0xFFFFFFFF }; size_t sent = ip_send(link, dstAddr, ip_broadcast, IPPROTO_ICMP, frame); frame_free(frame); return sent; } // 0 - no match, 1 - matched unsigned icmp_match(linkinterface_t* link, const mac_t* srcAddrs, unsigned addrNum, unsigned timeoutSec, mac_t* matchAddr, ip4addr_t* matchIp) { frame_t* frame = frame_full(link); size_t recv = link_recv_any_from(link, srcAddrs, addrNum, ETHERTYPE_IP, frame, timeoutSec, matchAddr); if (recv < 1) goto _match_bad1; // we got matching Ethernet frame, let's check IP const struct ip* ip = (const struct ip*)frame->data; if (memcmp(&ip->ip_dst.s_addr, link->host_ip, 4)) { // not originated to our host IP goto _match_bad1; } if (ip->ip_p != IPPROTO_ICMP) { goto _match_bad1; } // check ICMP const struct icmphdr* icmp = (const struct icmphdr*) ((uint8_t*)frame->data + (ip->ip_hl * 4)); if (icmp->type != ICMP_ECHOREPLY) { goto _match_bad1; } // so ether frame directed to us, IP direct to us // and ICMP is echo reply, therefore we sure // that we got right target IP memcpy(matchIp->octets, &ip->ip_src.s_addr, 4); frame_free(frame); return 1; _match_bad1: frame_free(frame); return 0; } typedef struct { mac_t addr; ip4addr_t ip; } mac_ip_t; mac_ip_t* icmp_resolve(linkinterface_t* link, const mac_t* targetAddrs, unsigned addrNum, unsigned timeoutSec, unsigned* resolvedNum) { mac_ip_t* output = NULL; unsigned resolved = 0; // sent ICMP packets for (unsigned i = 0; i < addrNum; i++) { if (icmp_direct_broadcast(link, &targetAddrs[i], 0) < 1) { return NULL; // bad link? } } // receive ICMP packets as many as we match uint64_t deadline = time(NULL) + timeoutSec; do { mac_t matchAddr; ip4addr_t matchIp; if (icmp_match(link, targetAddrs, addrNum, timeoutSec, &matchAddr, &matchIp)) { if (resolved) output = (mac_ip_t*)realloc(output, ++resolved * (sizeof(mac_ip_t))); else output = (mac_ip_t*)malloc(++resolved * (sizeof(mac_ip_t))); memcpy(&output[resolved - 1].addr, &matchAddr, sizeof(matchAddr)); memcpy(&output[resolved - 1].ip, &matchIp, sizeof(matchIp)); } } while (resolved < addrNum && time(NULL) < deadline); *resolvedNum = resolved; return output; } int main(int argc, char** argv) { // arper ifname targetMAC1 targetMAC2 ... srand(time(NULL)); linkinterface_t* link = link_open(argv[1]); if (!link) { perror("link_open"); return 1; } print_mac(link->host); printf("MTU: %d\n", link->if_mtu); unsigned targetNum = argc - 2; mac_t* targets = (mac_t*)calloc(sizeof(mac_t), targetNum); for (unsigned i = 0; i < targetNum; i++) { parse_mac(argv[2 + i], targets[i].mac); } unsigned resolvedNum; mac_ip_t* resolved = icmp_resolve(link, targets, targetNum, 5, &resolvedNum); printf("Resolved: %u\n", resolvedNum); if (resolved) { for (unsigned i = 0; i < resolvedNum; i++) { output_mac(resolved[i].addr.mac); printf(" -> %d.%d.%d.%d\n", resolved[i].ip.octets[0], resolved[i].ip.octets[1], resolved[i].ip.octets[2], resolved[i].ip.octets[3] ); } free(resolved); } link_free(link); return 0; }