From 6a60618094d288e845e7384ec6ab5b4e63f742e5 Mon Sep 17 00:00:00 2001 From: Hugo Santos Date: Sun, 15 Apr 2007 07:31:04 +0000 Subject: [PATCH] glued the multicast filter handling to the receive path: we are now capable of receiving multicast frames in datagram sockets. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@20695 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/net/net_protocol.h | 3 +- .../kernel/network/protocols/icmp/icmp.cpp | 1 + .../kernel/network/protocols/ipv4/ipv4.cpp | 141 +++++++++++++++--- .../network/protocols/ipv4/multicast.cpp | 110 +++++++++----- .../kernel/network/protocols/ipv4/multicast.h | 64 ++++++-- .../kernel/network/protocols/tcp/tcp.cpp | 1 + .../kernel/network/protocols/udp/udp.cpp | 88 ++++++++--- src/add-ons/kernel/network/stack/link.cpp | 1 + 8 files changed, 313 insertions(+), 96 deletions(-) diff --git a/headers/private/net/net_protocol.h b/headers/private/net/net_protocol.h index d5012f5b6f..e4014ccb86 100644 --- a/headers/private/net/net_protocol.h +++ b/headers/private/net/net_protocol.h @@ -55,8 +55,9 @@ struct net_protocol_module_info { size_t (*get_mtu)(net_protocol *self, const struct sockaddr *address); status_t (*receive_data)(net_buffer *data); - status_t (*error)(uint32 code, net_buffer *data); + status_t (*deliver_data)(net_protocol *protocol, net_buffer *data); + status_t (*error)(uint32 code, net_buffer *data); status_t (*error_reply)(net_protocol *self, net_buffer *causedError, uint32 code, void *errorData); }; diff --git a/src/add-ons/kernel/network/protocols/icmp/icmp.cpp b/src/add-ons/kernel/network/protocols/icmp/icmp.cpp index 22bc9fb7e0..2c591db367 100644 --- a/src/add-ons/kernel/network/protocols/icmp/icmp.cpp +++ b/src/add-ons/kernel/network/protocols/icmp/icmp.cpp @@ -342,6 +342,7 @@ net_protocol_module_info sICMPModule = { icmp_get_domain, icmp_get_mtu, icmp_receive_data, + NULL, icmp_error, icmp_error_reply, }; diff --git a/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp b/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp index 78f90d042f..7d1df7d491 100644 --- a/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp +++ b/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp @@ -117,9 +117,15 @@ class FragmentPacket { class MulticastGroup { public: + typedef MulticastGroupLink Link; + MulticastGroup(const in_addr &address); - status_t Deliver(net_buffer *buffer); + status_t Deliver(net_protocol_module_info *module, net_buffer *buffer); + + void Add(Link *link); + void Remove(Link *link); + bool IsEmpty() const { return fLinks.IsEmpty(); } static uint32 Hash(void *address, const void *key, uint32 range); static int Compare(void *address, const void *key); @@ -128,8 +134,12 @@ public: static const int kMaxGroups = 64; private: + typedef DoublyLinkedListCLink LinkLink; + typedef DoublyLinkedList Links; + MulticastGroup *fNext; in_addr fMulticastAddress; + Links fLinks; }; class RawSocket : public DoublyLinkedListLinkImpl, public DatagramSocket<> { @@ -139,9 +149,11 @@ class RawSocket : public DoublyLinkedListLinkImpl, public DatagramSoc typedef DoublyLinkedList RawSocketList; +typedef MulticastFilter IPv4MulticastFilter; + struct ipv4_protocol : net_protocol { - ipv4_protocol(net_socket *socket) - : multicast_filter(socket) {} + ipv4_protocol() + : multicast_filter(this) {} RawSocket *raw; uint8 service_type; @@ -149,7 +161,7 @@ struct ipv4_protocol : net_protocol { uint8 multicast_time_to_live; uint32 flags; - MulticastFilter multicast_filter; + IPv4MulticastFilter multicast_filter; }; // protocol flags @@ -424,9 +436,35 @@ MulticastGroup::MulticastGroup(const in_addr &address) status_t -MulticastGroup::Deliver(net_buffer *buffer) +MulticastGroup::Deliver(net_protocol_module_info *module, net_buffer *buffer) { - return B_ERROR; + if (module->deliver_data == NULL) + return B_OK; + + Links::Iterator iterator = fLinks.GetIterator(); + + while (iterator.HasNext()) { + Link *link = iterator.Next(); + + if (link->group->FilterAccepts(buffer)) + module->deliver_data(link->group->Socket(), buffer); + } + + return B_OK; +} + + +void +MulticastGroup::Add(Link *link) +{ + fLinks.Add(link); +} + + +void +MulticastGroup::Remove(Link *link) +{ + fLinks.Remove(link); } @@ -669,7 +707,7 @@ raw_receive_data(net_buffer *buffer) static status_t -deliver_multicast(net_buffer *buffer) +deliver_multicast(net_protocol_module_info *module, net_buffer *buffer) { BenaphoreLocker _(sMulticastGroupsLock); @@ -678,9 +716,54 @@ deliver_multicast(net_buffer *buffer) if (group == NULL) return B_OK; - // RAW sockets will be registered just like any other + // TODO fix sending multicast to RAW sockets, right now + // they are receiving the frames without the IP header - return group->Deliver(buffer); + return group->Deliver(module, buffer); +} + + +status_t +IPv4Multicast::JoinGroup(const in_addr &groupAddr, MulticastGroup::Link *link) +{ + BenaphoreLocker _(sMulticastGroupsLock); + + MulticastGroup *group = (MulticastGroup *)hash_lookup(sMulticastGroups, + &groupAddr); + if (group == NULL) { + group = new (std::nothrow) MulticastGroup(groupAddr); + if (group == NULL) + return B_NO_MEMORY; + + status_t status = hash_insert(sMulticastGroups, group); + if (status < B_OK) { + delete group; + return status; + } + } + + group->Add(link); + return B_OK; +} + + +status_t +IPv4Multicast::LeaveGroup(const in_addr &groupAddr, MulticastGroup::Link *link) +{ + BenaphoreLocker _(sMulticastGroupsLock); + + MulticastGroup *group = (MulticastGroup *)hash_lookup(sMulticastGroups, + &groupAddr); + if (group == NULL) + return ENOENT; + + group->Remove(link); + if (group->IsEmpty()) { + hash_remove(sMulticastGroups, group); + delete group; + } + + return B_OK; } @@ -715,7 +798,7 @@ fill_sockaddr_in(sockaddr_in *target, in_addr_t address) static status_t -ipv4_delta_group(MulticastFilter::GroupState *group, int option, +ipv4_delta_group(IPv4MulticastFilter::GroupState *group, int option, net_interface *interface, const in_addr *sourceAddr) { switch (option) { @@ -742,8 +825,8 @@ ipv4_delta_membership(ipv4_protocol *protocol, int option, net_interface *interface, const in_addr *groupAddr, const in_addr *sourceAddr) { - MulticastFilter &filter = protocol->multicast_filter; - MulticastFilter::GroupState *group = NULL; + IPv4MulticastFilter &filter = protocol->multicast_filter; + IPv4MulticastFilter::GroupState *group = NULL; switch (option) { case IP_ADD_MEMBERSHIP: @@ -833,7 +916,7 @@ ipv4_generic_delta_membership(ipv4_protocol *protocol, int option, net_protocol * ipv4_init_protocol(net_socket *socket) { - ipv4_protocol *protocol = new (std::nothrow) ipv4_protocol(socket); + ipv4_protocol *protocol = new (std::nothrow) ipv4_protocol(); if (protocol == NULL) return NULL; @@ -1412,19 +1495,12 @@ ipv4_receive_data(net_buffer *buffer) } } - if (buffer->flags & MSG_MCAST) { - // Unfortunely historical reasons dictate that the IP multicast - // model be a little different from the unicast one. We deliver - // this frame directly to all sockets registered with interest - // for this multicast group. - return deliver_multicast(buffer); - } - // Since the buffer might have been changed (reassembled fragment) // we must no longer access bufferHeader or header anymore after // this point - raw_receive_data(buffer); + if (!(buffer->flags & MSG_MCAST)) + raw_receive_data(buffer); gBufferModule->remove_header(buffer, headerLength); // the header is of variable size and may include IP options @@ -1436,10 +1512,30 @@ ipv4_receive_data(net_buffer *buffer) return EAFNOSUPPORT; } + if (buffer->flags & MSG_MCAST) { + // Unfortunely historical reasons dictate that the IP multicast + // model be a little different from the unicast one. We deliver + // this frame directly to all sockets registered with interest + // for this multicast group. + return deliver_multicast(module, buffer); + } + return module->receive_data(buffer); } +status_t +ipv4_deliver_data(net_protocol *_protocol, net_buffer *buffer) +{ + ipv4_protocol *protocol = (ipv4_protocol *)_protocol; + + if (protocol->raw == NULL) + return B_ERROR; + + return protocol->raw->SocketEnqueue(buffer); +} + + status_t ipv4_error(uint32 code, net_buffer *data) { @@ -1591,6 +1687,7 @@ net_protocol_module_info gIPv4Module = { ipv4_get_domain, ipv4_get_mtu, ipv4_receive_data, + ipv4_deliver_data, ipv4_error, ipv4_error_reply, }; diff --git a/src/add-ons/kernel/network/protocols/ipv4/multicast.cpp b/src/add-ons/kernel/network/protocols/ipv4/multicast.cpp index 6dfdaac992..26b0a57380 100644 --- a/src/add-ons/kernel/network/protocols/ipv4/multicast.cpp +++ b/src/add-ons/kernel/network/protocols/ipv4/multicast.cpp @@ -8,12 +8,14 @@ #include "multicast.h" +#include + #include #include -template class MulticastFilter; -template class MulticastGroupState; +template class MulticastFilter; +template class MulticastGroupState; template class MulticastGroupInterfaceState; static inline bool @@ -94,25 +96,23 @@ MulticastGroupInterfaceState::_Remove(Source *state) } -template -MulticastGroupState::MulticastGroupState( - MulticastFilter *parent, const AddressType &address) - : fParent(parent), fMulticastAddress(address), fFilterMode(kInclude) +template +MulticastGroupState::MulticastGroupState(net_protocol *socket, + const AddressType &address) + : fSocket(socket), fMulticastAddress(address), fFilterMode(kInclude) { } -template -MulticastGroupState::~MulticastGroupState() +template +MulticastGroupState::~MulticastGroupState() { - InterfaceList::Iterator iterator = fInterfaces.GetIterator(); - while (iterator.HasNext()) - _RemoveInterface(iterator.Next()); + Clear(); } -template status_t -MulticastGroupState::Add(net_interface *interface) +template status_t +MulticastGroupState::Add(net_interface *interface) { if (fFilterMode == kInclude && !fInterfaces.IsEmpty()) return EINVAL; @@ -123,8 +123,8 @@ MulticastGroupState::Add(net_interface *interface) } -template status_t -MulticastGroupState::Drop(net_interface *interface) +template status_t +MulticastGroupState::Drop(net_interface *interface) { InterfaceState *state = _GetInterface(interface, false); if (state == NULL) @@ -139,8 +139,8 @@ MulticastGroupState::Drop(net_interface *interface) } -template status_t -MulticastGroupState::BlockSource(net_interface *interface, +template status_t +MulticastGroupState::BlockSource(net_interface *interface, const AddressType &sourceAddress) { if (fFilterMode != kExclude) @@ -154,8 +154,8 @@ MulticastGroupState::BlockSource(net_interface *interface, } -template status_t -MulticastGroupState::UnblockSource(net_interface *interface, +template status_t +MulticastGroupState::UnblockSource(net_interface *interface, const AddressType &sourceAddress) { if (fFilterMode != kExclude) @@ -168,8 +168,8 @@ MulticastGroupState::UnblockSource(net_interface *interface, return state->Remove(sourceAddress); } -template status_t -MulticastGroupState::AddSSM(net_interface *interface, +template status_t +MulticastGroupState::AddSSM(net_interface *interface, const AddressType &sourceAddress) { if (fFilterMode == kExclude) @@ -183,8 +183,8 @@ MulticastGroupState::AddSSM(net_interface *interface, } -template status_t -MulticastGroupState::DropSSM(net_interface *interface, +template status_t +MulticastGroupState::DropSSM(net_interface *interface, const AddressType &sourceAddress) { if (fFilterMode == kExclude) @@ -198,8 +198,31 @@ MulticastGroupState::DropSSM(net_interface *interface, } -template MulticastGroupState::InterfaceState * -MulticastGroupState::_GetInterface(net_interface *interface, +template void +MulticastGroupState::Clear() +{ + InterfaceList::Iterator iterator = fInterfaces.GetIterator(); + while (iterator.HasNext()) + _RemoveInterface(iterator.Next()); +} + + +template bool +MulticastGroupState::FilterAccepts(net_buffer *buffer) +{ + InterfaceState *state = _GetInterface(buffer->interface, false); + if (state == NULL) + return false; + + bool has = state->Contains(*Addressing::AddressFromSockAddr( + (sockaddr *)&buffer->source)); + + return (has && fFilterMode == kInclude) || (!has && fFilterMode == kExclude); +} + + +template MulticastGroupState::InterfaceState * +MulticastGroupState::_GetInterface(net_interface *interface, bool create) { InterfaceList::Iterator iterator = fInterfaces.GetIterator(); @@ -220,35 +243,35 @@ MulticastGroupState::_GetInterface(net_interface *interface, } -template void -MulticastGroupState::_RemoveInterface(InterfaceState *state) +template void +MulticastGroupState::_RemoveInterface(InterfaceState *state) { fInterfaces.Remove(state); delete state; } -template -MulticastFilter::MulticastFilter(net_socket *socket) +template +MulticastFilter::MulticastFilter(net_protocol *socket) : fParent(socket) { } -template -MulticastFilter::~MulticastFilter() +template +MulticastFilter::~MulticastFilter() { States::Iterator iterator = fStates.GetIterator(); while (iterator.HasNext()) { GroupState *state = iterator.Next(); - fStates.Remove(state); - delete state; + state->Clear(); + ReturnGroup(state); } } -template MulticastFilter::GroupState * -MulticastFilter::GetGroup(const AddressType &groupAddress, +template MulticastFilter::GroupState * +MulticastFilter::GetGroup(const AddressType &groupAddress, bool create) { States::Iterator iterator = fStates.GetIterator(); @@ -262,17 +285,26 @@ MulticastFilter::GetGroup(const AddressType &groupAddress, if (!create) return NULL; - GroupState *state = new (nothrow) GroupState(this, groupAddress); - if (state) + GroupState *state = new (nothrow) GroupState(fParent, groupAddress); + if (state) { + if (Addressing::JoinGroup(groupAddress, state->ProtocolLink()) < B_OK) { + delete state; + return NULL; + } + fStates.Add(state); + } + return state; } -template void -MulticastFilter::ReturnGroup(GroupState *group) +template void +MulticastFilter::ReturnGroup(GroupState *group) { if (group->IsEmpty()) { + Addressing::LeaveGroup(group->Address(), group->ProtocolLink()); + fStates.Remove(group); delete group; } diff --git a/src/add-ons/kernel/network/protocols/ipv4/multicast.h b/src/add-ons/kernel/network/protocols/ipv4/multicast.h index 84c0ab64ab..18db4551bb 100644 --- a/src/add-ons/kernel/network/protocols/ipv4/multicast.h +++ b/src/add-ons/kernel/network/protocols/ipv4/multicast.h @@ -12,10 +12,30 @@ #include #include -struct net_interface; -struct net_socket; +#include -template class MulticastFilter; +struct net_buffer; +struct net_interface; +struct net_protocol; + +// This code is template'ized as it is reusable for IPv6 + +template class MulticastFilter; +template struct MulticastGroupLink; +template class MulticastGroupState; + +// TODO move this elsewhere... +struct IPv4Multicast { + typedef struct in_addr AddressType; + + static status_t JoinGroup(const in_addr &, MulticastGroupLink *); + static status_t LeaveGroup(const in_addr &, MulticastGroupLink *); + + static in_addr *AddressFromSockAddr(sockaddr *sockaddr) + { + return &((sockaddr_in *)sockaddr)->sin_addr; + } +}; template struct MulticastSource { @@ -34,6 +54,9 @@ public: status_t Add(const AddressType &address); status_t Remove(const AddressType &address); + bool Contains(const AddressType &address) + { return _Get(address, false) != NULL; } + list_link link; private: typedef MulticastSource Source; @@ -48,13 +71,22 @@ private: SourceList fSources; }; -template +template +struct MulticastGroupLink { + MulticastGroupState *group; + list_link link; +}; + +template class MulticastGroupState { public: - MulticastGroupState(MulticastFilter *parent, - const AddressType &address); + typedef typename Addressing::AddressType AddressType; + + MulticastGroupState(net_protocol *parent, const AddressType &address); ~MulticastGroupState(); + net_protocol *Socket() const { return fSocket; } + const AddressType &Address() const { return fMulticastAddress; } bool IsEmpty() const { return fFilterMode == kInclude && fInterfaces.IsEmpty(); } @@ -70,6 +102,12 @@ public: status_t DropSSM(net_interface *interface, const AddressType &sourceAddress); + void Clear(); + + bool FilterAccepts(net_buffer *buffer); + + MulticastGroupLink *ProtocolLink() { return &fInternalLink; } + list_link link; private: typedef MulticastGroupInterfaceState InterfaceState; @@ -84,21 +122,23 @@ private: kExclude }; - MulticastFilter *fParent; + net_protocol *fSocket; AddressType fMulticastAddress; FilterMode fFilterMode; InterfaceList fInterfaces; + MulticastGroupLink fInternalLink; }; -template +template class MulticastFilter { public: - typedef MulticastGroupState GroupState; + typedef typename Addressing::AddressType AddressType; + typedef MulticastGroupState GroupState; - MulticastFilter(net_socket *parent); + MulticastFilter(net_protocol *parent); ~MulticastFilter(); - net_socket *Parent() const { return fParent; } + net_protocol *Parent() const { return fParent; } GroupState *GetGroup(const AddressType &groupAddress, bool create); void ReturnGroup(GroupState *group); @@ -107,7 +147,7 @@ private: typedef DoublyLinkedListCLink GroupStateLink; typedef DoublyLinkedList States; - net_socket *fParent; + net_protocol *fParent; // TODO change this into an hash table or tree States fStates; diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp.cpp b/src/add-ons/kernel/network/protocols/tcp/tcp.cpp index 7ca85773dd..7497c1f875 100644 --- a/src/add-ons/kernel/network/protocols/tcp/tcp.cpp +++ b/src/add-ons/kernel/network/protocols/tcp/tcp.cpp @@ -718,6 +718,7 @@ net_protocol_module_info sTCPModule = { tcp_get_domain, tcp_get_mtu, tcp_receive_data, + NULL, tcp_error, tcp_error_reply, }; diff --git a/src/add-ons/kernel/network/protocols/udp/udp.cpp b/src/add-ons/kernel/network/protocols/udp/udp.cpp index 8d7b0888b0..9d3e59a82b 100644 --- a/src/add-ons/kernel/network/protocols/udp/udp.cpp +++ b/src/add-ons/kernel/network/protocols/udp/udp.cpp @@ -82,6 +82,7 @@ public: net_buffer **_buffer); status_t StoreData(net_buffer *buffer); + status_t DeliverData(net_buffer *buffer); net_domain * Domain() const { @@ -179,6 +180,7 @@ public: ~UdpEndpointManager(); status_t ReceiveData(net_buffer *buffer); + status_t Deframe(net_buffer *buffer); UdpDomainSupport *OpenEndpoint(UdpEndpoint *endpoint); status_t FreeEndpoint(UdpDomainSupport *domain); @@ -544,8 +546,40 @@ UdpEndpointManager::InitCheck() const status_t UdpEndpointManager::ReceiveData(net_buffer *buffer) { + status_t status = Deframe(buffer); + if (status < B_OK) + return status; + TRACE_EPM("ReceiveData(%p [%ld bytes])", buffer, buffer->size); + net_domain *domain = buffer->interface->domain; + + BenaphoreLocker _(fLock); + + UdpDomainSupport *domainSupport = _GetDomain(domain, false); + if (domainSupport == NULL) { + // we don't instantiate domain supports in the + // RX path as we are only interested in delivering + // data to existing sockets. + return B_ERROR; + } + + status = domainSupport->DemuxIncomingBuffer(buffer); + if (status < B_OK) { + TRACE_EPM(" ReceiveData(): no endpoint."); + // TODO: send ICMP-error + return B_ERROR; + } + + return B_ERROR; +} + + +status_t +UdpEndpointManager::Deframe(net_buffer *buffer) +{ + TRACE_EPM("Deframe(%p [%ld bytes])", buffer, buffer->size); + NetBufferHeaderReader bufferHeader(buffer); if (bufferHeader.Status() < B_OK) return bufferHeader.Status(); @@ -556,7 +590,7 @@ UdpEndpointManager::ReceiveData(net_buffer *buffer) struct sockaddr *destination = (struct sockaddr *)&buffer->destination; if (buffer->interface == NULL || buffer->interface->domain == NULL) { - TRACE_EPM(" ReceiveData(): UDP packed dropped as there was no domain " + TRACE_EPM(" Deframe(): UDP packed dropped as there was no domain " "specified (interface %p).", buffer->interface); return B_BAD_VALUE; } @@ -564,18 +598,16 @@ UdpEndpointManager::ReceiveData(net_buffer *buffer) net_domain *domain = buffer->interface->domain; net_address_module_info *addressModule = domain->address_module; - BenaphoreLocker _(fLock); - addressModule->set_port(source, header.source_port); addressModule->set_port(destination, header.destination_port); - TRACE_EPM(" ReceiveData(): data from %s to %s", + TRACE_EPM(" Deframe(): data from %s to %s", AddressString(domain, source, true).Data(), AddressString(domain, destination, true).Data()); uint16 udpLength = ntohs(header.udp_length); if (udpLength > buffer->size) { - TRACE_EPM(" ReceiveData(): buffer is too short, expected %hu.", + TRACE_EPM(" Deframe(): buffer is too short, expected %hu.", udpLength); return B_MISMATCHED_VALUES; } @@ -596,7 +628,7 @@ UdpEndpointManager::ReceiveData(net_buffer *buffer) << Checksum::BufferHelper(buffer, gBufferModule); uint16 sum = udpChecksum; if (sum != 0) { - TRACE_EPM(" ReceiveData(): bad checksum 0x%hx.", sum); + TRACE_EPM(" Deframe(): bad checksum 0x%hx.", sum); return B_BAD_VALUE; } } @@ -604,22 +636,7 @@ UdpEndpointManager::ReceiveData(net_buffer *buffer) bufferHeader.Remove(); // remove UDP-header from buffer before passing it on - UdpDomainSupport *domainSupport = _GetDomain(domain, false); - if (domainSupport == NULL) { - // we don't instantiate domain supports in the - // RX path as we are only interested in delivering - // data to existing sockets. - return B_ERROR; - } - - status_t status = domainSupport->DemuxIncomingBuffer(buffer); - if (status < B_OK) { - TRACE_EPM(" ReceiveData(): no endpoint."); - // TODO: send ICMP-error - return B_ERROR; - } - - return B_ERROR; + return B_OK; } @@ -937,6 +954,25 @@ UdpEndpoint::StoreData(net_buffer *buffer) } +status_t +UdpEndpoint::DeliverData(net_buffer *_buffer) +{ + net_buffer *buffer = gBufferModule->clone(_buffer, false); + if (buffer == NULL) + return B_NO_MEMORY; + + status_t status = sUdpEndpointManager->Deframe(buffer); + if (status < B_OK) { + gBufferModule->free(buffer); + return status; + } + + // we call Enqueue() instead of SocketEnqueue() as there is + // no need to clone the buffer again. + return Enqueue(buffer); +} + + // #pragma mark - protocol interface @@ -1093,6 +1129,13 @@ udp_receive_data(net_buffer *buffer) } +status_t +udp_deliver_data(net_protocol *protocol, net_buffer *buffer) +{ + return ((UdpEndpoint *)protocol)->DeliverData(buffer); +} + + status_t udp_error(uint32 code, net_buffer *data) { @@ -1204,6 +1247,7 @@ net_protocol_module_info sUDPModule = { udp_get_domain, udp_get_mtu, udp_receive_data, + udp_deliver_data, udp_error, udp_error_reply, }; diff --git a/src/add-ons/kernel/network/stack/link.cpp b/src/add-ons/kernel/network/stack/link.cpp index 43bb2f2469..1f30cbf787 100644 --- a/src/add-ons/kernel/network/stack/link.cpp +++ b/src/add-ons/kernel/network/stack/link.cpp @@ -510,6 +510,7 @@ net_protocol_module_info gLinkModule = { link_get_domain, link_get_mtu, link_receive_data, + NULL, link_error, link_error_reply, };