text: // SoftEther VPN Source Code - Developer Edition Master Branch
  // Mayaqua Kernel
  
  
  // TcpIp.c
  // Utility module for TCP/IP packet processing
  
  #include <GlobalConst.h>
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <wchar.h>
  #include <stdarg.h>
  #include <time.h>
  #include <errno.h>
  #include <Mayaqua/Mayaqua.h>
  
  // Release the memory for the ICMP response
  void IcmpFreeResult(ICMP_RESULT *r)
  {
  	// Validate arguments
  	if (r == NULL)
  	{
  		return;
  	}
  
  	IcmpApiFreeResult(r);
  }
  
  // Parse the ICMP reply packet received from the socket
  ICMP_RESULT *IcmpParseResult(IP *dest_ip, USHORT src_id, USHORT src_seqno, UCHAR *recv_buffer, UINT recv_buffer_size)
  {
  	ICMP_RESULT *ret = NULL;
  	UINT i;
  	// Validate arguments
  	if (dest_ip == NULL || IsIP4(dest_ip) == false || recv_buffer == NULL || recv_buffer_size == 0)
  	{
  		return NULL;
  	}
  
  	i = recv_buffer_size;
  
  	if (true)
  	{
  		UINT ip_header_size = GetIpHeaderSize(recv_buffer, i);
  		if (ip_header_size >= sizeof(IPV4_HEADER) && (ip_header_size <= i))
  		{
  			IPV4_HEADER *ipv4 = (IPV4_HEADER *)recv_buffer;
  			if ((IPV4_GET_VERSION(ipv4) == 4) && (ipv4->Protocol == IP_PROTO_ICMPV4))
  			{
  				UINT ip_total_len = (UINT)Endian16(ipv4->TotalLength);
  
  				if ((ip_total_len >= sizeof(IPV4_HEADER)) && (ip_total_len <= i) && (ip_total_len >= ip_header_size))
  				{
  					UINT icmp_packet_size = ip_total_len - ip_header_size;
  					ICMP_HEADER *icmp = (ICMP_HEADER *)(recv_buffer + ip_header_size);
  
  					if (icmp_packet_size >= sizeof(ICMP_HEADER))
  					{
  						USHORT chksum = icmp->Checksum;
  						USHORT chksum2;
  						icmp->Checksum = 0;
  
  						chksum2 = IpChecksum(icmp, icmp_packet_size);
  
  						if (chksum2 == chksum)
  						{
  							if (icmp->Type == ICMP_TYPE_ECHO_RESPONSE)
  							{
  								ICMP_ECHO *echo = (ICMP_ECHO *)(recv_buffer + ip_header_size + sizeof(ICMP_HEADER));
  								if (icmp_packet_size >= (sizeof(ICMP_HEADER) + sizeof(ICMP_ECHO)))
  								{
  									if (Endian16(echo->Identifier) == src_id && (src_seqno == 0 || Endian16(echo->SeqNo) == src_seqno))
  									{
  										IP ip;
  
  										UINTToIP(&ip, ipv4->SrcIP);
  
  										// Received the correct Echo response
  										ret = ZeroMalloc(sizeof(ICMP_RESULT));
  
  										ret->Ok = true;
  										ret->Ttl = ipv4->TimeToLive;
  										ret->DataSize = icmp_packet_size - (sizeof(ICMP_HEADER) + sizeof(ICMP_ECHO));
  										ret->Data = Clone(recv_buffer + ip_header_size + sizeof(ICMP_HEADER) + sizeof(ICMP_ECHO),
  										                  ret->DataSize);
  										Copy(&ret->IpAddress, &ip, sizeof(IP));
  									}
  								}
  							}
  							else if (icmp->Type == ICMP_TYPE_ECHO_REQUEST)
  							{
  								// Ignore because an Echo request should not arrive
  							}
  							else
  							{
  								// If an error is returned, compare to the copy of
  								// the ICMP packet last sent
  								IPV4_HEADER *orig_ipv4 = (IPV4_HEADER *)(recv_buffer + ip_header_size + 4 + sizeof(ICMP_HEADER));
  								if (icmp_packet_size >= (sizeof(ICMP_HEADER) + 4 + sizeof(IPV4_HEADER)))
  								{
  									UINT orig_ipv4_header_size = GetIpHeaderSize((UCHAR *)orig_ipv4, icmp_packet_size - 4 - sizeof(ICMP_HEADER));
  									if (orig_ipv4_header_size >= sizeof(IPV4_HEADER))
  									{
  										if ((IPV4_GET_VERSION(orig_ipv4) == 4) && (orig_ipv4->Protocol == IP_PROTO_ICMPV4))
  										{
  											if (icmp_packet_size >= (sizeof(ICMP_HEADER) + 4 + orig_ipv4_header_size + sizeof(ICMP_HEADER) + sizeof(ICMP_ECHO)))
  											{
  												ICMP_HEADER *orig_icmp = (ICMP_HEADER *)(recv_buffer + ip_header_size + sizeof(ICMP_HEADER) + 4 + orig_ipv4_header_size);
  												ICMP_ECHO *orig_echo = (ICMP_ECHO *)(recv_buffer + ip_header_size + sizeof(ICMP_HEADER) + 4 + orig_ipv4_header_size + sizeof(ICMP_HEADER));
  
  												if (orig_icmp->Type == ICMP_TYPE_ECHO_REQUEST && orig_echo->Identifier == Endian16(src_id) && (src_seqno == 0 || orig_echo->SeqNo == Endian16(src_seqno)))
  												{
  													IP ip;
  
  													UINTToIP(&ip, ipv4->SrcIP);
  
  													ret = ZeroMalloc(sizeof(ICMP_RESULT));
  
  													ret->Type = icmp->Type;
  													ret->Code = icmp->Code;
  													ret->Ttl = ipv4->TimeToLive;
  													ret->DataSize = icmp_packet_size - (sizeof(ICMP_HEADER) + sizeof(ICMP_ECHO));
  													ret->Data = Clone(recv_buffer + ip_header_size + sizeof(ICMP_HEADER) + sizeof(ICMP_ECHO),
  													                  ret->DataSize);
  													Copy(&ret->IpAddress, &ip, sizeof(IP));
  												}
  											}
  										}
  									}
  								}
  							}
  						}
  					}
  				}
  			}
  		}
  	}
  
  	return ret;
  }
  
  // Get whether the packet is a DHCP packet associated with the specified MAC address
  bool IsDhcpPacketForSpecificMac(UCHAR *data, UINT size, UCHAR *mac_address)
  {
  	USHORT *us;
  	IPV4_HEADER *ip;
  	UDP_HEADER *udp;
  	UINT ip_header_size;
  	bool is_send = false, is_recv = false;
  	// Validate arguments
  	if (data == NULL || mac_address == NULL || IsZero(mac_address, 6))
  	{
  		return false;
  	}
  
  	// Whether the src or the dest matches
  	if (size < 14)
  	{
  		return false;
  	}
  
  	// Destination MAC address
  	if (Cmp(data, mac_address, 6) == 0)
  	{
  		is_recv = true;
  	}
  	size -= 6;
  	data += 6;
  
  	// Source MAC address
  	if (Cmp(data, mac_address, 6) == 0)
  	{
  		is_send = true;
  	}
  	size -= 6;
  	data += 6;
  
  	if (is_send == false && is_recv == false)
  	{
  		return false;
  	}
  	if (is_send && is_recv)
  	{
  		return false;
  	}
  
  	// TPID
  	us = (USHORT *)data;
  	size -= 2;
  	data += 2;
  
  	if (READ_USHORT(us) != MAC_PROTO_IPV4)
  	{
  		// Other than IPv4
  		return false;
  	}
  
  	// IP header
  	ip_header_size = GetIpHeaderSize(data, size);
  	if (ip_header_size == 0)
  	{
  		// IPv4 header analysis failure
  		return false;
  	}
  
  	ip = (IPV4_HEADER *)data;
  	data += ip_header_size;
  	size -= ip_header_size;
  
  	if (ip->Protocol != IP_PROTO_UDP)
  	{
  		// Not an UDP packet
  		return false;
  	}
  
  	// UDP header
  	if (size < sizeof(UDP_HEADER))
  	{
  		return false;
  	}
  	udp = (UDP_HEADER *)data;
  	data += sizeof(UDP_HEADER);
  	size -= sizeof(UDP_HEADER);
  
  	if (is_send)
  	{
  		// Detect whether it's a DHCP Request packet
  		if (Endian16(udp->DstPort) == 67)
  		{
  			Debug("IsDhcpPacketForSpecificMac: DHCP Request Packet is Detected.\n");
  			return true;
  		}
  	}
  	else if (is_recv)
  	{
  		// Detect whether it's a DHCP Response packet
  		if (Endian16(udp->SrcPort) == 67)
  		{
  			Debug("IsDhcpPacketForSpecificMac: DHCP Response Packet is Detected.\n");
  			return true;
  		}
  	}
  
  	return false;
  }
  
  // Adjust the MSS of the TCP in the IP packet (L2)
  bool AdjustTcpMssL2(UCHAR *src, UINT src_size, UINT mss, USHORT tag_vlan_tpid)
  {
  	MAC_HEADER *mac;
  	USHORT proto;
  	// Validate arguments
  	if (src == NULL || src_size == 0 || mss == 0)
  	{
  		return false;
  	}
  	if (tag_vlan_tpid == 0)
  	{
  		tag_vlan_tpid = MAC_PROTO_TAGVLAN;
  	}
  
  	if (src_size < sizeof(MAC_HEADER))
  	{
  		return false;
  	}
  
  	mac = (MAC_HEADER *)src;
  
  	src += sizeof(MAC_HEADER);
  	src_size -= sizeof(MAC_HEADER);
  
  	proto = Endian16(mac->Protocol);
  
  	if (proto == MAC_PROTO_IPV4 || proto == MAC_PROTO_IPV6)
  	{
  		// Ordinary IPv4 / IPv6 packet
  		return AdjustTcpMssL3(src, src_size, mss);
  	}
  	else if (proto == tag_vlan_tpid)
  	{
  		// IPv4 / IPv6 packets in the VLAN tag
  		if (src_size < 4)
  		{
  			return false;
  		}
  
  		src += 2;
  		src_size -= 2;
  
  		proto = READ_USHORT(src);
  
  		if (proto == MAC_PROTO_IPV4 || proto == MAC_PROTO_IPV6)
  		{
  			if (mss >= 5)
  			{
  				mss -= 4;
  
  				src += 2;
  				src_size -= 2;
  
  				return AdjustTcpMssL3(src, src_size, mss);
  			}
  		}
  	}
  
  	return false;
  }
  
  // Get an IP header size
  UINT GetIpHeaderSize(UCHAR *src, UINT src_size)
  {
  	UCHAR ip_ver;
  	TCP_HEADER *tcp = NULL;
  	IPV4_HEADER *ip = NULL;
  	IPV6_HEADER *ip6 = NULL;
  	// Validate arguments
  	if (src == NULL || src_size == 0)
  	{
  		return 0;
  	}
  
  	// Get the IP version number
  	ip_ver = (src[0] >> 4) & 0x0f;
  
  	if (ip_ver == 4)
  	{
  		// IPv4
  		UINT ip_header_size;
  		if (src_size < sizeof(IPV4_HEADER))
  		{
  			// No IPv4 header
  			return 0;
  		}
  
  		ip = (IPV4_HEADER *)src;
  
  		ip_header_size = IPV4_GET_HEADER_LEN(ip) * 4;
  		if (ip_header_size < sizeof(IPV4_HEADER))
  		{
  			// Header size is invalid
  			return 0;
  		}
  
  		if (src_size < ip_header_size)
  		{
  			// No IPv4 header
  			return 0;
  		}
  
  		return ip_header_size;
  	}
  	else if (ip_ver == 6)
  	{
  		// IPv6
  		IPV6_HEADER_PACKET_INFO v6;
  
  		if (ParsePacketIPv6Header(&v6, src, src_size) == false)
  		{
  			// IPv6 analysis failure
  			return 0;
  		}
  
  		ip6 = v6.IPv6Header;
  		if (ip6 == NULL)
  		{
  			return 0;
  		}
  
  		if (src_size < v6.TotalHeaderSize)
  		{
  			// No header data
  			return 0;
  		}
  
  		return v6.TotalHeaderSize;
  	}
  	else
  	{
  		// Invalid
  		return 0;
  	}
  }
  
  // Adjust the MSS of TCP in the IP packet (L3)
  bool AdjustTcpMssL3(UCHAR *src, UINT src_size, UINT mss)
  {
  	UCHAR ip_ver;
  	TCP_HEADER *tcp = NULL;
  	UINT tcp_size = 0;
  	UINT tcp_header_size;
  	UCHAR *options;
  	UINT options_size;
  	IPV4_HEADER *ip = NULL;
  	IPV6_HEADER *ip6 = NULL;
  	// Validate arguments
  	if (src == NULL || src_size == 0 || mss == 0)
  	{
  		return false;
  	}
  
  	// Get the IP version number
  	ip_ver = (src[0] >> 4) & 0x0f;
  
  	if (ip_ver == 4)
  	{
  		UINT ip_header_size;
  		UINT ip_total_length;
  		// IPv4
  		if (src_size < sizeof(IPV4_HEADER))
  		{
  			// No IPv4 header
  			return false;
  		}
  
  		ip = (IPV4_HEADER *)src;
  
  		if (ip->Protocol != IP_PROTO_TCP)
  		{
  			// Non-TCP
  			return false;
  		}
  
  		if (IPV4_GET_OFFSET(ip) != 0)
  		{
  			// It is the second or later packet of fragmented packet
  			return false;
  		}
  
  		if (IPV4_GET_FLAGS(ip) & 0x01)
  		{
  			// Fragmented packet
  			return false;
  		}
  
  		ip_header_size = IPV4_GET_HEADER_LEN(ip) * 4;
  		if (ip_header_size < sizeof(IPV4_HEADER))
  		{
  			// Header size is invalid
  			return false;
  		}
  
  		if (src_size < ip_header_size)
  		{
  			// No IPv4 header
  			return false;
  		}
  
  		ip_total_length = READ_USHORT(&ip->TotalLength);
  
  		if (ip_total_length < ip_header_size)
  		{
  			// Invalid total length
  			return false;
  		}
  
  		if (src_size < ip_total_length)
  		{
  			// No total length
  			return false;
  		}
  
  		src += ip_header_size;
  		src_size = ip_total_length - ip_header_size;
  
  		if (src_size < sizeof(TCP_HEADER))
  		{
  			// No TCP header
  			return false;
  		}
  
  		tcp = (TCP_HEADER *)src;
  		tcp_size = src_size;
  	}
  	else if (ip_ver == 6)
  	{
  		// IPv6
  		IPV6_HEADER_PACKET_INFO v6;
  
  		if (ParsePacketIPv6Header(&v6, src, src_size) == false)
  		{
  			// IPv6 analysis failure
  			return false;
  		}
  
  		ip6 = v6.IPv6Header;
  		if (ip6 == NULL)
  		{
  			return false;
  		}
  
  		if (v6.Protocol != IP_PROTO_TCP)
  		{
  			// Non-TCP
  			return false;
  		}
  
  		if (v6.IsFragment)
  		{
  			// It is the second or later packet of fragmented packet
  			return false;
  		}
  
  		if (v6.FragmentHeader != NULL)
  		{
  			if (IPV6_GET_FLAGS(v6.FragmentHeader) & IPV6_FRAGMENT_HEADER_FLAG_MORE_FRAGMENTS)
  			{
  				// Fragmented packet
  				return false;
  			}
  		}
  
  		tcp = (TCP_HEADER *)v6.Payload;
  		tcp_size = v6.PayloadSize;
  	}
  	else
  	{
  		// This isn't either IPv4, IPv6
  		return false;
  	}
  
  	// Processing of the TCP header
  	if (tcp == NULL || tcp_size < sizeof(TCP_HEADER))
  	{
  		return false;
  	}
  
  	tcp_header_size = TCP_GET_HEADER_SIZE(tcp) * 4;
  	if (tcp_header_size < sizeof(TCP_HEADER))
  	{
  		// TCP header size is invalid
  		return false;
  	}
  
  	if (tcp_size < tcp_header_size)
  	{
  		// Packet length shortage
  		return false;
  	}
  
  	if (((tcp->Flag & TCP_SYN) == false) ||
  	        ((tcp->Flag & TCP_RST) ||
  	         (tcp->Flag & TCP_PSH) ||
  	         (tcp->Flag & TCP_URG)))
  	{
  		// Not a SYN packet
  		return false;
  	}
  
  	// Get the option field
  	options = ((UCHAR *)tcp) + sizeof(TCP_HEADER);
  	options_size = tcp_header_size - sizeof(TCP_HEADER);
  
  	if (ip6 != NULL)
  	{
  		// Reduce MSS by 20 since an IP header for IPv6 is 20 bytes larger than IPv4
  		if (mss >= 20)
  		{
  			mss -= 20;
  		}
  	}
  
  	// MSS should be at least 64
  	mss = MAX(mss, 64);
  
  	if (options_size >= 4 && options[0] == 0x02 && options[1] == 0x04)
  	{
  		// MSS option of TCP is added
  		USHORT current_mss = READ_USHORT(((UCHAR *)options) + 2);
  
  		if (current_mss <= mss)
  		{
  			// if the value of the MSS is smaller than the specified size
  			// from the beginning, it doesn't need to be rewritten
  			return false;
  		}
  		else
  		{
  			WRITE_USHORT(((UCHAR *)options) + 2, mss);
  
  			// Clear the checksum
  			tcp->Checksum = 0;
  
  			if (ip != NULL)
  			{
  				// Calculate the TCPv4 checksum
  				tcp->Checksum = CalcChecksumForIPv4(ip->SrcIP, ip->DstIP, IP_PROTO_TCP, tcp, tcp_size, 0);
  			}
  			else
  			{
  				// Calculate the TCPv6 checksum
  				tcp->Checksum = CalcChecksumForIPv6(&ip6->SrcAddress, &ip6->DestAddress,
  				                                    IP_PROTO_TCP, tcp, tcp_size, 0);
  			}
  
  			return true;
  		}
  	}
  	else
  	{
  		// MSS option of TCP is not added
  		return false;
  	}
  }
  
  
  // Parse the DHCPv4 packet
  DHCPV4_DATA *ParseDHCPv4Data(PKT *pkt)
  {
  	DHCPV4_DATA *d;
  	UCHAR *data;
  	UINT size;
  	UINT magic_cookie = Endian32(DHCP_MAGIC_COOKIE);
  	bool ok = false;
  	DHCP_OPTION *o;
  	// Validate arguments
  	if (pkt == NULL)
  	{
  		return NULL;
  	}
  	if (pkt->TypeL3 != L3_IPV4 || pkt->TypeL4 != L4_UDP || pkt->TypeL7 != L7_DHCPV4)
  	{
  		return NULL;
  	}
  
  	d = ZeroMalloc(sizeof(DHCPV4_DATA));
  	d->Size = (UINT)(pkt->PacketSize - (((UCHAR *)pkt->L7.PointerL7) - ((UCHAR *)pkt->PacketData)));
  	d->Data = Clone(pkt->L7.PointerL7, d->Size);
  
  	if (d->Size < sizeof(DHCPV4_HEADER))
  	{
  		goto LABEL_ERROR;
  	}
  
  	// Header
  	d->Header = (DHCPV4_HEADER *)d->Data;
  
  	data = d->Data;
  	size = d->Size;
  
  	// Search for the Magic Cookie
  	ok = false;
  	while (size >= 5)
  	{
  		if (Cmp(data, &magic_cookie, 4) == 0)
  		{
  			// Found
  			data += 4;
  			size -= 4;
  			ok = true;
  			break;
  		}
  
  		data++;
  		size--;
  	}
  
  	if (ok == false)
  	{
  		// Magic Cookie not found
  		goto LABEL_ERROR;
  	}
  
  	// Parse the DHCP Options
  	d->OptionData = data;
  	d->OptionSize = size;
  
  	d->OptionList = ParseDhcpOptions(data, size);
  	if (d->OptionList == NULL)
  	{
  		// Parsing failure
  		goto LABEL_ERROR;
  	}
  
  	UINTToIP(&d->SrcIP, pkt->L3.IPv4Header->SrcIP);
  	UINTToIP(&d->DestIP, pkt->L3.IPv4Header->DstIP);
  
  	d->SrcPort = Endian16(pkt->L4.UDPHeader->SrcPort);
  	d->DestPort = Endian16(pkt->L4.UDPHeader->DstPort);
  
  	o = GetDhcpOption(d->OptionList, DHCP_ID_MESSAGE_TYPE);
  	if (o == NULL || o->Size != 1)
  	{
  		goto LABEL_ERROR;
  	}
  
  	d->OpCode = *((UCHAR *)o->Data);
  
  	d->ParsedOptionList = ParseDhcpOptionList(d->OptionData, d->OptionSize);
  
  	if (d->ParsedOptionList == NULL)
  	{
  		goto LABEL_ERROR;
  	}
  
  	if (d->ParsedOptionList->ServerAddress == 0)
  	{
  		d->ParsedOptionList->ServerAddress = d->Header->ServerIP;
  	}
  
  	d->ParsedOptionList->ClientAddress = d->Header->YourIP;
  
  	return d;
  
  LABEL_ERROR:
  	FreeDHCPv4Data(d);
  	return NULL;
  }
  
  // Release the DHCPv4 packet
  void FreeDHCPv4Data(DHCPV4_DATA *d)
  {
  	// Validate arguments
  	if (d == NULL)
  	{
  		return;
  	}
  
  	FreeDhcpOptions(d->OptionList);
  	Free(d->Data);
  
  	Free(d->ParsedOptionList);
  
  	Free(d);
  }
  
  // Embed a VLAN tag to the packet
  void VLanInsertTag(void **packet_data, UINT *packet_size, UINT vlan_id, UINT vlan_tpid)
  {
  	UINT dest_size;
  	UCHAR *dest_data;
  	UINT src_size;
  	UCHAR *src_data;
  	USHORT vlan_ushort = Endian16(((USHORT)vlan_id) & 0xFFF);
  	USHORT vlan_tpid_ushort;
  	// Validate arguments
  	if (packet_data == NULL || *packet_data == NULL || packet_size == NULL ||
  	        *packet_size < 14 || vlan_id == 0)
  	{
  		return;
  	}
  	if (vlan_tpid == 0)
  	{
  		vlan_tpid = MAC_PROTO_TAGVLAN;
  	}
  
  	vlan_tpid_ushort = Endian16((USHORT)vlan_tpid);
  
  	src_size = *packet_size;
  	src_data = (UCHAR *)(*packet_data);
  
  	dest_size = src_size + 4;
  	dest_data = Malloc(dest_size);
  
  	Copy(&dest_data[12], &vlan_tpid_ushort, sizeof(USHORT));
  	Copy(&dest_data[14], &vlan_ushort, sizeof(USHORT));
  
  	Copy(&dest_data[0], &src_data[0], 12);
  	Copy(&dest_data[16], &src_data[12], src_size - 12);
  
  	*packet_size = dest_size;
  	*packet_data = dest_data;
  
  	Free(src_data);
  }
  
  // Remove the VLAN tag from the packet
  bool VLanRemoveTag(void **packet_data, UINT *packet_size, UINT vlan_id, UINT vlan_tpid)
  {
  	UCHAR *src_data;
  	UINT src_size;
  	USHORT vlan_tpid_ushort;
  	UCHAR *vlan_tpid_uchar;
  	// Validate arguments
  	if (packet_data == NULL || *packet_data == NULL || packet_size == NULL ||
  	        *packet_size < 14)
  	{
  		return false;
  	}
  
  	if (vlan_tpid == 0)
  	{
  		vlan_tpid = MAC_PROTO_TAGVLAN;
  	}
  
  	vlan_tpid_ushort = Endian16((USHORT)vlan_tpid);
  	vlan_tpid_uchar = (UCHAR *)(&vlan_tpid_ushort);
  
  	src_data = (UCHAR *)(*packet_data);
  	src_size = *packet_size;
  
  	if (src_data[12] == vlan_tpid_uchar[0] && src_data[13] == vlan_tpid_uchar[1])
  	{
  		if (src_size >= 18)
  		{
  			USHORT vlan_ushort;
  
  			vlan_ushort = READ_USHORT(&src_data[14]);
  			vlan_ushort = vlan_ushort & 0xFFF;
  
  			if (vlan_id == 0 || (vlan_ushort == vlan_id))
  			{
  				UINT dest_size = src_size - 4;
  				UINT i;
  
  				for (i = 12; i < dest_size; i++)
  				{
  					src_data[i] = src_data[i + 4];
  				}
  
  				*packet_size = dest_size;
  
  				return true;
  			}
  		}
  	}
  
  	return false;
  }
  
  // Sending of an ICMPv6 packet
  BUF *BuildICMPv6(IPV6_ADDR *src_ip, IPV6_ADDR *dest_ip, UCHAR hop_limit, UCHAR type, UCHAR code, void *data, UINT size, UINT id)
  {
  	ICMP_HEADER *icmp;
  	void *data_buf;
  	BUF *ret;
  	// Validate arguments
  	if (src_ip == NULL || dest_ip == NULL || data == NULL)
  	{
  		return NULL;
  	}
  
  	// Assemble the header
  	icmp = ZeroMalloc(sizeof(ICMP_HEADER) + size);
  	data_buf = ((UCHAR *)icmp) + sizeof(ICMP_HEADER);
  	Copy(data_buf, data, size);
  
  	icmp->Type = type;
  	icmp->Code = code;
  	icmp->Checksum = CalcChecksumForIPv6(src_ip, dest_ip, IP_PROTO_ICMPV6, icmp,
  	                                     sizeof(ICMP_HEADER) + size, 0);
  
  	ret = BuildIPv6(dest_ip, src_ip, id, IP_PROTO_ICMPV6, hop_limit, icmp,
  	                sizeof(ICMP_HEADER) + size);
  
  	Free(icmp);
  
  	return ret;
  }
  
  // Build an ICMPv6 Neighbor Solicitation packet
  BUF *BuildICMPv6NeighborSoliciation(IPV6_ADDR *src_ip, IPV6_ADDR *target_ip, UCHAR *my_mac_address, UINT id)
  {
  	ICMPV6_OPTION_LIST opt;
  	ICMPV6_OPTION_LINK_LAYER link;
  	ICMPV6_NEIGHBOR_SOLICIATION_HEADER header;
  	BUF *b;
  	BUF *b2;
  	BUF *ret;
  	// Validate arguments
  	if (src_ip == NULL || target_ip == NULL || my_mac_address == NULL)
  	{
  		return NULL;
  	}
  
  	Zero(&link, sizeof(link));
  	Copy(link.Address, my_mac_address, 6);
  
  	Zero(&opt, sizeof(opt));
  	opt.SourceLinkLayer = &link;
  
  	b = BuildICMPv6Options(&opt);
  
  	Zero(&header, sizeof(header));
  	Copy(&header.TargetAddress, target_ip, sizeof(IPV6_ADDR));
  
  	b2 = NewBuf();
  
  	WriteBuf(b2, &header, sizeof(header));
  	WriteBufBuf(b2, b);
  
  	ret = BuildICMPv6(src_ip, target_ip, 255,
  	                  ICMPV6_TYPE_NEIGHBOR_SOLICIATION, 0, b2->Buf, b2->Size, id);
  
  	FreeBuf(b);
  	FreeBuf(b2);
  
  	return ret;
  }
  
  BUF *BuildICMPv6RouterSoliciation(IPV6_ADDR *src_ip, IPV6_ADDR *target_ip, UCHAR *my_mac_address, UINT id)
  {
  	ICMPV6_OPTION_LIST opt;
  	ICMPV6_OPTION_LINK_LAYER link;
  	ICMPV6_ROUTER_SOLICIATION_HEADER header;
  	BUF *b;
  	BUF *b2;
  	BUF *ret;
  
  	if (src_ip == NULL || target_ip == NULL || my_mac_address == NULL)
  	{
  		return NULL;
  	}
  
  	Zero(&link, sizeof(link));
  	Copy(link.Address, my_mac_address, 6);
  
  	Zero(&opt, sizeof(opt));
  	opt.SourceLinkLayer = &link;
  
  	b = BuildICMPv6Options(&opt);
  
  	Zero(&header, sizeof(header));
  
  	b2 = NewBuf();
  
  	WriteBuf(b2, &header, sizeof(header));
  	WriteBufBuf(b2, b);
  
  	ret = BuildICMPv6(src_ip, target_ip, 255,
  	                  ICMPV6_TYPE_ROUTER_SOLICIATION, 0, b2->Buf, b2->Size, id);
  
  	FreeBuf(b);
  	FreeBuf(b2);
  
  	return ret;
  }
  
  // Get the next header number from the queue
  UCHAR IPv6GetNextHeaderFromQueue(QUEUE *q)
  {
  	UINT *p;
  	UCHAR v = 0;
  	// Validate arguments
  	if (q == NULL)
  	{
  		return IPV6_HEADER_NONE;
  	}
  
  	p = (UINT *)GetNext(q);
  	if (p != NULL)
  	{
  		v = (UCHAR)(*p);
  		Free(p);
  	}
  
  	return v;
  }
  
  // Add an IPv6 extension header option (variable length)
  void BuildAndAddIPv6PacketOptionHeader(BUF *b, IPV6_OPTION_HEADER *opt, UCHAR next_header, UINT size)
  {
  	IPV6_OPTION_HEADER *h;
  	UINT total_size;
  	// Validate arguments
  	if (b == NULL || opt == NULL)
  	{
  		return;
  	}
  
  	total_size = size;
  	if ((total_size % 8) != 0)
  	{
  		total_size = ((total_size / 8) + 1) * 8;
  	}
  
  	h = ZeroMalloc(total_size);
  	Copy(h, opt, size);
  	h->Size = (total_size / 8) - 1;
  	h->NextHeader = next_header;
  
  	WriteBuf(b, h, total_size);
  
  	Free(h);
  }
  
  // Build an IPv6 packet
  BUF *BuildIPv6(IPV6_ADDR *dest_ip, IPV6_ADDR *src_ip, UINT id, UCHAR protocol, UCHAR hop_limit, void *data,
                 UINT size)
  {
  	IPV6_HEADER_PACKET_INFO info;
  	IPV6_HEADER ip_header;
  	BUF *buf;
  	UINT size_for_headers;
  	// Validate arguments
  	if (dest_ip == NULL || src_ip == NULL || data == NULL)
  	{
  		return NULL;
  	}
  	if (hop_limit == 0)
  	{
  		hop_limit = 255;
  	}
  
  	// IPv6 header
  	Zero(&ip_header, sizeof(ip_header));
  	IPV6_SET_VERSION(&ip_header, 6);
  	ip_header.HopLimit = hop_limit;
  	Copy(&ip_header.SrcAddress, src_ip, sizeof(IPV6_ADDR));
  	Copy(&ip_header.DestAddress, dest_ip, sizeof(IPV6_ADDR));
  
  	// Arrangement of the packet header information
  	Zero(&info, sizeof(info));
  	info.IPv6Header = &ip_header;
  	info.Protocol = protocol;
  	info.Payload = data;
  	info.PayloadSize = size;
  
  	buf = BuildIPv6PacketHeader(&info, &size_for_headers);
  	if (buf == NULL)
  	{
  		return NULL;
  	}
  
  	return buf;
  }
  
  // Build the IPv6 packet header section
  BUF *BuildIPv6PacketHeader(IPV6_HEADER_PACKET_INFO *info, UINT *bytes_before_payload)
  {
  	BUF *b;
  	QUEUE *q;
  	UINT bbp = 0;
  	// Validate arguments
  	if (info == NULL)
  	{
  		return NULL;
  	}
  
  	b = NewBuf();
  	q = NewQueueFast();
  
  	// Create the list of options headers
  	if (info->HopHeader != NULL)
  	{
  		InsertQueueInt(q, IPV6_HEADER_HOP);
  	}
  	if (info->EndPointHeader != NULL)
  	{
  		InsertQueueInt(q, IPV6_HEADER_ENDPOINT);
  	}
  	if (info->RoutingHeader != NULL)
  	{
  		InsertQueueInt(q, IPV6_HEADER_ROUTING);
  	}
  	if (info->FragmentHeader != NULL)
  	{
  		InsertQueueInt(q, IPV6_HEADER_FRAGMENT);
  	}
  	InsertQueueInt(q, info->Protocol);
  
  	// IPv6 header
  	info->IPv6Header->NextHeader = IPv6GetNextHeaderFromQueue(q);
  	WriteBuf(b, info->IPv6Header, sizeof(IPV6_HEADER));
  
  	// Hop-by-hop option header
  	if (info->HopHeader != NULL)
  	{
  		BuildAndAddIPv6PacketOptionHeader(b, info->HopHeader,
  		                                  IPv6GetNextHeaderFromQueue(q), info->HopHeaderSize);
  	}
  
  	// End point option header
  	if (info->EndPointHeader != NULL)
  	{
  		BuildAndAddIPv6PacketOptionHeader(b, info->EndPointHeader,
  		                                  IPv6GetNextHeaderFromQueue(q), info->EndPointHeaderSize);
  	}
  
  	// Routing header
  	if (info->RoutingHeader != NULL)
  	{
  		BuildAndAddIPv6PacketOptionHeader(b, info->RoutingHeader,
  		                                  IPv6GetNextHeaderFromQueue(q), info->RoutingHeaderSize);
  	}
  
  	// Fragment header
  	if (info->FragmentHeader != NULL)
  	{
  		info->FragmentHeader->NextHeader = IPv6GetNextHeaderFromQueue(q);
  		WriteBuf(b, info->FragmentHeader, sizeof(IPV6_FRAGMENT_HEADER));
  	}
  
  	bbp = b->Size;
  	if (info->FragmentHeader == NULL)
  	{
  		bbp += sizeof(IPV6_FRAGMENT_HEADER);
  	}
  
  	// Payload
  	if (info->Protocol != IPV6_HEADER_NONE)
  	{
  		WriteBuf(b, info->Payload, info->PayloadSize);
  	}
  
  	ReleaseQueue(q);
  
  	SeekBuf(b, 0, 0);
  
  	// Payload length
  	((IPV6_HEADER *)b->Buf)->PayloadLength = Endian16(b->Size - (USHORT)sizeof(IPV6_HEADER));
  
  	if (bytes_before_payload != NULL)
  	{
  		// Calculate the length just before the payload
  		// (by assuming fragment header is always included)
  		*bytes_before_payload = bbp;
  	}
  
  	return b;
  }
  
  // Build the option values of an ICMPv6 packet
  void BuildICMPv6OptionValue(BUF *b, UCHAR type, void *header_pointer, UINT total_size)
  {
  	UINT packet_size;
  	UCHAR *packet;
  	ICMPV6_OPTION *opt;
  	// Validate arguments
  	if (b == NULL || header_pointer == NULL)
  	{
  		return;
  	}
  
  	packet_size = ((total_size + 7) / 8) * 8;
  	packet = ZeroMalloc(packet_size);
  
  	Copy(packet, header_pointer, total_size);
  	opt = (ICMPV6_OPTION *)packet;
  	opt->Length = (UCHAR)(packet_size / 8);
  	opt->Type = type;
  
  	WriteBuf(b, packet, packet_size);
  
  	Free(packet);
  }
  
  // Build the options of the ICMPv6 packet
  BUF *BuildICMPv6Options(ICMPV6_OPTION_LIST *o)
  {
  	BUF *b;
  	UINT i;
  	// Validate arguments
  	if (o == NULL)
  	{
  		return NULL;
  	}
  
  	b = NewBuf();
  
  	if (o->SourceLinkLayer != NULL)
  	{
  		BuildICMPv6OptionValue(b, ICMPV6_OPTION_TYPE_SOURCE_LINK_LAYER, o->SourceLinkLayer, sizeof(ICMPV6_OPTION_LINK_LAYER));
  	}
  	if (o->TargetLinkLayer != NULL)
  	{
  		BuildICMPv6OptionValue(b, ICMPV6_OPTION_TYPE_TARGET_LINK_LAYER, o->TargetLinkLayer, sizeof(ICMPV6_OPTION_LINK_LAYER));
  	}
  	for (i = 0; i < ICMPV6_OPTION_PREFIXES_MAX_COUNT; i++)
  	{
  		if (o->Prefix[i] != NULL)
  		{
  			BuildICMPv6OptionValue(b, ICMPV6_OPTION_TYPE_PREFIX, o->Prefix[i], sizeof(ICMPV6_OPTION_PREFIX));
  		}
  		else
  		{
  			break;
  		}
  	}
  	if (o->Mtu != NULL)
  	{
  		BuildICMPv6OptionValue(b, ICMPV6_OPTION_TYPE_MTU, o->Mtu, sizeof(ICMPV6_OPTION_MTU));
  	}
  
  	SeekBuf(b, 0, 0);
  
  	return b;
  }
  
  // Checksum calculation (IPv4)
  USHORT CalcChecksumForIPv4(UINT src_ip, UINT dst_ip, UCHAR protocol, void *data, UINT size, UINT real_size)
  {
  	UCHAR *tmp;
  	UINT tmp_size;
  	IPV4_PSEUDO_HEADER *ph;
  	USHORT ret;
  	bool use_free = false;
  	UCHAR tmp_buffer[1600];
  	// Validate arguments
  	if (data == NULL && size != 0)
  	{
  		return 0;
  	}
  
  	if (real_size == 0)
  	{
  		real_size = size;
  	}
  
  	if (real_size == INFINITE)
  	{
  		real_size = 0;
  	}
  
  	tmp_size = size + sizeof(IPV4_PSEUDO_HEADER);
  
  	if (tmp_size > sizeof(tmp_buffer))
  	{
  		tmp = Malloc(tmp_size);
  
  		use_free = true;
  	}
  	else
  	{
  		tmp = tmp_buffer;
  	}
  
  	ph = (IPV4_PSEUDO_HEADER *)tmp;
  	ph->SrcIP = src_ip;
  	ph->DstIP = dst_ip;
  	ph->PacketLength = Endian16(real_size);
  	ph->Protocol = protocol;
  	ph->Reserved = 0;
  
  	if (size >= 1)
  	{
  		Copy(((UCHAR *)tmp) + sizeof(IPV4_PSEUDO_HEADER), data, size);
  	}
  
  	ret = IpChecksum(tmp, tmp_size);
  
  	if (use_free)
  	{
  		Free(tmp);
  	}
  
  	return ret;
  }
  
  // Checksum calculation (IPv6)
  USHORT CalcChecksumForIPv6(IPV6_ADDR *src_ip, IPV6_ADDR *dest_ip, UCHAR protocol, void *data, UINT size, UINT real_size)
  {
  	UCHAR *tmp;
  	UINT tmp_size;
  	IPV6_PSEUDO_HEADER *ph;
  	USHORT ret;
  	bool use_free = false;
  	UCHAR tmp_buffer[256];
  	// Validate arguments
  	if (data == NULL && size != 0)
  	{
  		return 0;
  	}
  
  	if (real_size == 0)
  	{
  		real_size = size;
  	}
  
  	if (real_size == INFINITE)
  	{
  		real_size = 0;
  	}
  
  	tmp_size = size + sizeof(IPV6_PSEUDO_HEADER);
  
  	if (tmp_size > sizeof(tmp_buffer))
  	{
  		tmp = Malloc(tmp_size);
  
  		use_free = true;
  	}
  	else
  	{
  		tmp = tmp_buffer;
  	}
  
  	ph = (IPV6_PSEUDO_HEADER *)tmp;
  	Zero(ph, sizeof(IPV6_PSEUDO_HEADER));
  	Copy(&ph->SrcAddress, src_ip, sizeof(IPV6_ADDR));
  	Copy(&ph->DestAddress, dest_ip, sizeof(IPV6_ADDR));
  	ph->UpperLayerPacketSize = Endian32(real_size);
  	ph->NextHeader = protocol;
  
  	Copy(((UCHAR *)tmp) + sizeof(IPV6_PSEUDO_HEADER), data, size);
  
  	ret = IpChecksum(tmp, tmp_size);
  
  	if (use_free)
  	{
  		Free(tmp);
  	}
  
  	return ret;
  }
  
  // Release the cloned packet
  void FreeClonePacket(PKT *p)
  {
  	// Validate arguments
  	if (p == NULL)
  	{
  		return;
  	}
  
  	Free(p->IPv6HeaderPacketInfo.IPv6Header);
  	Free(p->IPv6HeaderPacketInfo.HopHeader);
  	Free(p->IPv6HeaderPacketInfo.EndPointHeader);
  	Free(p->IPv6HeaderPacketInfo.RoutingHeader);
  	Free(p->IPv6HeaderPacketInfo.FragmentHeader);
  	Free(p->IPv6HeaderPacketInfo.Payload);
  	Free(p->ICMPv6HeaderPacketInfo.Data);
  	Free(p->ICMPv6HeaderPacketInfo.EchoData);
  	Free(p->ICMPv6HeaderPacketInfo.Headers.HeaderPointer);
  	FreeCloneICMPv6Options(&p->ICMPv6HeaderPacketInfo.OptionList);
  	Free(p->L3.PointerL3);
  	Free(p->L4.PointerL4);
  	Free(p->L7.PointerL7);
  	Free(p->PacketData);
  	Free(p->MacHeader);
  	Free(p->HttpLog);
  	Free(p);
  }
  
  // Copy the packet header
  PKT *ClonePacket(PKT *p, bool copy_data)
  {
  	PKT *ret;
  	// Validate arguments
  	if (p == NULL)
  	{
  		return NULL;
  	}
  
  	ret = ZeroMallocFast(sizeof(PKT));
  	ret->PacketSize = p->PacketSize;
  
  	// Copy of the MAC header
  	ret->MacHeader = MallocFast(sizeof(MAC_HEADER));
  	Copy(ret->MacHeader, p->MacHeader, sizeof(MAC_HEADER));
  
  	// Copy of the MAC flag
  	ret->BroadcastPacket = p->BroadcastPacket;
  	ret->InvalidSourcePacket = p->InvalidSourcePacket;
  
  	// Copy of the IPv6 related structure
  	Copy(&ret->IPv6HeaderPacketInfo, &p->IPv6HeaderPacketInfo, sizeof(IPV6_HEADER_PACKET_INFO));
  	Copy(&ret->ICMPv6HeaderPacketInfo, &p->ICMPv6HeaderPacketInfo, sizeof(ICMPV6_HEADER_INFO));
  
  	// Layer 3
  	ret->TypeL3 = p->TypeL3;
  	switch (ret->TypeL3)
  	{
  	case L3_ARPV4:
  		// ARP packet
  		ret->L3.ARPv4Header = MallocFast(sizeof(ARPV4_HEADER));
  		Copy(ret->L3.ARPv4Header, p->L3.ARPv4Header, sizeof(ARPV4_HEADER));
  		break;
  
  	case L3_IPV4:
  		// IPv4 packet
  		ret->L3.IPv4Header = MallocFast(sizeof(IPV4_HEADER));
  		Copy(ret->L3.IPv4Header, p->L3.IPv4Header, sizeof(IPV4_HEADER));
  		break;
  
  	case L3_IPV6:
  		// IPv6 packet
  		ret->L3.IPv6Header = MallocFast(sizeof(IPV6_HEADER));
  		Copy(ret->L3.IPv6Header, p->L3.IPv6Header, sizeof(IPV6_HEADER));
  
  		ret->IPv6HeaderPacketInfo.IPv6Header = Clone(p->IPv6HeaderPacketInfo.IPv6Header,
  		                                       sizeof(IPV6_HEADER));
  
  		ret->IPv6HeaderPacketInfo.HopHeader = Clone(p->IPv6HeaderPacketInfo.HopHeader,
  		                                      sizeof(IPV6_OPTION_HEADER));
  
  		ret->IPv6HeaderPacketInfo.EndPointHeader = Clone(p->IPv6HeaderPacketInfo.EndPointHeader,
  		        sizeof(IPV6_OPTION_HEADER));
  
  		ret->IPv6HeaderPacketInfo.RoutingHeader = Clone(p->IPv6HeaderPacketInfo.RoutingHeader,
  		        sizeof(IPV6_OPTION_HEADER));
  
  		ret->IPv6HeaderPacketInfo.FragmentHeader = Clone(p->IPv6HeaderPacketInfo.FragmentHeader,
  		        sizeof(IPV6_FRAGMENT_HEADER));
  
  		ret->IPv6HeaderPacketInfo.Payload = Clone(p->IPv6HeaderPacketInfo.Payload,
  		                                    p->IPv6HeaderPacketInfo.PayloadSize);
  		break;
  	}
  
  	// Layer 4
  	ret->TypeL4 = p->TypeL4;
  	switch (ret->TypeL4)
  	{
  	case L4_ICMPV4:
  		// ICMPv4 packet
  		ret->L4.ICMPHeader = MallocFast(sizeof(ICMP_HEADER));
  		Copy(ret->L4.ICMPHeader, p->L4.ICMPHeader, sizeof(ICMP_HEADER));
  		break;
  
  	case L4_ICMPV6:
  		// ICMPv6 packet
  		ret->L4.ICMPHeader = MallocFast(sizeof(ICMP_HEADER));
  		Copy(ret->L4.ICMPHeader, p->L4.ICMPHeader, sizeof(ICMP_HEADER));
  
  		ret->ICMPv6HeaderPacketInfo.Data = Clone(p->ICMPv6HeaderPacketInfo.Data,
  		                                   p->ICMPv6HeaderPacketInfo.DataSize);
  
  		ret->ICMPv6HeaderPacketInfo.EchoData = Clone(p->ICMPv6HeaderPacketInfo.EchoData,
  		                                       p->ICMPv6HeaderPacketInfo.EchoDataSize);
  
  		switch (ret->ICMPv6HeaderPacketInfo.Type)
  		{
  		case ICMPV6_TYPE_ECHO_REQUEST:
  		case ICMPV6_TYPE_ECHO_RESPONSE:
  			break;
  
  		case ICMPV6_TYPE_ROUTER_SOLICIATION:
  			ret->ICMPv6HeaderPacketInfo.Headers.RouterSoliciationHeader =
  			    Clone(p->ICMPv6HeaderPacketInfo.Headers.RouterSoliciationHeader,
  			          sizeof(ICMPV6_ROUTER_SOLICIATION_HEADER));
  			break;
  
  		case ICMPV6_TYPE_ROUTER_ADVERTISEMENT:
  			ret->ICMPv6HeaderPacketInfo.Headers.RouterAdvertisementHeader =
  			    Clone(p->ICMPv6HeaderPacketInfo.Headers.RouterAdvertisementHeader,
  			          sizeof(ICMPV6_ROUTER_ADVERTISEMENT_HEADER));
  			break;
  
  		case ICMPV6_TYPE_NEIGHBOR_SOLICIATION:
  			ret->ICMPv6HeaderPacketInfo.Headers.NeighborSoliciationHeader =
  			    Clone(p->ICMPv6HeaderPacketInfo.Headers.NeighborSoliciationHeader,
  			          sizeof(ICMPV6_NEIGHBOR_SOLICIATION_HEADER));
  			break;
  
  		case ICMPV6_TYPE_NEIGHBOR_ADVERTISEMENT:
  			ret->ICMPv6HeaderPacketInfo.Headers.NeighborAdvertisementHeader =
  			    Clone(p->ICMPv6HeaderPacketInfo.Headers.NeighborAdvertisementHeader,
  			          sizeof(ICMPV6_NEIGHBOR_ADVERTISEMENT_HEADER));
  			break;
  		}
  
  		CloneICMPv6Options(&ret->ICMPv6HeaderPacketInfo.OptionList,
  		                   &p->ICMPv6HeaderPacketInfo.OptionList);
  		break;
  
  	case L4_TCP:
  		// TCP packet
  		ret->L4.TCPHeader = MallocFast(sizeof(TCP_HEADER));
  		Copy(ret->L4.TCPHeader, p->L4.TCPHeader, sizeof(TCP_HEADER));
  		break;
  
  	case L4_UDP:
  		// UDP packet
  		ret->L4.UDPHeader = MallocFast(sizeof(UDP_HEADER));
  		Copy(ret->L4.UDPHeader, p->L4.UDPHeader, sizeof(UDP_HEADER));
  		break;
  	}
  
  	// Layer 7
  	ret->TypeL7 = p->TypeL7;
  	switch (ret->TypeL7)
  	{
  	case L7_DHCPV4:
  		// DHCP packet
  		ret->L7.DHCPv4Header = MallocFast(sizeof(DHCPV4_HEADER));
  		Copy(ret->L7.DHCPv4Header, p->L7.DHCPv4Header, sizeof(DHCPV4_HEADER));
  		break;
  
  	case L7_IKECONN:
  		// IKE packet
  		ret->L7.IkeHeader = MallocFast(sizeof(IKE_HEADER));
  		Copy(ret->L7.IkeHeader, p->L7.IkeHeader, sizeof(IKE_HEADER));
  		break;
  
  	case L7_DNS:
  		StrCpy(ret->DnsQueryHost, sizeof(ret->DnsQueryHost), p->DnsQueryHost);
  		break;
  	}
  
  	// Address data
  	ret->MacAddressSrc = ret->MacHeader->SrcAddress;
  	ret->MacAddressDest = ret->MacHeader->DestAddress;
  
  	if (copy_data)
  	{
  		// Copy also the packet body
  		ret->PacketData = MallocFast(p->PacketSize);
  		Copy(ret->PacketData, p->PacketData, p->PacketSize);
  	}
  
  	if (p->HttpLog != NULL)
  	{
  		ret->HttpLog = Clone(p->HttpLog, sizeof(HTTPLOG));
  	}
  
  	return ret;
  }
  
  // Parse the packet but without data layer except for ICMP
  PKT *ParsePacketUpToICMPv6(UCHAR *buf, UINT size)
  {
  	return ParsePacketEx5(buf, size, false, 0, true, true, false, true);
  }
  
  // Parse the contents of the packet
  PKT *ParsePacket(UCHAR *buf, UINT size)
  {
  	return ParsePacketEx(buf, size, false);
  }
  PKT *ParsePacketEx(UCHAR *buf, UINT size, bool no_l3)
  {
  	return ParsePacketEx2(buf, size, no_l3, 0);
  }
  PKT *ParsePacketEx2(UCHAR *buf, UINT size, bool no_l3, UINT vlan_type_id)
  {
  	return ParsePacketEx3(buf, size, no_l3, vlan_type_id, true);
  }
  PKT *ParsePacketEx3(UCHAR *buf, UINT size, bool no_l3, UINT vlan_type_id, bool bridge_id_as_mac_address)
  {
  	return ParsePacketEx4(buf, size, no_l3, vlan_type_id, bridge_id_as_mac_address, false, false);
  }
  PKT *ParsePacketEx4(UCHAR *buf, UINT size, bool no_l3, UINT vlan_type_id, bool bridge_id_as_mac_address, bool no_http, bool correct_checksum)
  {
  	return ParsePacketEx5(buf, size, no_l3, vlan_type_id, bridge_id_as_mac_address, no_http, correct_checksum, false);
  }
  PKT *ParsePacketEx5(UCHAR *buf, UINT size, bool no_l3, UINT vlan_type_id, bool bridge_id_as_mac_address, bool no_http, bool correct_checksum, bool no_l3_l4_except_icmpv6)
  {
  	PKT *p;
  	USHORT vlan_type_id_16;
  	// Validate arguments
  	if (buf == NULL || size == 0)
  	{
  		return NULL;
  	}
  
  	if (vlan_type_id == 0)
  	{
  		vlan_type_id = MAC_PROTO_TAGVLAN;
  	}
  
  	vlan_type_id_16 = Endian16((USHORT)vlan_type_id);
  
  	p = ZeroMallocFast(sizeof(PKT));
  
  	p->VlanTypeID = vlan_type_id;
  
  	// If there is garbage after the payload in IPv4 and IPv6 packets, eliminate it
  	if (size >= 24)
  	{
  		if (buf[12] == 0x08 && buf[13] == 0x00)
  		{
  			USHORT ip_total_size2 = READ_USHORT(&buf[16]);
  			UINT mac_packet_size;
  
  			if (ip_total_size2 >= 1)
  			{
  				mac_packet_size = (UINT)ip_total_size2 + 14;
  
  				if (size > mac_packet_size)
  				{
  					size = mac_packet_size;
  				}
  			}
  		}
  		else if (buf[12] == 0x86 && buf[13] == 0xdd)
  		{
  			USHORT ip_payload_size_2 = READ_USHORT(&buf[18]);
  			UINT mac_packet_size;
  
  			if (ip_payload_size_2 >= 1)
  			{
  				mac_packet_size = (UINT)ip_payload_size_2 + 14 + 40;
  
  				if (size > mac_packet_size)
  				{
  					size = mac_packet_size;
  				}
  			}
  		}
  		else if (buf[12] == ((UCHAR *)&vlan_type_id_16)[0] && buf[13] == ((UCHAR *)&vlan_type_id_16)[1])
  		{
  			if (buf[16] == 0x08 && buf[17] == 0x00)
  			{
  				USHORT ip_total_size2 = READ_USHORT(&buf[20]);
  				UINT mac_packet_size;
  
  				if (ip_total_size2 >= 1)
  				{
  					mac_packet_size = (UINT)ip_total_size2 + 14 + 4;
  
  					if (size > mac_packet_size)
  					{
  						size = mac_packet_size;
  					}
  				}
  			}
  			else if (buf[16] == 0x86 && buf[17] == 0xdd)
  			{
  				USHORT ip_payload_size_2 = READ_USHORT(&buf[22]);
  				UINT mac_packet_size;
  
  				if (ip_payload_size_2 >= 1)
  				{
  					mac_packet_size = (UINT)ip_payload_size_2 + 14 + 40 + 4;
  
  					if (size > mac_packet_size)
  					{
  						size = mac_packet_size;
  					}
  				}
  			}
  		}
  	}
  
  	// Do parse
  	if (ParsePacketL2Ex(p, buf, size, no_l3, no_l3_l4_except_icmpv6) == false)
  	{
  		// Parsing failure
  		FreePacket(p);
  		return NULL;
  	}
  
  	p->PacketData = buf;
  	p->PacketSize = size;
  
  	p->MacAddressSrc = p->MacHeader->SrcAddress;
  	p->MacAddressDest = p->MacHeader->DestAddress;
  
  	if (bridge_id_as_mac_address)
  	{
  		if (p->TypeL3 == L3_BPDU)
  		{
  			if (p->L3.BpduHeader != NULL)
  			{
  				p->MacAddressSrc = p->L3.BpduHeader->BridgeMacAddress;
  			}
  		}
  	}
  
  	if (no_http == false)
  	{
  		USHORT port_raw = Endian16(80);
  		USHORT port_raw2 = Endian16(8080);
  		USHORT port_raw3 = Endian16(443);
  		USHORT port_raw4 = Endian16(3128);
  
  		// Analyze if the packet is a part of HTTP
  		if ((p->TypeL3 == L3_IPV4 || p->TypeL3 == L3_IPV6) && p->TypeL4 == L4_TCP)
  		{
  			TCP_HEADER *tcp = p->L4.TCPHeader;
  			if (tcp != NULL && (tcp->DstPort == port_raw || tcp->DstPort == port_raw2 || tcp->DstPort == port_raw4) &&
  			        (!((tcp->Flag & TCP_SYN) || (tcp->Flag & TCP_RST) || (tcp->Flag & TCP_FIN))))
  			{
  				if (p->PayloadSize >= 1)
  				{
  					p->HttpLog = ParseHttpAccessLog(p);
  				}
  			}
  			if (tcp != NULL && tcp->DstPort == port_raw3 &&
  			        (!((tcp->Flag & TCP_SYN) || (tcp->Flag & TCP_RST) || (tcp->Flag & TCP_FIN))))
  			{
  				if (p->PayloadSize >= 1)
  				{
  					p->HttpLog = ParseHttpsAccessLog(p);
  				}
  			}
  		}
  	}
  
  	if (p->TypeL3 == L3_IPV4 && p->TypeL4 == L4_UDP && p->TypeL7 == L7_DHCPV4)
  	{
  		// Get the DHCP opcode
  		DHCPV4_DATA *d = ParseDHCPv4Data(p);
  
  		if (d != NULL)
  		{
  			p->DhcpOpCode = d->OpCode;
  
  			FreeDHCPv4Data(d);
  		}
  	}
  
  	if (correct_checksum)
  	{
  		// Correct the checksum of the UDP, IP and TCP
  		CorrectChecksum(p);
  	}
  
  	// Parsing success
  	return p;
  }
  
  // Correct the checksum (store the correct value in the header by recalculating the checksum which is by off-load processing)
  void CorrectChecksum(PKT *p)
  {
  	// Validate arguments
  	if (p == NULL)
  	{
  		return;
  	}
  
  	if (p->TypeL3 == L3_IPV4)
  	{
  		IPV4_HEADER *v4 = p->L3.IPv4Header;
  
  		if (v4 != NULL)
  		{
  			if (v4->Checksum == 0x0000)
  			{
  				v4->Checksum = IpChecksum(v4, IPV4_GET_HEADER_LEN(v4) * 4);
  			}
  
  			if (p->TypeL4 == L4_TCP)
  			{
  				// Recalculate the TCP checksum
  				if (IPV4_GET_OFFSET(v4) == 0 && (IPV4_GET_FLAGS(v4) & 0x01) == 0)
  				{
  					// TCP checksuming doesn't target fragmented IP packets
  					TCP_HEADER *tcp = p->L4.TCPHeader;
  
  					if (tcp != NULL)
  					{
  						USHORT tcp_offloading_checksum1 = CalcChecksumForIPv4(v4->SrcIP, v4->DstIP, IP_PROTO_TCP, NULL, 0, p->IPv4PayloadSize);
  						USHORT tcp_offloading_checksum2 = ~tcp_offloading_checksum1;
  
  						if (tcp->Checksum == 0 || tcp->Checksum == tcp_offloading_checksum1 || tcp->Checksum == tcp_offloading_checksum2)
  						{
  							tcp->Checksum = 0;
  							tcp->Checksum = CalcChecksumForIPv4(v4->SrcIP, v4->DstIP, IP_PROTO_TCP, tcp, p->IPv4PayloadSize, 0);
  						}
  					}
  				}
  			}
  
  			if (p->TypeL4 == L4_UDP)
  			{
  				// Recalculation of the UDP checksum
  				if (IPV4_GET_OFFSET(v4) == 0 || (IPV4_GET_FLAGS(v4) & 0x01) == 0)
  				{
  					// If it is not divided, or it is divided but it is the first fragment of the UDP packet
  					UDP_HEADER *udp = p->L4.UDPHeader;
  
  					if (udp != NULL && udp->Checksum != 0)
  					{
  						USHORT udp_len = Endian16(udp->PacketLength);
  						USHORT udp_offloading_checksum1 = CalcChecksumForIPv4(v4->SrcIP, v4->DstIP, IP_PROTO_UDP, NULL, 0, udp_len);
  						USHORT udp_offloading_checksum2 = ~udp_offloading_checksum1;
  
  						if (udp->Checksum == udp_offloading_checksum1 || udp->Checksum == udp_offloading_checksum2)
  						{
  							udp->Checksum = 0;
  
  							if ((IPV4_GET_FLAGS(v4) & 0x01) == 0 && (p->IPv4PayloadSize >= udp_len))
  							{
  								// Calculate the checksum correctly based on the data in case of a non-fragmented packet
  								udp->Checksum = CalcChecksumForIPv4(v4->SrcIP, v4->DstIP, IP_PROTO_UDP, udp, udp_len, 0);
  							}
  							else
  							{
  								// In case of the first fragment of the packet, set the checksum to 0
  								// because there isn't entire data of the packet
  								udp->Checksum = 0;
  							}
  						}
  					}
  				}
  			}
  		}
  	}
  	else if (p->TypeL3 == L3_IPV6)
  	{
  		IPV6_HEADER *v6 = p->L3.IPv6Header;
  		IPV6_HEADER_PACKET_INFO *v6info = &p->IPv6HeaderPacketInfo;
  
  		if (v6 != NULL)
  		{
  			if (p->TypeL4 == L4_TCP)
  			{
  				// Recalculate the TCP checksum
  				if (v6info->IsFragment == false)
  				{
  					if (v6info->FragmentHeader == NULL || ((IPV6_GET_FLAGS(v6info->FragmentHeader) & IPV6_FRAGMENT_HEADER_FLAG_MORE_FRAGMENTS) == 0))
  					{
  						// TCP checksuming doesn't target fragmented packets
  						TCP_HEADER *tcp = p->L4.TCPHeader;
  
  						if (tcp != NULL)
  						{
  							USHORT tcp_offloading_checksum1 = CalcChecksumForIPv6(&v6->SrcAddress, &v6->DestAddress, IP_PROTO_TCP, NULL, 0, v6info->PayloadSize);
  							USHORT tcp_offloading_checksum2 = ~tcp_offloading_checksum1;
  
  							if (tcp->Checksum == 0 || tcp->Checksum == tcp_offloading_checksum1 || tcp->Checksum == tcp_offloading_checksum2)
  							{
  								tcp->Checksum = 0;
  								tcp->Checksum = CalcChecksumForIPv6(&v6->SrcAddress, &v6->DestAddress, IP_PROTO_TCP, tcp, v6info->PayloadSize, 0);
  							}
  						}
  					}
  				}
  			}
  			else if (p->TypeL4 == L4_UDP)
  			{
  				// Recalculation of the UDP checksum
  				if (v6info->IsFragment == false)
  				{
  					UDP_HEADER *udp = p->L4.UDPHeader;
  
  					if (udp != NULL && udp->Checksum != 0)
  					{
  						USHORT udp_len = Endian16(udp->PacketLength);
  						USHORT udp_offloading_checksum1 = CalcChecksumForIPv6(&v6->SrcAddress, &v6->DestAddress, IP_PROTO_UDP, NULL, 0, udp_len);
  						USHORT udp_offloading_checksum2 = ~udp_offloading_checksum1;
  
  						if (udp->Checksum == 0 || udp->Checksum == udp_offloading_checksum1 || udp->Checksum == udp_offloading_checksum2)
  						{
  							udp->Checksum = 0;
  
  							if ((v6info->FragmentHeader == NULL || ((IPV6_GET_FLAGS(v6info->FragmentHeader) & IPV6_FRAGMENT_HEADER_FLAG_MORE_FRAGMENTS) == 0)) && (v6info->PayloadSize >= udp_len))
  							{
  								// If the packet is not fragmented, recalculate the checksum
  								udp->Checksum = CalcChecksumForIPv6(&v6->SrcAddress, &v6->DestAddress, IP_PROTO_UDP, udp, udp_len, 0);
  							}
  							else
  							{
  								// Don't do (can't do) anything in the case of fragmented packet
  							}
  						}
  					}
  				}
  			}
  		}
  	}
  }
  
  
  // Parse the HTTPS access log
  HTTPLOG *ParseHttpsAccessLog(PKT *pkt)
  {
  	HTTPLOG h;
  	char sni[MAX_PATH];
  	// Validate arguments
  	if (pkt == NULL)
  	{
  		return NULL;
  	}
  
  	if (GetSniNameFromSslPacket(pkt->Payload, pkt->PayloadSize, sni, sizeof(sni)) == false)
  	{
  		return NULL;
  	}
  
  	Zero(&h, sizeof(h));
  
  	StrCpy(h.Method, sizeof(h.Method), "SSL_Connect");
  	StrCpy(h.Hostname, sizeof(h.Hostname), sni);
  	h.Port = Endian16(pkt->L4.TCPHeader->DstPort);
  	StrCpy(h.Path, sizeof(h.Path), "/");
  	h.IsSsl = true;
  
  	return Clone(&h, sizeof(h));
  }
  
  // Parse the HTTP access log
  HTTPLOG *ParseHttpAccessLog(PKT *pkt)
  {
  	HTTPLOG h;
  	UCHAR *buf;
  	UINT size;
  	BUF *b;
  	char *line1;
  	bool ok = false;
  	// Validate arguments
  	if (pkt == NULL)
  	{
  		return NULL;
  	}
  
  	buf = pkt->Payload;
  	size = pkt->PayloadSize;
  
  	if (size <= 5)
  	{
  		return NULL;
  	}
  
  	// Check whether it starts with the HTTP-specific string
  	if (CmpCaseIgnore(buf, "GET ", 4) != 0 &&
  	        CmpCaseIgnore(buf, "HEAD ", 5) != 0 &&
  	        CmpCaseIgnore(buf, "POST ", 5) != 0)
  	{
  		return NULL;
  	}
  
  	Zero(&h, sizeof(h));
  
  	h.Port = Endian16(pkt->L4.TCPHeader->DstPort);
  
  	b = NewBuf();
  	WriteBuf(b, buf, size);
  	SeekBuf(b, 0, 0);
  
  	line1 = CfgReadNextLine(b);
  
  	if (line1 != NULL)
  	{
  		TOKEN_LIST *tokens = ParseToken(line1, " \t");
  		if (tokens != NULL)
  		{
  			if (tokens->NumTokens == 3)
  			{
  				StrCpy(h.Method, sizeof(h.Hostname), tokens->Token[0]);
  				Trim(h.Method);
  
  				StrCpy(h.Path, sizeof(h.Path), tokens->Token[1]);
  				Trim(h.Path);
  
  				StrCpy(h.Protocol, sizeof(h.Protocol), tokens->Token[2]);
  				Trim(h.Protocol);
  
  				StrUpper(h.Method);
  
  				while (true)
  				{
  					char *line = CfgReadNextLine(b);
  					UINT i;
  
  					if (line == NULL)
  					{
  						break;
  					}
  
  					i = SearchStr(line, ":", 0);
  					if (i != INFINITE && i < (MAX_SIZE / 2))
  					{
  						char name[MAX_SIZE];
  						char value[MAX_SIZE];
  
  						StrCpy(name, sizeof(name), line);
  						name[i] = 0;
  						Trim(name);
  
  						StrCpy(value, sizeof(value), line + i + 1);
  						Trim(value);
  
  						if (StrCmpi(name, "host") == 0)
  						{
  							StrCpy(h.Hostname, sizeof(h.Hostname), value);
  						}
  						else if (StrCmpi(name, "referer") == 0)
  						{
  							StrCpy(h.Referer, sizeof(h.Referer), value);
  						}
  						else if (StrCmpi(name, "user-agent") == 0)
  						{
  							StrCpy(h.UserAgent, sizeof(h.UserAgent), value);
  						}
  					}
  
  					Free(line);
  				}
  
  				if (IsEmptyStr(h.Hostname) == false)
  				{
  					ok = true;
  				}
  			}
  			FreeToken(tokens);
  		}
  	}
  
  	Free(line1);
  	FreeBuf(b);
  
  	if (ok)
  	{
  		return Clone(&h, sizeof(h));
  	}
  	else
  	{
  		return NULL;
  	}
  }
  
  
  // Layer-2 parsing
  bool ParsePacketL2Ex(PKT *p, UCHAR *buf, UINT size, bool no_l3, bool no_l3_l4_except_icmpv6)
  {
  	UINT i;
  	bool b1, b2;
  	USHORT type_id_16;
  	// Validate arguments
  	if (p == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	// Check the size
  	if (size < sizeof(MAC_HEADER))
  	{
  		return false;
  	}
  
  	// MAC header
  	p->MacHeader = (MAC_HEADER *)buf;
  
  	buf += sizeof(MAC_HEADER);
  	size -= sizeof(MAC_HEADER);
  
  	// Analysis of the MAC header
  	p->BroadcastPacket = true;
  	b1 = true;
  	b2 = true;
  	for (i = 0; i < 6; i++)
  	{
  		if (p->MacHeader->DestAddress[i] != 0xff)
  		{
  			p->BroadcastPacket = false;
  		}
  		if (p->MacHeader->SrcAddress[i] != 0xff)
  		{
  			b1 = false;
  		}
  		if (p->MacHeader->SrcAddress[i] != 0x00)
  		{
  			b2 = false;
  		}
  	}
  	if (b1 || b2 || (memcmp(p->MacHeader->SrcAddress, p->MacHeader->DestAddress, 6) == 0))
  	{
  		p->InvalidSourcePacket = true;
  	}
  	else
  	{
  		p->InvalidSourcePacket = false;
  	}
  
  	if (p->MacHeader->DestAddress[0] & 0x01)
  	{
  		p->BroadcastPacket = true;
  	}
  
  	// Parse L3 packet
  	type_id_16 = Endian16(p->MacHeader->Protocol);
  
  	if (type_id_16 > 1500)
  	{
  		// Ordinary Ethernet frame
  		switch (type_id_16)
  		{
  		case MAC_PROTO_ARPV4:	// ARPv4
  			if (no_l3 || no_l3_l4_except_icmpv6)
  			{
  				return true;
  			}
  
  			return ParsePacketARPv4(p, buf, size);
  
  		case MAC_PROTO_IPV4:	// IPv4
  			if (no_l3 || no_l3_l4_except_icmpv6)
  			{
  				return true;
  			}
  
  			return ParsePacketIPv4(p, buf, size);
  
  		case MAC_PROTO_IPV6:	// IPv6
  			if (no_l3)
  			{
  				return true;
  			}
  
  			return ParsePacketIPv6(p, buf, size, no_l3_l4_except_icmpv6);
  
  		default:				// Unknown
  			if (type_id_16 == p->VlanTypeID)
  			{
  				// VLAN
  				return ParsePacketTAGVLAN(p, buf, size);
  			}
  			else
  			{
  				return true;
  			}
  		}
  	}
  	else
  	{
  		// Old IEEE 802.3 frame (payload length of the packet is written in the header)
  		// (It has been used in the BPDU, etc.)
  		UINT length = (UINT)type_id_16;
  		LLC_HEADER *llc;
  
  		// Check whether the length is remaining
  		if (size < length || size < sizeof(LLC_HEADER))
  		{
  			return true;
  		}
  
  		// Read an LLC header
  		llc = (LLC_HEADER *)buf;
  		buf += sizeof(LLC_HEADER);
  		size -= sizeof(LLC_HEADER);
  
  		// Determine the protocol by the value of DSAP and SSAP
  		if (llc->Dsap == LLC_DSAP_BPDU && llc->Ssap == LLC_SSAP_BPDU)
  		{
  			// This is a BPDU (Spanning Tree)
  			return ParsePacketBPDU(p, buf, size);
  		}
  		else
  		{
  			// Unknown protocol
  			return true;
  		}
  	}
  }
  
  // TAG VLAN parsing
  bool ParsePacketTAGVLAN(PKT *p, UCHAR *buf, UINT size)
  {
  	USHORT vlan_ushort;
  	// Validate arguments
  	if (p == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	// Check the size
  	if (size < sizeof(TAGVLAN_HEADER))
  	{
  		return false;
  	}
  
  	// TAG VLAN header
  	p->L3.TagVlanHeader = (TAGVLAN_HEADER *)buf;
  	p->TypeL3 = L3_TAGVLAN;
  
  	buf += sizeof(TAGVLAN_HEADER);
  	size -= sizeof(TAGVLAN_HEADER);
  
  	vlan_ushort = READ_USHORT(p->L3.TagVlanHeader->Data);
  	vlan_ushort = vlan_ushort & 0xFFF;
  
  	p->VlanId = vlan_ushort;
  
  	return true;
  }
  
  // BPDU Parsing
  bool ParsePacketBPDU(PKT *p, UCHAR *buf, UINT size)
  {
  	// Validate arguments
  	if (p == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	// Check the size
  	if (size < sizeof(BPDU_HEADER))
  	{
  		return true;
  	}
  
  	// BPDU header
  	p->L3.BpduHeader = (BPDU_HEADER *)buf;
  	p->TypeL3 = L3_BPDU;
  
  	buf += sizeof(BPDU_HEADER);
  	size -= sizeof(BPDU_HEADER);
  
  	return true;
  }
  
  // ARPv4 Parsing
  bool ParsePacketARPv4(PKT *p, UCHAR *buf, UINT size)
  {
  	// Validate arguments
  	if (p == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	// Check the size
  	if (size < sizeof(ARPV4_HEADER))
  	{
  		return false;
  	}
  
  	// ARPv4 header
  	p->L3.ARPv4Header = (ARPV4_HEADER *)buf;
  	p->TypeL3 = L3_ARPV4;
  
  	buf += sizeof(ARPV4_HEADER);
  	size -= sizeof(ARPV4_HEADER);
  
  	return true;
  }
  
  // Analysis of the IPv6 extension header
  bool ParseIPv6ExtHeader(IPV6_HEADER_PACKET_INFO *info, UCHAR next_header, UCHAR *buf, UINT size)
  {
  	bool ret = false;
  	IPV6_OPTION_HEADER *option_header;
  	UINT option_header_size;
  	UCHAR next_header_2 = IPV6_HEADER_NONE;
  	// Validate arguments
  	if (info == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	info->IsFragment = false;
  
  	while (true)
  	{
  		if (size > 8)
  		{
  			next_header_2 = *((UCHAR *)buf);
  		}
  
  		switch (next_header)
  		{
  		case IPV6_HEADER_HOP:
  		case IPV6_HEADER_ENDPOINT:
  		case IPV6_HEADER_ROUTING:
  			// Variable-length header
  			if (size < 8)
  			{
  				return false;
  			}
  
  			option_header = (IPV6_OPTION_HEADER *)buf;
  			option_header_size = (option_header->Size + 1) * 8;
  			if (size < option_header_size)
  			{
  				return false;
  			}
  
  			switch (next_header)
  			{
  			case IPV6_HEADER_HOP:
  				info->HopHeader = (IPV6_OPTION_HEADER *)buf;
  				info->HopHeaderSize = option_header_size;
  				break;
  
  			case IPV6_HEADER_ENDPOINT:
  				info->EndPointHeader = (IPV6_OPTION_HEADER *)buf;
  				info->EndPointHeaderSize = option_header_size;
  				break;
  
  			case IPV6_HEADER_ROUTING:
  				info->RoutingHeader = (IPV6_OPTION_HEADER *)buf;
  				info->RoutingHeaderSize = option_header_size;
  				break;
  			}
  
  			buf += option_header_size;
  			size -= option_header_size;
  			break;
  
  		case IPV6_HEADER_FRAGMENT:
  			// Fragment header (fixed length)
  			if (size < sizeof(IPV6_FRAGMENT_HEADER))
  			{
  				return false;
  			}
  
  			info->FragmentHeader = (IPV6_FRAGMENT_HEADER *)buf;
  
  			if (IPV6_GET_FRAGMENT_OFFSET(info->FragmentHeader) != 0)
  			{
  				info->IsFragment = true;
  			}
  
  			buf += sizeof(IPV6_FRAGMENT_HEADER);
  			size -= sizeof(IPV6_FRAGMENT_HEADER);
  			break;
  
  		default:
  			// Considered that the payload follows
  			if (next_header != IPV6_HEADER_NONE)
  			{
  				info->Payload = buf;
  				info->PayloadSize = size;
  			}
  			else
  			{
  				info->Payload = NULL;
  				info->PayloadSize = 0;
  			}
  			info->Protocol = next_header;
  			return true;
  		}
  
  		next_header = next_header_2;
  	}
  }
  
  // Analysis of the IPv6 header
  bool ParsePacketIPv6Header(IPV6_HEADER_PACKET_INFO *info, UCHAR *buf, UINT size)
  {
  	// Validate arguments
  	if (info == NULL || buf == NULL)
  	{
  		Zero(info, sizeof(IPV6_HEADER_PACKET_INFO));
  		return false;
  	}
  
  	Zero(info, sizeof(IPV6_HEADER_PACKET_INFO));
  
  	// IPv6 header
  	if (size < sizeof(IPV6_HEADER))
  	{
  		// Invalid size
  		return false;
  	}
  
  	info->IPv6Header = (IPV6_HEADER *)buf;
  	buf += sizeof(IPV6_HEADER);
  	size -= sizeof(IPV6_HEADER);
  
  	if (IPV6_GET_VERSION(info->IPv6Header) != 6)
  	{
  		// Invalid version
  		return false;
  	}
  
  	// Analysis of the extension header
  	if (ParseIPv6ExtHeader(info, info->IPv6Header->NextHeader, buf, size) == false)
  	{
  		return false;
  	}
  
  	// Record the header size
  	if (info->Payload != NULL)
  	{
  		info->TotalHeaderSize = (UINT)((UINT64)(info->Payload) - (UINT64)(info->IPv6Header));
  	}
  
  	return true;
  }
  
  // Analyse the options of ICMPv6 packet
  bool ParseICMPv6Options(ICMPV6_OPTION_LIST *o, UCHAR *buf, UINT size)
  {
  	// Validate arguments
  	if (o == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	Zero(o, sizeof(ICMPV6_OPTION_LIST));
  
  	// Read the header part
  	while (true)
  	{
  		ICMPV6_OPTION *option_header;
  		UINT header_total_size;
  		UCHAR *header_pointer;
  		if (size < sizeof(ICMPV6_OPTION))
  		{
  			// Size shortage
  			return true;
  		}
  
  		option_header = (ICMPV6_OPTION *)buf;
  		// Calculate the entire header size
  		header_total_size = option_header->Length * 8;
  		if (header_total_size == 0)
  		{
  			// The size is zero
  			return true;
  		}
  		if (size < header_total_size)
  		{
  			// Size shortage
  			return true;
  		}
  
  		header_pointer = buf;
  		buf += header_total_size;
  		size -= header_total_size;
  
  		switch (option_header->Type)
  		{
  		case ICMPV6_OPTION_TYPE_SOURCE_LINK_LAYER:
  		case ICMPV6_OPTION_TYPE_TARGET_LINK_LAYER:
  			// Source or target link-layer option
  			if (header_total_size >= sizeof(ICMPV6_OPTION_LINK_LAYER))
  			{
  				if (option_header->Type == ICMPV6_OPTION_TYPE_SOURCE_LINK_LAYER)
  				{
  					o->SourceLinkLayer = (ICMPV6_OPTION_LINK_LAYER *)header_pointer;
  				}
  				else
  				{
  					o->TargetLinkLayer = (ICMPV6_OPTION_LINK_LAYER *)header_pointer;
  				}
  			}
  			else
  			{
  				// ICMPv6 packet corruption?
  				return false;
  			}
  			break;
  
  		case ICMPV6_OPTION_TYPE_PREFIX:
  			// Prefix Information
  			if (header_total_size >= sizeof(ICMPV6_OPTION_PREFIX))
  			{
  				UINT i;
  				for (i = 0; i < ICMPV6_OPTION_PREFIXES_MAX_COUNT; i++)
  				{
  					if (o->Prefix[i] == NULL)
  					{
  						o->Prefix[i] = (ICMPV6_OPTION_PREFIX *)header_pointer;
  						break;
  					}
  				}
  			}
  			else
  			{
  				// ICMPv6 packet corruption?
  			}
  			break;
  
  		case ICMPV6_OPTION_TYPE_MTU:
  			// MTU
  			if (header_total_size >= sizeof(ICMPV6_OPTION_MTU))
  			{
  				o->Mtu = (ICMPV6_OPTION_MTU *)header_pointer;
  			}
  			else
  			{
  				// ICMPv6 packet corruption?
  			}
  			break;
  		}
  	}
  }
  
  // ICMPv6 parsing
  bool ParseICMPv6(PKT *p, UCHAR *buf, UINT size)
  {
  	ICMPV6_HEADER_INFO icmp_info;
  	ICMP_HEADER *icmp;
  	ICMP_ECHO *echo;
  	UINT msg_size;
  	// Validate arguments
  	if (p == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	Zero(&icmp_info, sizeof(icmp_info));
  
  	if (size < sizeof(ICMP_HEADER))
  	{
  		return false;
  	}
  
  	icmp = (ICMP_HEADER *)buf;
  	p->L4.ICMPHeader = icmp;
  
  	msg_size = size - sizeof(ICMP_HEADER);
  
  	icmp_info.Type = icmp->Type;
  	icmp_info.Code = icmp->Code;
  	icmp_info.Data = ((UCHAR *)buf) + sizeof(ICMP_HEADER);
  	icmp_info.DataSize = msg_size;
  
  	switch (icmp_info.Type)
  	{
  	case ICMPV6_TYPE_ECHO_REQUEST:
  	case ICMPV6_TYPE_ECHO_RESPONSE:
  		// ICMP Echo Request / Response
  		if (icmp_info.DataSize < sizeof(ICMP_ECHO))
  		{
  			return false;
  		}
  
  		echo = (ICMP_ECHO *)icmp_info.Data;
  
  		icmp_info.EchoHeader.Identifier = Endian16(echo->Identifier);
  		icmp_info.EchoHeader.SeqNo = Endian16(echo->SeqNo);
  		icmp_info.EchoData = (UCHAR *)echo + sizeof(ICMP_ECHO);
  		icmp_info.EchoDataSize = icmp_info.DataSize - sizeof(ICMP_ECHO);
  
  		break;
  
  	case ICMPV6_TYPE_ROUTER_SOLICIATION:
  		// Router Solicitation
  		if (icmp_info.DataSize < sizeof(ICMPV6_ROUTER_SOLICIATION_HEADER))
  		{
  			return false;
  		}
  
  		icmp_info.Headers.RouterSoliciationHeader =
  		    (ICMPV6_ROUTER_SOLICIATION_HEADER *)(((UCHAR *)icmp_info.Data));
  
  		if (ParseICMPv6Options(&icmp_info.OptionList, ((UCHAR *)icmp_info.Headers.HeaderPointer) + sizeof(ICMPV6_ROUTER_SOLICIATION_HEADER),
  		                       icmp_info.DataSize - sizeof(ICMPV6_ROUTER_SOLICIATION_HEADER)) == false)
  		{
  			return false;
  		}
  
  		break;
  
  	case ICMPV6_TYPE_ROUTER_ADVERTISEMENT:
  		// Router Advertisement
  		if (icmp_info.DataSize < sizeof(ICMPV6_ROUTER_ADVERTISEMENT_HEADER))
  		{
  			return false;
  		}
  
  		icmp_info.Headers.RouterAdvertisementHeader =
  		    (ICMPV6_ROUTER_ADVERTISEMENT_HEADER *)(((UCHAR *)icmp_info.Data));
  
  		if (ParseICMPv6Options(&icmp_info.OptionList, ((UCHAR *)icmp_info.Headers.HeaderPointer) + sizeof(ICMPV6_ROUTER_ADVERTISEMENT_HEADER),
  		                       icmp_info.DataSize - sizeof(ICMPV6_ROUTER_ADVERTISEMENT_HEADER)) == false)
  		{
  			return false;
  		}
  
  		break;
  
  	case ICMPV6_TYPE_NEIGHBOR_SOLICIATION:
  		// Neighbor Solicitation
  		if (icmp_info.DataSize < sizeof(ICMPV6_NEIGHBOR_SOLICIATION_HEADER))
  		{
  			return false;
  		}
  
  		icmp_info.Headers.NeighborSoliciationHeader =
  		    (ICMPV6_NEIGHBOR_SOLICIATION_HEADER *)(((UCHAR *)icmp_info.Data));
  
  		if (ParseICMPv6Options(&icmp_info.OptionList, ((UCHAR *)icmp_info.Headers.HeaderPointer) + sizeof(ICMPV6_NEIGHBOR_SOLICIATION_HEADER),
  		                       icmp_info.DataSize - sizeof(ICMPV6_NEIGHBOR_SOLICIATION_HEADER)) == false)
  		{
  			return false;
  		}
  
  		break;
  
  	case ICMPV6_TYPE_NEIGHBOR_ADVERTISEMENT:
  		// Neighbor Advertisement
  		if (icmp_info.DataSize < sizeof(ICMPV6_NEIGHBOR_ADVERTISEMENT_HEADER))
  		{
  			return false;
  		}
  
  		icmp_info.Headers.NeighborAdvertisementHeader =
  		    (ICMPV6_NEIGHBOR_ADVERTISEMENT_HEADER *)(((UCHAR *)icmp_info.Data));
  
  		if (ParseICMPv6Options(&icmp_info.OptionList, ((UCHAR *)icmp_info.Headers.HeaderPointer) + sizeof(ICMPV6_NEIGHBOR_ADVERTISEMENT_HEADER),
  		                       icmp_info.DataSize - sizeof(ICMPV6_NEIGHBOR_ADVERTISEMENT_HEADER)) == false)
  		{
  			return false;
  		}
  
  		break;
  	}
  
  	p->TypeL4 = L4_ICMPV6;
  	Copy(&p->ICMPv6HeaderPacketInfo, &icmp_info, sizeof(ICMPV6_HEADER_INFO));
  
  	return true;
  }
  
  // Release of the ICMPv6 options
  void FreeCloneICMPv6Options(ICMPV6_OPTION_LIST *o)
  {
  	UINT i;
  	// Validate arguments
  	if (o == NULL)
  	{
  		return;
  	}
  
  	Free(o->SourceLinkLayer);
  	Free(o->TargetLinkLayer);
  
  	for (i = 0; i < ICMPV6_OPTION_PREFIXES_MAX_COUNT; i++)
  	{
  		Free(o->Prefix[i]);
  		o->Prefix[i] = NULL;
  	}
  	Free(o->Mtu);
  }
  
  // Clone of the ICMPv6 options
  void CloneICMPv6Options(ICMPV6_OPTION_LIST *dst, ICMPV6_OPTION_LIST *src)
  {
  	UINT i;
  	// Validate arguments
  	if (dst == NULL || src == NULL)
  	{
  		return;
  	}
  
  	Zero(dst, sizeof(ICMPV6_OPTION_LIST));
  
  	dst->SourceLinkLayer = Clone(src->SourceLinkLayer, sizeof(ICMPV6_OPTION_LINK_LAYER));
  	dst->TargetLinkLayer = Clone(src->TargetLinkLayer, sizeof(ICMPV6_OPTION_LINK_LAYER));
  	for (i = 0; i < ICMPV6_OPTION_PREFIXES_MAX_COUNT; i++)
  	{
  		if (src->Prefix[i] != NULL)
  		{
  			dst->Prefix[i] = Clone(src->Prefix[i], sizeof(ICMPV6_OPTION_PREFIX));
  		}
  		else
  		{
  			break;
  		}
  	}
  	dst->Mtu = Clone(src->Mtu, sizeof(ICMPV6_OPTION_MTU));
  }
  
  // IPv6 parsing
  bool ParsePacketIPv6(PKT *p, UCHAR *buf, UINT size, bool no_l3_l4_except_icmpv6)
  {
  	// Validate arguments
  	if (p == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	if (ParsePacketIPv6Header(&p->IPv6HeaderPacketInfo, buf, size) == false)
  	{
  		return false;
  	}
  
  	p->TypeL3 = L3_IPV6;
  	p->L3.IPv6Header = p->IPv6HeaderPacketInfo.IPv6Header;
  
  	if (p->IPv6HeaderPacketInfo.Payload == NULL)
  	{
  		// No payload
  		return true;
  	}
  
  	buf = p->IPv6HeaderPacketInfo.Payload;
  	size = p->IPv6HeaderPacketInfo.PayloadSize;
  
  	if (p->IPv6HeaderPacketInfo.IsFragment)
  	{
  		// This is a fragmented packet. Quit interpreting
  		p->TypeL4 = L4_FRAGMENT;
  		return true;
  	}
  
  	// Parse a L4 packet
  	switch (p->IPv6HeaderPacketInfo.Protocol)
  	{
  	case IP_PROTO_ICMPV6:	// ICMPv6
  		if (ParseICMPv6(p, buf, size) == false)
  		{
  			// Returns true also if it fails to parse ICMPv6
  			return true;
  		}
  		else
  		{
  			return true;
  		}
  
  	case IP_PROTO_TCP:		// TCP
  		if (no_l3_l4_except_icmpv6)
  		{
  			return true;
  		}
  		return ParseTCP(p, buf, size);
  
  	case IP_PROTO_UDP:		// UDP
  		if (no_l3_l4_except_icmpv6)
  		{
  			return true;
  		}
  		return ParseUDP(p, buf, size);
  
  	default:				// Unknown
  		return true;
  	}
  
  	return true;
  }
  
  // Parse the IPv4 by adding a dummy MAC header
  PKT *ParsePacketIPv4WithDummyMacHeader(UCHAR *buf, UINT size)
  {
  	UCHAR *tmp;
  	UINT tmp_size;
  	PKT *ret;
  	// Validate arguments
  	if (buf == NULL)
  	{
  		return NULL;
  	}
  
  	tmp_size = size + 14;
  	tmp = Malloc(tmp_size);
  	Zero(tmp, 12);
  	WRITE_USHORT(tmp + 12, MAC_PROTO_IPV4);
  	Copy(tmp + 14, buf, size);
  
  	ret = ParsePacket(tmp, tmp_size);
  
  	if (ret == NULL)
  	{
  		Free(tmp);
  	}
  
  	return ret;
  }
  
  // IPv4 parsing
  bool ParsePacketIPv4(PKT *p, UCHAR *buf, UINT size)
  {
  	UINT header_size;
  	// Validate arguments
  	if (p == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	// Check the size
  	if (size < sizeof(IPV4_HEADER))
  	{
  		return false;
  	}
  
  	// IPv4 header
  	p->L3.IPv4Header = (IPV4_HEADER *)buf;
  	p->TypeL3 = L3_IPV4;
  
  	// Check the header
  	header_size = IPV4_GET_HEADER_LEN(p->L3.IPv4Header) * 4;
  	if (header_size < sizeof(IPV4_HEADER) || size < header_size)
  	{
  		// Header size is invalid
  		p->L3.IPv4Header = NULL;
  		p->TypeL3= L3_UNKNOWN;
  		return true;
  	}
  
  	buf += header_size;
  	size -= header_size;
  
  	p->IPv4PayloadSize = MIN(size, Endian16(p->L3.IPv4Header->TotalLength) - header_size);
  	if (Endian16(p->L3.IPv4Header->TotalLength) < header_size)
  	{
  		p->IPv4PayloadSize = 0;
  	}
  
  	p->IPv4PayloadData = buf;
  
  	if (IPV4_GET_OFFSET(p->L3.IPv4Header) != 0)
  	{
  		// Quit analysing since this is fragmented
  		p->TypeL4 = L4_FRAGMENT;
  
  		return true;
  	}
  
  	// Parse a L4 packet
  	switch (p->L3.IPv4Header->Protocol)
  	{
  	case IP_PROTO_ICMPV4:	// ICMPv4
  		return ParseICMPv4(p, buf, size);
  
  	case IP_PROTO_UDP:		// UDP
  		return ParseUDP(p, buf, size);
  
  	case IP_PROTO_TCP:		// TCP
  		return ParseTCP(p, buf, size);
  
  	default:				// Unknown
  		return true;
  	}
  }
  
  // ICMPv4 parsing
  bool ParseICMPv4(PKT *p, UCHAR *buf, UINT size)
  {
  	// Validate arguments
  	if (p == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	// Check the size
  	if (size < sizeof(ICMP_HEADER))
  	{
  		// Size is invalid
  		return false;
  	}
  
  	// ICMPv4 header
  	p->L4.ICMPHeader = (ICMP_HEADER *)buf;
  	p->TypeL4 = L4_ICMPV4;
  
  	buf += sizeof(ICMP_HEADER);
  	size -= sizeof(ICMP_HEADER);
  
  	return true;
  }
  
  // TCP parsing
  bool ParseTCP(PKT *p, UCHAR *buf, UINT size)
  {
  	UINT header_size;
  	// Validate arguments
  	if (p == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	// Check the size
  	if (size < sizeof(TCP_HEADER))
  	{
  		// Size is invalid
  		return false;
  	}
  
  	// TCP header
  	p->L4.TCPHeader = (TCP_HEADER *)buf;
  	p->TypeL4 = L4_TCP;
  
  	// Check the header size
  	header_size = TCP_GET_HEADER_SIZE(p->L4.TCPHeader) * 4;
  	if (header_size < sizeof(TCP_HEADER) || size < header_size)
  	{
  		// Header size is invalid
  		p->L4.TCPHeader = NULL;
  		p->TypeL4 = L4_UNKNOWN;
  		return true;
  	}
  
  	buf += header_size;
  	size -= header_size;
  
  	p->Payload = buf;
  	p->PayloadSize = size;
  
  	return true;
  }
  
  // Get the next byte
  UCHAR GetNextByte(BUF *b)
  {
  	UCHAR c = 0;
  	// Validate arguments
  	if (b == NULL)
  	{
  		return 0;
  	}
  
  	if (ReadBuf(b, &c, 1) != 1)
  	{
  		return 0;
  	}
  
  	return c;
  }
  
  // Interpret the DNS query
  bool ParseDnsQuery(char *name, UINT name_size, void *data, UINT data_size)
  {
  	BUF *b;
  	char tmp[257];
  	bool ok = true;
  	USHORT val;
  	// Validate arguments
  	if (name == NULL || data == NULL || data_size == 0)
  	{
  		return false;
  	}
  	StrCpy(name, name_size, "");
  
  	b = NewBuf();
  	WriteBuf(b, data, data_size);
  	SeekBuf(b, 0, 0);
  
  	while (true)
  	{
  		UINT next_len = (UINT)GetNextByte(b);
  		if (next_len > 0)
  		{
  			// Read only the specified length
  			Zero(tmp, sizeof(tmp));
  			if (ReadBuf(b, tmp, next_len) != next_len)
  			{
  				ok = false;
  				break;
  			}
  			// Append
  			if (StrLen(name) != 0)
  			{
  				StrCat(name, name_size, ".");
  			}
  			StrCat(name, name_size, tmp);
  		}
  		else
  		{
  			// Read all
  			break;
  		}
  	}
  
  	if (ReadBuf(b, &val, sizeof(val)) != sizeof(val))
  	{
  		ok = false;
  	}
  	else
  	{
  		if (Endian16(val) != 0x01 && Endian16(val) != 0x0c)
  		{
  			ok = false;
  		}
  	}
  
  	if (ReadBuf(b, &val, sizeof(val)) != sizeof(val))
  	{
  		ok = false;
  	}
  	else
  	{
  		if (Endian16(val) != 0x01)
  		{
  			ok = false;
  		}
  	}
  
  	FreeBuf(b);
  
  	if (ok == false || StrLen(name) == 0)
  	{
  		return false;
  	}
  	else
  	{
  		return true;
  	}
  }
  
  // DNS parsing
  void ParseDNS(PKT *p, UCHAR *buf, UINT size)
  {
  	UCHAR *query_data;
  	UINT query_data_size;
  	DNSV4_HEADER *dns;
  	char hostname[MAX_SIZE];
  	if (p == NULL|| buf == NULL)
  	{
  		return;
  	}
  
  	if (size < sizeof(DNSV4_HEADER))
  	{
  		return;
  	}
  
  	dns = (DNSV4_HEADER *)buf;
  
  	if ((dns->Flag1 & 78) != 0 || (dns->Flag1 & 0x80) != 0)
  	{
  		// Illegal opcode
  		return;
  	}
  	if (Endian16(dns->NumQuery) != 1)
  	{
  		// Number of queries is invalid
  		return;
  	}
  
  	query_data = ((UCHAR *)dns) + sizeof(DNSV4_HEADER);
  	query_data_size = size - sizeof(DNSV4_HEADER);
  
  	// Interpret the query
  	if (ParseDnsQuery(hostname, sizeof(hostname), query_data, query_data_size) == false)
  	{
  		// Interpretation fails
  		return;
  	}
  
  	StrCpy(p->DnsQueryHost, sizeof(p->DnsQueryHost), hostname);
  	p->TypeL7 = L7_DNS;
  }
  
  // UDP parsing
  bool ParseUDP(PKT *p, UCHAR *buf, UINT size)
  {
  	USHORT src_port, dst_port;
  	// Validate arguments
  	if (p == NULL || buf == NULL)
  	{
  		return false;
  	}
  
  	// Check the size
  	if (size < sizeof(UDP_HEADER))
  	{
  		// Size is invalid
  		return false;
  	}
  
  	// UDP header
  	p->L4.UDPHeader = (UDP_HEADER *)buf;
  	p->TypeL4 = L4_UDP;
  
  	buf += sizeof(UDP_HEADER);
  	size -= sizeof(UDP_HEADER);
  
  	p->Payload = buf;
  	p->PayloadSize = size;
  
  	// Check the port number
  	src_port = Endian16(p->L4.UDPHeader->SrcPort);
  	dst_port = Endian16(p->L4.UDPHeader->DstPort);
  
  	if ((src_port == 67 && dst_port == 68) ||
  	        (src_port == 68 && dst_port == 67))
  	{
  		if (p->TypeL3 == L3_IPV4)
  		{
  			// A DHCP packet is found
  			ParseDHCPv4(p, buf, size);
  
  			return true;
  		}
  	}
  
  	if (dst_port == 53)
  	{
  		ParseDNS(p, buf, size);
  		return true;
  	}
  
  
  	if (src_port == 500 || dst_port == 500 || src_port == 4500 || dst_port == 4500)
  	{
  		if (p->PayloadSize >= sizeof(IKE_HEADER))
  		{
  			IKE_HEADER *ike_header = (IKE_HEADER *)p->Payload;
  
  			if (ike_header->InitiatorCookie != 0 && ike_header->ResponderCookie == 0 &&
  			        (ike_header->ExchangeType == IKE_EXCHANGE_TYPE_MAIN ||
  			         ike_header->ExchangeType == IKE_EXCHANGE_TYPE_AGGRESSIVE))
  			{
  				// the IKE connection request packet is found
  				p->TypeL7 = L7_IKECONN;
  				p->L7.IkeHeader = ike_header;
  				return true;
  			}
  		}
  	}
  
  	// Determine whether it's an OpenVPN UDP packet
  	if (size == 14)
  	{
  		if (buf[0] == 0x38)
  		{
  			if (IsZero(buf + 9, 5))
  			{
  				if (IsZero(buf + 1, 8) == false)
  				{
  					// An OpenVPN connection request packet is found
  					p->TypeL7 = L7_OPENVPNCONN;
  					return true;
  				}
  			}
  		}
  	}
  
  	return true;
  }
  
  // DHCPv4 parsing
  void ParseDHCPv4(PKT *p, UCHAR *buf, UINT size)
  {
  	// Validate arguments
  	if (p == NULL || buf == NULL)
  	{
  		return;
  	}
  
  	// Check the size
  	if (size < sizeof(DHCPV4_HEADER))
  	{
  		// Size is invalid
  		return;
  	}
  
  	// DHCPv4 header
  	p->L7.DHCPv4Header = (DHCPV4_HEADER *)buf;
  	p->TypeL7 = L7_DHCPV4;
  }
  
  // Release the memory of the packet
  void FreePacket(PKT *p)
  {
  	// Validate arguments
  	if (p == NULL)
  	{
  		return;
  	}
  
  	if (p->MacHeader != NULL)
  	{
  		switch (p->TypeL3)
  		{
  		case L3_IPV4:
  			FreePacketIPv4(p);
  			break;
  
  		case L3_ARPV4:
  			FreePacketARPv4(p);
  			break;
  
  		case L3_TAGVLAN:
  			FreePacketTagVlan(p);
  			break;
  		}
  	}
  
  	if (p->HttpLog != NULL)
  	{
  		Free(p->HttpLog);
  	}
  
  	Free(p);
  }
  
  // Release the memory of the packet with data
  void FreePacketWithData(PKT *p)
  {
  	void *data;
  	// Validate arguments
  	if (p == NULL)
  	{
  		return;
  	}
  
  	data = p->PacketData;
  
  	FreePacket(p);
  
  	Free(data);
  }
  
  // Release the memory for the IPv4 packet
  void FreePacketIPv4(PKT *p)
  {
  	// Validate arguments
  	if (p == NULL)
  	{
  		return;
  	}
  
  	switch (p->TypeL4)
  	{
  	case L4_ICMPV4:
  		FreePacketICMPv4(p);
  		break;
  
  	case L4_TCP:
  		FreePacketTCPv4(p);
  		break;
  
  	case L4_UDP:
  		FreePacketUDPv4(p);
  		break;
  	}
  
  	p->L3.IPv4Header = NULL;
  	p->TypeL3 = L3_UNKNOWN;
  }
  
  // Release the memory for the tagged VLAN packet
  void FreePacketTagVlan(PKT *p)
  {
  	// Validate arguments
  	if (p == NULL)
  	{
  		return;
  	}
  
  	p->L3.TagVlanHeader = NULL;
  	p->TypeL3 = L3_UNKNOWN;
  }
  
  // Release the memory for the ARPv4 packet
  void FreePacketARPv4(PKT *p)
  {
  	// Validate arguments
  	if (p == NULL)
  	{
  		return;
  	}
  
  	p->L3.ARPv4Header = NULL;
  	p->TypeL3 = L3_UNKNOWN;
  }
  
  // Release the memory of the UDPv4 packet
  void FreePacketUDPv4(PKT *p)
  {
  	// Validate arguments
  	if (p == NULL)
  	{
  		return;
  	}
  
  	switch (p->TypeL7)
  	{
  	case L7_DHCPV4:
  		FreePacketDHCPv4(p);
  		break;
  	}
  
  	p->L4.UDPHeader = NULL;
  	p->TypeL4 = L4_UNKNOWN;
  }
  
  // Release the memory for the TCPv4 packet
  void FreePacketTCPv4(PKT *p)
  {
  	// Validate arguments
  	if (p == NULL)
  	{
  		return;
  	}
  
  	p->L4.TCPHeader = NULL;
  	p->TypeL4 = L4_UNKNOWN;
  }
  
  // Release the memory for the ICMPv4 packet
  void FreePacketICMPv4(PKT *p)
  {
  	// Validate arguments
  	if (p == NULL)
  	{
  		return;
  	}
  
  	p->L4.ICMPHeader = NULL;
  	p->TypeL4 = L4_UNKNOWN;
  }
  
  // Release the memory for the DHCPv4 packet
  void FreePacketDHCPv4(PKT *p)
  {
  	// Validate arguments
  	if (p == NULL)
  	{
  		return;
  	}
  
  	p->L7.DHCPv4Header = NULL;
  	p->TypeL7 = L7_UNKNOWN;
  }
  
  
  // Confirm the checksum of the IP header
  bool IpCheckChecksum(IPV4_HEADER *ip)
  {
  	UINT header_size;
  	USHORT checksum_original, checksum_calc;
  	// Validate arguments
  	if (ip == NULL)
  	{
  		return false;
  	}
  
  	header_size = IPV4_GET_HEADER_LEN(ip) * 4;
  	checksum_original = ip->Checksum;
  	ip->Checksum = 0;
  	checksum_calc = IpChecksum(ip, header_size);
  	ip->Checksum = checksum_original;
  
  	if (checksum_original == checksum_calc)
  	{
  		return true;
  	}
  	else
  	{
  		return false;
  	}
  }
  
  // Calculate the checksum
  USHORT IpChecksum(void *buf, UINT size)
  {
  	int sum = 0;
  	USHORT *addr = (USHORT *)buf;
  	int len = (int)size;
  	USHORT *w = addr;
  	int nleft = len;
  	USHORT answer = 0;
  
  	while (nleft > 1)
  	{
  		USHORT ww = 0;
  		Copy(&ww, w++, sizeof(USHORT));
  		sum += ww;
  		nleft -= 2;
  	}
  
  	if (nleft == 1)
  	{
  		*(UCHAR *)(&answer) = *(UCHAR *)w;
  		sum += answer;
  	}
  
  	sum = (sum >> 16) + (sum & 0xffff);
  	sum += (sum >> 16);
  
  	answer = ~sum;
  
  	return answer;
  }
  
  // Convert a DHCP option list into a buffer
  BUF *BuildDhcpOptionsBuf(LIST *o)
  {
  	BUF *b;
  	UINT i;
  	UCHAR id;
  	UCHAR sz;
  	// Validate arguments
  	if (o == NULL)
  	{
  		return NULL;
  	}
  
  	b = NewBuf();
  	for (i = 0; i < LIST_NUM(o); i++)
  	{
  		DHCP_OPTION *d = LIST_DATA(o, i);
  		UINT current_size = d->Size;
  		UINT current_pos = 0;
  
  		id = (UCHAR)d->Id;
  		if (d->Size <= 255)
  		{
  			sz = (UCHAR)d->Size;
  		}
  		else
  		{
  			sz = 0xFF;
  		}
  		WriteBuf(b, &id, 1);
  		WriteBuf(b, &sz, 1);
  		WriteBuf(b, d->Data, sz);
  
  		current_size -= sz;
  		current_pos += sz;
  
  		while (current_size != 0)
  		{
  			id = DHCP_ID_PRIVATE;
  			if (current_size <= 255)
  			{
  				sz = (UCHAR)current_size;
  			}
  			else
  			{
  				sz = 0xFF;
  			}
  			WriteBuf(b, &id, 1);
  			WriteBuf(b, &sz, 1);
  			WriteBuf(b, ((UCHAR *)d->Data) + current_pos, sz);
  
  			current_size -= sz;
  			current_pos += sz;
  		}
  
  	}
  
  	id = 0xff;
  	WriteBuf(b, &id, 1);
  
  	return b;
  }
  
  // Convert a DHCP option list to the DHCP option
  LIST *BuildDhcpOption(DHCP_OPTION_LIST *opt)
  {
  	LIST *o;
  	UCHAR opcode;
  	BUF *dns_buf;
  	// Validate arguments
  	if (opt == NULL)
  	{
  		return NULL;
  	}
  
  	o = NewListFast(NULL);
  
  	// Op-code
  	opcode = (UCHAR)opt->Opcode;
  	Add(o, NewDhcpOption(DHCP_ID_MESSAGE_TYPE, &opcode, sizeof(opcode)));
  	Add(o, NewDhcpOption(DHCP_ID_SERVER_ADDRESS, &opt->ServerAddress, sizeof(opt->ServerAddress)));
  
  	if (opt->LeaseTime != 0)
  	{
  		Add(o, NewDhcpOption(DHCP_ID_LEASE_TIME, &opt->LeaseTime, sizeof(opt->LeaseTime)));
  	}
  
  	if (StrLen(opt->DomainName) != 0 && opt->DnsServer != 0)
  	{
  		Add(o, NewDhcpOption(DHCP_ID_DOMAIN_NAME, opt->DomainName, StrLen(opt->DomainName)));
  	}
  	if (opt->SubnetMask != 0)
  	{
  		Add(o, NewDhcpOption(DHCP_ID_SUBNET_MASK, &opt->SubnetMask, sizeof(opt->SubnetMask)));
  	}
  	if (opt->Gateway != 0)
  	{
  		Add(o, NewDhcpOption(DHCP_ID_GATEWAY_ADDR, &opt->Gateway, sizeof(opt->Gateway)));
  	}
  
  	dns_buf = NewBuf();
  
  	if (opt->DnsServer != 0)
  	{
  		WriteBuf(dns_buf, &opt->DnsServer, sizeof(opt->DnsServer));
  	}
  	if (opt->DnsServer2 != 0)
  	{
  		WriteBuf(dns_buf, &opt->DnsServer2, sizeof(opt->DnsServer2));
  	}
  
  	if (dns_buf->Size >= 1)
  	{
  		Add(o, NewDhcpOption(DHCP_ID_DNS_ADDR, dns_buf->Buf, dns_buf->Size));
  	}
  
  	FreeBuf(dns_buf);
  
  	if (opt->ClasslessRoute.NumExistingRoutes >= 1)
  	{
  		BUF *b = DhcpBuildClasslessRouteData(&opt->ClasslessRoute);
  
  		if (b != NULL)
  		{
  			Add(o, NewDhcpOption(DHCP_ID_CLASSLESS_ROUTE, b->Buf, b->Size));
  			Add(o, NewDhcpOption(DHCP_ID_MS_CLASSLESS_ROUTE, b->Buf, b->Size));
  
  			FreeBuf(b);
  		}
  	}
  
  	return o;
  }
  
  // Create a new DHCP option item
  DHCP_OPTION *NewDhcpOption(UINT id, void *data, UINT size)
  {
  	DHCP_OPTION *ret;
  	if (size != 0 && data == NULL)
  	{
  		return NULL;
  	}
  
  	ret = ZeroMalloc(sizeof(DHCP_OPTION));
  	ret->Data = ZeroMalloc(size);
  	Copy(ret->Data, data, size);
  	ret->Size = (UCHAR)size;
  	ret->Id = (UCHAR)id;
  
  	return ret;
  }
  
  // Parse a DHCP options list
  DHCP_OPTION_LIST *ParseDhcpOptionList(void *data, UINT size)
  {
  	DHCP_OPTION_LIST *ret;
  	LIST *o;
  	DHCP_OPTION *a;
  	// Validate arguments
  	if (data == NULL)
  	{
  		return NULL;
  	}
  
  	// Parse the list
  	o = ParseDhcpOptions(data, size);
  	if (o == NULL)
  	{
  		return NULL;
  	}
  
  	ret = ZeroMalloc(sizeof(DHCP_OPTION_LIST));
  
  	// Get the opcode
  	a = GetDhcpOption(o, DHCP_ID_MESSAGE_TYPE);
  	if (a != NULL)
  	{
  		if (a->Size == 1)
  		{
  			ret->Opcode = *((UCHAR *)a->Data);
  		}
  	}
  
  	switch (ret->Opcode)
  	{
  	case DHCP_DISCOVER:
  	case DHCP_REQUEST:
  		// Parse this more finely because this is client requests
  		// Requested IP address
  		a = GetDhcpOption(o, DHCP_ID_REQUEST_IP_ADDRESS);
  		if (a != NULL && a->Size == 4)
  		{
  			Copy(&ret->RequestedIp, a->Data, 4);
  		}
  		// Host name
  		a = GetDhcpOption(o, DHCP_ID_HOST_NAME);
  		if (a != NULL)
  		{
  			if (a->Size > 1)
  			{
  				Copy(ret->Hostname, a->Data, MIN(a->Size, sizeof(ret->Hostname) - 1));
  			}
  		}
  		break;
  
  	case DHCP_OFFER:
  	case DHCP_ACK:
  		// Subnet mask
  		a = GetDhcpOption(o, DHCP_ID_SUBNET_MASK);
  		if (a != NULL && a->Size >= 4)
  		{
  			Copy(&ret->SubnetMask, a->Data, 4);
  		}
  
  		// Lease time
  		a = GetDhcpOption(o, DHCP_ID_LEASE_TIME);
  		if (a != NULL && a->Size == 4)
  		{
  			ret->LeaseTime = READ_UINT(a->Data);
  		}
  
  		// Server IP address
  		a = GetDhcpOption(o, DHCP_ID_SERVER_ADDRESS);
  		if (a != NULL && a->Size >= 4)
  		{
  			Copy(&ret->ServerAddress, a->Data, 4);
  		}
  
  		// Domain name
  		a = GetDhcpOption(o, DHCP_ID_DOMAIN_NAME);
  		if (a != NULL && a->Size >= 1)
  		{
  			Zero(ret->DomainName, sizeof(ret->DomainName));
  			Copy(ret->DomainName, a->Data, MIN(a->Size, sizeof(ret->DomainName) - 1));
  		}
  
  		// Gateway
  		a = GetDhcpOption(o, DHCP_ID_GATEWAY_ADDR);
  		if (a != NULL && a->Size >= 4)
  		{
  			Copy(&ret->Gateway, a->Data, 4);
  		}
  
  		// DNS server
  		a = GetDhcpOption(o, DHCP_ID_DNS_ADDR);
  		if (a != NULL && a->Size >= 4)
  		{
  			Copy(&ret->DnsServer, a->Data, 4);
  
  			if (a->Size >= 8)
  			{
  				Copy(&ret->DnsServer2, ((UCHAR *)a->Data) + 4, 4);
  			}
  		}
  
  		// WINS server
  		a = GetDhcpOption(o, DHCP_ID_WINS_ADDR);
  		if (a != NULL && a->Size >= 4)
  		{
  			Copy(&ret->WinsServer, a->Data, 4);
  
  			if (a->Size >= 8)
  			{
  				Copy(&ret->WinsServer2, ((UCHAR *)a->Data) + 4, 4);
  			}
  		}
  
  		// Classless static routing table entries
  		// RFC 3442
  		a = GetDhcpOption(o, DHCP_ID_CLASSLESS_ROUTE);
  		if (a != NULL)
  		{
  			DhcpParseClasslessRouteData(&ret->ClasslessRoute, a->Data, a->Size);
  		}
  		// Microsoft Extension
  		a = GetDhcpOption(o, DHCP_ID_MS_CLASSLESS_ROUTE);
  		if (a != NULL)
  		{
  			DhcpParseClasslessRouteData(&ret->ClasslessRoute, a->Data, a->Size);
  		}
  
  		break;
  	}
  
  	// Release the list
  	FreeDhcpOptions(o);
  
  	return ret;
  }
  
  // Normalize the classless routing table string
  bool NormalizeClasslessRouteTableStr(char *dst, UINT dst_size, char *src)
  {
  	DHCP_CLASSLESS_ROUTE_TABLE t;
  	// Validate arguments
  	if (dst == NULL || src == NULL)
  	{
  		return false;
  	}
  
  	Zero(&t, sizeof(t));
  	if (ParseClasslessRouteTableStr(&t, src))
  	{
  		BuildClasslessRouteTableStr(dst, dst_size, &t);
  
  		return true;
  	}
  
  	return false;
  }
  
  // Build the string from the classless routing table
  void BuildClasslessRouteTableStr(char *str, UINT str_size, DHCP_CLASSLESS_ROUTE_TABLE *t)
  {
  	UINT i;
  	UINT num = 0;
  	ClearStr(str, str_size);
  	// Validate arguments
  	if (str == NULL || t == NULL)
  	{
  		return;
  	}
  
  	for (i = 0; i < MAX_DHCP_CLASSLESS_ROUTE_ENTRIES; i++)
  	{
  		DHCP_CLASSLESS_ROUTE *r = &t->Entries[i];
  
  		if (r->Exists)
  		{
  			char tmp[128];
  
  			Zero(tmp, sizeof(tmp));
  			BuildClasslessRouteStr(tmp, sizeof(tmp), r);
  
  			if (IsEmptyStr(tmp) == false)
  			{
  				if (num >= 1)
  				{
  					StrCat(str, str_size, ", ");
  				}
  
  				StrCat(str, str_size, tmp);
  
  				num++;
  			}
  		}
  	}
  }
  
  // Build the string from the classless routing table entry
  void BuildClasslessRouteStr(char *str, UINT str_size, DHCP_CLASSLESS_ROUTE *r)
  {
  	ClearStr(str, str_size);
  	// Validate arguments
  	if (str == NULL || r == NULL || r->Exists == false)
  	{
  		return;
  	}
  
  	Format(str, str_size, "%r/%r/%r", &r->Network, &r->SubnetMask, &r->Gateway);
  }
  
  // Check the classless routing table string
  bool CheckClasslessRouteTableStr(char *str)
  {
  	DHCP_CLASSLESS_ROUTE_TABLE d;
  
  	// Validate arguments
  	if (str == NULL)
  	{
  		return false;
  	}
  
  	return ParseClasslessRouteTableStr(&d, str);
  }
  
  // Parse the classless routing table string
  bool ParseClasslessRouteTableStr(DHCP_CLASSLESS_ROUTE_TABLE *d, char *str)
  {
  	bool ret = true;
  	TOKEN_LIST *t;
  	// Validate arguments
  	if (d == NULL || str == NULL)
  	{
  		return false;
  	}
  
  	Zero(d, sizeof(DHCP_CLASSLESS_ROUTE_TABLE));
  
  	t = ParseTokenWithoutNullStr(str, NULL);
  
  	if (t != NULL)
  	{
  		UINT i;
  
  		for (i = 0; i < t->NumTokens; i++)
  		{
  			DHCP_CLASSLESS_ROUTE r;
  
  			Zero(&r, sizeof(r));
  			if (ParseClasslessRouteStr(&r, t->Token[i]))
  			{
  				if (d->NumExistingRoutes < MAX_DHCP_CLASSLESS_ROUTE_ENTRIES)
  				{
  					Copy(&d->Entries[d->NumExistingRoutes], &r, sizeof(DHCP_CLASSLESS_ROUTE));
  					d->NumExistingRoutes++;
  				}
  				else
  				{
  					// Overflow
  					ret = false;
  					break;
  				}
  			}
  			else
  			{
  				// Parse error
  				ret = false;
  				break;
  			}
  		}
  	}
  
  	FreeToken(t);
  
  	return ret;
  }
  
  // Parse the classless routing table entry string
  bool ParseClasslessRouteStr(DHCP_CLASSLESS_ROUTE *r, char *str)
  {
  	TOKEN_LIST *t;
  	bool ret = false;
  	char tmp[MAX_PATH];
  	// Validate arguments
  	if (r == NULL || str == NULL)
  	{
  		return false;
  	}
  
  	StrCpy(tmp, sizeof(tmp), str);
  	Trim(tmp);
  
  	t = ParseTokenWithoutNullStr(str, "/");
  	if (t == NULL)
  	{
  		return false;
  	}
  
  	if (t->NumTokens == 3)
  	{
  		char ip_and_mask[MAX_PATH];
  		char gateway[MAX_PATH];
  
  		Zero(r, sizeof(DHCP_CLASSLESS_ROUTE));
  
  		Format(ip_and_mask, sizeof(ip_and_mask), "%s/%s", t->Token[0], t->Token[1]);
  		StrCpy(gateway, sizeof(gateway), t->Token[2]);
  
  		if (ParseIpAndSubnetMask46(ip_and_mask, &r->Network, &r->SubnetMask))
  		{
  			r->SubnetMaskLen = SubnetMaskToInt4(&r->SubnetMask);
  
  			if (StrToIP(&r->Gateway, gateway))
  			{
  				if (IsIP4(&r->Gateway) && IsIP4(&r->Network) && IsIP4(&r->SubnetMask))
  				{
  					r->Exists = true;
  
  					IPAnd4(&r->Network, &r->Network, &r->SubnetMask);
  
  					ret = true;
  				}
  			}
  		}
  	}
  
  	FreeToken(t);
  
  	return ret;
  }
  
  // Build the classless static routing table data for a DHCP message
  BUF *DhcpBuildClasslessRouteData(DHCP_CLASSLESS_ROUTE_TABLE *t)
  {
  	BUF *b;
  	UINT i;
  	// Validate arguments
  	if (t == NULL || t->NumExistingRoutes == 0)
  	{
  		return NULL;
  	}
  
  	b = NewBuf();
  
  	for (i = 0; i < MAX_DHCP_CLASSLESS_ROUTE_ENTRIES; i++)
  	{
  		DHCP_CLASSLESS_ROUTE *r = &t->Entries[i];
  
  		if (r->Exists && r->SubnetMaskLen <= 32)
  		{
  			UCHAR c;
  			UINT data_len;
  			UINT ip32;
  			UCHAR tmp[4];
  
  			// Width of subnet mask
  			c = (UCHAR)r->SubnetMaskLen;
  			WriteBuf(b, &c, 1);
  
  			// Number of significant octets
  			data_len = (r->SubnetMaskLen + 7) / 8;
  			Zero(tmp, sizeof(tmp));
  			Copy(tmp, &r->Network, data_len);
  			WriteBuf(b, tmp, data_len);
  
  			// Gateway
  			ip32 = IPToUINT(&r->Gateway);
  			WriteBuf(b, &ip32, sizeof(UINT));
  		}
  	}
  
  	SeekBufToBegin(b);
  
  	return b;
  }
  
  // Parse a classless static routing table entries from the DHCP message
  void DhcpParseClasslessRouteData(DHCP_CLASSLESS_ROUTE_TABLE *t, void *data, UINT size)
  {
  	BUF *b;
  	// Validate arguments
  	if (t == NULL || data == NULL || size == 0)
  	{
  		return;
  	}
  
  	b = MemToBuf(data, size);
  
  	while (b->Current < b->Size)
  	{
  		UCHAR c;
  		UINT subnet_mask_len;
  		UINT data_len;
  		UCHAR tmp[4];
  		IP ip;
  		IP mask;
  		IP gateway;
  		DHCP_CLASSLESS_ROUTE r;
  		UINT ip32;
  		bool exists = false;
  		UINT i;
  
  		// Subnet mask length
  		c = ReadBufChar(b);
  		subnet_mask_len = c;
  		if (subnet_mask_len > 32)
  		{
  			// Invalid data
  			break;
  		}
  
  		data_len = (subnet_mask_len + 7) / 8;
  
  		Zero(tmp, sizeof(tmp));
  		if (ReadBuf(b, tmp, data_len) != data_len)
  		{
  			// Invalid data
  			break;
  		}
  
  		// IP address body
  		Zero(&ip, sizeof(IP));
  		Copy(ip.addr, tmp, data_len);
  
  		Zero(&mask, sizeof(mask));
  		IntToSubnetMask4(&mask, subnet_mask_len);
  
  		// Gateway address
  		Zero(&gateway, sizeof(gateway));
  		if (ReadBuf(b, &ip32, sizeof(UINT)) != sizeof(UINT))
  		{
  			// Invalid data
  			break;
  		}
  		UINTToIP(&gateway, ip32);
  
  		Zero(&r, sizeof(r));
  		r.Exists = true;
  		Copy(&r.Gateway, &gateway, sizeof(IP));
  		Copy(&r.Network, &ip, sizeof(IP));
  		Copy(&r.SubnetMask, &mask, sizeof(IP));
  		r.SubnetMaskLen = subnet_mask_len;
  
  		for (i = 0; i < MAX_DHCP_CLASSLESS_ROUTE_ENTRIES; i++)
  		{
  			if (Cmp(&t->Entries[i], &r, sizeof(DHCP_CLASSLESS_ROUTE)) == 0)
  			{
  				exists = true;
  				break;
  			}
  		}
  
  		if (exists == false)
  		{
  			if (t->NumExistingRoutes >= MAX_DHCP_CLASSLESS_ROUTE_ENTRIES)
  			{
  				// Overflow
  				break;
  			}
  
  			Copy(&t->Entries[t->NumExistingRoutes], &r, sizeof(DHCP_CLASSLESS_ROUTE));
  			t->NumExistingRoutes++;
  		}
  	}
  
  	FreeBuf(b);
  }
  
  // Finding a DHCP option
  DHCP_OPTION *GetDhcpOption(LIST *o, UINT id)
  {
  	UINT i;
  	DHCP_OPTION *ret = NULL;
  	// Validate arguments
  	if (o == NULL)
  	{
  		return NULL;
  	}
  
  	for (i = 0; i < LIST_NUM(o); i++)
  	{
  		DHCP_OPTION *opt = LIST_DATA(o, i);
  		if (opt->Id == id)
  		{
  			ret = opt;
  		}
  	}
  
  	return ret;
  }
  
  // Get the best classless routing table entry from the routing table
  DHCP_CLASSLESS_ROUTE *GetBestClasslessRoute(DHCP_CLASSLESS_ROUTE_TABLE *t, IP *ip)
  {
  	DHCP_CLASSLESS_ROUTE *ret = NULL;
  	UINT i;
  	UINT max_mask = 0;
  	// Validate arguments
  	if (t == NULL || ip == NULL)
  	{
  		return NULL;
  	}
  	if (t->NumExistingRoutes == 0)
  	{
  		return NULL;
  	}
  
  	for (i = 0; i < MAX_DHCP_CLASSLESS_ROUTE_ENTRIES; i++)
  	{
  		DHCP_CLASSLESS_ROUTE *e = &t->Entries[i];
  
  		if (e->Exists)
  		{
  			if (IsInSameNetwork4(ip, &e->Network, &e->SubnetMask))
  			{
  				if (max_mask <= e->SubnetMaskLen)
  				{
  					max_mask = e->SubnetMaskLen;
  					ret = e;
  				}
  			}
  		}
  	}
  
  	return ret;
  }
  
  // Release the DHCP option
  void FreeDhcpOptions(LIST *o)
  {
  	UINT i;
  	// Validate arguments
  	if (o == NULL)
  	{
  		return;
  	}
  
  	for (i = 0; i < LIST_NUM(o); i++)
  	{
  		DHCP_OPTION *opt = LIST_DATA(o, i);
  		Free(opt->Data);
  		Free(opt);
  	}
  
  	ReleaseList(o);
  }
  
  // Parse the DHCP Options
  LIST *ParseDhcpOptions(void *data, UINT size)
  {
  	BUF *b;
  	LIST *o;
  	DHCP_OPTION *last_opt;
  	// Validate arguments
  	if (data == NULL)
  	{
  		return NULL;
  	}
  
  	b = NewBuf();
  	WriteBuf(b, data, size);
  	SeekBuf(b, 0, 0);
  
  	o = NewListFast(NULL);
  
  	last_opt = NULL;
  
  	while (true)
  	{
  		UCHAR c = 0;
  		UCHAR sz = 0;
  		DHCP_OPTION *opt;
  		if (ReadBuf(b, &c, 1) != 1)
  		{
  			break;
  		}
  		if (c == 0xff)
  		{
  			break;
  		}
  		if (ReadBuf(b, &sz, 1) != 1)
  		{
  			break;
  		}
  
  		if (c == DHCP_ID_PRIVATE && last_opt != NULL)
  		{
  			UINT new_size = last_opt->Size + (UINT)sz;
  			UCHAR *new_buf = ZeroMalloc(new_size);
  			Copy(new_buf, last_opt->Data, last_opt->Size);
  			ReadBuf(b, new_buf + last_opt->Size, sz);
  			Free(last_opt->Data);
  			last_opt->Data = new_buf;
  			last_opt->Size = new_size;
  		}
  		else
  		{
  			opt = ZeroMalloc(sizeof(DHCP_OPTION));
  			opt->Id = (UINT)c;
  			opt->Size = (UINT)sz;
  			opt->Data = ZeroMalloc((UINT)sz);
  			ReadBuf(b, opt->Data, sz);
  			Add(o, opt);
  
  			last_opt = opt;
  		}
  	}
  
  	FreeBuf(b);
  
  	return o;
  }
  
  // Rewrite the DHCP message data in the requested IPv4 packet appropriately
  BUF *DhcpModifyIPv4(DHCP_MODIFY_OPTION *m, void *data, UINT size)
  {
  	PKT *p;
  	BUF *ret = NULL;
  	// Validate arguments
  	if (m == NULL || data == NULL || size == 0)
  	{
  		return NULL;
  	}
  
  	p = ParsePacketEx4(data, size, false, 0, false, false, false);
  
  	if (p != NULL && p->TypeL3 == L3_IPV4 && p->TypeL4 == L4_UDP && p->TypeL7 == L7_DHCPV4)
  	{
  		BUF *new_buf = DhcpModify(m, p->Payload, p->PayloadSize);
  
  		if (new_buf != NULL)
  		{
  			ret = NewBuf();
  
  			WriteBuf(ret, p->PacketData, p->PacketSize - p->PayloadSize);
  			WriteBuf(ret, new_buf->Buf, new_buf->Size);
  
  			FreeBuf(new_buf);
  		}
  	}
  
  	FreePacket(p);
  
  	if (ret != NULL)
  	{
  		PKT *p = ParsePacketEx4(ret->Buf, ret->Size, false, 0, false, false, false);
  
  		if (p != NULL)
  		{
  			// Recalculation of the UDP checksum
  			if (p->TypeL3 == L3_IPV4 && p->TypeL4 == L4_UDP)
  			{
  				UDP_HEADER *udp = p->L4.UDPHeader;
  
  				udp->Checksum = 0;
  				udp->Checksum = CalcChecksumForIPv4(p->L3.IPv4Header->SrcIP,
  				                                    p->L3.IPv4Header->DstIP,
  				                                    IP_PROTO_UDP,
  				                                    udp,
  				                                    p->PacketSize - (UINT)(((UCHAR *)udp) - ((UCHAR *)p->PacketData)), 0);
  			}
  
  			FreePacket(p);
  		}
  	}
  
  	return ret;
  }
  
  // Rewrite the DHCP packet appropriately
  BUF *DhcpModify(DHCP_MODIFY_OPTION *m, void *data, UINT size)
  {
  	DHCPV4_HEADER *dhcp_header;
  	UCHAR *data_ptr;
  	bool ret_ok = false;
  	BUF *ret = NULL;
  	BUF *opt_buf = NULL;
  	UINT magic_cookie = Endian32(DHCP_MAGIC_COOKIE);
  	bool ok = false;
  	DHCP_OPTION_LIST *opt = NULL;
  	LIST *opt_list = NULL;
  	LIST *opt_list2 = NULL;
  	UINT src_size = size;
  	UINT i;
  	// Validate arguments
  	if (m == NULL || data == NULL || size == 0)
  	{
  		return NULL;
  	}
  
  	data_ptr = (UCHAR *)data;
  
  	if (size < sizeof(DHCPV4_HEADER))
  	{
  		goto LABEL_CLEANUP;
  	}
  
  	dhcp_header = (DHCPV4_HEADER *)data_ptr;
  	data_ptr += sizeof(DHCPV4_HEADER);
  
  	// Search for a Magic Cookie
  	while (size >= 5)
  	{
  		if (Cmp(data_ptr, &magic_cookie, sizeof(UINT)) == 0)
  		{
  			// Found
  			data_ptr += sizeof(UINT);
  			size -= sizeof(UINT);
  			ok = true;
  			break;
  		}
  
  		data_ptr++;
  		size--;
  	}
  
  	if (ok == false)
  	{
  		// The packet is invalid
  		goto LABEL_CLEANUP;
  	}
  
  	ret = NewBuf();
  	WriteBuf(ret, data, (UINT)(data_ptr - ((UCHAR *)data)));
  
  	// Parse the DHCP options list
  	opt = ParseDhcpOptionList(data_ptr, size);
  	if (opt == NULL)
  	{
  		// The packet is invalid
  		goto LABEL_CLEANUP;
  	}
  
  	opt_list = ParseDhcpOptions(data_ptr, size);
  	if (opt_list == NULL)
  	{
  		// The packet is invalid
  		goto LABEL_CLEANUP;
  	}
  
  	// Rebuilding the options list
  	opt_list2 = NewListFast(NULL);
  
  	for (i = 0; i < LIST_NUM(opt_list); i++)
  	{
  		DHCP_OPTION *o = LIST_DATA(opt_list, i);
  		DHCP_OPTION *o2 = NULL;
  		bool ok = true;
  
  		if (m->RemoveDefaultGatewayOnReply)
  		{
  			if (opt->Opcode == DHCP_OFFER || opt->Opcode == DHCP_ACK)
  			{
  				// Remove the default gateway from the DHCP Reply
  				if (o->Id == DHCP_ID_GATEWAY_ADDR)
  				{
  					ok = false;
  				}
  				if (o->Id == DHCP_ID_DNS_ADDR || o->Id == DHCP_ID_WINS_ADDR || o->Id == DHCP_ID_DOMAIN_NAME)
  				{
  					ok = false;
  				}
  			}
  		}
  
  		if (ok && o2 == NULL)
  		{
  			o2 = NewDhcpOption(o->Id, o->Data, o->Size);
  		}
  
  		if (o2 != NULL)
  		{
  			Add(opt_list2, o2);
  		}
  	}
  
  	opt_buf = BuildDhcpOptionsBuf(opt_list2);
  
  	WriteBuf(ret, opt_buf->Buf, opt_buf->Size);
  
  	if (src_size != ret->Size || Cmp(data, ret->Buf, ret->Size) != 0)
  	{
  		// Rewrite if anything changes. Do not rewrite if there is no change
  		ret_ok = true;
  
  		if (ret->Size < DHCP_MIN_SIZE)
  		{
  			// Padding
  			UCHAR *pad_buf;
  			UINT pad_size = DHCP_MIN_SIZE - ret->Size;
  
  			pad_buf = ZeroMalloc(pad_size);
  
  			WriteBuf(ret, pad_buf, pad_size);
  
  			Free(pad_buf);
  		}
  	}
  
  LABEL_CLEANUP:
  	// Memory release
  	if (opt_buf != NULL)
  	{
  		FreeBuf(opt_buf);
  	}
  
  	if (opt != NULL)
  	{
  		Free(opt);
  	}
  
  	if (opt_list != NULL)
  	{
  		FreeDhcpOptions(opt_list);
  	}
  
  	if (opt_list2 != NULL)
  	{
  		FreeDhcpOptions(opt_list2);
  	}
  
  	// Return a value
  	if (ret_ok)
  	{
  		return ret;
  	}
  	else
  	{
  		FreeBuf(ret);
  		return NULL;
  	}
  }
  