* Copyright 2003-2006, Waldemar Kornewald <wkornew@gmx.net>
* Distributed under the terms of the MIT License.
*/
#include <cstdlib>
#include <ByteOrder.h>
#include <net/if_dl.h>
#include <net_stack.h>
#include <arpa/inet.h>
#include <ethernet.h>
#include <ether_driver.h>
#include "PPPoEDevice.h"
#include "DiscoveryPacket.h"
#include <settings_tools.h>
extern net_stack_module_info *gStackModule;
extern net_buffer_module_info *gBufferModule;
extern status_t
pppoe_input(void *cookie, net_device *_device, net_buffer *packet);
#if DEBUG
static char sDigits[] = "0123456789ABCDEF";
void
dump_packet(net_buffer *packet)
{
if (!packet)
return;
BufferHeaderReader<uint8> bufferheader(packet);
if (bufferheader.Status() != B_OK)
return;
uint8 &buffer = bufferheader.Data();
uint8 *data = &buffer;
uint8 buffer[33];
uint8 bufferIndex = 0;
TRACE("Dumping packet;len=%ld;pkthdr.len=%d\n", packet->m_len,
packet->m_flags & M_PKTHDR ? packet->m_pkthdr.len : -1);
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] = 0;
TRACE("%s\n", buffer);
bufferIndex = 0;
}
}
}
#endif
PPPoEDevice::PPPoEDevice(KPPPInterface& interface, driver_parameter *settings)
: KPPPDevice("PPPoE", PPPoE_HEADER_SIZE + ETHER_HDR_LEN, interface, settings),
fEthernetIfnet(NULL),
fSessionID(0),
fHostUniq(NewHostUniq()),
fACName(NULL),
fServiceName(NULL),
fAttempts(0),
fNextTimeout(0),
fState(INITIAL)
{
#if DEBUG
TRACE("PPPoEDevice: Constructor\n");
if (!settings || !settings->parameters)
TRACE("PPPoEDevice::ctor: No settings!\n");
#endif
interface.SetPFCOptions(PPP_ALLOW_PFC);
memset(fPeer, 0xFF, sizeof(fPeer));
SetMTU(1494);
if (!settings)
dprintf("%s::%s: settings is NULL\n", __FILE__, __func__);
finterfaceName = get_parameter_value(PPPoE_INTERFACE_KEY, settings);
TRACE("%s::%s: finterfaceName is %s\n", __FILE__, __func__,
finterfaceName ? finterfaceName : "NULL");
fACName = get_parameter_value(PPPoE_AC_NAME_KEY, settings);
TRACE("fACName is %s\n", fACName ? fACName : "NULL");
fServiceName = get_parameter_value(PPPoE_SERVICE_NAME_KEY, settings);
TRACE("fServiceName is %s\n", fServiceName ? fServiceName : "NULL");
#if DEBUG
if (!fEthernetIfnet)
TRACE("PPPoEDevice::ctor: could not find ethernet interface\n");
else
TRACE("%s::%s: Find Ethernet device", __FILE__, __func__);
#endif
}
status_t
PPPoEDevice::InitCheck() const
{
if (KPPPDevice::InitCheck() != B_OK)
dprintf("%s::%s: KPPPDevice::InitCheck() has error\n", __FILE__, __func__);
return KPPPDevice::InitCheck() == B_OK ? B_OK : B_ERROR;
}
bool
PPPoEDevice::Up()
{
TRACE("PPPoEDevice: Up()\n");
if (InitCheck() != B_OK)
return false;
if (IsUp())
return true;
fEthernetIfnet = FindPPPoEInterface(finterfaceName);
if (fEthernetIfnet == NULL) {
dprintf("%s::%s: fEthernetIfnet %s not found\n", __FILE__, __func__, finterfaceName);
return false;
}
memcpy(fEtherAddr, fEthernetIfnet->address.data, ETHER_ADDRESS_LENGTH);
dprintf("ppp's corresponding addr is %02x:%02x:%02x:%02x:%02x:%02x\n",
fEtherAddr[0], fEtherAddr[1], fEtherAddr[2], fEtherAddr[3],
fEtherAddr[4], fEtherAddr[5]);
if (fEthernetIfnet->module == NULL) {
dprintf("%s::%s: fEthernetIfnet->module not found\n", __FILE__,
__func__);
return false;
}
add_device(this);
fState = INITIAL;
if (fAttempts > PPPoE_MAX_ATTEMPTS) {
fAttempts = 0;
return false;
}
++fAttempts;
memset(fPeer, 0xFF, sizeof(fPeer));
DiscoveryPacket discovery(PADI);
if (ServiceName())
discovery.AddTag(SERVICE_NAME, ServiceName(), strlen(ServiceName()));
else
discovery.AddTag(SERVICE_NAME, NULL, 0);
discovery.AddTag(HOST_UNIQ, &fHostUniq, sizeof(fHostUniq));
discovery.AddTag(END_OF_LIST, NULL, 0);
net_buffer *packet = discovery.ToNetBuffer(MTU());
if (!packet)
return false;
NetBufferPrepend<ether_header> ethernetHeader(packet);
if (ethernetHeader.Status() != B_OK)
return false;
ether_header &header = ethernetHeader.Data();
memset(header.destination, 0xff, ETHER_ADDRESS_LENGTH);
memcpy(header.source, fEtherAddr, ETHER_ADDRESS_LENGTH);
header.type = htons(ETHER_TYPE_PPPOE_DISCOVERY);
ethernetHeader.Sync();
if (fAttempts > 0 && !UpStarted()) {
fAttempts = 0;
remove_device(this);
DownEvent();
return true;
}
fState = PADI_SENT;
status_t status = gStackModule->register_device_handler(fEthernetIfnet,
B_NET_FRAME_TYPE_PPPOE_DISCOVERY, &pppoe_input, NULL);
if (status != B_OK)
return false;
status = gStackModule->register_device_handler(fEthernetIfnet,
B_NET_FRAME_TYPE_PPPOE, &pppoe_input, NULL);
if (status != B_OK)
return false;
if (EthernetIfnet()->module->send_data(EthernetIfnet(), packet) != B_OK) {
fState = INITIAL;
fAttempts = 0;
ERROR("PPPoEDevice::Up(): EthernetIfnet()->output() failed!\n");
return false;
}
dprintf("PPPoEDevice::Up(): EthernetIfnet()->output() success!\n");
fNextTimeout = system_time() + PPPoE_TIMEOUT;
return true;
}
bool
PPPoEDevice::Down()
{
TRACE("PPPoEDevice: Down()\n");
gStackModule->unregister_device_handler(fEthernetIfnet,
B_NET_FRAME_TYPE_PPPOE_DISCOVERY);
gStackModule->unregister_device_handler(fEthernetIfnet,
B_NET_FRAME_TYPE_PPPOE);
remove_device(this);
if (InitCheck() != B_OK)
return false;
fState = INITIAL;
fAttempts = 0;
fNextTimeout = 0;
if (!IsUp()) {
DownEvent();
return true;
}
DownStarted();
DiscoveryPacket discovery(PADT, SessionID());
discovery.AddTag(END_OF_LIST, NULL, 0);
net_buffer *packet = discovery.ToNetBuffer(MTU());
if (!packet) {
ERROR("PPPoEDevice::Down(): ToNetBuffer() failed; MTU=%" B_PRIu32 "\n",
MTU());
DownEvent();
return false;
}
struct ether_header *ethernetHeader;
struct sockaddr destination;
memset(&destination, 0, sizeof(destination));
destination.sa_family = AF_UNSPEC;
ethernetHeader = (struct ether_header*) destination.sa_data;
ethernetHeader->type = ETHER_TYPE_PPPOE_DISCOVERY;
memcpy(ethernetHeader->destination, fPeer, sizeof(fPeer));
memset(fPeer, 0xFF, sizeof(fPeer));
EthernetIfnet()->module->send_data(EthernetIfnet(), packet);
DownEvent();
return true;
}
uint32
PPPoEDevice::InputTransferRate() const
{
return 10000000;
}
uint32
PPPoEDevice::OutputTransferRate() const
{
return 10000000;
}
uint32
PPPoEDevice::CountOutputBytes() const
{
return 0;
}
status_t
PPPoEDevice::Send(net_buffer *packet, uint16 protocolNumber)
{
TRACE("PPPoEDevice: Send()\n");
if (!packet)
return B_ERROR;
else if (InitCheck() != B_OK || protocolNumber != 0) {
ERROR("PPPoEDevice::Send(): InitCheck() != B_OK!\n");
gBufferModule->free(packet);
return B_ERROR;
}
if (!IsUp()) {
ERROR("PPPoEDevice::Send(): no connection!\n");
gBufferModule->free(packet);
return PPP_NO_CONNECTION;
}
uint16 length = packet->size;
NetBufferPrepend<pppoe_header> bufferheader(packet);
if (bufferheader.Status() != B_OK)
return B_ERROR;
pppoe_header &header = bufferheader.Data();
header.version = PPPoE_VERSION;
header.type = PPPoE_TYPE;
header.code = 0x00;
header.sessionID = SessionID();
header.length = htons(length);
bufferheader.Sync();
NetBufferPrepend<ether_header> ethernetHeader(packet);
if (ethernetHeader.Status() != B_OK)
return false;
ether_header ðheader = ethernetHeader.Data();
memcpy(ethheader.destination, fPeer, ETHER_ADDRESS_LENGTH);
memcpy(ethheader.source, fEtherAddr, ETHER_ADDRESS_LENGTH);
ethheader.type = htons(ETHER_TYPE_PPPOE);
ethernetHeader.Sync();
if (!packet)
ERROR("PPPoEDevice::Send(): packet is NULL!\n");
if (EthernetIfnet()->module->send_data(EthernetIfnet(), packet) != B_OK) {
ERROR("PPPoEDevice::Send(): EthernetIfnet()->output() failed!\n");
remove_device(this);
DownEvent();
return PPP_NO_CONNECTION;
}
return B_OK;
}
status_t
PPPoEDevice::Receive(net_buffer *packet, uint16 protocolNumber)
{
TRACE("%s entering %s: protocolNumber:%x\n", __FILE__, __func__, protocolNumber);
if (!packet)
return B_ERROR;
if (InitCheck() != B_OK || IsDown()) {
dprintf("PPPoED InitCheck fail or IsDown\n");
dprintf("PPPoEDevice::Receive fail\n");
return B_ERROR;
}
uint8 ethernetSource[6];
struct sockaddr_dl& source = *(struct sockaddr_dl*)packet->source;
memcpy(ethernetSource, source.sdl_data, sizeof(fPeer));
int32 PPP_Packet_Type = B_NET_FRAME_TYPE(source.sdl_type,
ntohs(source.sdl_e_type));
if (PPP_Packet_Type == B_NET_FRAME_TYPE_PPPOE) {
TRACE("ETHER_TYPE_PPPOE\n");
NetBufferHeaderReader<pppoe_header> bufferheader(packet);
if (bufferheader.Status() != B_OK) {
TRACE("creat NetBufferHeaderReader fail\n");
return B_ERROR;
}
pppoe_header &header = bufferheader.Data();
uint16 ppppoe_payload = ntohs(header.length);
if (!IsUp() || header.version != PPPoE_VERSION || header.type != PPPoE_TYPE
|| header.code != 0x0 || header.sessionID != SessionID()) {
TRACE("basic pppoe header check fail\n");
return B_ERROR;
}
bufferheader.Remove();
bufferheader.Sync();
gBufferModule->trim(packet, ppppoe_payload);
return Interface().ReceiveFromDevice(packet);
}
if (PPP_Packet_Type == B_NET_FRAME_TYPE_PPPOE_DISCOVERY) {
dprintf("ETHER_TYPE_PPPOE_DISCOVERY\n");
NetBufferHeaderReader<pppoe_header> bufferheader(packet);
if (bufferheader.Status() != B_OK) {
dprintf("create NetBufferHeaderReader fail\n");
return B_ERROR;
}
pppoe_header &header = bufferheader.Data();
if (header.version != PPPoE_VERSION || header.type != PPPoE_TYPE) {
dprintf("PPPoEDevice::Receive fail version type wrong\n");
return B_ERROR;
}
if (IsDown()) {
dprintf("PPPoEDevice::Receive fail PPPoEDev IsDown\n");
return B_ERROR;
}
DiscoveryPacket discovery(packet);
switch(discovery.Code()) {
case PADO: {
dprintf("processing PADO\n");
if (fState != PADI_SENT) {
dprintf("PPPoEDevice::Receive faile not PADI_Sent \n");
return B_OK;
}
bool hasServiceName = false, hasACName = false;
pppoe_tag *tag;
DiscoveryPacket reply(PADR);
for (int32 index = 0; index < discovery.CountTags(); index++) {
tag = discovery.TagAt(index);
if (!tag)
continue;
switch (tag->type) {
case SERVICE_NAME:
if (!hasServiceName && (!ServiceName()
|| ((strlen(ServiceName()) == tag->length)
&& !memcmp(tag->data, ServiceName(),
tag->length)))) {
hasServiceName = true;
reply.AddTag(tag->type, tag->data, tag->length);
}
break;
case AC_NAME:
if (!hasACName && (!ACName()
|| ((strlen(ACName()) == tag->length)
&& !memcmp(tag->data, ACName(),
tag->length)))) {
hasACName = true;
reply.AddTag(tag->type, tag->data, tag->length);
}
break;
case AC_COOKIE:
case RELAY_SESSION_ID:
reply.AddTag(tag->type, tag->data, tag->length);
break;
case SERVICE_NAME_ERROR:
case AC_SYSTEM_ERROR:
case GENERIC_ERROR:
dprintf("PPPoEDevice::generic error faile\n");
return B_ERROR;
break;
default:
;
}
}
if (!hasServiceName) {
dprintf("PPPoEDevice::Receive faile no svc name\n");
return B_ERROR;
}
dprintf("reply.AddTag\n");
reply.AddTag(HOST_UNIQ, &fHostUniq, sizeof(fHostUniq));
reply.AddTag(END_OF_LIST, NULL, 0);
net_buffer *replyPacket = reply.ToNetBuffer(MTU());
if (!replyPacket) {
dprintf("PPPoEDevice::Receive fail no reply pack\n");
return B_ERROR;
}
memcpy(fPeer, ethernetSource, ETHER_ADDRESS_LENGTH);
NetBufferPrepend<ether_header> ethernetHeader(replyPacket);
if (ethernetHeader.Status() != B_OK)
return false;
ether_header &header = ethernetHeader.Data();
memcpy(header.destination, fPeer, ETHER_ADDRESS_LENGTH);
memcpy(header.source, fEtherAddr, ETHER_ADDRESS_LENGTH);
header.type=htons(ETHER_TYPE_PPPOE_DISCOVERY);
ethernetHeader.Sync();
fState = PADR_SENT;
if (EthernetIfnet()->module->send_data(EthernetIfnet(), replyPacket) != B_OK) {
gBufferModule->free(replyPacket);
dprintf("PPPoEDevice::Receive fail send PADR\n");
return B_ERROR;
}
fNextTimeout = system_time() + PPPoE_TIMEOUT;
}
break;
case PADS:
dprintf("procesing PADS\n");
if (fState != PADR_SENT
|| memcmp(ethernetSource, fPeer, sizeof(fPeer))) {
dprintf("PPPoEDevice::Receive PADS but no PADR sent\n");
return B_ERROR;
}
fSessionID = header.sessionID;
fState = OPENED;
fNextTimeout = 0;
UpEvent();
break;
case PADT:
dprintf("procesing PADT\n");
if (!IsUp()
|| memcmp(ethernetSource, fPeer, sizeof(fPeer))
|| header.sessionID != SessionID()) {
dprintf("PPPoEDevice::Receive fail not up yet\n");
return B_ERROR;
}
fState = INITIAL;
fAttempts = 0;
fSessionID = 0;
fNextTimeout = 0;
remove_device(this);
DownEvent();
break;
default:
dprintf("PPPoEDevice::Receive fail unknown pppoed code\n");
return B_ERROR;
}
}
dprintf("PPPoEDevice::Receive PADX fine!\n");
return B_OK;
}
void
PPPoEDevice::Pulse()
{
if (fNextTimeout == 0 || IsUp() || IsDown())
return;
if (system_time() >= fNextTimeout) {
if (!Up())
UpFailedEvent();
}
}