diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:22 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:22 -0800 |
commit | f7c5421560640d23fc10803b9d59a9ff1d83e467 (patch) | |
tree | d6ae69d0d3f4a4d760a3254ec326bca4a8afacfe /net.c | |
parent | d97c47cad830d00c9da685cc4ea157d6185f6c97 (diff) | |
download | android_external_dhcpcd-f7c5421560640d23fc10803b9d59a9ff1d83e467.tar.gz android_external_dhcpcd-f7c5421560640d23fc10803b9d59a9ff1d83e467.tar.bz2 android_external_dhcpcd-f7c5421560640d23fc10803b9d59a9ff1d83e467.zip |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'net.c')
-rw-r--r-- | net.c | 707 |
1 files changed, 707 insertions, 0 deletions
@@ -0,0 +1,707 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright 2006-2008 Roy Marples <roy@marples.name> + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/if_arp.h> +#include <arpa/inet.h> +#include <netinet/in_systm.h> +#ifdef __linux__ +#include <netinet/ether.h> +#include <netpacket/packet.h> +#endif +#include <netinet/in.h> +#include <netinet/ip.h> +#define __FAVOR_BSD /* Nasty glibc hack so we can use BSD semantics for UDP */ +#include <netinet/udp.h> +#undef __FAVOR_BSD +#ifdef SIOCGIFMEDIA +#include <net/if_media.h> +#endif +#include <arpa/inet.h> +#ifdef AF_LINK +# include <net/if_dl.h> +#endif + +#include <ctype.h> +#include <errno.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "common.h" +#include "dhcp.h" +#include "logger.h" +#include "net.h" +#include "signals.h" + +int +inet_ntocidr(struct in_addr address) +{ + int cidr = 0; + uint32_t mask = htonl(address.s_addr); + + while (mask) { + cidr++; + mask <<= 1; + } + + return cidr; +} + +int +inet_cidrtoaddr(int cidr, struct in_addr *addr) +{ + int ocets; + + if (cidr < 0 || cidr > 32) { + errno = EINVAL; + return -1; + } + ocets = (cidr + 7) / 8; + + addr->s_addr = 0; + if (ocets > 0) { + memset(&addr->s_addr, 255, (size_t)ocets - 1); + memset((unsigned char *)&addr->s_addr + (ocets - 1), + (256 - (1 << (32 - cidr) % 8)), 1); + } + + return 0; +} + +uint32_t +get_netmask(uint32_t addr) +{ + uint32_t dst; + + if (addr == 0) + return 0; + + dst = htonl(addr); + if (IN_CLASSA(dst)) + return ntohl(IN_CLASSA_NET); + if (IN_CLASSB (dst)) + return ntohl(IN_CLASSB_NET); + if (IN_CLASSC (dst)) + return ntohl(IN_CLASSC_NET); + + return 0; +} + +char * +hwaddr_ntoa(const unsigned char *hwaddr, size_t hwlen) +{ + static char buffer[(HWADDR_LEN * 3) + 1]; + char *p = buffer; + size_t i; + + for (i = 0; i < hwlen && i < HWADDR_LEN; i++) { + if (i > 0) + *p ++= ':'; + p += snprintf(p, 3, "%.2x", hwaddr[i]); + } + + *p ++= '\0'; + + return buffer; +} + +size_t +hwaddr_aton(unsigned char *buffer, const char *addr) +{ + char c[3]; + const char *p = addr; + unsigned char *bp = buffer; + size_t len = 0; + + c[2] = '\0'; + while (*p) { + c[0] = *p++; + c[1] = *p++; + /* Ensure that digits are hex */ + if (isxdigit((unsigned char)c[0]) == 0 || + isxdigit((unsigned char)c[1]) == 0) + { + errno = EINVAL; + return 0; + } + /* We should have at least two entries 00:01 */ + if (len == 0 && *p == '\0') { + errno = EINVAL; + return 0; + } + /* Ensure that next data is EOL or a seperator with data */ + if (!(*p == '\0' || (*p == ':' && *(p + 1) != '\0'))) { + errno = EINVAL; + return 0; + } + if (*p) + p++; + if (bp) + *bp++ = (unsigned char)strtol(c, NULL, 16); + len++; + } + return len; +} + +int +do_interface(const char *ifname, + _unused unsigned char *hwaddr, _unused size_t *hwlen, + struct in_addr *addr, struct in_addr *net, int get) +{ + int s; + struct ifconf ifc; + int retval = 0, found = 0; + int len = 10 * sizeof(struct ifreq); + int lastlen = 0; + char *p; + union { + char *buffer; + struct ifreq *ifr; + } ifreqs; + struct sockaddr_in address; + struct ifreq *ifr; + struct sockaddr_in netmask; +#ifdef AF_LINK + struct sockaddr_dl *sdl; +#endif + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + + /* Not all implementations return the needed buffer size for + * SIOGIFCONF so we loop like so for all until it works */ + memset(&ifc, 0, sizeof(ifc)); + for (;;) { + ifc.ifc_len = len; + ifc.ifc_buf = xmalloc((size_t)len); + if (ioctl(s, SIOCGIFCONF, &ifc) == -1) { + if (errno != EINVAL || lastlen != 0) { + close(s); + free(ifc.ifc_buf); + return -1; + } + } else { + if (ifc.ifc_len == lastlen) + break; + lastlen = ifc.ifc_len; + } + + free(ifc.ifc_buf); + ifc.ifc_buf = NULL; + len *= 2; + } + + for (p = (char *)ifc.ifc_buf; p < (char *)ifc.ifc_buf + ifc.ifc_len;) { + /* Cast the ifc buffer to an ifreq cleanly */ + ifreqs.buffer = p; + ifr = ifreqs.ifr; + +#ifndef __linux__ + if (ifr->ifr_addr.sa_len > sizeof(ifr->ifr_ifru)) + p += offsetof(struct ifreq, ifr_ifru) + + ifr->ifr_addr.sa_len; + else +#endif + p += sizeof(*ifr); + + if (strcmp(ifname, ifr->ifr_name) != 0) + continue; + + found = 1; + +#ifdef AF_LINK + if (hwaddr && hwlen && ifr->ifr_addr.sa_family == AF_LINK) { + sdl = xmalloc(ifr->ifr_addr.sa_len); + memcpy(sdl, &ifr->ifr_addr, ifr->ifr_addr.sa_len); + *hwlen = sdl->sdl_alen; + memcpy(hwaddr, LLADDR(sdl), *hwlen); + free(sdl); + retval = 1; + break; + } +#endif + + if (ifr->ifr_addr.sa_family == AF_INET) { + memcpy(&address, &ifr->ifr_addr, sizeof(address)); + if (ioctl(s, SIOCGIFNETMASK, ifr) == -1) + continue; + memcpy(&netmask, &ifr->ifr_addr, sizeof(netmask)); + if (get) { + addr->s_addr = address.sin_addr.s_addr; + net->s_addr = netmask.sin_addr.s_addr; + retval = 1; + break; + } else { + if (address.sin_addr.s_addr == addr->s_addr && + (!net || + netmask.sin_addr.s_addr == net->s_addr)) + { + retval = 1; + break; + } + } + } + + } + + if (!found) + errno = ENXIO; + close(s); + free(ifc.ifc_buf); + return retval; +} + +int +up_interface(const char *ifname) +{ + int s; + struct ifreq ifr; + int retval = -1; +#ifdef __linux__ + char *p; +#endif + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); +#ifdef __linux__ + /* We can only bring the real interface up */ + if ((p = strchr(ifr.ifr_name, ':'))) + *p = '\0'; +#endif + if (ioctl(s, SIOCGIFFLAGS, &ifr) == 0) { + if ((ifr.ifr_flags & IFF_UP)) + retval = 0; + else { + ifr.ifr_flags |= IFF_UP; + if (ioctl(s, SIOCSIFFLAGS, &ifr) == 0) + retval = 0; + } + } + close(s); + return retval; +} + +int +carrier_status(const char *ifname) +{ + int s; + struct ifreq ifr; + int retval = -1; +#ifdef SIOCGIFMEDIA + struct ifmediareq ifmr; +#endif +#ifdef __linux__ + char *p; +#endif + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); +#ifdef __linux__ + /* We can only test the real interface up */ + if ((p = strchr(ifr.ifr_name, ':'))) + *p = '\0'; +#endif + if ((retval = ioctl(s, SIOCGIFFLAGS, &ifr)) == 0) { + if (ifr.ifr_flags & IFF_UP && ifr.ifr_flags & IFF_RUNNING) + retval = 1; + else + retval = 0; + } + +#ifdef SIOCGIFMEDIA + if (retval == 1) { + memset(&ifmr, 0, sizeof(ifmr)); + strncpy(ifmr.ifm_name, ifr.ifr_name, sizeof(ifmr.ifm_name)); + if (ioctl(s, SIOCGIFMEDIA, &ifmr) != -1 && + ifmr.ifm_status & IFM_AVALID) + { + if (!(ifmr.ifm_status & IFM_ACTIVE)) + retval = 0; + } + } +#endif + close(s); + return retval; +} + +struct interface * +read_interface(const char *ifname, _unused int metric) +{ + int s; + struct ifreq ifr; + struct interface *iface = NULL; + unsigned char *hwaddr = NULL; + size_t hwlen = 0; + sa_family_t family = 0; + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return NULL; + +#ifdef __linux__ + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFHWADDR, &ifr) == -1) + goto eexit; + + switch (ifr.ifr_hwaddr.sa_family) { + case ARPHRD_ETHER: + case ARPHRD_IEEE802: + hwlen = ETHER_ADDR_LEN; + break; + case ARPHRD_IEEE1394: + hwlen = EUI64_ADDR_LEN; + case ARPHRD_INFINIBAND: + hwlen = INFINIBAND_ADDR_LEN; + break; + } + + hwaddr = xmalloc(sizeof(unsigned char) * HWADDR_LEN); + memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, hwlen); + family = ifr.ifr_hwaddr.sa_family; +#else + ifr.ifr_metric = metric; + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCSIFMETRIC, &ifr) == -1) + goto eexit; + + hwaddr = xmalloc(sizeof(unsigned char) * HWADDR_LEN); + if (do_interface(ifname, hwaddr, &hwlen, NULL, NULL, 0) != 1) + goto eexit; + + family = ARPHRD_ETHER; +#endif + + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFMTU, &ifr) == -1) + goto eexit; + + /* Ensure that the MTU is big enough for DHCP */ + if (ifr.ifr_mtu < MTU_MIN) { + ifr.ifr_mtu = MTU_MIN; + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCSIFMTU, &ifr) == -1) + goto eexit; + } + + if (up_interface(ifname) != 0) + goto eexit; + + iface = xzalloc(sizeof(*iface)); + strlcpy(iface->name, ifname, IF_NAMESIZE); + snprintf(iface->leasefile, PATH_MAX, LEASEFILE, ifname); + memcpy(&iface->hwaddr, hwaddr, hwlen); + iface->hwlen = hwlen; + + iface->family = family; + iface->arpable = !(ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK)); + + /* 0 is a valid fd, so init to -1 */ + iface->raw_fd = -1; + iface->udp_fd = -1; + iface->arp_fd = -1; + iface->link_fd = -1; + +eexit: + close(s); + free(hwaddr); + return iface; +} + +int +do_mtu(const char *ifname, short int mtu) +{ + struct ifreq ifr; + int r; + int s; + + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ifr.ifr_mtu = mtu; + r = ioctl(s, mtu ? SIOCSIFMTU : SIOCGIFMTU, &ifr); + close(s); + if (r == -1) + return -1; + return ifr.ifr_mtu; +} + +void +free_routes(struct rt *routes) +{ + struct rt *r; + + while (routes) { + r = routes->next; + free(routes); + routes = r; + } +} + +int +open_udp_socket(struct interface *iface) +{ + int s; + union sockunion { + struct sockaddr sa; + struct sockaddr_in sin; + } su; + int n; +#ifdef SO_BINDTODEVICE + struct ifreq ifr; +#endif + + if ((s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) + return -1; + + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)) == -1) + goto eexit; +#ifdef SO_BINDTODEVICE + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, iface->name, sizeof(ifr.ifr_name)); + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(ifr)) == -1) + goto eexit; +#endif + /* As we don't use this socket for receiving, set the + * receive buffer to 1 */ + n = 1; + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)) == -1) + goto eexit; + memset(&su, 0, sizeof(su)); + su.sin.sin_family = AF_INET; + su.sin.sin_port = htons(DHCP_CLIENT_PORT); + su.sin.sin_addr.s_addr = iface->addr.s_addr; + if (bind(s, &su.sa, sizeof(su)) == -1) + goto eexit; + + iface->udp_fd = s; + set_cloexec(s); + return 0; + +eexit: + close(s); + return -1; +} + +ssize_t +send_packet(const struct interface *iface, struct in_addr to, + const uint8_t *data, ssize_t len) +{ + union sockunion { + struct sockaddr sa; + struct sockaddr_in sin; + } su; + + memset(&su, 0, sizeof(su)); + su.sin.sin_family = AF_INET; + su.sin.sin_addr.s_addr = to.s_addr; + su.sin.sin_port = htons(DHCP_SERVER_PORT); + + return sendto(iface->udp_fd, data, len, 0, &su.sa, sizeof(su)); +} + +struct udp_dhcp_packet +{ + struct ip ip; + struct udphdr udp; + struct dhcp_message dhcp; +}; +const size_t udp_dhcp_len = sizeof(struct udp_dhcp_packet); + +static uint16_t +checksum(const void *data, uint16_t len) +{ + const uint8_t *addr = data; + uint32_t sum = 0; + + while (len > 1) { + sum += addr[0] * 256 + addr[1]; + addr += 2; + len -= 2; + } + + if (len == 1) + sum += *addr * 256; + + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + + sum = htons(sum); + + return ~sum; +} + +ssize_t +make_udp_packet(uint8_t **packet, const uint8_t *data, size_t length, + struct in_addr source, struct in_addr dest) +{ + struct udp_dhcp_packet *udpp; + struct ip *ip; + struct udphdr *udp; + + udpp = xzalloc(sizeof(*udpp)); + ip = &udpp->ip; + udp = &udpp->udp; + + /* OK, this is important :) + * We copy the data to our packet and then create a small part of the + * ip structure and an invalid ip_len (basically udp length). + * We then fill the udp structure and put the checksum + * of the whole packet into the udp checksum. + * Finally we complete the ip structure and ip checksum. + * If we don't do the ordering like so then the udp checksum will be + * broken, so find another way of doing it! */ + + memcpy(&udpp->dhcp, data, length); + + ip->ip_p = IPPROTO_UDP; + ip->ip_src.s_addr = source.s_addr; + if (dest.s_addr == 0) + ip->ip_dst.s_addr = INADDR_BROADCAST; + else + ip->ip_dst.s_addr = dest.s_addr; + + udp->uh_sport = htons(DHCP_CLIENT_PORT); + udp->uh_dport = htons(DHCP_SERVER_PORT); + udp->uh_ulen = htons(sizeof(*udp) + length); + ip->ip_len = udp->uh_ulen; + udp->uh_sum = checksum(udpp, sizeof(*udpp)); + + ip->ip_v = IPVERSION; + ip->ip_hl = 5; + ip->ip_id = 0; + ip->ip_tos = IPTOS_LOWDELAY; + ip->ip_len = htons (sizeof(*ip) + sizeof(*udp) + length); + ip->ip_id = 0; + ip->ip_off = htons(IP_DF); /* Don't fragment */ + ip->ip_ttl = IPDEFTTL; + + ip->ip_sum = checksum(ip, sizeof(*ip)); + + *packet = (uint8_t *)udpp; + return sizeof(*ip) + sizeof(*udp) + length; +} + +ssize_t +get_udp_data(const uint8_t **data, const uint8_t *udp) +{ + struct udp_dhcp_packet packet; + + memcpy(&packet, udp, sizeof(packet)); + *data = udp + offsetof(struct udp_dhcp_packet, dhcp); + return ntohs(packet.ip.ip_len) - sizeof(packet.ip) - sizeof(packet.udp); +} + +int +valid_udp_packet(const uint8_t *data) +{ + struct udp_dhcp_packet packet; + uint16_t bytes; + uint16_t ipsum; + uint16_t iplen; + uint16_t udpsum; + struct in_addr source; + struct in_addr dest; + int retval = 0; + + memcpy(&packet, data, sizeof(packet)); + bytes = ntohs(packet.ip.ip_len); + ipsum = packet.ip.ip_sum; + iplen = packet.ip.ip_len; + udpsum = packet.udp.uh_sum; + + if (0 != checksum(&packet.ip, sizeof(packet.ip))) { + errno = EINVAL; + return -1; + } + + packet.ip.ip_sum = 0; + memcpy(&source, &packet.ip.ip_src, sizeof(packet.ip.ip_src)); + memcpy(&dest, &packet.ip.ip_dst, sizeof(packet.ip.ip_dst)); + memset(&packet.ip, 0, sizeof(packet.ip)); + packet.udp.uh_sum = 0; + + packet.ip.ip_p = IPPROTO_UDP; + memcpy(&packet.ip.ip_src, &source, sizeof(packet.ip.ip_src)); + memcpy(&packet.ip.ip_dst, &dest, sizeof(packet.ip.ip_dst)); + packet.ip.ip_len = packet.udp.uh_ulen; + if (udpsum && udpsum != checksum(&packet, bytes)) { + errno = EINVAL; + retval = -1; + } + + return retval; +} + +int +send_arp(const struct interface *iface, int op, in_addr_t sip, in_addr_t tip) +{ + struct arphdr *arp; + size_t arpsize; + uint8_t *p; + int retval; + + arpsize = sizeof(*arp) + 2 * iface->hwlen + 2 * sizeof(sip); + arp = xmalloc(arpsize); + arp->ar_hrd = htons(iface->family); + arp->ar_pro = htons(ETHERTYPE_IP); + arp->ar_hln = iface->hwlen; + arp->ar_pln = sizeof(sip); + arp->ar_op = htons(op); + p = (uint8_t *)arp; + p += sizeof(*arp); + memcpy(p, iface->hwaddr, iface->hwlen); + p += iface->hwlen; + memcpy(p, &sip, sizeof(sip)); + p += sizeof(sip); + /* ARP requests should ignore this */ + retval = iface->hwlen; + while (retval--) + *p++ = '\0'; + memcpy(p, &tip, sizeof(tip)); + p += sizeof(tip); + retval = send_raw_packet(iface, ETHERTYPE_ARP, arp, arpsize); + free(arp); + return retval; +} |