diff --git a/headers/posix/netinet/in.h b/headers/posix/netinet/in.h index 71986c5211..18c77aef07 100644 --- a/headers/posix/netinet/in.h +++ b/headers/posix/netinet/in.h @@ -80,6 +80,30 @@ struct sockaddr_in { }; /* the address is therefore at sin_addr.s_addr */ +/* RFC 3678 - Socket Interface Extensions for Multicast Source Filters */ + +struct ip_mreq { + struct in_addr imr_multiaddr; /* IP address of group */ + struct in_addr imr_interface; /* IP address of interface */ +}; + +struct ip_mreq_source { + struct in_addr imr_multiaddr; /* IP address of group */ + struct in_addr imr_sourceaddr; /* IP address of source */ + struct in_addr imr_interface; /* IP address of interface */ +}; + +struct group_req { + uint32_t gr_interface; /* interface index */ + struct sockaddr_storage gr_group; /* group address */ +}; + +struct group_source_req { + uint32_t gsr_interface; /* interface index */ + struct sockaddr_storage gsr_group; /* group address */ + struct sockaddr_storage gsr_source; /* source address */ +}; + /* * Options for use with [gs]etsockopt at the IP level. * First word of comment is data type; bool is stored in int. @@ -97,6 +121,16 @@ struct sockaddr_in { #define IP_MULTICAST_LOOP 11 /* u_char; set/get IP multicast loopback */ #define IP_ADD_MEMBERSHIP 12 /* ip_mreq; add an IP group membership */ #define IP_DROP_MEMBERSHIP 13 /* ip_mreq; drop an IP group membership */ +#define IP_BLOCK_SOURCE 14 /* ip_mreq_source */ +#define IP_UNBLOCK_SOURCE 15 /* ip_mreq_source */ +#define IP_ADD_SOURCE_MEMBERSHIP 16 /* ip_mreq_source */ +#define IP_DROP_SOURCE_MEMBERSHIP 17 /* ip_mreq_source */ +#define MCAST_JOIN_GROUP 18 /* group_req */ +#define MCAST_BLOCK_SOURCE 19 /* group_source_req */ +#define MCAST_UNBLOCK_SOURCE 20 /* group_source_req */ +#define MCAST_LEAVE_GROUP 21 /* group_req */ +#define MCAST_JOIN_SOURCE_GROUP 22 /* group_source_req */ +#define MCAST_LEAVE_SOURCE_GROUP 23 /* group_source_req */ #define __IPADDR(x) ((uint32_t)htonl((uint32_t)(x))) diff --git a/headers/private/net/net_datalink.h b/headers/private/net/net_datalink.h index b93adc972d..fd88a9bf9d 100644 --- a/headers/private/net/net_datalink.h +++ b/headers/private/net/net_datalink.h @@ -71,6 +71,8 @@ struct net_datalink_module_info { const struct sockaddr *address, net_interface **_interface, uint32 *_matchedType); + net_interface *(*get_interface_with_address)(struct net_domain *domain, + const struct sockaddr *address); // routes status_t (*add_route)(struct net_domain *domain, diff --git a/src/add-ons/kernel/network/protocols/ipv4/Jamfile b/src/add-ons/kernel/network/protocols/ipv4/Jamfile index e4a7012d0d..029ba88ee5 100644 --- a/src/add-ons/kernel/network/protocols/ipv4/Jamfile +++ b/src/add-ons/kernel/network/protocols/ipv4/Jamfile @@ -15,6 +15,7 @@ UsePrivateHeaders kernel net ; KernelAddon ipv4 : ipv4.cpp ipv4_address.cpp + multicast.cpp ; # Installation diff --git a/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp b/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp index 7979f679e0..c52cfa8bae 100644 --- a/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp +++ b/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp @@ -8,6 +8,7 @@ #include "ipv4_address.h" +#include "multicast.h" #include #include @@ -126,6 +127,8 @@ struct ipv4_protocol : net_protocol { uint8 service_type; uint8 time_to_live; uint32 flags; + + MulticastFilter multicast_filter; }; // protocol flags @@ -618,6 +621,86 @@ receiving_protocol(uint8 protocol) } +static status_t +ipv4_delta_group(MulticastFilter::GroupState *group, int option, + net_interface *interface, in_addr *sourceAddr) +{ + switch (option) { + case IP_ADD_MEMBERSHIP: + return group->Add(interface); + case IP_DROP_MEMBERSHIP: + return group->Drop(interface); + case IP_BLOCK_SOURCE: + return group->BlockSource(interface, *sourceAddr); + case IP_UNBLOCK_SOURCE: + return group->UnblockSource(interface, *sourceAddr); + case IP_ADD_SOURCE_MEMBERSHIP: + return group->AddSSM(interface, *sourceAddr); + case IP_DROP_SOURCE_MEMBERSHIP: + return group->DropSSM(interface, *sourceAddr); + } + + return B_ERROR; +} + + +static status_t +ipv4_delta_membership(ipv4_protocol *protocol, int option, + in_addr *interfaceAddr, in_addr *groupAddr, in_addr *sourceAddr) +{ + net_interface *interface = NULL; + + if (interfaceAddr->s_addr == INADDR_ANY) { + interface = sDatalinkModule->get_interface_with_address(sDomain, NULL); + } else { + sockaddr_in address; + + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_len = sizeof(address); + address.sin_addr = *interfaceAddr; + + interface = sDatalinkModule->get_interface_with_address(sDomain, + (sockaddr *)&address); + } + + if (interface == NULL) + return ENODEV; + + MulticastFilter &filter = protocol->multicast_filter; + MulticastFilter::GroupState *group = NULL; + + switch (option) { + case IP_ADD_MEMBERSHIP: + case IP_ADD_SOURCE_MEMBERSHIP: + group = filter.GetGroup(*groupAddr, true); + if (group == NULL) + return ENOBUFS; + break; + + case IP_DROP_MEMBERSHIP: + case IP_BLOCK_SOURCE: + case IP_UNBLOCK_SOURCE: + case IP_DROP_SOURCE_MEMBERSHIP: + group = filter.GetGroup(*groupAddr, false); + if (group == NULL) { + if (option == IP_DROP_SOURCE_MEMBERSHIP + || option == IP_DROP_SOURCE_MEMBERSHIP) + return EADDRNOTAVAIL; + else + return EINVAL; + } + break; + } + + status_t status = ipv4_delta_group(group, option, interface, sourceAddr); + + filter.ReturnGroup(group); + + return status; +} + + // #pragma mark - @@ -756,6 +839,17 @@ ipv4_control(net_protocol *_protocol, int level, int option, void *value, return user_memcpy(value, &serviceType, sizeof(serviceType)); } + case IP_ADD_MEMBERSHIP: + case IP_DROP_MEMBERSHIP: + case IP_BLOCK_SOURCE: + case IP_UNBLOCK_SOURCE: + case IP_ADD_SOURCE_MEMBERSHIP: + case IP_DROP_SOURCE_MEMBERSHIP: + // RFC 3678, Section 4.1: + // ``An error of EOPNOTSUPP is returned if these options are + // used with getsockopt().'' + return EOPNOTSUPP; + default: dprintf("IPv4::control(): get unknown option: %d\n", option); return ENOPROTOOPT; @@ -804,6 +898,34 @@ ipv4_control(net_protocol *_protocol, int level, int option, void *value, return B_OK; } + case IP_ADD_MEMBERSHIP: + case IP_DROP_MEMBERSHIP: + { + ip_mreq mreq; + if (*_length != sizeof(ip_mreq)) + return B_BAD_VALUE; + if (user_memcpy(&mreq, value, sizeof(ip_mreq)) < B_OK) + return B_BAD_ADDRESS; + + return ipv4_delta_membership(protocol, option, &mreq.imr_interface, + &mreq.imr_multiaddr, NULL); + } + + case IP_BLOCK_SOURCE: + case IP_UNBLOCK_SOURCE: + case IP_ADD_SOURCE_MEMBERSHIP: + case IP_DROP_SOURCE_MEMBERSHIP: + { + ip_mreq_source mreq; + if (*_length != sizeof(ip_mreq_source)) + return B_BAD_VALUE; + if (user_memcpy(&mreq, value, sizeof(ip_mreq_source)) < B_OK) + return B_BAD_ADDRESS; + + return ipv4_delta_membership(protocol, option, &mreq.imr_interface, + &mreq.imr_multiaddr, &mreq.imr_sourceaddr); + } + default: dprintf("IPv4::control(): set unknown option: %d\n", option); return ENOPROTOOPT; diff --git a/src/add-ons/kernel/network/protocols/ipv4/multicast.cpp b/src/add-ons/kernel/network/protocols/ipv4/multicast.cpp new file mode 100644 index 0000000000..e93f9c1962 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv4/multicast.cpp @@ -0,0 +1,272 @@ +/* + * Copyright 2007, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Hugo Santos, hugosantos@gmail.com + */ + +#include "multicast.h" + +#include + +#include + +template class MulticastFilter; +template class MulticastGroupState; +template class MulticastGroupInterfaceState; + +static inline bool +operator==(const in_addr &a1, const in_addr &a2) +{ + return a1.s_addr == a2.s_addr; +} + +using std::nothrow; + + +template +MulticastGroupInterfaceState::MulticastGroupInterfaceState( + net_interface *interface) + : fInterface(interface) +{ +} + + +template +MulticastGroupInterfaceState::~MulticastGroupInterfaceState() +{ + SourceList::Iterator iterator = fSources.GetIterator(); + while (iterator.HasNext()) { + _Remove(iterator.Next()); + } +} + + +template status_t +MulticastGroupInterfaceState::Add(const AddressType &address) +{ + return _Get(address, true) ? B_OK : ENOBUFS; +} + + +template status_t +MulticastGroupInterfaceState::Remove(const AddressType &address) +{ + Source *state = _Get(address, false); + if (state == NULL) + return EADDRNOTAVAIL; + + _Remove(state); + return B_OK; +} + + +template MulticastGroupInterfaceState::Source * +MulticastGroupInterfaceState::_Get(const AddressType &address, + bool create) +{ + SourceList::Iterator iterator = fSources.GetIterator(); + while (iterator.HasNext()) { + Source *state = iterator.Next(); + if (state->address == address) + return state; + } + + if (!create) + return false; + + Source *state = new (nothrow) Source; + if (state) { + state->address = address; + fSources.Add(state); + } + + return state; +} + + +template void +MulticastGroupInterfaceState::_Remove(Source *state) +{ + fSources.Remove(state); + delete state; +} + + +template +MulticastGroupState::MulticastGroupState(const AddressType &address) + : fMulticastAddress(address), fFilterMode(kInclude) +{ +} + + +template +MulticastGroupState::~MulticastGroupState() +{ + InterfaceList::Iterator iterator = fInterfaces.GetIterator(); + while (iterator.HasNext()) + _RemoveInterface(iterator.Next()); +} + + +template status_t +MulticastGroupState::Add(net_interface *interface) +{ + if (fFilterMode == kInclude && !fInterfaces.IsEmpty()) + return EINVAL; + + fFilterMode = kExclude; + + return _GetInterface(interface, true) != NULL ? B_OK : ENOBUFS; +} + + +template status_t +MulticastGroupState::Drop(net_interface *interface) +{ + InterfaceState *state = _GetInterface(interface, false); + if (state == NULL) + return EADDRNOTAVAIL; + + _RemoveInterface(state); + + if (fInterfaces.IsEmpty()) + fFilterMode = kInclude; + + return B_OK; +} + + +template status_t +MulticastGroupState::BlockSource(net_interface *interface, + const AddressType &sourceAddress) +{ + if (fFilterMode != kExclude) + return EINVAL; + + InterfaceState *state = _GetInterface(interface, false); + if (state == NULL) + return EINVAL; + + return state->Add(sourceAddress); +} + + +template status_t +MulticastGroupState::UnblockSource(net_interface *interface, + const AddressType &sourceAddress) +{ + if (fFilterMode != kExclude) + return EINVAL; + + InterfaceState *state = _GetInterface(interface, false); + if (state == NULL) + return EINVAL; + + return state->Remove(sourceAddress); +} + +template status_t +MulticastGroupState::AddSSM(net_interface *interface, + const AddressType &sourceAddress) +{ + if (fFilterMode == kExclude) + return EINVAL; + + InterfaceState *state = _GetInterface(interface, true); + if (state == NULL) + return ENOBUFS; + + return state->Add(sourceAddress); +} + + +template status_t +MulticastGroupState::DropSSM(net_interface *interface, + const AddressType &sourceAddress) +{ + if (fFilterMode == kExclude) + return EINVAL; + + InterfaceState *state = _GetInterface(interface, false); + if (state == NULL) + return EADDRNOTAVAIL; + + return state->Remove(sourceAddress); +} + + +template MulticastGroupState::InterfaceState * +MulticastGroupState::_GetInterface(net_interface *interface, + bool create) +{ + InterfaceList::Iterator iterator = fInterfaces.GetIterator(); + while (iterator.HasNext()) { + InterfaceState *state = iterator.Next(); + if (state->Interface() == interface) + return state; + } + + if (!create) + return false; + + InterfaceState *state = new (nothrow) InterfaceState(interface); + if (state) + fInterfaces.Add(state); + + return state; +} + + +template void +MulticastGroupState::_RemoveInterface(InterfaceState *state) +{ + fInterfaces.Remove(state); + delete state; +} + + +template +MulticastFilter::~MulticastFilter() +{ + States::Iterator iterator = fStates.GetIterator(); + while (iterator.HasNext()) { + GroupState *state = iterator.Next(); + fStates.Remove(state); + delete state; + } +} + + +template MulticastFilter::GroupState * +MulticastFilter::GetGroup(const AddressType &groupAddress, + bool create) +{ + States::Iterator iterator = fStates.GetIterator(); + + while (iterator.HasNext()) { + GroupState *state = iterator.Next(); + if (state->Address() == groupAddress) + return state; + } + + if (!create) + return NULL; + + GroupState *state = new (nothrow) GroupState(groupAddress); + if (state) + fStates.Add(state); + return state; +} + + +template void +MulticastFilter::ReturnGroup(GroupState *group) +{ + if (group->IsEmpty()) { + 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 new file mode 100644 index 0000000000..9b8946fd41 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv4/multicast.h @@ -0,0 +1,106 @@ +/* + * Copyright 2007, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Hugo Santos, hugosantos@gmail.com + */ + +#ifndef _PRIVATE_MULTICAST_H_ +#define _PRIVATE_MULTICAST_H_ + +#include +#include + +struct net_interface; + +template +struct MulticastSource { + AddressType address; + list_link link; +}; + +template +class MulticastGroupInterfaceState { +public: + MulticastGroupInterfaceState(net_interface *interface); + ~MulticastGroupInterfaceState(); + + net_interface *Interface() const { return fInterface; } + + status_t Add(const AddressType &address); + status_t Remove(const AddressType &address); + + list_link link; +private: + typedef MulticastSource Source; + typedef DoublyLinkedListCLink SourceLink; + typedef DoublyLinkedList SourceList; + + Source *_Get(const AddressType &address, bool create); + void _Remove(Source *state); + + net_interface *fInterface; + // TODO make this an hash table as well + SourceList fSources; +}; + +template +class MulticastGroupState { +public: + MulticastGroupState(const AddressType &address); + ~MulticastGroupState(); + + const AddressType &Address() const { return fMulticastAddress; } + bool IsEmpty() const + { return fFilterMode == kInclude && fInterfaces.IsEmpty(); } + + status_t Add(net_interface *interface); + status_t Drop(net_interface *interface); + status_t BlockSource(net_interface *interface, + const AddressType &sourceAddress); + status_t UnblockSource(net_interface *interface, + const AddressType &sourceAddress); + status_t AddSSM(net_interface *interface, + const AddressType &sourceAddress); + status_t DropSSM(net_interface *interface, + const AddressType &sourceAddress); + + list_link link; +private: + typedef MulticastGroupInterfaceState InterfaceState; + typedef DoublyLinkedListCLink InterfaceStateLink; + typedef DoublyLinkedList InterfaceList; + + InterfaceState *_GetInterface(net_interface *interface, bool create); + void _RemoveInterface(InterfaceState *state); + + enum FilterMode { + kInclude, + kExclude + }; + + AddressType fMulticastAddress; + FilterMode fFilterMode; + InterfaceList fInterfaces; +}; + +template +class MulticastFilter { +public: + typedef MulticastGroupState GroupState; + + ~MulticastFilter(); + + GroupState *GetGroup(const AddressType &groupAddress, bool create); + void ReturnGroup(GroupState *group); + +private: + typedef DoublyLinkedListCLink GroupStateLink; + typedef DoublyLinkedList States; + + // TODO change this into an hash table or tree + States fStates; +}; + +#endif diff --git a/src/add-ons/kernel/network/stack/datalink.cpp b/src/add-ons/kernel/network/stack/datalink.cpp index aa37a445b4..405a519552 100644 --- a/src/add-ons/kernel/network/stack/datalink.cpp +++ b/src/add-ons/kernel/network/stack/datalink.cpp @@ -419,6 +419,36 @@ datalink_is_local_address(net_domain *_domain, const struct sockaddr *address, } +net_interface * +datalink_get_interface_with_address(net_domain *_domain, + const sockaddr *address) +{ + net_domain_private *domain = (net_domain_private *)_domain; + if (domain == NULL) + return NULL; + + BenaphoreLocker _(domain->lock); + + net_interface *interface = NULL; + + while (true) { + interface = (net_interface *)list_get_next_item( + &domain->interfaces, interface); + if (interface == NULL) + break; + + if (address == NULL) + return interface; + + if (domain->address_module->equal_addresses(interface->address, + address)) + return interface; + } + + return NULL; +} + + static status_t datalink_std_ops(int32 op, ...) { @@ -745,6 +775,7 @@ net_datalink_module_info gNetDatalinkModule = { datalink_control, datalink_send_data, datalink_is_local_address, + datalink_get_interface_with_address, add_route, remove_route,