blob: b764f3446bb04273d664a1c9b86695cb0e12671a [file] [log] [blame]
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>
#include <poll.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <time.h>
#include "mbtk_log.h"
#include "mbtk_type.h"
#include "mbtk_ifc.h"
#include "mbtk_dhcp.h"
#define VERBOSE 2
#define STATE_SELECTING 1
#define STATE_REQUESTING 2
#define TIMEOUT_INITIAL 4000
#define TIMEOUT_MAX 32000
static int verbose = 1;
// static char errmsg[2048];
static void *init_dhcp_msg(dhcp_msg *msg, int type, void *hwaddr, uint32_t xid)
{
uint8_t *x;
memset(msg, 0, sizeof(dhcp_msg));
msg->op = OP_BOOTREQUEST;
msg->htype = HTYPE_ETHER;
msg->hlen = 6;
msg->hops = 0;
msg->flags = htons(FLAGS_BROADCAST);
msg->xid = xid;
memcpy(msg->chaddr, hwaddr, 6);
x = msg->options;
*x++ = OPT_COOKIE1;
*x++ = OPT_COOKIE2;
*x++ = OPT_COOKIE3;
*x++ = OPT_COOKIE4;
*x++ = OPT_MESSAGE_TYPE;
*x++ = 1;
*x++ = type;
return x;
}
static int init_dhcp_discover_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid)
{
uint8_t *x;
x = init_dhcp_msg(msg, DHCPDISCOVER, hwaddr, xid);
*x++ = OPT_PARAMETER_LIST;
*x++ = 4;
*x++ = OPT_SUBNET_MASK;
*x++ = OPT_GATEWAY;
*x++ = OPT_DNS;
*x++ = OPT_BROADCAST_ADDR;
*x++ = OPT_END;
return DHCP_MSG_FIXED_SIZE + (x - msg->options);
}
static int init_dhcp_request_msg(dhcp_msg *msg, void *hwaddr, uint32_t xid,
uint32_t ipaddr, uint32_t serveraddr)
{
uint8_t *x;
x = init_dhcp_msg(msg, DHCPREQUEST, hwaddr, xid);
*x++ = OPT_PARAMETER_LIST;
*x++ = 4;
*x++ = OPT_SUBNET_MASK;
*x++ = OPT_GATEWAY;
*x++ = OPT_DNS;
*x++ = OPT_BROADCAST_ADDR;
*x++ = OPT_REQUESTED_IP;
*x++ = 4;
memcpy(x, &ipaddr, 4);
x += 4;
*x++ = OPT_SERVER_ID;
*x++ = 4;
memcpy(x, &serveraddr, 4);
x += 4;
*x++ = OPT_END;
return DHCP_MSG_FIXED_SIZE + (x - msg->options);
}
static msecs_t get_msecs(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
{
return 0;
}
else
{
return (((msecs_t) ts.tv_sec) * ((msecs_t) 1000)) +
(((msecs_t) ts.tv_nsec) / ((msecs_t) 1000000));
}
}
static int open_raw_socket(const char *ifname __attribute__((unused)), uint8_t *hwaddr, int if_index)
{
int s;
struct sockaddr_ll bindaddr;
if((s = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0)
{
LOGE("socket(PF_PACKET)");
return -1;
}
memset(&bindaddr, 0, sizeof(bindaddr));
bindaddr.sll_family = AF_PACKET;
bindaddr.sll_protocol = htons(ETH_P_IP);
bindaddr.sll_halen = ETH_ALEN;
memcpy(bindaddr.sll_addr, hwaddr, ETH_ALEN);
bindaddr.sll_ifindex = if_index;
if (bind(s, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0)
{
LOGE("Cannot bind raw socket to interface");
return -1;
}
return s;
}
static uint32_t checksum(void *buffer, unsigned int count, uint32_t startsum)
{
uint16_t *up = (uint16_t *)buffer;
uint32_t sum = startsum;
uint32_t upper16;
while (count > 1)
{
sum += *up++;
count -= 2;
}
if (count > 0)
{
sum += (uint16_t) *(uint8_t *)up;
}
while ((upper16 = (sum >> 16)) != 0)
{
sum = (sum & 0xffff) + upper16;
}
return sum;
}
static uint32_t finish_sum(uint32_t sum)
{
return ~sum & 0xffff;
}
static int send_packet(int s, int if_index, dhcp_msg *msg, int size,
uint32_t saddr, uint32_t daddr, uint32_t sport, uint32_t dport)
{
struct iphdr ip;
struct udphdr udp;
struct iovec iov[3];
uint32_t udpsum;
uint16_t temp;
struct msghdr msghdr;
struct sockaddr_ll destaddr;
ip.version = IPVERSION;
ip.ihl = sizeof(ip) >> 2;
ip.tos = 0;
ip.tot_len = htons(sizeof(ip) + sizeof(udp) + size);
ip.id = 0;
ip.frag_off = 0;
ip.ttl = IPDEFTTL;
ip.protocol = IPPROTO_UDP;
ip.check = 0;
ip.saddr = saddr;
ip.daddr = daddr;
ip.check = finish_sum(checksum(&ip, sizeof(ip), 0));
udp.source = htons(sport);
udp.dest = htons(dport);
udp.len = htons(sizeof(udp) + size);
udp.check = 0;
/* Calculate checksum for pseudo header */
udpsum = checksum(&ip.saddr, sizeof(ip.saddr), 0);
udpsum = checksum(&ip.daddr, sizeof(ip.daddr), udpsum);
temp = htons(IPPROTO_UDP);
udpsum = checksum(&temp, sizeof(temp), udpsum);
temp = udp.len;
udpsum = checksum(&temp, sizeof(temp), udpsum);
/* Add in the checksum for the udp header */
udpsum = checksum(&udp, sizeof(udp), udpsum);
/* Add in the checksum for the data */
udpsum = checksum(msg, size, udpsum);
udp.check = finish_sum(udpsum);
iov[0].iov_base = (char *)&ip;
iov[0].iov_len = sizeof(ip);
iov[1].iov_base = (char *)&udp;
iov[1].iov_len = sizeof(udp);
iov[2].iov_base = (char *)msg;
iov[2].iov_len = size;
memset(&destaddr, 0, sizeof(destaddr));
destaddr.sll_family = AF_PACKET;
destaddr.sll_protocol = htons(ETH_P_IP);
destaddr.sll_ifindex = if_index;
destaddr.sll_halen = ETH_ALEN;
memcpy(destaddr.sll_addr, "\xff\xff\xff\xff\xff\xff", ETH_ALEN);
msghdr.msg_name = &destaddr;
msghdr.msg_namelen = sizeof(destaddr);
msghdr.msg_iov = iov;
msghdr.msg_iovlen = sizeof(iov) / sizeof(struct iovec);
msghdr.msg_flags = 0;
msghdr.msg_control = 0;
msghdr.msg_controllen = 0;
return sendmsg(s, &msghdr, 0);
}
static int receive_packet(int s, dhcp_msg *msg)
{
int nread;
int is_valid;
struct dhcp_packet
{
struct iphdr ip;
struct udphdr udp;
dhcp_msg dhcp;
} packet;
int dhcp_size;
uint32_t sum;
uint16_t temp;
uint32_t saddr, daddr;
nread = read(s, &packet, sizeof(packet));
if (nread < 0)
{
return -1;
}
/*
* The raw packet interface gives us all packets received by the
* network interface. We need to filter out all packets that are
* not meant for us.
*/
is_valid = 0;
if (nread < (int)(sizeof(struct iphdr) + sizeof(struct udphdr)))
{
#if VERBOSE
LOGD("Packet is too small (%d) to be a UDP datagram", nread);
#endif
}
else if (packet.ip.version != IPVERSION || packet.ip.ihl != (sizeof(packet.ip) >> 2))
{
#if VERBOSE
LOGD("Not a valid IP packet");
#endif
}
else if (nread < ntohs(packet.ip.tot_len))
{
#if VERBOSE
LOGD("Packet was truncated (read %d, needed %d)", nread, ntohs(packet.ip.tot_len));
#endif
}
else if (packet.ip.protocol != IPPROTO_UDP)
{
#if VERBOSE
LOGD("IP protocol (%d) is not UDP", packet.ip.protocol);
#endif
}
else if (packet.udp.dest != htons(PORT_BOOTP_CLIENT))
{
#if VERBOSE
LOGD("UDP dest port (%d) is not DHCP client", ntohs(packet.udp.dest));
#endif
}
else
{
is_valid = 1;
}
if (!is_valid)
{
return -1;
}
/* Seems like it's probably a valid DHCP packet */
/* validate IP header checksum */
sum = finish_sum(checksum(&packet.ip, sizeof(packet.ip), 0));
if (sum != 0)
{
LOGW("IP header checksum failure (0x%x)", packet.ip.check);
return -1;
}
/*
* Validate the UDP checksum.
* Since we don't need the IP header anymore, we "borrow" it
* to construct the pseudo header used in the checksum calculation.
*/
dhcp_size = ntohs(packet.udp.len) - sizeof(packet.udp);
/*
* check validity of dhcp_size.
* 1) cannot be negative or zero.
* 2) src buffer contains enough bytes to copy
* 3) cannot exceed destination buffer
*/
if ((dhcp_size <= 0) ||
((int)(nread - sizeof(struct iphdr) - sizeof(struct udphdr)) < dhcp_size) ||
((int)sizeof(dhcp_msg) < dhcp_size))
{
#if VERBOSE
LOGD("Malformed Packet");
#endif
return -1;
}
saddr = packet.ip.saddr;
daddr = packet.ip.daddr;
nread = ntohs(packet.ip.tot_len);
memset(&packet.ip, 0, sizeof(packet.ip));
packet.ip.saddr = saddr;
packet.ip.daddr = daddr;
packet.ip.protocol = IPPROTO_UDP;
packet.ip.tot_len = packet.udp.len;
temp = packet.udp.check;
packet.udp.check = 0;
sum = finish_sum(checksum(&packet, nread, 0));
packet.udp.check = temp;
if (!sum)
sum = finish_sum(sum);
if (temp != sum)
{
LOGW("UDP header checksum failure (0x%x should be 0x%x)", sum, temp);
return -1;
}
memcpy(msg, &packet.dhcp, dhcp_size);
return dhcp_size;
}
static void hex2str(char *buf, size_t buf_size, const unsigned char *array, int len)
{
int i;
char *cp = buf;
char *buf_end = buf + buf_size;
for (i = 0; i < len; i++)
{
cp += snprintf(cp, buf_end - cp, " %02x ", array[i]);
}
}
static const char *ipaddr(in_addr_t addr)
{
struct in_addr in_addr;
in_addr.s_addr = addr;
return inet_ntoa(in_addr);
}
static const char *dhcp_type_to_name(uint32_t type)
{
switch(type)
{
case DHCPDISCOVER:
return "discover";
case DHCPOFFER:
return "offer";
case DHCPREQUEST:
return "request";
case DHCPDECLINE:
return "decline";
case DHCPACK:
return "ack";
case DHCPNAK:
return "nak";
case DHCPRELEASE:
return "release";
case DHCPINFORM:
return "inform";
default:
return "???";
}
}
static void dump_dhcp_msg(dhcp_msg *msg, int len)
{
unsigned char *x;
unsigned int n,c;
int optsz;
const char *name;
char buf[2048];
LOGD("===== DHCP message:");
if (len < DHCP_MSG_FIXED_SIZE)
{
LOGD("Invalid length %d, should be %d", len, DHCP_MSG_FIXED_SIZE);
return;
}
len -= DHCP_MSG_FIXED_SIZE;
if (msg->op == OP_BOOTREQUEST)
name = "BOOTREQUEST";
else if (msg->op == OP_BOOTREPLY)
name = "BOOTREPLY";
else
name = "????";
LOGD("op = %s (%d), htype = %d, hlen = %d, hops = %d",
name, msg->op, msg->htype, msg->hlen, msg->hops);
LOGD("xid = 0x%08x secs = %d, flags = 0x%04x optlen = %d",
ntohl(msg->xid), ntohs(msg->secs), ntohs(msg->flags), len);
LOGD("ciaddr = %s", ipaddr(msg->ciaddr));
LOGD("yiaddr = %s", ipaddr(msg->yiaddr));
LOGD("siaddr = %s", ipaddr(msg->siaddr));
LOGD("giaddr = %s", ipaddr(msg->giaddr));
c = msg->hlen > 16 ? 16 : msg->hlen;
hex2str(buf, sizeof(buf), msg->chaddr, c);
LOGD("chaddr = {%s}", buf);
for (n = 0; n < 64; n++)
{
unsigned char x = msg->sname[n];
if ((x < ' ') || (x > 127))
{
if (x == 0) break;
msg->sname[n] = '.';
}
}
msg->sname[63] = 0;
for (n = 0; n < 128; n++)
{
unsigned char x = msg->file[n];
if ((x < ' ') || (x > 127))
{
if (x == 0) break;
msg->file[n] = '.';
}
}
msg->file[127] = 0;
LOGD("sname = '%s'", msg->sname);
LOGD("file = '%s'", msg->file);
if (len < 4) return;
len -= 4;
x = msg->options + 4;
while (len > 2)
{
if (*x == 0)
{
x++;
len--;
continue;
}
if (*x == OPT_END)
{
break;
}
len -= 2;
optsz = x[1];
if (optsz > len) break;
if (x[0] == OPT_DOMAIN_NAME || x[0] == OPT_MESSAGE)
{
if ((unsigned int)optsz < sizeof(buf) - 1)
{
n = optsz;
}
else
{
n = sizeof(buf) - 1;
}
memcpy(buf, &x[2], n);
buf[n] = '\0';
}
else
{
hex2str(buf, sizeof(buf), &x[2], optsz);
}
if (x[0] == OPT_MESSAGE_TYPE)
name = dhcp_type_to_name(x[2]);
else
name = NULL;
LOGD("op %d len %d {%s} %s", x[0], optsz, buf, name == NULL ? "" : name);
len -= optsz;
x = x + optsz + 2;
}
}
static int send_message(int sock, int if_index, dhcp_msg *msg, int size)
{
#if VERBOSE > 1
dump_dhcp_msg(msg, size);
#endif
return send_packet(sock, if_index, msg, size, INADDR_ANY, INADDR_BROADCAST,
PORT_BOOTP_CLIENT, PORT_BOOTP_SERVER);
}
// static dhcp_info last_good_info;
static int dhcp_configure(const char *ifname, dhcp_info *info)
{
//last_good_info = *info;
return mbtk_ifc_configure1(ifname, info->ipaddr, info->prefixLength, info->gateway, 0);
}
static int is_valid_reply(dhcp_msg *msg, dhcp_msg *reply, int sz)
{
if (sz < DHCP_MSG_FIXED_SIZE)
{
if (verbose) LOGD("Wrong size %d != %d\n", sz, DHCP_MSG_FIXED_SIZE);
return 0;
}
if (reply->op != OP_BOOTREPLY)
{
if (verbose) LOGD("Wrong Op %d != %d\n", reply->op, OP_BOOTREPLY);
return 0;
}
if (reply->xid != msg->xid)
{
if (verbose) LOGD("Wrong Xid 0x%x != 0x%x\n", ntohl(reply->xid),
ntohl(msg->xid));
return 0;
}
if (reply->htype != msg->htype)
{
if (verbose) LOGD("Wrong Htype %d != %d\n", reply->htype, msg->htype);
return 0;
}
if (reply->hlen != msg->hlen)
{
if (verbose) LOGD("Wrong Hlen %d != %d\n", reply->hlen, msg->hlen);
return 0;
}
if (memcmp(msg->chaddr, reply->chaddr, msg->hlen))
{
if (verbose) LOGD("Wrong chaddr %x != %x\n", *(reply->chaddr),*(msg->chaddr));
return 0;
}
return 1;
}
int ipv4NetmaskToPrefixLength(in_addr_t mask)
{
int prefixLength = 0;
uint32_t m = (uint32_t)ntohl(mask);
while (m & 0x80000000)
{
prefixLength++;
m = m << 1;
}
return prefixLength;
}
static int decode_dhcp_msg(dhcp_msg *msg, int len, dhcp_info *info)
{
uint8_t *x;
unsigned int opt;
int optlen;
memset(info, 0, sizeof(dhcp_info));
if (len < (DHCP_MSG_FIXED_SIZE + 4)) return -1;
len -= (DHCP_MSG_FIXED_SIZE + 4);
if (msg->options[0] != OPT_COOKIE1) return -1;
if (msg->options[1] != OPT_COOKIE2) return -1;
if (msg->options[2] != OPT_COOKIE3) return -1;
if (msg->options[3] != OPT_COOKIE4) return -1;
x = msg->options + 4;
while (len > 2)
{
opt = *x++;
if (opt == OPT_PAD)
{
len--;
continue;
}
if (opt == OPT_END)
{
break;
}
optlen = *x++;
len -= 2;
if (optlen > len)
{
break;
}
switch(opt)
{
case OPT_SUBNET_MASK:
if (optlen >= 4)
{
in_addr_t mask;
memcpy(&mask, x, 4);
info->prefixLength = ipv4NetmaskToPrefixLength(mask);
}
break;
case OPT_GATEWAY:
if (optlen >= 4) memcpy(&info->gateway, x, 4);
break;
case OPT_DNS:
if (optlen >= 4) memcpy(&info->dns1, x + 0, 4);
if (optlen >= 8) memcpy(&info->dns2, x + 4, 4);
break;
case OPT_LEASE_TIME:
if (optlen >= 4)
{
memcpy(&info->lease, x, 4);
info->lease = ntohl(info->lease);
}
break;
case OPT_SERVER_ID:
if (optlen >= 4) memcpy(&info->serveraddr, x, 4);
break;
case OPT_MESSAGE_TYPE:
info->type = *x;
break;
default:
break;
}
x += optlen;
len -= optlen;
}
info->ipaddr = msg->yiaddr;
return 0;
}
void dump_dhcp_info(dhcp_info *info)
{
char addr[20], gway[20];
LOGD("--- dhcp %s (%d) ---",
dhcp_type_to_name(info->type), info->type);
strcpy(addr, ipaddr(info->ipaddr));
strcpy(gway, ipaddr(info->gateway));
LOGD("ip %s gw %s prefixLength %d", addr, gway, info->prefixLength);
if (info->dns1) LOGD("dns1: %s", ipaddr(info->dns1));
if (info->dns2) LOGD("dns2: %s", ipaddr(info->dns2));
LOGD("server %s, lease %d seconds",
ipaddr(info->serveraddr), info->lease);
}
static int dhcp_init_ifc(const char *ifname)
{
dhcp_msg discover_msg;
dhcp_msg request_msg;
dhcp_msg reply;
dhcp_msg *msg;
dhcp_info info;
int s, r, size;
int valid_reply;
uint32_t xid;
unsigned char hwaddr[6];
struct pollfd pfd;
unsigned int state;
unsigned int timeout;
int if_index;
xid = (uint32_t) get_msecs();
if (mbtk_ifc_get_hwaddr(ifname, hwaddr))
{
LOGE("cannot obtain interface address");
return -1;
}
if (mbtk_ifc_get_ifindex(ifname, &if_index))
{
LOGE("cannot obtain interface index");
return -1;
}
s = open_raw_socket(ifname, hwaddr, if_index);
timeout = TIMEOUT_INITIAL;
state = STATE_SELECTING;
info.type = 0;
goto transmit;
for (;;)
{
pfd.fd = s;
pfd.events = POLLIN;
pfd.revents = 0;
r = poll(&pfd, 1, timeout);
if (r == 0)
{
#if VERBOSE
LOGE("TIMEOUT");
#endif
if (timeout >= TIMEOUT_MAX)
{
LOGE("timed out");
if ( info.type == DHCPOFFER )
{
LOGE("no acknowledgement from DHCP server\nconfiguring %s with offered parameters", ifname);
return dhcp_configure(ifname, &info);
}
errno = ETIME;
close(s);
return -1;
}
timeout = timeout * 2;
transmit:
size = 0;
msg = NULL;
switch(state)
{
case STATE_SELECTING:
msg = &discover_msg;
size = init_dhcp_discover_msg(msg, hwaddr, xid);
break;
case STATE_REQUESTING:
msg = &request_msg;
size = init_dhcp_request_msg(msg, hwaddr, xid, info.ipaddr, info.serveraddr);
break;
default:
r = 0;
}
if (size != 0)
{
r = send_message(s, if_index, msg, size);
if (r < 0)
{
LOGE("error sending dhcp msg: %s\n", strerror(errno));
}
}
continue;
}
if (r < 0)
{
if ((errno == EAGAIN) || (errno == EINTR))
{
continue;
}
LOGE("poll failed");
return -1;
}
errno = 0;
r = receive_packet(s, &reply);
if (r < 0)
{
if (errno != 0)
{
LOGD("receive_packet failed (%d): %s", r, strerror(errno));
if (errno == ENETDOWN || errno == ENXIO)
{
return -1;
}
}
continue;
}
#if VERBOSE > 1
dump_dhcp_msg(&reply, r);
#endif
decode_dhcp_msg(&reply, r, &info);
if (state == STATE_SELECTING)
{
valid_reply = is_valid_reply(&discover_msg, &reply, r);
}
else
{
valid_reply = is_valid_reply(&request_msg, &reply, r);
}
if (!valid_reply)
{
LOGE("invalid reply");
continue;
}
if (verbose)
dump_dhcp_info(&info);
switch(state)
{
case STATE_SELECTING:
if (info.type == DHCPOFFER)
{
state = STATE_REQUESTING;
timeout = TIMEOUT_INITIAL;
xid++;
goto transmit;
}
break;
case STATE_REQUESTING:
if (info.type == DHCPACK)
{
LOGE("configuring %s", ifname);
close(s);
return dhcp_configure(ifname, &info);
}
else if (info.type == DHCPNAK)
{
LOGE("configuration request denied");
close(s);
return -1;
}
else
{
LOGE("ignoring %s message in state %d",
dhcp_type_to_name(info.type), state);
}
break;
}
}
close(s);
return 0;
}
int mbtk_do_dhcp(const char *name)
{
int ret = 0;
if(mbtk_ifc_open())
{
LOGE("mbtk_ifc_open() fail.");
ret = -1;
goto return_result;
}
if (mbtk_ifc_set_addr(name, 0, 0))
{
LOGE("failed to set ip addr for %s to 0.0.0.0: %s", name, strerror(errno));
ret = -1;
goto return_result;
}
if (mbtk_ifc_up(name))
{
LOGE("failed to bring up interface %s: %s", name, strerror(errno));
ret = -1;
goto return_result;
}
if(dhcp_init_ifc(name))
{
LOGE("dhcp_init_ifc() fail.");
ret = -1;
goto return_result;
}
return_result:
mbtk_ifc_close();
return ret;
}