⛏️ index : haiku.git

/*
 * Copyright 2003-2006, Waldemar Kornewald <wkornew@gmx.net>
 * Distributed under the terms of the MIT License.
 */

#if DEBUG
#include <cstdio>
#endif

#include "Protocol.h"
#include "IPCP.h"
#include <KPPPConfigurePacket.h>
#include <KPPPInterface.h>
#include <settings_tools.h>

#include <PPPoEDevice.h>

#include <cstring>

#include <arpa/inet.h>
#include <net_buffer.h>
#include <net_stack.h>
#include <net/route.h>
#include <sys/sockio.h>

// For updating resolv.conf
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define RESOLV_CONF_FILE "/boot/system/settings/network/resolv.conf"

extern net_buffer_module_info *gBufferModule;
static struct net_datalink_module_info *sDatalinkModule;
static struct net_stack_module_info *sStackModule;

#if DEBUG
#include <unistd.h>

static int sFD;
	// the file descriptor for debug output
static char sDigits[] = "0123456789ABCDEF";
void
dump_packet(net_buffer *packet, const char *direction)
{
	if (!packet)
		return;

	uint8 *data = mtod(packet, uint8*);
	uint8 buffer[128];
	uint8 bufferIndex = 0;

	sprintf((char*) buffer, "Dumping %s packet;len=%ld;pkthdr.len=%d\n", direction,
		packet->m_len, packet->m_flags & M_PKTHDR ? packet->m_pkthdr.len : -1);
	write(sFD, buffer, strlen((char*) buffer));

	for (uint32 index = 0; index < packet->m_len; index++) {
		buffer[bufferIndex++] = sDigits[data[index] >> 4];
		buffer[bufferIndex++] = sDigits[data[index] & 0x0F];
		if (bufferIndex == 32 || index == packet->m_len - 1) {
			buffer[bufferIndex++] = '\n';
			buffer[bufferIndex] = 0;
			write(sFD, buffer, strlen((char*) buffer));
			bufferIndex = 0;
		}
	}
}
#endif


static const bigtime_t kIPCPStateMachineTimeout = 3000000;
	// 3 seconds


IPCP::IPCP(KPPPInterface& interface, driver_parameter *settings)
	: KPPPProtocol("IPCP", PPP_NCP_PHASE, IPCP_PROTOCOL, PPP_PROTOCOL_LEVEL,
		AF_INET, 0, interface, settings, PPP_INCLUDES_NCP),
	fDefaultRoute(NULL),
	fRequestPrimaryDNS(false),
	fRequestSecondaryDNS(false),
	fState(PPP_INITIAL_STATE),
	fID(system_time() & 0xFF),
	fMaxRequest(10),
	fMaxTerminate(2),
	fMaxNak(5),
	fRequestID(0),
	fTerminateID(0),
	fNextTimeout(0)
{
	// reset configurations
	memset(&fLocalConfiguration, 0, sizeof(ipcp_configuration));
	memset(&fPeerConfiguration, 0, sizeof(ipcp_configuration));

	// reset requests
	memset(&fLocalRequests, 0, sizeof(ipcp_requests));
	memset(&fPeerRequests, 0, sizeof(ipcp_requests));

	// Parse settings:
	// "Local" and "Peer" describe each side's settings
	ParseSideRequests(get_parameter_with_name(IPCP_LOCAL_SIDE_KEY, Settings()),
		PPP_LOCAL_SIDE);
	ParseSideRequests(get_parameter_with_name(IPCP_PEER_SIDE_KEY, Settings()),
		PPP_PEER_SIDE);

	get_module(NET_STACK_MODULE_NAME, (module_info **)&sStackModule);
	get_module(NET_DATALINK_MODULE_NAME, (module_info **)&sDatalinkModule);
#if DEBUG
	sFD = open("/boot/home/ipcpdebug", O_WRONLY | O_CREAT | O_TRUNC);
#endif
}


IPCP::~IPCP()
{

	put_module(NET_DATALINK_MODULE_NAME);
	put_module(NET_STACK_MODULE_NAME);
#if DEBUG
	close(sFD);
#endif
}


void
IPCP::Uninit()
{
	RemoveRoutes();
}


status_t
IPCP::StackControl(uint32 op, void *data)
{
	TRACE("IPCP: StackControl(op=%ld)\n", op);

	// TODO:
	// check values

	switch (op) {
		case SIOCSIFADDR:
		break;

		case SIOCSIFFLAGS:
		break;

		case SIOCSIFDSTADDR:
		break;

		case SIOCSIFNETMASK:
		break;

		default:
			ERROR("IPCP: Unknown ioctl: %" B_PRIu32 "\n", op);
			return KPPPProtocol::StackControl(op, data);
	}

	return B_OK;
}


bool
IPCP::Up()
{
	TRACE("IPCP: Up() state=%d\n", State());

	// Servers do not send a configure-request when Up() is called. They wait until
	// the client requests this protocol.
	if (Interface().Mode() == PPP_SERVER_MODE)
		return true;

	switch (State()) {
		case PPP_INITIAL_STATE:
			NewState(PPP_REQ_SENT_STATE);
			InitializeRestartCount();
			SendConfigureRequest();
		break;

		default:
			;
	}

	return true;
}


bool
IPCP::Down()
{
	TRACE("IPCP: Down() state=%d\n", State());

	switch (Interface().Phase()) {
		case PPP_DOWN_PHASE:
			// interface finished terminating
			NewState(PPP_INITIAL_STATE);
			ReportDownEvent();
				// this will also reset and update addresses
		break;

/*		case PPP_TERMINATION_PHASE:
			// interface is terminating
		break;

		case PPP_ESTABLISHMENT_PHASE:
			// interface is reconfiguring
		break;
*/
		case PPP_ESTABLISHED_PHASE:
			// terminate this NCP individually (block until we finished terminating)
			if (State() != PPP_INITIAL_STATE && State() != PPP_CLOSING_STATE) {
				NewState(PPP_CLOSING_STATE);
				InitializeRestartCount();
				SendTerminateRequest();
			}

			while (State() == PPP_CLOSING_STATE)
				snooze(50000);
		break;

		default:
			;
	}

	return true;
}


status_t
IPCP::Send(net_buffer *packet, uint16 protocolNumber)
{
	TRACE("IPCP: Send(0x%X)\n", protocolNumber);

	if ((protocolNumber == IP_PROTOCOL && State() == PPP_OPENED_STATE)
			|| protocolNumber == IPCP_PROTOCOL) {
#if DEBUG
		dump_packet(packet, "outgoing");
#endif
		Interface().UpdateIdleSince();
		return SendToNext(packet, protocolNumber);
	}

	ERROR("IPCP: Send() failed because of wrong state or protocol number!\n");

	gBufferModule->free(packet);
	return B_ERROR;
}


status_t
IPCP::Receive(net_buffer *packet, uint16 protocolNumber)
{
	TRACE("IPCP: Receive(0x%X)\n", protocolNumber);

	if (!packet)
		return B_ERROR;

	if (protocolNumber == IP_PROTOCOL)
		return ReceiveIPPacket(packet, protocolNumber);

	if (protocolNumber != IPCP_PROTOCOL)
		return PPP_UNHANDLED;

	NetBufferHeaderReader<ppp_lcp_packet> bufferheader(packet);
	if (bufferheader.Status() != B_OK)
		return B_ERROR;
	ppp_lcp_packet &data = bufferheader.Data();

	if (ntohs(data.length) < 4)
		return B_ERROR;

	// packet is freed by event methods
	switch (data.code) {
		case PPP_CONFIGURE_REQUEST:
			RCREvent(packet);
		break;

		case PPP_CONFIGURE_ACK:
			RCAEvent(packet);
		break;

		case PPP_CONFIGURE_NAK:
		case PPP_CONFIGURE_REJECT:
			RCNEvent(packet);
		break;

		case PPP_TERMINATE_REQUEST:
			RTREvent(packet);
		break;

		case PPP_TERMINATE_ACK:
			RTAEvent(packet);
		break;

		case PPP_CODE_REJECT:
			RXJBadEvent(packet);
				// we implemented the minimum requirements
		break;

		default:
			RUCEvent(packet);
			return PPP_REJECTED;
	}

	return B_OK;
}


status_t
IPCP::ReceiveIPPacket(net_buffer *packet, uint16 protocolNumber)
{
	if (protocolNumber != IP_PROTOCOL || State() != PPP_OPENED_STATE)
		return PPP_UNHANDLED;

	// TODO: add VJC support (the packet would be decoded here)

	if (packet) {
#if DEBUG
		dump_packet(packet, "incoming");
#endif
		Interface().UpdateIdleSince();

		TRACE("We got 1 IP packet from %s::%s\n", __FILE__, __func__);
		gBufferModule->free(packet);
		return B_OK;
	} else {
		ERROR("IPCP: Error: Could not find input function for IP!\n");
		gBufferModule->free(packet);
		return B_ERROR;
	}
}


void
IPCP::Pulse()
{
	if (fNextTimeout == 0 || fNextTimeout > system_time())
		return;
	fNextTimeout = 0;

	switch (State()) {
		case PPP_CLOSING_STATE:
			if (fTerminateCounter <= 0)
				TOBadEvent();
			else
				TOGoodEvent();
		break;

		case PPP_REQ_SENT_STATE:
		case PPP_ACK_RCVD_STATE:
		case PPP_ACK_SENT_STATE:
			if (fRequestCounter <= 0)
				TOBadEvent();
			else
				TOGoodEvent();
		break;

		default:
			;
	}
}


bool
IPCP::ParseSideRequests(const driver_parameter *requests, ppp_side side)
{
	if (!requests)
		return false;

	ipcp_requests *selectedRequests;

	if (side == PPP_LOCAL_SIDE) {
		selectedRequests = &fLocalRequests;
		fRequestPrimaryDNS = fRequestSecondaryDNS = false;
	} else
		selectedRequests = &fPeerRequests;

	memset(selectedRequests, 0, sizeof(ipcp_requests));
		// reset current requests

	// The following values are allowed:
	//  "Address"		the ip address that will be suggested
	//  "Netmask"		the netmask that should be used
	//  "PrimaryDNS"	primary DNS server
	//  "SecondaryDNS"	secondary DNS server
	// Setting any value to 0.0.0.0 or "auto" means it should be chosen automatically.

	in_addr_t address = INADDR_ANY;
	for (int32 index = 0; index < requests->parameter_count; index++) {
		if (requests->parameters[index].value_count == 0)
			continue;

		// all values are IP addresses, so parse the address here
		if (strcasecmp(requests->parameters[index].values[0], "auto")) {
			address = inet_addr(requests->parameters[index].values[0]);
			// address = INADDR_ANY;
			if (address == INADDR_NONE)
				continue;
		}

		if (!strcasecmp(requests->parameters[index].name, IPCP_IP_ADDRESS_KEY))
			selectedRequests->address = address;
		else if (!strcasecmp(requests->parameters[index].name, IPCP_NETMASK_KEY))
			selectedRequests->netmask = address;
		else if (!strcasecmp(requests->parameters[index].name, IPCP_PRIMARY_DNS_KEY)) {
			selectedRequests->primaryDNS = address;
			if (side == PPP_LOCAL_SIDE)
				fRequestPrimaryDNS = true;
		} else if (!strcasecmp(requests->parameters[index].name,
				IPCP_SECONDARY_DNS_KEY)) {
			selectedRequests->secondaryDNS = address;
			if (side == PPP_LOCAL_SIDE)
				fRequestSecondaryDNS = true;
		}
	}

	return true;
}


net_interface *
get_interface_by_name(net_domain *domain, const char *name)
{
	ifreq request;
	memset(&request, 0, sizeof(request));
	size_t size = sizeof(request);

	strlcpy(request.ifr_name, name, IF_NAMESIZE);

	if (sDatalinkModule->control(domain, SIOCGIFINDEX, &request, &size) != B_OK) {
		TRACE("sDatalinkModule->control failure\n");
		return NULL;
	}
	return sDatalinkModule->get_interface(domain, request.ifr_index);
}


status_t
set_interface_address(net_domain* domain, struct ifaliasreq* inreq)
{
	size_t size = sizeof(struct ifaliasreq);
	return sDatalinkModule->control(domain, B_SOCKET_SET_ALIAS, inreq, &size);
}


void
IPCP::UpdateAddresses()
{
	TRACE("%s::%s: entering UpdateAddresses\n", __FILE__, __func__);
	RemoveRoutes();

	if (State() != PPP_OPENED_STATE && !Interface().DoesConnectOnDemand())
		return;

	TRACE("%s::%s: entering ChangeAddress\n", __FILE__, __func__);
	if (sDatalinkModule == NULL) {
		TRACE("%s::%s: some module not found!\n", __FILE__, __func__);
		return;
	}


	struct sockaddr newAddr = {6, AF_INET, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
	struct sockaddr netmask = {6, AF_INET, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
	struct sockaddr broadaddr = {6, AF_INET, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};

	if (fLocalRequests.address != INADDR_ANY)
		memcpy(newAddr.sa_data + 2, &fLocalRequests.address, sizeof(in_addr_t));
	else if (fLocalConfiguration.address == INADDR_ANY) {
		in_addr_t inaddrBroadcast = 0x010F0F0F; // was: INADDR_BROADCAST
		memcpy(newAddr.sa_data + 2, &inaddrBroadcast, sizeof(in_addr_t));
	} else
		memcpy(newAddr.sa_data + 2, &fLocalConfiguration.address, sizeof(in_addr_t));

	struct ifaliasreq inreq;
	memset(&inreq, 0, sizeof(struct ifaliasreq));
	memcpy(inreq.ifra_name, Interface().Name(), IF_NAMESIZE);
	memcpy(&inreq.ifra_addr, &newAddr, sizeof(struct sockaddr));
	memcpy(&inreq.ifra_mask, &netmask, sizeof(struct sockaddr));
	memcpy(&inreq.ifra_broadaddr, &broadaddr, sizeof(struct sockaddr));
	inreq.ifra_index = -1;
		// create a new interface address
		// Is it OK when we already have one?
		// test case:	ifconfig ppp up
		// 		ifconfig ppp down
		// 		ifconfig ppp up
		// 		check if some weird things happen

	net_domain* domain = sStackModule->get_domain(AF_INET);
	status_t status = set_interface_address(domain, &inreq);

	if (status != B_OK) {
		TRACE("%s:%s: set_interface_address Fail!!!!\n", __FILE__, __func__);
		return;
	}
	TRACE("%s:%s: set_interface_address fine\n", __FILE__, __func__);


	net_interface* pppInterface = get_interface_by_name(domain, Interface().Name());
	if (pppInterface == NULL) {
		TRACE("%s::%s: pppInterface not found!\n", __FILE__, __func__);
		return;
	}

	net_interface_address* pppInterfaceAddress = NULL;
	while (sDatalinkModule->get_next_interface_address(pppInterface,
				&pppInterfaceAddress)) {
		if (pppInterfaceAddress->domain->family != AF_INET)
			continue;
		break;
	}

	// add default/subnet route
	if (Side() == PPP_LOCAL_SIDE && pppInterfaceAddress != NULL) {
		struct sockaddr addrGateway = {8, AF_INET, {0x00, 0x00, 0x00, 0x00,
			0x00, 0x00} };

		// create destination address
		if (fPeerRequests.address != INADDR_ANY)
			memcpy(addrGateway.sa_data + 2, &fPeerRequests.address,
				sizeof(in_addr_t));
		else if (fPeerConfiguration.address == INADDR_ANY) {
			in_addr_t gateway = 0x020F0F0F;
			memcpy(addrGateway.sa_data + 2, &gateway, sizeof(in_addr_t));
				// was: INADDR_BROADCAST
		} else
			memcpy(addrGateway.sa_data + 2, &fPeerConfiguration.address,
				sizeof(in_addr_t));

		net_route defaultRoute;
		defaultRoute.destination = NULL;
		defaultRoute.mask = NULL;
		defaultRoute.gateway = &addrGateway;
		defaultRoute.flags = RTF_DEFAULT | RTF_GATEWAY;
		defaultRoute.interface_address = pppInterfaceAddress;
			// route->interface_address;

		status_t status = sDatalinkModule->add_route(domain, &defaultRoute);
		if (status == B_OK)
			dprintf("%s::%s: add route default OK!\n", __FILE__, __func__);
		else
			dprintf("%s::%s: add route default Fail!\n", __FILE__, __func__);

		sDatalinkModule->put_interface_address(pppInterfaceAddress);
	}

	if (Side() == PPP_LOCAL_SIDE) {
		int file;
		int primary_dns, secondary_dns;
		char buf[256];

		file = open(RESOLV_CONF_FILE, O_RDWR);

		primary_dns = ntohl(fLocalConfiguration.primaryDNS);
		secondary_dns = ntohl(fLocalConfiguration.secondaryDNS);

		sprintf(buf, "%s\t%d.%d.%d.%d\n%s\t%d.%d.%d.%d\n",
				"nameserver",
				(primary_dns & 0xff000000) >> 24,
				(primary_dns & 0x00ff0000) >> 16,
				(primary_dns & 0x0000ff00) >> 8,
				(primary_dns & 0x000000ff),
				"nameserver",
				(secondary_dns & 0xff000000) >> 24,
				(secondary_dns & 0x00ff0000) >> 16,
				(secondary_dns & 0x0000ff00) >> 8,
				(secondary_dns & 0x000000ff));

		write(file, buf, strlen(buf));
		close(file);
	}
}


void
IPCP::RemoveRoutes()
{
	// note:
	// 	haiku supports multi default route. But for Desktop, ppp is generally
	// 	the only default route. So is it necessary to remove other default
	// 	route?
	TRACE("%s::%s: entering RemoveRoutes!\n", __FILE__, __func__);

	char *ethernetName = NULL;
	PPPoEDevice* pppoeDevice = (PPPoEDevice *)Interface().Device();
	if (pppoeDevice == NULL)
		return;
	ethernetName = pppoeDevice->EthernetIfnet()->name;
	if (ethernetName == NULL)
		return;

	net_domain* domain = sStackModule->get_domain(AF_INET);
	net_interface* pppInterface = get_interface_by_name(domain, ethernetName);

	if (pppInterface == NULL) {
		TRACE("%s::%s: pppInterface not found!\n", __FILE__, __func__);
		return;
	}

	net_interface_address* pppInterfaceAddress = NULL;

	while (sDatalinkModule->get_next_interface_address(pppInterface,
				&pppInterfaceAddress)) {
		if (pppInterfaceAddress->domain->family != AF_INET)
			continue;

		net_route oldDefaultRoute;
		oldDefaultRoute.destination = NULL;
		oldDefaultRoute.mask = NULL;
		oldDefaultRoute.gateway = NULL;
		oldDefaultRoute.flags= RTF_DEFAULT;
		oldDefaultRoute.interface_address = pppInterfaceAddress;

		status_t status = sDatalinkModule->remove_route(domain, &oldDefaultRoute);
			// current: can not get the system default route so we fake
			// 	    one default route for delete
			// Todo: save the oldDefaultRoute to fDefaultRoute
			// 	 restore the fDefaultRoute when ppp is down

		sDatalinkModule->put_interface_address(pppInterfaceAddress);

		if (status == B_OK)
			dprintf("IPCP::RemoveRoutes: remove old default route OK!\n");
		else
			dprintf("IPCP::RemoveRoutes: remove old default route Fail!\n");

		break;
	}

	if (fDefaultRoute) {
		struct sockaddr_in netmask;
		memset(&netmask, 0, sizeof(struct sockaddr_in));

		netmask.sin_family = AF_INET;
		netmask.sin_addr.s_addr = fLocalRequests.netmask;
		netmask.sin_len = sizeof(struct sockaddr_in);

		// if (rtrequest(RTM_DELETE, (struct sockaddr*) &netmask,
				// (struct sockaddr*) &fGateway, (struct sockaddr*) &netmask,
				// RTF_UP | RTF_GATEWAY, &fDefaultRoute) != B_OK)
			ERROR("IPCP: RemoveRoutes(): could not remove default/subnet route!\n");

		fDefaultRoute = NULL;
	}
}


uint8
IPCP::NextID()
{
	return (uint8) atomic_add(&fID, 1);
}


void
IPCP::NewState(ppp_state next)
{
	TRACE("IPCP: NewState(%d) state=%d\n", next, State());

	// report state changes
	if (State() == PPP_INITIAL_STATE && next != State())
		UpStarted();
	else if (State() == PPP_OPENED_STATE && next != State())
		DownStarted();

	// maybe we do not need the timer anymore
	if (next < PPP_CLOSING_STATE || next == PPP_OPENED_STATE)
		fNextTimeout = 0;

	fState = next;
}


void
IPCP::TOGoodEvent()
{
#if DEBUG
	printf("IPCP: TOGoodEvent() state=%d\n", State());
#endif

	switch (State()) {
		case PPP_CLOSING_STATE:
			SendTerminateRequest();
		break;

		case PPP_ACK_RCVD_STATE:
			NewState(PPP_REQ_SENT_STATE);

		case PPP_REQ_SENT_STATE:
		case PPP_ACK_SENT_STATE:
			SendConfigureRequest();
		break;

		default:
			IllegalEvent(PPP_TO_GOOD_EVENT);
	}
}


void
IPCP::TOBadEvent()
{
	TRACE("IPCP: TOBadEvent() state=%d\n", State());

	switch (State()) {
		case PPP_CLOSING_STATE:
			NewState(PPP_INITIAL_STATE);
			ReportDownEvent();
		break;

		case PPP_REQ_SENT_STATE:
		case PPP_ACK_RCVD_STATE:
		case PPP_ACK_SENT_STATE:
			NewState(PPP_INITIAL_STATE);
			ReportUpFailedEvent();
		break;

		default:
			IllegalEvent(PPP_TO_BAD_EVENT);
	}
}


void
IPCP::RCREvent(net_buffer *packet)
{
	TRACE("IPCP: RCREvent() state=%d\n", State());

	KPPPConfigurePacket request(packet);
	KPPPConfigurePacket nak(PPP_CONFIGURE_NAK);
	KPPPConfigurePacket reject(PPP_CONFIGURE_REJECT);

	NetBufferHeaderReader<ppp_lcp_packet> bufferheader(packet);
	if (bufferheader.Status() != B_OK)
		return;
	ppp_lcp_packet &lcpHeader = bufferheader.Data();

	// we should not use the same id as the peer
	if (fID == lcpHeader.id)
		fID -= 128;

	nak.SetID(request.ID());
	reject.SetID(request.ID());

	// parse each item
	ppp_configure_item *item;
	in_addr_t *requestedAddress, *wishedAddress = NULL;
	for (int32 index = 0; index < request.CountItems(); index++) {
		item = request.ItemAt(index);
		if (!item)
			continue;

		// addresses have special handling to reduce code size
		switch (item->type) {
			case IPCP_ADDRESSES:
				// abandoned by the standard
			case IPCP_ADDRESS:
				wishedAddress = &fPeerRequests.address;
			break;

			case IPCP_PRIMARY_DNS:
				wishedAddress = &fPeerRequests.primaryDNS;
			break;

			case IPCP_SECONDARY_DNS:
				wishedAddress = &fPeerRequests.secondaryDNS;
			break;
		}

		// now parse item
		switch (item->type) {
			case IPCP_ADDRESSES:
				// abandoned by the standard
			case IPCP_ADDRESS:
			case IPCP_PRIMARY_DNS:
			case IPCP_SECONDARY_DNS:
				if (item->length != 6) {
					// the packet is invalid
					gBufferModule->free(packet);
					NewState(PPP_INITIAL_STATE);
					ReportUpFailedEvent();
					return;
				}

				requestedAddress = (in_addr_t*) item->data;
				if (*wishedAddress == INADDR_ANY) {
					if (*requestedAddress == INADDR_ANY) {
						// we do not have an address for you
						gBufferModule->free(packet);
						NewState(PPP_INITIAL_STATE);
						ReportUpFailedEvent();
						return;
					}
				} else if (*requestedAddress != *wishedAddress) {
					// we do not want this address
					ip_item ipItem;
					ipItem.type = item->type;
					ipItem.length = 6;
					ipItem.address = *wishedAddress;
					nak.AddItem((ppp_configure_item*) &ipItem);
				}
			break;

//			case IPCP_COMPRESSION_PROTOCOL:
				// TODO: implement me!
//			break;

			default:
				reject.AddItem(item);
		}
	}

	// append additional values to the nak
	if (!request.ItemWithType(IPCP_ADDRESS) && fPeerRequests.address == INADDR_ANY) {
		// The peer did not provide us his address. Tell him to do so.
		ip_item ipItem;
		ipItem.type = IPCP_ADDRESS;
		ipItem.length = 6;
		ipItem.address = INADDR_ANY;
		nak.AddItem((ppp_configure_item*) &ipItem);
	}

	if (nak.CountItems() > 0) {
		RCRBadEvent(nak.ToNetBuffer(Interface().MRU()), NULL);
		gBufferModule->free(packet);
	} else if (reject.CountItems() > 0) {
		RCRBadEvent(NULL, reject.ToNetBuffer(Interface().MRU()));
		gBufferModule->free(packet);
	} else
		RCRGoodEvent(packet);
}


void
IPCP::RCRGoodEvent(net_buffer *packet)
{
	TRACE("IPCP: RCRGoodEvent() state=%d\n", State());

	switch (State()) {
		case PPP_INITIAL_STATE:
			NewState(PPP_ACK_SENT_STATE);
			InitializeRestartCount();
			SendConfigureRequest();
			SendConfigureAck(packet);
		break;

		case PPP_REQ_SENT_STATE:
			NewState(PPP_ACK_SENT_STATE);

		case PPP_ACK_SENT_STATE:
			SendConfigureAck(packet);
		break;

		case PPP_ACK_RCVD_STATE:
			NewState(PPP_OPENED_STATE);
			SendConfigureAck(packet);
			ReportUpEvent();
		break;

		case PPP_OPENED_STATE:
			NewState(PPP_ACK_SENT_STATE);
			SendConfigureRequest();
			SendConfigureAck(packet);
		break;

		default:
			gBufferModule->free(packet);
	}
}


void
IPCP::RCRBadEvent(net_buffer *nak, net_buffer *reject)
{
	TRACE("IPCP: RCRBadEvent() state=%d\n", State());

	uint16 lcpHdrRejectLength = 0;
	uint16 lcpHdrNakLength = 0;

	if (nak) {
		NetBufferHeaderReader<ppp_lcp_packet> nakBufferHeaderReader(nak);
		if (nakBufferHeaderReader.Status() != B_OK)
			return;
		ppp_lcp_packet &lcpNakPacket = nakBufferHeaderReader.Data();
		lcpHdrNakLength = lcpNakPacket.length;
	}


	if (reject) {
		NetBufferHeaderReader<ppp_lcp_packet> rejectBufferHeaderReader(reject);
		if (rejectBufferHeaderReader.Status() != B_OK)
			return;
		ppp_lcp_packet &lcpRejectPacket = rejectBufferHeaderReader.Data();
		lcpHdrRejectLength = lcpRejectPacket.length;
	}

	switch (State()) {
		case PPP_OPENED_STATE:
			NewState(PPP_REQ_SENT_STATE);
			SendConfigureRequest();

		case PPP_ACK_SENT_STATE:
			if (State() == PPP_ACK_SENT_STATE)
				NewState(PPP_REQ_SENT_STATE);
					// OPENED_STATE might have set this already

		case PPP_INITIAL_STATE:
		case PPP_REQ_SENT_STATE:
		case PPP_ACK_RCVD_STATE:
			if (nak && ntohs(lcpHdrNakLength) > 3)
				SendConfigureNak(nak);
			else if (reject && ntohs(lcpHdrRejectLength) > 3)
				SendConfigureNak(reject);
		return;
			// prevents the nak/reject from being m_freem()'d

		default:
			;
	}

	if (nak)
		gBufferModule->free(nak);
	if (reject)
		gBufferModule->free(reject);
}


void
IPCP::RCAEvent(net_buffer *packet)
{
	ERROR("IPCP: RCAEvent() state=%d\n", State());

	NetBufferHeaderReader<ppp_lcp_packet> bufferheader(packet);
	if (bufferheader.Status() != B_OK)
		return;
	ppp_lcp_packet &lcpHeader = bufferheader.Data();
	if (fRequestID != lcpHeader.id) {
		// this packet is not a reply to our request

		// TODO: log this event
		gBufferModule->free(packet);
		return;
	}

	// parse this ack
	KPPPConfigurePacket ack(packet);
	ppp_configure_item *item;
	in_addr_t *requestedAddress, *wishedAddress = NULL, *configuredAddress = NULL;
	for (int32 index = 0; index < ack.CountItems(); index++) {
		item = ack.ItemAt(index);
		if (!item)
			continue;

		// addresses have special handling to reduce code size
		switch (item->type) {
			case IPCP_ADDRESSES:
				// abandoned by the standard
			case IPCP_ADDRESS:
				wishedAddress = &fLocalRequests.address;
				configuredAddress = &fLocalConfiguration.address;
			break;

			case IPCP_PRIMARY_DNS:
				wishedAddress = &fLocalRequests.primaryDNS;
				configuredAddress = &fLocalConfiguration.primaryDNS;
			break;

			case IPCP_SECONDARY_DNS:
				wishedAddress = &fLocalRequests.secondaryDNS;
				configuredAddress = &fLocalConfiguration.secondaryDNS;
			break;
		}

		// now parse item
		switch (item->type) {
			case IPCP_ADDRESSES:
				// abandoned by the standard
			case IPCP_ADDRESS:
			case IPCP_PRIMARY_DNS:
			case IPCP_SECONDARY_DNS:
				requestedAddress = (in_addr_t*) item->data;
				if ((*wishedAddress == INADDR_ANY && *requestedAddress != INADDR_ANY)
						|| *wishedAddress == *requestedAddress)
					*configuredAddress = *requestedAddress;
			break;

//			case IPCP_COMPRESSION_PROTOCOL:
				// TODO: implement me
//			break;

			default:
				;
		}
	}

	// if address was not specified we should select the given one
	if (!ack.ItemWithType(IPCP_ADDRESS))
		fLocalConfiguration.address = fLocalRequests.address;


	switch (State()) {
		case PPP_INITIAL_STATE:
			IllegalEvent(PPP_RCA_EVENT);
		break;

		case PPP_REQ_SENT_STATE:
			NewState(PPP_ACK_RCVD_STATE);
			InitializeRestartCount();
		break;

		case PPP_ACK_RCVD_STATE:
			NewState(PPP_REQ_SENT_STATE);
			SendConfigureRequest();
		break;

		case PPP_ACK_SENT_STATE:
			NewState(PPP_OPENED_STATE);
			InitializeRestartCount();
			ReportUpEvent();
		break;

		case PPP_OPENED_STATE:
			NewState(PPP_REQ_SENT_STATE);
			SendConfigureRequest();
		break;

		default:
			;
	}

	gBufferModule->free(packet);
}


void
IPCP::RCNEvent(net_buffer *packet)
{
	TRACE("IPCP: RCNEvent() state=%d\n", State());

	NetBufferHeaderReader<ppp_lcp_packet> bufferheader(packet);
	if (bufferheader.Status() != B_OK)
		return;
	ppp_lcp_packet &lcpHeader = bufferheader.Data();

	if (fRequestID != lcpHeader.id) {
		// this packet is not a reply to our request

		// TODO: log this event
		gBufferModule->free(packet);
		return;
	}

	// parse this nak/reject
	KPPPConfigurePacket nak_reject(packet);
	ppp_configure_item *item;
	in_addr_t *requestedAddress;
	if (nak_reject.Code() == PPP_CONFIGURE_NAK)
		for (int32 index = 0; index < nak_reject.CountItems(); index++) {
			item = nak_reject.ItemAt(index);
			if (!item)
				continue;

			switch (item->type) {
				case IPCP_ADDRESSES:
					// abandoned by the standard
				case IPCP_ADDRESS:
					if (item->length != 6)
						continue;

					requestedAddress = (in_addr_t*) item->data;
					if (fLocalRequests.address == INADDR_ANY
							&& *requestedAddress != INADDR_ANY)
						fLocalConfiguration.address = *requestedAddress;
							// this will be used in our next request
				break;

//				case IPCP_COMPRESSION_PROTOCOL:
					// TODO: implement me!
//				break;

				case IPCP_PRIMARY_DNS:
					if (item->length != 6)
						continue;

					requestedAddress = (in_addr_t*) item->data;
					if (fRequestPrimaryDNS
							&& fLocalRequests.primaryDNS == INADDR_ANY
							&& *requestedAddress != INADDR_ANY)
						fLocalConfiguration.primaryDNS = *requestedAddress;
							// this will be used in our next request
				break;

				case IPCP_SECONDARY_DNS:
					if (item->length != 6)
						continue;

					requestedAddress = (in_addr_t*) item->data;
					if (fRequestSecondaryDNS
							&& fLocalRequests.secondaryDNS == INADDR_ANY
							&& *requestedAddress != INADDR_ANY)
						fLocalConfiguration.secondaryDNS = *requestedAddress;
							// this will be used in our next request
				break;

				default:
					;
			}
		}
	else if (nak_reject.Code() == PPP_CONFIGURE_REJECT)
		for (int32 index = 0; index < nak_reject.CountItems(); index++) {
			item = nak_reject.ItemAt(index);
			if (!item)
				continue;

			switch (item->type) {
//				case IPCP_COMPRESSION_PROTOCOL:
					// TODO: implement me!
//				break;

				default:
					// DNS and addresses must be supported if we set them to auto
					gBufferModule->free(packet);
					NewState(PPP_INITIAL_STATE);
					ReportUpFailedEvent();
					return;
			}
		}

	switch (State()) {
		case PPP_INITIAL_STATE:
			IllegalEvent(PPP_RCN_EVENT);
		break;

		case PPP_REQ_SENT_STATE:
		case PPP_ACK_SENT_STATE:
			InitializeRestartCount();

		case PPP_ACK_RCVD_STATE:
		case PPP_OPENED_STATE:
			if (State() == PPP_ACK_RCVD_STATE || State() == PPP_OPENED_STATE)
				NewState(PPP_REQ_SENT_STATE);
			SendConfigureRequest();
		break;

		default:
			;
	}

	gBufferModule->free(packet);
}


void
IPCP::RTREvent(net_buffer *packet)
{
	TRACE("IPCP: RTREvent() state=%d\n", State());

	NetBufferHeaderReader<ppp_lcp_packet> bufferheader(packet);
	if (bufferheader.Status() != B_OK)
		return;
	ppp_lcp_packet &lcpHeader = bufferheader.Data();

	// we should not use the same ID as the peer
	if (fID == lcpHeader.id)
		fID -= 128;

	switch (State()) {
		case PPP_INITIAL_STATE:
			IllegalEvent(PPP_RTR_EVENT);
		break;

		case PPP_ACK_RCVD_STATE:
		case PPP_ACK_SENT_STATE:
			NewState(PPP_REQ_SENT_STATE);

		case PPP_CLOSING_STATE:
		case PPP_REQ_SENT_STATE:
			SendTerminateAck(packet);
		return;
			// do not free packet

		case PPP_OPENED_STATE:
			NewState(PPP_CLOSING_STATE);
			ResetRestartCount();
			SendTerminateAck(packet);
		return;
			// do not free packet

		default:
			;
	}

	gBufferModule->free(packet);
}


void
IPCP::RTAEvent(net_buffer *packet)
{
	TRACE("IPCP: RTAEvent() state=%d\n", State());

	NetBufferHeaderReader<ppp_lcp_packet> bufferheader(packet);
	if (bufferheader.Status() != B_OK)
		return;
	ppp_lcp_packet &lcpHeader = bufferheader.Data();
	if (fTerminateID != lcpHeader.id) {
		// this packet is not a reply to our request

		// TODO: log this event
		gBufferModule->free(packet);
		return;
	}

	switch (State()) {
		case PPP_INITIAL_STATE:
			IllegalEvent(PPP_RTA_EVENT);
		break;

		case PPP_CLOSING_STATE:
			NewState(PPP_INITIAL_STATE);
			ReportDownEvent();
		break;

		case PPP_ACK_RCVD_STATE:
			NewState(PPP_REQ_SENT_STATE);
		break;

		case PPP_OPENED_STATE:
			NewState(PPP_REQ_SENT_STATE);
			SendConfigureRequest();
		break;

		default:
			;
	}

	gBufferModule->free(packet);
}


void
IPCP::RUCEvent(net_buffer *packet)
{
	TRACE("IPCP: RUCEvent() state=%d\n", State());

	SendCodeReject(packet);
}


void
IPCP::RXJBadEvent(net_buffer *packet)
{
	TRACE("IPCP: RXJBadEvent() state=%d\n", State());

	switch (State()) {
		case PPP_INITIAL_STATE:
			IllegalEvent(PPP_RXJ_BAD_EVENT);
		break;

		case PPP_CLOSING_STATE:
			NewState(PPP_INITIAL_STATE);
			ReportDownEvent();
		break;

		case PPP_REQ_SENT_STATE:
		case PPP_ACK_RCVD_STATE:
		case PPP_ACK_SENT_STATE:
			NewState(PPP_INITIAL_STATE);
			ReportUpFailedEvent();
		break;

		case PPP_OPENED_STATE:
			NewState(PPP_CLOSING_STATE);
			InitializeRestartCount();
			SendTerminateRequest();
		break;

		default:
			;
	}

	gBufferModule->free(packet);
}


// actions
void
IPCP::IllegalEvent(ppp_event event)
{
	// TODO: update error statistics
	ERROR("IPCP: IllegalEvent(event=%d) state=%d\n", event, State());
}


void
IPCP::ReportUpFailedEvent()
{
	// reset configurations
	memset(&fLocalConfiguration, 0, sizeof(ipcp_configuration));
	memset(&fPeerConfiguration, 0, sizeof(ipcp_configuration));

	UpdateAddresses();

	UpFailedEvent();
}


void
IPCP::ReportUpEvent()
{
	UpdateAddresses();

	UpEvent();
}


void
IPCP::ReportDownEvent()
{
	// reset configurations
	memset(&fLocalConfiguration, 0, sizeof(ipcp_configuration));
	memset(&fPeerConfiguration, 0, sizeof(ipcp_configuration));

	// don't update address if connect on demand is enabled
	dprintf("ppp down, and leaving old address and rotues\n");
	// UpdateAddresses();

	DownEvent();
}


void
IPCP::InitializeRestartCount()
{
	fRequestCounter = fMaxRequest;
	fTerminateCounter = fMaxTerminate;
	fNakCounter = fMaxNak;
}


void
IPCP::ResetRestartCount()
{
	fRequestCounter = 0;
	fTerminateCounter = 0;
	fNakCounter = 0;
}


bool
IPCP::SendConfigureRequest()
{
	TRACE("IPCP: SendConfigureRequest() state=%d\n", State());

	--fRequestCounter;
	fNextTimeout = system_time() + kIPCPStateMachineTimeout;

	KPPPConfigurePacket request(PPP_CONFIGURE_REQUEST);
	request.SetID(NextID());
	fRequestID = request.ID();
	ip_item ipItem;
	ipItem.length = 6;

	// add address
	ipItem.type = IPCP_ADDRESS;
	if (fLocalRequests.address == INADDR_ANY)
		ipItem.address = (fLocalConfiguration.address);
	else
		ipItem.address =(fLocalRequests.address);
	request.AddItem((ppp_configure_item*) &ipItem);

	TRACE("IPCP: SCR: confaddr=%X; reqaddr=%X; addr=%X\n",
		fLocalConfiguration.address, fLocalRequests.address,
		((ip_item*)request.ItemAt(0))->address);

	// add primary DNS (if needed)
	if (fRequestPrimaryDNS && fLocalRequests.primaryDNS == INADDR_ANY) {
		ipItem.type = IPCP_PRIMARY_DNS;
		ipItem.address = fLocalConfiguration.primaryDNS;
			// at first this is 0.0.0.0, but a nak might have set it to a correct value
		request.AddItem((ppp_configure_item*) &ipItem);
	}

	// add secondary DNS (if needed)
	if (fRequestSecondaryDNS && fLocalRequests.primaryDNS == INADDR_ANY) {
		ipItem.type = IPCP_SECONDARY_DNS;
		ipItem.address = fLocalConfiguration.secondaryDNS;
			// at first this is 0.0.0.0, but a nak might have set it to a correct value
		request.AddItem((ppp_configure_item*) &ipItem);
	}

	// TODO: add VJC support

	return Send(request.ToNetBuffer(Interface().MRU())) == B_OK;
}


bool
IPCP::SendConfigureAck(net_buffer *packet)
{
	TRACE("IPCP: SendConfigureAck() state=%d\n", State());

	if (!packet)
		return false;

	NetBufferHeaderReader<ppp_lcp_packet> bufferheader(packet);
	if (bufferheader.Status() != B_OK)
		return false;
	ppp_lcp_packet &lcpheader = bufferheader.Data();
	lcpheader.code = PPP_CONFIGURE_ACK;

	bufferheader.Sync();

	KPPPConfigurePacket ack(packet);

	// verify items
	ppp_configure_item *item;
	in_addr_t *requestedAddress, *wishedAddress = NULL, *configuredAddress = NULL;
	for (int32 index = 0; index < ack.CountItems(); index++) {
		item = ack.ItemAt(index);
		if (!item)
			continue;

		// addresses have special handling to reduce code size
		switch (item->type) {
			case IPCP_ADDRESSES:
				// abandoned by the standard
			case IPCP_ADDRESS:
				wishedAddress = &fPeerRequests.address;
				configuredAddress = &fPeerConfiguration.address;
			break;

			case IPCP_PRIMARY_DNS:
				wishedAddress = &fPeerRequests.primaryDNS;
				configuredAddress = &fPeerConfiguration.primaryDNS;
			break;

			case IPCP_SECONDARY_DNS:
				wishedAddress = &fPeerRequests.secondaryDNS;
				configuredAddress = &fPeerConfiguration.secondaryDNS;
			break;
		}

		// now parse item
		switch (item->type) {
			case IPCP_ADDRESSES:
				// abandoned by the standard
			case IPCP_ADDRESS:
			case IPCP_PRIMARY_DNS:
			case IPCP_SECONDARY_DNS:
				requestedAddress = (in_addr_t*) item->data;
				if ((*wishedAddress == INADDR_ANY && *requestedAddress != INADDR_ANY)
						|| *wishedAddress == *requestedAddress)
					*configuredAddress = *requestedAddress;
			break;

//			case IPCP_COMPRESSION_PROTOCOL:
				// TODO: implement me!
//			break;

			default:
				;
		}
	}

	// if address was not specified we should select the given one
	if (!ack.ItemWithType(IPCP_ADDRESS))
		fPeerConfiguration.address = fPeerRequests.address;

	return Send(packet) == B_OK;
}


bool
IPCP::SendConfigureNak(net_buffer *packet)
{
	TRACE("IPCP: SendConfigureNak() state=%d\n", State());

	if (!packet)
		return false;

	NetBufferHeaderReader<ppp_lcp_packet> bufferheader(packet);
	if (bufferheader.Status() != B_OK)
		return false;

	ppp_lcp_packet &nak = bufferheader.Data();

	if (nak.code == PPP_CONFIGURE_NAK) {
		if (fNakCounter == 0) {
			// We sent enough naks. Let's try a reject.
			nak.code = PPP_CONFIGURE_REJECT;
		} else
			--fNakCounter;
	}

	bufferheader.Sync();

	return Send(packet) == B_OK;
}


bool
IPCP::SendTerminateRequest()
{
	TRACE("IPCP: SendTerminateRequest() state=%d\n", State());

	--fTerminateCounter;
	fNextTimeout = system_time() + kIPCPStateMachineTimeout;

	net_buffer *packet = gBufferModule->create(256);
	if (!packet)
		return false;

	ppp_lcp_packet *request;
	status_t status = gBufferModule->append_size(packet, 1492, (void **)(&request));
	if (status != B_OK)
		return false;

	request->code = PPP_TERMINATE_REQUEST;
	request->id = fTerminateID = NextID();
	request->length = htons(4);

	status = gBufferModule->trim(packet, 4);
	if (status != B_OK)
		return false;

	return Send(packet) == B_OK;
}


bool
IPCP::SendTerminateAck(net_buffer *request)
{
	TRACE("IPCP: SendTerminateAck() state=%d\n", State());

	net_buffer *reply = request;

	if (!reply) {
		reply = gBufferModule->create(256);
		ppp_lcp_packet *ack;
		status_t status = gBufferModule->append_size(reply, 1492, (void **)(&ack));
		if (status != B_OK) {
			gBufferModule->free(reply);
			return false;
		}
		ack->id = NextID();
		ack->code = PPP_TERMINATE_ACK;
		ack->length = htons(4);
		gBufferModule->trim(reply, 4);
	} else {
		NetBufferHeaderReader<ppp_lcp_packet> bufferHeader(reply);
		if (bufferHeader.Status() < B_OK)
			return false;
		ppp_lcp_packet &ack = bufferHeader.Data();
		ack.code = PPP_TERMINATE_ACK;
		ack.length = htons(4);
	}

	return Send(reply) == B_OK;
}


bool
IPCP::SendCodeReject(net_buffer *packet)
{
	TRACE("IPCP: SendCodeReject() state=%d\n", State());

	if (!packet)
		return false;

	NetBufferPrepend<ppp_lcp_packet> bufferHeader(packet);
	if (bufferHeader.Status() != B_OK)
		return false;

	ppp_lcp_packet &reject = bufferHeader.Data();

	reject.code = PPP_CODE_REJECT;
	reject.id = NextID();
	reject.length = htons(packet->size);

	bufferHeader.Sync();

	return Send(packet) == B_OK;
}