#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/route.h>
#include <new>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sockio.h>
#include <KernelExport.h>
#include <net_datalink.h>
#include <net_device.h>
#include <NetUtilities.h>
#include "device_interfaces.h"
#include "domains.h"
#include "interfaces.h"
#include "routes.h"
#include "stack_private.h"
#include "utility.h"
#ifdef TRACE_DATALINK
# define TRACE(x...) dprintf(STACK_DEBUG_PREFIX x)
#else
# define TRACE(x...) ;
#endif
struct datalink_protocol : net_protocol {
struct net_domain_private* domain;
};
struct interface_protocol : net_datalink_protocol {
struct net_device_module_info* device_module;
struct net_device* device;
};
#ifdef TRACE_DATALINK
static const char*
option_to_string(int32 option)
{
# define CODE(x) case x: return #x;
switch (option) {
CODE(SIOCADDRT)
CODE(SIOCDELRT)
CODE(SIOCSIFADDR)
CODE(SIOCGIFADDR)
CODE(SIOCSIFDSTADDR)
CODE(SIOCGIFDSTADDR)
CODE(SIOCSIFFLAGS)
CODE(SIOCGIFFLAGS)
CODE(SIOCGIFBRDADDR)
CODE(SIOCSIFBRDADDR)
CODE(SIOCGIFCOUNT)
CODE(SIOCGIFCONF)
CODE(SIOCGIFINDEX)
CODE(SIOCGIFNAME)
CODE(SIOCGIFNETMASK)
CODE(SIOCSIFNETMASK)
CODE(SIOCGIFMETRIC)
CODE(SIOCSIFMETRIC)
CODE(SIOCDIFADDR)
CODE(SIOCAIFADDR)
CODE(SIOCADDMULTI)
CODE(SIOCDELMULTI)
CODE(SIOCGIFMTU)
CODE(SIOCSIFMTU)
CODE(SIOCSIFMEDIA)
CODE(SIOCGIFMEDIA)
CODE(SIOCGRTSIZE)
CODE(SIOCGRTTABLE)
CODE(SIOCGETRT)
CODE(SIOCGIFSTATS)
CODE(SIOCGIFTYPE)
CODE(SIOCSPACKETCAP)
CODE(SIOCCPACKETCAP)
CODE(SIOCSHIWAT)
CODE(SIOCGHIWAT)
CODE(SIOCSLOWAT)
CODE(SIOCGLOWAT)
CODE(SIOCATMARK)
CODE(SIOCSPGRP)
CODE(SIOCGPGRP)
CODE(SIOCGPRIVATE_0)
CODE(SIOCGPRIVATE_1)
CODE(SIOCSDRVSPEC)
CODE(SIOCGDRVSPEC)
CODE(SIOCSIFGENERIC)
CODE(SIOCGIFGENERIC)
CODE(B_SOCKET_SET_ALIAS)
CODE(B_SOCKET_GET_ALIAS)
CODE(B_SOCKET_COUNT_ALIASES)
default:
static char buffer[24];
snprintf(buffer, sizeof(buffer), "%" B_PRId32, option);
return buffer;
}
# undef CODE
}
#endif
static status_t
get_interface_name_or_index(net_domain* domain, int32 option, void* value,
size_t* _length)
{
ASSERT(option == SIOCGIFINDEX || option == SIOCGIFNAME);
size_t expected = option == SIOCGIFINDEX ? IF_NAMESIZE : sizeof(ifreq);
if (*_length > 0 && *_length < expected)
return B_BAD_VALUE;
ifreq request;
memset(&request, 0, sizeof(request));
if (user_memcpy(&request, value, expected) < B_OK)
return B_BAD_ADDRESS;
Interface* interface = NULL;
if (option == SIOCGIFINDEX)
interface = get_interface(domain, request.ifr_name);
else
interface = get_interface(domain, request.ifr_index);
if (interface == NULL)
return B_BAD_VALUE;
if (option == SIOCGIFINDEX)
request.ifr_index = interface->index;
else
strlcpy(request.ifr_name, interface->name, IF_NAMESIZE);
*_length = sizeof(ifreq);
interface->ReleaseReference();
return user_memcpy(value, &request, sizeof(ifreq));
}
static void
set_interface_address(net_interface_address*& target, InterfaceAddress* address)
{
if (target != NULL)
static_cast<InterfaceAddress*>(target)->ReleaseReference();
target = address;
}
static status_t
fill_address(const sockaddr* from, sockaddr* to, size_t maxLength)
{
if (from != NULL) {
return user_memcpy(to, from, min_c(from->sa_len, maxLength));
}
sockaddr empty;
empty.sa_len = 2;
empty.sa_family = AF_UNSPEC;
return user_memcpy(to, &empty, min_c(2, maxLength));
}
static void
update_device_send_stats(struct net_device* device, status_t status, size_t packetSize)
{
if (status == B_OK) {
atomic_add((int32*)&device->stats.send.packets, 1);
atomic_add64((int64*)&device->stats.send.bytes, packetSize);
} else {
atomic_add((int32*)&device->stats.send.errors, 1);
}
}
static status_t
datalink_control(net_domain* _domain, int32 option, void* value,
size_t* _length)
{
TRACE("%s(domain %p, option %s, value %p, length %zu)\n", __FUNCTION__,
_domain, option_to_string(option), value, *_length);
net_domain_private* domain = (net_domain_private*)_domain;
if (domain == NULL || domain->family == AF_LINK) {
return B_BAD_VALUE;
}
switch (option) {
case SIOCGIFINDEX:
case SIOCGIFNAME:
return get_interface_name_or_index(domain, option, value, _length);
case SIOCAIFADDR:
{
if (*_length > 0 && *_length < sizeof(struct ifaliasreq))
return B_BAD_VALUE;
struct ifaliasreq request;
if (user_memcpy(&request, value, sizeof(struct ifaliasreq)) != B_OK)
return B_BAD_ADDRESS;
Interface* interface = get_interface(domain, request.ifra_name);
if (interface != NULL) {
status_t status = add_interface_address(interface, domain,
request);
notify_interface_changed(interface);
interface->ReleaseReference();
return status;
}
net_device_interface* deviceInterface
= get_device_interface(request.ifra_name);
if (deviceInterface == NULL)
return B_DEVICE_NOT_FOUND;
status_t status = add_interface(request.ifra_name, domain, request,
deviceInterface);
put_device_interface(deviceInterface);
return status;
}
case SIOCDIFADDR:
{
struct ifreq request;
if (user_memcpy(&request, value, sizeof(struct ifreq)) != B_OK)
return B_BAD_ADDRESS;
Interface* interface = get_interface(domain, request.ifr_name);
if (interface == NULL)
return B_BAD_VALUE;
status_t status = B_OK;
if (request.ifr_addr.sa_family != AF_UNSPEC
&& request.ifr_addr.sa_len != 0) {
status = interface->Control(domain, SIOCDIFADDR, request,
(ifreq*)value, *_length);
} else
remove_interface(interface);
interface->ReleaseReference();
return status;
}
case SIOCGIFCOUNT:
{
struct ifconf config;
config.ifc_value = count_interfaces();
return user_memcpy(value, &config, sizeof(struct ifconf));
}
case SIOCGIFCONF:
{
struct ifconf config;
if (user_memcpy(&config, value, sizeof(struct ifconf)) < B_OK)
return B_BAD_ADDRESS;
size_t size = config.ifc_len;
status_t status
= list_interfaces(domain->family, config.ifc_buf, &size);
if (status != B_OK)
return status;
config.ifc_len = (int)size;
return user_memcpy(value, &config, sizeof(struct ifconf));
}
case SIOCGRTSIZE:
{
struct ifconf config;
config.ifc_value = route_table_size(domain);
return user_memcpy(value, &config, sizeof(struct ifconf));
}
case SIOCGRTTABLE:
{
struct ifconf config;
if (user_memcpy(&config, value, sizeof(struct ifconf)) < B_OK)
return B_BAD_ADDRESS;
return list_routes(domain, config.ifc_buf, config.ifc_len);
}
case SIOCGETRT:
return get_route_information(domain, value, *_length);
default:
{
size_t length = sizeof(struct ifreq);
if (*_length > 0 && *_length >= IF_NAMESIZE)
length = min_c(length, *_length);
struct ifreq request;
if (user_memcpy(&request, value, length) != B_OK)
return B_BAD_ADDRESS;
Interface* interface = get_interface(domain, request.ifr_name);
if (interface == NULL)
return B_BAD_VALUE;
status_t status = interface->Control(domain, option, request,
(ifreq*)value, *_length);
interface->ReleaseReference();
return status;
}
}
return B_BAD_VALUE;
}
static status_t
datalink_send_routed_data(struct net_route* route, net_buffer* buffer)
{
TRACE("%s(route %p, buffer %p)\n", __FUNCTION__, route, buffer);
InterfaceAddress* address = (InterfaceAddress*)route->interface_address;
Interface* interface = (Interface*)address->interface;
if ((route->flags & RTF_REJECT) != 0) {
TRACE(" rejected route\n");
return ENETUNREACH;
}
if ((route->flags & RTF_HOST) != 0) {
TRACE(" host route\n");
address->AcquireReference();
set_interface_address(buffer->interface_address, address);
}
if ((route->flags & RTF_LOCAL) != 0) {
TRACE(" local route\n");
address->AcquireReference();
set_interface_address(buffer->interface_address, address);
if (atomic_get(&interface->DeviceInterface()->monitor_count) > 0)
device_interface_monitor_receive(interface->DeviceInterface(), buffer);
const size_t packetSize = buffer->size;
status_t status = fifo_enqueue_buffer(
&interface->DeviceInterface()->receive_queue, buffer);
update_device_send_stats(interface->DeviceInterface()->device,
status, packetSize);
return status;
}
if ((route->flags & RTF_GATEWAY) != 0) {
TRACE(" gateway route\n");
if (route->gateway == NULL)
return B_MISMATCHED_VALUES;
memcpy(buffer->destination, route->gateway, route->gateway->sa_len);
}
domain_datalink* datalink
= interface->DomainDatalink(address->domain->family);
return datalink->first_info->send_data(datalink->first_protocol, buffer);
}
static status_t
datalink_send_data(net_protocol* protocol, net_domain* domain,
net_buffer* buffer)
{
TRACE("%s(%p, domain %p, buffer %p)\n", __FUNCTION__, protocol, domain,
buffer);
if (protocol == NULL && domain == NULL)
return B_BAD_VALUE;
net_protocol_module_info* module = protocol != NULL
? protocol->module : domain->module;
if (domain == NULL)
domain = protocol->module->get_domain(protocol);
net_route* route = NULL;
status_t status;
if (protocol != NULL && protocol->socket != NULL
&& protocol->socket->bound_to_device != 0) {
status = get_device_route(domain, protocol->socket->bound_to_device,
&route);
} else
status = get_buffer_route(domain, buffer, &route);
TRACE(" route status: %s\n", strerror(status));
if (status != B_OK)
return status;
status = module->send_routed_data(protocol, route, buffer);
put_route(domain, route);
return status;
}
static bool
datalink_is_local_address(net_domain* domain, const struct sockaddr* address,
net_interface_address** _interfaceAddress, uint32* _matchedType)
{
TRACE("%s(domain %p, address %s)\n", __FUNCTION__, domain,
AddressString(domain, address).Data());
if (domain == NULL || address == NULL
|| domain->family != address->sa_family)
return false;
uint32 matchedType = 0;
InterfaceAddress* interfaceAddress = get_interface_address(address);
if (interfaceAddress == NULL) {
if ((domain->address_module->flags
& NET_ADDRESS_MODULE_FLAG_BROADCAST_ADDRESS) != 0) {
interfaceAddress
= get_interface_address_for_destination(domain, address);
matchedType = MSG_BCAST;
}
if (interfaceAddress == NULL) {
TRACE(" no\n");
return false;
}
}
TRACE(" it is, interface address %p\n", interfaceAddress);
if (_interfaceAddress != NULL)
set_interface_address(*_interfaceAddress, interfaceAddress);
else
interfaceAddress->ReleaseReference();
if (_matchedType != NULL)
*_matchedType = matchedType;
return true;
}
static bool
datalink_is_local_link_address(net_domain* domain, bool unconfiguredOnly,
const struct sockaddr* address, net_interface_address** _interfaceAddress)
{
if (domain == NULL || address == NULL || address->sa_family != AF_LINK)
return false;
#ifdef TRACE_DATALINK
uint8* data = LLADDR((sockaddr_dl*)address);
TRACE("%s(domain %p, unconfiguredOnly %d, address %02x:%02x:%02x:%02x:%02x"
":%02x)\n", __FUNCTION__, domain, unconfiguredOnly, data[0], data[1],
data[2], data[3], data[4], data[5]);
#endif
InterfaceAddress* interfaceAddress = get_interface_address_for_link(domain,
address, unconfiguredOnly);
if (interfaceAddress == NULL) {
TRACE(" no\n");
return false;
}
if (_interfaceAddress != NULL)
set_interface_address(*_interfaceAddress, interfaceAddress);
else
interfaceAddress->ReleaseReference();
return true;
}
static net_interface*
datalink_get_interface(net_domain* domain, uint32 index)
{
return get_interface(domain, index);
}
static net_interface*
datalink_get_interface_with_address(const sockaddr* address)
{
InterfaceAddress* interfaceAddress = get_interface_address(address);
if (interfaceAddress == NULL)
return NULL;
Interface* interface = static_cast<Interface*>(interfaceAddress->interface);
interface->AcquireReference();
interfaceAddress->ReleaseReference();
return interface;
}
static void
datalink_put_interface(net_interface* interface)
{
if (interface == NULL)
return;
((Interface*)interface)->ReleaseReference();
}
static net_interface_address*
datalink_get_interface_address(const struct sockaddr* address)
{
return get_interface_address(address);
}
static bool
datalink_get_next_interface_address(net_interface* _interface,
net_interface_address** _address)
{
Interface* interface = (Interface*)_interface;
InterfaceAddress* address = (InterfaceAddress*)*_address;
bool gotOne = interface->GetNextAddress(&address);
*_address = address;
return gotOne;
}
static void
datalink_put_interface_address(net_interface_address* address)
{
if (address == NULL)
return;
((InterfaceAddress*)address)->ReleaseReference();
}
static status_t
datalink_join_multicast(net_interface* _interface, net_domain* domain,
const struct sockaddr* address)
{
Interface* interface = (Interface*)_interface;
domain_datalink* datalink = interface->DomainDatalink(domain->family);
return datalink->first_info->join_multicast(datalink->first_protocol,
address);
}
static status_t
datalink_leave_multicast(net_interface* _interface, net_domain* domain,
const struct sockaddr* address)
{
Interface* interface = (Interface*)_interface;
domain_datalink* datalink = interface->DomainDatalink(domain->family);
return datalink->first_info->leave_multicast(datalink->first_protocol,
address);
}
static status_t
datalink_std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
case B_MODULE_UNINIT:
return B_OK;
default:
return B_ERROR;
}
}
static status_t
interface_protocol_init(net_interface* interface, net_domain* domain,
net_datalink_protocol** _protocol)
{
interface_protocol* protocol = new(std::nothrow) interface_protocol;
if (protocol == NULL)
return B_NO_MEMORY;
TRACE("%s(%p, interface %p - %s, domain %p)\n", __FUNCTION__, protocol,
interface, interface->name, domain);
protocol->device_module = interface->device->module;
protocol->device = interface->device;
*_protocol = protocol;
return B_OK;
}
static status_t
interface_protocol_uninit(net_datalink_protocol* protocol)
{
TRACE("%s(%p)\n", __FUNCTION__, protocol);
delete protocol;
return B_OK;
}
static status_t
interface_protocol_send_data(net_datalink_protocol* _protocol,
net_buffer* buffer)
{
TRACE("%s(%p, buffer %p)\n", __FUNCTION__, _protocol, buffer);
interface_protocol* protocol = (interface_protocol*)_protocol;
Interface* interface = (Interface*)protocol->interface;
if (atomic_get(&interface->DeviceInterface()->monitor_count) > 0)
device_interface_monitor_receive(interface->DeviceInterface(), buffer);
const size_t packetSize = buffer->size;
status_t status = protocol->device_module->send_data(protocol->device, buffer);
update_device_send_stats(protocol->device, status, packetSize);
return status;
}
static status_t
interface_protocol_up(net_datalink_protocol* protocol)
{
TRACE("%s(%p)\n", __FUNCTION__, protocol);
return B_OK;
}
static void
interface_protocol_down(net_datalink_protocol* _protocol)
{
TRACE("%s(%p)\n", __FUNCTION__, _protocol);
interface_protocol* protocol = (interface_protocol*)_protocol;
Interface* interface = (Interface*)protocol->interface;
net_device_interface* deviceInterface = interface->DeviceInterface();
if (deviceInterface->up_count == 0)
return;
deviceInterface->up_count--;
interface->WentDown();
if (deviceInterface->up_count > 0)
return;
down_device_interface(deviceInterface);
}
static status_t
interface_protocol_change_address(net_datalink_protocol* protocol,
net_interface_address* interfaceAddress, int32 option,
const struct sockaddr* oldAddress, const struct sockaddr* newAddress)
{
TRACE("%s(%p, interface address %p, option %s, old %p, new %p)\n",
__FUNCTION__, protocol, interfaceAddress, option_to_string(option),
oldAddress, newAddress);
switch (option) {
case SIOCSIFADDR:
case SIOCSIFNETMASK:
case SIOCSIFBRDADDR:
case SIOCSIFDSTADDR:
case SIOCDIFADDR:
return update_interface_address((InterfaceAddress*)interfaceAddress,
option, oldAddress, newAddress);
}
return B_OK;
}
static status_t
interface_protocol_control(net_datalink_protocol* _protocol, int32 option,
void* argument, size_t length)
{
TRACE("%s(%p, option %s, argument %p, length %zu)\n", __FUNCTION__,
_protocol, option_to_string(option), argument, length);
interface_protocol* protocol = (interface_protocol*)_protocol;
Interface* interface = (Interface*)protocol->interface;
switch (option) {
case SIOCGIFADDR:
case SIOCGIFNETMASK:
case SIOCGIFBRDADDR:
case SIOCGIFDSTADDR:
{
if (length == 0)
length = sizeof(ifreq);
else if (length < sizeof(ifreq))
return B_BAD_VALUE;
ifreq request;
if (user_memcpy(&request, argument, sizeof(struct ifreq)) != B_OK)
return B_BAD_ADDRESS;
InterfaceAddress* interfaceAddress
= get_interface_address(&request.ifr_addr);
if (interfaceAddress == NULL) {
interfaceAddress
= interface->FirstForFamily(protocol->domain->family);
if (interfaceAddress == NULL)
return B_BAD_VALUE;
}
size_t maxLength = length - offsetof(ifreq, ifr_addr);
status_t status = fill_address(
*interfaceAddress->AddressFor(option),
&((struct ifreq*)argument)->ifr_addr, maxLength);
interfaceAddress->ReleaseReference();
return status;
}
case B_SOCKET_COUNT_ALIASES:
{
ifreq request;
request.ifr_count = interface->CountAddresses();
return user_memcpy(&((struct ifreq*)argument)->ifr_count,
&request.ifr_count, sizeof(request.ifr_count));
}
case B_SOCKET_GET_ALIAS:
{
ifaliasreq request;
if (user_memcpy(&request, argument, sizeof(ifaliasreq)) != B_OK)
return B_BAD_ADDRESS;
InterfaceAddress* address = NULL;
if (request.ifra_index < 0) {
if (!protocol->domain->address_module->is_empty_address(
(const sockaddr*)&request.ifra_addr, false)) {
address = interface->AddressForLocal(protocol->domain,
(const sockaddr*)&request.ifra_addr);
} else {
address = interface->FirstForFamily(
protocol->domain->family);
}
request.ifra_index = interface->IndexOfAddress(address);
} else
address = interface->AddressAt(request.ifra_index);
if (address == NULL)
return B_BAD_VALUE;
status_t status = user_memcpy(
&((struct ifaliasreq*)argument)->ifra_index,
&request.ifra_index, sizeof(request.ifra_index));
if (status == B_OK) {
status = fill_address(address->local,
(sockaddr*)&((struct ifaliasreq*)argument)->ifra_addr,
sizeof(sockaddr_storage));
}
if (status == B_OK) {
status = fill_address(address->mask,
(sockaddr*)&((struct ifaliasreq*)argument)->ifra_mask,
sizeof(sockaddr_storage));
}
if (status == B_OK) {
status = fill_address(address->destination,
(sockaddr*)&((struct ifaliasreq*)argument)
->ifra_destination,
sizeof(sockaddr_storage));
}
address->ReleaseReference();
return status;
}
case SIOCGIFFLAGS:
{
struct ifreq request;
request.ifr_flags = interface->flags | interface->device->flags;
return user_memcpy(&((struct ifreq*)argument)->ifr_flags,
&request.ifr_flags, sizeof(request.ifr_flags));
}
case SIOCGIFSTATS:
{
struct ifreq_stats stats = interface->DeviceInterface()->device->stats;
struct ifreq_stats deviceStats;
if (protocol->device_module->control(protocol->device, SIOCGIFSTATS,
&deviceStats, sizeof(struct ifreq_stats)) == B_OK) {
stats.receive.dropped += deviceStats.receive.dropped;
stats.receive.errors += deviceStats.receive.errors;
stats.send = deviceStats.send;
stats.collisions += deviceStats.collisions;
}
return user_memcpy(&((struct ifreq*)argument)->ifr_stats,
&stats, sizeof(struct ifreq_stats));
}
case SIOCGIFTYPE:
{
struct ifreq request;
request.ifr_type = interface->type;
return user_memcpy(&((struct ifreq*)argument)->ifr_type,
&request.ifr_type, sizeof(request.ifr_type));
}
case SIOCGIFMTU:
{
struct ifreq request;
request.ifr_mtu = interface->device->mtu;
return user_memcpy(&((struct ifreq*)argument)->ifr_mtu,
&request.ifr_mtu, sizeof(request.ifr_mtu));
}
case SIOCSIFMTU:
{
struct ifreq request;
if (user_memcpy(&request, argument, sizeof(struct ifreq)) != B_OK)
return B_BAD_ADDRESS;
status_t status = interface->device->module->set_mtu(
interface->device, request.ifr_mtu);
if (status == B_OK)
notify_interface_changed(interface);
return status;
}
case SIOCSIFMEDIA:
{
struct ifreq request;
if (user_memcpy(&request, argument, sizeof(struct ifreq)) != B_OK)
return B_BAD_ADDRESS;
status_t status
= interface->device->module->set_media(
interface->device, request.ifr_media);
if (status == B_NOT_SUPPORTED) {
status = interface->device->module->control(
interface->device, SIOCSIFMEDIA, &request, sizeof(request));
}
return status;
}
case SIOCGIFMEDIA:
{
const size_t copylen = offsetof(ifreq, ifr_media) + sizeof(ifreq::ifr_media);
if (length > 0 && length < copylen)
return B_BAD_VALUE;
struct ifreq request;
if (user_memcpy(&request, argument, copylen) != B_OK)
return B_BAD_ADDRESS;
request.ifr_media = interface->device->media;
return user_memcpy(argument, &request, copylen);
}
case SIOCGIFMETRIC:
{
struct ifreq request;
request.ifr_metric = interface->metric;
return user_memcpy(&((struct ifreq*)argument)->ifr_metric,
&request.ifr_metric, sizeof(request.ifr_metric));
}
case SIOCSIFMETRIC:
{
struct ifreq request;
if (user_memcpy(&request, argument, sizeof(struct ifreq)) < B_OK)
return B_BAD_ADDRESS;
interface->metric = request.ifr_metric;
notify_interface_changed(interface);
return B_OK;
}
case SIOCADDRT:
case SIOCDELRT:
return control_routes(interface, protocol->domain, option, argument,
length);
}
return protocol->device_module->control(protocol->device,
option, argument, length);
}
static status_t
interface_protocol_join_multicast(net_datalink_protocol* _protocol,
const sockaddr* address)
{
interface_protocol* protocol = (interface_protocol*)_protocol;
return protocol->device_module->add_multicast(protocol->device, address);
}
static status_t
interface_protocol_leave_multicast(net_datalink_protocol* _protocol,
const sockaddr* address)
{
interface_protocol* protocol = (interface_protocol*)_protocol;
return protocol->device_module->remove_multicast(protocol->device,
address);
}
net_datalink_module_info gNetDatalinkModule = {
{
NET_DATALINK_MODULE_NAME,
0,
datalink_std_ops
},
datalink_control,
datalink_send_routed_data,
datalink_send_data,
datalink_is_local_address,
datalink_is_local_link_address,
datalink_get_interface,
datalink_get_interface_with_address,
datalink_put_interface,
datalink_get_interface_address,
datalink_get_next_interface_address,
datalink_put_interface_address,
datalink_join_multicast,
datalink_leave_multicast,
add_route,
remove_route,
get_route,
get_buffer_route,
put_route,
register_route_info,
unregister_route_info,
update_route_info
};
net_datalink_protocol_module_info gDatalinkInterfaceProtocolModule = {
{
NULL,
0,
NULL
},
interface_protocol_init,
interface_protocol_uninit,
interface_protocol_send_data,
interface_protocol_up,
interface_protocol_down,
interface_protocol_change_address,
interface_protocol_control,
interface_protocol_join_multicast,
interface_protocol_leave_multicast,
};