diff --git a/ncmlib/io.c b/ncmlib/io.c index 3bda0dc..46d1940 100644 --- a/ncmlib/io.c +++ b/ncmlib/io.c @@ -1,5 +1,5 @@ /* io.c - light wrappers for POSIX i/o functions - * Time-stamp: <2010-11-13 08:22:53 njk> + * Time-stamp: <2010-11-15 19:45:39 njk> * * (c) 2010 Nicholas J. Kain * All rights reserved. @@ -28,6 +28,8 @@ */ #include +#include +#include #include /* returns -1 on error, >= 0 and equal to # chars read on success */ @@ -65,3 +67,22 @@ int safe_write(int fd, const char *buf, int len) } return s; } + +/* returns -1 on error, >= 0 and equal to # chars written on success */ +int safe_sendto(int fd, const char *buf, int len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + int r, s = 0; + while (s < len) { + r = sendto(fd, buf + s, len - s, flags, dest_addr, addrlen); + if (r == -1) { + if (errno == EINTR) + continue; + else + return -1; + } + s += r; + } + return s; +} + diff --git a/ncmlib/io.h b/ncmlib/io.h index 9e24419..74ac169 100644 --- a/ncmlib/io.h +++ b/ncmlib/io.h @@ -1,5 +1,5 @@ /* io.h - light wrappers for POSIX i/o functions - * Time-stamp: <2010-11-13 08:22:48 njk> + * Time-stamp: <2010-11-15 19:45:32 njk> * * (c) 2010 Nicholas J. Kain * All rights reserved. @@ -32,5 +32,7 @@ int safe_read(int fd, char *buf, int len); int safe_write(int fd, const char *buf, int len); +int safe_sendto(int fd, const char *buf, int len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen); #endif /* NCM_IO_H_ */ diff --git a/ndhc/CMakeLists.txt b/ndhc/CMakeLists.txt index 4e821f6..c1cfeb2 100644 --- a/ndhc/CMakeLists.txt +++ b/ndhc/CMakeLists.txt @@ -8,6 +8,7 @@ set(NDHC_SRCS packet.c script.c clientpacket.c + arpping.c ndhc.c ) diff --git a/ndhc/arpping.c b/ndhc/arpping.c new file mode 100644 index 0000000..c848ec8 --- /dev/null +++ b/ndhc/arpping.c @@ -0,0 +1,160 @@ +/* + * Shamelessly ripped off from busybox's udhcpc variant, which in turn was... + * Mostly stolen from: dhcpcd - DHCP client daemon + * by Yoichi Hariguchi + * Licensed under GPLv2, see file LICENSE in this source tree. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dhcpd.h" +#include "log.h" +#include "strl.h" +#include "io.h" + +struct arpMsg { + /* Ethernet header */ + uint8_t h_dest[6]; /* 00 destination ether addr */ + uint8_t h_source[6]; /* 06 source ether addr */ + uint16_t h_proto; /* 0c packet type ID field */ + + /* ARP packet */ + uint16_t htype; /* 0e hardware type (must be ARPHRD_ETHER) */ + uint16_t ptype; /* 10 protocol type (must be ETH_P_IP) */ + uint8_t hlen; /* 12 hardware address length (must be 6) */ + uint8_t plen; /* 13 protocol address length (must be 4) */ + uint16_t operation; /* 14 ARP opcode */ + uint8_t sHaddr[6]; /* 16 sender's hardware address */ + uint8_t sInaddr[4]; /* 1c sender's IP address */ + uint8_t tHaddr[6]; /* 20 target's hardware address */ + uint8_t tInaddr[4]; /* 26 target's IP address */ + uint8_t pad[18]; /* 2a pad for min. ethernet payload (60 bytes) */ +}; + +enum { + ARP_MSG_SIZE = 0x2a +}; + +static int safe_poll(struct pollfd *ufds, nfds_t nfds, int timeout) +{ + while (1) { + int n = poll(ufds, nfds, timeout); + if (n >= 0) + return n; + /* Make sure we inch towards completion */ + if (timeout > 0) + timeout--; + /* E.g. strace causes poll to return this */ + if (errno == EINTR) + continue; + /* Kernel is very low on memory. Retry. */ + /* I doubt many callers would handle this correctly! */ + if (errno == ENOMEM) + continue; + log_warning("poll error: %s", strerror(errno)); + return n; + } +} + +static unsigned long long curms() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000ULL + tv.tv_usec / 1000ULL; +} + +/* Returns 1 if no reply received */ +int arpping(uint32_t test_nip, const uint8_t *safe_mac, uint32_t from_ip, + uint8_t *from_mac, const char *interface) +{ + int timeout_ms; + struct pollfd pfd[1]; + int rv = 1; /* "no reply received" yet */ + int opt = 1; + struct sockaddr addr; /* for interface name */ + struct arpMsg arp; + + pfd[0].fd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)); + if (pfd[0].fd == -1) { + log_warning("arpping: failed to create socket: %s", strerror(errno)); + return -1; + } + + if (setsockopt(pfd[0].fd, SOL_SOCKET, SO_BROADCAST, + &opt, sizeof opt) == -1) { + log_warning("arpping: failed to set broadcast: %s", strerror(errno)); + goto ret; + } + + /* send arp request */ + memset(&arp, 0, sizeof arp); + memset(arp.h_dest, 0xff, 6); /* MAC DA */ + memcpy(arp.h_source, from_mac, 6); /* MAC SA */ + arp.h_proto = htons(ETH_P_ARP); /* protocol type (Ethernet) */ + arp.htype = htons(ARPHRD_ETHER); /* hardware type */ + arp.ptype = htons(ETH_P_IP); /* protocol type (ARP message) */ + arp.hlen = 6; /* hardware address length */ + arp.plen = 4; /* protocol address length */ + arp.operation = htons(ARPOP_REQUEST); /* ARP op code */ + memcpy(arp.sHaddr, from_mac, 6); /* source hardware address */ + memcpy(arp.sInaddr, &from_ip, sizeof from_ip); /* source IP address */ + /* tHaddr is zero-filled */ /* target hardware address */ + memcpy(arp.tInaddr, &test_nip, sizeof test_nip);/* target IP address */ + + memset(&addr, 0, sizeof addr); + strlcpy(addr.sa_data, interface, sizeof addr.sa_data); + if (safe_sendto(pfd[0].fd, (const char *)&arp, sizeof arp, + 0, &addr, sizeof addr) < 0) { + log_error("arpping: sendto failed: %s", strerror(errno)); + goto ret; + } + + /* wait for arp reply, and check it */ + timeout_ms = 2000; + do { + typedef uint32_t aliased_uint32_t __attribute__((__may_alias__)); + int r; + unsigned long long prevTime = curms(); + + pfd[0].events = POLLIN; + r = safe_poll(pfd, 1, timeout_ms); + if (r < 0) + break; + if (r) { + r = safe_read(pfd[0].fd, (char *)&arp, sizeof arp); + if (r < 0) + break; + + //log3("sHaddr %02x:%02x:%02x:%02x:%02x:%02x", + //arp.sHaddr[0], arp.sHaddr[1], arp.sHaddr[2], + //arp.sHaddr[3], arp.sHaddr[4], arp.sHaddr[5]); + + if (r >= ARP_MSG_SIZE + && arp.operation == htons(ARPOP_REPLY) + /* don't check it: Linux doesn't return proper tHaddr (fixed in 2.6.24?) */ + /* && memcmp(arp.tHaddr, from_mac, 6) == 0 */ + && *(aliased_uint32_t*)arp.sInaddr == test_nip + ) { + /* if ARP source MAC matches safe_mac + * (which is client's MAC), then it's not a conflict + * (client simply already has this IP and replies to ARPs!) + */ + if (!safe_mac || memcmp(safe_mac, arp.sHaddr, 6) != 0) + rv = 0; + break; + } + } + timeout_ms -= (int)(curms() - prevTime); + } while (timeout_ms > 0); + + ret: + close(pfd[0].fd); + log_line("%srp reply received for this address", rv ? "No a" : "A"); + return rv; +} diff --git a/ndhc/arpping.h b/ndhc/arpping.h new file mode 100644 index 0000000..e9ae76f --- /dev/null +++ b/ndhc/arpping.h @@ -0,0 +1,8 @@ +#ifndef ARPPING_H_ +#define ARPPING_H_ + +int arpping(uint32_t test_nip, const uint8_t *safe_mac, uint32_t from_ip, + uint8_t *from_mac, const char *interface); + + +#endif /* ARPPING_H_ */ diff --git a/ndhc/clientpacket.c b/ndhc/clientpacket.c index 8052f94..5e9a64b 100644 --- a/ndhc/clientpacket.c +++ b/ndhc/clientpacket.c @@ -175,6 +175,30 @@ int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr) return bcast_raw_packet(&packet); } +/* Broadcast a DHCP decline message */ +int send_decline(uint32_t xid, uint32_t server, uint32_t requested) +{ + struct dhcpMessage packet; + + /* Fill in: op, htype, hlen, cookie, chaddr, random xid fields, + * client-id option (unless -C), message type option: + */ + init_packet(&packet, DHCPDECLINE); + + /* RFC 2131 says DHCPDECLINE's xid is randomly selected by client, + * but in case the server is buggy and wants DHCPDECLINE's xid + * to match the xid which started entire handshake, + * we use the same xid we used in initial DHCPDISCOVER: + */ + packet.xid = xid; + /* DHCPDECLINE uses "requested ip", not ciaddr, to store offered IP */ + add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + add_simple_option(packet.options, DHCP_SERVER_ID, server); + + log_line("Sending decline..."); + return bcast_raw_packet(&packet); +} + /* Unicasts a DHCP release message */ int send_release(uint32_t server, uint32_t ciaddr) { diff --git a/ndhc/clientpacket.h b/ndhc/clientpacket.h index a0a3b4e..42dabba 100644 --- a/ndhc/clientpacket.h +++ b/ndhc/clientpacket.h @@ -8,6 +8,7 @@ int send_discover(uint32_t xid, uint32_t requested); int send_selecting(uint32_t xid, uint32_t server, uint32_t requested); int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr); int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr); +int send_decline(uint32_t xid, uint32_t server, uint32_t requested); int send_release(uint32_t server, uint32_t ciaddr); int get_raw_packet(struct dhcpMessage *payload, int fd); diff --git a/ndhc/ndhc.c b/ndhc/ndhc.c index 0f5caa1..0cc2cb9 100644 --- a/ndhc/ndhc.c +++ b/ndhc/ndhc.c @@ -47,6 +47,7 @@ #include "packet.h" #include "script.h" #include "socket.h" +#include "arpping.h" #include "log.h" #include "chroot.h" #include "cap.h" @@ -378,6 +379,21 @@ static void handle_packet(void) lease = RETRY_DELAY; } + if (!arpping(packet.yiaddr, NULL, 0, client_config.arp, + client_config.interface)) { + log_line("Offered address is in use: declining."); + send_decline(xid, server_addr, packet.yiaddr); + + if (state != REQUESTING) + run_script(NULL, SCRIPT_DECONFIG); + state = INIT_SELECTING; + requested_ip = 0; + timeout = now; + packet_num = 0; + change_listen_mode(LISTEN_RAW); + break; + } + /* enter bound state */ t1 = lease >> 1; diff --git a/ndhc/packet.c b/ndhc/packet.c index 17de8db..ed6ff3a 100644 --- a/ndhc/packet.c +++ b/ndhc/packet.c @@ -61,24 +61,6 @@ uint16_t checksum(void *addr, int count) return ~sum; } -/* returns -1 on error, >= 0 and equal to # chars written on success */ -static int safe_sendto(int fd, const char *buf, int len, int flags, - const struct sockaddr *dest_addr, socklen_t addrlen) -{ - int r, s = 0; - while (s < len) { - r = sendto(fd, buf + s, len - s, flags, dest_addr, addrlen); - if (r == -1) { - if (errno == EINTR) - continue; - else - return -1; - } - s += r; - } - return s; -} - /* Constuct a ip/udp header for a packet, and specify the source and dest * hardware address */ int raw_packet(struct dhcpMessage *payload, uint32_t source_ip,