text: // SoftEther VPN Source Code - Developer Edition Master Branch
  // Cedar Communication Module
  
  
  // IPC.c
  // In-process VPN client module
  
  #include "CedarPch.h"
  
  // Extract the MS-CHAP v2 authentication information by parsing the password string
  bool ParseAndExtractMsChapV2InfoFromPassword(IPC_MSCHAP_V2_AUTHINFO *d, char *password)
  {
  	TOKEN_LIST *t;
  	bool ret = false;
  	// Validate arguments
  	if (d == NULL || password == NULL)
  	{
  		return false;
  	}
  
  	Zero(d, sizeof(IPC_MSCHAP_V2_AUTHINFO));
  
  	if (StartWith(password, IPC_PASSWORD_MSCHAPV2_TAG) == false)
  	{
  		return false;
  	}
  
  	t = ParseTokenWithNullStr(password, ":");
  
  	if (t->NumTokens == 6)
  	{
  		BUF *b1, *b2, *b3, *b4;
  
  		b1 = StrToBin(t->Token[2]);
  		b2 = StrToBin(t->Token[3]);
  		b3 = StrToBin(t->Token[4]);
  		b4 = StrToBin(t->Token[5]);
  
  		if (IsEmptyStr(t->Token[1]) == false && b1->Size == 16 && b2->Size == 16 && b3->Size == 24
  		        && b4->Size == 8)
  		{
  			UINT64 eap_client_ptr = 0;
  
  			StrCpy(d->MsChapV2_PPPUsername, sizeof(d->MsChapV2_PPPUsername), t->Token[1]);
  			Copy(d->MsChapV2_ServerChallenge, b1->Buf, 16);
  			Copy(d->MsChapV2_ClientChallenge, b2->Buf, 16);
  			Copy(d->MsChapV2_ClientResponse, b3->Buf, 24);
  			Copy(&eap_client_ptr, b4->Buf, 8);
  
  			d->MsChapV2_EapClient = (EAP_CLIENT *)eap_client_ptr;
  
  			ret = true;
  		}
  
  		FreeBuf(b1);
  		FreeBuf(b2);
  		FreeBuf(b3);
  		FreeBuf(b4);
  	}
  
  	FreeToken(t);
  
  	return ret;
  }
  
  // Start an IPC connection asynchronously
  IPC_ASYNC *NewIPCAsync(CEDAR *cedar, IPC_PARAM *param, SOCK_EVENT *sock_event)
  {
  	IPC_ASYNC *a;
  	// Validate arguments
  	if (cedar == NULL || param == NULL)
  	{
  		return NULL;
  	}
  
  	a = ZeroMalloc(sizeof(IPC_ASYNC));
  
  	a->TubeForDisconnect = NewTube(0);
  
  	a->Cedar = cedar;
  	AddRef(a->Cedar->ref);
  
  	Copy(&a->Param, param, sizeof(IPC_PARAM));
  
  	if (param->ClientCertificate != NULL)
  	{
  		// Client certificate must be copied for async processing
  		a->Param.ClientCertificate = CloneX(param->ClientCertificate);
  	}
  
  	if (sock_event != NULL)
  	{
  		a->SockEvent = sock_event;
  		AddRef(a->SockEvent->ref);
  	}
  
  	a->Thread = NewThread(IPCAsyncThreadProc, a);
  
  	return a;
  }
  
  // asynchronous IPC connection creation thread
  void IPCAsyncThreadProc(THREAD *thread, void *param)
  {
  	IPC_ASYNC *a;
  	// Validate arguments
  	if (thread == NULL || param == NULL)
  	{
  		return;
  	}
  
  	a = (IPC_ASYNC *)param;
  
  	// Attempt to connect
  	a->Ipc = NewIPCByParam(a->Cedar, &a->Param, &a->ErrorCode);
  
  	if (a->Ipc != NULL)
  	{
  		if (a->Param.IsL3Mode)
  		{
  			DHCP_OPTION_LIST cao;
  
  			Zero(&cao, sizeof(cao));
  
  			// Get an IP address from the DHCP server in the case of L3 mode
  			Debug("IPCDhcpAllocateIP() start...\n");
  			if (IPCDhcpAllocateIP(a->Ipc, &cao, a->TubeForDisconnect))
  			{
  				UINT t;
  				IP ip, subnet, gw;
  
  				Debug("IPCDhcpAllocateIP() Ok.\n");
  
  				// Calculate the DHCP update interval
  				t = cao.LeaseTime;
  				if (t == 0)
  				{
  					t = 600;
  				}
  
  				t = t / 3;
  
  				if (t == 0)
  				{
  					t = 1;
  				}
  
  				// Save the options list
  				Copy(&a->L3ClientAddressOption, &cao, sizeof(DHCP_OPTION_LIST));
  				a->L3DhcpRenewInterval = (UINT64)t * (UINT64)1000;
  
  				// Set the obtained IP address parameters to the IPC virtual host
  				UINTToIP(&ip, cao.ClientAddress);
  				UINTToIP(&subnet, cao.SubnetMask);
  				UINTToIP(&gw, cao.Gateway);
  
  				IPCSetIPv4Parameters(a->Ipc, &ip, &subnet, &gw, &cao.ClasslessRoute);
  
  				a->L3NextDhcpRenewTick = Tick64() + a->L3DhcpRenewInterval;
  			}
  			else
  			{
  				Debug("IPCDhcpAllocateIP() Error.\n");
  
  				a->DhcpAllocFailed = true;
  
  				FreeIPC(a->Ipc);
  				a->Ipc = NULL;
  			}
  		}
  	}
  
  	// Procedure complete
  	a->Done = true;
  
  	if (a->SockEvent != NULL)
  	{
  		SetSockEvent(a->SockEvent);
  	}
  }
  
  // Release the IPC asynchronous connection object
  void FreeIPCAsync(IPC_ASYNC *a)
  {
  	// Validate arguments
  	if (a == NULL)
  	{
  		return;
  	}
  
  	TubeDisconnect(a->TubeForDisconnect);
  	WaitThread(a->Thread, INFINITE);
  	ReleaseThread(a->Thread);
  
  	if (a->Ipc != NULL)
  	{
  		FreeIPC(a->Ipc);
  		a->Ipc = NULL;
  	}
  
  	if (a->SockEvent != NULL)
  	{
  		ReleaseSockEvent(a->SockEvent);
  	}
  
  	ReleaseCedar(a->Cedar);
  
  	ReleaseTube(a->TubeForDisconnect);
  
  	if (a->Param.ClientCertificate != NULL)
  	{
  		FreeX(a->Param.ClientCertificate);
  	}
  
  	Free(a);
  }
  
  // Start a new IPC connection by specifying the parameter structure
  IPC *NewIPCByParam(CEDAR *cedar, IPC_PARAM *param, UINT *error_code)
  {
  	IPC *ipc;
  	// Validate arguments
  	if (cedar == NULL || param == NULL)
  	{
  		return NULL;
  	}
  
  	ipc = NewIPC(cedar, param->ClientName, param->Postfix, param->HubName,
  	             param->UserName, param->Password, error_code, &param->ClientIp,
  	             param->ClientPort, &param->ServerIp, param->ServerPort,
  	             param->ClientHostname, param->CryptName,
  	             param->BridgeMode, param->Mss, NULL, param->ClientCertificate, param->Layer);
  
  	return ipc;
  }
  
  // Start a new IPC connection
  IPC *NewIPC(CEDAR *cedar, char *client_name, char *postfix, char *hubname, char *username, char *password,
              UINT *error_code, IP *client_ip, UINT client_port, IP *server_ip, UINT server_port,
              char *client_hostname, char *crypt_name,
              bool bridge_mode, UINT mss, EAP_CLIENT *eap_client, X *client_certificate,
              UINT layer)
  {
  	IPC *ipc;
  	UINT dummy_int = 0;
  	SOCK *a;
  	SOCK *s;
  	PACK *p;
  	UINT err = ERR_INTERNAL_ERROR;
  	char server_str[MAX_SIZE];
  	char macstr[30];
  	UINT server_ver, server_build;
  	UCHAR unique[SHA1_SIZE];
  	NODE_INFO info;
  	BUF *b;
  	UCHAR mschap_v2_server_response_20[20];
  	UINT64 u64;
  	// Validate arguments
  	if (cedar == NULL || username == NULL || password == NULL || client_hostname == NULL)
  	{
  		return NULL;
  	}
  	if (IsEmptyStr(client_name))
  	{
  		client_name = "InProc VPN Connection";
  	}
  	if (IsEmptyStr(crypt_name))
  	{
  		crypt_name = "";
  	}
  	if (error_code == NULL)
  	{
  		error_code = &dummy_int;
  	}
  
  	Zero(mschap_v2_server_response_20, sizeof(mschap_v2_server_response_20));
  
  	err = *error_code = ERR_INTERNAL_ERROR;
  
  	a = GetInProcListeningSock(cedar);
  	if (a == NULL)
  	{
  		return NULL;
  	}
  
  	ipc = ZeroMalloc(sizeof(IPC));
  
  	ipc->Cedar = cedar;
  	AddRef(cedar->ref);
  
  	ipc->Layer = layer;
  	if (ipc->Layer == 0)
  	{
  		ipc->Layer = IPC_LAYER_2;
  	}
  
  	ipc->FlushList = NewTubeFlushList();
  
  	StrCpy(ipc->ClientHostname, sizeof(ipc->ClientHostname), client_hostname);
  	StrCpy(ipc->HubName, sizeof(ipc->HubName), hubname);
  	StrCpy(ipc->UserName, sizeof(ipc->UserName), username);
  	StrCpy(ipc->Password, sizeof(ipc->Password), password);
  
  	// Connect the in-process socket
  	s = ConnectInProc(a, client_ip, client_port, server_ip, server_port);
  	if (s == NULL)
  	{
  		goto LABEL_ERROR;
  	}
  
  	// Protocol initialization process
  	if (ClientUploadSignature(s) == false)
  	{
  		err = ERR_DISCONNECTED;
  		goto LABEL_ERROR;
  	}
  
  	p = HttpClientRecv(s);
  	if (p == NULL)
  	{
  		err = ERR_DISCONNECTED;
  		goto LABEL_ERROR;
  	}
  
  	err = GetErrorFromPack(p);
  	if (err != ERR_NO_ERROR)
  	{
  		FreePack(p);
  		goto LABEL_ERROR;
  	}
  
  	if (GetHello(p, ipc->random, &server_ver, &server_build, server_str, sizeof(server_str)) == false)
  	{
  		FreePack(p);
  		err = ERR_DISCONNECTED;
  		goto LABEL_ERROR;
  	}
  
  	FreePack(p);
  
  	// Upload the authentication data
  	if (client_certificate != NULL)
  	{
  		p = PackLoginWithOpenVPNCertificate(hubname, username, client_certificate);
  	}
  	else
  	{
  		p = PackLoginWithPlainPassword(hubname, username, password);
  	}
  
  	if (p == NULL)
  	{
  		err = ERR_AUTH_FAILED;
  		goto LABEL_ERROR;
  	}
  
  	PackAddStr(p, "hello", client_name);
  	PackAddInt(p, "client_ver", cedar->Version);
  	PackAddInt(p, "client_build", cedar->Build);
  	PackAddInt(p, "max_connection", 1);
  	PackAddInt(p, "use_encrypt", 0);
  	PackAddInt(p, "use_compress", 0);
  	PackAddInt(p, "half_connection", 0);
  	PackAddInt(p, "adjust_mss", mss);
  	PackAddBool(p, "require_bridge_routing_mode", bridge_mode);
  	PackAddBool(p, "require_monitor_mode", false);
  	PackAddBool(p, "qos", false);
  
  	if (eap_client != NULL)
  	{
  		UINT64 ptr = (UINT64)eap_client;
  		PackAddInt64(p, "release_me_eap_client", ptr);
  
  		AddRef(eap_client->Ref);
  	}
  
  	// Unique ID is determined by the sum of the connecting client IP address and the client_name
  	b = NewBuf();
  	WriteBuf(b, client_ip, sizeof(IP));
  	WriteBufStr(b, client_name);
  	WriteBufStr(b, crypt_name);
  
  	Sha1(unique, b->Buf, b->Size);
  
  	FreeBuf(b);
  
  	PackAddData(p, "unique_id", unique, SHA1_SIZE);
  
  	PackAddStr(p, "inproc_postfix", postfix);
  	PackAddStr(p, "inproc_cryptname", crypt_name);
  	PackAddInt(p, "inproc_layer", ipc->Layer);
  
  	// Node information
  	Zero(&info, sizeof(info));
  	StrCpy(info.ClientProductName, sizeof(info.ClientProductName), client_name);
  	info.ClientProductVer = Endian32(cedar->Version);
  	info.ClientProductBuild = Endian32(cedar->Build);
  	StrCpy(info.ServerProductName, sizeof(info.ServerProductName), server_str);
  	info.ServerProductVer = Endian32(server_ver);
  	info.ServerProductBuild = Endian32(server_build);
  	StrCpy(info.ClientOsName, sizeof(info.ClientOsName), client_name);
  	StrCpy(info.ClientOsVer, sizeof(info.ClientOsVer), "-");
  	StrCpy(info.ClientOsProductId, sizeof(info.ClientOsProductId), "-");
  	info.ClientIpAddress = IPToUINT(&s->LocalIP);
  	info.ClientPort = Endian32(s->LocalPort);
  	StrCpy(info.ClientHostname, sizeof(info.ClientHostname), ipc->ClientHostname);
  	IPToStr(info.ServerHostname, sizeof(info.ServerHostname), &s->RemoteIP);
  	info.ServerIpAddress = IPToUINT(&s->RemoteIP);
  	info.ServerPort = Endian32(s->RemotePort);
  	StrCpy(info.HubName, sizeof(info.HubName), hubname);
  	Copy(info.UniqueId, unique, 16);
  	if (IsIP6(&s->LocalIP))
  	{
  		Copy(info.ClientIpAddress6, s->LocalIP.ipv6_addr, 16);
  	}
  	if (IsIP6(&s->RemoteIP))
  	{
  		Copy(info.ServerIpAddress6, s->RemoteIP.ipv6_addr, 16);
  	}
  	OutRpcNodeInfo(p, &info);
  
  	if (HttpClientSend(s, p) == false)
  	{
  		FreePack(p);
  		err = ERR_DISCONNECTED;
  		goto LABEL_ERROR;
  	}
  
  	FreePack(p);
  
  	// Receive a Welcome packet
  	p = HttpClientRecv(s);
  	if (p == NULL)
  	{
  		err = ERR_DISCONNECTED;
  		goto LABEL_ERROR;
  	}
  
  	err = GetErrorFromPack(p);
  	if (err != ERR_NO_ERROR)
  	{
  		FreePack(p);
  		goto LABEL_ERROR;
  	}
  
  	if (ParseWelcomeFromPack(p, ipc->SessionName, sizeof(ipc->SessionName),
  	                         ipc->ConnectionName, sizeof(ipc->ConnectionName), &ipc->Policy) == false)
  	{
  		err = ERR_PROTOCOL_ERROR;
  		FreePack(p);
  		goto LABEL_ERROR;
  	}
  
  	if (PackGetData2(p, "IpcMacAddress", ipc->MacAddress, 6) == false || IsZero(ipc->MacAddress, 6))
  	{
  		err = ERR_PROTOCOL_ERROR;
  		FreePack(p);
  		goto LABEL_ERROR;
  	}
  
  	if (PackGetData2(p, "IpcMsChapV2ServerResponse", mschap_v2_server_response_20, sizeof(mschap_v2_server_response_20)))
  	{
  		Copy(ipc->MsChapV2_ServerResponse, mschap_v2_server_response_20, sizeof(mschap_v2_server_response_20));
  	}
  
  	PackGetStr(p, "IpcHubName", ipc->HubName, sizeof(ipc->HubName));
  	Debug("IPC Hub Name: %s\n", ipc->HubName);
  
  	MacToStr(macstr, sizeof(macstr), ipc->MacAddress);
  
  	Debug("IPC: Session = %s, Connection = %s, Mac = %s\n", ipc->SessionName, ipc->ConnectionName, macstr);
  
  	u64 = PackGetInt64(p, "IpcSessionSharedBuffer");
  	ipc->IpcSessionSharedBuffer = (SHARED_BUFFER *)u64;
  	ipc->IpcSessionShared = ipc->IpcSessionSharedBuffer->Data;
  
  	FreePack(p);
  
  	ReleaseSock(a);
  	ipc->Sock = s;
  
  	Debug("NewIPC() Succeed.\n");
  
  	ipc->Interrupt = NewInterruptManager();
  
  	// Create an ARP table
  	ipc->ArpTable = NewList(IPCCmpArpTable);
  
  	// Create an IPv4 reception queue
  	ipc->IPv4ReceivedQueue = NewQueue();
  	ipc->IPv4State = IPC_PROTO_STATUS_CLOSED;
  
  	ipc->DHCPv4Awaiter.IsAwaiting = false;
  	ipc->DHCPv4Awaiter.DhcpData = NULL;
  
  	IPCIPv6Init(ipc);
  
  	return ipc;
  
  LABEL_ERROR:
  	Debug("NewIPC() Failed: Err = %u\n", err);
  	Disconnect(s);
  	ReleaseSock(s);
  	ReleaseSock(a);
  	FreeIPC(ipc);
  	*error_code = err;
  	return NULL;
  }
  
  // Create a new IPC based on SOCK
  IPC *NewIPCBySock(CEDAR *cedar, SOCK *s, void *mac_address)
  {
  	IPC *ipc;
  	// Validate arguments
  	if (cedar == NULL || mac_address == NULL || s == NULL)
  	{
  		return NULL;
  	}
  
  	ipc = ZeroMalloc(sizeof(IPC));
  
  	ipc->Cedar = cedar;
  	AddRef(cedar->ref);
  
  	ipc->Sock = s;
  	AddRef(s->ref);
  
  	Copy(ipc->MacAddress, mac_address, 6);
  
  	ipc->Interrupt = NewInterruptManager();
  
  	// Create an ARP table
  	ipc->ArpTable = NewList(IPCCmpArpTable);
  
  	// Create an IPv4 reception queue
  	ipc->IPv4ReceivedQueue = NewQueue();
  	ipc->IPv4State = IPC_PROTO_STATUS_CLOSED;
  
  	ipc->DHCPv4Awaiter.IsAwaiting = false;
  	ipc->DHCPv4Awaiter.DhcpData = NULL;
  
  	ipc->FlushList = NewTubeFlushList();
  
  	IPCIPv6Init(ipc);
  
  	return ipc;
  }
  
  // Get whether the IPC is connected
  bool IsIPCConnected(IPC *ipc)
  {
  	// Validate arguments
  	if (ipc == NULL)
  	{
  		return false;
  	}
  
  	if (IsTubeConnected(ipc->Sock->RecvTube) == false || IsTubeConnected(ipc->Sock->SendTube) == false)
  	{
  		return false;
  	}
  
  	return true;
  }
  
  // Get to hit the SOCK_EVENT when a new data has arrived in the IPC
  void IPCSetSockEventWhenRecvL2Packet(IPC *ipc, SOCK_EVENT *e)
  {
  	// Validate arguments
  	if (ipc == NULL || e == NULL)
  	{
  		return;
  	}
  
  	JoinSockToSockEvent(ipc->Sock, e);
  }
  
  // End of IPC connection
  void FreeIPC(IPC *ipc)
  {
  	UINT i;
  	// Validate arguments
  	if (ipc == NULL)
  	{
  		return;
  	}
  
  	FreeTubeFlushList(ipc->FlushList);
  
  	Disconnect(ipc->Sock);
  	ReleaseSock(ipc->Sock);
  
  	if (ipc->Policy != NULL)
  	{
  		Free(ipc->Policy);
  	}
  
  	ReleaseCedar(ipc->Cedar);
  
  	FreeInterruptManager(ipc->Interrupt);
  
  	for (i = 0; i < LIST_NUM(ipc->ArpTable); i++)
  	{
  		IPC_ARP *a = LIST_DATA(ipc->ArpTable, i);
  		IPCFreeARP(a);
  	}
  
  	ReleaseList(ipc->ArpTable);
  
  	while (true)
  	{
  		BLOCK *b = GetNext(ipc->IPv4ReceivedQueue);
  		if (b == NULL)
  		{
  			break;
  		}
  
  		FreeBlock(b);
  	}
  
  	ReleaseQueue(ipc->IPv4ReceivedQueue);
  
  	ReleaseSharedBuffer(ipc->IpcSessionSharedBuffer);
  
  	FreeDHCPv4Data(ipc->DHCPv4Awaiter.DhcpData);
  
  	IPCIPv6Free(ipc);
  
  	Free(ipc);
  }
  
  // Set User Class option if corresponding Virtual Hub optin is set
  void IPCDhcpSetConditionalUserClass(IPC *ipc, DHCP_OPTION_LIST *req)
  {
  	HUB *hub;
  
  	hub = GetHub(ipc->Cedar, ipc->HubName);
  	if (hub == NULL)
  	{
  		return;
  	}
  
  	if (hub->Option && hub->Option->UseHubNameAsDhcpUserClassOption)
  	{
  		StrCpy(req->UserClass, sizeof(req->UserClass), ipc->HubName);
  	}
  	ReleaseHub(hub);
  }
  
  // Release the IP address from the DHCP server
  void IPCDhcpFreeIP(IPC *ipc, IP *dhcp_server)
  {
  	DHCP_OPTION_LIST req;
  	UINT tran_id = Rand32();
  	// Validate arguments
  	if (ipc == NULL || dhcp_server == NULL)
  	{
  		return;
  	}
  
  	Zero(&req, sizeof(req));
  	req.Opcode = DHCP_RELEASE;
  	req.ServerAddress = IPToUINT(dhcp_server);
  	IPCDhcpSetConditionalUserClass(ipc, &req);
  
  	FreeDHCPv4Data(IPCSendDhcpRequest(ipc, NULL, tran_id, &req, 0, 0, NULL));
  }
  
  // Update the IP address using the DHCP
  void IPCDhcpRenewIP(IPC *ipc, IP *dhcp_server)
  {
  	DHCP_OPTION_LIST req;
  	UINT tran_id = Rand32();
  	// Validate arguments
  	if (ipc == NULL || dhcp_server == NULL)
  	{
  		return;
  	}
  
  	// Send a DHCP Request
  	Zero(&req, sizeof(req));
  	req.Opcode = DHCP_REQUEST;
  	StrCpy(req.Hostname, sizeof(req.Hostname), ipc->ClientHostname);
  	req.RequestedIp = IPToUINT(&ipc->ClientIPAddress);
  	IPCDhcpSetConditionalUserClass(ipc, &req);
  
  	FreeDHCPv4Data(IPCSendDhcpRequest(ipc, dhcp_server, tran_id, &req, 0, 0, NULL));
  }
  
  // Get the information other than the IP address with using DHCP
  bool IPCDhcpRequestInformIP(IPC *ipc, DHCP_OPTION_LIST *opt, TUBE *discon_poll_tube, IP *client_ip)
  {
  	DHCP_OPTION_LIST req;
  	DHCPV4_DATA *d;
  	UINT tran_id = Rand32();
  	bool ok;
  	// Validate arguments
  	if (ipc == NULL || opt == NULL || client_ip == NULL)
  	{
  		return false;
  	}
  
  	// Send a DHCP Inform
  	Zero(&req, sizeof(req));
  	req.Opcode = DHCP_INFORM;
  	req.ClientAddress = IPToUINT(client_ip);
  	StrCpy(req.Hostname, sizeof(req.Hostname), ipc->ClientHostname);
  	IPCDhcpSetConditionalUserClass(ipc, &req);
  
  	d = IPCSendDhcpRequest(ipc, NULL, tran_id, &req, DHCP_ACK, IPC_DHCP_TIMEOUT, discon_poll_tube);
  	if (d == NULL)
  	{
  		return false;
  	}
  
  	// Analyze the DHCP Ack
  	ok = true;
  	if (d->ParsedOptionList->SubnetMask == 0)
  	{
  		ok = false;
  	}
  
  	if (ok == false)
  	{
  		FreeDHCPv4Data(d);
  		return false;
  	}
  
  	Copy(opt, d->ParsedOptionList, sizeof(DHCP_OPTION_LIST));
  
  	FreeDHCPv4Data(d);
  
  	return true;
  }
  
  // Make a request for IP addresses using DHCP
  bool IPCDhcpAllocateIP(IPC *ipc, DHCP_OPTION_LIST *opt, TUBE *discon_poll_tube)
  {
  	DHCP_OPTION_LIST req;
  	DHCPV4_DATA *d, *d2;
  	UINT tran_id = Rand32();
  	bool ok;
  	// Validate arguments
  	if (ipc == NULL || opt == NULL)
  	{
  		return false;
  	}
  
  	// Send a DHCP Discover
  	Zero(&req, sizeof(req));
  	req.RequestedIp = 0;
  	req.Opcode = DHCP_DISCOVER;
  	StrCpy(req.Hostname, sizeof(req.Hostname), ipc->ClientHostname);
  	IPCDhcpSetConditionalUserClass(ipc, &req);
  
  	d = IPCSendDhcpRequest(ipc, NULL, tran_id, &req, DHCP_OFFER, IPC_DHCP_TIMEOUT, discon_poll_tube);
  	if (d == NULL)
  	{
  		return false;
  	}
  
  	// Analyze the DHCP Offer
  	ok = true;
  	if (IsValidUnicastIPAddressUINT4(d->ParsedOptionList->ClientAddress) == false)
  	{
  		ok = false;
  	}
  	if (IsValidUnicastIPAddressUINT4(d->ParsedOptionList->ServerAddress) == false)
  	{
  		ok = false;
  	}
  	if (d->ParsedOptionList->SubnetMask == 0)
  	{
  		ok = false;
  	}
  	if (d->ParsedOptionList->LeaseTime == 0)
  	{
  		d->ParsedOptionList->LeaseTime = IPC_DHCP_DEFAULT_LEASE;
  	}
  	if (d->ParsedOptionList->LeaseTime <= IPC_DHCP_MIN_LEASE)
  	{
  		d->ParsedOptionList->LeaseTime = IPC_DHCP_MIN_LEASE;
  	}
  
  	if (ok == false)
  	{
  		FreeDHCPv4Data(d);
  		return false;
  	}
  
  	// Send a DHCP Request
  	Zero(&req, sizeof(req));
  	req.Opcode = DHCP_REQUEST;
  	StrCpy(req.Hostname, sizeof(req.Hostname), ipc->ClientHostname);
  	req.ServerAddress = d->ParsedOptionList->ServerAddress;
  	req.RequestedIp = d->ParsedOptionList->ClientAddress;
  	IPCDhcpSetConditionalUserClass(ipc, &req);
  
  	d2 = IPCSendDhcpRequest(ipc, NULL, tran_id, &req, DHCP_ACK, IPC_DHCP_TIMEOUT, discon_poll_tube);
  	if (d2 == NULL)
  	{
  		FreeDHCPv4Data(d);
  		return false;
  	}
  
  	// Analyze the DHCP Ack
  	ok = true;
  	if (IsValidUnicastIPAddressUINT4(d2->ParsedOptionList->ClientAddress) == false)
  	{
  		ok = false;
  	}
  	if (IsValidUnicastIPAddressUINT4(d2->ParsedOptionList->ServerAddress) == false)
  	{
  		ok = false;
  	}
  	if (d2->ParsedOptionList->SubnetMask == 0)
  	{
  		ok = false;
  	}
  	if (d2->ParsedOptionList->LeaseTime == 0)
  	{
  		d2->ParsedOptionList->LeaseTime = IPC_DHCP_DEFAULT_LEASE;
  	}
  	if (d2->ParsedOptionList->LeaseTime <= IPC_DHCP_MIN_LEASE)
  	{
  		d2->ParsedOptionList->LeaseTime = IPC_DHCP_MIN_LEASE;
  	}
  
  	if (ok == false)
  	{
  		FreeDHCPv4Data(d);
  		FreeDHCPv4Data(d2);
  		return false;
  	}
  
  	Copy(opt, d2->ParsedOptionList, sizeof(DHCP_OPTION_LIST));
  
  	FreeDHCPv4Data(d);
  	FreeDHCPv4Data(d2);
  
  	return true;
  }
  
  // Send out a DHCP request, and wait for a corresponding response
  DHCPV4_DATA *IPCSendDhcpRequest(IPC *ipc, IP *dest_ip, UINT tran_id, DHCP_OPTION_LIST *opt, UINT expecting_code, UINT timeout, TUBE *discon_poll_tube)
  {
  	UINT resend_interval;
  	UINT64 giveup_time;
  	UINT64 next_send_time = 0;
  	TUBE *tubes[3];
  	UINT num_tubes = 0;
  	// Validate arguments
  	if (ipc == NULL || opt == NULL || (expecting_code != 0 && timeout == 0))
  	{
  		return NULL;
  	}
  
  	// Retransmission interval
  	resend_interval = MAX(1, (timeout / 3) - 100);
  
  	// Time-out time
  	giveup_time = Tick64() + (UINT64)timeout;
  
  	AddInterrupt(ipc->Interrupt, giveup_time);
  
  	tubes[num_tubes++] = ipc->Sock->RecvTube;
  	tubes[num_tubes++] = ipc->Sock->SendTube;
  
  	if (discon_poll_tube != NULL)
  	{
  		tubes[num_tubes++] = discon_poll_tube;
  	}
  
  	while (true)
  	{
  		UINT64 now = Tick64();
  		BUF *dhcp_packet;
  
  		IPCFlushArpTable(ipc);
  
  		// Time-out inspection
  		if ((expecting_code != 0) && (now >= giveup_time))
  		{
  			ipc->DHCPv4Awaiter.IsAwaiting = false;
  			FreeDHCPv4Data(ipc->DHCPv4Awaiter.DhcpData);
  			ipc->DHCPv4Awaiter.DhcpData = NULL;
  			return NULL;
  		}
  
  		// Send by building a DHCP packet periodically
  		if (next_send_time == 0 || next_send_time <= now)
  		{
  			dhcp_packet = IPCBuildDhcpRequest(ipc, dest_ip, tran_id, opt);
  			if (dhcp_packet == NULL)
  			{
  				ipc->DHCPv4Awaiter.IsAwaiting = false;
  				FreeDHCPv4Data(ipc->DHCPv4Awaiter.DhcpData);
  				ipc->DHCPv4Awaiter.DhcpData = NULL;
  				return NULL;
  			}
  
  			IPCSendIPv4(ipc, dhcp_packet->Buf, dhcp_packet->Size);
  
  			FreeBuf(dhcp_packet);
  
  			if (expecting_code == 0)
  			{
  				ipc->DHCPv4Awaiter.IsAwaiting = false;
  				FreeDHCPv4Data(ipc->DHCPv4Awaiter.DhcpData);
  				ipc->DHCPv4Awaiter.DhcpData = NULL;
  				return NULL;
  			}
  
  			next_send_time = now + (UINT64)resend_interval;
  
  			AddInterrupt(ipc->Interrupt, next_send_time);
  		}
  
  		// Happy processing
  		ipc->DHCPv4Awaiter.IsAwaiting = true;
  		FreeDHCPv4Data(ipc->DHCPv4Awaiter.DhcpData);
  		ipc->DHCPv4Awaiter.DhcpData = NULL;
  		ipc->DHCPv4Awaiter.TransCode = tran_id;
  		ipc->DHCPv4Awaiter.OpCode = expecting_code;
  		IPCProcessL3Events(ipc);
  
  		if (ipc->DHCPv4Awaiter.DhcpData != NULL)
  		{
  			DHCPV4_DATA *dhcp = ipc->DHCPv4Awaiter.DhcpData;
  			ipc->DHCPv4Awaiter.IsAwaiting = false;
  			ipc->DHCPv4Awaiter.DhcpData = NULL;
  			return dhcp;
  		}
  
  		if (IsTubeConnected(ipc->Sock->RecvTube) == false || IsTubeConnected(ipc->Sock->SendTube) == false ||
  		        (discon_poll_tube != NULL && IsTubeConnected(discon_poll_tube) == false))
  		{
  			// Session is disconnected
  			ipc->DHCPv4Awaiter.IsAwaiting = false;
  			FreeDHCPv4Data(ipc->DHCPv4Awaiter.DhcpData);
  			ipc->DHCPv4Awaiter.DhcpData = NULL;
  			return NULL;
  		}
  
  		// Keep the CPU waiting
  		WaitForTubes(tubes, num_tubes, GetNextIntervalForInterrupt(ipc->Interrupt));
  	}
  
  	ipc->DHCPv4Awaiter.IsAwaiting = false;
  	FreeDHCPv4Data(ipc->DHCPv4Awaiter.DhcpData);
  	ipc->DHCPv4Awaiter.DhcpData = NULL;
  	return NULL;
  }
  
  // Build a DHCP request packet
  BUF *IPCBuildDhcpRequest(IPC *ipc, IP *dest_ip, UINT tran_id, DHCP_OPTION_LIST *opt)
  {
  	IPV4_HEADER ip;
  	UDP_HEADER *udp;
  	DHCPV4_HEADER dhcp;
  	UINT blank_size = 128 + 64;
  	BUF *ret;
  	BUF *b;
  	UDPV4_PSEUDO_HEADER *ph;
  	UINT ph_size;
  	UINT udp_size;
  	UINT magic_number = Endian32(DHCP_MAGIC_COOKIE);
  	USHORT checksum;
  	// Validate arguments
  	if (ipc == NULL || opt == NULL)
  	{
  		return NULL;
  	}
  
  	// DHCPv4 Options
  	b = IPCBuildDhcpRequestOptions(ipc, opt);
  	if (b == NULL)
  	{
  		return NULL;
  	}
  
  	// DHCPv4 Header
  	Zero(&dhcp, sizeof(dhcp));
  	dhcp.OpCode = 1;
  	dhcp.HardwareType = ARP_HARDWARE_TYPE_ETHERNET;
  	dhcp.HardwareAddressSize = 6;
  	dhcp.Hops = 0;
  	dhcp.TransactionId = Endian32(tran_id);
  	dhcp.ClientIP = IPToUINT(&ipc->ClientIPAddress);
  	if (dhcp.ClientIP == 0)
  	{
  		dhcp.ClientIP = opt->ClientAddress;
  	}
  	Copy(dhcp.ClientMacAddress, ipc->MacAddress, 6);
  
  	// UDP pseudo header
  	ph_size = b->Size + sizeof(dhcp) + blank_size + sizeof(UINT) + sizeof(UDPV4_PSEUDO_HEADER);
  	udp_size = b->Size + sizeof(dhcp) + blank_size + sizeof(UINT) + sizeof(UDP_HEADER);
  
  	ph = ZeroMalloc(ph_size);
  	ph->SrcIP = IPToUINT(&ipc->ClientIPAddress);
  	ph->DstIP = IPToUINT(dest_ip);
  	ph->Protocol = IP_PROTO_UDP;
  	ph->PacketLength1 = Endian16(udp_size);
  	ph->SrcPort = Endian16(NAT_DHCP_CLIENT_PORT);
  	ph->DstPort = Endian16(NAT_DHCP_SERVER_PORT);
  	ph->PacketLength2 = Endian16(udp_size);
  
  	Copy(((UCHAR *)(ph)) + sizeof(UDPV4_PSEUDO_HEADER), &dhcp, sizeof(dhcp));
  	Copy(((UCHAR *)(ph)) + sizeof(UDPV4_PSEUDO_HEADER) + sizeof(dhcp) + blank_size, &magic_number, sizeof(UINT));
  	Copy(((UCHAR *)(ph)) + sizeof(UDPV4_PSEUDO_HEADER) + sizeof(dhcp) + blank_size + sizeof(UINT),
  	     b->Buf, b->Size);
  
  	// UDP Header
  	udp = (UDP_HEADER *)(((UCHAR *)ph) + 12);
  
  	// Calculate the checksum
  	checksum = IpChecksum(ph, ph_size);
  	if (checksum == 0x0000)
  	{
  		checksum = 0xffff;
  	}
  	udp->Checksum = checksum;
  
  	// IP Header
  	Zero(&ip, sizeof(ip));
  	IPV4_SET_VERSION(&ip, 4);
  	IPV4_SET_HEADER_LEN(&ip, 5);
  	ip.Identification = Rand16();
  	ip.TimeToLive = 128;
  	ip.Protocol = IP_PROTO_UDP;
  	ip.SrcIP = IPToUINT(&ipc->ClientIPAddress);
  	if (dest_ip != NULL)
  	{
  		ip.DstIP = IPToUINT(dest_ip);
  	}
  	else
  	{
  		ip.DstIP = Endian32(0xffffffff);
  	}
  	ip.TotalLength = Endian16((USHORT)(sizeof(IPV4_HEADER) + udp_size));
  	ip.Checksum = IpChecksum(&ip, sizeof(IPV4_HEADER));
  
  	ret = NewBuf();
  
  	WriteBuf(ret, &ip, sizeof(IPV4_HEADER));
  	WriteBuf(ret, udp, udp_size);
  
  	FreeBuf(b);
  	Free(ph);
  
  	return ret;
  }
  
  // Build a option data in the DHCP request packet
  BUF *IPCBuildDhcpRequestOptions(IPC *ipc, DHCP_OPTION_LIST *opt)
  {
  	LIST *o;
  	UCHAR opcode;
  	BUF *ret;
  	// Validate arguments
  	if (ipc == NULL || opt == NULL)
  	{
  		return NULL;
  	}
  
  	o = NewListFast(NULL);
  
  	// Opcode
  	opcode = opt->Opcode;
  	Add(o, NewDhcpOption(DHCP_ID_MESSAGE_TYPE, &opcode, sizeof(opcode)));
  
  	// Server ID
  	if (opt->ServerAddress != 0)
  	{
  		Add(o, NewDhcpOption(DHCP_ID_SERVER_ADDRESS, &opt->ServerAddress, 4));
  	}
  
  	// Requested IP Address
  	if (opt->RequestedIp != 0)
  	{
  		Add(o, NewDhcpOption(DHCP_ID_REQUEST_IP_ADDRESS, &opt->RequestedIp, 4));
  	}
  
  	// Hostname
  	if (IsEmptyStr(opt->Hostname) == false)
  	{
  		UCHAR client_id[MAX_HOST_NAME_LEN + 32];
  		UCHAR macstr[30];
  		MacToStr(macstr, sizeof(macstr), ipc->MacAddress);
  
  		Format(client_id, sizeof(client_id), "%s/%s", opt->Hostname, macstr);
  
  		Add(o, NewDhcpOption(DHCP_ID_HOST_NAME, opt->Hostname, StrLen(opt->Hostname)));
  		Add(o, NewDhcpOption(DHCP_ID_CLIENT_ID, client_id, StrLen(client_id)));
  	}
  	else // Client MAC Address
  	{
  		UCHAR client_id[7];
  		client_id[0] = ARP_HARDWARE_TYPE_ETHERNET;
  		Copy(client_id + 1, ipc->MacAddress, 6);
  		Add(o, NewDhcpOption(DHCP_ID_CLIENT_ID, client_id, sizeof(client_id)));
  	}
  
  	// User Class
  	if (IsEmptyStr(opt->UserClass) == false)
  	{
  		Add(o, NewDhcpOption(DHCP_ID_USER_CLASS, opt->UserClass, StrLen(opt->UserClass)));
  	}
  
  	// Vendor
  	Add(o, NewDhcpOption(DHCP_ID_VENDOR_ID, IPC_DHCP_VENDOR_ID, StrLen(IPC_DHCP_VENDOR_ID)));
  
  	// Parameter Request List
  	if (opcode == DHCP_DISCOVER || opcode == DHCP_REQUEST || opcode == DHCP_INFORM)
  	{
  		UCHAR param_list[12];
  
  		param_list[0] = 1;
  		param_list[1] = 15;
  		param_list[2] = 3;
  		param_list[3] = 6;
  		param_list[4] = 44;
  		param_list[5] = 46;
  		param_list[6] = 47;
  		param_list[7] = 31;
  		param_list[8] = 33;
  		param_list[9] = 121;
  		param_list[10] = 249;
  		param_list[11] = 43;
  
  		Add(o, NewDhcpOption(DHCP_ID_REQ_PARAM_LIST, param_list, sizeof(param_list)));
  	}
  
  	ret = BuildDhcpOptionsBuf(o);
  
  	FreeDhcpOptions(o);
  
  	return ret;
  }
  
  // Process the received ARP
  void IPCProcessArp(IPC *ipc, BLOCK *b)
  {
  	UCHAR *dest_mac;
  	UCHAR *src_mac;
  	ARPV4_HEADER *arp;
  	UCHAR *sender_mac;
  	IP sender_ip;
  	UCHAR *target_mac;
  	IP target_ip;
  	// Validate arguments
  	if (ipc == NULL || b == NULL || b->Size < (14 + sizeof(ARPV4_HEADER)))
  	{
  		return;
  	}
  
  	dest_mac = b->Buf + 0;
  	src_mac = b->Buf + 6;
  
  	arp = (ARPV4_HEADER *)(b->Buf + 14);
  
  	if (arp->HardwareType != Endian16(ARP_HARDWARE_TYPE_ETHERNET))
  	{
  		return;
  	}
  	if (arp->ProtocolType != Endian16(MAC_PROTO_IPV4))
  	{
  		return;
  	}
  	if (arp->HardwareSize != 6 || arp->ProtocolSize != 4)
  	{
  		return;
  	}
  
  	sender_mac = arp->SrcAddress;
  	UINTToIP(&sender_ip, arp->SrcIP);
  
  	target_mac = arp->TargetAddress;
  	UINTToIP(&target_ip, arp->TargetIP);
  
  	if (CmpIpAddr(&sender_ip, &ipc->ClientIPAddress) == 0)
  	{
  		// Source is myself
  		return;
  	}
  
  	IPCAssociateOnArpTable(ipc, &sender_ip, sender_mac);
  	IPCAssociateOnArpTable(ipc, &target_ip, target_mac);
  
  	if (Endian16(arp->Operation) == ARP_OPERATION_REQUEST)
  	{
  		// Received an ARP request
  		if (CmpIpAddr(&target_ip, &ipc->ClientIPAddress) == 0)
  		{
  			// Create a response since a request for its own IP address have received
  			if (IsMacUnicast(sender_mac))
  			{
  				UCHAR tmp[14 + sizeof(ARPV4_HEADER)];
  				ARPV4_HEADER *arp = (ARPV4_HEADER *)(tmp + 14);
  
  				Copy(tmp + 0, sender_mac, 6);
  				Copy(tmp + 6, ipc->MacAddress, 6);
  				WRITE_USHORT(tmp + 12, MAC_PROTO_ARPV4);
  
  				arp->HardwareType = Endian16(ARP_HARDWARE_TYPE_ETHERNET);
  				arp->ProtocolType = Endian16(MAC_PROTO_IPV4);
  				arp->HardwareSize = 6;
  				arp->ProtocolSize = 4;
  				arp->Operation = Endian16(ARP_OPERATION_RESPONSE);
  
  				Copy(arp->SrcAddress, ipc->MacAddress, 6);
  				arp->SrcIP = IPToUINT(&ipc->ClientIPAddress);
  
  				Copy(arp->TargetAddress, sender_mac, 6);
  				arp->TargetIP = IPToUINT(&sender_ip);
  
  				IPCSendL2(ipc, tmp, sizeof(tmp));
  			}
  		}
  	}
  }
  
  // Associate the MAC address and IP address on the ARP table
  void IPCAssociateOnArpTable(IPC *ipc, IP *ip, UCHAR *mac_address)
  {
  	IPC_ARP *a;
  	// Validate arguments
  	if (ipc == NULL || ip == NULL || IsValidUnicastIPAddress4(ip) == false || IsMacUnicast(mac_address) == false)
  	{
  		return;
  	}
  	if (CmpIpAddr(&ipc->ClientIPAddress, ip) == 0 || Cmp(ipc->MacAddress, mac_address, 6) == 0)
  	{
  		return;
  	}
  	if (IsInSameNetwork4(ip, &ipc->ClientIPAddress, &ipc->SubnetMask) == false)
  	{
  		// Not to learn the IP address of outside the subnet
  		return;
  	}
  
  	if (CmpIpAddr(&ipc->BroadcastAddress, ip) == 0)
  	{
  		// Not to learn the broadcast IP address
  		return;
  	}
  
  	// Search whether there is ARP table entry already
  	a = IPCSearchArpTable(ipc->ArpTable, ip);
  	if (a == NULL)
  	{
  		// Add to the ARP table
  		a = IPCNewARP(ip, mac_address);
  
  		Insert(ipc->ArpTable, a);
  	}
  	else
  	{
  		Copy(a->MacAddress, mac_address, 6);
  
  		// There is the ARP table entry already
  		if (a->Resolved == false)
  		{
  			a->Resolved = true;
  			a->GiveupTime = 0;
  
  			// Send all the packets that are accumulated to be sent
  			while (true)
  			{
  				BLOCK *b = GetNext(a->PacketQueue);
  
  				if (b == NULL)
  				{
  					break;
  				}
  
  				IPCSendIPv4WithDestMacAddr(ipc, b->Buf, b->Size, a->MacAddress);
  
  				FreeBlock(b);
  			}
  		}
  
  		// Extend the expiration date
  		a->ExpireTime = Tick64() + (UINT64)IPC_ARP_LIFETIME;
  	}
  }
  
  // Interrupt process (This is called periodically)
  void IPCProcessInterrupts(IPC *ipc)
  {
  	// Validate arguments
  	if (ipc == NULL)
  	{
  		return;
  	}
  
  	FlushTubeFlushList(ipc->FlushList);
  }
  
  // Process the L3 event by the IPC
  void IPCProcessL3Events(IPC *ipc)
  {
  	IPCProcessL3EventsEx(ipc, 0);
  }
  void IPCProcessL3EventsIPv4Only(IPC *ipc)
  {
  	UINT previousStatus4 = IPC_PROTO_GET_STATUS(ipc, IPv4State);
  	UINT previousStatus6 = IPC_PROTO_GET_STATUS(ipc, IPv6State);
  	IPC_PROTO_SET_STATUS(ipc, IPv4State, IPC_PROTO_STATUS_OPENED);
  	IPC_PROTO_SET_STATUS(ipc, IPv6State, IPC_PROTO_STATUS_CLOSED);
  	IPCProcessL3Events(ipc);
  	IPC_PROTO_SET_STATUS(ipc, IPv4State, previousStatus4);
  	IPC_PROTO_SET_STATUS(ipc, IPv6State, previousStatus6);
  }
  void IPCProcessL3EventsEx(IPC *ipc, UINT64 now)
  {
  	// Validate arguments
  	if (ipc == NULL)
  	{
  		return;
  	}
  	if (now == 0)
  	{
  		now = Tick64();
  	}
  
  	// Remove old ARP table entries
  	IPCFlushArpTableEx(ipc, now);
  	IPCIPv6FlushNDTEx(ipc, now);
  
  	// Receive all the L2 packet
  	while (true)
  	{
  		BLOCK *b = IPCRecvL2(ipc);
  		if (b == NULL)
  		{
  			// All reception completed
  			break;
  		}
  
  		if (b->Size >= 14)
  		{
  			UCHAR *dest_mac = b->Buf + 0;
  			UCHAR *src_mac = b->Buf + 6;
  			USHORT protocol = READ_USHORT(b->Buf + 12);
  
  			// Confirm the destination MAC address
  			// (Receive if the destination MAC address is the IPC address or a broadcast address)
  			if (Cmp(dest_mac, ipc->MacAddress, 6) == 0 || IsMacBroadcast(dest_mac) || IsMacMulticast(dest_mac))
  			{
  				// If the source MAC address is itselves or invalid address, ignore the packet
  				if (Cmp(src_mac, ipc->MacAddress, 6) != 0 && IsMacUnicast(src_mac))
  				{
  					if (protocol == MAC_PROTO_ARPV4)
  					{
  						// ARP receiving process
  						IPCProcessArp(ipc, b);
  					}
  					else if (protocol == MAC_PROTO_IPV4)
  					{
  						// IPv4 receiving process
  						if (b->Size >= (14 + 20))
  						{
  							UCHAR *data = Clone(b->Buf + 14, b->Size - 14);
  							UINT size = b->Size - 14;
  							IP ip_src, ip_dst;
  							bool ok = false;
  
  							// Extract the IP address portion
  							UINTToIP(&ip_src, *((UINT *)(((UCHAR *)data) + 12)));
  							UINTToIP(&ip_dst, *((UINT *)(((UCHAR *)data) + 16)));
  
  							// Receive only if the IPv4 destination address is its own
  							// or 255.255.255.255 or a multicast address or a broadcast address
  							if (CmpIpAddr(&ip_dst, &ipc->ClientIPAddress) == 0)
  							{
  								ok = true;
  							}
  							else if (ip_dst.addr[0] == 255 && ip_dst.addr[1] == 255 &&
  							         ip_dst.addr[2] == 255 && ip_dst.addr[3] == 255)
  							{
  								ok = true;
  							}
  							else if (ip_dst.addr[0] >= 224 && ip_dst.addr[0] <= 239)
  							{
  								ok = true;
  							}
  							else
  							{
  								if (CmpIpAddr(&ipc->BroadcastAddress, &ip_dst) == 0)
  								{
  									ok = true;
  								}
  
  								if (IsZeroIP(&ipc->ClientIPAddress))
  								{
  									// Client IP address is undetermined
  									ok = true;
  								}
  							}
  
  							if (ok)
  							{
  								// Parse DHCP packets
  								bool packetConsumed = false;
  								if (ipc->DHCPv4Awaiter.IsAwaiting)
  								{
  									PKT *pkt;
  									DHCPV4_DATA *dhcp;
  
  									Debug("Parsing for DHCP awaiter\n");
  									pkt = ParsePacketIPv4WithDummyMacHeader(data, size);
  									dhcp = ParseDHCPv4Data(pkt);
  
  									if (dhcp != NULL)
  									{
  										if (Endian32(dhcp->Header->TransactionId) == ipc->DHCPv4Awaiter.TransCode &&
  										        dhcp->OpCode == ipc->DHCPv4Awaiter.OpCode)
  										{
  											FreeDHCPv4Data(ipc->DHCPv4Awaiter.DhcpData);
  											ipc->DHCPv4Awaiter.DhcpData = dhcp;
  											packetConsumed = true;
  										}
  										else
  										{
  											FreeDHCPv4Data(dhcp);
  										}
  									}
  
  									FreePacketWithData(pkt);
  								}
  
  								IPCAssociateOnArpTable(ipc, &ip_src, src_mac);
  
  								if (ipc->IPv4State == IPC_PROTO_STATUS_OPENED && !packetConsumed)
  								{
  									// Place in the reception queue
  									InsertQueue(ipc->IPv4ReceivedQueue, NewBlock(data, size, 0));
  								}
  								else
  								{
  									Free(data); // We need to free the packet if we don't save it
  								}
  
  							}
  							else
  							{
  								// This packet is discarded because it is irrelevant for me
  								Free(data);
  							}
  						}
  					}
  					else if (protocol == MAC_PROTO_IPV6)
  					{
  						PKT *p = ParsePacketUpToICMPv6(b->Buf, b->Size);
  						if (p != NULL)
  						{
  							IP ip_src, ip_dst;
  							bool ndtProcessed = false;
  							UINT size = b->Size - 14;
  
  							UCHAR *data = Clone(b->Buf + 14, size);
  
  							IPv6AddrToIP(&ip_src, &p->IPv6HeaderPacketInfo.IPv6Header->SrcAddress);
  							IPv6AddrToIP(&ip_dst, &p->IPv6HeaderPacketInfo.IPv6Header->DestAddress);
  
  							if (p->IPv6HeaderPacketInfo.Protocol == IP_PROTO_ICMPV6)
  							{
  								IP icmpHeaderAddr;
  								// We need to parse the Router Advertisement and Neighbor Advertisement messages
  								// to build the Neighbor Discovery Table (aka ARP table for IPv6)
  								switch (p->ICMPv6HeaderPacketInfo.Type)
  								{
  								case ICMPV6_TYPE_ROUTER_ADVERTISEMENT:
  									// We save the router advertisement data for later use
  									IPCIPv6AddRouterPrefixes(ipc, &p->ICMPv6HeaderPacketInfo.OptionList, src_mac, &ip_src);
  									IPCIPv6AssociateOnNDTEx(ipc, &ip_src, src_mac, true);
  									IPCIPv6AssociateOnNDTEx(ipc, &ip_src, p->ICMPv6HeaderPacketInfo.OptionList.SourceLinkLayer->Address, true);
  									break;
  								case ICMPV6_TYPE_NEIGHBOR_ADVERTISEMENT:
  									// We save the neighbor advertisements into NDT
  									IPv6AddrToIP(&icmpHeaderAddr, &p->ICMPv6HeaderPacketInfo.Headers.NeighborAdvertisementHeader->TargetAddress);
  									IPCIPv6AssociateOnNDTEx(ipc, &icmpHeaderAddr, src_mac, true);
  									IPCIPv6AssociateOnNDTEx(ipc, &ip_src, src_mac, true);
  									ndtProcessed = true;
  									break;
  								}
  							}
  
  							// We update the NDT only if we have an entry in it for the IP+Mac
  							if (!ndtProcessed)
  							{
  								IPCIPv6AssociateOnNDT(ipc, &ip_src, src_mac);
  							}
  
  							/// TODO: should we or not filter Neighbor Advertisements and/or Neighbor Solicitations?
  							if (ipc->IPv6State == IPC_PROTO_STATUS_OPENED)
  							{
  								InsertQueue(ipc->IPv6ReceivedQueue, NewBlock(data, size, 0));
  							}
  							else
  							{
  								Free(data); // We need to free the packet if we don't save it
  							}
  
  							FreePacket(p);
  						}
  					}
  				}
  			}
  		}
  
  		FreeBlock(b);
  	}
  
  	IPCProcessInterrupts(ipc);
  }
  
  // Configure IPv4 parameters
  bool IPCSetIPv4Parameters(IPC *ipc, IP *ip, IP *subnet, IP *gw, DHCP_CLASSLESS_ROUTE_TABLE *rt)
  {
  	bool changed = false;
  	// Validate arguments
  	if (ipc == NULL || ip == NULL || subnet == NULL)
  	{
  		return false;
  	}
  
  	if (CmpIpAddr(&ipc->ClientIPAddress, ip) != 0)
  	{
  		changed = true;
  	}
  	Copy(&ipc->ClientIPAddress, ip, sizeof(IP));
  
  	if (CmpIpAddr(&ipc->SubnetMask, subnet) != 0)
  	{
  		changed = true;
  	}
  	Copy(&ipc->SubnetMask, subnet, sizeof(IP));
  
  	if (gw != NULL)
  	{
  		if (CmpIpAddr(&ipc->DefaultGateway, gw) != 0)
  		{
  			changed = true;
  		}
  
  		Copy(&ipc->DefaultGateway, gw, sizeof(IP));
  	}
  	else
  	{
  		if (IsZeroIP(&ipc->DefaultGateway) == false)
  		{
  			changed = true;
  		}
  
  		Zero(&ipc->DefaultGateway, sizeof(IP));
  	}
  
  	GetBroadcastAddress4(&ipc->BroadcastAddress, ip, subnet);
  
  	if (rt != NULL && rt->NumExistingRoutes >= 1)
  	{
  		if (Cmp(&ipc->ClasslessRoute, rt, sizeof(DHCP_CLASSLESS_ROUTE_TABLE)) != 0)
  		{
  			changed = true;
  
  			Copy(&ipc->ClasslessRoute, rt, sizeof(DHCP_CLASSLESS_ROUTE_TABLE));
  		}
  	}
  
  	return changed;
  }
  
  // Send an IPv4 packet (client -> server)
  void IPCSendIPv4(IPC *ipc, void *data, UINT size)
  {
  	IP ip_src, ip_dst;
  	IP ip_dst_local;
  	bool is_broadcast = false;
  	UCHAR uc;
  	DHCP_CLASSLESS_ROUTE *r = NULL;
  	// Validate arguments
  	if (ipc == NULL || data == NULL || size < 20 || size > 1500)
  	{
  		return;
  	}
  
  	uc = ((UCHAR *)data)[0];
  	if (((uc >> 4) & 0x0f) != 4)
  	{
  		// Not an IPv4
  		return;
  	}
  
  	// Extract the IP address portion
  	UINTToIP(&ip_src, *((UINT *)(((UCHAR *)data) + 12)));
  	UINTToIP(&ip_dst, *((UINT *)(((UCHAR *)data) + 16)));
  
  	// Filter the source IP address
  	if (CmpIpAddr(&ip_src, &ipc->ClientIPAddress) != 0)
  	{
  		// Cut off packets from illegal IP address
  		return;
  	}
  
  	if (IsZeroIP(&ip_dst))
  	{
  		// Illegal destination address
  		return;
  	}
  
  	if (CmpIpAddr(&ip_dst, &ipc->ClientIPAddress) == 0)
  	{
  		// Packet destined for myself
  		return;
  	}
  
  	// Get the IP address of the relayed destination
  	Copy(&ip_dst_local, &ip_dst, sizeof(IP));
  
  	if (IsInSameNetwork4(&ip_dst, &ipc->ClientIPAddress, &ipc->SubnetMask) == false)
  	{
  		r = GetBestClasslessRoute(&ipc->ClasslessRoute, &ip_dst);
  
  		if (r == NULL)
  		{
  			Copy(&ip_dst_local, &ipc->DefaultGateway, sizeof(IP));
  		}
  		else
  		{
  			Copy(&ip_dst_local, &r->Gateway, sizeof(IP));
  		}
  	}
  
  	if (CmpIpAddr(&ipc->BroadcastAddress, &ip_dst) == 0)
  	{
  		// Local Broadcast
  		is_broadcast = true;
  	}
  
  	if (ip_dst.addr[0] == 255 && ip_dst.addr[1] == 255 && ip_dst.addr[2] == 255 && ip_dst.addr[3] == 255)
  	{
  		// Global Broadcast
  		is_broadcast = true;
  	}
  
  	if (ip_dst.addr[0] >= 224 && ip_dst.addr[0] <= 239)
  	{
  		// IPv4 Multicast
  		is_broadcast = true;
  	}
  
  	if (is_broadcast)
  	{
  		// Send a broadcast packet
  		UCHAR dest[6];
  		UINT i;
  
  		// Destination
  		for (i = 0; i < 6; i++)
  		{
  			dest[i] = 0xff;
  		}
  
  		// Send
  		IPCSendIPv4WithDestMacAddr(ipc, data, size, dest);
  
  		return;
  	}
  
  	if (IsZeroIP(&ip_dst_local))
  	{
  		// Unable to send
  		return;
  	}
  
  	// Send a unicast packet
  	IPCSendIPv4Unicast(ipc, data, size, &ip_dst_local);
  }
  
  // Send an IPv4 packet with a specified destination MAC address
  void IPCSendIPv4WithDestMacAddr(IPC *ipc, void *data, UINT size, UCHAR *dest_mac_addr)
  {
  	UCHAR tmp[1514];
  	// Validate arguments
  	if (ipc == NULL || data == NULL || size < 20 || size > 1500 || dest_mac_addr == NULL)
  	{
  		return;
  	}
  
  	// Destination
  	Copy(tmp + 0, dest_mac_addr, 6);
  
  	// Source
  	Copy(tmp + 6, ipc->MacAddress, 6);
  
  	// Protocol number
  	WRITE_USHORT(tmp + 12, MAC_PROTO_IPV4);
  
  	// Data
  	Copy(tmp + 14, data, size);
  
  	// Send
  	IPCSendL2(ipc, tmp, size + 14);
  }
  
  // Remove old ARP table entries
  void IPCFlushArpTable(IPC *ipc)
  {
  	IPCFlushArpTableEx(ipc, 0);
  }
  void IPCFlushArpTableEx(IPC *ipc, UINT64 now)
  {
  	UINT i;
  	LIST *o = NULL;
  	// Validate arguments
  	if (ipc == NULL)
  	{
  		return;
  	}
  	if (now == 0)
  	{
  		now = Tick64();
  	}
  
  	for (i = 0; i < LIST_NUM(ipc->ArpTable); i++)
  	{
  		IPC_ARP *a = LIST_DATA(ipc->ArpTable, i);
  		bool b = false;
  
  		if (a->Resolved && a->ExpireTime <= now)
  		{
  			b = true;
  		}
  		else if (a->Resolved == false && a->GiveupTime <= now)
  		{
  			b = true;
  		}
  
  		if (b)
  		{
  			if (o == NULL)
  			{
  				o = NewListFast(NULL);
  			}
  
  			Add(o, a);
  		}
  	}
  
  	if (o != NULL)
  	{
  		for (i = 0; i < LIST_NUM(o); i++)
  		{
  			IPC_ARP *a = LIST_DATA(o, i);
  
  			IPCFreeARP(a);
  
  			Delete(ipc->ArpTable, a);
  		}
  
  		ReleaseList(o);
  	}
  }
  
  // Send an IPv4 unicast packet
  void IPCSendIPv4Unicast(IPC *ipc, void *data, UINT size, IP *next_ip)
  {
  	IPC_ARP *a;
  	// Validate arguments
  	if (ipc == NULL || data == NULL || size < 20 || size > 1500 || next_ip == NULL)
  	{
  		return;
  	}
  
  	a = IPCSearchArpTable(ipc->ArpTable, next_ip);
  
  	if (a != NULL)
  	{
  		// ARP entry is found
  		if (a->Resolved)
  		{
  			// Send
  			a->ExpireTime = Tick64() + (UINT64)IPC_ARP_LIFETIME;
  
  			IPCSendIPv4WithDestMacAddr(ipc, data, size, a->MacAddress);
  		}
  		else
  		{
  			// Undeliverable because of unresolved table. Accumulate in the queue
  			if (a->PacketQueue->num_item < IPC_MAX_PACKET_QUEUE_LEN)
  			{
  				InsertQueue(a->PacketQueue, NewBlock(Clone(data, size), size, false));
  			}
  		}
  	}
  	else
  	{
  		ARPV4_HEADER arp;
  		UCHAR tmp[14 + sizeof(ARPV4_HEADER)];
  		UINT i;
  
  		// Because there is no such ARP entry, create a new one
  		a = IPCNewARP(next_ip, NULL);
  
  		// Send an ARP request
  		Zero(&arp, sizeof(arp));
  		arp.HardwareType = Endian16(ARP_HARDWARE_TYPE_ETHERNET);
  		arp.ProtocolType = Endian16(MAC_PROTO_IPV4);
  		arp.HardwareSize = 6;
  		arp.ProtocolSize = 4;
  		arp.Operation = Endian16(ARP_OPERATION_REQUEST);
  		Copy(&arp.SrcAddress, &ipc->MacAddress, 6);
  		arp.SrcIP = IPToUINT(&ipc->ClientIPAddress);
  		arp.TargetIP = IPToUINT(next_ip);
  
  		for (i = 0; i < 6; i++)
  		{
  			tmp[i] = 0xff;
  		}
  
  		Copy(tmp + 6, ipc->MacAddress, 6);
  
  		WRITE_USHORT(tmp + 12, MAC_PROTO_ARPV4);
  		Copy(tmp + 14, &arp, sizeof(ARPV4_HEADER));
  
  		IPCSendL2(ipc, tmp, 14 + sizeof(ARPV4_HEADER));
  
  		// Accumulate the IP packet to be transmitted in the queue
  		if (a->PacketQueue->num_item < IPC_MAX_PACKET_QUEUE_LEN)
  		{
  			InsertQueue(a->PacketQueue, NewBlock(Clone(data, size), size, false));
  		}
  
  		Insert(ipc->ArpTable, a);
  	}
  }
  
  // Search the ARP table
  IPC_ARP *IPCSearchArpTable(LIST *arpTable, IP *ip)
  {
  	IPC_ARP t;
  	IPC_ARP *a;
  	// Validate arguments
  	if (arpTable == NULL || ip == NULL)
  	{
  		return NULL;
  	}
  
  	Copy(&t.Ip, ip, sizeof(IP));
  
  	a = Search(arpTable, &t);
  
  	return a;
  }
  
  // Release the ARP entry
  void IPCFreeARP(IPC_ARP *a)
  {
  	BLOCK *b;
  	// Validate arguments
  	if (a == NULL)
  	{
  		return;
  	}
  
  	while (true)
  	{
  		b = GetNext(a->PacketQueue);
  		if (b == NULL)
  		{
  			break;
  		}
  
  		FreeBlock(b);
  	}
  
  	ReleaseQueue(a->PacketQueue);
  
  	Free(a);
  }
  
  // Create a new ARP entry
  IPC_ARP *IPCNewARP(IP *ip, UCHAR *mac_address)
  {
  	IPC_ARP *a;
  	// Validate arguments
  	if (ip == NULL)
  	{
  		return NULL;
  	}
  
  	a = ZeroMalloc(sizeof(IPC_ARP));
  
  	Copy(&a->Ip, ip, sizeof(IP));
  	if (mac_address != NULL)
  	{
  		Copy(a->MacAddress, mac_address, 6);
  		a->Resolved = true;
  		a->ExpireTime = Tick64() + (UINT64)IPC_ARP_LIFETIME;
  	}
  	else
  	{
  		a->GiveupTime = Tick64() + (UINT64)IPC_ARP_GIVEUPTIME;
  	}
  
  	a->PacketQueue = NewQueueFast();
  
  	return a;
  }
  
  // Compare ARP entries
  int IPCCmpArpTable(void *p1, void *p2)
  {
  	IPC_ARP *a1, *a2;
  	// Validate arguments
  	if (p1 == NULL || p2 == NULL)
  	{
  		return 0;
  	}
  	a1 = *(IPC_ARP **)p1;
  	a2 = *(IPC_ARP **)p2;
  	if (a1 == NULL || a2 == NULL)
  	{
  		return 0;
  	}
  
  	return CmpIpAddr(&a1->Ip, &a2->Ip);
  }
  
  // Send an Ethernet packet (client -> server)
  void IPCSendL2(IPC *ipc, void *data, UINT size)
  {
  	// Validate arguments
  	if (ipc == NULL || data == NULL || size == 0)
  	{
  		return;
  	}
  
  	if (ipc->Sock == NULL)
  	{
  		return;
  	}
  
  	TubeSendEx(ipc->Sock->SendTube, data, size, NULL, true);
  	AddTubeToFlushList(ipc->FlushList, ipc->Sock->SendTube);
  }
  
  // Receive an IPv4 packet (server -> client)
  BLOCK *IPCRecvIPv4(IPC *ipc)
  {
  	BLOCK *b;
  	// Validate arguments
  	if (ipc == NULL)
  	{
  		return NULL;
  	}
  
  	b = GetNext(ipc->IPv4ReceivedQueue);
  
  	return b;
  }
  
  // Receive an Ethernet packet (server -> client)
  BLOCK *IPCRecvL2(IPC *ipc)
  {
  	TUBEDATA *d;
  	BLOCK *b;
  	// Validate arguments
  	if (ipc == NULL)
  	{
  		return NULL;
  	}
  
  	if (ipc->Sock == NULL)
  	{
  		return NULL;
  	}
  
  	d = TubeRecvAsync(ipc->Sock->RecvTube);
  
  	if (d == NULL)
  	{
  		return NULL;
  	}
  
  	b = NewBlock(d->Data, d->DataSize, 0);
  
  	Free(d->Header);
  	Free(d);
  
  	return b;
  }
  
  // IPv6 stuff
  // Memory management
  void IPCIPv6Init(IPC *ipc)
  {
  	ipc->IPv6ReceivedQueue = NewQueue();
  	// The NDT is basically the same as ARP Table with some slight adjustments
  	ipc->IPv6NeighborTable = NewList(IPCCmpArpTable);
  	ipc->IPv6RouterAdvs = NewList(NULL);
  
  	ipc->IPv6ClientEUI = 0;
  	ipc->IPv6ServerEUI = 0;
  
  	ipc->IPv6State = IPC_PROTO_STATUS_CLOSED;
  }
  void IPCIPv6Free(IPC *ipc)
  {
  	UINT i;
  	for (i = 0; i < LIST_NUM(ipc->IPv6NeighborTable); i++)
  	{
  		IPC_ARP *a = LIST_DATA(ipc->IPv6NeighborTable, i);
  		IPCFreeARP(a);
  	}
  
  	ReleaseList(ipc->IPv6NeighborTable);
  
  	for (i = 0; i < LIST_NUM(ipc->IPv6RouterAdvs); i++)
  	{
  		IPC_IPV6_ROUTER_ADVERTISEMENT *ra = LIST_DATA(ipc->IPv6RouterAdvs, i);
  		Free(ra);
  	}
  
  	ReleaseList(ipc->IPv6RouterAdvs);
  
  	while (true)
  	{
  		BLOCK *b = GetNext(ipc->IPv6ReceivedQueue);
  		if (b == NULL)
  		{
  			break;
  		}
  
  		FreeBlock(b);
  	}
  
  	ReleaseQueue(ipc->IPv6ReceivedQueue);
  }
  
  // NDT
  void IPCIPv6AssociateOnNDT(IPC *ipc, IP *ip, UCHAR *mac_address)
  {
  	IPCIPv6AssociateOnNDTEx(ipc, ip, mac_address, false);
  }
  void IPCIPv6AssociateOnNDTEx(IPC *ipc, IP *ip, UCHAR *mac_address, bool isNeighborAdv)
  {
  	IPC_ARP *a;
  	UINT addrType = 0;
  	if (ipc == NULL || ip == NULL ||
  	        IsValidUnicastIPAddress6(ip) == false ||
  	        IsMacUnicast(mac_address) == false)
  	{
  		return;
  	}
  
  	addrType = GetIPAddrType6(ip);
  
  	if (addrType != IPV6_ADDR_LOCAL_UNICAST &&
  	        addrType != IPV6_ADDR_GLOBAL_UNICAST)
  	{
  		return;
  	}
  
  	if (addrType == IPV6_ADDR_GLOBAL_UNICAST)
  	{
  		if (!IPCIPv6CheckUnicastFromRouterPrefix(ipc, ip, NULL))
  		{
  			return;
  		}
  	}
  
  	a = IPCSearchArpTable(ipc->IPv6NeighborTable, ip);
  
  	// We create a new entry only if we got a neighbor advertisement
  	if (a == NULL && isNeighborAdv)
  	{
  		a = IPCNewARP(ip, mac_address);
  		Insert(ipc->IPv6NeighborTable, a);
  	}
  	else if (a == NULL)
  	{
  		// We skip the NDT association on random packets from unknown locations
  		return;
  	}
  	else
  	{
  		Copy(a->MacAddress, mac_address, 6);
  
  		if (a->Resolved == false)
  		{
  			a->Resolved = true;
  			a->GiveupTime = 0;
  			while (true)
  			{
  				BLOCK *b = GetNext(a->PacketQueue);
  
  				if (b == NULL)
  				{
  					break;
  				}
  
  				IPCIPv6SendWithDestMacAddr(ipc, b->Buf, b->Size, a->MacAddress);
  
  				FreeBlock(b);
  			}
  		}
  
  		a->ExpireTime = Tick64() + (UINT64)IPC_IPV6_NDT_LIFETIME;
  	}
  }
  
  void IPCIPv6FlushNDT(IPC *ipc)
  {
  	IPCIPv6FlushNDTEx(ipc, 0);
  }
  void IPCIPv6FlushNDTEx(IPC *ipc, UINT64 now)
  {
  	UINT i;
  	LIST *o = NULL;
  	// Validate arguments
  	if (ipc == NULL)
  	{
  		return;
  	}
  	if (now == 0)
  	{
  		now = Tick64();
  	}
  
  	for (i = 0; i < LIST_NUM(ipc->IPv6NeighborTable); i++)
  	{
  		IPC_ARP *a = LIST_DATA(ipc->IPv6NeighborTable, i);
  		bool b = false;
  
  		if (a->Resolved && a->ExpireTime <= now)
  		{
  			b = true;
  		}
  		else if (a->Resolved == false && a->GiveupTime <= now)
  		{
  			b = true;
  		}
  		/// TODO: think about adding retransmission as per RFC4861
  
  		if (b)
  		{
  			if (o == NULL)
  			{
  				o = NewListFast(NULL);
  			}
  
  			Add(o, a);
  		}
  	}
  
  	if (o != NULL)
  	{
  		for (i = 0; i < LIST_NUM(o); i++)
  		{
  			IPC_ARP *a = LIST_DATA(o, i);
  
  			IPCFreeARP(a);
  
  			Delete(ipc->IPv6NeighborTable, a);
  		}
  
  		ReleaseList(o);
  	}
  }
  
  // Scan the hub IP Table to try to find the EUI-based link-local address
  bool IPCIPv6CheckExistingLinkLocal(IPC *ipc, UINT64 eui)
  {
  	HUB t, *h;
  	IP_TABLE_ENTRY i, *e;
  	t.Name = ipc->HubName;
  
  	// Construct link local from eui
  	ZeroIP6(&i.Ip);
  	i.Ip.ipv6_addr[0] = 0xFE;
  	i.Ip.ipv6_addr[1] = 0x80;
  	Copy(&i.Ip.ipv6_addr[8], &eui, sizeof(UINT64));
  
  	h = Search(ipc->Cedar->HubList, &t);
  
  	if (h != NULL)
  	{
  		e = Search(h->IpTable, &i);
  		if (e != NULL)
  		{
  			return true;
  		}
  	}
  
  	return false;
  }
  
  // RA
  void IPCIPv6AddRouterPrefixes(IPC *ipc, ICMPV6_OPTION_LIST *recvPrefix, UCHAR *macAddress, IP *ip)
  {
  	UINT i, j;
  	for (i = 0; i < ICMPV6_OPTION_PREFIXES_MAX_COUNT; i++)
  	{
  		if (recvPrefix->Prefix[i] != NULL)
  		{
  			bool foundPrefix = false;
  			for (j = 0; j < LIST_NUM(ipc->IPv6RouterAdvs); j++)
  			{
  				IPC_IPV6_ROUTER_ADVERTISEMENT *existingRA = LIST_DATA(ipc->IPv6RouterAdvs, j);
  				if (Cmp(&recvPrefix->Prefix[i]->Prefix, &existingRA->RoutedPrefix.ipv6_addr, sizeof(IPV6_ADDR)) == 0)
  				{
  					foundPrefix = true;
  					break;
  				}
  			}
  
  			if (!foundPrefix)
  			{
  				IPC_IPV6_ROUTER_ADVERTISEMENT *newRA = Malloc(sizeof(IPC_IPV6_ROUTER_ADVERTISEMENT));
  				IPv6AddrToIP(&newRA->RoutedPrefix, &recvPrefix->Prefix[i]->Prefix);
  				IntToSubnetMask6(&newRA->RoutedMask, recvPrefix->Prefix[i]->SubnetLength);
  				CopyIP(&newRA->RouterAddress, ip);
  				Copy(newRA->RouterMacAddress, macAddress, 6);
  				Copy(newRA->RouterLinkLayerAddress, recvPrefix->SourceLinkLayer->Address, 6);
  				Add(ipc->IPv6RouterAdvs, newRA);
  			}
  		}
  		else
  		{
  			break;
  		}
  	}
  }
  
  bool IPCIPv6CheckUnicastFromRouterPrefix(IPC *ipc, IP *ip, IPC_IPV6_ROUTER_ADVERTISEMENT *matchedRA)
  {
  	UINT i;
  	IPC_IPV6_ROUTER_ADVERTISEMENT *matchingRA = NULL;
  	bool isInPrefix = false;
  	for (i = 0; i < LIST_NUM(ipc->IPv6RouterAdvs); i++)
  	{
  		IPC_IPV6_ROUTER_ADVERTISEMENT *ra = LIST_DATA(ipc->IPv6RouterAdvs, i);
  		isInPrefix = IsInSameNetwork6(ip, &ra->RoutedPrefix, &ra->RoutedMask);
  		if (isInPrefix)
  		{
  			matchingRA = ra;
  			break;
  		}
  	}
  
  	if (matchedRA != NULL && matchingRA != NULL)
  	{
  		Copy(matchedRA, matchingRA, sizeof(IPC_IPV6_ROUTER_ADVERTISEMENT));
  	}
  
  	return isInPrefix;
  }
  
  // Send router solicitation and then eventually populate the info from Router Advertisements
  UINT64 IPCIPv6GetServerEui(IPC *ipc)
  {
  	// It is already configured, nothing to do here
  	if (ipc->IPv6ServerEUI != 0)
  	{
  		return ipc->IPv6ServerEUI;
  	}
  
  	// If we don't have a valid client EUI, we can't generate a correct link local
  	if (ipc->IPv6ClientEUI == 0)
  	{
  		return ipc->IPv6ServerEUI;
  	}
  
  	if (LIST_NUM(ipc->IPv6RouterAdvs) == 0)
  	{
  		IP destIP;
  		IPV6_ADDR destV6;
  		UCHAR destMacAddress[6];
  		IPV6_ADDR linkLocal;
  		BUF *packet;
  		UINT64 giveup_time = Tick64() + (UINT64)(IPC_IPV6_RA_MAX_RETRIES * IPC_IPV6_RA_INTERVAL);
  		UINT64 timeout_retry = 0;
  
  		Zero(&linkLocal, sizeof(IPV6_ADDR));
  
  		// Generate link local from client's EUI
  		linkLocal.Value[0] = 0xFE;
  		linkLocal.Value[1] = 0x80;
  		Copy(&linkLocal.Value[8], &ipc->IPv6ClientEUI, sizeof(UINT64));
  
  		GetAllRouterMulticastAddress6(&destIP);
  
  		// Generate the MAC address from the multicast address
  		destMacAddress[0] = 0x33;
  		destMacAddress[1] = 0x33;
  		Copy(&destMacAddress[2], &destIP.ipv6_addr[12], sizeof(UINT));
  
  		IPToIPv6Addr(&destV6, &destIP);
  
  		packet = BuildICMPv6RouterSoliciation(&linkLocal, &destV6, ipc->MacAddress, 0);
  
  		while (LIST_NUM(ipc->IPv6RouterAdvs) == 0)
  		{
  			UINT64 now = Tick64();
  			if (now >= timeout_retry)
  			{
  				timeout_retry = now + (UINT64)IPC_IPV6_RA_INTERVAL;
  				IPCIPv6SendWithDestMacAddr(ipc, packet->Buf, packet->Size, destMacAddress);
  			}
  
  			AddInterrupt(ipc->Interrupt, timeout_retry);
  
  			if (Tick64() >= giveup_time)
  			{
  				// We failed to receive any router advertisements
  				break;
  			}
  
  			// The processing should populate the received RAs by itself
  			IPCProcessL3Events(ipc);
  		}
  
  		FreeBuf(packet);
  	}
  
  	// Populating the IPv6 Server EUI for IPV6CP
  	if (LIST_NUM(ipc->IPv6RouterAdvs) > 0)
  	{
  		IPC_IPV6_ROUTER_ADVERTISEMENT *ra = LIST_DATA(ipc->IPv6RouterAdvs, 0);
  		Copy(&ipc->IPv6ServerEUI, &ra->RouterAddress.ipv6_addr[8], sizeof(UINT64));
  	}
  
  	// If it is still not defined, let's just generate something random
  	while (ipc->IPv6ServerEUI == 0)
  	{
  		ipc->IPv6ServerEUI = Rand64();
  		if (ipc->IPv6ClientEUI == ipc->IPv6ServerEUI)
  		{
  			ipc->IPv6ServerEUI = 0;
  		}
  	}
  
  	return ipc->IPv6ServerEUI;
  }
  
  // Data flow
  BLOCK *IPCIPv6Recv(IPC *ipc)
  {
  	BLOCK *b;
  	// Validate arguments
  	if (ipc == NULL)
  	{
  		return NULL;
  	}
  
  	b = GetNext(ipc->IPv6ReceivedQueue);
  
  	return b;
  }
  
  void IPCIPv6Send(IPC *ipc, void *data, UINT size)
  {
  	IP destAddr;
  	UINT ipv6Type;
  	UCHAR destMac[6];
  	IPV6_HEADER *header = data;
  
  	IPv6AddrToIP(&destAddr, &header->DestAddress);
  
  	if (IsValidUnicastIPAddress6(&destAddr))
  	{
  		IPCIPv6SendUnicast(ipc, data, size, &destAddr);
  		return;
  	}
  
  	// Here we're probably dealing with a multicast packet. But let's check it anyway
  	ipv6Type = GetIPAddrType6(&destAddr);
  	if (ipv6Type & IPV6_ADDR_MULTICAST)
  	{
  		// Constructing multicast MAC address based on destination IP address, then just fire and forget
  		destMac[0] = 0x33;
  		destMac[1] = 0x33;
  		destMac[2] = destAddr.ipv6_addr[12];
  		destMac[3] = destAddr.ipv6_addr[13];
  		destMac[4] = destAddr.ipv6_addr[14];
  		destMac[5] = destAddr.ipv6_addr[15];
  		IPCIPv6SendWithDestMacAddr(ipc, data, size, destMac);
  		return;
  	}
  	else
  	{
  		Debug("We got a weird packet with a weird type! %i\n", ipv6Type);
  	}
  }
  
  void IPCIPv6SendWithDestMacAddr(IPC *ipc, void *data, UINT size, UCHAR *dest_mac_addr)
  {
  	UCHAR tmp[1514];
  
  	IPV6_HEADER *header = data;
  
  
  	// Validate arguments
  	if (ipc == NULL || data == NULL || size < 40 || size > 1500 || dest_mac_addr == NULL)
  	{
  		return;
  	}
  
  	// Destination
  	Copy(tmp + 0, dest_mac_addr, 6);
  
  	// Source
  	Copy(tmp + 6, ipc->MacAddress, 6);
  
  	// Protocol number
  	WRITE_USHORT(tmp + 12, MAC_PROTO_IPV6);
  
  	// Data
  	Copy(tmp + 14, data, size);
  
  	// Parse the packet for ND ICMPv6 fixup
  	if (header->NextHeader == IP_PROTO_ICMPV6)
  	{
  		PKT *p = ParsePacketUpToICMPv6(tmp, size + 14);
  		if (p != NULL)
  		{
  			ICMPV6_OPTION_LINK_LAYER linkLayer;
  			BUF *buf;
  			BUF *optBuf;
  			BUF *packet;
  			// We need to rebuild the packet to
  			switch (p->ICMPv6HeaderPacketInfo.Type)
  			{
  			case ICMPV6_TYPE_NEIGHBOR_SOLICIATION:
  				if (p->ICMPv6HeaderPacketInfo.OptionList.SourceLinkLayer == NULL)
  				{
  					p->ICMPv6HeaderPacketInfo.OptionList.SourceLinkLayer = &linkLayer;
  				}
  				Copy(p->ICMPv6HeaderPacketInfo.OptionList.SourceLinkLayer->Address, ipc->MacAddress, 6);
  				break;
  			case ICMPV6_TYPE_NEIGHBOR_ADVERTISEMENT:
  				if (p->ICMPv6HeaderPacketInfo.OptionList.TargetLinkLayer == NULL)
  				{
  					p->ICMPv6HeaderPacketInfo.OptionList.TargetLinkLayer = &linkLayer;
  				}
  				Copy(p->ICMPv6HeaderPacketInfo.OptionList.TargetLinkLayer->Address, ipc->MacAddress, 6);
  				break;
  			}
  			switch (p->ICMPv6HeaderPacketInfo.Type)
  			{
  			case ICMPV6_TYPE_NEIGHBOR_SOLICIATION:
  			case ICMPV6_TYPE_NEIGHBOR_ADVERTISEMENT:
  				optBuf = BuildICMPv6Options(&p->ICMPv6HeaderPacketInfo.OptionList);
  				buf = NewBuf();
  				WriteBuf(buf, p->ICMPv6HeaderPacketInfo.Headers.HeaderPointer,
  				         p->ICMPv6HeaderPacketInfo.Type == ICMPV6_TYPE_NEIGHBOR_SOLICIATION ? sizeof(ICMPV6_NEIGHBOR_SOLICIATION_HEADER) : sizeof(ICMPV6_NEIGHBOR_ADVERTISEMENT_HEADER));
  				WriteBufBuf(buf, optBuf);
  				packet = BuildICMPv6(&p->IPv6HeaderPacketInfo.IPv6Header->SrcAddress,
  				                     &p->IPv6HeaderPacketInfo.IPv6Header->DestAddress,
  				                     p->IPv6HeaderPacketInfo.IPv6Header->HopLimit,
  				                     p->ICMPv6HeaderPacketInfo.Type,
  				                     p->ICMPv6HeaderPacketInfo.Code,
  				                     buf->Buf,
  				                     buf->Size,
  				                     0);
  				Copy(tmp + 14, packet->Buf, packet->Size);
  				size = packet->Size;
  				FreeBuf(optBuf);
  				FreeBuf(buf);
  				FreeBuf(packet);
  				break;
  			}
  		}
  		FreePacket(p);
  	}
  
  	// Send
  	IPCSendL2(ipc, tmp, size + 14);
  }
  
  void IPCIPv6SendUnicast(IPC *ipc, void *data, UINT size, IP *next_ip)
  {
  	IPC_ARP *ndtMatch;
  	UCHAR *destMac = NULL;
  	IPC_IPV6_ROUTER_ADVERTISEMENT ra;
  	IPV6_HEADER *header = data;
  	IP srcIp;
  	bool isLocal = false;
  	// First we need to understand if it is a local packet or we should route it through the router
  	UINT addrType = GetIPAddrType6(next_ip);
  
  	Zero(&ra, sizeof(IPC_IPV6_ROUTER_ADVERTISEMENT));
  	IPv6AddrToIP(&srcIp, &header->SrcAddress);
  
  	// Link local is always local =)
  	if (addrType & IPV6_ADDR_LOCAL_UNICAST)
  	{
  		isLocal = true;
  	}
  
  	// If it matches any received prefix from router advertisements, it's also local
  	if (!isLocal && IPCIPv6CheckUnicastFromRouterPrefix(ipc, next_ip, &ra))
  	{
  		isLocal = true;
  	}
  
  	// If it is a global packet, we need to get our source IP prefix to know through which router shall we route
  	if (!isLocal)
  	{
  		if (!IPCIPv6CheckUnicastFromRouterPrefix(ipc, &srcIp, &ra))
  		{
  			// If we didn't find a router for the source IP, let's just try to pick the first router and try to send to it
  			if (LIST_NUM(ipc->IPv6RouterAdvs) > 0)
  			{
  				Copy(&ra, LIST_DATA(ipc->IPv6RouterAdvs, 0), sizeof(IPC_IPV6_ROUTER_ADVERTISEMENT));
  			}
  			else
  			{
  				char tmp[MAX_SIZE];
  				IPToStr6(tmp, MAX_SIZE, &srcIp);
  				Debug("We couldn't find a router for the source address of %s! Trying as local.\n", tmp);
  				isLocal = true;
  			}
  		}
  
  		destMac = ra.RouterMacAddress;
  		if (!IsMacUnicast(destMac) && !IsMacInvalid(ra.RouterMacAddress))
  		{
  			destMac = ra.RouterLinkLayerAddress;
  		}
  	}
  
  	// If it is local it should be routed directly through the NDT
  	if (isLocal)
  	{
  		ndtMatch = IPCSearchArpTable(ipc->IPv6NeighborTable, next_ip);
  		if (ndtMatch == NULL)
  		{
  			// Creating a non-matched NDT entry
  			ndtMatch = IPCNewARP(next_ip, NULL);
  			Add(ipc->IPv6NeighborTable, ndtMatch);
  		}
  
  		if (ndtMatch->Resolved != true && LIST_NUM(ipc->IPv6RouterAdvs) > 0)
  		{
  			// First try to look up in router advertisements
  			UINT i;
  			for (i = 0; i < LIST_NUM(ipc->IPv6RouterAdvs); i++)
  			{
  				IPC_IPV6_ROUTER_ADVERTISEMENT *ra = LIST_DATA(ipc->IPv6RouterAdvs, i);
  				if (CmpIpAddr(next_ip, &ra->RouterAddress) == 0)
  				{
  					Copy(ndtMatch->MacAddress, IsMacUnicast(ra->RouterLinkLayerAddress) ? ra->RouterLinkLayerAddress : ra->RouterMacAddress, 6);
  					ndtMatch->Resolved = true;
  					break;
  				}
  			}
  		}
  
  		if (ndtMatch->Resolved != true)
  		{
  			// We need to send the Neighbor Solicitation and save the packet for sending later
  			// Generate the MAC address from the multicast address
  			BUF *neighborSolicit;
  			UCHAR destMacAddress[6];
  			IPV6_ADDR solicitAddress;
  
  			char tmp[MAX_SIZE];
  			UCHAR *copy;
  			BLOCK *blk;
  
  			Zero(&solicitAddress, sizeof(IPV6_ADDR));
  			Copy(&solicitAddress.Value[13], &header->DestAddress.Value[13], 3);
  			solicitAddress.Value[0] = 0xFF;
  			solicitAddress.Value[1] = 0x02;
  			solicitAddress.Value[11] = 0x01;
  			solicitAddress.Value[12] = 0xFF;
  
  			neighborSolicit = BuildICMPv6NeighborSoliciation(&header->SrcAddress, &solicitAddress, ipc->MacAddress, 0);
  			destMacAddress[0] = 0x33;
  			destMacAddress[1] = 0x33;
  			Copy(&destMacAddress[2], &solicitAddress.Value[12], sizeof(UINT));
  			IPCIPv6SendWithDestMacAddr(ipc, neighborSolicit->Buf, neighborSolicit->Size, destMacAddress);
  
  			FreeBuf(neighborSolicit);
  
  
  			copy = Clone(data, size);
  			blk = NewBlock(copy, size, 0);
  			InsertQueue(ndtMatch->PacketQueue, blk);
  			IPToStr6(tmp, MAX_SIZE, next_ip);
  
  			return;
  		}
  		destMac = ndtMatch->MacAddress;
  	}
  
  	if (destMac != NULL && !IsMacInvalid(destMac))
  	{
  		IPCIPv6SendWithDestMacAddr(ipc, data, size, destMac);
  	}
  	else
  	{
  		char tmp[MAX_SIZE];
  		IPToStr6(tmp, MAX_SIZE, next_ip);
  		Debug("We couldn't deduce the MAC address for unicast address %s, packet dropped.\n", tmp);
  		/// TODO: think about sending to the all routers broadcast MAC as a last resort
  	}
  }
  