#include <sys/types.h>
#include <sys/socket.h>
#include <sys/mbuf.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/if_ether.h>
#include <stdio.h>
#include <string.h>
#include "dhcp.h"
#include "tree.h"
#include "dhcpd.h"
#include "log.h"
u_int32_t
checksum(unsigned char *buf, u_int32_t nbytes, u_int32_t sum)
{
unsigned int i;
for (i = 0; i < (nbytes & ~1U); i += 2) {
sum += (u_int16_t)ntohs(*((u_int16_t *)(buf + i)));
if (sum > 0xFFFF)
sum -= 0xFFFF;
}
if (i < nbytes) {
sum += buf[i] << 8;
if (sum > 0xFFFF)
sum -= 0xFFFF;
}
return (sum);
}
u_int32_t
wrapsum(u_int32_t sum)
{
sum = ~sum & 0xFFFF;
return (htons(sum));
}
void
assemble_hw_header(struct interface_info *interface, unsigned char *buf,
int *bufix, struct hardware *to)
{
struct ether_header eh;
if (to != NULL && to->hlen == 6)
memcpy(eh.ether_dhost, to->haddr, sizeof(eh.ether_dhost));
else
memset(eh.ether_dhost, 0xff, sizeof(eh.ether_dhost));
memset(eh.ether_shost, 0x00, sizeof(eh.ether_shost));
eh.ether_type = htons(ETHERTYPE_IP);
memcpy(&buf[*bufix], &eh, ETHER_HDR_LEN);
*bufix += ETHER_HDR_LEN;
}
void
assemble_udp_ip_header(struct interface_info *interface, unsigned char *buf,
int *bufix, u_int32_t from, u_int32_t to, unsigned int port,
unsigned char *data, int len)
{
struct ip ip;
struct udphdr udp;
ip.ip_v = 4;
ip.ip_hl = 5;
ip.ip_tos = IPTOS_LOWDELAY;
ip.ip_len = htons(sizeof(ip) + sizeof(udp) + len);
ip.ip_id = 0;
ip.ip_off = 0;
ip.ip_ttl = 16;
ip.ip_p = IPPROTO_UDP;
ip.ip_sum = 0;
ip.ip_src.s_addr = from;
ip.ip_dst.s_addr = to;
ip.ip_sum = wrapsum(checksum((unsigned char *)&ip, sizeof(ip), 0));
memcpy(&buf[*bufix], &ip, sizeof(ip));
*bufix += sizeof(ip);
udp.uh_sport = server_port;
udp.uh_dport = port;
udp.uh_ulen = htons(sizeof(udp) + len);
memset(&udp.uh_sum, 0, sizeof(udp.uh_sum));
udp.uh_sum = wrapsum(checksum((unsigned char *)&udp, sizeof(udp),
checksum(data, len, checksum((unsigned char *)&ip.ip_src,
2 * sizeof(ip.ip_src),
IPPROTO_UDP + (u_int32_t)ntohs(udp.uh_ulen)))));
memcpy(&buf[*bufix], &udp, sizeof(udp));
*bufix += sizeof(udp);
}
ssize_t
decode_hw_header(unsigned char *buf, u_int32_t buflen, struct hardware *from)
{
struct ether_header eh;
if (buflen < sizeof(eh))
return (-1);
memcpy(&eh, buf, sizeof(eh));
memcpy(from->haddr, eh.ether_shost, sizeof(eh.ether_shost));
from->htype = ARPHRD_ETHER;
from->hlen = sizeof(eh.ether_shost);
return (sizeof(eh));
}
ssize_t
decode_udp_ip_header(unsigned char *buf, u_int32_t buflen,
struct sockaddr_in *from, u_int16_t csumflags)
{
struct ip *ip;
struct udphdr *udp;
unsigned char *data;
u_int32_t ip_len;
static unsigned int ip_packets_seen;
static unsigned int ip_packets_bad_checksum;
static unsigned int udp_packets_seen;
static unsigned int udp_packets_bad_checksum;
static unsigned int udp_packets_length_checked;
static unsigned int udp_packets_length_overflow;
int len;
if (sizeof(*ip) > buflen)
return (-1);
ip_len = (*buf & 0xf) << 2;
if (ip_len > buflen)
return (-1);
ip = (struct ip *)buf;
ip_packets_seen++;
if ((csumflags & M_IPV4_CSUM_IN_OK) == 0 &&
wrapsum(checksum((unsigned char *)ip, ip_len, 0)) != 0) {
ip_packets_bad_checksum++;
if (ip_packets_seen > 4 && ip_packets_bad_checksum != 0 &&
(ip_packets_seen / ip_packets_bad_checksum) < 2) {
log_info("%u bad IP checksums seen in %u packets",
ip_packets_bad_checksum, ip_packets_seen);
ip_packets_seen = ip_packets_bad_checksum = 0;
}
return (-1);
}
memcpy(&from->sin_addr, &ip->ip_src, sizeof(from->sin_addr));
#ifdef DEBUG
if (ntohs(ip->ip_len) != buflen)
log_debug("ip length %d disagrees with bytes received %d.",
ntohs(ip->ip_len), buflen);
#endif
if (ntohs(ip->ip_len) > buflen)
return (-1);
if (ip_len + sizeof(*udp) > buflen)
return (-1);
udp = (struct udphdr *)(buf + ip_len);
udp_packets_seen++;
if (ip_len + ntohs(udp->uh_ulen) > buflen)
return (-1);
data = buf + ip_len + sizeof(*udp);
udp_packets_length_checked++;
len = ntohs(udp->uh_ulen) - sizeof(*udp);
if ((len < 0) || (len + data > buf + buflen)) {
udp_packets_length_overflow++;
if (udp_packets_length_checked > 4 &&
udp_packets_length_overflow != 0 &&
(udp_packets_length_checked /
udp_packets_length_overflow) < 2) {
log_info("%u udp packets in %u too long - dropped",
udp_packets_length_overflow,
udp_packets_length_checked);
udp_packets_length_overflow =
udp_packets_length_checked = 0;
}
return (-1);
}
if (len + data != buf + buflen)
log_debug("accepting packet with data after udp payload.");
udp_packets_seen++;
if ((csumflags & M_UDP_CSUM_IN_OK) == 0 &&
udp->uh_sum != 0) {
udp->uh_sum = wrapsum(checksum((uint8_t *)udp, sizeof(*udp),
checksum(data, len,
checksum((uint8_t *)&ip->ip_src, 2 * sizeof(ip->ip_src),
IPPROTO_UDP + ntohs(udp->uh_ulen)))));
if (udp->uh_sum != 0) {
udp_packets_bad_checksum++;
if (udp_packets_seen > 4 &&
udp_packets_bad_checksum != 0 &&
(udp_packets_seen / udp_packets_bad_checksum) < 2) {
log_info("%u bad udp checksums in %u packets",
udp_packets_bad_checksum, udp_packets_seen);
udp_packets_seen = udp_packets_bad_checksum = 0;
}
return (-1);
}
}
memcpy(&from->sin_port, &udp->uh_sport, sizeof(udp->uh_sport));
return (ip_len + sizeof(*udp));
}