Added PPPoE module.

git-svn-id: file:///srv/svn/repos/haiku/trunk/current@5121 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Waldemar Kornewald 2003-10-23 18:01:36 +00:00
parent 4fbbad3009
commit 5a4503c3ff
8 changed files with 982 additions and 0 deletions

View File

@ -1,3 +1,4 @@
SubDir OBOS_TOP src add-ons kernel network ppp ;
SubInclude OBOS_TOP src add-ons kernel network ppp pppoe ;
SubInclude OBOS_TOP src add-ons kernel network ppp shared ;

View File

@ -0,0 +1,152 @@
//----------------------------------------------------------------------
// This software is part of the OpenBeOS distribution and is covered
// by the OpenBeOS license.
//
// Copyright (c) 2003 Waldemar Kornewald, Waldemar.Kornewald@web.de
//---------------------------------------------------------------------
#include "DiscoveryPacket.h"
#include <core_funcs.h>
#include <kernel_cpp.h>
DiscoveryPacket::DiscoveryPacket(uint8 code, uint16 sessionID = 0x0000)
: fCode(code),
fSessionID(sessionID)
{
}
DiscoveryPacket::DiscoveryPacket(struct mbuf *packet)
{
// decode packet
pppoe_header *header = mtod(packet, pppoe_header*);
SetCode(header->code);
if(ntohs(header->length) < 4)
return;
// there are no tags (or one corrupted tag)
int32 position = 0;
pppoe_tag *tag;
while(position <= ntohs(header->length) - 4) {
tag = (pppoe_tag*) (header->data + position);
position += ntohs(tag->length) + 4;
AddTag(ntohs(tag->type), ntohs(tag->length), tag->data);
}
}
DiscoveryPacket::~DiscoveryPacket()
{
for(int32 index = 0; index < CountTags(); index++)
free(TagAt(index));
}
bool
DiscoveryPacket::AddTag(uint16 type, uint16 length, void *data, int32 index = -1)
{
pppoe_tag *add = (pppoe_tag*) malloc(length + 4);
add->type = type;
add->length = length;
memcpy(add->data, data, length);
bool status;
if(index < 0)
status = fTags.AddItem(add);
else
status = fTags.AddItem(add, index);
if(!status) {
free(add);
return false;
}
return true;
}
bool
DiscoveryPacket::RemoveTag(pppoe_tag *tag)
{
if(!fTags.HasItem(tag))
return false;
fTags.RemoveItem(tag);
free(tag);
return true;
}
pppoe_tag*
DiscoveryPacket::TagAt(int32 index) const
{
pppoe_tag *tag = fTags.ItemAt(index);
if(tag == fTags.GetDefaultItem())
return NULL;
return tag;
}
pppoe_tag*
DiscoveryPacket::TagWithType(uint16 type) const
{
pppoe_tag *tag;
for(int32 index = 0; index < CountTags(); index++) {
tag = TagAt(index);
if(tag && tag->type == type)
return tag;
}
return NULL;
}
struct mbuf*
DiscoveryPacket::ToMbuf(uint32 reserve = 0)
{
struct mbuf *packet = m_gethdr(MT_DATA);
packet->m_data += reserve;
pppoe_header *header = mtod(packet, pppoe_header*);
memset(header, 0, sizeof(header));
header->ethernetHeader.ether_type = ETHERTYPE_PPPOEDISC;
header->version = PPPoE_VERSION;
header->type = PPPoE_TYPE;
header->code = Code();
header->sessionID = SessionID();
uint16 length = 0;
pppoe_tag *tag;
for(int32 index = 0; index < CountTags(); index++) {
tag = TagAt(index);
// make sure we have enough space left
if(1494 - length < tag->length) {
m_freem(packet);
return NULL;
}
*((uint16*)(header->data + length)) = htons(tag->type);
length += 2;
*((uint16*)(header->data + length)) = htons(tag->length);
length += 2;
memcpy(header->data + length, tag->data, tag->length);
length += tag->length;
}
header->length = htons(length);
packet->m_len = length;
return packet;
}

View File

@ -0,0 +1,78 @@
//----------------------------------------------------------------------
// This software is part of the OpenBeOS distribution and is covered
// by the OpenBeOS license.
//
// Copyright (c) 2003 Waldemar Kornewald, Waldemar.Kornewald@web.de
//---------------------------------------------------------------------
#ifndef DISCOVERY_PACKET__H
#define DISCOVERY_PACKET__H
#include "PPPoE.h"
#include <List.h>
enum PPPoE_TAG_TYPE {
END_OF_LIST = 0x0000,
SERVICE_NAME = 0x0101,
AC_NAME = 0x0102,
HOST_UNIQ = 0x0103,
AC_COOKIE = 0x0104,
VENDOR_SPECIFIC = 0x0105,
RELAY_SESSION_ID = 0x0110,
SERVICE_NAME_ERROR = 0x0201,
AC_SYSTEM_ERROR = 0x0202,
GENERIC_ERROR = 0x0203
};
enum PPPoE_CODE {
PADI = 0x09,
PADO = 0x07,
PADR = 0x19,
PADS = 0x65,
PADT = 0xA7
};
typedef struct pppoe_tag {
uint16 type;
uint16 length;
uint8 data[0];
};
class DiscoveryPacket {
public:
DiscoveryPacket(uint8 code, uint16 sessionID = 0x0000);
DiscoveryPacket(struct mbuf *packet);
~DiscoveryPacket();
void SetCode(uint8 code)
{ fCode = code; }
uint8 Code() const
{ return fCode; }
void SetSessionID(uint16 sessionID)
{ fSessionID = sessionID; }
uint16 SessionID() const
{ return fSessionID; }
bool AddTag(uint16 type, uint16 length, void *data, int32 index = -1);
bool RemoveTag(pppoe_tag *tag);
int32 CountTags() const
{ return fTags.CountItems(); }
pppoe_tag *TagAt(int32 index) const;
pppoe_tag *TagWithType(uint16 type) const;
struct mbuf *ToMbuf(uint32 reserve = 0);
// the user is responsible for freeing the mbuf
private:
uint8 fCode;
uint16 fSessionID;
List<pppoe_tag*> fTags;
};
#endif

View File

@ -0,0 +1,22 @@
SubDir OBOS_TOP src add-ons kernel network ppp pppoe ;
# for kernel_cpp.cpp/h and BLocker
UsePrivateHeaders net ;
UsePrivateHeaders [ FDirName kernel ] ;
UsePrivateHeaders [ FDirName kernel util ] ;
UseHeaders [ FDirName $(OBOS_TOP) src add-ons kernel network ppp shared libkernelppp headers ] ;
UseHeaders [ FDirName $(OBOS_TOP) src add-ons kernel network ppp shared libkernelppp ] ;
R5KernelAddon pppoe : kernel network ppp :
pppoe.cpp
PPPoEDevice.cpp
DiscoveryPacket.cpp
;
LinkSharedOSLibs pppoe : libkernelppp.a ;
# Installation
OBOSInstall install-networking
: /boot/home/config/add-ons/kernel/network/ppp
: pppoe ;

View File

@ -0,0 +1,44 @@
//----------------------------------------------------------------------
// This software is part of the OpenBeOS distribution and is covered
// by the OpenBeOS license.
//
// Copyright (c) 2003 Waldemar Kornewald, Waldemar.Kornewald@web.de
//---------------------------------------------------------------------
#ifndef PPPoE__H
#define PPPoE__H
#include <SupportDefs.h>
#include <net/if.h>
#include <netinet/if_ether.h>
#define PPPoE_HEADER_SIZE 14
// including ethernet header
#define PPPoE_TIMEOUT 3000000
// 3 seconds
#define PPPoE_VERSION 0x1
#define PPPoE_TYPE 0x1
#define PPPoE_INTERFACE_KEY "interface"
extern struct core_module_info *core;
typedef struct pppoe_header {
struct ether_header ethernetHeader;
uint8 version : 4;
uint8 type : 4;
uint8 code;
uint16 sessionID;
uint16 length;
uint8 data[0];
} pppoe_header _PACKED;
uint32 NewHostUniq();
// defined in pppoe.cpp
#endif

View File

@ -0,0 +1,443 @@
//----------------------------------------------------------------------
// This software is part of the OpenBeOS distribution and is covered
// by the OpenBeOS license.
//
// Copyright (c) 2003 Waldemar Kornewald, Waldemar.Kornewald@web.de
//---------------------------------------------------------------------
#include "PPPoEDevice.h"
#include "DiscoveryPacket.h"
#include <core_funcs.h>
#include <cstdlib>
#include <kernel_cpp.h>
// from libkernelppp
#include <settings_tools.h>
#include <LockerHelper.h>
#ifdef _KERNEL_MODE
#define spawn_thread spawn_kernel_thread
#define printf dprintf
#endif
PPPoEDevice::PPPoEDevice(PPPInterface& interface, driver_parameter *settings)
: PPPDevice("PPPoE", interface, settings),
fEthernetIfnet(NULL),
fSessionID(0),
fHostUniq(NewHostUniq()),
fACName(NULL),
fServiceName(NULL),
fNextTimeout(0),
fState(INITIAL)
{
#if DEBUG
printf("PPPoEDevice: Constructor\n");
if(!settings || !settings->parameters)
printf("PPPoEDevice::ctor: No settings!\n");
else if(settings->parameter_count > 0 && settings->parameters[0].value_count > 0)
printf("PPPoEDevice::ctor: Value0: %s\n", settings->parameters[0].values[0]);
else
printf("PPPoEDevice::ctor: No values!\n");
#endif
memset(fPeer, 0xFF, sizeof(fPeer));
SetMTU(1494);
// MTU size does not contain PPP header
// find ethernet device
const char *interfaceName = get_parameter_value(PPPoE_INTERFACE_KEY, settings);
if(!interfaceName)
return;
#if DEBUG
printf("PPPoEDevice::ctor: interfaceName: %s\n", interfaceName);
#endif
ifnet *current = get_interfaces();
for(; current; current = current->if_next) {
if(current->if_name && !strcmp(current->if_name, interfaceName)) {
#if DEBUG
printf("PPPoEDevice::ctor: found ethernet interface\n");
#endif
fEthernetIfnet = current;
break;
}
}
#if DEBUG
if(!fEthernetIfnet)
printf("PPPoEDevice::ctor: could not find ethernet interface\n");
#endif
}
PPPoEDevice::~PPPoEDevice()
{
#if DEBUG
printf("PPPoEDevice: Destructor\n");
#endif
free(fACName);
free(fServiceName);
}
status_t
PPPoEDevice::InitCheck() const
{
return EthernetIfnet() && EthernetIfnet()->output
&& PPPDevice::InitCheck() == B_OK ? B_OK : B_ERROR;
}
bool
PPPoEDevice::Up()
{
#if DEBUG
printf("PPPoEDevice: Up()\n");
#endif
if(InitCheck() != B_OK)
return false;
LockerHelper locker(fLock);
if(IsUp())
return true;
// reset connection settings
memset(fPeer, 0xFF, sizeof(fPeer));
// create PADI
DiscoveryPacket discovery(PADI);
if(fServiceName)
discovery.AddTag(SERVICE_NAME, strlen(fServiceName), fServiceName);
else
discovery.AddTag(SERVICE_NAME, 0, NULL);
discovery.AddTag(HOST_UNIQ, sizeof(uint32), &fHostUniq);
discovery.AddTag(END_OF_LIST, 0, NULL);
// set up PPP header
struct mbuf *packet = discovery.ToMbuf();
if(!packet)
return false;
// create destination
struct sockaddr destination;
memset(&destination, 0, sizeof(destination));
destination.sa_family = AF_UNSPEC;
// raw packet with ethernet header
memcpy(destination.sa_data, fPeer, sizeof(fPeer));
if(EthernetIfnet()->output(EthernetIfnet(), packet, &destination, NULL) != B_OK)
return false;
// check if we are allowed to go up now (user intervention might disallow that)
if(!UpStarted()) {
fState = INITIAL;
// reset state
DownEvent();
return true;
}
fState = PADI_SENT;
fNextTimeout = system_time() + PPPoE_TIMEOUT;
return true;
}
bool
PPPoEDevice::Down()
{
#if DEBUG
printf("PPPoEDevice: Down()\n");
#endif
if(InitCheck() != B_OK)
return false;
LockerHelper locker(fLock);
fNextTimeout = 0;
// disable timeouts
DownStarted();
// this tells StateMachine that DownEvent() does not mean we lost connection
if(!IsUp()) {
DownEvent();
return true;
}
// create PADT
DiscoveryPacket discovery(PADT, SessionID());
discovery.AddTag(END_OF_LIST, 0, NULL);
struct mbuf *packet = discovery.ToMbuf();
if(!packet) {
DownEvent();
return false;
}
// create destination
struct sockaddr destination;
memset(&destination, 0, sizeof(destination));
destination.sa_family = AF_UNSPEC;
// raw packet with ethernet header
memcpy(destination.sa_data, fPeer, sizeof(fPeer));
// reset connection settings
memset(fPeer, 0xFF, sizeof(fPeer));
EthernetIfnet()->output(EthernetIfnet(), packet, &destination, NULL);
DownEvent();
return false;
}
uint32
PPPoEDevice::InputTransferRate() const
{
return 10000000;
}
uint32
PPPoEDevice::OutputTransferRate() const
{
return 10000000;
}
uint32
PPPoEDevice::CountOutputBytes() const
{
// TODO:
// ?look through ethernet queue for outgoing pppoe packets coming from our device?
return 0;
}
status_t
PPPoEDevice::Send(struct mbuf *packet, uint16 protocolNumber = 0)
{
#if DEBUG
printf("PPPoEDevice: Send()\n");
#endif
if(InitCheck() != B_OK || protocolNumber != 0) {
m_freem(packet);
return B_ERROR;
} else if(!packet)
return B_ERROR;
LockerHelper locker(fLock);
if(!IsUp())
return PPP_NO_CONNECTION;
uint16 length = packet->m_flags & M_PKTHDR ? packet->m_pkthdr.len : packet->m_len;
// encapsulate packet into pppoe header
M_PREPEND(packet, PPPoE_HEADER_SIZE);
pppoe_header *header = mtod(packet, pppoe_header*);
header->ethernetHeader.ether_type = ETHERTYPE_PPPOE;
header->version = PPPoE_VERSION;
header->type = PPPoE_TYPE;
header->code = 0x00;
header->sessionID = SessionID();
header->length = htons(length);
// create destination
struct sockaddr destination;
memset(&destination, 0, sizeof(destination));
destination.sa_family = AF_UNSPEC;
// raw packet with ethernet header
memcpy(destination.sa_data, fPeer, sizeof(fPeer));
locker.UnlockNow();
if(EthernetIfnet()->output(EthernetIfnet(), packet, &destination, NULL) != B_OK) {
printf("PPPoEDevice::Send(): EthernetIfnet()->output() failed!\n");
DownEvent();
// DownEvent() without DownStarted() indicates connection lost
return PPP_NO_CONNECTION;
}
return B_OK;
}
status_t
PPPoEDevice::Receive(struct mbuf *packet, uint16 protocolNumber = 0)
{
#if DEBUG
printf("PPPoEDevice: Receive()\n");
#endif
if(InitCheck() != B_OK || IsDown()) {
m_freem(packet);
return B_ERROR;
} else if(!packet)
return B_ERROR;
pppoe_header *header = mtod(packet, pppoe_header*);
if(!header) {
m_freem(packet);
return B_ERROR;
}
status_t result = B_OK;
if(header->ethernetHeader.ether_type == ETHERTYPE_PPPOE) {
if(!IsUp() || header->version != PPPoE_VERSION || header->type != PPPoE_TYPE
|| header->code != 0x0 || header->sessionID != SessionID()) {
m_freem(packet);
return B_ERROR;
}
m_adj(packet, PPPoE_HEADER_SIZE);
return Interface().ReceiveFromDevice(packet);
} else if(header->ethernetHeader.ether_type == ETHERTYPE_PPPOEDISC) {
// we do not need to check HOST_UNIQ tag as this is done in pppoe.cpp
if(header->version != PPPoE_VERSION || header->type != PPPoE_TYPE) {
m_freem(packet);
return B_ERROR;
}
LockerHelper locker(fLock);
if(IsDown()) {
m_freem(packet);
return B_ERROR;
}
DiscoveryPacket discovery(packet);
switch(discovery.Code()) {
case PADO: {
if(fState != PADI_SENT) {
m_freem(packet);
return B_OK;
}
bool hasServiceName = false;
pppoe_tag *tag;
DiscoveryPacket reply(PADR);
for(int32 index = 0; index < discovery.CountTags(); index++) {
tag = discovery.TagAt(index);
switch(tag->type) {
case SERVICE_NAME:
if(!hasServiceName && (!fServiceName
|| !memcmp(tag->data, fServiceName, tag->length))) {
hasServiceName = true;
reply.AddTag(tag->type, tag->length, tag->data);
}
break;
case AC_COOKIE:
case RELAY_SESSION_ID:
reply.AddTag(tag->type, tag->length, tag->data);
break;
case SERVICE_NAME_ERROR:
case AC_SYSTEM_ERROR:
case GENERIC_ERROR:
m_freem(packet);
return B_ERROR;
break;
default:
;
}
}
if(!hasServiceName) {
m_freem(packet);
return B_ERROR;
}
reply.AddTag(END_OF_LIST, 0, NULL);
struct mbuf *replyPacket = reply.ToMbuf();
if(!replyPacket) {
m_freem(packet);
return B_ERROR;
}
memcpy(fPeer, header->ethernetHeader.ether_shost, sizeof(fPeer));
// create destination
struct sockaddr destination;
memset(&destination, 0, sizeof(destination));
destination.sa_family = AF_UNSPEC;
// raw packet with ethernet header
memcpy(destination.sa_data, fPeer, sizeof(fPeer));
if(EthernetIfnet()->output(EthernetIfnet(), replyPacket, &destination,
NULL) != B_OK) {
m_freem(packet);
return B_ERROR;
}
fState = PADR_SENT;
fNextTimeout = system_time() + PPPoE_TIMEOUT;
} break;
case PADS:
if(fState != PADR_SENT
|| memcmp(header->ethernetHeader.ether_shost, fPeer,
sizeof(fPeer))) {
m_freem(packet);
return B_ERROR;
}
fSessionID = header->sessionID;
fState = OPENED;
fNextTimeout = 0;
UpEvent();
break;
case PADT:
if(!IsUp()
|| memcmp(header->ethernetHeader.ether_shost, fPeer,
sizeof(fPeer))
|| header->sessionID != SessionID()) {
m_freem(packet);
return B_ERROR;
}
fState = INITIAL;
fSessionID = 0;
fNextTimeout = 0;
DownEvent();
break;
default:
m_freem(packet);
return B_ERROR;
}
} else
result = B_ERROR;
m_freem(packet);
return result;
}
void
PPPoEDevice::Pulse()
{
// We use Pulse() for timeout of connection establishment.
if(fNextTimeout == 0 || IsUp() || IsDown())
return;
LockerHelper locker(fLock);
// check if timed out
if(system_time() >= fNextTimeout)
Up();
}

View File

@ -0,0 +1,75 @@
//----------------------------------------------------------------------
// This software is part of the OpenBeOS distribution and is covered
// by the OpenBeOS license.
//
// Copyright (c) 2003 Waldemar Kornewald, Waldemar.Kornewald@web.de
//---------------------------------------------------------------------
#ifndef PPPoE_DEVICE__H
#define PPPoE_DEVICE__H
#include "PPPoE.h"
#include <KPPPDevice.h>
enum pppoe_state {
INITIAL,
// the same as IsDown()
PADI_SENT,
PADR_SENT,
OPENED
// the same as IsUp()
};
class PPPoEDevice : public PPPDevice {
public:
PPPoEDevice(PPPInterface& interface, driver_parameter *settings);
virtual ~PPPoEDevice();
ifnet *EthernetIfnet() const
{ return fEthernetIfnet; }
const uint8 *Peer() const
{ return fPeer; }
uint16 SessionID() const
{ return fSessionID; }
uint32 HostUniq() const
{ return fHostUniq; }
const char *ACName() const
{ return fACName; }
const char *ServiceName() const
{ return fServiceName; }
virtual status_t InitCheck() const;
virtual bool Up();
virtual bool Down();
virtual uint32 InputTransferRate() const;
virtual uint32 OutputTransferRate() const;
virtual uint32 CountOutputBytes() const;
virtual status_t Send(struct mbuf *packet, uint16 protocolNumber = 0);
virtual status_t Receive(struct mbuf *packet, uint16 protocolNumber = 0);
virtual void Pulse();
private:
ifnet *fEthernetIfnet;
uint8 fPeer[6];
uint16 fSessionID;
uint32 fHostUniq;
char *fACName, *fServiceName;
bigtime_t fNextTimeout;
pppoe_state fState;
BLocker fLock;
};
#endif

View File

@ -0,0 +1,167 @@
//----------------------------------------------------------------------
// This software is part of the OpenBeOS distribution and is covered
// by the OpenBeOS license.
//
// Copyright (c) 2003 Waldemar Kornewald, Waldemar.Kornewald@web.de
//---------------------------------------------------------------------
#include <core_funcs.h>
#include <KernelExport.h>
#include <driver_settings.h>
#include <kernel_cpp.h>
#include <ethernet_module.h>
#include <KPPPInterface.h>
#include <KPPPModule.h>
#include <LockerHelper.h>
#include "PPPoEDevice.h"
#include "DiscoveryPacket.h"
#ifdef _KERNEL_MODE
#define spawn_thread spawn_kernel_thread
#define printf dprintf
#endif
#define PPPoE_MODULE_NAME "network/ppp/pppoe"
struct core_module_info *core = NULL;
static struct ethernet_module_info *ethernet;
static int32 host_uniq = 0;
status_t std_ops(int32 op, ...);
static BLocker lock;
static List<PPPoEDevice*> devices;
uint32
NewHostUniq()
{
return (uint32) atomic_add(&host_uniq, 1);
}
static
void
pppoe_input(struct mbuf *packet)
{
if(!packet)
return;
ifnet *sourceIfnet = packet->m_pkthdr.rcvif;
pppoe_header *header = mtod(packet, pppoe_header*);
PPPoEDevice *device;
LockerHelper locker(lock);
for(int32 index = 0; index < devices.CountItems(); index++) {
device = devices.ItemAt(index);
if(device && device->EthernetIfnet() == sourceIfnet) {
if(header->ethernetHeader.ether_type == ETHERTYPE_PPPOE
&& header->sessionID == device->SessionID()) {
device->Receive(packet);
return;
} else if(header->ethernetHeader.ether_type == ETHERTYPE_PPPOE
&& header->code != PADI && header->code != PADR
&& !device->IsDown()) {
DiscoveryPacket discovery(packet);
pppoe_tag *tag = discovery.TagWithType(HOST_UNIQ);
if(tag && tag->length == 4
&& *((uint32*)tag->data) == device->HostUniq()) {
device->Receive(packet);
return;
}
}
}
}
printf("PPPoE: No device found for packet from: %s\n", sourceIfnet->if_name);
m_freem(packet);
}
static
bool
add_to(PPPInterface& mainInterface, PPPInterface *subInterface,
driver_parameter *settings, ppp_module_key_type type)
{
if(type != PPP_DEVICE_KEY_TYPE)
return B_ERROR;
PPPoEDevice *device;
bool success;
if(subInterface) {
device = new PPPoEDevice(*subInterface, settings);
success = subInterface->SetDevice(device);
} else
device = new PPPoEDevice(mainInterface, settings); {
success = mainInterface.SetDevice(device);
}
#if DEBUG
printf("PPPoE: add_to(): %s\n", success && device && device->InitCheck() == B_OK ? "OK" : "ERROR");
#endif
return success && device && device->InitCheck() == B_OK;
}
static ppp_module_info pppoe_module = {
{
PPPoE_MODULE_NAME,
0,
std_ops
},
NULL,
add_to
};
_EXPORT
status_t
std_ops(int32 op, ...)
{
switch(op) {
case B_MODULE_INIT:
if(get_module(NET_CORE_MODULE_NAME, (module_info**)&core) != B_OK)
return B_ERROR;
if(get_module(NET_ETHERNET_MODULE_NAME, (module_info**)&ethernet) != B_OK) {
put_module(NET_CORE_MODULE_NAME);
return B_ERROR;
}
ethernet->set_pppoe_receiver(pppoe_input);
#if DEBUG
printf("PPPoE: Registered PPPoE receiver.\n");
#endif
return B_OK;
case B_MODULE_UNINIT:
ethernet->unset_pppoe_receiver();
#if DEBUG
printf("PPPoE: Unregistered PPPoE receiver.\n");
#endif
put_module(NET_CORE_MODULE_NAME);
put_module(NET_ETHERNET_MODULE_NAME);
break;
default:
return B_ERROR;
}
return B_OK;
}
_EXPORT
module_info *modules[] = {
(module_info*) &pppoe_module,
NULL
};