/*
 *  TAP-Windows -- A kernel driver to provide virtual tap
 *                 device functionality on Windows.
 *
 *  This code was inspired by the CIPE-Win32 driver by Damion K. Wilson.
 *
 *  This source code is Copyright (C) 2002-2014 OpenVPN Technologies, Inc.,
 *  and is released under the GPL version 2 (see below).
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2
 *  as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program (see the file COPYING included with this
 *  distribution); if not, write to the Free Software Foundation, Inc.,
 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "tap.h"

//=========================
// Code to set DHCP options
//=========================

VOID SetDHCPOpt(__in DHCPMsg *m, __in void *data, __in unsigned int len) {
  if (!m->overflow) {
    if (m->optlen + len <= DHCP_OPTIONS_BUFFER_SIZE) {
      if (len) {
        NdisMoveMemory(m->msg.options + m->optlen, data, len);
        m->optlen += len;
      }
    } else {
      m->overflow = TRUE;
    }
  }
}

VOID SetDHCPOpt0(__in DHCPMsg *msg, __in int type) {
  DHCPOPT0 opt;
  opt.type = (UCHAR)type;
  SetDHCPOpt(msg, &opt, sizeof(opt));
}

VOID SetDHCPOpt8(__in DHCPMsg *msg, __in int type, __in ULONG data) {
  DHCPOPT8 opt;
  opt.type = (UCHAR)type;
  opt.len = sizeof(opt.data);
  opt.data = (UCHAR)data;
  SetDHCPOpt(msg, &opt, sizeof(opt));
}

VOID SetDHCPOpt32(__in DHCPMsg *msg, __in int type, __in ULONG data) {
  DHCPOPT32 opt;
  opt.type = (UCHAR)type;
  opt.len = sizeof(opt.data);
  opt.data = data;
  SetDHCPOpt(msg, &opt, sizeof(opt));
}

//==============
// Checksum code
//==============

USHORT
ip_checksum(__in const UCHAR *buf, __in const int len_ip_header) {
  USHORT word16;
  ULONG sum = 0;
  int i;

  // make 16 bit words out of every two adjacent 8 bit words in the packet
  // and add them up
  for (i = 0; i < len_ip_header - 1; i += 2) {
    word16 = ((buf[i] << 8) & 0xFF00) + (buf[i + 1] & 0xFF);
    sum += (ULONG)word16;
  }

  // take only 16 bits out of the 32 bit sum and add up the carries
  while (sum >> 16) {
    sum = (sum & 0xFFFF) + (sum >> 16);
  }

  // one's complement the result
  return ((USHORT)~sum);
}

USHORT
udp_checksum(__in const UCHAR *buf, __in const int len_udp, __in const UCHAR *src_addr,
             __in const UCHAR *dest_addr) {
  USHORT word16;
  ULONG sum = 0;
  int i;

  // make 16 bit words out of every two adjacent 8 bit words and
  // calculate the sum of all 16 bit words
  for (i = 0; i < len_udp; i += 2) {
    word16 = ((buf[i] << 8) & 0xFF00) + ((i + 1 < len_udp) ? (buf[i + 1] & 0xFF) : 0);
    sum += word16;
  }

  // add the UDP pseudo header which contains the IP source and destination addresses
  for (i = 0; i < 4; i += 2) {
    word16 = ((src_addr[i] << 8) & 0xFF00) + (src_addr[i + 1] & 0xFF);
    sum += word16;
  }

  for (i = 0; i < 4; i += 2) {
    word16 = ((dest_addr[i] << 8) & 0xFF00) + (dest_addr[i + 1] & 0xFF);
    sum += word16;
  }

  // the protocol number and the length of the UDP packet
  sum += (USHORT)IPPROTO_UDP + (USHORT)len_udp;

  // keep only the last 16 bits of the 32 bit calculated sum and add the carries
  while (sum >> 16) {
    sum = (sum & 0xFFFF) + (sum >> 16);
  }

  // Take the one's complement of sum
  return ((USHORT)~sum);
}

//================================
// Set IP and UDP packet checksums
//================================

VOID SetChecksumDHCPMsg(__in DHCPMsg *m) {
  // Set IP checksum
  m->msg.pre.ip.check = htons(ip_checksum((UCHAR *)&m->msg.pre.ip, sizeof(IPHDR)));

  // Set UDP Checksum
  m->msg.pre.udp.check =
      htons(udp_checksum((UCHAR *)&m->msg.pre.udp, sizeof(UDPHDR) + sizeof(DHCP) + m->optlen,
                         (UCHAR *)&m->msg.pre.ip.saddr, (UCHAR *)&m->msg.pre.ip.daddr));
}

//===================
// DHCP message tests
//===================

int GetDHCPMessageType(__in const DHCP *dhcp, __in const int optlen) {
  const UCHAR *p = (UCHAR *)(dhcp + 1);
  int i;

  for (i = 0; i < optlen; ++i) {
    const UCHAR type = p[i];
    const int room = optlen - i - 1;

    if (type == DHCP_END)  // didn't find what we were looking for
      return -1;
    else if (type == DHCP_PAD)  // no-operation
      ;
    else if (type == DHCP_MSG_TYPE)  // what we are looking for
    {
      if (room >= 2) {
        if (p[i + 1] == 1)  // message length should be 1
          return p[i + 2];  // return message type
      }
      return -1;
    } else  // some other message
    {
      if (room >= 1) {
        const int len = p[i + 1];  // get message length
        i += (len + 1);            // advance to next message
      }
    }
  }
  return -1;
}

BOOLEAN
DHCPMessageOurs(__in const PTAP_ADAPTER_CONTEXT Adapter, __in const ETH_HEADER *eth,
                __in const IPHDR *ip, __in const UDPHDR *udp, __in const DHCP *dhcp) {
  // Must be UDPv4 protocol
  if (!(eth->proto == htons(NDIS_ETH_TYPE_IPV4) && ip->protocol == IPPROTO_UDP)) {
    return FALSE;
  }

  // Source MAC must be our adapter
  if (!MAC_EQUAL(eth->src, Adapter->CurrentAddress)) {
    return FALSE;
  }

  // Dest MAC must be either broadcast or our virtual DHCP server
  if (!(ETH_IS_BROADCAST(eth->dest) || MAC_EQUAL(eth->dest, Adapter->m_dhcp_server_mac))) {
    return FALSE;
  }

  // Port numbers must be correct
  if (!(udp->dest == htons(BOOTPS_PORT) && udp->source == htons(BOOTPC_PORT))) {
    return FALSE;
  }

  // Hardware address must be MAC addr sized
  if (!(dhcp->hlen == sizeof(MACADDR))) {
    return FALSE;
  }

  // Hardware address must match our adapter
  if (!MAC_EQUAL(eth->src, dhcp->chaddr)) {
    return FALSE;
  }

  return TRUE;
}

//=====================================================
// Build all of DHCP packet except for DHCP options.
// Assume that *p has been zeroed before we are called.
//=====================================================

VOID BuildDHCPPre(__in const PTAP_ADAPTER_CONTEXT Adapter, __inout DHCPPre *p,
                  __in const ETH_HEADER *eth, __in const IPHDR *ip, __in const UDPHDR *udp,
                  __in const DHCP *dhcp, __in const int optlen, __in const int type) {
  // Should we broadcast or direct to a specific MAC / IP address?
  const BOOLEAN broadcast = (type == DHCPNAK || ETH_IS_BROADCAST(eth->dest));

  //
  // Build ethernet header
  //
  ETH_COPY_NETWORK_ADDRESS(p->eth.src, Adapter->m_dhcp_server_mac);

  if (broadcast) {
    memset(p->eth.dest, 0xFF, ETH_LENGTH_OF_ADDRESS);
  } else {
    ETH_COPY_NETWORK_ADDRESS(p->eth.dest, eth->src);
  }

  p->eth.proto = htons(NDIS_ETH_TYPE_IPV4);

  //
  // Build IP header
  //
  p->ip.version_len = (4 << 4) | (sizeof(IPHDR) >> 2);
  p->ip.tos = 0;
  p->ip.tot_len = htons(sizeof(IPHDR) + sizeof(UDPHDR) + sizeof(DHCP) + optlen);
  p->ip.id = 0;
  p->ip.frag_off = 0;
  p->ip.ttl = 16;
  p->ip.protocol = IPPROTO_UDP;
  p->ip.check = 0;
  p->ip.saddr = Adapter->m_dhcp_server_ip;

  if (broadcast) {
    p->ip.daddr = ~0;
  } else {
    p->ip.daddr = Adapter->m_dhcp_addr;
  }

  //
  // Build UDP header
  //
  p->udp.source = htons(BOOTPS_PORT);
  p->udp.dest = htons(BOOTPC_PORT);
  p->udp.len = htons(sizeof(UDPHDR) + sizeof(DHCP) + optlen);
  p->udp.check = 0;

  // Build DHCP response

  p->dhcp.op = BOOTREPLY;
  p->dhcp.htype = 1;
  p->dhcp.hlen = sizeof(MACADDR);
  p->dhcp.hops = 0;
  p->dhcp.xid = dhcp->xid;
  p->dhcp.secs = 0;
  p->dhcp.flags = 0;
  p->dhcp.ciaddr = 0;

  if (type == DHCPNAK) {
    p->dhcp.yiaddr = 0;
  } else {
    p->dhcp.yiaddr = Adapter->m_dhcp_addr;
  }

  p->dhcp.siaddr = Adapter->m_dhcp_server_ip;
  p->dhcp.giaddr = 0;
  ETH_COPY_NETWORK_ADDRESS(p->dhcp.chaddr, eth->src);
  p->dhcp.magic = htonl(0x63825363);
}

//=============================
// Build specific DHCP messages
//=============================

VOID SendDHCPMsg(__in PTAP_ADAPTER_CONTEXT Adapter, __in const int type, __in const ETH_HEADER *eth,
                 __in const IPHDR *ip, __in const UDPHDR *udp, __in const DHCP *dhcp) {
  DHCPMsg *pkt;

  if (!(type == DHCPOFFER || type == DHCPACK || type == DHCPNAK)) {
    DEBUGP(("[TAP] SendDHCPMsg: Bad DHCP type: %d\n", type));
    return;
  }

  pkt = (DHCPMsg *)MemAlloc(sizeof(DHCPMsg), TRUE);

  if (pkt) {
    //-----------------------
    // Build DHCP options
    //-----------------------

    // Message Type
    SetDHCPOpt8(pkt, DHCP_MSG_TYPE, type);

    // Server ID
    SetDHCPOpt32(pkt, DHCP_SERVER_ID, Adapter->m_dhcp_server_ip);

    if (type == DHCPOFFER || type == DHCPACK) {
      // Lease Time
      SetDHCPOpt32(pkt, DHCP_LEASE_TIME, htonl(Adapter->m_dhcp_lease_time));

      // Netmask
      SetDHCPOpt32(pkt, DHCP_NETMASK, Adapter->m_dhcp_netmask);

      // Other user-defined options
      SetDHCPOpt(pkt, Adapter->m_dhcp_user_supplied_options_buffer,
                 Adapter->m_dhcp_user_supplied_options_buffer_len);
    }

    // End
    SetDHCPOpt0(pkt, DHCP_END);

    if (!DHCPMSG_OVERFLOW(pkt)) {
      // The initial part of the DHCP message (not including options) gets built here
      BuildDHCPPre(Adapter, &pkt->msg.pre, eth, ip, udp, dhcp, DHCPMSG_LEN_OPT(pkt), type);

      SetChecksumDHCPMsg(pkt);

      DUMP_PACKET("DHCPMsg", DHCPMSG_BUF(pkt), DHCPMSG_LEN_FULL(pkt));

      // Return DHCP response to kernel
      IndicateReceivePacket(Adapter, DHCPMSG_BUF(pkt), DHCPMSG_LEN_FULL(pkt));
    } else {
      DEBUGP(("[TAP] SendDHCPMsg: DHCP buffer overflow\n"));
    }

    MemFree(pkt, sizeof(DHCPMsg));
  }
}

//===================================================================
// Handle a BOOTPS packet produced by the local system to
// resolve the address/netmask of this adapter.
// If we are in TAP_WIN_IOCTL_CONFIG_DHCP_MASQ mode, reply
// to the message.  Return TRUE if we processed the passed
// message, so that downstream stages can ignore it.
//===================================================================

BOOLEAN
ProcessDHCP(__in PTAP_ADAPTER_CONTEXT Adapter, __in const ETH_HEADER *eth, __in const IPHDR *ip,
            __in const UDPHDR *udp, __in const DHCP *dhcp, __in int optlen) {
  int msg_type;

  // Sanity check IP header
  if (!(ntohs(ip->tot_len) == sizeof(IPHDR) + sizeof(UDPHDR) + sizeof(DHCP) + optlen &&
        (ntohs(ip->frag_off) & IP_OFFMASK) == 0)) {
    return TRUE;
  }

  // Does this message belong to us?
  if (!DHCPMessageOurs(Adapter, eth, ip, udp, dhcp)) {
    return FALSE;
  }

  msg_type = GetDHCPMessageType(dhcp, optlen);

  // Drop non-BOOTREQUEST messages
  if (dhcp->op != BOOTREQUEST) {
    return TRUE;
  }

  // Drop any messages except DHCPDISCOVER or DHCPREQUEST
  if (!(msg_type == DHCPDISCOVER || msg_type == DHCPREQUEST)) {
    return TRUE;
  }

  // Should we reply with DHCPOFFER, DHCPACK, or DHCPNAK?
  if (msg_type == DHCPREQUEST && ((dhcp->ciaddr && dhcp->ciaddr != Adapter->m_dhcp_addr) ||
                                  !Adapter->m_dhcp_received_discover ||
                                  Adapter->m_dhcp_bad_requests >= BAD_DHCPREQUEST_NAK_THRESHOLD)) {
    SendDHCPMsg(Adapter, DHCPNAK, eth, ip, udp, dhcp);
  } else {
    SendDHCPMsg(Adapter, (msg_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK), eth, ip, udp, dhcp);
  }

  // Remember if we received a DHCPDISCOVER
  if (msg_type == DHCPDISCOVER) {
    Adapter->m_dhcp_received_discover = TRUE;
  }

  // Is this a bad DHCPREQUEST?
  if (msg_type == DHCPREQUEST && dhcp->ciaddr && dhcp->ciaddr != Adapter->m_dhcp_addr) {
    ++Adapter->m_dhcp_bad_requests;
  }

  return TRUE;
}

#if DBG

const char *message_op_text(int op) {
  switch (op) {
    case BOOTREQUEST:
      return "BOOTREQUEST";

    case BOOTREPLY:
      return "BOOTREPLY";

    default:
      return "???";
  }
}

const char *message_type_text(int type) {
  switch (type) {
    case DHCPDISCOVER:
      return "DHCPDISCOVER";

    case DHCPOFFER:
      return "DHCPOFFER";

    case DHCPREQUEST:
      return "DHCPREQUEST";

    case DHCPDECLINE:
      return "DHCPDECLINE";

    case DHCPACK:
      return "DHCPACK";

    case DHCPNAK:
      return "DHCPNAK";

    case DHCPRELEASE:
      return "DHCPRELEASE";

    case DHCPINFORM:
      return "DHCPINFORM";

    default:
      return "???";
  }
}

const char *port_name(int port) {
  switch (port) {
    case BOOTPS_PORT:
      return "BOOTPS";

    case BOOTPC_PORT:
      return "BOOTPC";

    default:
      return "unknown";
  }
}

VOID DumpDHCP(const ETH_HEADER *eth, const IPHDR *ip, const UDPHDR *udp, const DHCP *dhcp,
              const int optlen) {
  DEBUGP((" %s", message_op_text(dhcp->op)));
  DEBUGP((" %s ", message_type_text(GetDHCPMessageType(dhcp, optlen))));
  PrIP(ip->saddr);
  DEBUGP((":%s[", port_name(ntohs(udp->source))));
  PrMac(eth->src);
  DEBUGP(("] -> "));
  PrIP(ip->daddr);
  DEBUGP((":%s[", port_name(ntohs(udp->dest))));
  PrMac(eth->dest);
  DEBUGP(("]"));
  if (dhcp->ciaddr) {
    DEBUGP((" ci="));
    PrIP(dhcp->ciaddr);
  }
  if (dhcp->yiaddr) {
    DEBUGP((" yi="));
    PrIP(dhcp->yiaddr);
  }
  if (dhcp->siaddr) {
    DEBUGP((" si="));
    PrIP(dhcp->siaddr);
  }
  if (dhcp->hlen == sizeof(MACADDR)) {
    DEBUGP((" ch="));
    PrMac(dhcp->chaddr);
  }

  DEBUGP((" xid=0x%08x", ntohl(dhcp->xid)));

  if (ntohl(dhcp->magic) != 0x63825363) DEBUGP((" ma=0x%08x", ntohl(dhcp->magic)));
  if (dhcp->htype != 1) DEBUGP((" htype=%d", dhcp->htype));
  if (dhcp->hops) DEBUGP((" hops=%d", dhcp->hops));
  if (ntohs(dhcp->secs)) DEBUGP((" secs=%d", ntohs(dhcp->secs)));
  if (ntohs(dhcp->flags)) DEBUGP((" flags=0x%04x", ntohs(dhcp->flags)));

  // extra stuff

  if (ip->version_len != 0x45) DEBUGP((" vl=0x%02x", ip->version_len));
  if (ntohs(ip->tot_len) != sizeof(IPHDR) + sizeof(UDPHDR) + sizeof(DHCP) + optlen)
    DEBUGP((" tl=%d", ntohs(ip->tot_len)));
  if (ntohs(udp->len) != sizeof(UDPHDR) + sizeof(DHCP) + optlen)
    DEBUGP((" ul=%d", ntohs(udp->len)));

  if (ip->tos) DEBUGP((" tos=0x%02x", ip->tos));
  if (ntohs(ip->id)) DEBUGP((" id=0x%04x", ntohs(ip->id)));
  if (ntohs(ip->frag_off)) DEBUGP((" frag_off=0x%04x", ntohs(ip->frag_off)));

  DEBUGP((" ttl=%d", ip->ttl));
  DEBUGP((" ic=0x%04x [0x%04x]", ntohs(ip->check), ip_checksum((UCHAR *)ip, sizeof(IPHDR))));
  DEBUGP((" uc=0x%04x [0x%04x/%d]", ntohs(udp->check),
          udp_checksum((UCHAR *)udp, sizeof(UDPHDR) + sizeof(DHCP) + optlen, (UCHAR *)&ip->saddr,
                       (UCHAR *)&ip->daddr),
          optlen));

  // Options
  {
    const UCHAR *opt = (UCHAR *)(dhcp + 1);
    int i;

    DEBUGP((" OPT"));
    for (i = 0; i < optlen; ++i) {
      const UCHAR data = opt[i];
      DEBUGP((".%d", data));
    }
  }
}

#endif /* DBG */
