diff --git a/build/jam/HaikuImage b/build/jam/HaikuImage index bc03a04741..ccc6bcb6e1 100644 --- a/build/jam/HaikuImage +++ b/build/jam/HaikuImage @@ -27,7 +27,7 @@ BEOS_BIN = addattr alert basename beep cat catattr chgrp chmod chop chown clear join keymap kill less lessecho lesskey link listarea listattr listdev listimage listport listres listsem ln locate logger logname ls lsindex makebootable md5sum mimeset mkdir mkindex modifiers mount mountvolume mv open pathchk ping play playfile playsound - playwav ppp_up pppconfig ps pwd + playwav ps pwd query quit renice rm rmattr rmindex rmdir roster route safemode screen_blanker sed settype setversion setvolume sh shutdown sleep sort split strace su sum sync sysinfo tail tar tee top touch tput traceroute translate true tty uname unmount unzip unzipsfx diff --git a/headers/private/net/NetBufferUtilities.h b/headers/private/net/NetBufferUtilities.h new file mode 100644 index 0000000000..5d102322d5 --- /dev/null +++ b/headers/private/net/NetBufferUtilities.h @@ -0,0 +1,129 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef NET_BUFFER_UTILITIES_H +#define NET_BUFFER_UTILITIES_H + + +#include + + +extern net_buffer_module_info *sBufferModule; + +class NetBufferModuleGetter { + public: + static net_buffer_module_info *Get() { return sBufferModule; } +}; + +//! A class to retrieve and remove a header from a buffer +template class NetBufferHeader { + public: + NetBufferHeader(net_buffer *buffer) + : + fBuffer(buffer) + { + } + + ~NetBufferHeader() + { + Remove(); + } + + status_t + Status() + { + return fBuffer->size < sizeof(Type) ? B_BAD_VALUE : B_OK; + } + + Type & + Data() + { + Type *data; + if (Module::Get()->direct_access(fBuffer, 0, sizeof(Type), + (void **)&data) == B_OK) + return *data; + + Module::Get()->read(fBuffer, 0, &fDataBuffer, sizeof(Type)); + return fDataBuffer; + } + + void + Remove() + { + if (fBuffer != NULL) { + Module::Get()->remove_header(fBuffer, sizeof(Type)); + fBuffer = NULL; + } + } + + void + Remove(size_t bytes) + { + if (fBuffer != NULL) { + Module::Get()->remove_header(fBuffer, bytes); + fBuffer = NULL; + } + } + + void + Detach() + { + fBuffer = NULL; + } + + private: + net_buffer *fBuffer; + Type fDataBuffer; +}; + +//! A class to add a header to a buffer +template class NetBufferPrepend { + public: + NetBufferPrepend(net_buffer *buffer) + : + fBuffer(buffer), + fData(NULL) + { + fStatus = Module::Get()->prepend_size(buffer, sizeof(Type), (void **)&fData); + } + + ~NetBufferPrepend() + { + if (fBuffer != NULL) + Detach(); + } + + status_t + Status() + { + return fStatus; + } + + Type & + Data() + { + if (fData != NULL) + return *fData; + + return fDataBuffer; + } + + // TODO: I'm not sure it's a good idea to have Detach() routines + // in NetBufferHeader and here with such a different outcome... + void + Detach() + { + if (fData == NULL) + Module::Get()->write(fBuffer, 0, &fDataBuffer, sizeof(Type)); + fBuffer = NULL; + } + + private: + net_buffer *fBuffer; + status_t fStatus; + Type *fData; + Type fDataBuffer; +}; + +#endif // NET_BUFFER_UTILITIES_H diff --git a/headers/private/net/NetUtilities.h b/headers/private/net/NetUtilities.h new file mode 100644 index 0000000000..4c96498143 --- /dev/null +++ b/headers/private/net/NetUtilities.h @@ -0,0 +1,111 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef NET_UTILITIES_H +#define NET_UTILITIES_H + + +#include +#include + +#include + +class Checksum { + public: + struct BufferHelper { + BufferHelper(net_buffer *_buffer, net_buffer_module_info *_bufferModule) + : buffer(_buffer), + bufferModule(_bufferModule) + { + } + net_buffer *buffer; + net_buffer_module_info *bufferModule; + }; + + Checksum(); + + Checksum& operator<<(uint8 val); + Checksum& operator<<(uint16 val); + Checksum& operator<<(uint32 val); + Checksum& operator<<(const BufferHelper &bufferHelper); + + operator uint16(); + + private: + uint32 fSum; +}; + +inline Checksum::Checksum() + : fSum(0) +{ +} + +inline Checksum& Checksum::operator<<(uint8 _val) { +#if B_HOST_IS_LENDIAN + fSum += _val; +#else + uint16 val = _val; + fSum += val << 8; +#endif + return *this; +} + +inline Checksum& Checksum::operator<<(uint16 val) { + fSum += val; + return *this; +} + +inline Checksum& Checksum::operator<<(uint32 val) { + fSum += (val & 0xFFFF) + (val >> 16); + return *this; +} + +inline Checksum& Checksum::operator<<(const BufferHelper &bufferHelper) { + net_buffer *buffer = bufferHelper.buffer; + fSum += bufferHelper.bufferModule->checksum(buffer, 0, buffer->size, false); + return *this; +} + +inline Checksum::operator uint16() { + while (fSum >> 16) { + fSum = (fSum & 0xffff) + (fSum >> 16); + } + uint16 result = (uint16)fSum; + result ^= 0xFFFF; + return result; +} + + +// helper class that prints an address (and optionally a port) into a buffer that +// is automatically freed at end of scope: +class AddressString { +public: + inline AddressString(net_domain *domain, const sockaddr *address, + bool printPort = false) + : fBuffer(NULL) + { + domain->address_module->print_address(address, &fBuffer, printPort); + } + + inline AddressString(net_domain *domain, const sockaddr &address, + bool printPort = false) + : fBuffer(NULL) + { + domain->address_module->print_address(&address, &fBuffer, printPort); + } + + inline ~AddressString() + { + free(fBuffer); + } + + inline char *Data() + { + return fBuffer; + } +private: + char *fBuffer; +}; + +#endif // NET_UTILITIES_H diff --git a/headers/private/net/arp_control.h b/headers/private/net/arp_control.h new file mode 100644 index 0000000000..c60e447eb6 --- /dev/null +++ b/headers/private/net/arp_control.h @@ -0,0 +1,38 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef ARP_CONTROL_H +#define ARP_CONTROL_H + + +#include + +#include + + +// ARP flags +#define ARP_FLAG_LOCAL 0x01 +#define ARP_FLAG_REJECT 0x02 +#define ARP_FLAG_PERMANENT 0x04 +#define ARP_FLAG_PUBLISH 0x08 + + +// generic syscall interface +#define ARP_SYSCALLS "network/arp" + +#define ARP_SET_ENTRY 1 +#define ARP_GET_ENTRY 2 +#define ARP_GET_ENTRIES 3 +#define ARP_DELETE_ENTRY 4 +#define ARP_FLUSH_ENTRIES 5 +#define ARP_IGNORE_REPLIES 6 + +struct arp_control { + in_addr_t address; + uint8 ethernet_address[ETHER_ADDRESS_LENGTH]; + uint32 flags; + uint32 cookie; +}; + +#endif // ARP_CONTROL_H diff --git a/headers/private/net/ether_driver.h b/headers/private/net/ether_driver.h new file mode 100644 index 0000000000..55dc18c466 --- /dev/null +++ b/headers/private/net/ether_driver.h @@ -0,0 +1,71 @@ +/* + * ether_driver.h + * + * Ethernet driver: handles NE2000 and 3C503 cards + */ +/* + Copyright 1999, Be Incorporated. All Rights Reserved. + This file may be used under the terms of the Be Sample Code License. +*/ +#ifndef _ETHER_DRIVER_H +#define _ETHER_DRIVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * ioctls: belongs in a public header file + * somewhere, so that the net_server and other ethernet drivers can use. + */ +enum { + ETHER_GETADDR = B_DEVICE_OP_CODES_END, /* get ethernet address */ + ETHER_INIT, /* set irq and port */ + ETHER_NONBLOCK, /* set/unset nonblocking mode */ + ETHER_ADDMULTI, /* add multicast addr */ + ETHER_REMMULTI, /* rem multicast addr */ + ETHER_SETPROMISC, /* set promiscuous */ + ETHER_GETFRAMESIZE, /* get frame size */ + ETHER_ADDTIMESTAMP, /* (try to) add timestamps to packets (BONE ext) */ + ETHER_HASIOVECS, /* does the driver implement writev ? (BONE ext) (bool *) */ + ETHER_GETIFTYPE, /* get the IFT_ type of the interface (int *) */ + ETHER_GETLINKSTATE /* get line speed, quality, duplex mode, etc. */ +}; + + +/* + * 48-bit ethernet address, passed back from ETHER_GETADDR + */ +typedef struct { + unsigned char ebyte[6]; +} ether_address_t; + + +/* + * info passed to ETHER_INIT + */ + +typedef struct ether_init_params { + short port; + short irq; + unsigned long mem; +} ether_init_params_t; + +/* + * info returned from ETHER_GETLINKSTATE + */ + +typedef struct ether_link_state { + float link_speed; /* In Mbits per second */ + float link_quality; /* Set to zero if not connected */ + char duplex_mode; /* Set to 1 for full duplex, 0 for half */ +} ether_link_state_t; + +#ifdef __cplusplus +} +#endif + + +#endif /* _ETHER_DRIVER_H */ diff --git a/headers/private/net/ethernet.h b/headers/private/net/ethernet.h new file mode 100644 index 0000000000..cb6d5f0de6 --- /dev/null +++ b/headers/private/net/ethernet.h @@ -0,0 +1,35 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef ETHERNET_H +#define ETHERNET_H + + +#include + + +#define ETHER_ADDRESS_LENGTH 6 +#define ETHER_CRC_LENGTH 4 +#define ETHER_HEADER_LENGTH 14 + +#define ETHER_MIN_FRAME_SIZE 64 +#define ETHER_MAX_FRAME_SIZE 1514 + +struct ether_header { + uint8 destination[ETHER_ADDRESS_LENGTH]; + uint8 source[ETHER_ADDRESS_LENGTH]; + uint16 type; +} _PACKED; + +#define ETHER_FRAME_TYPE 0x00010000 + +// ethernet types +#define ETHER_TYPE_IP 0x0800 +#define ETHER_TYPE_ARP 0x0806 +#define ETHER_TYPE_IPX 0x8137 +#define ETHER_TYPE_IPV6 0x86dd +#define ETHER_TYPE_PPPOE_DISCOVERY 0x8863 // PPPoE discovery stage +#define ETHER_TYPE_PPPOE 0x8864 // PPPoE session stage + +#endif // ETHERNET_H diff --git a/headers/private/net/net_buffer.h b/headers/private/net/net_buffer.h new file mode 100644 index 0000000000..d34efa4cdf --- /dev/null +++ b/headers/private/net/net_buffer.h @@ -0,0 +1,77 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef NET_BUFFER_H +#define NET_BUFFER_H + + +#include + +#include +#include + + +#define NET_BUFFER_MODULE_NAME "network/stack/buffer/v1" + +typedef struct net_buffer { + struct list_link link; + + // TODO: we should think about moving the address fields into the buffer data itself + // via associated data or something like this. Or this structure as a whole, too... + struct sockaddr_storage source; + struct sockaddr_storage destination; + struct net_interface *interface; + uint32 flags; + uint32 size; + uint8 protocol; +} net_buffer; + +struct net_buffer_module_info { + module_info info; + + net_buffer * (*create)(size_t headerSpace); + void (*free)(net_buffer *buffer); + + net_buffer * (*duplicate)(net_buffer *from); + net_buffer * (*clone)(net_buffer *from, bool shareFreeSpace); + net_buffer * (*split)(net_buffer *from, uint32 offset); + status_t (*merge)(net_buffer *buffer, net_buffer *with, bool after); + + status_t (*prepend_size)(net_buffer *buffer, size_t size, + void **_contiguousBuffer); + status_t (*prepend)(net_buffer *buffer, const void *data, + size_t bytes); + status_t (*append_size)(net_buffer *buffer, size_t size, + void **_contiguousBuffer); + status_t (*append)(net_buffer *buffer, const void *data, + size_t bytes); + status_t (*insert)(net_buffer *buffer, uint32 offset, + const void *data, size_t bytes, uint32 flags); + status_t (*remove)(net_buffer *buffer, uint32 offset, + size_t bytes); + status_t (*remove_header)(net_buffer *buffer, size_t bytes); + status_t (*remove_trailer)(net_buffer *buffer, size_t bytes); + status_t (*trim)(net_buffer *buffer, size_t newSize); + + status_t (*associate_data)(net_buffer *buffer, void *data); + + status_t (*direct_access)(net_buffer *buffer, uint32 offset, + size_t bytes, void **_data); + status_t (*read)(net_buffer *buffer, uint32 offset, void *data, + size_t bytes); + status_t (*write)(net_buffer *buffer, uint32 offset, + const void *data, size_t bytes); + + int32 (*checksum)(net_buffer *buffer, uint32 offset, size_t bytes, + bool finalize); + status_t (*get_memory_map)(net_buffer *buffer, + struct iovec *iovecs, uint32 vecCount); + uint32 (*get_iovecs)(net_buffer *buffer, + struct iovec *iovecs, uint32 vecCount); + uint32 (*count_iovecs)(net_buffer *buffer); + + void (*dump)(net_buffer *buffer); +}; + +#endif // NET_BUFFER_H diff --git a/headers/private/net/net_datalink.h b/headers/private/net/net_datalink.h new file mode 100644 index 0000000000..e521b55256 --- /dev/null +++ b/headers/private/net/net_datalink.h @@ -0,0 +1,131 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef NET_DATALINK_H +#define NET_DATALINK_H + + +#include +#include + +#include + +#include + + +#define NET_DATALINK_MODULE_NAME "network/stack/datalink/v1" + +typedef struct net_datalink_protocol net_datalink_protocol; + +typedef struct net_domain { + const char *name; + int family; + struct list interfaces; + + struct net_protocol_module_info *module; + struct net_address_module_info *address_module; +} net_domain; + +struct net_interface { + struct list_link link; + struct net_domain *domain; + struct net_device *device; + struct net_datalink_protocol *first_protocol; + struct net_datalink_protocol_module_info *first_info; + + char name[IF_NAMESIZE]; + struct sockaddr *address; + struct sockaddr *destination; + struct sockaddr *mask; + uint32 index; + uint32 flags; + uint8 type; + uint32 mtu; + uint32 metric; +}; + +struct net_route { + struct sockaddr *destination; + struct sockaddr *mask; + struct sockaddr *gateway; + uint32 flags; + uint32 mtu; + struct net_interface *interface; +}; + +struct net_route_info { + struct list_link link; + struct net_route *route; + struct sockaddr address; +}; + +struct net_datalink_module_info { + module_info info; + + status_t (*control)(struct net_domain *domain, int32 option, void *value, + size_t *_length); + status_t (*send_data)(struct net_route *route, struct net_buffer *buffer); + + bool (*is_local_address)(struct net_domain *domain, + const struct sockaddr *address, + net_interface **_interface = NULL, + uint32 *_matchedType = NULL); + + // routes + status_t (*add_route)(struct net_domain *domain, + const struct net_route *route); + status_t (*remove_route)(struct net_domain *domain, + const struct net_route *route); + struct net_route *(*get_route)(struct net_domain *domain, + const struct sockaddr *address); + void (*put_route)(struct net_domain *domain, struct net_route *route); + + status_t (*register_route_info)(struct net_domain *domain, + struct net_route_info *info); + status_t (*unregister_route_info)(struct net_domain *domain, + struct net_route_info *info); + status_t (*update_route_info)(struct net_domain *domain, + struct net_route_info *info); +}; + +struct net_address_module_info { + module_info info; + + status_t (*copy_address)(const sockaddr *from, sockaddr **to, + bool replaceWithZeros = false, sockaddr *mask = NULL); + + status_t (*mask_address)(const sockaddr *address, const sockaddr *mask, + sockaddr *result); + + bool (*equal_addresses)(const sockaddr *a, const sockaddr *b); + bool (*equal_ports)(const sockaddr *a, const sockaddr *b); + bool (*equal_addresses_and_ports)(const sockaddr *a, const sockaddr *b); + bool (*equal_masked_addresses)(const sockaddr *a, const sockaddr *b, + const sockaddr *mask); + bool (*is_empty_address)(const sockaddr *address, bool checkPort = true); + + int32 (*first_mask_bit)(sockaddr *mask); + + bool (*check_mask)(sockaddr *address); + + status_t (*print_address)(const sockaddr *address, char **buffer, + bool printPort); + + uint16 (*get_port)(const sockaddr *address); + status_t (*set_port)(sockaddr *address, uint16 port); + + status_t (*set_to)(sockaddr *address, const sockaddr *from); + status_t (*set_to_empty_address)(sockaddr *address); + + uint32 (*hash_address_pair)(const sockaddr *ourAddress, + const sockaddr *peerAddress); + + status_t (*checksum_address)(struct Checksum *checksum, + const sockaddr *address); + + bool (*matches_broadcast_address)(const sockaddr *address, + const sockaddr *mask, const sockaddr *broadcastAddr); +}; + +#endif // NET_DATALINK_H diff --git a/headers/private/net/net_datalink_protocol.h b/headers/private/net/net_datalink_protocol.h new file mode 100644 index 0000000000..9d7caee966 --- /dev/null +++ b/headers/private/net/net_datalink_protocol.h @@ -0,0 +1,35 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef NET_DATALINK_PROTOCOL_H +#define NET_DATALINK_PROTOCOL_H + + +#include + + +typedef struct net_datalink_protocol { + struct net_datalink_protocol *next; + struct net_datalink_protocol_module_info *module; + struct net_interface *interface; +} net_datalink_protocol; + +struct net_datalink_protocol_module_info { + module_info info; + + status_t (*init_protocol)(struct net_interface *interface, + net_datalink_protocol **_protocol); + status_t (*uninit_protocol)(net_datalink_protocol *self); + + status_t (*send_data)(net_datalink_protocol *self, + net_buffer *buffer); + + status_t (*interface_up)(net_datalink_protocol *self); + void (*interface_down)(net_datalink_protocol *self); + + status_t (*control)(net_datalink_protocol *self, + int32 op, void *argument, size_t length); +}; + +#endif // NET_DATALINK_PROTOCOL_H diff --git a/headers/private/net/net_device.h b/headers/private/net/net_device.h new file mode 100644 index 0000000000..46903cb3f3 --- /dev/null +++ b/headers/private/net/net_device.h @@ -0,0 +1,60 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef NET_DEVICE_H +#define NET_DEVICE_H + + +#include + +#include + + +struct net_hardware_address { + uint8 data[64]; + uint8 length; +}; + +struct net_device { + struct net_device_module_info *module; + + char name[IF_NAMESIZE]; + uint32 index; + uint32 flags; // IFF_LOOPBACK, ... + uint32 type; // IFT_ETHER, ... + size_t mtu; + uint32 media; + size_t header_length; + + struct net_hardware_address address; + + ifreq_stats stats; +}; + +struct net_device_module_info { + struct module_info info; + + status_t (*init_device)(const char *name, struct net_device **_device); + status_t (*uninit_device)(struct net_device *device); + + status_t (*up)(struct net_device *device); + void (*down)(struct net_device *device); + + status_t (*control)(struct net_device *device, int32 op, + void *argument, size_t length); + + status_t (*send_data)(struct net_device *device, struct net_buffer *buffer); + status_t (*receive_data)(struct net_device *device, struct net_buffer **_buffer); + + status_t (*set_mtu)(struct net_device *device, size_t mtu); + status_t (*set_promiscuous)(struct net_device *device, bool promiscuous); + status_t (*set_media)(struct net_device *device, uint32 media); + + status_t (*get_multicast_addrs)(struct net_device *device, + net_hardware_address **addressArray, uint32 count); + status_t (*set_multicast_addrs)(struct net_device *device, + const net_hardware_address **addressArray, uint32 count); +}; + +#endif // NET_DEVICE_H diff --git a/headers/private/net/net_protocol.h b/headers/private/net/net_protocol.h new file mode 100644 index 0000000000..4f50c1f9db --- /dev/null +++ b/headers/private/net/net_protocol.h @@ -0,0 +1,64 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef NET_PROTOCOL_H +#define NET_PROTOCOL_H + + +#include +#include + + +// level flags to pass to control() +#define LEVEL_SET_OPTION 0x10000000 +#define LEVEL_GET_OPTION 0x20000000 +#define LEVEL_DRIVER_IOCTL 0x0f000000 +#define LEVEL_MASK 0x0fffffff + +typedef struct net_protocol { + struct net_protocol *next; + struct net_protocol_module_info *module; + net_socket *socket; +} net_protocol; + +struct net_protocol_module_info { + module_info info; + + net_protocol *(*init_protocol)(net_socket *socket); + status_t (*uninit_protocol)(net_protocol *self); + + status_t (*open)(net_protocol *self); + status_t (*close)(net_protocol *self); + status_t (*free)(net_protocol *self); + + status_t (*connect)(net_protocol *self, const struct sockaddr *address); + status_t (*accept)(net_protocol *self, struct net_socket **_acceptedSocket); + status_t (*control)(net_protocol *self, int level, int option, void *value, + size_t *_length); + + status_t (*bind)(net_protocol *self, struct sockaddr *address); + status_t (*unbind)(net_protocol *self, struct sockaddr *address); + status_t (*listen)(net_protocol *self, int count); + status_t (*shutdown)(net_protocol *self, int direction); + + status_t (*send_data)(net_protocol *self, net_buffer *buffer); + status_t (*send_routed_data)(net_protocol *self, + struct net_route *route, net_buffer *buffer); + ssize_t (*send_avail)(net_protocol *self); + + status_t (*read_data)(net_protocol *self, size_t numBytes, uint32 flags, + net_buffer **_buffer); + ssize_t (*read_avail)(net_protocol *self); + + struct net_domain *(*get_domain)(net_protocol *self); + 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 (*error_reply)(net_protocol *self, net_buffer *causedError, + uint32 code, void *errorData); +}; + +#endif // NET_PROTOCOL_H diff --git a/headers/private/net/net_routing_info.h b/headers/private/net/net_routing_info.h new file mode 100644 index 0000000000..287f957ac7 --- /dev/null +++ b/headers/private/net/net_routing_info.h @@ -0,0 +1,17 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef NET_ROUTING_INFO_H +#define NET_ROUTING_INFO_H + + +struct net_routing_info { + void *root; + void *default_route; + + int addr_start; + int addr_end; +}; + +#endif // NET_ROUTING_INFO_H diff --git a/headers/private/net/net_socket.h b/headers/private/net/net_socket.h new file mode 100644 index 0000000000..7eb38827b3 --- /dev/null +++ b/headers/private/net/net_socket.h @@ -0,0 +1,81 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef NET_SOCKET_H +#define NET_SOCKET_H + + +#include +#include + + +#define NET_SOCKET_MODULE_NAME "network/stack/socket/v1" + +typedef struct net_socket { + struct net_protocol *first_protocol; + struct net_protocol_module_info *first_info; + + int family; + int type; + int protocol; + + struct sockaddr_storage address; + struct sockaddr_storage peer; + + int options; + int linger; + + struct { + uint32 buffer_size; + uint32 low_water_mark; + bigtime_t timeout; + } send, receive; +} net_socket; + +struct net_socket_module_info { + struct module_info info; + + status_t (*socket)(int family, int type, int protocol, net_socket **_socket); + status_t (*close)(net_socket *socket); + status_t (*free)(net_socket *socket); + + status_t (*readv)(net_socket *socket, const iovec *vecs, size_t vecCount, + size_t *_length); + status_t (*writev)(net_socket *socket, const iovec *vecs, size_t vecCount, + size_t *_length); + status_t (*control)(net_socket *socket, int32 op, void *data, size_t length); + + ssize_t (*read_avail)(net_socket *socket); + ssize_t (*send_avail)(net_socket *socket); + + status_t (*send_data)(net_socket *socket, net_buffer *buffer); + status_t (*receive_data)(net_socket *socket, size_t length, uint32 flags, + net_buffer **_buffer); + + // standard socket API + int (*accept)(net_socket *socket, struct sockaddr *address, + socklen_t *_addressLength, net_socket **_acceptedSocket); + int (*bind)(net_socket *socket, const struct sockaddr *address, + socklen_t addressLength); + int (*connect)(net_socket *socket, const struct sockaddr *address, + socklen_t addressLength); + int (*getpeername)(net_socket *socket, struct sockaddr *address, + socklen_t *_addressLength); + int (*getsockname)(net_socket *socket, struct sockaddr *address, + socklen_t *_addressLength); + int (*getsockopt)(net_socket *socket, int level, int option, + void *optionValue, int *_optionLength); + int (*listen)(net_socket *socket, int backlog); + ssize_t (*recv)(net_socket *socket, void *data, size_t length, int flags); + ssize_t (*recvfrom)(net_socket *socket, void *data, size_t length, int flags, + struct sockaddr *address, socklen_t *_addressLength); + ssize_t (*send)(net_socket *socket, const void *data, size_t length, int flags); + ssize_t (*sendto)(net_socket *socket, const void *data, size_t length, + int flags, const struct sockaddr *address, socklen_t addressLength); + int (*setsockopt)(net_socket *socket, int level, int option, + const void *optionValue, int optionLength); + int (*shutdown)(net_socket *socket, int direction); +}; + +#endif // NET_SOCKET_H diff --git a/headers/private/net/net_stack.h b/headers/private/net/net_stack.h new file mode 100644 index 0000000000..9f97fcd702 --- /dev/null +++ b/headers/private/net/net_stack.h @@ -0,0 +1,95 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef NET_STACK_H +#define NET_STACK_H + + +#include +#include + +#include + + +#define NET_STACK_MODULE_NAME "network/stack/v1" + +struct net_fifo { + benaphore lock; + sem_id notify; + int32 waiting; + + size_t max_bytes; + size_t current_bytes; + + struct list buffers; +}; + +typedef void (*net_timer_func)(struct net_timer *timer, void *data); + +struct net_timer { + struct list_link link; + net_timer_func hook; + void *data; + bigtime_t due; +}; + +typedef int32 (*net_deframe_func)(struct net_device *device, struct net_buffer *buffer); +typedef status_t (*net_receive_func)(void *cookie, struct net_buffer *buffer); + +struct net_stack_module_info { + module_info info; + + status_t (*register_domain)(int family, const char *name, + struct net_protocol_module_info *module, + struct net_address_module_info *addressModule, + struct net_domain **_domain); + status_t (*unregister_domain)(struct net_domain *domain); + struct net_domain *(*get_domain)(int family); + + status_t (*register_domain_protocols)(int family, int type, int protocol, ...); + status_t (*register_domain_datalink_protocols)(int family, int type, ...); + status_t (*register_domain_receiving_protocol)(int family, int type, + const char *moduleName); + + status_t (*get_domain_receiving_protocol)(struct net_domain *domain, uint32 type, + struct net_protocol_module_info **_module); + status_t (*put_domain_receiving_protocol)(struct net_domain *domain, uint32 type); + + // devices + status_t (*register_device_deframer)(struct net_device *device, + net_deframe_func deframeFunc); + status_t (*unregister_device_deframer)(struct net_device *device); + + status_t (*register_domain_device_handler)(struct net_device *device, + int32 type, struct net_domain *domain); + status_t (*register_device_handler)(struct net_device *device, int32 type, + net_receive_func receiveFunc, void *cookie); + status_t (*unregister_device_handler)(struct net_device *device, int32 type); + + status_t (*register_device_monitor)(struct net_device *device, + net_receive_func receiveFunc, void *cookie); + status_t (*unregister_device_monitor)(struct net_device *device, + net_receive_func receiveFunc, void *cookie); + + status_t (*device_removed)(struct net_device *device); + + // Utility Functions + + // checksum + uint16 (*checksum)(uint8 *buffer, size_t length); + + // fifo + status_t (*init_fifo)(struct net_fifo *fifo, const char *name, size_t maxBytes); + void (*uninit_fifo)(struct net_fifo *fifo); + status_t (*fifo_enqueue_buffer)(struct net_fifo *fifo, struct net_buffer *buffer); + ssize_t (*fifo_dequeue_buffer)(struct net_fifo *fifo, uint32 flags, + bigtime_t timeout, struct net_buffer **_buffer); + status_t (*clear_fifo)(struct net_fifo *fifo); + + // timer + void (*init_timer)(struct net_timer *timer, net_timer_func hook, void *data); + void (*set_timer)(struct net_timer *timer, bigtime_t delay); +}; + +#endif // NET_STACK_H diff --git a/headers/private/net/net_stack_driver.h b/headers/private/net/net_stack_driver.h new file mode 100644 index 0000000000..3629d14cc0 --- /dev/null +++ b/headers/private/net/net_stack_driver.h @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2006, Haiku, Inc. All Rights Reserved. + * This file may be used under the terms of the MIT License. + */ +#ifndef NET_STACK_DRIVER_H +#define NET_STACK_DRIVER_H + + +#include + +#include +#include + + +// Forward declaration +struct sockaddr; + +#define NET_STACK_DRIVER_DEV "net/stack" +#define NET_STACK_DRIVER_PATH "/dev/" NET_STACK_DRIVER_DEV + +enum { + NET_STACK_IOCTL_BASE = 8800, + NET_STACK_IOCTL_END = 8999, + + // ops not acting on an existing socket + NET_STACK_SOCKET = NET_STACK_IOCTL_BASE, // socket_args * + NET_STACK_GET_COOKIE, // void ** + NET_STACK_CONTROL_NET_MODULE, // control_net_module_args * + NET_STACK_SYSCTL, // sysctl_args * + + // ops acting on an existing socket + NET_STACK_BIND, // sockaddr_args * + NET_STACK_RECVFROM, // struct msghdr * + NET_STACK_RECV, // transfer_args * + NET_STACK_SENDTO, // struct msghdr * + NET_STACK_SEND, // transfer_args * + NET_STACK_LISTEN, // int_args * (value = backlog) + NET_STACK_ACCEPT, // sockaddr_args * + NET_STACK_CONNECT, // sockaddr_args * + NET_STACK_SHUTDOWN, // int_args * (value = how) + NET_STACK_GETSOCKOPT, // sockopt_args * + NET_STACK_SETSOCKOPT, // sockopt_args * + NET_STACK_GETSOCKNAME, // sockaddr_args * + NET_STACK_GETPEERNAME, // sockaddr_args * + NET_STACK_SOCKETPAIR, // socketpair_args * + + // TODO: remove R5 select() emulation + NET_STACK_SELECT, // select_args * + NET_STACK_DESELECT, // select_args * + + NET_STACK_NOTIFY_SOCKET_EVENT, // notify_socket_event_args * (userland stack only) + + NET_STACK_IOCTL_MAX +}; + +struct sockaddr_args { // used by NET_STACK_CONNECT/_BIND/_GETSOCKNAME/_GETPEERNAME + struct sockaddr *address; + socklen_t address_length; +}; + +struct sockopt_args { // used by NET_STACK_SETSOCKOPT/_GETSOCKOPT + int level; + int option; + void *value; + int length; +}; + +struct transfer_args { // used by NET_STACK_SEND/_RECV + void *data; + size_t data_length; + int flags; + struct sockaddr *address; // only used for recvfrom() and sendto() + socklen_t address_length; // "" +}; + +struct socket_args { // used by NET_STACK_SOCKET + int family; + int type; + int protocol; +}; + +struct socketpair_args { // used by NET_STACK_SOCKETPAIR + void *cookie; +}; + +struct accept_args { // used by NET_STACK_ACCEPT + void *cookie; + struct sockaddr *address; + socklen_t address_length; +}; + +struct sysctl_args { // used by NET_STACK_SYSCTL + int *name; + uint namelen; + void *oldp; + size_t *oldlenp; + void *newp; + size_t newlen; +}; + +struct control_net_module_args { // used by NET_STACK_CONTROL_NET_MODULE + const char *name; + uint32 op; + void *data; + size_t length; +}; + +/* + Userland stack driver on_socket_event() callback mecanism implementation: + the driver start a kernel thread waiting on a port for + a NET_STACK_SOCKET_EVENT_MSG message, which come with a socket_event_data block. + + The on_socket_event() mechanism stay in driver (kernelland) because it's + only there we can call kernel's notify_select_event() on BONE systems. + For non-BONE systems, we use our own r5_notify_select_event() + implementation, that could be moved into the userland net_server code, + but it'll have split (again!) the /dev/net/stack driver code... +*/ + +struct notify_socket_event_args { // used by NET_STACK_NOTIFY_SOCKET_EVENT + port_id notify_port; // port waiting for notification, -1 = stop notify + void *cookie; // this cookie value will be pass back in the socket_event_args +}; + +#define NET_STACK_SOCKET_EVENT_NOTIFICATION 'sevn' + +struct socket_event_data { + uint32 event; // B_SELECT_READ, B_SELECT_WRITE or B_SELECT_ERROR + void *cookie; // The cookie as set in notify_socket_event_args for this socket +}; + +/* + R5.0.3 and before select() kernel support is too buggy to be used, so + here are the structures we used to support select() on sockets, and *only* on + sockets! +*/ + +struct select_args { // used by NET_STACK_SELECT and NET_STACK_DESELECT + struct selectsync *sync; // in fact, it's the area_id of a r5_selectsync struct!!! + uint32 ref; +}; + +struct r5_selectsync { + sem_id lock; // lock this r5_selectsync + sem_id wakeup; // sem to release to wakeup select() + struct fd_set rbits; // read event bits field + struct fd_set wbits; // write event bits field + struct fd_set ebits; // exception event bits field +}; + +#endif /* NET_STACK_DRIVER_H */ diff --git a/headers/private/net/userland_ipc.h b/headers/private/net/userland_ipc.h new file mode 100644 index 0000000000..8fc7ae673f --- /dev/null +++ b/headers/private/net/userland_ipc.h @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2006, Haiku, Inc. All Rights Reserved. + * This file may be used under the terms of the MIT License. + */ +#ifndef USERLAND_IPC_H +#define USERLAND_IPC_H + +/*! userland_ipc - Communication between the network driver + and the userland stack. +*/ + +#include +#include + +#include "net_stack_driver.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NET_STACK_PORTNAME "net_server connection" + +enum { + NET_STACK_OPEN = NET_STACK_IOCTL_MAX, + NET_STACK_CLOSE, + NET_STACK_NEW_CONNECTION, +}; + +#define MAX_NET_AREAS 16 + +typedef struct { + area_id id; + uint8 *offset; +} net_area_info; + +typedef struct { + int32 op; +// int32 buffer; + uint8 *data; + int32 length; + int32 result; + net_area_info area[MAX_NET_AREAS]; +} net_command; + +#define CONNECTION_QUEUE_LENGTH 128 +#define CONNECTION_COMMAND_SIZE 2048 + +typedef struct { + port_id port; + area_id area; + thread_id socket_thread; + + sem_id commandSemaphore; // command queue + uint32 numCommands,bufferSize; +} net_connection; + + +extern status_t init_userland_ipc(void); +extern void shutdown_userland_ipc(void); + + +#ifdef __cplusplus +} // end of extern "C" +#endif + +#endif /* USERLAND_IPC_H */ diff --git a/src/add-ons/kernel/network/datalink_protocols/Jamfile b/src/add-ons/kernel/network/datalink_protocols/Jamfile new file mode 100644 index 0000000000..d059982e9c --- /dev/null +++ b/src/add-ons/kernel/network/datalink_protocols/Jamfile @@ -0,0 +1,4 @@ +SubDir HAIKU_TOP src add-ons kernel network datalink_protocols ; + +SubInclude HAIKU_TOP src add-ons kernel network datalink_protocols arp ; +SubInclude HAIKU_TOP src add-ons kernel network datalink_protocols ethernet_frame ; diff --git a/src/add-ons/kernel/network/datalink_protocols/arp/Jamfile b/src/add-ons/kernel/network/datalink_protocols/arp/Jamfile new file mode 100644 index 0000000000..f8fc3f2184 --- /dev/null +++ b/src/add-ons/kernel/network/datalink_protocols/arp/Jamfile @@ -0,0 +1,25 @@ +SubDir HAIKU_TOP src add-ons kernel network datalink_protocols arp ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UsePrivateHeaders net ; + +KernelAddon arp : kernel haiku_network datalink_protocols : + arp.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network/datalink_protocols + : arp ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network datalink_protocols ; diff --git a/src/add-ons/kernel/network/datalink_protocols/arp/arp.cpp b/src/add-ons/kernel/network/datalink_protocols/arp/arp.cpp new file mode 100644 index 0000000000..92fb9e6531 --- /dev/null +++ b/src/add-ons/kernel/network/datalink_protocols/arp/arp.cpp @@ -0,0 +1,974 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + +//! Ethernet Address Resolution Protocol, see RFC 826. + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +#define TRACE_ARP +#ifdef TRACE_ARP +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + + +struct arp_header { + uint16 hardware_type; + uint16 protocol_type; + uint8 hardware_length; + uint8 protocol_length; + uint16 opcode; + + // TODO: this should be a variable length header, but for our current + // usage (Ethernet/IPv4), this should work fine. + uint8 hardware_sender[6]; + in_addr_t protocol_sender; + uint8 hardware_target[6]; + in_addr_t protocol_target; +} _PACKED; + +#define ARP_OPCODE_REQUEST 1 +#define ARP_OPCODE_REPLY 2 + +#define ARP_HARDWARE_TYPE_ETHER 1 + +struct arp_entry { + arp_entry *next; + in_addr_t protocol_address; + sockaddr_dl hardware_address; + uint32 flags; + sem_id resolved_sem; + net_buffer *request_buffer; + net_timer timer; + uint32 timer_state; + bigtime_t timestamp; + net_datalink_protocol *protocol; + + static int Compare(void *_entry, const void *_key); + static uint32 Hash(void *_entry, const void *_key, uint32 range); + static arp_entry *Lookup(in_addr_t protocolAddress); + static arp_entry *Add(in_addr_t protocolAddress, sockaddr_dl *hardwareAddress, + uint32 flags); +}; + +// see arp_control.h for flags + +#define ARP_NO_STATE 0 +#define ARP_STATE_REQUEST 1 +#define ARP_STATE_LAST_REQUEST 5 +#define ARP_STATE_REQUEST_FAILED 6 +#define ARP_STATE_REMOVE_FAILED 7 +#define ARP_STATE_STALE 8 + +#define ARP_STALE_TIMEOUT 30 * 60000000LL // 30 minutes +#define ARP_REJECT_TIMEOUT 20000000LL // 20 seconds +#define ARP_REQUEST_TIMEOUT 1000000LL // 1 second + +struct arp_protocol : net_datalink_protocol { +}; + + +static void arp_timer(struct net_timer *timer, void *data); + +struct net_buffer_module_info *sBufferModule; +static net_stack_module_info *sStackModule; +static hash_table *sCache; +static benaphore sCacheLock; +static bool sIgnoreReplies; + + +/*static*/ int +arp_entry::Compare(void *_entry, const void *_key) +{ + arp_entry *entry = (arp_entry *)_entry; + in_addr_t key = (in_addr_t)_key; + + if (entry->protocol_address == key) + return 0; + + return 1; +} + + +/*static*/ uint32 +arp_entry::Hash(void *_entry, const void *_key, uint32 range) +{ + arp_entry *entry = (arp_entry *)_entry; + in_addr_t key = (in_addr_t)_key; + +// TODO: check if this makes a good hash... +#define HASH(o) (((o >> 24) ^ (o >> 16) ^ (o >> 8) ^ o) % range) + +#ifdef TRACE_ARP + in_addr_t a = entry ? entry->protocol_address : key; + dprintf("%ld.%ld.%ld.%ld: Hash: %lu\n", a >> 24, (a >> 16) & 0xff, + (a >> 8) & 0xff, a & 0xff, HASH(a)); +#endif + + if (entry != NULL) + return HASH(entry->protocol_address); + + return HASH(key); +#undef HASH +} + + +/*static*/ arp_entry * +arp_entry::Lookup(in_addr_t address) +{ + return (arp_entry *)hash_lookup(sCache, (void *)address); +} + + +/*static*/ arp_entry * +arp_entry::Add(in_addr_t protocolAddress, sockaddr_dl *hardwareAddress, + uint32 flags) +{ + arp_entry *entry = new (std::nothrow) arp_entry; + if (entry == NULL) + return NULL; + + entry->protocol_address = protocolAddress; + entry->flags = flags; + entry->timestamp = system_time(); + entry->protocol = NULL; + entry->request_buffer = NULL; + entry->timer_state = ARP_NO_STATE; + sStackModule->init_timer(&entry->timer, arp_timer, entry); + + if (hardwareAddress != NULL) { + // this entry is already resolved + entry->hardware_address = *hardwareAddress; + entry->hardware_address.sdl_e_type = ETHER_TYPE_IP; + entry->resolved_sem = -1; + } else { + // this entry still needs to be resolved + entry->hardware_address.sdl_alen = 0; + + char name[32]; + snprintf(name, sizeof(name), "arp %08lx", protocolAddress); + entry->resolved_sem = create_sem(0, name); + if (entry->resolved_sem < B_OK) { + delete entry; + return NULL; + } + } + if (entry->hardware_address.sdl_len != sizeof(sockaddr_dl)) { + // explicitly set correct length in case our caller hasn't... + entry->hardware_address.sdl_len = sizeof(sockaddr_dl); + } + + if (hash_insert(sCache, entry) != B_OK) { + delete entry; + return NULL; + } + + return entry; +} + + +// #pragma mark - + + +/*! + Updates the entry determined by \a protocolAddress with the specified + \a hardwareAddress. + If such an entry does not exist yet, a new entry is added. If you try + to update a local existing entry but didn't ask for it (by setting + \a flags to ARP_FLAG_LOCAL), an error is returned. + + This function does not lock the cache - you have to do it yourself + before calling it. +*/ +status_t +arp_update_entry(in_addr_t protocolAddress, sockaddr_dl *hardwareAddress, + uint32 flags, arp_entry **_entry = NULL) +{ + arp_entry *entry = arp_entry::Lookup(protocolAddress); + if (entry != NULL) { + // We disallow updating of entries that had been resolved before, + // but to a different address. + // Right now, you have to manually purge the ARP entries (or wait some + // time) to let us switch to the new address. + if (entry->hardware_address.sdl_alen != 0 + && memcmp(LLADDR(&entry->hardware_address), hardwareAddress, ETHER_ADDRESS_LENGTH)) { + dprintf("ARP host %08lx updated with different hardware address %02x:%02x:%02x:%02x:%02x:%02x.\n", + protocolAddress, hardwareAddress->sdl_data[0] & 0xff, hardwareAddress->sdl_data[1] & 0xff, + hardwareAddress->sdl_data[2] & 0xff, hardwareAddress->sdl_data[3] & 0xff, + hardwareAddress->sdl_data[4] & 0xff, hardwareAddress->sdl_data[5] & 0xff); + return B_ERROR; + } + + entry->hardware_address = *hardwareAddress; + entry->timestamp = system_time(); + } else { + entry = arp_entry::Add(protocolAddress, hardwareAddress, flags); + if (entry == NULL) + return B_NO_MEMORY; + } + + // if someone was waiting for this ARP request to be resolved + if (entry->resolved_sem >= B_OK) { + delete_sem(entry->resolved_sem); + entry->resolved_sem = -1; + } + + if (entry->request_buffer != NULL) { + sBufferModule->free(entry->request_buffer); + entry->request_buffer = NULL; + } + + if ((entry->flags & ARP_FLAG_PERMANENT) == 0) { + // (re)start the stale timer + entry->timer_state = ARP_STATE_STALE; + sStackModule->set_timer(&entry->timer, ARP_STALE_TIMEOUT); + } + + if (_entry) + *_entry = entry; + + return B_OK; +} + + +static status_t +arp_update_local(net_datalink_protocol *protocol) +{ + net_interface *interface = protocol->interface; + + if (interface->address == NULL) { + // interface has not yet been set + return B_OK; + } + + sockaddr_dl address; + address.sdl_len = sizeof(sockaddr_dl); + address.sdl_family = AF_DLI; + address.sdl_type = IFT_ETHER; + address.sdl_e_type = ETHER_TYPE_IP; + address.sdl_nlen = 0; + address.sdl_slen = 0; + address.sdl_alen = interface->device->address.length; + memcpy(LLADDR(&address), interface->device->address.data, address.sdl_alen); + + arp_entry *entry; + status_t status = arp_update_entry(((sockaddr_in *)interface->address)->sin_addr.s_addr, + &address, ARP_FLAG_LOCAL | ARP_FLAG_PERMANENT, &entry); + if (status == B_OK) + entry->protocol = protocol; + + return status; +} + + +static status_t +handle_arp_request(net_buffer *buffer, arp_header &header) +{ + BenaphoreLocker locker(sCacheLock); + + if (!sIgnoreReplies) { + arp_update_entry(header.protocol_sender, (sockaddr_dl *)&buffer->source, 0); + // remember the address of the sender as we might need it later + } + + // check if this request is for us + + arp_entry *entry = arp_entry::Lookup(header.protocol_target); + if (entry == NULL || (entry->flags & (ARP_FLAG_LOCAL | ARP_FLAG_PUBLISH)) == 0) { + // We're not the one to answer this request + // TODO: instead of letting the other's request time-out, can we reply + // failure somehow? + TRACE((" not for us\n")); + return B_ERROR; + } + + // send a reply (by reusing the buffer we got) + + TRACE((" send reply!\n")); + header.opcode = htons(ARP_OPCODE_REPLY); + + memcpy(header.hardware_target, header.hardware_sender, ETHER_ADDRESS_LENGTH); + header.protocol_target = header.protocol_sender; + memcpy(header.hardware_sender, LLADDR(&entry->hardware_address), ETHER_ADDRESS_LENGTH); + header.protocol_sender = entry->protocol_address; + + // exchange source and destination address + memcpy(LLADDR((sockaddr_dl *)&buffer->source), header.hardware_sender, + ETHER_ADDRESS_LENGTH); + memcpy(LLADDR((sockaddr_dl *)&buffer->destination), header.hardware_target, + ETHER_ADDRESS_LENGTH); + + buffer->flags = 0; + // make sure this won't be a broadcast message + + return entry->protocol->next->module->send_data(entry->protocol->next, buffer); +} + + +static void +handle_arp_reply(net_buffer *buffer, arp_header &header) +{ + if (sIgnoreReplies) + return; + + BenaphoreLocker locker(sCacheLock); + arp_update_entry(header.protocol_sender, (sockaddr_dl *)&buffer->source, 0); +} + + +static status_t +arp_receive(void *cookie, net_buffer *buffer) +{ + TRACE(("ARP receive\n")); + + NetBufferHeader bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return bufferHeader.Status(); + + arp_header &header = bufferHeader.Data(); + uint16 opcode = ntohs(header.opcode); + +#ifdef TRACE_ARP + dprintf(" hw sender: %02x:%02x:%02x:%02x:%02x:%02x\n", + header.hardware_sender[0], header.hardware_sender[1], header.hardware_sender[2], + header.hardware_sender[3], header.hardware_sender[4], header.hardware_sender[5]); + dprintf(" proto sender: %ld.%ld.%ld.%ld\n", header.protocol_sender >> 24, (header.protocol_sender >> 16) & 0xff, + (header.protocol_sender >> 8) & 0xff, header.protocol_sender & 0xff); + dprintf(" hw target: %02x:%02x:%02x:%02x:%02x:%02x\n", + header.hardware_target[0], header.hardware_target[1], header.hardware_target[2], + header.hardware_target[3], header.hardware_target[4], header.hardware_target[5]); + dprintf(" proto target: %ld.%ld.%ld.%ld\n", header.protocol_target >> 24, (header.protocol_target >> 16) & 0xff, + (header.protocol_target >> 8) & 0xff, header.protocol_target & 0xff); +#endif + + if (ntohs(header.protocol_type) != ETHER_TYPE_IP + || ntohs(header.hardware_type) != ARP_HARDWARE_TYPE_ETHER) + return B_BAD_TYPE; + + // check if the packet is okay + + if (header.hardware_length != ETHER_ADDRESS_LENGTH + || header.protocol_length != sizeof(in_addr_t)) + return B_BAD_DATA; + + bufferHeader.Detach(); + + // handle packet + + switch (opcode) { + case ARP_OPCODE_REQUEST: + TRACE((" got ARP request\n")); + if (handle_arp_request(buffer, header) == B_OK) { + // the function will take care of the buffer if everything went well + return B_OK; + } + break; + case ARP_OPCODE_REPLY: + TRACE((" got ARP reply\n")); + handle_arp_reply(buffer, header); + break; + + default: + dprintf("unknown ARP opcode %d\n", opcode); + return B_ERROR; + } + + sBufferModule->free(buffer); + return B_OK; +} + + +static void +arp_timer(struct net_timer *timer, void *data) +{ + arp_entry *entry = (arp_entry *)data; + TRACE(("ARP timer %ld, entry %p!\n", entry->timer_state, entry)); + + switch (entry->timer_state) { + case ARP_NO_STATE: + // who are you kidding? + break; + + case ARP_STATE_REQUEST_FAILED: + // requesting the ARP entry failed, we keep it around for a while, though, + // so that we won't try to request the same address again too soon. + TRACE((" requesting ARP entry %p failed!\n", entry)); + entry->timer_state = ARP_STATE_REMOVE_FAILED; + entry->flags |= ARP_FLAG_REJECT; + sStackModule->set_timer(&entry->timer, ARP_REJECT_TIMEOUT); + break; + + case ARP_STATE_REMOVE_FAILED: + case ARP_STATE_STALE: + // the entry has aged so much that we're going to remove it + TRACE((" remove ARP entry %p!\n", entry)); + + benaphore_lock(&sCacheLock); + hash_remove(sCache, entry); + benaphore_unlock(&sCacheLock); + + delete entry; + + break; + default: + if (entry->timer_state > ARP_STATE_LAST_REQUEST) + break; + + TRACE((" send request for ARP entry %p!\n", entry)); + net_buffer *request = entry->request_buffer; + if (entry->timer_state < ARP_STATE_LAST_REQUEST) { + // we'll still need our buffer, so in order to prevent it being + // freed by a successful send, we need to clone it + request = sBufferModule->clone(request, true); + if (request == NULL) { + // cloning failed - that means we won't be able to send as + // many requests as originally planned + request = entry->request_buffer; + entry->timer_state = ARP_STATE_LAST_REQUEST; + } + } + + // we're trying to resolve the address, so keep sending requests + status_t status = entry->protocol->next->module->send_data( + entry->protocol->next, request); + if (status < B_OK) + sBufferModule->free(request); + + if (entry->timer_state == ARP_STATE_LAST_REQUEST) { + // buffer has been freed on send + entry->request_buffer = NULL; + } + + entry->timer_state++; + sStackModule->set_timer(&entry->timer, ARP_REQUEST_TIMEOUT); + } +} + + +/*! + Checks if the ARP \a entry has already been resolved. If it wasn't yet, + and MSG_DONTWAIT is not set in \a flags, it will wait for the entry to + become resolved. + You need to have the sCacheLock held when calling this function - but + note that the lock may be interrupted (in which case entry is updated). +*/ +static status_t +arp_check_resolved(arp_entry **_entry, uint32 flags) +{ + arp_entry *entry = *_entry; + + if ((entry->flags & ARP_FLAG_REJECT) != 0) + return EHOSTUNREACH; + + if (entry->resolved_sem < B_OK) + return B_OK; + + // we need to wait for this entry to become resolved + + if ((flags & MSG_DONTWAIT) != 0) + return B_ERROR; + + // store information we cannot access anymore after having unlocked the cache + sem_id waitSem = entry->resolved_sem; + in_addr_t address = entry->protocol_address; + + benaphore_unlock(&sCacheLock); + + status_t status = acquire_sem_etc(waitSem, 1, B_RELATIVE_TIMEOUT, 5 * 1000000); + + benaphore_lock(&sCacheLock); + + if (status == B_TIMED_OUT) + return EHOSTUNREACH; + + // retrieve the entry again, as we reacquired the cache lock + entry = arp_entry::Lookup(address); + if (entry == NULL) + return B_ERROR; + + *_entry = entry; + return B_OK; +} + + +/*! + Address resolver function: prepares and sends the ARP request necessary + to retrieve the hardware address for \a address. + You need to have the sCacheLock held when calling this function - but + note that the lock will be interrupted here if everything goes well. +*/ +static status_t +arp_resolve(net_datalink_protocol *protocol, in_addr_t address, arp_entry **_entry) +{ + // create an unresolved ARP entry as a placeholder + arp_entry *entry = arp_entry::Add(address, NULL, 0); + if (entry == NULL) + return B_NO_MEMORY; + + // prepare ARP request + + entry->request_buffer = sBufferModule->create(256); + if (entry->request_buffer == NULL) { + // TODO: do something with the entry + return B_NO_MEMORY; + } + + NetBufferPrepend bufferHeader(entry->request_buffer); + status_t status = bufferHeader.Status(); + if (status < B_OK) { + // TODO: do something with the entry + return status; + } + + // prepare ARP header + + net_device *device = protocol->interface->device; + arp_header &header = bufferHeader.Data(); + + header.hardware_type = htons(ARP_HARDWARE_TYPE_ETHER); + header.protocol_type = htons(ETHER_TYPE_IP); + header.hardware_length = ETHER_ADDRESS_LENGTH; + header.protocol_length = sizeof(in_addr_t); + header.opcode = htons(ARP_OPCODE_REQUEST); + + memcpy(header.hardware_sender, device->address.data, ETHER_ADDRESS_LENGTH); + header.protocol_sender = ((sockaddr_in *)protocol->interface->address)->sin_addr.s_addr; + memset(header.hardware_target, 0, ETHER_ADDRESS_LENGTH); + header.protocol_target = address; + + // prepare source and target addresses + + struct sockaddr_dl &source = *(struct sockaddr_dl *)&entry->request_buffer->source; + source.sdl_len = sizeof(sockaddr_dl); + source.sdl_family = AF_DLI; + source.sdl_index = device->index; + source.sdl_type = IFT_ETHER; + source.sdl_e_type = ETHER_TYPE_ARP; + source.sdl_nlen = source.sdl_slen = 0; + source.sdl_alen = ETHER_ADDRESS_LENGTH; + memcpy(source.sdl_data, device->address.data, ETHER_ADDRESS_LENGTH); + + entry->request_buffer->flags = MSG_BCAST; + // this is a broadcast packet, we don't need to fill in the destination + + entry->protocol = protocol; + entry->timer_state = ARP_STATE_REQUEST; + sStackModule->set_timer(&entry->timer, 0); + // start request timer + + sem_id waitSem = entry->resolved_sem; + benaphore_unlock(&sCacheLock); + + // TODO: resend the request periodically via timer + // (and abort it that way, too) + + status = acquire_sem_etc(waitSem, 1, B_RELATIVE_TIMEOUT, 5 * 1000000); + // wait for the entry to become resolved + + benaphore_lock(&sCacheLock); + + // retrieve the entry again, as we reacquired the cache lock + entry = arp_entry::Lookup(address); + if (entry == NULL) + return B_ERROR; + + if (status == B_TIMED_OUT) { + // we didn't get a response, mark ARP entry as non-existant + entry->flags = ARP_FLAG_REJECT; + // TODO: remove the ARP entry after some time + return EHOSTUNREACH; + } + + *_entry = entry; + return B_OK; +} + + +static status_t +arp_control(const char *subsystem, uint32 function, + void *buffer, size_t bufferSize) +{ + struct arp_control control; + if (bufferSize != sizeof(struct arp_control)) + return B_BAD_VALUE; + if (user_memcpy(&control, buffer, sizeof(struct arp_control)) < B_OK) + return B_BAD_ADDRESS; + + BenaphoreLocker locker(sCacheLock); + + switch (function) { + case ARP_SET_ENTRY: + sockaddr_dl hardwareAddress; + + hardwareAddress.sdl_len = sizeof(sockaddr_dl); + hardwareAddress.sdl_family = AF_DLI; + hardwareAddress.sdl_index = 0; + hardwareAddress.sdl_type = IFT_ETHER; + hardwareAddress.sdl_e_type = ETHER_TYPE_IP; + hardwareAddress.sdl_nlen = hardwareAddress.sdl_slen = 0; + hardwareAddress.sdl_alen = ETHER_ADDRESS_LENGTH; + memcpy(hardwareAddress.sdl_data, control.ethernet_address, ETHER_ADDRESS_LENGTH); + + return arp_update_entry(control.address, &hardwareAddress, + control.flags & (ARP_FLAG_PUBLISH | ARP_FLAG_PERMANENT | ARP_FLAG_REJECT)); + + case ARP_GET_ENTRY: + { + arp_entry *entry = arp_entry::Lookup(control.address); + if (entry == NULL || entry->resolved_sem < B_OK) + return B_ENTRY_NOT_FOUND; + + memcpy(control.ethernet_address, entry->hardware_address.sdl_data, + ETHER_ADDRESS_LENGTH); + control.flags = entry->flags; + return user_memcpy(buffer, &control, sizeof(struct arp_control)); + } + + case ARP_GET_ENTRIES: + { + hash_iterator iterator; + hash_open(sCache, &iterator); + + arp_entry *entry; + uint32 i = 0; + while ((entry = (arp_entry *)hash_next(sCache, &iterator)) != NULL + && i < control.cookie) { + i++; + } + hash_close(sCache, &iterator, false); + + if (entry == NULL) + return B_ENTRY_NOT_FOUND; + + control.cookie++; + control.address = entry->protocol_address; + memcpy(control.ethernet_address, entry->hardware_address.sdl_data, + ETHER_ADDRESS_LENGTH); + control.flags = entry->flags; + + return user_memcpy(buffer, &control, sizeof(struct arp_control)); + } + + case ARP_DELETE_ENTRY: + { + arp_entry *entry = arp_entry::Lookup(control.address); + if (entry == NULL || entry->resolved_sem < B_OK) + return B_ENTRY_NOT_FOUND; + if ((entry->flags & ARP_FLAG_LOCAL) != 0) + return B_BAD_VALUE; + + // schedule a timer to remove this entry + entry->timer_state = ARP_STATE_REMOVE_FAILED; + sStackModule->set_timer(&entry->timer, 0); + return B_OK; + } + + case ARP_FLUSH_ENTRIES: + { + hash_iterator iterator; + hash_open(sCache, &iterator); + + arp_entry *entry; + while ((entry = (arp_entry *)hash_next(sCache, &iterator)) != NULL) { + // we never flush local ARP entries + if ((entry->flags & ARP_FLAG_LOCAL) != 0) + continue; + + // schedule a timer to remove this entry + entry->timer_state = ARP_STATE_REMOVE_FAILED; + sStackModule->set_timer(&entry->timer, 0); + } + hash_close(sCache, &iterator, false); + return B_OK; + } + + case ARP_IGNORE_REPLIES: + sIgnoreReplies = control.flags != 0; + return B_OK; + } + + return B_BAD_VALUE; +} + + +static status_t +arp_init() +{ + status_t status = get_module(NET_STACK_MODULE_NAME, (module_info **)&sStackModule); + if (status < B_OK) + return status; + status = get_module(NET_BUFFER_MODULE_NAME, (module_info **)&sBufferModule); + if (status < B_OK) + goto err1; + + status = benaphore_init(&sCacheLock, "arp cache"); + if (status < B_OK) + goto err2; + + sCache = hash_init(64, offsetof(struct arp_entry, next), + &arp_entry::Compare, &arp_entry::Hash); + if (sCache == NULL) { + status = B_NO_MEMORY; + goto err3; + } + + register_generic_syscall(ARP_SYSCALLS, arp_control, 1, 0); + return B_OK; + +err3: + benaphore_destroy(&sCacheLock); +err2: + put_module(NET_BUFFER_MODULE_NAME); +err1: + put_module(NET_STACK_MODULE_NAME); + return status; +} + + +static status_t +arp_uninit() +{ + unregister_generic_syscall(ARP_SYSCALLS, 1); + + put_module(NET_BUFFER_MODULE_NAME); + put_module(NET_STACK_MODULE_NAME); + return B_OK; +} + + +// #pragma mark - + + +status_t +arp_init_protocol(struct net_interface *interface, net_datalink_protocol **_protocol) +{ + // We currently only support a single family and type! + if (interface->domain->family != AF_INET + || interface->device->type != IFT_ETHER) + return B_BAD_TYPE; + + status_t status = sStackModule->register_device_handler(interface->device, + ETHER_FRAME_TYPE | ETHER_TYPE_ARP, &arp_receive, NULL); + if (status == B_OK) { + // We also register the domain as a handler for deframed packets; + // while the ethernet_frame module is not really connected to our + // domain, we are. + status = sStackModule->register_domain_device_handler(interface->device, + ETHER_FRAME_TYPE | ETHER_TYPE_IP, interface->domain); + } + + if (status < B_OK) + return status; + + arp_protocol *protocol = new (std::nothrow) arp_protocol; + if (protocol == NULL) + return B_NO_MEMORY; + + *_protocol = protocol; + return B_OK; +} + + +status_t +arp_uninit_protocol(net_datalink_protocol *protocol) +{ + sStackModule->unregister_device_handler(protocol->interface->device, + ETHER_FRAME_TYPE | ETHER_TYPE_ARP); + sStackModule->unregister_device_handler(protocol->interface->device, + ETHER_FRAME_TYPE | ETHER_TYPE_IP); + + delete protocol; + return B_OK; +} + + +status_t +arp_send_data(net_datalink_protocol *protocol, + net_buffer *buffer) +{ + { + BenaphoreLocker locker(sCacheLock); + + // Lookup source (us) + + arp_entry *entry = arp_entry::Lookup( + ((struct sockaddr_in *)&buffer->source)->sin_addr.s_addr); + if (entry == NULL) + return B_ERROR; + + memcpy(&buffer->source, &entry->hardware_address, + entry->hardware_address.sdl_len); + + // Lookup destination (we may need to wait for this) + entry = arp_entry::Lookup( + ((struct sockaddr_in *)&buffer->destination)->sin_addr.s_addr); + if (entry == NULL) { + // The ARP entry does not yet exist, if we're allowed to wait, + // we'll send an ARP request and try to change that. + if ((buffer->flags & MSG_DONTWAIT) != 0) { + // TODO: implement delaying packet send after ARP response! + return B_ERROR; + } + + status_t status = arp_resolve(protocol, + ((struct sockaddr_in *)&buffer->destination)->sin_addr.s_addr, &entry); + if (status < B_OK) + return status; + } else { + // The entry exists, but we have to check if it has already been + // resolved and is valid. + status_t status = arp_check_resolved(&entry, buffer->flags); + if (status < B_OK) + return status; + } + + memcpy(&buffer->destination, &entry->hardware_address, + entry->hardware_address.sdl_len); + } + + return protocol->next->module->send_data(protocol->next, buffer); +} + + +status_t +arp_up(net_datalink_protocol *_protocol) +{ + arp_protocol *protocol = (arp_protocol *)_protocol; + status_t status = protocol->next->module->interface_up(protocol->next); + if (status < B_OK) + return status; + + // cache this device's address for later use + + status = arp_update_local(protocol); + if (status < B_OK) { + protocol->next->module->interface_down(protocol->next); + return status; + } + + return B_OK; +} + + +void +arp_down(net_datalink_protocol *protocol) +{ + // remove local ARP entry from the cache + + if (protocol->interface->address != NULL) { + BenaphoreLocker locker(sCacheLock); + + arp_entry *entry = arp_entry::Lookup( + ((sockaddr_in *)protocol->interface->address)->sin_addr.s_addr); + if (entry != NULL) { + hash_remove(sCache, entry); + delete entry; + } + } + + protocol->next->module->interface_down(protocol->next); +} + + +status_t +arp_control(net_datalink_protocol *protocol, + int32 op, void *argument, size_t length) +{ + if (op == SIOCSIFADDR && (protocol->interface->flags & IFF_UP) != 0) { + // The interface may get a new address, so we need to update our + // local entries. + in_addr_t oldAddress = 0; + if (protocol->interface->address != NULL) + oldAddress = ((sockaddr_in *)protocol->interface->address)->sin_addr.s_addr; + + status_t status = protocol->next->module->control(protocol->next, + SIOCSIFADDR, argument, length); + if (status < B_OK) + return status; + + arp_update_local(protocol); + + if (oldAddress == ((sockaddr_in *)protocol->interface->address)->sin_addr.s_addr + || oldAddress == 0) + return B_OK; + + // remove previous address from cache + // TODO: we should be able to do this (add/remove) in one atomic operation! + + BenaphoreLocker locker(sCacheLock); + + arp_entry *entry = arp_entry::Lookup(oldAddress); + if (entry != NULL) { + hash_remove(sCache, entry); + delete entry; + } + + return B_OK; + } + + return protocol->next->module->control(protocol->next, + op, argument, length); +} + + +static status_t +arp_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + return arp_init(); + case B_MODULE_UNINIT: + return arp_uninit(); + + default: + return B_ERROR; + } +} + + +static net_datalink_protocol_module_info sARPModule = { + { + "network/datalink_protocols/arp/v1", + 0, + arp_std_ops + }, + arp_init_protocol, + arp_uninit_protocol, + arp_send_data, + arp_up, + arp_down, + arp_control, +}; + +module_info *modules[] = { + (module_info *)&sARPModule, + NULL +}; diff --git a/src/add-ons/kernel/network/datalink_protocols/ethernet_frame/Jamfile b/src/add-ons/kernel/network/datalink_protocols/ethernet_frame/Jamfile new file mode 100644 index 0000000000..6fe947ce89 --- /dev/null +++ b/src/add-ons/kernel/network/datalink_protocols/ethernet_frame/Jamfile @@ -0,0 +1,25 @@ +SubDir HAIKU_TOP src add-ons kernel network datalink_protocols ethernet_frame ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UsePrivateHeaders net ; + +KernelAddon ethernet_frame : kernel haiku_network datalink_protocols : + ethernet_frame.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network/datalink_protocols + : ethernet_frame ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network datalink_protocols ; diff --git a/src/add-ons/kernel/network/datalink_protocols/ethernet_frame/ethernet_frame.cpp b/src/add-ons/kernel/network/datalink_protocols/ethernet_frame/ethernet_frame.cpp new file mode 100644 index 0000000000..a46bc8a94e --- /dev/null +++ b/src/add-ons/kernel/network/datalink_protocols/ethernet_frame/ethernet_frame.cpp @@ -0,0 +1,205 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + + +struct ethernet_frame_protocol : net_datalink_protocol { +}; + + +static const uint8 kBroadcastAddress[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + +struct net_buffer_module_info *sBufferModule; + + +int32 +ethernet_deframe(net_device *device, net_buffer *buffer) +{ + //dprintf("asked to deframe buffer for device %s\n", device->name); + + NetBufferHeader bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return bufferHeader.Status(); + + ether_header &header = bufferHeader.Data(); + uint16 type = ntohs(header.type); + + struct sockaddr_dl &source = *(struct sockaddr_dl *)&buffer->source; + struct sockaddr_dl &destination = *(struct sockaddr_dl *)&buffer->destination; + + source.sdl_len = sizeof(sockaddr_dl); + source.sdl_family = AF_DLI; + source.sdl_index = device->index; + source.sdl_type = IFT_ETHER; + source.sdl_e_type = type; + source.sdl_nlen = source.sdl_slen = 0; + source.sdl_alen = ETHER_ADDRESS_LENGTH; + memcpy(source.sdl_data, header.source, ETHER_ADDRESS_LENGTH); + + destination.sdl_len = sizeof(sockaddr_dl); + destination.sdl_family = AF_DLI; + destination.sdl_index = device->index; + destination.sdl_type = IFT_ETHER; + destination.sdl_nlen = destination.sdl_slen = 0; + destination.sdl_alen = ETHER_ADDRESS_LENGTH; + memcpy(destination.sdl_data, header.destination, ETHER_ADDRESS_LENGTH); + + // mark buffer if it was a broadcast/multicast packet + if (!memcmp(header.destination, kBroadcastAddress, ETHER_ADDRESS_LENGTH)) + buffer->flags |= MSG_BCAST; + else if (header.destination[0] & 0x01) + buffer->flags |= MSG_MCAST; + + return ETHER_FRAME_TYPE | type; +} + + +// #pragma mark - + + +status_t +ethernet_frame_init(struct net_interface *interface, net_datalink_protocol **_protocol) +{ + net_stack_module_info *stack; + status_t status = get_module(NET_STACK_MODULE_NAME, (module_info **)&stack); + if (status < B_OK) + return status; + + status = stack->register_device_deframer(interface->device, ðernet_deframe); + put_module(NET_STACK_MODULE_NAME); + + if (status < B_OK) + return status; + + ethernet_frame_protocol *protocol = new (std::nothrow) ethernet_frame_protocol; + if (protocol == NULL) + return B_NO_MEMORY; + + *_protocol = protocol; + return B_OK; +} + + +status_t +ethernet_frame_uninit(net_datalink_protocol *protocol) +{ + net_stack_module_info *stack; + if (get_module(NET_STACK_MODULE_NAME, (module_info **)&stack) == B_OK) { + stack->unregister_device_deframer(protocol->interface->device); + put_module(NET_STACK_MODULE_NAME); + } + + delete protocol; + return B_OK; +} + + +status_t +ethernet_frame_send_data(net_datalink_protocol *protocol, + net_buffer *buffer) +{ + struct sockaddr_dl &source = *(struct sockaddr_dl *)&buffer->source; + struct sockaddr_dl &destination = *(struct sockaddr_dl *)&buffer->destination; + + if (source.sdl_family != AF_DLI || source.sdl_type != IFT_ETHER) + return B_ERROR; + + NetBufferPrepend bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return bufferHeader.Status(); + + ether_header &header = bufferHeader.Data(); + + header.type = htons(source.sdl_e_type); + memcpy(header.source, source.sdl_data, ETHER_ADDRESS_LENGTH); + if (buffer->flags & MSG_BCAST) + memcpy(header.destination, kBroadcastAddress, ETHER_ADDRESS_LENGTH); + else + memcpy(header.destination, destination.sdl_data, ETHER_ADDRESS_LENGTH); + + bufferHeader.Detach(); + // make sure the framing is already written to the buffer at this point + + return protocol->next->module->send_data(protocol->next, buffer); +} + + +status_t +ethernet_frame_up(net_datalink_protocol *_protocol) +{ + ethernet_frame_protocol *protocol = (ethernet_frame_protocol *)_protocol; + + return protocol->next->module->interface_up(protocol->next); +} + + +void +ethernet_frame_down(net_datalink_protocol *protocol) +{ + return protocol->next->module->interface_down(protocol->next); +} + + +status_t +ethernet_frame_control(net_datalink_protocol *protocol, + int32 op, void *argument, size_t length) +{ + return protocol->next->module->control(protocol->next, op, argument, length); +} + + +static status_t +ethernet_frame_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + return get_module(NET_BUFFER_MODULE_NAME, (module_info **)&sBufferModule); + case B_MODULE_UNINIT: + put_module(NET_BUFFER_MODULE_NAME); + return B_OK; + + default: + return B_ERROR; + } +} + + +static net_datalink_protocol_module_info sEthernetFrameModule = { + { + "network/datalink_protocols/ethernet_frame/v1", + 0, + ethernet_frame_std_ops + }, + ethernet_frame_init, + ethernet_frame_uninit, + ethernet_frame_send_data, + ethernet_frame_up, + ethernet_frame_down, + ethernet_frame_control, +}; + +module_info *modules[] = { + (module_info *)&sEthernetFrameModule, + NULL +}; diff --git a/src/add-ons/kernel/network/devices/Jamfile b/src/add-ons/kernel/network/devices/Jamfile new file mode 100644 index 0000000000..a6c6e26851 --- /dev/null +++ b/src/add-ons/kernel/network/devices/Jamfile @@ -0,0 +1,4 @@ +SubDir HAIKU_TOP src add-ons kernel network devices ; + +SubInclude HAIKU_TOP src add-ons kernel network devices ethernet ; +SubInclude HAIKU_TOP src add-ons kernel network devices loopback ; diff --git a/src/add-ons/kernel/network/devices/ethernet/Jamfile b/src/add-ons/kernel/network/devices/ethernet/Jamfile new file mode 100644 index 0000000000..dc8574d04f --- /dev/null +++ b/src/add-ons/kernel/network/devices/ethernet/Jamfile @@ -0,0 +1,25 @@ +SubDir HAIKU_TOP src add-ons kernel network devices ethernet ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UsePrivateHeaders net ; + +KernelAddon ethernet : kernel haiku_network devices : + ethernet.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network/devices + : ethernet ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network protocols ; diff --git a/src/add-ons/kernel/network/devices/ethernet/ethernet.cpp b/src/add-ons/kernel/network/devices/ethernet/ethernet.cpp new file mode 100644 index 0000000000..855f008dc5 --- /dev/null +++ b/src/add-ons/kernel/network/devices/ethernet/ethernet.cpp @@ -0,0 +1,293 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + + +struct ethernet_device : net_device { + int fd; + uint32 frame_size; +}; + + +struct net_buffer_module_info *sBufferModule; + + +status_t +ethernet_init(const char *name, net_device **_device) +{ + // make sure this is a device in /dev/net, but not the + // networking (userland) stack driver + if (strncmp(name, "/dev/net/", 9) || !strcmp(name, "/dev/net/stack") + || !strcmp(name, "/dev/net/userland_server")) + return B_BAD_VALUE; + + status_t status = get_module(NET_BUFFER_MODULE_NAME, (module_info **)&sBufferModule); + if (status < B_OK) + return status; + + ethernet_device *device = new (std::nothrow) ethernet_device; + if (device == NULL) { + put_module(NET_BUFFER_MODULE_NAME); + return B_NO_MEMORY; + } + + memset(device, 0, sizeof(ethernet_device)); + + strcpy(device->name, name); + device->flags = IFF_BROADCAST; + device->type = IFT_ETHER; + device->mtu = 1500; + device->header_length = ETHER_HEADER_LENGTH; + device->fd = -1; + + *_device = device; + return B_OK; +} + + +status_t +ethernet_uninit(net_device *device) +{ + put_module(NET_BUFFER_MODULE_NAME); + delete device; + + return B_OK; +} + + +status_t +ethernet_up(net_device *_device) +{ + ethernet_device *device = (ethernet_device *)_device; + + device->fd = open(device->name, O_RDWR); + if (device->fd < 0) + return errno; + + ether_init_params params; + memset(¶ms, 0, sizeof(ether_init_params)); + if (ioctl(device->fd, ETHER_INIT, ¶ms, sizeof(ether_init_params)) < 0) + goto err; + + if (ioctl(device->fd, ETHER_GETADDR, device->address.data, ETHER_ADDRESS_LENGTH) < 0) + goto err; + + if (ioctl(device->fd, ETHER_GETFRAMESIZE, &device->frame_size, sizeof(uint32)) < 0) { + // this call is obviously optional + device->frame_size = ETHER_MAX_FRAME_SIZE; + } + + device->address.length = ETHER_ADDRESS_LENGTH; + device->mtu = device->frame_size - device->header_length; + return B_OK; + +err: + close(device->fd); + device->fd = -1; + return errno; +} + + +void +ethernet_down(net_device *_device) +{ + ethernet_device *device = (ethernet_device *)_device; + close(device->fd); +} + + +status_t +ethernet_control(net_device *_device, int32 op, void *argument, + size_t length) +{ + ethernet_device *device = (ethernet_device *)_device; + return ioctl(device->fd, op, argument, length); +} + + +status_t +ethernet_send_data(net_device *_device, net_buffer *buffer) +{ + ethernet_device *device = (ethernet_device *)_device; + +dprintf("try to send ethernet packet of %lu bytes (flags %ld):\n", buffer->size, buffer->flags); + if (buffer->size > device->frame_size || buffer->size < ETHER_HEADER_LENGTH) + return B_BAD_VALUE; + + if (sBufferModule->count_iovecs(buffer) > 1) { + dprintf("scattered I/O is not yet supported by ethernet device.\n"); + return B_NOT_SUPPORTED; + } + + struct iovec iovec; + sBufferModule->get_iovecs(buffer, &iovec, 1); + +dump_block((const char *)iovec.iov_base, buffer->size, " "); + ssize_t bytesWritten = write(device->fd, iovec.iov_base, iovec.iov_len); +dprintf("sent: %ld\n", bytesWritten); + if (bytesWritten < 0) { + device->stats.send.errors++; + return bytesWritten; + } + + device->stats.send.packets++; + device->stats.send.bytes += bytesWritten; + + sBufferModule->free(buffer); + return B_OK; +} + + +status_t +ethernet_receive_data(net_device *_device, net_buffer **_buffer) +{ + ethernet_device *device = (ethernet_device *)_device; + + // TODO: better header space + net_buffer *buffer = sBufferModule->create(256); + if (buffer == NULL) + return ENOBUFS; + + // TODO: this only works for standard ethernet frames - we need iovecs + // for jumbo frame support (or a separate read buffer)! + // It would be even nicer to get net_buffers from the ethernet driver + // directly. + + ssize_t bytesRead; + void *data; + + status_t status = sBufferModule->append_size(buffer, device->frame_size, &data); + if (status == B_OK && data == NULL) { + dprintf("scattered I/O is not yet supported by ethernet device.\n"); + status = B_NOT_SUPPORTED; + } + if (status < B_OK) + goto err; + + bytesRead = read(device->fd, data, device->frame_size); + if (bytesRead < 0) { + device->stats.receive.errors++; + status = bytesRead; + goto err; + } + + status = sBufferModule->trim(buffer, bytesRead); + if (status < B_OK) { + device->stats.receive.dropped++; + goto err; + } + + device->stats.receive.bytes += bytesRead; + device->stats.receive.packets++; + + *_buffer = buffer; + return B_OK; + +err: + sBufferModule->free(buffer); + return status; +} + + +status_t +ethernet_set_mtu(net_device *_device, size_t mtu) +{ + ethernet_device *device = (ethernet_device *)_device; + + if (mtu > device->frame_size - ETHER_HEADER_LENGTH + || mtu <= ETHER_HEADER_LENGTH + 10) + return B_BAD_VALUE; + + device->mtu = mtu; + return B_OK; +} + + +status_t +ethernet_set_promiscuous(net_device *device, bool promiscuous) +{ + return EOPNOTSUPP; +} + + +status_t +ethernet_set_media(net_device *device, uint32 media) +{ + return EOPNOTSUPP; +} + + +status_t +ethernet_get_multicast_addrs(struct net_device *device, + net_hardware_address **addressArray, uint32 count) +{ + return EOPNOTSUPP; +} + + +status_t +ethernet_set_multicast_addrs(struct net_device *device, + const net_hardware_address **addressArray, uint32 count) +{ + return EOPNOTSUPP; +} + + +static status_t +ethernet_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + case B_MODULE_UNINIT: + return B_OK; + + default: + return B_ERROR; + } +} + + +net_device_module_info sEthernetModule = { + { + "network/devices/ethernet/v1", + 0, + ethernet_std_ops + }, + ethernet_init, + ethernet_uninit, + ethernet_up, + ethernet_down, + ethernet_control, + ethernet_send_data, + ethernet_receive_data, + ethernet_set_mtu, + ethernet_set_promiscuous, + ethernet_set_media, + ethernet_get_multicast_addrs, + ethernet_set_multicast_addrs +}; + +module_info *modules[] = { + (module_info *)&sEthernetModule, + NULL +}; diff --git a/src/add-ons/kernel/network/devices/loopback/Jamfile b/src/add-ons/kernel/network/devices/loopback/Jamfile new file mode 100644 index 0000000000..e0341f4bff --- /dev/null +++ b/src/add-ons/kernel/network/devices/loopback/Jamfile @@ -0,0 +1,25 @@ +SubDir HAIKU_TOP src add-ons kernel network devices loopback ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UsePrivateHeaders net ; + +KernelAddon loopback : kernel haiku_network devices : + loopback.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network/devices + : loopback ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network protocols ; diff --git a/src/add-ons/kernel/network/devices/loopback/loopback.cpp b/src/add-ons/kernel/network/devices/loopback/loopback.cpp new file mode 100644 index 0000000000..173b2c4557 --- /dev/null +++ b/src/add-ons/kernel/network/devices/loopback/loopback.cpp @@ -0,0 +1,146 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include + +#include + +#include +#include +#include +#include +#include + + +struct loopback_device : net_device { +}; + + +status_t +loopback_init(const char *name, net_device **_device) +{ + if (strncmp(name, "loop", 4)) + return B_BAD_VALUE; + + loopback_device *device = new (std::nothrow) loopback_device; + if (device == NULL) + return B_NO_MEMORY; + + memset(device, 0, sizeof(loopback_device)); + + strcpy(device->name, name); + device->flags = IFF_LOOPBACK; + device->type = IFT_LOOP; + device->mtu = 16384; + + *_device = device; + return B_OK; +} + + +status_t +loopback_uninit(net_device *device) +{ + delete device; + return B_OK; +} + + +status_t +loopback_up(net_device *device) +{ + return B_OK; +} + + +void +loopback_down(net_device *device) +{ +} + + +status_t +loopback_control(net_device *device, int32 op, void *argument, + size_t length) +{ + return B_BAD_VALUE; +} + + +status_t +loopback_send_data(net_device *device, net_buffer *buffer) +{ + return B_ERROR; +} + + +status_t +loopback_receive_data(net_device *device, net_buffer **_buffer) +{ + return B_ERROR; +} + + +status_t +loopback_set_mtu(net_device *device, size_t mtu) +{ + return B_ERROR; +} + + +status_t +loopback_set_promiscuous(net_device *device, bool promiscuous) +{ + return B_ERROR; +} + + +status_t +loopback_set_media(net_device *device, uint32 media) +{ + return B_ERROR; +} + + +static status_t +loopback_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + case B_MODULE_UNINIT: + return B_OK; + + default: + return B_ERROR; + } +} + + +net_device_module_info sLoopbackModule = { + { + "network/devices/loopback/v1", + 0, + loopback_std_ops + }, + loopback_init, + loopback_uninit, + loopback_up, + loopback_down, + loopback_control, + loopback_send_data, + loopback_receive_data, + loopback_set_mtu, + loopback_set_promiscuous, + loopback_set_media, +}; + +module_info *modules[] = { + (module_info *)&sLoopbackModule, + NULL +}; diff --git a/src/add-ons/kernel/network/protocols/Jamfile b/src/add-ons/kernel/network/protocols/Jamfile new file mode 100644 index 0000000000..26d4cd7ab4 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/Jamfile @@ -0,0 +1,6 @@ +SubDir HAIKU_TOP src add-ons kernel network protocols ; + +SubInclude HAIKU_TOP src add-ons kernel network protocols icmp ; +SubInclude HAIKU_TOP src add-ons kernel network protocols ipv4 ; +SubInclude HAIKU_TOP src add-ons kernel network protocols tcp ; +SubInclude HAIKU_TOP src add-ons kernel network protocols udp ; diff --git a/src/add-ons/kernel/network/protocols/icmp/Jamfile b/src/add-ons/kernel/network/protocols/icmp/Jamfile new file mode 100644 index 0000000000..c4692ca53f --- /dev/null +++ b/src/add-ons/kernel/network/protocols/icmp/Jamfile @@ -0,0 +1,25 @@ +SubDir HAIKU_TOP src add-ons kernel network protocols icmp ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UsePrivateHeaders net ; + +KernelAddon icmp : kernel haiku_network protocols : + icmp.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network/protocols + : icmp ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network protocols ; diff --git a/src/add-ons/kernel/network/protocols/icmp/icmp.cpp b/src/add-ons/kernel/network/protocols/icmp/icmp.cpp new file mode 100644 index 0000000000..83dbb53318 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/icmp/icmp.cpp @@ -0,0 +1,356 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + + +struct icmp_header { + uint8 type; + uint8 code; + uint16 checksum; + union { + struct { + uint16 id; + uint16 sequence; + } echo; + struct { + in_addr_t gateway; + } redirect; + struct { + uint16 _reserved; + uint16 next_mtu; + } path_mtu; + uint32 zero; + }; +}; + +#define ICMP_TYPE_ECHO_REPLY 0 +#define ICMP_TYPE_UNREACH 3 +#define ICMP_TYPE_REDIRECT 5 +#define ICMP_TYPE_ECHO_REQUEST 8 + +// type unreach codes +#define ICMP_CODE_UNREACH_NEED_FRAGMENT 4 // this is used for path MTU discovery + +struct icmp_protocol : net_protocol { +}; + + +static net_stack_module_info *sStackModule; +struct net_buffer_module_info *sBufferModule; + + +net_protocol * +icmp_init_protocol(net_socket *socket) +{ + icmp_protocol *protocol = new (std::nothrow) icmp_protocol; + if (protocol == NULL) + return NULL; + + return protocol; +} + + +status_t +icmp_uninit_protocol(net_protocol *protocol) +{ + delete protocol; + return B_OK; +} + + +status_t +icmp_open(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +icmp_close(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +icmp_free(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +icmp_connect(net_protocol *protocol, const struct sockaddr *address) +{ + return B_ERROR; +} + + +status_t +icmp_accept(net_protocol *protocol, struct net_socket **_acceptedSocket) +{ + return EOPNOTSUPP; +} + + +status_t +icmp_control(net_protocol *protocol, int level, int option, void *value, + size_t *_length) +{ + return protocol->next->module->control(protocol->next, level, option, + value, _length); +} + + +status_t +icmp_bind(net_protocol *protocol, struct sockaddr *address) +{ + return B_ERROR; +} + + +status_t +icmp_unbind(net_protocol *protocol, struct sockaddr *address) +{ + return B_ERROR; +} + + +status_t +icmp_listen(net_protocol *protocol, int count) +{ + return EOPNOTSUPP; +} + + +status_t +icmp_shutdown(net_protocol *protocol, int direction) +{ + return EOPNOTSUPP; +} + + +status_t +icmp_send_data(net_protocol *protocol, net_buffer *buffer) +{ + return protocol->next->module->send_data(protocol->next, buffer); +} + + +status_t +icmp_send_routed_data(net_protocol *protocol, struct net_route *route, + net_buffer *buffer) +{ + return protocol->next->module->send_routed_data(protocol->next, route, buffer); +} + + +ssize_t +icmp_send_avail(net_protocol *protocol) +{ + return B_ERROR; +} + + +status_t +icmp_read_data(net_protocol *protocol, size_t numBytes, uint32 flags, + net_buffer **_buffer) +{ + return B_ERROR; +} + + +ssize_t +icmp_read_avail(net_protocol *protocol) +{ + return B_ERROR; +} + + +struct net_domain * +icmp_get_domain(net_protocol *protocol) +{ + return protocol->next->module->get_domain(protocol->next); +} + + +size_t +icmp_get_mtu(net_protocol *protocol, const struct sockaddr *address) +{ + return protocol->next->module->get_mtu(protocol->next, address); +} + + +status_t +icmp_receive_data(net_buffer *buffer) +{ + dprintf("ICMP received some data, buffer length %lu\n", buffer->size); + + NetBufferHeader bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return bufferHeader.Status(); + + icmp_header &header = bufferHeader.Data(); + bufferHeader.Detach(); + // the pointer stays valid after this + + dprintf(" got type %u, code %u, checksum %u\n", header.type, header.code, + ntohs(header.checksum)); + dprintf(" computed checksum: %ld\n", sBufferModule->checksum(buffer, 0, buffer->size, true)); + if (sBufferModule->checksum(buffer, 0, buffer->size, true) != 0) + return B_BAD_DATA; + + switch (header.type) { + case ICMP_TYPE_ECHO_REPLY: + break; + + case ICMP_TYPE_ECHO_REQUEST: + { + net_domain *domain; + if (buffer->interface != NULL) + domain = buffer->interface->domain; + else + domain = sStackModule->get_domain(buffer->source.ss_family); + if (domain == NULL || domain->module == NULL) + break; + + net_buffer *reply = sBufferModule->duplicate(buffer); + if (reply == NULL) + return B_NO_MEMORY; + + // switch source/destination address + memcpy(&reply->source, &buffer->destination, buffer->destination.ss_len); + memcpy(&reply->destination, &buffer->source, buffer->source.ss_len); + + // There already is an ICMP header, and we'll reuse it + icmp_header *header; + status_t status = sBufferModule->direct_access(reply, + 0, sizeof(icmp_header), (void **)&header); + if (status == B_OK) { + header->type = ICMP_TYPE_ECHO_REPLY; + header->code = 0; + header->checksum = 0; + header->checksum = sBufferModule->checksum(reply, 0, reply->size, true); + } + + if (status == B_OK) + status = domain->module->send_data(NULL, reply); + + if (status < B_OK) { + sBufferModule->free(reply); + return status; + } + } + } + + sBufferModule->free(buffer); + return B_OK; +} + + +status_t +icmp_error(uint32 code, net_buffer *data) +{ + return B_ERROR; +} + + +status_t +icmp_error_reply(net_protocol *protocol, net_buffer *causedError, uint32 code, + void *errorData) +{ + return B_ERROR; +} + + +// #pragma mark - + + +static status_t +icmp_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + { + status_t status = get_module(NET_STACK_MODULE_NAME, (module_info **)&sStackModule); + if (status < B_OK) + return status; + status = get_module(NET_BUFFER_MODULE_NAME, (module_info **)&sBufferModule); + if (status < B_OK) { + put_module(NET_STACK_MODULE_NAME); + return status; + } + + sStackModule->register_domain_protocols(AF_INET, SOCK_DGRAM, IPPROTO_ICMP, + "network/protocols/icmp/v1", + "network/protocols/ipv4/v1", + NULL); + + sStackModule->register_domain_receiving_protocol(AF_INET, IPPROTO_ICMP, + "network/protocols/icmp/v1"); + return B_OK; + } + + case B_MODULE_UNINIT: + put_module(NET_BUFFER_MODULE_NAME); + put_module(NET_STACK_MODULE_NAME); + return B_OK; + + default: + return B_ERROR; + } +} + + +net_protocol_module_info sICMPModule = { + { + "network/protocols/icmp/v1", + 0, + icmp_std_ops + }, + icmp_init_protocol, + icmp_uninit_protocol, + icmp_open, + icmp_close, + icmp_free, + icmp_connect, + icmp_accept, + icmp_control, + icmp_bind, + icmp_unbind, + icmp_listen, + icmp_shutdown, + icmp_send_data, + icmp_send_routed_data, + icmp_send_avail, + icmp_read_data, + icmp_read_avail, + icmp_get_domain, + icmp_get_mtu, + icmp_receive_data, + icmp_error, + icmp_error_reply, +}; + +module_info *modules[] = { + (module_info *)&sICMPModule, + NULL +}; diff --git a/src/add-ons/kernel/network/protocols/ipv4/Jamfile b/src/add-ons/kernel/network/protocols/ipv4/Jamfile new file mode 100644 index 0000000000..d1b5817988 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv4/Jamfile @@ -0,0 +1,26 @@ +SubDir HAIKU_TOP src add-ons kernel network protocols ipv4 ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UsePrivateHeaders net ; + +KernelAddon ipv4 : kernel haiku_network protocols : + ipv4.cpp + ipv4_address.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network/protocols + : ipv4 ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network protocols ; diff --git a/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp b/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp new file mode 100644 index 0000000000..4094ebea7d --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv4/ipv4.cpp @@ -0,0 +1,1130 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include "ipv4_address.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#define TRACE_IPV4 +#ifdef TRACE_IPV4 +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + +struct ipv4_header { +#if B_HOST_IS_LENDIAN == 1 + uint8 header_length : 4; // header length in 32-bit words + uint8 version : 4; +#else + uint8 version : 4; + uint8 header_length : 4; +#endif + uint8 service_type; + uint16 total_length; + uint16 id; + uint16 fragment_offset; + uint8 time_to_live; + uint8 protocol; + uint16 checksum; + in_addr_t source; + in_addr_t destination; + + uint16 HeaderLength() const { return header_length << 2; } + uint16 TotalLength() const { return ntohs(total_length); } + uint16 FragmentOffset() const { return ntohs(fragment_offset); } +} _PACKED; + +#define IP_VERSION 4 + +// fragment flags +#define IP_RESERVED_FLAG 0x8000 +#define IP_DONT_FRAGMENT 0x4000 +#define IP_MORE_FRAGMENTS 0x2000 +#define IP_FRAGMENT_OFFSET_MASK 0x1fff + +#define MAX_HASH_FRAGMENTS 64 + // slots in the fragment packet's hash +#define FRAGMENT_TIMEOUT 60000000LL + // discard fragment after 60 seconds + +struct ipv4_fragment : DoublyLinkedListLinkImpl { + uint16 start; + uint16 end; + net_buffer *buffer; +}; + +typedef DoublyLinkedList FragmentList; + +struct ipv4_packet_key { + in_addr_t source; + in_addr_t destination; + uint16 id; + uint8 protocol; +}; + +class FragmentPacket { + public: + FragmentPacket(const ipv4_packet_key &key); + ~FragmentPacket(); + + status_t AddFragment(uint16 start, uint16 end, net_buffer *buffer, + bool lastFragment); + status_t Reassemble(net_buffer *to); + + bool IsComplete() const { return fReceivedLastFragment && fBytesLeft == 0; } + + static uint32 Hash(void *_packet, const void *_key, uint32 range); + static int Compare(void *_packet, const void *_key); + static int32 NextOffset() { return offsetof(FragmentPacket, fNext); } + static void StaleTimer(struct net_timer *timer, void *data); + + private: + FragmentPacket *fNext; + struct ipv4_packet_key fKey; + bool fReceivedLastFragment; + int32 fBytesLeft; + FragmentList fFragments; + net_timer fTimer; +}; + +typedef DoublyLinkedList RawSocketList; + +class RawSocket : public DoublyLinkedListLinkImpl { + public: + RawSocket(); + ~RawSocket(); + + status_t InitCheck(); + + status_t Read(size_t numBytes, uint32 flags, bigtime_t timeout, + net_buffer **_buffer); + ssize_t BytesAvailable(); + + status_t Write(net_buffer *buffer); + + private: + net_fifo fFifo; +}; + +struct ipv4_protocol : net_protocol { + RawSocket *raw; + uint32 flags; +}; + +// protocol flags +#define IP_FLAG_HEADER_INCLUDED 0x01 + + +extern net_protocol_module_info gIPv4Module; + // we need this in ipv4_std_ops() for registering the AF_INET domain + +static struct net_domain *sDomain; +static net_datalink_module_info *sDatalinkModule; +static net_stack_module_info *sStackModule; +struct net_buffer_module_info *sBufferModule; +static int32 sPacketID; +static RawSocketList sRawSockets; +static benaphore sRawSocketsLock; +static benaphore sFragmentLock; +static hash_table *sFragmentHash; + + +RawSocket::RawSocket() +{ + status_t status = sStackModule->init_fifo(&fFifo, "ipv4 raw socket", 65536); + if (status < B_OK) + fFifo.notify = status; +} + + +RawSocket::~RawSocket() +{ + if (fFifo.notify >= B_OK) + sStackModule->uninit_fifo(&fFifo); +} + + +status_t +RawSocket::InitCheck() +{ + return fFifo.notify >= B_OK ? B_OK : fFifo.notify; +} + + +status_t +RawSocket::Read(size_t numBytes, uint32 flags, bigtime_t timeout, + net_buffer **_buffer) +{ + net_buffer *buffer; + status_t status = sStackModule->fifo_dequeue_buffer(&fFifo, + flags, timeout, &buffer); + if (status < B_OK) + return status; + + if (numBytes < buffer->size) { + // discard any data behind the amount requested + sBufferModule->trim(buffer, numBytes); + } + + *_buffer = buffer; + return B_OK; +} + + +ssize_t +RawSocket::BytesAvailable() +{ + return fFifo.current_bytes; +} + + +status_t +RawSocket::Write(net_buffer *source) +{ + // we need to make a clone for that buffer and pass it to the socket + net_buffer *buffer = sBufferModule->clone(source, false); + TRACE(("ipv4::RawSocket::Write(): cloned buffer %p\n", buffer)); + if (buffer == NULL) + return B_NO_MEMORY; + + return sStackModule->fifo_enqueue_buffer(&fFifo, buffer); +} + + +// #pragma mark - + + +FragmentPacket::FragmentPacket(const ipv4_packet_key &key) + : + fKey(key), + fReceivedLastFragment(false), + fBytesLeft(IP_MAXPACKET) +{ + sStackModule->init_timer(&fTimer, StaleTimer, this); +} + + +FragmentPacket::~FragmentPacket() +{ + // cancel the kill timer + sStackModule->set_timer(&fTimer, -1); + + // delete all fragments + ipv4_fragment *fragment; + while ((fragment = fFragments.RemoveHead()) != NULL) { + if (fragment->buffer != NULL) + sBufferModule->free(fragment->buffer); + delete fragment; + } +} + + +status_t +FragmentPacket::AddFragment(uint16 start, uint16 end, net_buffer *buffer, + bool lastFragment) +{ + // restart the timer + sStackModule->set_timer(&fTimer, FRAGMENT_TIMEOUT); + + if (start >= end) { + // invalid fragment + return B_BAD_DATA; + } + + // Search for a position in the list to insert the fragment + + FragmentList::ReverseIterator iterator = fFragments.GetReverseIterator(); + ipv4_fragment *previous = NULL; + ipv4_fragment *next = NULL; + while ((previous = iterator.Next()) != NULL) { + + if (previous->start <= start) { + // The new fragment can be inserted after this one + break; + } + + next = previous; + } + + // See if we already have the fragment's data + + if (previous != NULL && previous->start <= start && previous->end >= end) { + // we do, so we can just drop this fragment + sBufferModule->free(buffer); + return B_OK; + } + + TRACE((" previous: %p, next: %p\n", previous, next)); + + // If we have parts of the data already, truncate as needed + + if (previous != NULL && previous->end > start) { + TRACE((" remove header %d bytes\n", previous->end - start)); + sBufferModule->remove_header(buffer, previous->end - start); + start = previous->end; + } + if (next != NULL && next->start < end) { + TRACE((" remove trailer %d bytes\n", next->start - end)); + sBufferModule->remove_trailer(buffer, next->start - end); + end = next->start; + } + + // Now try if we can already merge the fragments together + + // We will always keep the last buffer received, so that we can still + // report an error (in which case we're not responsible for freeing it) + + if (previous != NULL && previous->end == start) { + status_t status = sBufferModule->merge(buffer, previous->buffer, false); + TRACE((" merge previous: %s\n", strerror(status))); + if (status < B_OK) + return status; + + previous->buffer = buffer; + previous->end = end; + + // cut down existing hole + fBytesLeft -= end - start; + + if (lastFragment && !fReceivedLastFragment) { + fReceivedLastFragment = true; + fBytesLeft -= IP_MAXPACKET - end; + } + + TRACE((" hole length: %d\n", (int)fBytesLeft)); + + return B_OK; + } else if (next != NULL && next->start == end) { + status_t status = sBufferModule->merge(buffer, next->buffer, true); + TRACE((" merge next: %s\n", strerror(status))); + if (status < B_OK) + return status; + + next->buffer = buffer; + next->start = start; + + // cut down existing hole + fBytesLeft -= end - start; + + if (lastFragment && !fReceivedLastFragment) { + fReceivedLastFragment = true; + fBytesLeft -= IP_MAXPACKET - end; + } + + TRACE((" hole length: %d\n", (int)fBytesLeft)); + + return B_OK; + } + + // We couldn't merge the fragments, so we need to add a new fragment + + ipv4_fragment *fragment = new (std::nothrow) ipv4_fragment; + TRACE((" new fragment: %p, bytes %d-%d\n", fragment, start, end)); + if (fragment == NULL) + return B_NO_MEMORY; + + fragment->start = start; + fragment->end = end; + fragment->buffer = buffer; + fFragments.Insert(next, fragment); + + // update length of the hole, if any + fBytesLeft -= end - start; + + if (lastFragment && !fReceivedLastFragment) { + fReceivedLastFragment = true; + fBytesLeft -= IP_MAXPACKET - end; + } + + TRACE((" hole length: %d\n", (int)fBytesLeft)); + + return B_OK; +} + + +/*! + Reassembles the fragments to the specified buffer \a to. + This buffer must have been added via AddFragment() before. +*/ +status_t +FragmentPacket::Reassemble(net_buffer *to) +{ + if (!IsComplete()) + return NULL; + + net_buffer *buffer = NULL; + + ipv4_fragment *fragment; + while ((fragment = fFragments.RemoveHead()) != NULL) { + if (buffer != NULL) { + status_t status; + if (to == fragment->buffer) { + status = sBufferModule->merge(fragment->buffer, buffer, false); + buffer = fragment->buffer; + } else + status = sBufferModule->merge(buffer, fragment->buffer, true); + if (status < B_OK) + return status; + } else + buffer = fragment->buffer; + + delete fragment; + } + + if (buffer != to) + panic("ipv4 packet reassembly did not work correctly.\n"); + + return B_OK; +} + + +int +FragmentPacket::Compare(void *_packet, const void *_key) +{ + const ipv4_packet_key *key = (ipv4_packet_key *)_key; + ipv4_packet_key *packetKey = &((FragmentPacket *)_packet)->fKey; + + if (packetKey->id == key->id + && packetKey->source == key->source + && packetKey->destination == key->destination + && packetKey->protocol == key->protocol) + return 0; + + return 1; +} + + +uint32 +FragmentPacket::Hash(void *_packet, const void *_key, uint32 range) +{ + const struct ipv4_packet_key *key = (struct ipv4_packet_key *)_key; + FragmentPacket *packet = (FragmentPacket *)_packet; + if (packet != NULL) + key = &packet->fKey; + + return (key->source ^ key->destination ^ key->protocol ^ key->id) % range; +} + + +void +FragmentPacket::StaleTimer(struct net_timer *timer, void *data) +{ + BenaphoreLocker locker(&sFragmentLock); + hash_remove(sFragmentHash, (FragmentPacket *)data); + + TRACE(("Assembling FragmentPacket timed out!\n")); + delete (FragmentPacket *)data; +} + +// #pragma mark - + + +static void +dump_ipv4_header(ipv4_header &header) +{ + struct pretty_ipv4 { + #if B_HOST_IS_LENDIAN == 1 + uint8 a; + uint8 b; + uint8 c; + uint8 d; + #else + uint8 d; + uint8 c; + uint8 b; + uint8 a; + #endif + }; + struct pretty_ipv4 *src = (struct pretty_ipv4 *)&header.source; + struct pretty_ipv4 *dst = (struct pretty_ipv4 *)&header.destination; + dprintf(" version: %d\n", header.version); + dprintf(" header_length: 4 * %d\n", header.header_length); + dprintf(" service_type: %d\n", header.service_type); + dprintf(" total_length: %d\n", header.TotalLength()); + dprintf(" id: %d\n", ntohs(header.id)); + dprintf(" fragment_offset: %d (flags: %c%c%c)\n", + header.FragmentOffset() & IP_FRAGMENT_OFFSET_MASK, + (header.FragmentOffset() & IP_RESERVED_FLAG) ? 'r' : '-', + (header.FragmentOffset() & IP_DONT_FRAGMENT) ? 'd' : '-', + (header.FragmentOffset() & IP_MORE_FRAGMENTS) ? 'm' : '-'); + dprintf(" time_to_live: %d\n", header.time_to_live); + dprintf(" protocol: %d\n", header.protocol); + dprintf(" checksum: %d\n", ntohs(header.checksum)); + dprintf(" source: %d.%d.%d.%d\n", src->a, src->b, src->c, src->d); + dprintf(" destination: %d.%d.%d.%d\n", dst->a, dst->b, dst->c, dst->d); +} + + +/*! + Attempts to re-assemble fragmented packets. + \return B_OK if everything went well; if it could reassemble the packet, \a _buffer + will point to its buffer, otherwise, it will be \c NULL. + \return various error codes if something went wrong (mostly B_NO_MEMORY) + + TODO: Implement packet aging +*/ +static status_t +reassemble_fragments(const ipv4_header &header, net_buffer **_buffer) +{ + net_buffer *buffer = *_buffer; + status_t status; + + struct ipv4_packet_key key; + key.source = (in_addr_t)header.source; + key.destination = (in_addr_t)header.destination; + key.id = header.id; + key.protocol = header.protocol; + + // TODO: Make locking finer grained. + BenaphoreLocker locker(&sFragmentLock); + + FragmentPacket *packet = (FragmentPacket *)hash_lookup(sFragmentHash, &key); + if (packet == NULL) { + // New fragment packet + packet = new (std::nothrow) FragmentPacket(key); + if (packet == NULL) + return B_NO_MEMORY; + + // add packet to hash + status = hash_insert(sFragmentHash, packet); + if (status != B_OK) { + delete packet; + return status; + } + } + + uint16 fragmentOffset = header.FragmentOffset(); + uint16 start = (fragmentOffset & IP_FRAGMENT_OFFSET_MASK) << 3; + uint16 end = start + header.TotalLength() - header.HeaderLength(); + bool lastFragment = (fragmentOffset & IP_MORE_FRAGMENTS) == 0; + + TRACE((" Received IPv4 %sfragment of size %d, offset %d.\n", + lastFragment ? "last ": "", end - start, start)); + + // Remove header unless this is the first fragment + if (start != 0) + sBufferModule->remove_header(buffer, header.HeaderLength()); + + status = packet->AddFragment(start, end, buffer, lastFragment); + if (status != B_OK) + return status; + + if (packet->IsComplete()) { + hash_remove(sFragmentHash, packet); + // no matter if reassembling succeeds, we won't need this packet anymore + + status = packet->Reassemble(buffer); + delete packet; + + // _buffer does not change + return status; + } + + // This indicates that the packet is not yet complete + *_buffer = NULL; + return B_OK; +} + + +static void +raw_receive_data(net_buffer *buffer) +{ + BenaphoreLocker locker(sRawSocketsLock); + RawSocketList::Iterator iterator = sRawSockets.GetIterator(); + + while (iterator.HasNext()) { + RawSocket *raw = iterator.Next(); + raw->Write(buffer); + } +} + + +// #pragma mark - + + +net_protocol * +ipv4_init_protocol(net_socket *socket) +{ + ipv4_protocol *protocol = new (std::nothrow) ipv4_protocol; + if (protocol == NULL) + return NULL; + + protocol->raw = NULL; + protocol->flags = 0; + return protocol; +} + + +status_t +ipv4_uninit_protocol(net_protocol *_protocol) +{ + ipv4_protocol *protocol = (ipv4_protocol *)_protocol; + + delete protocol->raw; + delete protocol; + return B_OK; +} + + +/*! + Since open() is only called on the top level protocol, when we get here + it means we are on a SOCK_RAW socket. +*/ +status_t +ipv4_open(net_protocol *_protocol) +{ + ipv4_protocol *protocol = (ipv4_protocol *)_protocol; + + RawSocket *raw = new (std::nothrow) RawSocket; + if (raw == NULL) + return B_NO_MEMORY; + + status_t status = raw->InitCheck(); + if (status < B_OK) { + delete raw; + return status; + } + + protocol->raw = raw; + + BenaphoreLocker locker(sRawSocketsLock); + sRawSockets.Add(raw); + return B_OK; +} + + +status_t +ipv4_close(net_protocol *_protocol) +{ + ipv4_protocol *protocol = (ipv4_protocol *)_protocol; + RawSocket *raw = protocol->raw; + if (raw == NULL) + return B_ERROR; + + BenaphoreLocker locker(sRawSocketsLock); + sRawSockets.Remove(raw); + delete raw; + protocol->raw = NULL; + + return B_OK; +} + + +status_t +ipv4_free(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +ipv4_connect(net_protocol *protocol, const struct sockaddr *address) +{ + return B_ERROR; +} + + +status_t +ipv4_accept(net_protocol *protocol, struct net_socket **_acceptedSocket) +{ + return EOPNOTSUPP; +} + + +status_t +ipv4_control(net_protocol *_protocol, int level, int option, void *value, + size_t *_length) +{ + if ((level & LEVEL_MASK) != IPPROTO_IP) + return sDatalinkModule->control(sDomain, option, value, _length); + + ipv4_protocol *protocol = (ipv4_protocol *)_protocol; + + if (level & LEVEL_GET_OPTION) { + // get options + + switch (option) { + case IP_HDRINCL: + { + if (*_length != sizeof(int)) + return B_BAD_VALUE; + + int headerIncluded = (protocol->flags & IP_FLAG_HEADER_INCLUDED) != 0; + return user_memcpy(value, &headerIncluded, sizeof(headerIncluded)); + } + + default: + return ENOPROTOOPT; + } + } else { + // set options + + switch (option) { + case IP_HDRINCL: + { + int headerIncluded; + if (*_length != sizeof(int)) + return B_BAD_VALUE; + if (user_memcpy(&headerIncluded, value, sizeof(headerIncluded)) < B_OK) + return B_BAD_ADDRESS; + + if (headerIncluded) + protocol->flags |= IP_FLAG_HEADER_INCLUDED; + else + protocol->flags &= ~IP_FLAG_HEADER_INCLUDED; + break; + } + + default: + return ENOPROTOOPT; + } + } + + return B_BAD_VALUE; +} + + +status_t +ipv4_bind(net_protocol *protocol, struct sockaddr *address) +{ + if (address->sa_family != AF_INET) + return EAFNOSUPPORT; + + // only INADDR_ANY and addresses of local interfaces are accepted: + if (((sockaddr_in *)address)->sin_addr.s_addr == INADDR_ANY + || sDatalinkModule->is_local_address(sDomain, address)) { + protocol->socket->address.ss_len = sizeof(struct sockaddr_in); + // explicitly set length, as our callers can't be trusted to + // always provide the correct length! + return B_OK; + } + + return B_ERROR; + // address is unknown on this host +} + + +status_t +ipv4_unbind(net_protocol *protocol, struct sockaddr *address) +{ + // nothing to do here + return B_OK; +} + + +status_t +ipv4_listen(net_protocol *protocol, int count) +{ + return EOPNOTSUPP; +} + + +status_t +ipv4_shutdown(net_protocol *protocol, int direction) +{ + return EOPNOTSUPP; +} + + +status_t +ipv4_send_routed_data(net_protocol *_protocol, struct net_route *route, + net_buffer *buffer) +{ + ipv4_protocol *protocol = (ipv4_protocol *)_protocol; + net_interface *interface = route->interface; + + TRACE(("someone tries to send some actual routed data!\n")); + + sockaddr_in &source = *(sockaddr_in *)&buffer->source; + if (source.sin_addr.s_addr == INADDR_ANY) { + // replace an unbound source address with the address of the interface + // TODO: couldn't we replace all addresses here? + source.sin_addr.s_addr = ((sockaddr_in *)route->interface->address)->sin_addr.s_addr; + } + + // Add IP header (if needed) + + if (protocol == NULL || (protocol->flags & IP_FLAG_HEADER_INCLUDED) == 0) { + NetBufferPrepend bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return bufferHeader.Status(); + + ipv4_header &header = bufferHeader.Data(); + + header.version = IP_VERSION; + header.header_length = sizeof(ipv4_header) >> 2; + header.service_type = 0; + header.total_length = htons(buffer->size); + header.id = htons(atomic_add(&sPacketID, 1)); + header.fragment_offset = 0; + header.time_to_live = 254; + header.protocol = protocol ? protocol->socket->protocol : buffer->protocol; + header.checksum = 0; + header.source = ((sockaddr_in *)route->interface->address)->sin_addr.s_addr; + // always use the actual used source address + header.destination = ((sockaddr_in *)&buffer->destination)->sin_addr.s_addr; + + header.checksum = sBufferModule->checksum(buffer, 0, sizeof(ipv4_header), true); + dump_ipv4_header(header); + + bufferHeader.Detach(); + // make sure the IP-header is already written to the buffer at this point + } + + TRACE(("header chksum: %ld, buffer checksum: %ld\n", + sBufferModule->checksum(buffer, 0, sizeof(ipv4_header), true), + sBufferModule->checksum(buffer, 0, buffer->size, true))); + + uint32 mtu = route->mtu ? route->mtu : interface->mtu; + if (buffer->size > mtu) { + // we need to fragment the packet + dprintf("ipv4 needs to fragment (size %lu, MTU %lu), but that's not yet implemented...\n", buffer->size, mtu); + return B_ERROR; + } + + TRACE(("destination-IP: buffer=%p addr=%p %08lx\n", buffer, &buffer->destination, + ntohl(((sockaddr_in *)&buffer->destination)->sin_addr.s_addr))); + + return sDatalinkModule->send_data(route, buffer); +} + + +status_t +ipv4_send_data(net_protocol *protocol, net_buffer *buffer) +{ + TRACE(("someone tries to send some actual data!\n")); + + // find route + struct net_route *route = sDatalinkModule->get_route(sDomain, + (sockaddr *)&buffer->destination); + if (route == NULL) + return ENETUNREACH; + + status_t status = ipv4_send_routed_data(protocol, route, buffer); + sDatalinkModule->put_route(sDomain, route); + + return status; +} + + +ssize_t +ipv4_send_avail(net_protocol *protocol) +{ + return B_ERROR; +} + + +status_t +ipv4_read_data(net_protocol *_protocol, size_t numBytes, uint32 flags, + net_buffer **_buffer) +{ + ipv4_protocol *protocol = (ipv4_protocol *)_protocol; + RawSocket *raw = protocol->raw; + if (raw == NULL) + return B_ERROR; + + TRACE(("read is waiting for data...\n")); + return raw->Read(numBytes, flags, protocol->socket->receive.timeout, _buffer); +} + + +ssize_t +ipv4_read_avail(net_protocol *_protocol) +{ + ipv4_protocol *protocol = (ipv4_protocol *)_protocol; + RawSocket *raw = protocol->raw; + if (raw == NULL) + return B_ERROR; + + return raw->BytesAvailable(); +} + + +struct net_domain * +ipv4_get_domain(net_protocol *protocol) +{ + return sDomain; +} + + +size_t +ipv4_get_mtu(net_protocol *protocol, const struct sockaddr *address) +{ + net_route *route = sDatalinkModule->get_route(sDomain, address); + if (route == NULL) + return 0; + + size_t mtu; + if (route->mtu != 0) + mtu = route->mtu; + else + mtu = route->interface->mtu; + + sDatalinkModule->put_route(sDomain, route); + return mtu - sizeof(ipv4_header); +} + + +status_t +ipv4_receive_data(net_buffer *buffer) +{ + TRACE(("IPv4 received a packet of %ld size!\n", buffer->size)); + + NetBufferHeader bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return bufferHeader.Status(); + + ipv4_header &header = bufferHeader.Data(); + bufferHeader.Detach(); + dump_ipv4_header(header); + + if (header.version != IP_VERSION) + return B_BAD_TYPE; + + uint16 packetLength = header.TotalLength(); + uint16 headerLength = header.HeaderLength(); + if (packetLength > buffer->size + || headerLength < sizeof(ipv4_header)) + return B_BAD_DATA; + + // TODO: would be nice to have a direct checksum function somewhere + if (sBufferModule->checksum(buffer, 0, headerLength, true) != 0) + return B_BAD_DATA; + + struct sockaddr_in &source = *(struct sockaddr_in *)&buffer->source; + struct sockaddr_in &destination = *(struct sockaddr_in *)&buffer->destination; + + source.sin_len = sizeof(sockaddr_in); + source.sin_family = AF_INET; + source.sin_addr.s_addr = header.source; + + destination.sin_len = sizeof(sockaddr_in); + destination.sin_family = AF_INET; + destination.sin_addr.s_addr = header.destination; + + // test if the packet is really for us + uint32 matchedAddressType; + if (!sDatalinkModule->is_local_address(sDomain, (sockaddr*)&destination, + &buffer->interface, &matchedAddressType)) { + TRACE(("this packet was not for us\n")); + return B_ERROR; + } + if (matchedAddressType != 0) { + // copy over special address types (MSG_BCAST or MSG_MCAST): + buffer->flags |= matchedAddressType; + } + + uint8 protocol = buffer->protocol = header.protocol; + + // remove any trailing/padding data + status_t status = sBufferModule->trim(buffer, packetLength); + if (status < B_OK) + return status; + + // check for fragmentation + uint16 fragmentOffset = ntohs(header.fragment_offset); + if ((fragmentOffset & IP_MORE_FRAGMENTS) != 0 + || (fragmentOffset & IP_FRAGMENT_OFFSET_MASK) != 0) { + // this is a fragment + TRACE((" Found a Fragment!\n")); + status = reassemble_fragments(header, &buffer); + TRACE((" -> %s!\n", strerror(status))); + if (status != B_OK) + return status; + + if (buffer == NULL) { + // buffer was put into fragment packet + TRACE((" Not yet assembled...\n")); + return B_OK; + } + } + + // Since the buffer might have been changed (reassembled fragment) + // we must no longer access bufferHeader or header anymore after + // this point + + if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) { + // SOCK_RAW doesn't get all packets + raw_receive_data(buffer); + } + + sBufferModule->remove_header(buffer, headerLength); + // the header is of variable size and may include IP options + // (that we ignore for now) + + // TODO: since we'll doing this for every packet, we may want to cache the module + // (and only put them when we're about to be unloaded) + net_protocol_module_info *module; + status = sStackModule->get_domain_receiving_protocol(sDomain, protocol, &module); + if (status < B_OK) { + // no handler for this packet + return status; + } + + status = module->receive_data(buffer); + sStackModule->put_domain_receiving_protocol(sDomain, protocol); + + return status; +} + + +status_t +ipv4_error(uint32 code, net_buffer *data) +{ + return B_ERROR; +} + + +status_t +ipv4_error_reply(net_protocol *protocol, net_buffer *causedError, uint32 code, + void *errorData) +{ + return B_ERROR; +} + + +// #pragma mark - + + +status_t +init_ipv4() +{ + status_t status = get_module(NET_STACK_MODULE_NAME, (module_info **)&sStackModule); + if (status < B_OK) + return status; + status = get_module(NET_BUFFER_MODULE_NAME, (module_info **)&sBufferModule); + if (status < B_OK) + goto err1; + status = get_module(NET_DATALINK_MODULE_NAME, (module_info **)&sDatalinkModule); + if (status < B_OK) + goto err2; + + sPacketID = (int32)system_time(); + + status = benaphore_init(&sRawSocketsLock, "raw sockets"); + if (status < B_OK) + goto err3; + + status = benaphore_init(&sFragmentLock, "IPv4 Fragments"); + if (status < B_OK) + goto err4; + + sFragmentHash = hash_init(MAX_HASH_FRAGMENTS, FragmentPacket::NextOffset(), + &FragmentPacket::Compare, &FragmentPacket::Hash); + if (sFragmentHash == NULL) + goto err5; + + new (&sRawSockets) RawSocketList; + // static initializers do not work in the kernel, + // so we have to do it here, manually + // TODO: for modules, this shouldn't be required + + sStackModule->register_domain_protocols(AF_INET, SOCK_RAW, 0, + "network/protocols/ipv4/v1", NULL); + + return sStackModule->register_domain(AF_INET, "internet", &gIPv4Module, + &gIPv4AddressModule, &sDomain); + +err5: + benaphore_destroy(&sFragmentLock); +err4: + benaphore_destroy(&sRawSocketsLock); +err3: + put_module(NET_DATALINK_MODULE_NAME); +err2: + put_module(NET_BUFFER_MODULE_NAME); +err1: + put_module(NET_STACK_MODULE_NAME); + return status; +} + + +status_t +uninit_ipv4() +{ + hash_uninit(sFragmentHash); + + benaphore_destroy(&sFragmentLock); + benaphore_destroy(&sRawSocketsLock); + + sStackModule->unregister_domain(sDomain); + put_module(NET_DATALINK_MODULE_NAME); + put_module(NET_BUFFER_MODULE_NAME); + put_module(NET_STACK_MODULE_NAME); + return B_OK; +} + + +static status_t +ipv4_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + return init_ipv4(); + case B_MODULE_UNINIT: + return uninit_ipv4(); + + default: + return B_ERROR; + } +} + + +net_protocol_module_info gIPv4Module = { + { + "network/protocols/ipv4/v1", + 0, + ipv4_std_ops + }, + ipv4_init_protocol, + ipv4_uninit_protocol, + ipv4_open, + ipv4_close, + ipv4_free, + ipv4_connect, + ipv4_accept, + ipv4_control, + ipv4_bind, + ipv4_unbind, + ipv4_listen, + ipv4_shutdown, + ipv4_send_data, + ipv4_send_routed_data, + ipv4_send_avail, + ipv4_read_data, + ipv4_read_avail, + ipv4_get_domain, + ipv4_get_mtu, + ipv4_receive_data, + ipv4_error, + ipv4_error_reply, +}; + +module_info *modules[] = { + (module_info *)&gIPv4Module, + NULL +}; diff --git a/src/add-ons/kernel/network/protocols/ipv4/ipv4_address.cpp b/src/add-ons/kernel/network/protocols/ipv4/ipv4_address.cpp new file mode 100644 index 0000000000..00dc2875c0 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv4/ipv4_address.cpp @@ -0,0 +1,399 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + * Oliver Tappe, zooey@hirschkaefer.de + */ + + +#include + +#include +#include + +#include + +#include +#include +#include +#include + + +/*! + Routing utility function: copies address \a from into a new address + that is put into \a to. + If \a replaceWithZeros is set \a from will be replaced by an empty + address. + If a \a mask is given it is applied to \a from (such that \a to is the + result of \a from & \a mask). + \return B_OK if the address could be copied + \return B_NO_MEMORY if the new address could not be allocated + \return B_MISMATCHED_VALUES if \a address does not match family AF_INET +*/ +static status_t +ipv4_copy_address(const sockaddr *from, sockaddr **to, + bool replaceWithZeros = false, sockaddr *mask = NULL) +{ + if (replaceWithZeros) { + *to = (sockaddr *)malloc(sizeof(sockaddr_in)); + if (*to == NULL) + return B_NO_MEMORY; + + memset(*to, 0, sizeof(sockaddr_in)); + (*to)->sa_family = AF_INET; + (*to)->sa_len = sizeof(sockaddr_in); + } else { + if (from == NULL) + return B_OK; + if (from->sa_family != AF_INET) + return B_MISMATCHED_VALUES; + + *to = (sockaddr *)malloc(sizeof(sockaddr_in)); + if (*to == NULL) + return B_NO_MEMORY; + + memcpy(*to, from, sizeof(sockaddr_in)); + + if (mask != NULL) { + ((sockaddr_in *)*to)->sin_addr.s_addr + &= ((sockaddr_in *)mask)->sin_addr.s_addr; + } + } + return B_OK; +} + + +/*! + Routing utility function: applies \a mask to given \a address and puts + the resulting address into \a result. + \return B_OK if the mask has been applied + \return B_BAD_VALUE if \a address or \a mask is NULL +*/ +static status_t +ipv4_mask_address(const sockaddr *address, const sockaddr *mask, sockaddr *result) +{ + if (address == NULL || result == NULL) + return B_BAD_VALUE; + + memcpy(result, address, sizeof(sockaddr_in)); + if (mask != NULL) { + ((sockaddr_in *)result)->sin_addr.s_addr + &= ((sockaddr_in *)mask)->sin_addr.s_addr; + } + + return B_OK; +} + + +/*! + Checks if the given \a address is the empty address. By default, the port + is checked, too, but you can avoid that by passing \a checkPort = false. + \return true if \a address is NULL, uninitialized or the empty address, + false if not +*/ +static bool +ipv4_is_empty_address(const sockaddr *address, bool checkPort) +{ + if (address == NULL || address->sa_len == 0) + return true; + + return ((sockaddr_in *)address)->sin_addr.s_addr == 0 + && (!checkPort || ((sockaddr_in *)address)->sin_port == 0); +} + + +/*! + Compares the IP-addresses of the two given address structures \a a and \a b. + \return true if IP-addresses of \a a and \a b are equal, false if not +*/ +static bool +ipv4_equal_addresses(const sockaddr *a, const sockaddr *b) +{ + if (a == NULL && b == NULL) + return true; + if (a != NULL && b == NULL) + return ipv4_is_empty_address(a, false); + if (a == NULL && b != NULL) + return ipv4_is_empty_address(b, false); + + return ((sockaddr_in *)a)->sin_addr.s_addr == ((sockaddr_in *)b)->sin_addr.s_addr; +} + + +/*! + Compares the ports of the two given address structures \a a and \a b. + \return true if ports of \a a and \a b are equal, false if not +*/ +static bool +ipv4_equal_ports(const sockaddr *a, const sockaddr *b) +{ + uint16 portA = a ? ((sockaddr_in *)a)->sin_port : 0; + uint16 portB = b ? ((sockaddr_in *)b)->sin_port : 0; + return portA == portB; +} + + +/*! + Compares the IP-addresses and ports of the two given address structures + \a a and \a b. + \return true if IP-addresses and ports of \a a and \a b are equal, false if not +*/ +static bool +ipv4_equal_addresses_and_ports(const sockaddr *a, const sockaddr *b) +{ + if (a == NULL && b == NULL) + return true; + if (a != NULL && b == NULL) + return ipv4_is_empty_address(a, true); + if (a == NULL && b != NULL) + return ipv4_is_empty_address(b, true); + + return ((sockaddr_in *)a)->sin_addr.s_addr == ((sockaddr_in *)b)->sin_addr.s_addr + && ((sockaddr_in *)a)->sin_port == ((sockaddr_in *)b)->sin_port; +} + + +/*! + Applies the given \a mask two \a a and \a b and then checks whether + the masked addresses match. + \return true if \a a matches \a b after masking both, false if not +*/ +static bool +ipv4_equal_masked_addresses(const sockaddr *a, const sockaddr *b, + const sockaddr *mask) +{ + if (a == NULL && b == NULL) + return true; + + sockaddr emptyAddr; + if (a == NULL || b == NULL) { + memset(&emptyAddr, 0, sizeof(sockaddr_in)); + if (a == NULL) + a = &emptyAddr; + else if (b == NULL) + b = &emptyAddr; + } + + uint32 aValue = ((sockaddr_in *)a)->sin_addr.s_addr; + uint32 bValue = ((sockaddr_in *)b)->sin_addr.s_addr; + + if (!mask) + return aValue == bValue; + + uint32 maskValue = ((sockaddr_in *)mask)->sin_addr.s_addr; + return (aValue & maskValue) == (bValue & maskValue); +} + + +/*! + Routing utility function: determines the least significant bit that is set + in the given \a mask. + \return the number of the first bit that is set (0-32, where 32 means + that there's no bit set in the mask). +*/ +static int32 +ipv4_first_mask_bit(sockaddr *_mask) +{ + if (_mask == NULL) + return 0; + + uint32 mask = ntohl(((sockaddr_in *)_mask)->sin_addr.s_addr); + + // TODO: this can be optimized, there are also some nice assembler mnemonics for this + int8 bit = 0; + for (uint32 bitMask = 1; bit < 32; bitMask <<= 1, bit++) { + if (mask & bitMask) + return bit; + } + + return 32; +} + + +/*! + Routing utility function: checks the given \a mask for correctness (which + means that (starting with LSB) consists zero or more unset bits, followed + by bits that are all set). + \return true if \a mask is ok, false if not +*/ +static bool +ipv4_check_mask(const sockaddr *_mask) +{ + if (_mask == NULL) + return true; + + uint32 mask = ntohl(((sockaddr_in *)_mask)->sin_addr.s_addr); + + // A mask (from LSB) starts with zeros, after the first one, only ones + // are allowed: + bool zero = true; + int8 bit = 0; + for (uint32 bitMask = 1; bit < 32; bitMask <<= 1, bit++) { + if (mask & bitMask) { + if (zero) + zero = false; + } else if (!zero) + return false; + } + return true; +} + + +/*! + Creates a buffer for the given \a address and prints the address into + it (hexadecimal representation in host byte order or ''). + If \a printPort is set, the port is printed, too. + \return B_OK if the address could be printed, \a buffer will point to + the resulting string + \return B_BAD_VALUE if no buffer has been given + \return B_NO_MEMORY if the buffer could not be allocated +*/ +static status_t +ipv4_print_address(const sockaddr *address, char **_buffer, bool printPort) +{ + if (_buffer == NULL) + return B_BAD_VALUE; + + int bufLen = printPort ? 15 : 9; + char *buffer = (char *)malloc(bufLen); + if (buffer == NULL) + return B_NO_MEMORY; + + if (address == NULL) + strcpy(buffer, ""); + else if (printPort) { + sprintf(buffer, "%08lx:%u", ntohl(((sockaddr_in *)address)->sin_addr.s_addr), + ntohs(((sockaddr_in *)address)->sin_port)); + } else + sprintf(buffer, "%08lx", ntohl(((sockaddr_in *)address)->sin_addr.s_addr)); + *_buffer = buffer; + return B_OK; +} + + +/*! + Determines the port of the given \a address. + \return uint16 representing the port-nr +*/ +static uint16 +ipv4_get_port(const sockaddr *address) +{ + if (address == NULL) + return 0; + + return ((sockaddr_in *)address)->sin_port; +} + + +/*! + Sets the port of the given \a address to \a port. + \return B_OK if the port has been set + \return B_BAD_VALUE if \a address is NULL +*/ +static status_t +ipv4_set_port(sockaddr *address, uint16 port) +{ + if (address == NULL) + return B_BAD_VALUE; + + ((sockaddr_in *)address)->sin_port = port; + return B_OK; +} + + +/*! + Sets \a address to \a from. + \return B_OK if \a from has been copied into \a address + \return B_BAD_VALUE if either \a address or \a from is NULL + \return B_MISMATCHED_VALUES if from is not of family AF_INET +*/ +static status_t +ipv4_set_to(sockaddr *address, const sockaddr *from) +{ + if (address == NULL || from == NULL) + return B_BAD_VALUE; + + if (from->sa_family != AF_INET) + return B_MISMATCHED_VALUES; + + memcpy(address, from, sizeof(sockaddr_in)); + address->sa_len = sizeof(sockaddr_in); + return B_OK; +} + + +/*! + Sets \a address to the empty address (0.0.0.0). + \return B_OK if \a address has been set + \return B_BAD_VALUE if \a address is NULL +*/ +static status_t +ipv4_set_to_empty_address(sockaddr *address) +{ + if (address == NULL) + return B_BAD_VALUE; + + memset(address, 0, sizeof(sockaddr_in)); + address->sa_len = sizeof(sockaddr_in); + address->sa_family = AF_INET; + return B_OK; +} + + +/*! + Computes a hash-value of the given addresses \a ourAddress + and \a peerAddress. + \return uint32 representing the hash-value +*/ +static uint32 +ipv4_hash_address_pair(const sockaddr *ourAddress, const sockaddr *peerAddress) +{ + int32 hash = (((sockaddr_in *)ourAddress)->sin_port + | ((sockaddr_in *)peerAddress)->sin_port << 16) + ^ ((sockaddr_in *)ourAddress)->sin_addr.s_addr + ^ ((sockaddr_in *)peerAddress)->sin_addr.s_addr; + return hash; +} + + +/*! + Adds the given \a address to the IP-checksum \a checksum. + \return B_OK if \a address has been added to the checksum + \return B_BAD_VALUE if either \a address or \a checksum is NULL +*/ +static status_t +ipv4_checksum_address(Checksum *checksum, const sockaddr *address) +{ + if (checksum == NULL || address == NULL) + return B_BAD_VALUE; + + (*checksum) << (uint32)((sockaddr_in *)address)->sin_addr.s_addr; + return B_OK; +} + + +net_address_module_info gIPv4AddressModule = { + { + NULL, + 0, + NULL + }, + ipv4_copy_address, + ipv4_mask_address, + ipv4_equal_addresses, + ipv4_equal_ports, + ipv4_equal_addresses_and_ports, + ipv4_equal_masked_addresses, + ipv4_is_empty_address, + ipv4_first_mask_bit, + ipv4_check_mask, + ipv4_print_address, + ipv4_get_port, + ipv4_set_port, + ipv4_set_to, + ipv4_set_to_empty_address, + ipv4_hash_address_pair, + ipv4_checksum_address, +}; diff --git a/src/add-ons/kernel/network/protocols/ipv4/ipv4_address.h b/src/add-ons/kernel/network/protocols/ipv4/ipv4_address.h new file mode 100644 index 0000000000..94680b93d5 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/ipv4/ipv4_address.h @@ -0,0 +1,15 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Oliver Tappe, zooey@hirschkaefer.de + */ + + +#ifndef IPV4_ADDRESS_H +#define IPV4_ADDRESS_H + +extern struct net_address_module_info gIPv4AddressModule; + +#endif // IPV4_ADDRESS_H diff --git a/src/add-ons/kernel/network/protocols/tcp/Jamfile b/src/add-ons/kernel/network/protocols/tcp/Jamfile new file mode 100644 index 0000000000..a6f6010c5a --- /dev/null +++ b/src/add-ons/kernel/network/protocols/tcp/Jamfile @@ -0,0 +1,25 @@ +SubDir HAIKU_TOP src add-ons kernel network protocols tcp ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UsePrivateHeaders net ; + +KernelAddon tcp : kernel haiku_network protocols : + tcp.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network/protocols + : tcp ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network protocols ; diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp.cpp b/src/add-ons/kernel/network/protocols/tcp/tcp.cpp new file mode 100644 index 0000000000..6b260e8b3e --- /dev/null +++ b/src/add-ons/kernel/network/protocols/tcp/tcp.cpp @@ -0,0 +1,262 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include +#include + +#include +#include + +#include +#include +#include + +#include "tcp.h" + +struct tcp_protocol : net_protocol { +}; + + +net_protocol * +tcp_init_protocol(net_socket *socket) +{ + tcp_protocol *protocol = new (std::nothrow) tcp_protocol; + if (protocol == NULL) + return NULL; + + return protocol; +} + + +status_t +tcp_uninit_protocol(net_protocol *protocol) +{ + delete protocol; + return B_OK; +} + + +status_t +tcp_open(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +tcp_close(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +tcp_free(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +tcp_connect(net_protocol *protocol, const struct sockaddr *address) +{ + return B_ERROR; +} + + +status_t +tcp_accept(net_protocol *protocol, struct net_socket **_acceptedSocket) +{ + return B_ERROR; +} + + +status_t +tcp_control(net_protocol *protocol, int level, int option, void *value, + size_t *_length) +{ + return protocol->next->module->control(protocol->next, level, option, + value, _length); +} + + +status_t +tcp_bind(net_protocol *protocol, struct sockaddr *address) +{ + return B_ERROR; +} + + +status_t +tcp_unbind(net_protocol *protocol, struct sockaddr *address) +{ + return B_ERROR; +} + + +status_t +tcp_listen(net_protocol *protocol, int count) +{ + return B_ERROR; +} + + +status_t +tcp_shutdown(net_protocol *protocol, int direction) +{ + return B_ERROR; +} + + +status_t +tcp_send_data(net_protocol *protocol, net_buffer *buffer) +{ + return protocol->next->module->send_data(protocol->next, buffer); +} + + +status_t +tcp_send_routed_data(net_protocol *protocol, struct net_route *route, + net_buffer *buffer) +{ + return protocol->next->module->send_routed_data(protocol->next, route, buffer); +} + + +ssize_t +tcp_send_avail(net_protocol *protocol) +{ + return B_ERROR; +} + + +status_t +tcp_read_data(net_protocol *protocol, size_t numBytes, uint32 flags, + net_buffer **_buffer) +{ + return B_ERROR; +} + + +ssize_t +tcp_read_avail(net_protocol *protocol) +{ + return B_ERROR; +} + + +struct net_domain * +tcp_get_domain(net_protocol *protocol) +{ + return protocol->next->module->get_domain(protocol->next); +} + + +size_t +tcp_get_mtu(net_protocol *protocol, const struct sockaddr *address) +{ + return protocol->next->module->get_mtu(protocol->next, address); +} + + +status_t +tcp_receive_data(net_buffer *buffer) +{ + return B_ERROR; +} + + +status_t +tcp_error(uint32 code, net_buffer *data) +{ + return B_ERROR; +} + + +status_t +tcp_error_reply(net_protocol *protocol, net_buffer *causedError, uint32 code, + void *errorData) +{ + return B_ERROR; +} + + +// #pragma mark - + + +static status_t +tcp_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + { + net_stack_module_info *stack; + status_t status = get_module(NET_STACK_MODULE_NAME, (module_info **)&stack); + if (status < B_OK) + return status; + + stack->register_domain_protocols(AF_INET, SOCK_STREAM, IPPROTO_IP, + "network/protocols/tcp/v1", + "network/protocols/ipv4/v1", + NULL); + stack->register_domain_protocols(AF_INET, SOCK_STREAM, IPPROTO_TCP, + "network/protocols/tcp/v1", + "network/protocols/ipv4/v1", + NULL); + + stack->register_domain_receiving_protocol(AF_INET, IPPROTO_TCP, + "network/protocols/tcp/v1"); + + put_module(NET_STACK_MODULE_NAME); + return B_OK; + } + + case B_MODULE_UNINIT: + return B_OK; + + default: + return B_ERROR; + } +} + + +net_protocol_module_info sTCPModule = { + { + "network/protocols/tcp/v1", + 0, + tcp_std_ops + }, + tcp_init_protocol, + tcp_uninit_protocol, + tcp_open, + tcp_close, + tcp_free, + tcp_connect, + tcp_accept, + tcp_control, + tcp_bind, + tcp_unbind, + tcp_listen, + tcp_shutdown, + tcp_send_data, + tcp_send_routed_data, + tcp_send_avail, + tcp_read_data, + tcp_read_avail, + tcp_get_domain, + tcp_get_mtu, + tcp_receive_data, + tcp_error, + tcp_error_reply, +}; + +module_info *modules[] = { + (module_info *)&sTCPModule, + NULL +}; diff --git a/src/add-ons/kernel/network/protocols/tcp/tcp.h b/src/add-ons/kernel/network/protocols/tcp/tcp.h new file mode 100644 index 0000000000..207634c6c4 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/tcp/tcp.h @@ -0,0 +1,54 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Andrew Galante, haiku.galante@gmail.com + */ + +#include + +typedef enum { + CLOSED, + LISTEN, + SYN_SENT, + SYN_RCVD, + ESTABLISHED, + CLOSE_WAIT, + LAST_ACK, + FIN_WAIT1, + FIN_WAIT2, + CLOSING, + TIME_WAIT +} tcp_state; + +struct tcp_header { + uint16 source_port; + uint16 destination_port; + uint32 sequence_num; + uint32 acknowledge_num; + struct { +#if B_HOST_IS_LENDIAN == 1 + uint8 reserved : 4; + uint8 header_length : 4; +#else + uint8 header_length : 4; + uint8 reserved : 4; +#endif + }; + uint8 flags; + uint16 advertised_window; + uint16 checksum; + uint16 urgent_ptr; + uint32 options; +}; + +/* TCP flag constants */ +#define TCP_FLG_CWR 0x80 /* Congestion Window Reduced */ +#define TCP_FLG_ECN 0x40 /* Explicit Congestion Notification echo */ +#define TCP_FLG_URG 0x20 /* URGent */ +#define TCP_FLG_ACK 0x10 /* ACKnowledge */ +#define TCP_FLG_PUS 0x08 /* PUSh */ +#define TCP_FLG_RST 0x04 /* ReSeT */ +#define TCP_FLG_SYN 0x02 /* SYNchronize */ +#define TCP_FLG_FIN 0x01 /* FINish */ diff --git a/src/add-ons/kernel/network/protocols/udp/Jamfile b/src/add-ons/kernel/network/protocols/udp/Jamfile new file mode 100644 index 0000000000..1d4215e224 --- /dev/null +++ b/src/add-ons/kernel/network/protocols/udp/Jamfile @@ -0,0 +1,25 @@ +SubDir HAIKU_TOP src add-ons kernel network protocols udp ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + +UsePrivateHeaders net ; + +KernelAddon udp : kernel haiku_network protocols : + udp.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network/protocols + : udp ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network protocols ; diff --git a/src/add-ons/kernel/network/protocols/udp/udp.cpp b/src/add-ons/kernel/network/protocols/udp/udp.cpp new file mode 100644 index 0000000000..7699235c1c --- /dev/null +++ b/src/add-ons/kernel/network/protocols/udp/udp.cpp @@ -0,0 +1,1210 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Oliver Tappe, zooey@hirschkaefer.de + */ + + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + + +#define TRACE_UDP +#ifdef TRACE_UDP +# define TRACE(x) dprintf x +# define TRACE_BLOCK(x) dump_block x +#else +# define TRACE(x) +# define TRACE_BLOCK(x) +#endif + + +struct udp_header { + uint16 source_port; + uint16 destination_port; + uint16 udp_length; + uint16 udp_checksum; +} _PACKED; + + +class UdpEndpoint : public net_protocol { +public: + UdpEndpoint(net_socket *socket); + ~UdpEndpoint(); + + status_t Bind(sockaddr *newAddr); + status_t Unbind(sockaddr *newAddr); + status_t Connect(const sockaddr *newAddr); + + status_t Open(); + status_t Close(); + status_t Free(); + + status_t SendData(net_buffer *buffer, net_route *route); + + ssize_t BytesAvailable(); + status_t FetchData(size_t numBytes, uint32 flags, + net_buffer **_buffer); + + status_t StoreData(net_buffer *buffer); + + UdpEndpoint *hash_link; + // link required by hash_table (see khash.h) +private: + status_t _Activate(); + status_t _Deactivate(); + + bool fActive; + // an active UdpEndpoint is part of the endpoint + // hash (and it is bound and optionally connected) + net_fifo fFifo; + // storage space for incoming data +}; + + +class UdpEndpointManager { + + struct hash_key { + hash_key(sockaddr *ourAddress, sockaddr *peerAddress); + + sockaddr ourAddress; + sockaddr peerAddress; + }; + + class Ephemerals { + public: + Ephemerals(); + ~Ephemerals(); + + uint16 GetNext(hash_table *activeEndpoints); + static const uint16 kFirst = 49152; + static const uint16 kLast = 65535; + private: + uint16 fLastUsed; + }; + +public: + UdpEndpointManager(); + ~UdpEndpointManager(); + + status_t DemuxBroadcast(net_buffer *buffer); + status_t DemuxMulticast(net_buffer *buffer); + status_t DemuxUnicast(net_buffer *buffer); + status_t DemuxIncomingBuffer(net_buffer *buffer); + status_t ReceiveData(net_buffer *buffer); + + static int Compare(void *udpEndpoint, const void *_key); + static uint32 ComputeHash(sockaddr *ourAddress, sockaddr *peerAddress); + static uint32 Hash(void *udpEndpoint, const void *key, uint32 range); + + UdpEndpoint *FindActiveEndpoint(sockaddr *ourAddress, + sockaddr *peerAddress); + status_t CheckBindRequest(sockaddr *address, int socketOptions); + + status_t ActivateEndpoint(UdpEndpoint *endpoint); + status_t DeactivateEndpoint(UdpEndpoint *endpoint); + + status_t OpenEndpoint(UdpEndpoint *endpoint); + status_t CloseEndpoint(UdpEndpoint *endpoint); + status_t FreeEndpoint(UdpEndpoint *endpoint); + + uint16 GetEphemeralPort(); + + benaphore *Locker(); + status_t InitCheck() const; +private: + benaphore fLock; + hash_table *fActiveEndpoints; + static const uint32 kNumHashBuckets = 0x800; + // if you change this, adjust the shifting in + // Hash() accordingly! + Ephemerals fEphemerals; + status_t fStatus; + uint32 fEndpointCount; +}; + + +static UdpEndpointManager *sUdpEndpointManager; + +static net_domain *sDomain; + +static net_address_module_info *sAddressModule; +net_buffer_module_info *sBufferModule; +static net_datalink_module_info *sDatalinkModule; +static net_stack_module_info *sStackModule; + + +// #pragma mark - + + +UdpEndpointManager::hash_key::hash_key(sockaddr *_ourAddress, sockaddr *_peerAddress) +{ + memcpy(&ourAddress, _ourAddress, sizeof(sockaddr)); + memcpy(&peerAddress, _peerAddress, sizeof(sockaddr)); +} + + +// #pragma mark - + + +UdpEndpointManager::Ephemerals::Ephemerals() + : + fLastUsed(kLast) +{ +} + + +UdpEndpointManager::Ephemerals::~Ephemerals() +{ +} + + +uint16 +UdpEndpointManager::Ephemerals::GetNext(hash_table *activeEndpoints) +{ + uint16 stop, curr, ncurr; + if (fLastUsed < kLast) { + stop = fLastUsed; + curr = fLastUsed + 1; + } else { + stop = kLast; + curr = kFirst; + } + + TRACE(("UdpEndpointManager::Ephemerals::GetNext()...\n")); + // TODO: a free list could be used to avoid the impact of these + // two nested loops most of the time... let's see how bad this really is + UdpEndpoint *endpoint; + struct hash_iterator endpointIterator; + hash_open(activeEndpoints, &endpointIterator); + bool found = false; + uint16 endpointPort; + while(!found && curr != stop) { + TRACE(("...trying port %u...\n", curr)); + ncurr = htons(curr); + for(hash_rewind(activeEndpoints, &endpointIterator); !found; ) { + endpoint = (UdpEndpoint *)hash_next(activeEndpoints, &endpointIterator); + if (!endpoint) { + found = true; + break; + } + endpointPort = sAddressModule->get_port( + (sockaddr *)&endpoint->socket->address); + TRACE(("...checking endpoint %p (port=%u)...\n", endpoint, + ntohs(endpointPort))); + if (endpointPort == ncurr) + break; + } + if (!found) { + if (curr < kLast) + curr++; + else + curr = kFirst; + } + } + hash_close(activeEndpoints, &endpointIterator, false); + if (!found) + return 0; + TRACE(("...using port %u\n", curr)); + fLastUsed = curr; + return curr; +} + + +// #pragma mark - + + +UdpEndpointManager::UdpEndpointManager() + : + fStatus(B_NO_INIT), + fEndpointCount(0) +{ + fActiveEndpoints = hash_init(kNumHashBuckets, offsetof(UdpEndpoint, hash_link), + &Compare, &Hash); + if (fActiveEndpoints == NULL) { + fStatus = B_NO_MEMORY; + return; + } + + fStatus = benaphore_init(&fLock, "UDP endpoints"); + if (fStatus < B_OK) + hash_uninit(fActiveEndpoints); +} + + +UdpEndpointManager::~UdpEndpointManager() +{ + benaphore_destroy(&fLock); + hash_uninit(fActiveEndpoints); +} + + +inline benaphore * +UdpEndpointManager::Locker() +{ + return &fLock; +} + + +inline status_t +UdpEndpointManager::InitCheck() const +{ + return fStatus; +} + + +// #pragma mark - hashing + + +/*static*/ int +UdpEndpointManager::Compare(void *_udpEndpoint, const void *_key) +{ + struct UdpEndpoint *udpEndpoint = (UdpEndpoint*)_udpEndpoint; + hash_key *key = (hash_key *)_key; + + sockaddr *ourAddr = (sockaddr *)&udpEndpoint->socket->address; + sockaddr *peerAddr = (sockaddr *)&udpEndpoint->socket->peer; + + if (sAddressModule->equal_addresses_and_ports(ourAddr, &key->ourAddress) + && sAddressModule->equal_addresses_and_ports(peerAddr, &key->peerAddress)) + return 0; + + return 1; +} + + +/*static*/ inline uint32 +UdpEndpointManager::ComputeHash(sockaddr *ourAddress, sockaddr *peerAddress) +{ + return sAddressModule->hash_address_pair(ourAddress, peerAddress); +} + + +/*static*/ uint32 +UdpEndpointManager::Hash(void *_udpEndpoint, const void *_key, uint32 range) +{ + uint32 hash; + + if (_udpEndpoint) { + struct UdpEndpoint *udpEndpoint = (UdpEndpoint*)_udpEndpoint; + sockaddr *ourAddr = (sockaddr*)&udpEndpoint->socket->address; + sockaddr *peerAddr = (sockaddr*)&udpEndpoint->socket->peer; + hash = ComputeHash(ourAddr, peerAddr); + } else { + hash_key *key = (hash_key *)_key; + hash = ComputeHash(&key->ourAddress, &key->peerAddress); + } + + // move the bits into the relevant range (as defined by kNumHashBuckets): + hash = (hash & 0x000007FF) ^ (hash & 0x003FF800) >> 11 + ^ (hash & 0xFFC00000UL) >> 22; + + TRACE(("UDP-endpoint hash is %lx\n", hash % range)); + return hash % range; +} + + +// #pragma mark - inbound + + +UdpEndpoint * +UdpEndpointManager::FindActiveEndpoint(sockaddr *ourAddress, + sockaddr *peerAddress) +{ + TRACE(("trying to find UDP-endpoint for (l:%s p:%s)\n", + AddressString(sDomain, ourAddress, true).Data(), + AddressString(sDomain, peerAddress, true).Data())); + hash_key key(ourAddress, peerAddress); + UdpEndpoint *endpoint = (UdpEndpoint *)hash_lookup(fActiveEndpoints, &key); + return endpoint; +} + + +status_t +UdpEndpointManager::DemuxBroadcast(net_buffer *buffer) +{ + sockaddr *peerAddr = (sockaddr *)&buffer->source; + sockaddr *broadcastAddr = (sockaddr *)&buffer->destination; + sockaddr *mask = NULL; + if (buffer->interface) + mask = (sockaddr *)buffer->interface->mask; + + TRACE(("demuxing buffer %p as broadcast...\n", buffer)); + + sockaddr anyAddr; + sAddressModule->set_to_empty_address(&anyAddr); + + uint16 incomingPort = sAddressModule->get_port(broadcastAddr); + + UdpEndpoint *endpoint; + sockaddr *addr, *connectAddr; + struct hash_iterator endpointIterator; + for(hash_open(fActiveEndpoints, &endpointIterator); ; ) { + endpoint = (UdpEndpoint *)hash_next(fActiveEndpoints, &endpointIterator); + if (!endpoint) + break; + + addr = (sockaddr *)&endpoint->socket->address; + TRACE(("UDP-DemuxBroadcast() is checking endpoint %s...\n", + AddressString(sDomain, addr, true).Data())); + + if (incomingPort != sAddressModule->get_port(addr)) { + // ports don't match, so we do not dispatch to this endpoint... + continue; + } + + connectAddr = (sockaddr *)&endpoint->socket->peer; + if (!sAddressModule->is_empty_address(connectAddr)) { + // endpoint is connected to a specific destination, we check if + // this datagram is from there: + if (!sAddressModule->equal_addresses_and_ports(connectAddr, peerAddr)) { + // no, datagram is from another peer, so we do not dispatch to + // this endpoint... + continue; + } + } + + if (sAddressModule->equal_masked_addresses(addr, broadcastAddr, mask) + || sAddressModule->equal_addresses(addr, &anyAddr)) { + // address matches, dispatch to this endpoint: + endpoint->StoreData(buffer); + } + } + hash_close(fActiveEndpoints, &endpointIterator, false); + return B_OK; +} + + +status_t +UdpEndpointManager::DemuxMulticast(net_buffer *buffer) +{ // TODO: implement! + return B_ERROR; +} + + +status_t +UdpEndpointManager::DemuxUnicast(net_buffer *buffer) +{ + struct sockaddr *peerAddr = (struct sockaddr *)&buffer->source; + struct sockaddr *localAddr = (struct sockaddr *)&buffer->destination; + + TRACE(("demuxing buffer %p as unicast...\n", buffer)); + + struct sockaddr anyAddr; + sAddressModule->set_to_empty_address(&anyAddr); + + UdpEndpoint *endpoint; + // look for full (most special) match: + endpoint = FindActiveEndpoint(localAddr, peerAddr); + if (!endpoint) { + // look for endpoint matching local address & port: + endpoint = FindActiveEndpoint(localAddr, &anyAddr); + if (!endpoint) { + // look for endpoint matching peer address & port and local port: + sockaddr localPortAddr; + sAddressModule->set_to_empty_address(&localPortAddr); + uint16 localPort = sAddressModule->get_port(localAddr); + sAddressModule->set_port(&localPortAddr, localPort); + endpoint = FindActiveEndpoint(&localPortAddr, peerAddr); + if (!endpoint) { + // last chance: look for endpoint matching local port only: + endpoint = FindActiveEndpoint(&localPortAddr, &anyAddr); + } + } + } + if (!endpoint) + return B_NAME_NOT_FOUND; + + endpoint->StoreData(buffer); + return B_OK; +} + + +status_t +UdpEndpointManager::DemuxIncomingBuffer(net_buffer *buffer) +{ + status_t status; + + if (buffer->flags & MSG_BCAST) + status = DemuxBroadcast(buffer); + else if (buffer->flags & MSG_MCAST) + status = DemuxMulticast(buffer); + else + status = DemuxUnicast(buffer); + + return status; +} + + +status_t +UdpEndpointManager::ReceiveData(net_buffer *buffer) +{ + NetBufferHeader bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return bufferHeader.Status(); + + udp_header &header = bufferHeader.Data(); + + struct sockaddr *source = (struct sockaddr *)&buffer->source; + struct sockaddr *destination = (struct sockaddr *)&buffer->destination; + + BenaphoreLocker locker(sUdpEndpointManager->Locker()); + if (!sDomain) { + // domain and address module are not known yet, we copy them from + // the buffer's interface (if any): + if (buffer->interface == NULL || buffer->interface->domain == NULL) + sDomain = sStackModule->get_domain(AF_INET); + else + sDomain = buffer->interface->domain; + if (sDomain == NULL) { + // this shouldn't occur, of course, but who knows... + return B_BAD_VALUE; + } + sAddressModule = sDomain->address_module; + } + sAddressModule->set_port(source, header.source_port); + sAddressModule->set_port(destination, header.destination_port); + TRACE(("UDP received data from source %s for destination %s\n", + AddressString(sDomain, source, true).Data(), + AddressString(sDomain, destination, true).Data())); + + uint16 udpLength = ntohs(header.udp_length); + if (udpLength > buffer->size) { + TRACE(("buffer %p is too short (%lu instead of %u), we drop it!\n", + buffer, buffer->size, udpLength)); + return B_MISMATCHED_VALUES; + } + if (buffer->size > udpLength) { + TRACE(("buffer %p is too long (%lu instead of %u), trimming it.\n", + buffer, buffer->size, udpLength)); + sBufferModule->trim(buffer, udpLength); + } + + if (header.udp_checksum != 0) { + // check UDP-checksum (simulating a so-called "pseudo-header"): + Checksum udpChecksum; + sAddressModule->checksum_address(&udpChecksum, source); + sAddressModule->checksum_address(&udpChecksum, destination); + udpChecksum + << (uint16)htons(IPPROTO_UDP) + << header.udp_length + // peculiar but correct: UDP-len is used twice for checksum + // (as it is already contained in udp_header) + << Checksum::BufferHelper(buffer, sBufferModule); + uint16 sum = udpChecksum; + if (sum != 0) { + TRACE(("buffer %p has bad checksum (%u), we drop it!\n", buffer, sum)); + return B_BAD_VALUE; + } + } + + bufferHeader.Remove(); + // remove UDP-header from buffer before passing it on + + status_t status = DemuxIncomingBuffer(buffer); + if (status < B_OK) { + TRACE(("no matching endpoint found for buffer %p, we drop it!", buffer)); + // TODO: send ICMP-error + return B_ERROR; + } + + return B_ERROR; +} + + +// #pragma mark - activation + + +status_t +UdpEndpointManager::CheckBindRequest(sockaddr *address, int socketOptions) +{ // sUdpEndpointManager->Locker() must be locked! + status_t status = B_OK; + UdpEndpoint *otherEndpoint; + sockaddr *otherAddr; + struct hash_iterator endpointIterator; + + // Iterate over all active UDP-endpoints and check if the requested bind + // is allowed (see figure 22.24 in [Stevens - TCP2, p735]): + hash_open(fActiveEndpoints, &endpointIterator); + TRACE(("UdpEndpointManager::CheckBindRequest() for %s...\n", + AddressString(sDomain, address, true).Data())); + while(1) { + otherEndpoint = (UdpEndpoint *)hash_next(fActiveEndpoints, &endpointIterator); + if (!otherEndpoint) + break; + otherAddr = (sockaddr *)&otherEndpoint->socket->address; + TRACE(("...checking endpoint %p (port=%u)...\n", otherEndpoint, + ntohs(sAddressModule->get_port(otherAddr)))); + if (sAddressModule->equal_ports(otherAddr, address)) { + // port is already bound, SO_REUSEADDR or SO_REUSEPORT is required: + if (otherEndpoint->socket->options & (SO_REUSEADDR | SO_REUSEPORT) == 0 + || socketOptions & (SO_REUSEADDR | SO_REUSEPORT) == 0) { + status = EADDRINUSE; + break; + } + // if both addresses are the same, SO_REUSEPORT is required: + if (sAddressModule->equal_addresses(otherAddr, address) + && (otherEndpoint->socket->options & SO_REUSEPORT == 0 + || socketOptions & SO_REUSEPORT == 0)) { + status = EADDRINUSE; + break; + } + } + } + hash_close(fActiveEndpoints, &endpointIterator, false); + + TRACE(("UdpEndpointManager::CheckBindRequest done (status=%lx)\n", status)); + return status; +} + + +status_t +UdpEndpointManager::ActivateEndpoint(UdpEndpoint *endpoint) +{ // sUdpEndpointManager->Locker() must be locked! + TRACE(("UDP-endpoint(%s) is activated\n", + AddressString(sDomain, (sockaddr *)&endpoint->socket->address, true).Data())); + return hash_insert(fActiveEndpoints, endpoint); +} + + +status_t +UdpEndpointManager::DeactivateEndpoint(UdpEndpoint *endpoint) +{ // sUdpEndpointManager->Locker() must be locked! + TRACE(("UDP-endpoint(%s) is deactivated\n", + AddressString(sDomain, (sockaddr *)&endpoint->socket->address, true).Data())); + return hash_remove(fActiveEndpoints, endpoint); +} + + +status_t +UdpEndpointManager::OpenEndpoint(UdpEndpoint *endpoint) +{ // sUdpEndpointManager->Locker() must be locked! + if (fEndpointCount++ == 0) { + sDomain = sStackModule->get_domain(AF_INET); + sAddressModule = sDomain->address_module; + TRACE(("udp: setting domain-pointer to %p.\n", sDomain)); + } + return B_OK; +} + + +status_t +UdpEndpointManager::CloseEndpoint(UdpEndpoint *endpoint) +{ // sUdpEndpointManager->Locker() must be locked! + return B_OK; +} + + +status_t +UdpEndpointManager::FreeEndpoint(UdpEndpoint *endpoint) +{ // sUdpEndpointManager->Locker() must be locked! + if (--fEndpointCount == 0) { + TRACE(("udp: clearing domain-pointer and address-module.\n")); + sDomain = NULL; + sAddressModule = NULL; + } + return B_OK; +} + + +uint16 +UdpEndpointManager::GetEphemeralPort() +{ + return fEphemerals.GetNext(fActiveEndpoints); +} + + +// #pragma mark - + + +UdpEndpoint::UdpEndpoint(net_socket *socket) + : + fActive(false) +{ + status_t status = sStackModule->init_fifo(&fFifo, "UDP endpoint fifo", + socket->receive.buffer_size); + if (status < B_OK) + fFifo.notify = status; +} + + +UdpEndpoint::~UdpEndpoint() +{ + if (fFifo.notify >= B_OK) + sStackModule->uninit_fifo(&fFifo); +} + + +// #pragma mark - activation + + +status_t +UdpEndpoint::Bind(sockaddr *address) +{ + if (address->sa_family != AF_INET) + return EAFNOSUPPORT; + + // let IP check whether there is an interface that supports the given address: + status_t status = next->module->bind(next, address); + if (status < B_OK) + return status; + + BenaphoreLocker locker(sUdpEndpointManager->Locker()); + + if (fActive) { + // socket module should have called unbind() before! + return EINVAL; + } + + if (sAddressModule->get_port(address) == 0) { + uint16 port = htons(sUdpEndpointManager->GetEphemeralPort()); + if (port == 0) + return ENOBUFS; + // whoa, no more ephemeral port available!?! + sAddressModule->set_port((sockaddr *)&socket->address, port); + } else { + status = sUdpEndpointManager->CheckBindRequest((sockaddr *)&socket->address, + socket->options); + if (status < B_OK) + return status; + } + + return _Activate(); +} + + +status_t +UdpEndpoint::Unbind(sockaddr *address) +{ + if (address->sa_family != AF_INET) + return EAFNOSUPPORT; + + BenaphoreLocker locker(sUdpEndpointManager->Locker()); + + return _Deactivate(); +} + + +status_t +UdpEndpoint::Connect(const sockaddr *address) +{ + if (address->sa_family != AF_INET && address->sa_family != AF_UNSPEC) + return EAFNOSUPPORT; + + BenaphoreLocker locker(sUdpEndpointManager->Locker()); + + if (fActive) + _Deactivate(); + + if (address->sa_family == AF_UNSPEC) { + // [Stevens-UNP1, p226]: specifying AF_UNSPEC requests a "disconnect", + // so we reset the peer address: + sAddressModule->set_to_empty_address((sockaddr *)&socket->peer); + } else + sAddressModule->set_to((sockaddr *)&socket->peer, address); + + // we need to activate no matter whether or not we have just disconnected, + // as calling connect() always triggers an implicit bind(): + return _Activate(); +} + + +status_t +UdpEndpoint::Open() +{ + BenaphoreLocker locker(sUdpEndpointManager->Locker()); + return sUdpEndpointManager->OpenEndpoint(this); +} + + +status_t +UdpEndpoint::Close() +{ + BenaphoreLocker locker(sUdpEndpointManager->Locker()); + if (fActive) + _Deactivate(); + return sUdpEndpointManager->CloseEndpoint(this); +} + + +status_t +UdpEndpoint::Free() +{ + BenaphoreLocker locker(sUdpEndpointManager->Locker()); + return sUdpEndpointManager->FreeEndpoint(this); +} + + +status_t +UdpEndpoint::_Activate() +{ + if (fActive) + return B_ERROR; + status_t status = sUdpEndpointManager->ActivateEndpoint(this); + fActive = (status == B_OK); + return status; +} + + +status_t +UdpEndpoint::_Deactivate() +{ + if (!fActive) + return B_ERROR; + status_t status = sUdpEndpointManager->DeactivateEndpoint(this); + fActive = false; + return status; +} + + +// #pragma mark - outbound + + +status_t +UdpEndpoint::SendData(net_buffer *buffer, net_route *route) +{ + if (buffer->size > socket->send.buffer_size) + return EMSGSIZE; + + buffer->protocol = IPPROTO_UDP; + + { // scope for lifetime of bufferHeader + + // add and fill UDP-specific header: + NetBufferPrepend bufferHeader(buffer); + if (bufferHeader.Status() < B_OK) + return bufferHeader.Status(); + + udp_header &header = bufferHeader.Data(); + + header.source_port = sAddressModule->get_port((sockaddr *)&buffer->source); + header.destination_port = sAddressModule->get_port( + (sockaddr *)&buffer->destination); + header.udp_length = htons(buffer->size); + // the udp-header is already included in the buffer-size + header.udp_checksum = 0; + + // generate UDP-checksum (simulating a so-called "pseudo-header"): + Checksum udpChecksum; + sAddressModule->checksum_address(&udpChecksum, + (sockaddr *)route->interface->address); + sAddressModule->checksum_address(&udpChecksum, + (sockaddr *)&buffer->destination); + udpChecksum + << (uint16)htons(IPPROTO_UDP) + << (uint16)htons(buffer->size) + // peculiar but correct: UDP-len is used twice for checksum + // (as it is already contained in udp_header) + << Checksum::BufferHelper(buffer, sBufferModule); + header.udp_checksum = udpChecksum; + if (header.udp_checksum == 0) + header.udp_checksum = 0xFFFF; + + TRACE_BLOCK(((char*)&header, sizeof(udp_header), "udp-hdr: ")); + } + return next->module->send_routed_data(next, route, buffer); +} + + +// #pragma mark - inbound + + +ssize_t +UdpEndpoint::BytesAvailable() +{ + return fFifo.current_bytes; +} + + +status_t +UdpEndpoint::FetchData(size_t numBytes, uint32 flags, net_buffer **_buffer) +{ + net_buffer *buffer; + AddressString addressString(sDomain, (sockaddr *)&socket->address, true); + TRACE(("FetchData() with size=%ld called for endpoint with (%s)\n", + numBytes, addressString.Data())); + + status_t status = sStackModule->fifo_dequeue_buffer(&fFifo, flags, + socket->receive.timeout, &buffer); + TRACE(("Endpoint with (%s) returned from fifo status=%lx\n", + addressString.Data(), status)); + if (status < B_OK) + return status; + + if (numBytes < buffer->size) { + // discard any data behind the amount requested + sBufferModule->trim(buffer, numBytes); + // TODO: we should indicate MSG_TRUNC to application! + } + + TRACE(("FetchData() returns buffer with %ld data bytes\n", buffer->size)); + *_buffer = buffer; + return B_OK; +} + + +status_t +UdpEndpoint::StoreData(net_buffer *_buffer) +{ + TRACE(("buffer %p passed to endpoint with (%s)\n", _buffer, + AddressString(sDomain, (sockaddr *)&socket->address, true).Data())); + net_buffer *buffer = sBufferModule->clone(_buffer, false); + if (buffer == NULL) + return B_NO_MEMORY; + + status_t status = sStackModule->fifo_enqueue_buffer(&fFifo, buffer); + if (status < B_OK) + sBufferModule->free(buffer); + + return status; +} + + +// #pragma mark - protocol interface + + +net_protocol * +udp_init_protocol(net_socket *socket) +{ + socket->protocol = IPPROTO_UDP; + socket->send.buffer_size = 65535 - 20 - 8; + // subtract lengths of IP and UDP headers (NOTE: IP headers could be + // larger if IP options are used, but we do not currently care for that) + + UdpEndpoint *endpoint = new (std::nothrow) UdpEndpoint(socket); + TRACE(("udp_init_protocol(%p) created endpoint %p\n", socket, endpoint)); + return endpoint; +} + + +status_t +udp_uninit_protocol(net_protocol *protocol) +{ + TRACE(("udp_uninit_protocol(%p)\n", protocol)); + UdpEndpoint *udpEndpoint = (UdpEndpoint *)protocol; + delete udpEndpoint; + return B_OK; +} + + +status_t +udp_open(net_protocol *protocol) +{ + TRACE(("udp_open(%p)\n", protocol)); + UdpEndpoint *udpEndpoint = (UdpEndpoint *)protocol; + return udpEndpoint->Open(); +} + + +status_t +udp_close(net_protocol *protocol) +{ + TRACE(("udp_close(%p)\n", protocol)); + UdpEndpoint *udpEndpoint = (UdpEndpoint *)protocol; + return udpEndpoint->Close(); +} + + +status_t +udp_free(net_protocol *protocol) +{ + TRACE(("udp_free(%p)\n", protocol)); + UdpEndpoint *udpEndpoint = (UdpEndpoint *)protocol; + return udpEndpoint->Free(); +} + + +status_t +udp_connect(net_protocol *protocol, const struct sockaddr *address) +{ + TRACE(("udp_connect(%p) on address %s\n", protocol, + AddressString(sDomain, address, true).Data())); + UdpEndpoint *udpEndpoint = (UdpEndpoint *)protocol; + return udpEndpoint->Connect(address); +} + + +status_t +udp_accept(net_protocol *protocol, struct net_socket **_acceptedSocket) +{ + return EOPNOTSUPP; +} + + +status_t +udp_control(net_protocol *protocol, int level, int option, void *value, + size_t *_length) +{ + TRACE(("udp_control(%p)\n", protocol)); + return protocol->next->module->control(protocol->next, level, option, + value, _length); +} + + +status_t +udp_bind(net_protocol *protocol, struct sockaddr *address) +{ + TRACE(("udp_bind(%p) on address %s\n", protocol, + AddressString(sDomain, address, true).Data())); + UdpEndpoint *udpEndpoint = (UdpEndpoint *)protocol; + return udpEndpoint->Bind(address); +} + + +status_t +udp_unbind(net_protocol *protocol, struct sockaddr *address) +{ + TRACE(("udp_unbind(%p) on address %s\n", protocol, + AddressString(sDomain, address, true).Data())); + UdpEndpoint *udpEndpoint = (UdpEndpoint *)protocol; + return udpEndpoint->Unbind(address); +} + + +status_t +udp_listen(net_protocol *protocol, int count) +{ + return EOPNOTSUPP; +} + + +status_t +udp_shutdown(net_protocol *protocol, int direction) +{ + return EOPNOTSUPP; +} + + +status_t +udp_send_routed_data(net_protocol *protocol, struct net_route *route, + net_buffer *buffer) +{ + TRACE(("udp_send_routed_data(%p) size=%lu\n", protocol, buffer->size)); + UdpEndpoint *udpEndpoint = (UdpEndpoint *)protocol; + return udpEndpoint->SendData(buffer, route); +} + + +status_t +udp_send_data(net_protocol *protocol, net_buffer *buffer) +{ + TRACE(("udp_send_data(%p) size=%lu\n", protocol, buffer->size)); + + struct net_route *route = sDatalinkModule->get_route(sDomain, + (sockaddr *)&buffer->destination); + if (route == NULL) + return ENETUNREACH; + + UdpEndpoint *udpEndpoint = (UdpEndpoint *)protocol; + status_t status = udpEndpoint->SendData(buffer, route); + sDatalinkModule->put_route(sDomain, route); + return status; +} + + +ssize_t +udp_send_avail(net_protocol *protocol) +{ + ssize_t avail = protocol->socket->send.buffer_size; + TRACE(("udp_send_avail(%p) result=%lu\n", protocol, avail)); + return avail; +} + + +status_t +udp_read_data(net_protocol *protocol, size_t numBytes, uint32 flags, + net_buffer **_buffer) +{ + TRACE(("udp_read_data(%p) size=%lu flags=%lx\n", protocol, numBytes, flags)); + UdpEndpoint *udpEndpoint = (UdpEndpoint *)protocol; + return udpEndpoint->FetchData(numBytes, flags, _buffer); +} + + +ssize_t +udp_read_avail(net_protocol *protocol) +{ + UdpEndpoint *udpEndpoint = (UdpEndpoint *)protocol; + return udpEndpoint->BytesAvailable(); +} + + +struct net_domain * +udp_get_domain(net_protocol *protocol) +{ + return protocol->next->module->get_domain(protocol->next); +} + + +size_t +udp_get_mtu(net_protocol *protocol, const struct sockaddr *address) +{ + return protocol->next->module->get_mtu(protocol->next, address); +} + + +status_t +udp_receive_data(net_buffer *buffer) +{ + TRACE(("udp_receive_data() size=%lu\n", buffer->size)); + return sUdpEndpointManager->ReceiveData(buffer); +} + + +status_t +udp_error(uint32 code, net_buffer *data) +{ + return B_ERROR; +} + + +status_t +udp_error_reply(net_protocol *protocol, net_buffer *causedError, uint32 code, + void *errorData) +{ + return B_ERROR; +} + + +// #pragma mark - module interface + + +static status_t +init_udp() +{ + status_t status; + TRACE(("init_udp()\n")); + + status = get_module(NET_STACK_MODULE_NAME, (module_info **)&sStackModule); + if (status < B_OK) + return status; + status = get_module(NET_BUFFER_MODULE_NAME, (module_info **)&sBufferModule); + if (status < B_OK) + goto err1; + status = get_module(NET_DATALINK_MODULE_NAME, (module_info **)&sDatalinkModule); + if (status < B_OK) + goto err2; + + sUdpEndpointManager = new (std::nothrow) UdpEndpointManager; + if (sUdpEndpointManager == NULL) { + status = ENOBUFS; + goto err3; + } + status = sUdpEndpointManager->InitCheck(); + if (status != B_OK) + goto err3; + + status = sStackModule->register_domain_protocols(AF_INET, SOCK_DGRAM, IPPROTO_IP, + "network/protocols/udp/v1", + "network/protocols/ipv4/v1", + NULL); + if (status < B_OK) + goto err4; + status = sStackModule->register_domain_protocols(AF_INET, SOCK_DGRAM, IPPROTO_UDP, + "network/protocols/udp/v1", + "network/protocols/ipv4/v1", + NULL); + if (status < B_OK) + goto err4; + + status = sStackModule->register_domain_receiving_protocol(AF_INET, IPPROTO_UDP, + "network/protocols/udp/v1"); + if (status < B_OK) + goto err4; + + return B_OK; + +err4: + delete sUdpEndpointManager; +err3: + put_module(NET_DATALINK_MODULE_NAME); +err2: + put_module(NET_BUFFER_MODULE_NAME); +err1: + put_module(NET_STACK_MODULE_NAME); + + TRACE(("init_udp() fails with %lx (%s)\n", status, strerror(status))); + return status; +} + + +static status_t +uninit_udp() +{ + TRACE(("uninit_udp()\n")); + delete sUdpEndpointManager; + put_module(NET_DATALINK_MODULE_NAME); + put_module(NET_BUFFER_MODULE_NAME); + put_module(NET_STACK_MODULE_NAME); + return B_OK; +} + + +static status_t +udp_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + return init_udp(); + + case B_MODULE_UNINIT: + return uninit_udp(); + + default: + return B_ERROR; + } +} + + +net_protocol_module_info sUDPModule = { + { + "network/protocols/udp/v1", + 0, + udp_std_ops + }, + udp_init_protocol, + udp_uninit_protocol, + udp_open, + udp_close, + udp_free, + udp_connect, + udp_accept, + udp_control, + udp_bind, + udp_unbind, + udp_listen, + udp_shutdown, + udp_send_data, + udp_send_routed_data, + udp_send_avail, + udp_read_data, + udp_read_avail, + udp_get_domain, + udp_get_mtu, + udp_receive_data, + udp_error, + udp_error_reply, +}; + +module_info *modules[] = { + (module_info *)&sUDPModule, + NULL +}; diff --git a/src/add-ons/kernel/network/stack/Jamfile b/src/add-ons/kernel/network/stack/Jamfile new file mode 100644 index 0000000000..34e771304c --- /dev/null +++ b/src/add-ons/kernel/network/stack/Jamfile @@ -0,0 +1,35 @@ +SubDir HAIKU_TOP src add-ons kernel network stack ; + +SetSubDirSupportedPlatformsBeOSCompatible ; + +if $(TARGET_PLATFORM) != haiku { + UseHeaders [ FStandardOSHeaders ] : true ; + # Needed for and maybe other stuff. + UseHeaders [ FDirName $(HAIKU_TOP) headers posix ] : true ; + # We need the public network headers also when not compiling for Haiku. + # Unfortunately we get more than we want, namely all POSIX headers. +} + + +UsePrivateHeaders net ; + +KernelAddon stack : kernel haiku_network : + datalink.cpp + domains.cpp + interfaces.cpp + net_buffer.cpp + net_socket.cpp + link.cpp + radix.c + routes.cpp + stack.cpp + utility.cpp +; + +# Installation +HaikuInstall install-networking : /boot/home/config/add-ons/kernel/haiku_network + : stack ; + +Package haiku-networkingkit-cvs : + haiku : + boot home config add-ons kernel haiku_network ; diff --git a/src/add-ons/kernel/network/stack/datalink.cpp b/src/add-ons/kernel/network/stack/datalink.cpp new file mode 100644 index 0000000000..f35f681cc0 --- /dev/null +++ b/src/add-ons/kernel/network/stack/datalink.cpp @@ -0,0 +1,750 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include "datalink.h" +#include "domains.h" +#include "interfaces.h" +#include "routes.h" +#include "stack_private.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + + +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; +}; + + +static status_t +device_reader_thread(void *_interface) +{ + net_device_interface *interface = (net_device_interface *)_interface; + net_device *device = interface->device; + status_t status = B_OK; + int32 tries = 0; + + while ((device->flags & IFF_UP) != 0) { + net_buffer *buffer; + status = device->module->receive_data(device, &buffer); + if (status == B_OK) { + dprintf("received buffer of %ld bytes length\n", buffer->size); + tries = 0; + + // feed device monitors + // TODO: locking! + DeviceMonitorList::Iterator iterator = interface->monitor_funcs.GetIterator(); + while (iterator.HasNext()) { + net_device_monitor *monitor = iterator.Next(); + monitor->func(monitor->cookie, buffer); + } + + int32 type = interface->deframe_func(device, buffer); + if (type >= 0) { + // find handler for this packet + // TODO: locking! + DeviceHandlerList::Iterator iterator = interface->receive_funcs.GetIterator(); + status = B_ERROR; + + while (iterator.HasNext()) { + net_device_handler *handler = iterator.Next(); + + if (handler->type == type) { + status = handler->func(handler->cookie, buffer); + if (status == B_OK) + break; + } + } + } else + status = type; + + if (status == B_OK) { + // the buffer no longer belongs to us + continue; + } + + gNetBufferModule.free(buffer); + } + + if (status < B_OK) { + // this is a near real-time thread - don't render the system unusable + // in case of a device going down + snooze(10000); + + if (++tries > 20) { + // TODO: bring down the interface! + break; + } + } + } + + return status; +} + + +static struct sockaddr ** +interface_address(net_interface *interface, int32 option) +{ + switch (option) { + case SIOCSIFADDR: + case SIOCGIFADDR: + return &interface->address; + + case SIOCSIFNETMASK: + case SIOCGIFNETMASK: + return &interface->mask; + + case SIOCSIFBRDADDR: + case SIOCSIFDSTADDR: + case SIOCGIFBRDADDR: + case SIOCGIFDSTADDR: + return &interface->destination; + + default: + return NULL; + } +} + + +void +remove_default_routes(net_interface_private *interface, int32 option) +{ + net_route route; + route.destination = interface->address; + route.gateway = NULL; + route.interface = interface; + + if (interface->mask != NULL && (option == SIOCSIFNETMASK || option == SIOCSIFADDR)) { + route.mask = interface->mask; + route.flags = 0; + remove_route(interface->domain, &route); + } + + if (option == SIOCSIFADDR) { + route.mask = NULL; + route.flags = RTF_LOCAL | RTF_HOST; + remove_route(interface->domain, &route); + } +} + + +void +add_default_routes(net_interface_private *interface, int32 option) +{ + net_route route; + route.destination = interface->address; + route.gateway = NULL; + route.interface = interface; + + if (interface->mask != NULL && (option == SIOCSIFNETMASK || option == SIOCSIFADDR)) { + route.mask = interface->mask; + route.flags = 0; + add_route(interface->domain, &route); + } + + if (option == SIOCSIFADDR) { + route.mask = NULL; + route.flags = RTF_LOCAL | RTF_HOST; + add_route(interface->domain, &route); + } +} + + +// #pragma mark - datalink module + + +status_t +datalink_control(net_domain *_domain, int32 option, void *value, + size_t *_length) +{ + net_domain_private *domain = (net_domain_private *)_domain; + if (domain == NULL || domain->family == AF_LINK) { + // the AF_LINK family is already handled completely in the link protocol + return B_BAD_VALUE; + } + + switch (option) { + case SIOCGIFINDEX: + { + // get index of interface + struct ifreq request; + if (user_memcpy(&request, value, IF_NAMESIZE) < B_OK) + return B_BAD_ADDRESS; + + benaphore_lock(&domain->lock); + + net_interface *interface = find_interface(domain, + request.ifr_name); + if (interface != NULL) + request.ifr_index = interface->index; + else + request.ifr_index = 0; + + benaphore_unlock(&domain->lock); + + if (request.ifr_index == 0) + return ENODEV; + + return user_memcpy(value, &request, sizeof(struct ifreq)); + } + case SIOCGIFNAME: + { + // get name of interface via index + struct ifreq request; + if (user_memcpy(&request, value, sizeof(struct ifreq)) < B_OK) + return B_BAD_ADDRESS; + + benaphore_lock(&domain->lock); + status_t status = B_OK; + + net_interface *interface = find_interface(domain, + request.ifr_index); + if (interface != NULL) + strlcpy(request.ifr_name, interface->name, IF_NAMESIZE); + else + status = B_BAD_VALUE; + + benaphore_unlock(&domain->lock); + + if (status < B_OK) + return status; + + return user_memcpy(value, &request, sizeof(struct ifreq)); + } + + case SIOCAIFADDR: + { + // add new interface address + struct ifreq request; + if (user_memcpy(&request, value, sizeof(struct ifreq)) < B_OK) + return B_BAD_ADDRESS; + + return add_interface_to_domain(domain, request); + } + case SIOCDIFADDR: + { + // remove interface address + struct ifreq request; + if (user_memcpy(&request, value, sizeof(struct ifreq)) < B_OK) + return B_BAD_ADDRESS; + + benaphore_lock(&domain->lock); + status_t status; + + net_interface *interface = find_interface(domain, + request.ifr_name); + if (interface != NULL) + status = remove_interface_from_domain(interface); + else + status = ENODEV; + + benaphore_unlock(&domain->lock); + + return status; + } + + case SIOCGIFCOUNT: + { + // count number of interfaces + struct ifconf config; + config.ifc_value = count_domain_interfaces(); + + return user_memcpy(value, &config, sizeof(struct ifconf)); + } + + case SIOCGIFCONF: + { + // retrieve ifreqs for all interfaces + struct ifconf config; + if (user_memcpy(&config, value, sizeof(struct ifconf)) < B_OK) + return B_BAD_ADDRESS; + + return list_domain_interfaces(config.ifc_buf, config.ifc_len); + } + + case SIOCGRTSIZE: + { + // determine size of buffer to hold the routing table + struct ifconf config; + config.ifc_value = route_table_size(domain); + + return user_memcpy(value, &config, sizeof(struct ifconf)); + } + case SIOCGRTTABLE: + { + // retrieve all routes for this domain + 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); + } + + default: + { + // try to pass the request to an existing interface + + struct ifreq request; + if (user_memcpy(&request, value, sizeof(struct ifreq)) < B_OK) + return B_BAD_ADDRESS; + + benaphore_lock(&domain->lock); + status_t status = B_OK; + + net_interface *interface = find_interface(domain, + request.ifr_name); + if (interface != NULL) { + // filter out bringing the interface up or down + if (option == SIOCSIFFLAGS + && ((uint32)request.ifr_flags & IFF_UP) != (interface->flags & IFF_UP)) { + if ((interface->flags & IFF_UP) != 0) { + // bring the interface down + interface->flags &= ~IFF_UP; + interface->first_info->interface_down(interface->first_protocol); + } else { + // bring it up + status = interface->first_info->interface_up(interface->first_protocol); + if (status == B_OK) + interface->flags |= IFF_UP; + } + + request.ifr_flags = interface->flags; + } + + if (status == B_OK) { + // pass the request into the datalink protocol stack + status = interface->first_info->control(interface->first_protocol, + option, value, *_length); + } + } else + status = B_BAD_VALUE; + + benaphore_unlock(&domain->lock); + return status; + } + } + return B_BAD_VALUE; +} + + +status_t +datalink_send_data(struct net_route *route, net_buffer *buffer) +{ + net_interface *interface = route->interface; + net_domain *domain = interface->domain; + + dprintf("send buffer (%ld bytes) to interface %s (route flags %lx)\n", + buffer->size, interface->name, route->flags); + + if (route->flags & RTF_REJECT) + return ENETUNREACH; + + if (route->flags & RTF_LOCAL) { + // this one goes back to the domain directly + return domain->module->receive_data(buffer); + } + + if (route->flags & RTF_GATEWAY) { + // this route involves a gateway, we need to use the gateway address + // instead of the destination address: + if (route->gateway == NULL) + return B_MISMATCHED_VALUES; + memcpy(&buffer->destination, route->gateway, sizeof(sockaddr)); + } + + // this goes out to the datalink protocols + return interface->first_info->send_data(interface->first_protocol, buffer); +} + + +bool +is_local_address(net_domain *_domain, const struct sockaddr *address, + net_interface **_interface, uint32 *_matchedType) +{ + net_domain_private *domain = (net_domain_private *)_domain; + if (domain == NULL || address == NULL) + return false; + + BenaphoreLocker locker(domain->lock); + + uint32 matchedType = 0; + + net_interface *interface = NULL; + while (true) { + interface = (net_interface *)list_get_next_item( + &domain->interfaces, interface); + if (interface == NULL) + break; + if (interface->address == NULL) + continue; + + // check for matching unicast address first + if (domain->address_module->equal_addresses(interface->address, address)) + break; + + // check for matching broadcast address if interface support broadcasting + if (interface->flags & IFF_BROADCAST + && domain->address_module->equal_addresses(interface->destination, + address)) { + matchedType = MSG_BCAST; + break; + } + } + + if (interface == NULL) + return false; + + if (_interface != NULL) + *_interface = interface; + if (_matchedType != NULL) + *_matchedType = matchedType; + return true; +} + + +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; + } +} + + +// #pragma mark - net_datalink_protocol + + +status_t +interface_protocol_init(struct net_interface *_interface, net_datalink_protocol **_protocol) +{ + net_interface_private *interface = (net_interface_private *)_interface; + + interface_protocol *protocol = new (std::nothrow) interface_protocol; + if (protocol == NULL) + return B_NO_MEMORY; + + protocol->device_module = interface->device->module; + protocol->device = interface->device; + + *_protocol = protocol; + return B_OK; +} + + +status_t +interface_protocol_uninit(net_datalink_protocol *protocol) +{ + delete protocol; + return B_OK; +} + + +status_t +interface_protocol_send_data(net_datalink_protocol *_protocol, + net_buffer *buffer) +{ + interface_protocol *protocol = (interface_protocol *)_protocol; + return protocol->device_module->send_data(protocol->device, buffer); +} + + +status_t +interface_protocol_up(net_datalink_protocol *_protocol) +{ + interface_protocol *protocol = (interface_protocol *)_protocol; + net_device_interface *deviceInterface = + ((net_interface_private *)protocol->interface)->device_interface; + net_device *device = protocol->device; + + // TODO: locking! + + if (deviceInterface->up_count != 0) { + deviceInterface->up_count++; + return B_OK; + } + + status_t status = protocol->device_module->up(device); + if (status < B_OK) + return status; + + // give the thread a nice name + char name[B_OS_NAME_LENGTH]; + snprintf(name, sizeof(name), "%s reader", device->name); + + thread_id thread = spawn_kernel_thread(device_reader_thread, name, + B_REAL_TIME_DISPLAY_PRIORITY - 10, deviceInterface); + if (thread < B_OK) + return thread; + + device->flags |= IFF_UP; + resume_thread(thread); + + deviceInterface->up_count = 1; + return B_OK; +} + + +void +interface_protocol_down(net_datalink_protocol *_protocol) +{ + interface_protocol *protocol = (interface_protocol *)_protocol; + net_device_interface *deviceInterface = + ((net_interface_private *)protocol->interface)->device_interface; + net_device *device = protocol->device; + + // TODO: locking! + if (deviceInterface->up_count == 0) + return; + + deviceInterface->up_count--; + + if (deviceInterface->up_count > 0) + return; + + device->flags &= ~IFF_UP; + protocol->device_module->down(protocol->device); +} + + +status_t +interface_protocol_control(net_datalink_protocol *_protocol, + int32 option, void *argument, size_t length) +{ + interface_protocol *protocol = (interface_protocol *)_protocol; + net_interface_private *interface = (net_interface_private *)protocol->interface; + + switch (option) { + case SIOCSIFADDR: + case SIOCSIFNETMASK: + case SIOCSIFBRDADDR: + case SIOCSIFDSTADDR: + { + // set logical interface address + struct ifreq request; + if (user_memcpy(&request, argument, sizeof(struct ifreq)) < B_OK) + return B_BAD_ADDRESS; + + sockaddr **_address = interface_address(interface, option); + if (_address == NULL) + return B_BAD_VALUE; + + sockaddr *address = *_address; + sockaddr *original = address; + + // allocate new address if needed + if (address == NULL + || (address->sa_len < request.ifr_addr.sa_len + && request.ifr_addr.sa_len > sizeof(struct sockaddr))) { + address = (sockaddr *)malloc( + max_c(request.ifr_addr.sa_len, sizeof(struct sockaddr))); + } + + // copy new address over + if (address != NULL) { + remove_default_routes(interface, option); + + if (original != address) { + free(original); + *_address = address; + } + + memcpy(address, &request.ifr_addr, request.ifr_addr.sa_len); + add_default_routes(interface, option); + } + + return address != NULL ? B_OK : B_NO_MEMORY; + } + + case SIOCGIFADDR: + case SIOCGIFNETMASK: + case SIOCGIFBRDADDR: + case SIOCGIFDSTADDR: + { + // get logical interface address + sockaddr **_address = interface_address(interface, option); + if (_address == NULL) + return B_BAD_VALUE; + + struct ifreq request; + + sockaddr *address = *_address; + if (address != NULL) + memcpy(&request.ifr_addr, address, address->sa_len); + else { + request.ifr_addr.sa_len = 2; + request.ifr_addr.sa_family = AF_UNSPEC; + } + + // copy address over + return user_memcpy(&((struct ifreq *)argument)->ifr_addr, + &request.ifr_addr, request.ifr_addr.sa_len); + } + + case SIOCGIFFLAGS: + { + // get flags + struct ifreq request; + request.ifr_flags = interface->flags; + + return user_memcpy(&((struct ifreq *)argument)->ifr_flags, + &request.ifr_flags, sizeof(request.ifr_flags)); + } + case SIOCSIFFLAGS: + { + // set flags + struct ifreq request; + if (user_memcpy(&request, argument, sizeof(struct ifreq)) < B_OK) + return B_BAD_ADDRESS; + + // TODO: check flags! + interface->flags = request.ifr_flags; + return B_OK; + } + + case SIOCGIFPARAM: + { + // get interface parameter + struct ifreq request; + strlcpy(request.ifr_parameter.base_name, interface->base_name, IF_NAMESIZE); + strlcpy(request.ifr_parameter.device, interface->device_interface->name, + IF_NAMESIZE); + request.ifr_parameter.sub_type = 0; + // TODO: for now, we ignore the sub type... + + return user_memcpy(&((struct ifreq *)argument)->ifr_parameter, + &request.ifr_parameter, sizeof(request.ifr_parameter)); + } + + case SIOCGIFSTATS: + { + // get stats + return user_memcpy(&((struct ifreq *)argument)->ifr_stats, + &interface->device_interface->device->stats, + sizeof(struct ifreq_stats)); + } + + case SIOCGIFTYPE: + { + // get type + 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: + { + // get MTU + struct ifreq request; + request.ifr_mtu = interface->mtu; + + return user_memcpy(&((struct ifreq *)argument)->ifr_mtu, + &request.ifr_mtu, sizeof(request.ifr_mtu)); + } + case SIOCSIFMTU: + { + // set MTU + struct ifreq request; + if (user_memcpy(&request, argument, sizeof(struct ifreq)) < B_OK) + return B_BAD_ADDRESS; + + // check for valid bounds + if (request.ifr_mtu < 100 || (uint32)request.ifr_mtu > interface->device->mtu) + return B_BAD_VALUE; + + interface->mtu = request.ifr_mtu; + return B_OK; + } + + case SIOCGIFMETRIC: + { + // get metric + 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: + { + // set metric + struct ifreq request; + if (user_memcpy(&request, argument, sizeof(struct ifreq)) < B_OK) + return B_BAD_ADDRESS; + + interface->metric = request.ifr_metric; + return B_OK; + } + + case SIOCADDRT: + case SIOCDELRT: + // interface related route options + return control_routes(interface, option, argument, length); + } + + return protocol->device_module->control(protocol->device, + option, argument, length); +} + + +net_datalink_module_info gNetDatalinkModule = { + { + NET_DATALINK_MODULE_NAME, + 0, + datalink_std_ops + }, + + datalink_control, + datalink_send_data, + + is_local_address, + + add_route, + remove_route, + get_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_control, +}; diff --git a/src/add-ons/kernel/network/stack/datalink.h b/src/add-ons/kernel/network/stack/datalink.h new file mode 100644 index 0000000000..0c330abdc2 --- /dev/null +++ b/src/add-ons/kernel/network/stack/datalink.h @@ -0,0 +1,19 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ +#ifndef DATALINK_H +#define DATALINK_H + + +#include + + +status_t datalink_control(struct net_domain *domain, int32 option, + void *value, size_t *_length); +status_t datalink_send_data(struct net_route *route, net_buffer *buffer); + +#endif // DATALINK_H diff --git a/src/add-ons/kernel/network/stack/domains.cpp b/src/add-ons/kernel/network/stack/domains.cpp new file mode 100644 index 0000000000..b1c2ec4d80 --- /dev/null +++ b/src/add-ons/kernel/network/stack/domains.cpp @@ -0,0 +1,274 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include "domains.h" +#include "interfaces.h" + +#include + +#include +#include + +#include +#include + + +#define TRACE_DOMAINS +#ifdef TRACE_DOMAINS +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + + +static benaphore sDomainLock; +static list sDomains; + + +/*! + Scans the domain list for the specified family. + You need to hold the sDomainLock when calling this function. +*/ +static net_domain_private * +lookup_domain(int family) +{ + net_domain_private *domain = NULL; + while (true) { + domain = (net_domain_private *)list_get_next_item(&sDomains, domain); + if (domain == NULL) + break; + + if (domain->family == family) + return domain; + } + + return NULL; +} + + +// #pragma mark - + + +/*! + Gets the domain of the specified family. +*/ +net_domain * +get_domain(int family) +{ + BenaphoreLocker locker(sDomainLock); + return lookup_domain(family); +} + + +uint32 +count_domain_interfaces() +{ + BenaphoreLocker locker(sDomainLock); + + net_domain_private *domain = NULL; + uint32 count = 0; + + while (true) { + domain = (net_domain_private *)list_get_next_item(&sDomains, domain); + if (domain == NULL) + break; + + net_interface *interface = NULL; + while (true) { + interface = (net_interface *)list_get_next_item(&domain->interfaces, + interface); + if (interface == NULL) + break; + + count++; + } + } + + return count; +} + + +/*! + Dumps a list of all interfaces into the supplied userland buffer. + If the interfaces don't fit into the buffer, an error (\c ENOBUFS) is + returned. +*/ +status_t +list_domain_interfaces(void *buffer, size_t size) +{ + BenaphoreLocker locker(sDomainLock); + + net_domain_private *domain = NULL; + size_t spaceLeft = size; + + while (true) { + domain = (net_domain_private *)list_get_next_item(&sDomains, domain); + if (domain == NULL) + break; + + net_interface *interface = NULL; + while (true) { + interface = (net_interface *)list_get_next_item(&domain->interfaces, + interface); + if (interface == NULL) + break; + + size = IF_NAMESIZE + (interface->address ? interface->address->sa_len : 1); + if (spaceLeft < size) + return ENOBUFS; + + ifreq request; + strlcpy(request.ifr_name, interface->name, IF_NAMESIZE); + if (interface->address != NULL) + memcpy(&request.ifr_addr, interface->address, interface->address->sa_len); + else { + // empty address + request.ifr_addr.sa_len = 2; + request.ifr_addr.sa_family = AF_UNSPEC; + } + + if (user_memcpy(buffer, &request, size) < B_OK) + return B_BAD_ADDRESS; + + buffer = (void *)((addr_t)buffer + size); + spaceLeft -= size; + } + } + + return B_OK; +} + + +status_t +add_interface_to_domain(net_domain *_domain, + struct ifreq& request) +{ + net_domain_private *domain = (net_domain_private *)_domain; + + const char *deviceName = request.ifr_parameter.device[0] + ? request.ifr_parameter.device : request.ifr_name; + net_device_interface *deviceInterface = get_device_interface(deviceName); + if (deviceInterface == NULL) + return ENODEV; + + BenaphoreLocker locker(domain->lock); + + if (find_interface(domain, request.ifr_name) != NULL) + return B_NAME_IN_USE; + + net_interface_private *interface; + status_t status = create_interface(domain, + request.ifr_name, request.ifr_parameter.base_name[0] + ? request.ifr_parameter.base_name : request.ifr_name, + deviceInterface, &interface); + if (status < B_OK) { + put_device_interface(deviceInterface); + return status; + } + + list_add_item(&domain->interfaces, interface); + return B_OK; +} + + +/*! + Removes the interface from its domain, and deletes it. + You need to hold the domain's lock when calling this function. +*/ +status_t +remove_interface_from_domain(net_interface *interface) +{ + net_domain_private *domain = (net_domain_private *)interface->domain; + + list_remove_item(&domain->interfaces, interface); + delete_interface((net_interface_private *)interface); + return B_OK; +} + + +status_t +register_domain(int family, const char *name, + struct net_protocol_module_info *module, + struct net_address_module_info *addressModule, + net_domain **_domain) +{ + TRACE(("register_domain(%d, %s)\n", family, name)); + BenaphoreLocker locker(sDomainLock); + + struct net_domain_private *domain = lookup_domain(family); + if (domain != NULL) + return B_NAME_IN_USE; + + domain = new (std::nothrow) net_domain_private; + if (domain == NULL) + return B_NO_MEMORY; + + status_t status = benaphore_init(&domain->lock, name); + if (status < B_OK) { + delete domain; + return status; + } + + domain->family = family; + domain->name = name; + domain->module = module; + domain->address_module = addressModule; + + list_init(&domain->interfaces); + + list_add_item(&sDomains, domain); + + *_domain = domain; + return B_OK; +} + + +status_t +unregister_domain(net_domain *_domain) +{ + TRACE(("unregister_domain(%p, %d, %s)\n", _domain, _domain->family, _domain->name)); + + net_domain_private *domain = (net_domain_private *)_domain; + BenaphoreLocker locker(sDomainLock); + + list_remove_item(&sDomains, domain); + + net_interface_private *interface = NULL; + while (true) { + interface = (net_interface_private *)list_remove_head_item(&domain->interfaces); + if (interface == NULL) + break; + + delete_interface(interface); + } + + benaphore_destroy(&domain->lock); + delete domain; + return B_OK; +} + + +status_t +init_domains() +{ + if (benaphore_init(&sDomainLock, "net domains") < B_OK) + return B_ERROR; + + list_init_etc(&sDomains, offsetof(struct net_domain_private, link)); + return B_OK; +} + + +status_t +uninit_domains() +{ + benaphore_destroy(&sDomainLock); + return B_OK; +} + diff --git a/src/add-ons/kernel/network/stack/domains.h b/src/add-ons/kernel/network/stack/domains.h new file mode 100644 index 0000000000..7750fcb53f --- /dev/null +++ b/src/add-ons/kernel/network/stack/domains.h @@ -0,0 +1,44 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ +#ifndef DOMAINS_H +#define DOMAINS_H + + +#include "routes.h" + +#include +#include +#include + + +struct net_domain_private : net_domain { + struct list_link link; + + benaphore lock; + + RouteList routes; + RouteInfoList route_infos; +}; + + +status_t init_domains(); +status_t uninit_domains(); + +uint32 count_domain_interfaces(); +status_t list_domain_interfaces(void *buffer, size_t size); +status_t add_interface_to_domain(net_domain *domain, struct ifreq& request); +status_t remove_interface_from_domain(net_interface *interface); + +net_domain *get_domain(int family); +status_t register_domain(int family, const char *name, + struct net_protocol_module_info *module, + struct net_address_module_info *addressModule, + net_domain **_domain); +status_t unregister_domain(net_domain *domain); + +#endif // DOMAINS_H diff --git a/src/add-ons/kernel/network/stack/interfaces.cpp b/src/add-ons/kernel/network/stack/interfaces.cpp new file mode 100644 index 0000000000..340f28f94e --- /dev/null +++ b/src/add-ons/kernel/network/stack/interfaces.cpp @@ -0,0 +1,610 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include "domains.h" +#include "interfaces.h" +#include "stack_private.h" + +#include + +#include +#include + +#include + +#include +#include +#include +#include + + +#define TRACE_INTERFACES +#ifdef TRACE_INTERFACES +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + + +static benaphore sInterfaceLock; +static list sInterfaces; +static uint32 sInterfaceIndex; +static uint32 sDeviceIndex; + + +static net_device_interface * +find_device_interface(const char *name) +{ + net_device_interface *interface = NULL; + + while (true) { + interface = (net_device_interface *)list_get_next_item(&sInterfaces, interface); + if (interface == NULL) + break; + + if (!strcmp(interface->name, name)) + return interface; + } + + return NULL; +} + + +static status_t +domain_receive_adapter(void *cookie, net_buffer *buffer) +{ + net_domain_private *domain = (net_domain_private *)cookie; + return domain->module->receive_data(buffer); +} + + +// #pragma mark - interfaces + + +/*! + Searches for a specific interface in a domain by name. + You need to have the domain's lock hold when calling this function. +*/ +struct net_interface_private * +find_interface(struct net_domain *domain, const char *name) +{ + net_interface_private *interface = NULL; + + while (true) { + interface = (net_interface_private *)list_get_next_item( + &domain->interfaces, interface); + if (interface == NULL) + break; + + if (!strcmp(interface->name, name)) + return interface; + } + + return NULL; +} + + +/*! + Searches for a specific interface in a domain by index. + You need to have the domain's lock hold when calling this function. +*/ +struct net_interface_private * +find_interface(struct net_domain *domain, uint32 index) +{ + net_interface_private *interface = NULL; + + while (true) { + interface = (net_interface_private *)list_get_next_item( + &domain->interfaces, interface); + if (interface == NULL) + break; + + if (interface->index == index) + return interface; + } + + return NULL; +} + + +status_t +create_interface(net_domain *domain, const char *name, const char *baseName, + net_device_interface *deviceInterface, net_interface_private **_interface) +{ + net_interface_private *interface = + new (std::nothrow) net_interface_private; + if (interface == NULL) + return B_NO_MEMORY; + + strlcpy(interface->name, name, IF_NAMESIZE); + strlcpy(interface->base_name, baseName, IF_NAMESIZE); + interface->domain = domain; + interface->device = deviceInterface->device; + + interface->address = NULL; + interface->destination = NULL; + interface->mask = NULL; + + interface->index = ++sInterfaceIndex; + interface->flags = deviceInterface->device->flags & ~IFF_UP; + interface->type = 0; + interface->mtu = deviceInterface->device->mtu; + interface->metric = 0; + interface->device_interface = deviceInterface; + + status_t status = get_domain_datalink_protocols(interface); + if (status < B_OK) { + delete interface; + return status; + } + + // Grab a reference to the networking stack, to make sure it won't be + // unloaded as long as an interface exists + module_info *module; + get_module(NET_STARTER_MODULE_NAME, &module); + + *_interface = interface; + return B_OK; +} + + +void +delete_interface(net_interface_private *interface) +{ + put_device_interface(interface->device_interface); + + free(interface->address); + free(interface->destination); + free(interface->mask); + + delete interface; + + // Release reference of the stack - at this point, our stack may be unloaded + // if no other interfaces or sockets are left + put_module(NET_STARTER_MODULE_NAME); +} + + +void +put_interface(struct net_interface_private *interface) +{ + // TODO: reference counting + // TODO: better locking scheme + benaphore_unlock(&((net_domain_private *)interface->domain)->lock); +} + + +struct net_interface_private * +get_interface(net_domain *_domain, const char *name) +{ + net_domain_private *domain = (net_domain_private *)_domain; + benaphore_lock(&domain->lock); + + net_interface_private *interface = NULL; + while (true) { + interface = (net_interface_private *)list_get_next_item( + &domain->interfaces, interface); + if (interface == NULL) + break; + + if (!strcmp(interface->name, name)) + return interface; + } + + benaphore_unlock(&domain->lock); + return NULL; +} + + +// #pragma mark - device interfaces + + +void +get_device_interface_address(net_device_interface *interface, sockaddr *_address) +{ + sockaddr_dl &address = *(sockaddr_dl *)_address; + + address.sdl_family = AF_LINK; + address.sdl_index = interface->device->index; + address.sdl_type = interface->device->type; + address.sdl_nlen = strlen(interface->name); + address.sdl_slen = 0; + memcpy(address.sdl_data, interface->name, address.sdl_nlen); + + address.sdl_alen = interface->device->address.length; + memcpy(LLADDR(&address), interface->device->address.data, address.sdl_alen); + + address.sdl_len = sizeof(sockaddr_dl) - sizeof(address.sdl_data) + + address.sdl_nlen + address.sdl_alen; +} + + +uint32 +count_device_interfaces() +{ + BenaphoreLocker locker(sInterfaceLock); + + net_device_interface *interface = NULL; + uint32 count = 0; + + while (true) { + interface = (net_device_interface *)list_get_next_item(&sInterfaces, + interface); + if (interface == NULL) + break; + + count++; + } + + return count; +} + + +/*! + Dumps a list of all interfaces into the supplied userland buffer. + If the interfaces don't fit into the buffer, an error (\c ENOBUFS) is + returned. +*/ +status_t +list_device_interfaces(void *buffer, size_t size) +{ + BenaphoreLocker locker(sInterfaceLock); + + net_device_interface *interface = NULL; + size_t spaceLeft = size; + + while (true) { + interface = (net_device_interface *)list_get_next_item(&sInterfaces, + interface); + if (interface == NULL) + break; + + ifreq request; + strlcpy(request.ifr_name, interface->name, IF_NAMESIZE); + get_device_interface_address(interface, &request.ifr_addr); + + size = IF_NAMESIZE + request.ifr_addr.sa_len; + if (spaceLeft < size) + return ENOBUFS; + + if (user_memcpy(buffer, &request, size) < B_OK) + return B_BAD_ADDRESS; + + buffer = (void *)((addr_t)buffer + size); + spaceLeft -= size; + } + + return B_OK; +} + + +/*! + Releases the reference for the interface. When all references are + released, the interface is removed. +*/ +void +put_device_interface(struct net_device_interface *interface) +{ + if (atomic_add(&interface->ref_count, -1) != 1) + return; + + // we need to remove this interface! + + { + BenaphoreLocker locker(sInterfaceLock); + list_remove_item(&sInterfaces, interface); + } + + interface->module->uninit_device(interface->device); + put_module(interface->module->info.name); +} + + +/*! + Finds an interface by the specified index and grabs a reference to it. +*/ +struct net_device_interface * +get_device_interface(uint32 index) +{ + BenaphoreLocker locker(sInterfaceLock); + net_device_interface *interface = NULL; + + while (true) { + interface = (net_device_interface *)list_get_next_item(&sInterfaces, interface); + if (interface == NULL) + break; + + if (interface->device->index == index) { + if (atomic_add(&interface->ref_count, 1) != 0) + return interface; + } + } + + return NULL; +} + + +/*! + Finds an interface by the specified name and grabs a reference to it. + If the interface does not yet exist, a new one is created. +*/ +struct net_device_interface * +get_device_interface(const char *name) +{ + BenaphoreLocker locker(sInterfaceLock); + + net_device_interface *interface = find_device_interface(name); + if (interface != NULL) { + if (atomic_add(&interface->ref_count, 1) != 0) + return interface; + + // try to recreate interface - it just got removed + } + + void *cookie = open_module_list("network/devices"); + if (cookie == NULL) + return NULL; + + while (true) { + char moduleName[B_FILE_NAME_LENGTH]; + size_t length = sizeof(moduleName); + if (read_next_module_name(cookie, moduleName, &length) != B_OK) + break; + + TRACE(("get_device_interface: ask \"%s\" for %s\n", moduleName, name)); + + net_device_module_info *module; + if (get_module(moduleName, (module_info **)&module) == B_OK) { + net_device *device; + status_t status = module->init_device(name, &device); + if (status == B_OK) { + // create new module interface for this + interface = new (std::nothrow) net_device_interface; + if (interface != NULL) { + interface->name = device->name; + interface->module = module; + interface->device = device; + interface->up_count = 0; + interface->ref_count = 1; + interface->deframe_func = NULL; + interface->deframe_ref_count = 0; + + device->index = ++sDeviceIndex; + device->module = module; + + list_add_item(&sInterfaces, interface); + return interface; + } else + module->uninit_device(device); + } + + put_module(moduleName); + } + } + + return NULL; +} + + +// #pragma mark - devices + + +/*! + Unregisters a previously registered deframer function. + This function is part of the net_manager_module_info API. +*/ +status_t +unregister_device_deframer(net_device *device) +{ + BenaphoreLocker locker(sInterfaceLock); + + // find device interface for this device + net_device_interface *interface = find_device_interface(device->name); + if (interface == NULL) + return ENODEV; + + if (--interface->deframe_ref_count == 0) + interface->deframe_func = NULL; + + return B_OK; +} + + +/*! + Registers the deframer function for the specified \a device. + Note, however, that right now, you can only register one single + deframer function per device. + + If the need arises, we might want to lift that limitation at a + later time (which would require a slight API change, though). + + This function is part of the net_manager_module_info API. +*/ +status_t +register_device_deframer(net_device *device, net_deframe_func deframeFunc) +{ + BenaphoreLocker locker(sInterfaceLock); + + // find device interface for this device + net_device_interface *interface = find_device_interface(device->name); + if (interface == NULL) + return ENODEV; + + if (interface->deframe_func != NULL && interface->deframe_func != deframeFunc) + return B_ERROR; + + interface->deframe_func = deframeFunc; + interface->deframe_ref_count++; + return B_OK; +} + + +status_t +register_domain_device_handler(struct net_device *device, int32 type, + struct net_domain *_domain) +{ + net_domain_private *domain = (net_domain_private *)_domain; + if (domain->module == NULL || domain->module->receive_data == NULL) + return B_BAD_VALUE; + + return register_device_handler(device, type, &domain_receive_adapter, domain); +} + + +status_t +register_device_handler(struct net_device *device, int32 type, + net_receive_func receiveFunc, void *cookie) +{ + BenaphoreLocker locker(sInterfaceLock); + + // find device interface for this device + net_device_interface *interface = find_device_interface(device->name); + if (interface == NULL) + return ENODEV; + + // see if such a handler already for this device + + DeviceHandlerList::Iterator iterator = interface->receive_funcs.GetIterator(); + while (iterator.HasNext()) { + net_device_handler *handler = iterator.Next(); + + if (handler->type == type) + return B_ERROR; + } + + // Add new handler + + net_device_handler *handler = new (std::nothrow) net_device_handler; + if (handler == NULL) + return B_NO_MEMORY; + + handler->func = receiveFunc; + handler->type = type; + handler->cookie = cookie; + interface->receive_funcs.Add(handler); + return B_OK; +} + + +status_t +unregister_device_handler(struct net_device *device, int32 type) +{ + BenaphoreLocker locker(sInterfaceLock); + + // find device interface for this device + net_device_interface *interface = find_device_interface(device->name); + if (interface == NULL) + return ENODEV; + + // search for the handler + + DeviceHandlerList::Iterator iterator = interface->receive_funcs.GetIterator(); + while (iterator.HasNext()) { + net_device_handler *handler = iterator.Next(); + + if (handler->type == type) { + // found it + iterator.Remove(); + delete handler; + return B_OK; + } + } + + return B_BAD_VALUE; +} + + +status_t +register_device_monitor(struct net_device *device, + net_receive_func receiveFunc, void *cookie) +{ + BenaphoreLocker locker(sInterfaceLock); + + // find device interface for this device + net_device_interface *interface = find_device_interface(device->name); + if (interface == NULL) + return ENODEV; + + // Add new monitor + + net_device_monitor *monitor = new (std::nothrow) net_device_monitor; + if (monitor == NULL) + return B_NO_MEMORY; + + monitor->func = receiveFunc; + monitor->cookie = cookie; + interface->monitor_funcs.Add(monitor); + return B_OK; +} + + +status_t +unregister_device_monitor(struct net_device *device, + net_receive_func receiveFunc, void *cookie) +{ + BenaphoreLocker locker(sInterfaceLock); + + // find device interface for this device + net_device_interface *interface = find_device_interface(device->name); + if (interface == NULL) + return ENODEV; + + // search for the monitor + + DeviceMonitorList::Iterator iterator = interface->monitor_funcs.GetIterator(); + while (iterator.HasNext()) { + net_device_monitor *monitor = iterator.Next(); + + if (monitor->cookie == cookie && monitor->func == receiveFunc) { + // found it + iterator.Remove(); + delete monitor; + return B_OK; + } + } + + return B_BAD_VALUE; +} + + +/*! + This function is called by device modules once their device got + physically removed, ie. a USB networking card is unplugged. + It is part of the net_manager_module_info API. +*/ +status_t +device_removed(net_device *device) +{ + BenaphoreLocker locker(sInterfaceLock); + // TODO: all what this function should do is to clear the IFF_UP flag of the interfaces. + return B_OK; +} + + +// #pragma mark - + + +status_t +init_interfaces() +{ + if (benaphore_init(&sInterfaceLock, "net interfaces") < B_OK) + return B_ERROR; + + list_init(&sInterfaces); + return B_OK; +} + + +status_t +uninit_interfaces() +{ + benaphore_destroy(&sInterfaceLock); + return B_OK; +} + diff --git a/src/add-ons/kernel/network/stack/interfaces.h b/src/add-ons/kernel/network/stack/interfaces.h new file mode 100644 index 0000000000..6275901519 --- /dev/null +++ b/src/add-ons/kernel/network/stack/interfaces.h @@ -0,0 +1,93 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ +#ifndef INTERFACES_H +#define INTERFACES_H + + +#include +#include + +#include + + +struct net_device_handler : public DoublyLinkedListLinkImpl { + net_receive_func func; + int32 type; + void *cookie; +}; + +struct net_device_monitor : public DoublyLinkedListLinkImpl { + net_receive_func func; + void *cookie; +}; + +typedef DoublyLinkedList DeviceHandlerList; +typedef DoublyLinkedList DeviceMonitorList; + +struct net_device_interface { + struct list_link link; + const char *name; + struct net_device_module_info *module; + struct net_device *device; + uint32 up_count; + // a device can be brought up by more than one interface + int32 ref_count; + + net_deframe_func deframe_func; + int32 deframe_ref_count; + + DeviceMonitorList monitor_funcs; + DeviceHandlerList receive_funcs; +}; + +struct net_interface_private : net_interface { + char base_name[IF_NAMESIZE]; + net_device_interface *device_interface; +}; + + +status_t init_interfaces(); +status_t uninit_interfaces(); + +// interfaces +struct net_interface_private *find_interface(struct net_domain *domain, + const char *name); +struct net_interface_private *find_interface(struct net_domain *domain, + uint32 index); +void put_interface(struct net_interface_private *interface); +struct net_interface_private *get_interface(net_domain *domain, + const char *name); +status_t create_interface(net_domain *domain, const char *name, + const char *baseName, net_device_interface *deviceInterface, + struct net_interface_private **_interface); +void delete_interface(net_interface_private *interface); + +// device interfaces +void get_device_interface_address(net_device_interface *interface, + sockaddr *address); +uint32 count_device_interfaces(); +status_t list_device_interfaces(void *buffer, size_t size); +void put_device_interface(struct net_device_interface *interface); +struct net_device_interface *get_device_interface(uint32 index); +struct net_device_interface *get_device_interface(const char *name); + +// devices +status_t unregister_device_deframer(net_device *device); +status_t register_device_deframer(net_device *device, net_deframe_func deframeFunc); +status_t register_domain_device_handler(struct net_device *device, int32 type, + struct net_domain *domain); +status_t register_device_handler(struct net_device *device, int32 type, + net_receive_func receiveFunc, void *cookie); +status_t unregister_device_handler(struct net_device *device, int32 type); +status_t register_device_monitor(struct net_device *device, + net_receive_func receiveFunc, void *cookie); +status_t unregister_device_monitor(struct net_device *device, + net_receive_func receiveFunc, void *cookie); +status_t device_removed(net_device *device); + +#endif // INTERFACES_H diff --git a/src/add-ons/kernel/network/stack/link.cpp b/src/add-ons/kernel/network/stack/link.cpp new file mode 100644 index 0000000000..7f127c8463 --- /dev/null +++ b/src/add-ons/kernel/network/stack/link.cpp @@ -0,0 +1,442 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include "datalink.h" +#include "domains.h" +#include "interfaces.h" +#include "link.h" +#include "stack_private.h" +#include "utility.h" + +#include + +#include + +#include +#include +#include +#include +#include + + +struct link_protocol : net_protocol { + net_fifo fifo; + char registered_interface[IF_NAMESIZE]; + bool registered_monitor; +}; + + +struct net_domain *sDomain; + + +static status_t +link_monitor_data(void *cookie, net_buffer *packet) +{ + link_protocol *protocol = (link_protocol *)cookie; + + // we need to make a clone for that buffer and pass it to the socket + net_buffer *buffer = gNetBufferModule.clone(packet, false); + if (buffer == NULL) + return B_NO_MEMORY; + + return fifo_enqueue_buffer(&protocol->fifo, buffer); +} + + +// #pragma mark - + + +net_protocol * +link_init_protocol(net_socket *socket) +{ + link_protocol *protocol = new (std::nothrow) link_protocol; + if (protocol == NULL) + return NULL; + + if (init_fifo(&protocol->fifo, "packet monitor socket", 65536) < B_OK) { + delete protocol; + return NULL; + } + + protocol->registered_monitor = false; + return protocol; +} + + +status_t +link_uninit_protocol(net_protocol *_protocol) +{ + link_protocol *protocol = (link_protocol *)_protocol; + + if (protocol->registered_monitor) { + net_device_interface *interface = get_device_interface(protocol->registered_interface); + if (interface != NULL) { + unregister_device_monitor(interface->device, link_monitor_data, protocol); + put_device_interface(interface); + } + } + + uninit_fifo(&protocol->fifo); + delete protocol; + return B_OK; +} + + +status_t +link_open(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +link_close(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +link_free(net_protocol *protocol) +{ + return B_OK; +} + + +status_t +link_connect(net_protocol *protocol, const struct sockaddr *address) +{ + return EOPNOTSUPP; +} + + +status_t +link_accept(net_protocol *protocol, struct net_socket **_acceptedSocket) +{ + return EOPNOTSUPP; +} + + +status_t +link_control(net_protocol *_protocol, int level, int option, void *value, + size_t *_length) +{ + link_protocol *protocol = (link_protocol *)_protocol; + + switch (option) { + case SIOCGIFINDEX: + { + // get index of interface + struct ifreq request; + if (user_memcpy(&request, value, IF_NAMESIZE) < B_OK) + return B_BAD_ADDRESS; + + net_device_interface *interface = get_device_interface(request.ifr_name); + if (interface != NULL) { + request.ifr_index = interface->device->index; + put_device_interface(interface); + } else + request.ifr_index = 0; + + return user_memcpy(value, &request, sizeof(struct ifreq)); + } + case SIOCGIFNAME: + { + // get name of interface via index + struct ifreq request; + if (user_memcpy(&request, value, sizeof(struct ifreq)) < B_OK) + return B_BAD_ADDRESS; + + net_device_interface *interface = get_device_interface(request.ifr_index); + if (interface != NULL) { + strlcpy(request.ifr_name, interface->name, IF_NAMESIZE); + put_device_interface(interface); + } else + return ENODEV; + + return user_memcpy(value, &request, sizeof(struct ifreq)); + } + + case SIOCGIFCOUNT: + { + // count number of interfaces + struct ifconf config; + config.ifc_value = count_device_interfaces(); + + return user_memcpy(value, &config, sizeof(struct ifconf)); + } + + case SIOCGIFCONF: + { + // count number of interfaces + struct ifconf config; + if (user_memcpy(&config, value, sizeof(struct ifconf)) < B_OK) + return B_BAD_ADDRESS; + + return list_device_interfaces(config.ifc_buf, config.ifc_len); + } + + case SIOCGIFADDR: + { + // get address of interface + struct ifreq request; + if (user_memcpy(&request, value, IF_NAMESIZE) < B_OK) + return B_BAD_ADDRESS; + + net_device_interface *interface = get_device_interface(request.ifr_name); + if (interface != NULL) { + get_device_interface_address(interface, &request.ifr_addr); + put_device_interface(interface); + } else + return ENODEV; + + return user_memcpy(&((struct ifreq *)value)->ifr_addr, + &request.ifr_addr, request.ifr_addr.sa_len); + } + + case SIOCSPACKETCAP: + { + // start packet monitoring + + if (protocol->registered_monitor) + return B_BUSY; + + struct ifreq request; + if (user_memcpy(&request, value, IF_NAMESIZE) < B_OK) + return B_BAD_ADDRESS; + + net_device_interface *interface = get_device_interface(request.ifr_name); + status_t status; + if (interface != NULL) { + status = register_device_monitor(interface->device, + link_monitor_data, protocol); + if (status == B_OK) { + // we're now registered + strlcpy(protocol->registered_interface, request.ifr_name, IF_NAMESIZE); + protocol->registered_monitor = true; + } + put_device_interface(interface); + } else + status = ENODEV; + + return status; + } + + case SIOCCPACKETCAP: + { + // stop packet monitoring + + if (!protocol->registered_monitor) + return B_BAD_VALUE; + + struct ifreq request; + if (user_memcpy(&request, value, IF_NAMESIZE) < B_OK) + return B_BAD_ADDRESS; + + net_device_interface *interface = get_device_interface(request.ifr_name); + status_t status; + if (interface != NULL) { + status = unregister_device_monitor(interface->device, + link_monitor_data, protocol); + if (status == B_OK) { + // we're now no longer registered + protocol->registered_monitor = false; + } + put_device_interface(interface); + } else + status = ENODEV; + + return status; + } + } + + return datalink_control(sDomain, option, value, _length); +} + + +status_t +link_bind(net_protocol *protocol, struct sockaddr *address) +{ + // TODO: bind to a specific interface and ethernet type + return B_ERROR; +} + + +status_t +link_unbind(net_protocol *protocol, struct sockaddr *address) +{ + return B_ERROR; +} + + +status_t +link_listen(net_protocol *protocol, int count) +{ + return EOPNOTSUPP; +} + + +status_t +link_shutdown(net_protocol *protocol, int direction) +{ + return EOPNOTSUPP; +} + + +status_t +link_send_data(net_protocol *protocol, net_buffer *buffer) +{ + return B_NOT_ALLOWED; +} + + +status_t +link_send_routed_data(net_protocol *protocol, struct net_route *route, + net_buffer *buffer) +{ + return B_NOT_ALLOWED; +} + + +ssize_t +link_send_avail(net_protocol *protocol) +{ + return B_ERROR; +} + + +status_t +link_read_data(net_protocol *_protocol, size_t numBytes, uint32 flags, + net_buffer **_buffer) +{ + link_protocol *protocol = (link_protocol *)_protocol; + + dprintf("link_read is waiting for data...\n"); + + net_buffer *buffer; + status_t status = fifo_dequeue_buffer(&protocol->fifo, + flags, protocol->socket->receive.timeout, &buffer); + if (status < B_OK) + return status; + + if (numBytes < buffer->size) { + // discard any data behind the amount requested + gNetBufferModule.trim(buffer, numBytes); + } + + *_buffer = buffer; + return B_OK; +} + + +ssize_t +link_read_avail(net_protocol *_protocol) +{ + link_protocol *protocol = (link_protocol *)_protocol; + return protocol->fifo.current_bytes; +} + + +struct net_domain * +link_get_domain(net_protocol *protocol) +{ + return sDomain; +} + + +size_t +link_get_mtu(net_protocol *protocol, const struct sockaddr *address) +{ + // TODO: for now + return 0; +} + + +status_t +link_receive_data(net_buffer *buffer) +{ + return B_ERROR; +} + + +status_t +link_error(uint32 code, net_buffer *data) +{ + return B_ERROR; +} + + +status_t +link_error_reply(net_protocol *protocol, net_buffer *causedError, uint32 code, + void *errorData) +{ + return B_ERROR; +} + + +static status_t +link_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + return register_domain(AF_LINK, "link", NULL, NULL, &sDomain); + + case B_MODULE_UNINIT: + unregister_domain(sDomain); + return B_OK; + + default: + return B_ERROR; + } +} + + +// #pragma mark - + + +void +link_init() +{ + register_domain_protocols(AF_LINK, SOCK_DGRAM, 0, "network/stack/link/v1", NULL); + + register_domain_datalink_protocols(AF_LINK, IFT_ETHER, + "network/datalink_protocols/ethernet_frame/v1", + NULL); +} + + +net_protocol_module_info gLinkModule = { + { + "network/stack/link/v1", + 0, + link_std_ops + }, + link_init_protocol, + link_uninit_protocol, + link_open, + link_close, + link_free, + link_connect, + link_accept, + link_control, + link_bind, + link_unbind, + link_listen, + link_shutdown, + link_send_data, + link_send_routed_data, + link_send_avail, + link_read_data, + link_read_avail, + link_get_domain, + link_get_mtu, + link_receive_data, + link_error, + link_error_reply, +}; diff --git a/src/add-ons/kernel/network/stack/link.h b/src/add-ons/kernel/network/stack/link.h new file mode 100644 index 0000000000..cbd24000f0 --- /dev/null +++ b/src/add-ons/kernel/network/stack/link.h @@ -0,0 +1,19 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ +#ifndef LINK_H +#define LINK_H + + +#include + + +extern net_protocol_module_info gLinkModule; + +void link_init(); + +#endif // LINK_H diff --git a/src/add-ons/kernel/network/stack/net_buffer.cpp b/src/add-ons/kernel/network/stack/net_buffer.cpp new file mode 100644 index 0000000000..71de2e0b1b --- /dev/null +++ b/src/add-ons/kernel/network/stack/net_buffer.cpp @@ -0,0 +1,902 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include "utility.h" + +#include +#include + +#include +#include + +#include +#include +#include + + +#define TRACE_BUFFER +#ifdef TRACE_BUFFER +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + +#define BUFFER_SIZE 2048 + +struct data_node { + struct list_link link; + struct data_header *header; + size_t offset; // the net_buffer-wide offset of this node + uint8 *start; // points to the start of the data + size_t used; // defines how much memory is used by this node + size_t header_space; + size_t tail_space; +}; + +struct data_header { + int32 ref_count; + addr_t physical_address; + size_t size; + uint8 *data_end; + size_t data_space; + data_node *first_node; +}; + +struct net_buffer_private : net_buffer { + struct list buffers; + data_node first_node; +}; + + +static status_t append_data(net_buffer *buffer, const void *data, size_t size); + + +static data_header * +create_data_header(size_t size, size_t headerSpace) +{ + // TODO: don't use malloc! + data_header *header = (data_header *)malloc(size); + if (header == NULL) + return NULL; + + header->ref_count = 1; + header->physical_address = 0; + // TODO: initialize this correctly + header->size = size; + header->data_space = headerSpace; + header->data_end = (uint8 *)header + sizeof(struct data_header); + header->first_node = NULL; + + TRACE((" create new data header %p\n", header)); + return header; +} + + +static void +release_data_header(data_header *header) +{ + if (atomic_add(&header->ref_count, -1) != 1) + return; + + TRACE((" free header %p\n", header)); + free(header); +} + + +inline void +acquire_data_header(data_header *header) +{ + atomic_add(&header->ref_count, 1); +} + + +static void +free_data_header_space(data_header *header, uint8 *data, size_t size) +{ + if (header->data_end != data + size) { + // this wasn't the last allocation, unfortunately, there is nothing + // to do for us, then + // TODO: if the need arises, a simple free list could do wonder + // TODO: remove_data_node() currently calls this function no matter + // where the node had been placed - this would need to be changed + // then, too. + return; + } + + header->data_end -= size; + header->data_space += size; +} + + +static uint8 * +alloc_data_header_space(data_header *header, size_t size) +{ + if (header->data_space < size) + return NULL; + + uint8 *data = header->data_end; + header->data_end += size; + header->data_space -= size; + + if (header->first_node != NULL) + header->first_node->header_space -= size; +#if 0 + else + dprintf("add data to a header without first node - could overwrite something!\n"); +#endif + + return data; +} + + +static void +init_data_node(data_node *node, data_header *header, size_t headerSpace) +{ + node->header = header; + node->offset = 0; + node->start = (uint8 *)header + sizeof(data_header) + headerSpace; + node->used = 0; + node->header_space = headerSpace; + node->tail_space = header->size - headerSpace - sizeof(data_header); +} + + +static data_node * +add_data_node(data_header *header) +{ + data_node *node = (data_node *)alloc_data_header_space(header, sizeof(data_node)); + if (node == NULL) + return NULL; + + TRACE((" add data node %p to header %p\n", node, header)); + acquire_data_header(header); + memset(node, 0, sizeof(struct data_node)); + return node; +} + + +void +remove_data_node(data_node *node) +{ + data_header *header = node->header; + + TRACE((" remove data node %p from header %p\n", node, header)); + free_data_header_space(header, (uint8 *)node, sizeof(data_node)); + if (header->first_node == node) + header->first_node = NULL; + + release_data_header(node->header); +} + + +// #pragma mark - + + +static net_buffer * +create_buffer(size_t headerSpace) +{ + net_buffer_private *buffer = (net_buffer_private *)malloc(sizeof(struct net_buffer_private)); + if (buffer == NULL) + return NULL; + + TRACE(("create buffer %p\n", buffer)); + + data_header *header = create_data_header(BUFFER_SIZE, headerSpace); + if (header == NULL) { + free(buffer); + return NULL; + } + + init_data_node(&buffer->first_node, header, headerSpace); + header->first_node = &buffer->first_node; + + list_init(&buffer->buffers); + list_add_item(&buffer->buffers, &buffer->first_node); + + buffer->source.ss_len = 0; + buffer->destination.ss_len = 0; + buffer->interface = NULL; + buffer->flags = 0; + buffer->size = 0; + + return buffer; +} + + +static void +free_buffer(net_buffer *_buffer) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + + TRACE(("free buffer %p\n", buffer)); + + data_node *node; + while ((node = (data_node *)list_remove_head_item(&buffer->buffers)) != NULL) { + remove_data_node(node); + } + + free(buffer); +} + + +/*! Creates a duplicate of the \a buffer. The new buffer does not share internal + storage; they are completely independent from each other. +*/ +static net_buffer * +duplicate_buffer(net_buffer *_buffer) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + + net_buffer *duplicate = create_buffer(buffer->first_node.header_space); + if (duplicate == NULL) + return NULL; + + // copy the data from the source buffer + + data_node *node = (data_node *)list_get_first_item(&buffer->buffers); + while (true) { + if (append_data(duplicate, node->start, node->used) < B_OK) { + free_buffer(duplicate); + return NULL; + } + + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) + break; + } + + // copy meta data from source buffer + + memcpy(&duplicate->source, &buffer->source, buffer->source.ss_len); + memcpy(&duplicate->destination, &buffer->destination, buffer->destination.ss_len); + + duplicate->flags = buffer->flags; + duplicate->interface = buffer->interface; + duplicate->size = buffer->size; + duplicate->protocol = buffer->protocol; + + return duplicate; +} + + +/*! Clones the buffer by grabbing another reference to the underlying data. + If that data changes, it will be changed in the clone as well. + + If \a shareFreeSpace is \c true, the cloned buffer may claim the free + space in the original buffer as the original buffer can still do. If you + are using this, it's your responsibility that only one of the buffers + will do this. +*/ +static net_buffer * +clone_buffer(net_buffer *_buffer, bool shareFreeSpace) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + + net_buffer_private *clone = (net_buffer_private *)malloc(sizeof(struct net_buffer_private)); + if (clone == NULL) + return NULL; + + data_node *node = &clone->first_node; + data_node *sourceNode = (data_node *)list_get_first_item(&buffer->buffers); + if (sourceNode == NULL) { + free(clone); + return NULL; + } + + list_init(&clone->buffers); + + // grab reference to this buffer - all additional nodes will get + // theirs in add_data_node() + atomic_add(&sourceNode->header->ref_count, 1); + + while (sourceNode != NULL) { + node->header = sourceNode->header; + node->start = sourceNode->start; + node->used = sourceNode->used; + node->offset = sourceNode->offset; + + if (shareFreeSpace) { + // both buffers could claim the free space - note that this option + // has to be used carefully + node->header_space = sourceNode->header_space; + node->tail_space = sourceNode->tail_space; + } else { + // the free space stays with the original buffer + node->header_space = 0; + node->tail_space = 0; + } + + // add node to clone's list of buffers + list_add_item(&clone->buffers, node); + + sourceNode = (data_node *)list_get_next_item(&buffer->buffers, sourceNode); + if (sourceNode == NULL) + break; + + node = add_data_node(sourceNode->header); + if (node == NULL) { + // There was not enough space left for another node in this buffer + // TODO: handle this case! + panic("clone buffer hits size limit... (fix me)"); + free(clone); + return NULL; + } + } + + // copy meta data from source buffer + + memcpy(&clone->source, &buffer->source, buffer->source.ss_len); + memcpy(&clone->destination, &buffer->destination, buffer->destination.ss_len); + + clone->flags = buffer->flags; + clone->interface = buffer->interface; + clone->size = buffer->size; + clone->protocol = buffer->protocol; + + return clone; +} + + +/*! + Merges the second buffer with the first. If \a after is \c true, the + second buffer's contents will be appended to the first ones, else they + will be prepended. + The second buffer will be freed if this function succeeds. +*/ +static status_t +merge_buffer(net_buffer *_buffer, net_buffer *_with, bool after) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + net_buffer_private *with = (net_buffer_private *)_with; + if (with == NULL) + return B_BAD_VALUE; + + TRACE(("merge buffer %p with %p (%s)\n", buffer, with, after ? "after" : "before")); + // TODO: this is currently very simplistic, I really need to finish the + // harder part of this implementation (data_node management per header) + + if (!after) { + // change offset of all nodes already in the buffer + data_node *node = NULL; + while (true) { + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) + break; + + node->offset += with->size; + } + } + + data_node *last = NULL; + + while (true) { + data_node *node = (data_node *)list_get_next_item(&with->buffers, last); + if (node == NULL) + break; + + if ((uint8 *)node > (uint8 *)node->header + && (uint8 *)node < (uint8 *)node->header + node->header->size) { + // The node is already in the buffer, we can just move it + // over to the new owner + list_remove_item(&with->buffers, node); + } else { + // we need a new place for this node + data_node *newNode = add_data_node(node->header); + if (newNode == NULL) { +// TODO: this can't work right now as add_data_node() also grabs a reference +// to the header - but in this case, we would need two references, one +// for the data, one for the node, and there is no mechanism for this. +#if 0 + // try again on the buffers own header + newNode = add_data_node(buffer->first_node.header); + if (newNode == NULL) +#endif +// TODO: try to revert buffers to their initial state!! + return ENOBUFS; + } + + last = node; + *newNode = *node; + node = newNode; + // the old node will get freed with its buffer + } + + if (after) { + list_add_item(&buffer->buffers, node); + node->offset = buffer->size; + } else + list_add_link_to_head(&buffer->buffers, node); + + buffer->size += node->used; + } + + // the data has been merged completely at this point + free_buffer(with); + return B_OK; +} + + +/*! Writes into existing allocated memory. + \return B_BAD_VALUE if you write outside of the buffers current + bounds. +*/ +static status_t +write_data(net_buffer *_buffer, size_t offset, const void *data, size_t size) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + + if (offset + size > buffer->size) + return B_BAD_VALUE; + if (size == 0) + return B_OK; + + // find first node to write into + + data_node *node = (data_node *)list_get_first_item(&buffer->buffers); + while (node->offset + node->used < offset) { + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) + return B_BAD_VALUE; + } + + offset -= node->offset; + + while (true) { + size_t written = min_c(size, node->used - offset); + memcpy(node->start + offset, data, written); + + size -= written; + if (size == 0) + break; + + offset = 0; + + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) + return B_BAD_VALUE; + } + + return B_OK; +} + + +static status_t +read_data(net_buffer *_buffer, size_t offset, void *data, size_t size) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + + if (offset + size > buffer->size) + return B_BAD_VALUE; + if (size == 0) + return B_OK; + + // find first node to read from + + data_node *node = (data_node *)list_get_first_item(&buffer->buffers); + while (node->offset + node->used < offset) { + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) + return B_BAD_VALUE; + } + + offset -= node->offset; + + while (true) { + size_t bytesRead = min_c(size, node->used - offset); + memcpy(data, node->start + offset, bytesRead); + + size -= bytesRead; + if (size == 0) + break; + + offset = 0; + + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) + return B_BAD_VALUE; + } + + return B_OK; +} + + +static status_t +prepend_size(net_buffer *_buffer, size_t size, void **_contiguousBuffer) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + data_node *node = (data_node *)list_get_first_item(&buffer->buffers); + + if (node->header_space < size) { + // we need to prepend a new buffer + + // TODO: implement me! + panic("prepending buffer not implemented\n"); + + if (_contiguousBuffer) + *_contiguousBuffer = NULL; + + return B_ERROR; + } + + // the data fits into this buffer + node->header_space -= size; + node->start -= size; + node->used += size; + + if (_contiguousBuffer) + *_contiguousBuffer = node->start; + + buffer->size += size; + return B_OK; +} + + +static status_t +prepend_data(net_buffer *buffer, const void *data, size_t size) +{ + void *contiguousBuffer; + status_t status = prepend_size(buffer, size, &contiguousBuffer); + if (status < B_OK) + return status; + + if (contiguousBuffer) + memcpy(contiguousBuffer, data, size); + else + write_data(buffer, 0, data, size); + + return B_OK; +} + + +static status_t +append_size(net_buffer *_buffer, size_t size, void **_contiguousBuffer) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + data_node *node = (data_node *)list_get_last_item(&buffer->buffers); + + if (node->tail_space < size) { + // we need to append a new buffer + + // compute how many buffers we're going to need + // TODO: this doesn't leave any tail space, if that should be desired... + uint32 tailSpace = node->tail_space; + uint32 minimalHeaderSpace = sizeof(data_header) + 2 * sizeof(data_node); + uint32 sizeNeeded = size - tailSpace; + uint32 count = (sizeNeeded + BUFFER_SIZE - minimalHeaderSpace - 1) + / (BUFFER_SIZE - minimalHeaderSpace); + uint32 averageHeaderSpace = BUFFER_SIZE - sizeNeeded / count - sizeof(data_header); + uint32 averageSize = BUFFER_SIZE - sizeof(data_header) - averageHeaderSpace; + + // allocate space left in the node + node->tail_space -= tailSpace; + node->used += tailSpace; + buffer->size += tailSpace; + + // allocate all buffers + + for (uint32 i = 0; i < count; i++) { + data_header *header = create_data_header(BUFFER_SIZE, averageHeaderSpace); + if (header == NULL) { + // TODO: free up headers we already allocated! + return B_NO_MEMORY; + } + + node = (data_node *)alloc_data_header_space(header, sizeof(data_node)); + // this can't fail as we made sure there will be enough header space + + init_data_node(node, header, averageHeaderSpace); + node->header_space = header->data_space; + node->tail_space -= averageSize; + node->used = averageSize; + node->offset = buffer->size; + buffer->size += averageSize; + + list_add_item(&buffer->buffers, node); + } + + if (_contiguousBuffer) + *_contiguousBuffer = NULL; + + return B_OK; + } + + // the data fits into this buffer + node->tail_space -= size; + + if (_contiguousBuffer) + *_contiguousBuffer = node->start + node->used; + + node->used += size; + buffer->size += size; + return B_OK; +} + + +static status_t +append_data(net_buffer *buffer, const void *data, size_t size) +{ + size_t used = buffer->size; + + void *contiguousBuffer; + status_t status = append_size(buffer, size, &contiguousBuffer); + if (status < B_OK) + return status; + + if (contiguousBuffer) + memcpy(contiguousBuffer, data, size); + else + write_data(buffer, used, data, size); + + return B_OK; +} + + +/*! + Removes bytes from the beginning of the buffer. +*/ +static status_t +remove_header(net_buffer *_buffer, size_t bytes) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + + if (bytes > buffer->size) + return B_BAD_VALUE; + + size_t left = bytes; + + data_node *node = (data_node *)list_get_first_item(&buffer->buffers); + while (node != NULL && left > 0) { + size_t cut = min_c(node->used, left); + node->offset = 0; + node->start += cut; + node->header_space += cut; + node->used -= cut; + + left -= cut; + + node = (data_node *)list_get_next_item(&buffer->buffers, node); + } + + // adjust offset of following nodes + while (node != NULL) { + node->offset -= bytes; + node = (data_node *)list_get_next_item(&buffer->buffers, node); + } + + buffer->size -= bytes; + return B_OK; +} + + +/*! + Trims the buffer to the specified \a newSize by removing space from + the end of the buffer. +*/ +static status_t +trim_data(net_buffer *_buffer, size_t newSize) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + + if (newSize > buffer->size) + return B_BAD_VALUE; + if (newSize == buffer->size) + return B_OK; + + data_node *node = (data_node *)list_get_first_item(&buffer->buffers); + while (node->offset + node->used < newSize) { + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) { + // trim size greater than buffer size + return B_BAD_VALUE; + } + } + + int32 diff = node->used + node->offset - newSize; + node->tail_space += diff; + node->used -= diff; + + if (node->used > 0) + node = (data_node *)list_get_next_item(&buffer->buffers, node); + + while (node != NULL) { + data_node *next = (data_node *)list_get_next_item(&buffer->buffers, node); + list_remove_item(&buffer->buffers, node); + remove_data_node(node); + + node = next; + } + + buffer->size = newSize; + return B_OK; +} + + +/*! + Tries to directly access the requested space in the buffer. + If the space is contiguous, the function will succeed and place a pointer + to that space into \a _contiguousBuffer. + + \return B_BAD_VALUE if the offset is outside of the buffer's bounds. + \return B_ERROR in case the buffer is not contiguous at that location. +*/ +static status_t +direct_access(net_buffer *_buffer, uint32 offset, size_t size, + void **_contiguousBuffer) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + + if (offset + size > buffer->size) + return B_BAD_VALUE; + + // find node to access + + data_node *node = (data_node *)list_get_first_item(&buffer->buffers); + while (node->offset + node->used < offset) { + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) + return B_BAD_VALUE; + } + + offset -= node->offset; + + if (size > node->used - offset) + return B_ERROR; + + *_contiguousBuffer = node->start + offset; + return B_OK; +} + + +static int32 +checksum_data(net_buffer *_buffer, uint32 offset, size_t size, bool finalize) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + + if (offset + size > buffer->size || size == 0) + return B_BAD_VALUE; + + // find first node to read from + + data_node *node = (data_node *)list_get_first_item(&buffer->buffers); + while (node->offset + node->used < offset) { + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) + return B_ERROR; + } + + offset -= node->offset; + + // Since the maximum buffer size is 65536 bytes, it's impossible + // to overlap 32 bit - we don't need to handle this overlap in + // the loop, we can safely do it afterwards + uint32 sum = 0; + + while (true) { + size_t bytes = min_c(size, node->used - offset); + if ((offset + node->offset) & 1) { + // if we're at an uneven offset, we have to swap the checksum + sum += __swap_int16(compute_checksum(node->start + offset, bytes)); + } else + sum += compute_checksum(node->start + offset, bytes); + + size -= bytes; + if (size == 0) + break; + + offset = 0; + + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) + return B_ERROR; + } + + while (sum >> 16) { + sum = (sum & 0xffff) + (sum >> 16); + } + + if (!finalize) + return (uint16)sum; + + return (uint16)~sum; +} + + +static uint32 +get_iovecs(net_buffer *_buffer, struct iovec *iovecs, uint32 vecCount) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + data_node *node = (data_node *)list_get_first_item(&buffer->buffers); + uint32 count = 0; + + while (count < vecCount) { + iovecs[count].iov_base = node->start; + iovecs[count].iov_len = node->used; + count++; + + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) + break; + } + + return count; +} + + +static uint32 +count_iovecs(net_buffer *_buffer) +{ + net_buffer_private *buffer = (net_buffer_private *)_buffer; + data_node *node = (data_node *)list_get_first_item(&buffer->buffers); + uint32 count = 0; + + while (true) { + count++; + + node = (data_node *)list_get_next_item(&buffer->buffers, node); + if (node == NULL) + break; + } + + return count; +} + + +static status_t +std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + case B_MODULE_UNINIT: + return B_OK; + + default: + return B_ERROR; + } +} + + +net_buffer_module_info gNetBufferModule = { + { + NET_BUFFER_MODULE_NAME, + 0, + std_ops + }, + create_buffer, + free_buffer, + + duplicate_buffer, + clone_buffer, + NULL, // split + merge_buffer, + + prepend_size, + prepend_data, + append_size, + append_data, + NULL, // insert + NULL, // remove + remove_header, + NULL, // remove_trailer + trim_data, + + NULL, // associate_data + + direct_access, + read_data, + write_data, + + checksum_data, + + NULL, // get_memory_map + get_iovecs, + count_iovecs, + + NULL, // dump +}; + diff --git a/src/add-ons/kernel/network/stack/net_socket.cpp b/src/add-ons/kernel/network/stack/net_socket.cpp new file mode 100644 index 0000000000..0000a9e7ac --- /dev/null +++ b/src/add-ons/kernel/network/stack/net_socket.cpp @@ -0,0 +1,553 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include "stack_private.h" + +#include +#include + +#include +#include + +#include +#include +#include + + +status_t +create_socket(int family, int type, int protocol, net_socket **_socket) +{ + struct net_socket *socket = new (std::nothrow) net_socket; + if (socket == NULL) + return B_NO_MEMORY; + + memset(socket, 0, sizeof(net_socket)); + socket->family = family; + socket->type = type; + socket->protocol = protocol; + + // set defaults (may be overridden by the protocols) + socket->send.buffer_size = 65536; + socket->send.low_water_mark = 1; + socket->send.timeout = B_INFINITE_TIMEOUT; + socket->receive.buffer_size = 65536; + socket->receive.low_water_mark = 1; + socket->receive.timeout = B_INFINITE_TIMEOUT; + + status_t status = get_domain_protocols(socket); + if (status < B_OK) { + delete socket; + return status; + } + + status = socket->first_info->open(socket->first_protocol); + if (status < B_OK) { + put_domain_protocols(socket); + delete socket; + return status; + } + + *_socket = socket; + return B_OK; +} + + +status_t +socket_close(net_socket *socket) +{ + return socket->first_info->close(socket->first_protocol); +} + + +status_t +socket_free(net_socket *socket) +{ + status_t status = socket->first_info->free(socket->first_protocol); + + put_domain_protocols(socket); + delete socket; + + return status; +} + + +status_t +socket_readv(net_socket *socket, const iovec *vecs, size_t vecCount, size_t *_length) +{ + return -1; +} + + +status_t +socket_writev(net_socket *socket, const iovec *vecs, size_t vecCount, size_t *_length) +{ + if (socket->peer.ss_len == 0) + return ECONNRESET; + + if (socket->address.ss_len == 0) { + // TODO: bind?! + return ENETUNREACH; + } + + // TODO: useful, maybe even computed header space! + net_buffer *buffer = gNetBufferModule.create(256); + if (buffer == NULL) + return ENOBUFS; + + // copy data into buffer + + for (uint32 i = 0; i < vecCount; i++) { + if (gNetBufferModule.append(buffer, vecs[i].iov_base, + vecs[i].iov_len) < B_OK) { + gNetBufferModule.free(buffer); + return ENOBUFS; + } + } + + //buffer->source = (sockaddr *)&socket->address; + //buffer->destination = (sockaddr *)&socket->peer; + memcpy(&buffer->source, &socket->address, socket->address.ss_len); + memcpy(&buffer->destination, &socket->peer, socket->peer.ss_len); + + ssize_t bytesWritten = socket->first_info->send_data(socket->first_protocol, + buffer); + if (bytesWritten < B_OK) { + *_length = 0; + return bytesWritten; + } + + *_length = bytesWritten; + return B_OK; +} + + +status_t +socket_control(net_socket *socket, int32 op, void *data, size_t length) +{ + return socket->first_info->control(socket->first_protocol, + LEVEL_DRIVER_IOCTL, op, data, &length); +} + + +ssize_t +socket_read_avail(net_socket *socket) +{ + return socket->first_info->read_avail(socket->first_protocol); +} + + +ssize_t +socket_send_avail(net_socket *socket) +{ + return socket->first_info->send_avail(socket->first_protocol); +} + + +status_t +socket_send_data(net_socket *socket, net_buffer *buffer) +{ + return socket->first_info->send_data(socket->first_protocol, + buffer); +} + + +status_t +socket_receive_data(net_socket *socket, size_t length, uint32 flags, + net_buffer **_buffer) +{ + return socket->first_info->read_data(socket->first_protocol, + length, flags, _buffer); +} + + +// #pragma mark - standard socket API + + +int +socket_accept(net_socket *socket, struct sockaddr *address, socklen_t *_addressLength, + net_socket **_acceptedSocket) +{ + net_socket *accepted; + status_t status = socket->first_info->accept(socket->first_protocol, + &accepted); + if (status < B_OK) + return status; + + if (address && *_addressLength > 0) { + memcpy(address, &accepted->peer, min_c(*_addressLength, accepted->peer.ss_len)); + *_addressLength = accepted->peer.ss_len; + } + + *_acceptedSocket = accepted; + return B_OK; +} + + +int +socket_bind(net_socket *socket, const struct sockaddr *address, socklen_t addressLength) +{ + sockaddr empty; + if (address == NULL) { + // special - try to bind to an empty address, like INADDR_ANY + memset(&empty, 0, sizeof(sockaddr)); + empty.sa_len = sizeof(sockaddr); + empty.sa_family = socket->family; + + address = ∅ + addressLength = sizeof(sockaddr); + } + + if (socket->address.ss_len != 0) { + status_t status = socket->first_info->unbind(socket->first_protocol, + (sockaddr *)&socket->address); + if (status < B_OK) + return status; + } + + memcpy(&socket->address, address, sizeof(sockaddr)); + + status_t status = socket->first_info->bind(socket->first_protocol, + (sockaddr *)address); + if (status < B_OK) { + // clear address again, as binding failed + socket->address.ss_len = 0; + } + + return status; +} + + +int +socket_connect(net_socket *socket, const struct sockaddr *address, socklen_t addressLength) +{ + if (address == NULL || addressLength == 0) + return ENETUNREACH; + + if (socket->address.ss_len == 0) { + // try to bind first + status_t status = socket_bind(socket, NULL, 0); + if (status < B_OK) + return status; + } + + return socket->first_info->connect(socket->first_protocol, address); +} + + +int +socket_getpeername(net_socket *socket, struct sockaddr *address, socklen_t *_addressLength) +{ + if (socket->peer.ss_len == 0) + return ENOTCONN; + + memcpy(address, &socket->peer, min_c(*_addressLength, socket->peer.ss_len)); + *_addressLength = socket->peer.ss_len; + return B_OK; +} + + +int +socket_getsockname(net_socket *socket, struct sockaddr *address, socklen_t *_addressLength) +{ + if (socket->address.ss_len == 0) + return ENOTCONN; + + memcpy(address, &socket->address, min_c(*_addressLength, socket->address.ss_len)); + *_addressLength = socket->address.ss_len; + return B_OK; +} + + +int +socket_getsockopt(net_socket *socket, int level, int option, void *value, + int *_length) +{ + if (level != SOL_SOCKET) { + return socket->first_info->control(socket->first_protocol, + level | LEVEL_GET_OPTION, option, value, (size_t *)_length); + } + + switch (option) { + case SO_SNDBUF: + { + uint32 *size = (uint32 *)value; + *size = socket->send.buffer_size; + *_length = sizeof(uint32); + return B_OK; + } + + case SO_RCVBUF: + { + uint32 *size = (uint32 *)value; + *size = socket->receive.buffer_size; + *_length = sizeof(uint32); + return B_OK; + } + + default: + break; + } + + return ENOPROTOOPT; +} + + +int +socket_listen(net_socket *socket, int backlog) +{ + return socket->first_info->listen(socket->first_protocol, backlog); +} + + +ssize_t +socket_recv(net_socket *socket, void *data, size_t length, int flags) +{ + net_buffer *buffer; + status_t status = socket->first_info->read_data( + socket->first_protocol, length, flags, &buffer); + if (status < B_OK) + return status; + + ssize_t bytesReceived = buffer->size; + gNetBufferModule.read(buffer, 0, data, bytesReceived); + gNetBufferModule.free(buffer); + + return bytesReceived; +} + + +ssize_t +socket_recvfrom(net_socket *socket, void *data, size_t length, int flags, + struct sockaddr *address, socklen_t *_addressLength) +{ + net_buffer *buffer; + status_t status = socket->first_info->read_data( + socket->first_protocol, length, flags, &buffer); + if (status < B_OK) + return status; + + ssize_t bytesReceived = buffer->size; + gNetBufferModule.read(buffer, 0, data, bytesReceived); + + // copy source address + if (address != NULL && *_addressLength > 0) { + *_addressLength = min_c(buffer->source.ss_len, *_addressLength); + memcpy(address, &buffer->source, *_addressLength); + } + + gNetBufferModule.free(buffer); + + return bytesReceived; +} + + +ssize_t +socket_send(net_socket *socket, const void *data, size_t length, int flags) +{ + if (socket->peer.ss_len == 0) + return EDESTADDRREQ; + + if (socket->address.ss_len == 0) { + // try to bind first + status_t status = socket_bind(socket, NULL, 0); + if (status < B_OK) + return status; + } + + // TODO: useful, maybe even computed header space! + net_buffer *buffer = gNetBufferModule.create(256); + if (buffer == NULL) + return ENOBUFS; + + // copy data into buffer + if (gNetBufferModule.append(buffer, data, length) < B_OK) { + gNetBufferModule.free(buffer); + return ENOBUFS; + } + + buffer->flags = flags; + //buffer->source = (sockaddr *)&socket->address; + //buffer->destination = (sockaddr *)&socket->peer; + memcpy(&buffer->source, &socket->address, socket->address.ss_len); + memcpy(&buffer->destination, &socket->peer, socket->peer.ss_len); + + status_t status = socket->first_info->send_data(socket->first_protocol, buffer); + if (status < B_OK) { + gNetBufferModule.free(buffer); + return status; + } + + return length; +} + + +ssize_t +socket_sendto(net_socket *socket, const void *data, size_t length, int flags, + const struct sockaddr *address, socklen_t addressLength) +{ + if ((address == NULL || addressLength == 0) && socket->peer.ss_len != 0) { + // socket is connected, we use that address: + address = (struct sockaddr *)&socket->peer; + addressLength = socket->peer.ss_len; + } + if (address == NULL || addressLength == 0) { + // don't know where to send to: + return EDESTADDRREQ; + } + if (socket->peer.ss_len != 0) { + // an address has been given but socket is connected already: + return EISCONN; + } + + if (socket->address.ss_len == 0) { + // try to bind first + status_t status = socket_bind(socket, NULL, 0); + if (status < B_OK) + return status; + } + + // TODO: useful, maybe even computed header space! + net_buffer *buffer = gNetBufferModule.create(256); + if (buffer == NULL) + return ENOBUFS; + + // copy data into buffer + if (gNetBufferModule.append(buffer, data, length) < B_OK) { + gNetBufferModule.free(buffer); + return ENOBUFS; + } + + buffer->flags = flags; + memcpy(&buffer->source, &socket->address, socket->address.ss_len); + memcpy(&buffer->destination, address, addressLength); + + status_t status = socket->first_info->send_data(socket->first_protocol, buffer); + if (status < B_OK) { + gNetBufferModule.free(buffer); + return status; + } + + return length; +} + + +int +socket_setsockopt(net_socket *socket, int level, int option, const void *value, + int length) +{ + if (level != SOL_SOCKET) { + return socket->first_info->control(socket->first_protocol, + level | LEVEL_SET_OPTION, option, (void *)value, (size_t *)&length); + } + + switch (option) { + // TODO: implement other options! + case SO_LINGER: + { + if (length < (int)sizeof(struct linger)) + return B_BAD_VALUE; + + struct linger *linger = (struct linger *)value; + if (linger->l_onoff) { + socket->options |= SO_LINGER; + socket->linger = linger->l_linger; + } else { + socket->options &= ~SO_LINGER; + socket->linger = 0; + } + return B_OK; + } + + case SO_SNDBUF: + if (length != sizeof(uint32)) + return B_BAD_VALUE; + + socket->send.buffer_size = *(const uint32 *)value; + return B_OK; + + case SO_RCVBUF: + if (length != sizeof(uint32)) + return B_BAD_VALUE; + + socket->receive.buffer_size = *(const uint32 *)value; + return B_OK; + + default: + break; + } + + return ENOPROTOOPT; +} + + +int +socket_shutdown(net_socket *socket, int direction) +{ + return socket->first_info->shutdown(socket->first_protocol, direction); +} + + +// #pragma mark - + + +static status_t +socket_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + { + // initialize the main stack if not done so already + module_info *module; + return get_module(NET_STARTER_MODULE_NAME, &module); + } + case B_MODULE_UNINIT: + return put_module(NET_STARTER_MODULE_NAME); + + default: + return B_ERROR; + } +} + + +net_socket_module_info gNetSocketModule = { + { + NET_SOCKET_MODULE_NAME, + 0, + socket_std_ops + }, + create_socket, + socket_close, + socket_free, + + socket_readv, + socket_writev, + socket_control, + + socket_read_avail, + socket_send_avail, + + socket_send_data, + socket_receive_data, + + // standard socket API + socket_accept, + socket_bind, + socket_connect, + socket_getpeername, + socket_getsockname, + socket_getsockopt, + socket_listen, + socket_recv, + socket_recvfrom, + socket_send, + socket_sendto, + socket_setsockopt, + socket_shutdown, +}; + diff --git a/src/add-ons/kernel/network/stack/radix.c b/src/add-ons/kernel/network/stack/radix.c new file mode 100644 index 0000000000..9c0cfecb8a --- /dev/null +++ b/src/add-ons/kernel/network/stack/radix.c @@ -0,0 +1,1111 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ + +/* + * Copyright (c) 1988, 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Routines to build and maintain radix trees for routing lookups. + */ + +#include "radix.h" + +#include + +#include +#include + + +static int rn_walktree_from(struct radix_node_head *h, void *a, void *m, + walktree_f_t *f, void *w); +static int rn_walktree(struct radix_node_head *, walktree_f_t *, void *); +static struct radix_node *rn_insert(void *, struct radix_node_head *, int *, + struct radix_node [2]); +static struct radix_node *rn_newpair(void *, int, struct radix_node[2]); +static struct radix_node *rn_search(void *, struct radix_node *); +static struct radix_node *rn_search_m(void *, struct radix_node *, void *); + +static int max_keylen; +static struct radix_mask *rn_mkfreelist; +static struct radix_node_head *mask_rnhead; +/* + * Work area -- the following point to 3 buffers of size max_keylen, + * allocated in this order in a block of memory malloc'ed by rn_init. + */ +static uint8 *rn_zeros, *rn_ones, *addmask_key; + +#define MKFree(m) { (m)->rm_mklist = rn_mkfreelist; rn_mkfreelist = (m);} + +#define rn_masktop (mask_rnhead->rnh_treetop) + +static int rn_lexobetter(void *m_arg, void *n_arg); +static struct radix_mask *rn_new_radix_mask(struct radix_node *tt, + struct radix_mask *next); +static int rn_satisfies_leaf(char *trial, struct radix_node *leaf, + int skip); + +/* + * The data structure for the keys is a radix tree with one way + * branching removed. The index rn_bit at an internal node n represents a bit + * position to be tested. The tree is arranged so that all descendants + * of a node n have keys whose bits all agree up to position rn_bit - 1. + * (We say the index of n is rn_bit.) + * + * There is at least one descendant which has a one bit at position rn_bit, + * and at least one with a zero there. + * + * A route is determined by a pair of key and mask. We require that the + * bit-wise logical and of the key and mask to be the key. + * We define the index of a route to associated with the mask to be + * the first bit number in the mask where 0 occurs (with bit number 0 + * representing the highest order bit). + * + * We say a mask is normal if every bit is 0, past the index of the mask. + * If a node n has a descendant (k, m) with index(m) == index(n) == rn_bit, + * and m is a normal mask, then the route applies to every descendant of n. + * If the index(m) < rn_bit, this implies the trailing last few bits of k + * before bit b are all 0, (and hence consequently true of every descendant + * of n), so the route applies to all descendants of the node as well. + * + * Similar logic shows that a non-normal mask m such that + * index(m) <= index(n) could potentially apply to many children of n. + * Thus, for each non-host route, we attach its mask to a list at an internal + * node as high in the tree as we can go. + * + * The present version of the code makes use of normal routes in short- + * circuiting an explict mask and compare operation when testing whether + * a key satisfies a normal route, and also in remembering the unique leaf + * that governs a subtree. + */ + +/* + * Most of the functions in this code assume that the key/mask arguments + * are sockaddr-like structures, where the first byte is an u_char + * indicating the size of the entire structure. + * + * To make the assumption more explicit, we use the LEN() macro to access + * this field. It is safe to pass an expression with side effects + * to LEN() as the argument is evaluated only once. + */ +#define LEN(x) (*(const u_char *)(x)) + +/* + * XXX THIS NEEDS TO BE FIXED + * In the code, pointers to keys and masks are passed as either + * 'void *' (because callers use to pass pointers of various kinds), or + * 'caddr_t' (which is fine for pointer arithmetics, but not very + * clean when you dereference it to access data). Furthermore, caddr_t + * is really 'char *', while the natural type to operate on keys and + * masks would be 'u_char'. This mismatch require a lot of casts and + * intermediate variables to adapt types that clutter the code. + */ + + +static int /* XXX: arbitrary ordering for non-contiguous masks */ +rn_lexobetter(void *m_arg, void *n_arg) +{ + register uint8 *mp = m_arg, *np = n_arg, *lim; + + if (LEN(mp) > LEN(np)) + return 1; /* not really, but need to check longer one first */ + if (LEN(mp) == LEN(np)) { + for (lim = mp + LEN(mp); mp < lim;) { + if (*mp++ > *np++) + return 1; + } + } + return 0; +} + + +static struct radix_mask * +rn_new_radix_mask(register struct radix_node *tt, register struct radix_mask *next) +{ + register struct radix_mask *m; + + if (rn_mkfreelist) { + m = rn_mkfreelist; + rn_mkfreelist = m->rm_mklist; + } else + m = (struct radix_mask *)malloc(sizeof(struct radix_mask)); + if (m == 0) { + dprintf("Mask for route not entered\n"); + return 0; + } + memset(m, 0, sizeof *m); + m->rm_bit = tt->rn_bit; + m->rm_flags = tt->rn_flags; + if (tt->rn_flags & RNF_NORMAL) + m->rm_leaf = tt; + else + m->rm_mask = tt->rn_mask; + m->rm_mklist = next; + tt->rn_mklist = m; + return m; +} + + +/*! + Search a node in the tree matching the key. +*/ +static struct radix_node * +rn_search(void *v_arg, struct radix_node *head) +{ + register struct radix_node *x; + register caddr_t v; + + for (x = head, v = v_arg; x->rn_bit >= 0;) { + if (x->rn_bmask & v[x->rn_offset]) + x = x->rn_right; + else + x = x->rn_left; + } + return x; +} + + +/*! + Same as above, but with an additional mask. + XXX note this function is used only once. +*/ +static struct radix_node * +rn_search_m(void *v_arg, struct radix_node *head, void *m_arg) +{ + register struct radix_node *x; + register caddr_t v = v_arg, m = m_arg; + + for (x = head; x->rn_bit >= 0;) { + if ((x->rn_bmask & m[x->rn_offset]) + && (x->rn_bmask & v[x->rn_offset])) + x = x->rn_right; + else + x = x->rn_left; + } + return x; +} + + +static int +rn_satisfies_leaf(char *trial, register struct radix_node *leaf, int skip) +{ + register char *cp = trial, *cp2 = leaf->rn_key, *cp3 = leaf->rn_mask; + char *cplim; + int length = min(LEN(cp), LEN(cp2)); + + if (cp3 == 0) + cp3 = rn_ones; + else + length = min(length, *(u_char *)cp3); + cplim = cp + length; cp3 += skip; cp2 += skip; + for (cp += skip; cp < cplim; cp++, cp2++, cp3++) + if ((*cp ^ *cp2) & *cp3) + return 0; + return 1; +} + + +/* + * Whenever we add a new leaf to the tree, we also add a parent node, + * so we allocate them as an array of two elements: the first one must be + * the leaf (see RNTORT() in route.c), the second one is the parent. + * This routine initializes the relevant fields of the nodes, so that + * the leaf is the left child of the parent node, and both nodes have + * (almost) all all fields filled as appropriate. + * (XXX some fields are left unset, see the '#if 0' section). + * The function returns a pointer to the parent node. + */ + +static struct radix_node * +rn_newpair(void *v, int b, struct radix_node nodes[2]) +{ + register struct radix_node *tt = nodes, *t = tt + 1; + t->rn_bit = b; + t->rn_bmask = 0x80 >> (b & 7); + t->rn_left = tt; + t->rn_offset = b >> 3; + +#if 0 /* XXX perhaps we should fill these fields as well. */ + t->rn_parent = t->rn_right = NULL; + + tt->rn_mask = NULL; + tt->rn_dupedkey = NULL; + tt->rn_bmask = 0; +#endif + tt->rn_bit = -1; + tt->rn_key = (caddr_t)v; + tt->rn_parent = t; + tt->rn_flags = t->rn_flags = RNF_ACTIVE; + tt->rn_mklist = t->rn_mklist = 0; + return t; +} + + +static struct radix_node * +rn_insert(void *v_arg, struct radix_node_head *head, int *dupentry, + struct radix_node nodes[2]) +{ + uint8 *v = v_arg; + struct radix_node *top = head->rnh_treetop; + int head_off = top->rn_offset, vlen = (int)LEN(v); + register struct radix_node *t = rn_search(v_arg, top); + register uint8 *cp = v + head_off; + register int b; + struct radix_node *tt; + /* + * Find first bit at which v and t->rn_key differ + */ + { + register uint8 *cp2 = t->rn_key + head_off; + register int cmp_res; + uint8 *cplim = v + vlen; + + while (cp < cplim) { + if (*cp2++ != *cp++) + goto on1; + } + *dupentry = 1; + return t; + on1: + *dupentry = 0; + cmp_res = (cp[-1] ^ cp2[-1]) & 0xff; + for (b = (cp - v) << 3; cmp_res; b--) { + cmp_res >>= 1; + } + } + { + register struct radix_node *p, *x = top; + cp = v; + do { + p = x; + if (cp[x->rn_offset] & x->rn_bmask) + x = x->rn_right; + else + x = x->rn_left; + } while (b > (unsigned) x->rn_bit); + /* x->rn_bit < b && x->rn_bit >= 0 */ + t = rn_newpair(v_arg, b, nodes); + tt = t->rn_left; + if ((cp[p->rn_offset] & p->rn_bmask) == 0) + p->rn_left = t; + else + p->rn_right = t; + x->rn_parent = t; + t->rn_parent = p; /* frees x, p as temp vars below */ + if ((cp[t->rn_offset] & t->rn_bmask) == 0) { + t->rn_right = x; + } else { + t->rn_right = tt; + t->rn_left = x; + } + } + return tt; +} + + +/*! + This is the same as rn_walktree() except for the parameters and the + exit. +*/ +static int +rn_walktree_from(struct radix_node_head *h, void *a, void *m, walktree_f_t *f, void *w) +{ + int error; + struct radix_node *base, *next; + u_char *xa = (u_char *)a; + u_char *xm = (u_char *)m; + register struct radix_node *rn, *last = 0 /* shut up gcc */; + int stopping = 0; + int lastb; + + /* + * rn_search_m is sort-of-open-coded here. We cannot use the + * function because we need to keep track of the last node seen. + */ + /* printf("about to search\n"); */ + for (rn = h->rnh_treetop; rn->rn_bit >= 0; ) { + last = rn; + /* printf("rn_bit %d, rn_bmask %x, xm[rn_offset] %x\n", + rn->rn_bit, rn->rn_bmask, xm[rn->rn_offset]); */ + if (!(rn->rn_bmask & xm[rn->rn_offset])) { + break; + } + if (rn->rn_bmask & xa[rn->rn_offset]) { + rn = rn->rn_right; + } else { + rn = rn->rn_left; + } + } + /* printf("done searching\n"); */ + + /* + * Two cases: either we stepped off the end of our mask, + * in which case last == rn, or we reached a leaf, in which + * case we want to start from the last node we looked at. + * Either way, last is the node we want to start from. + */ + rn = last; + lastb = rn->rn_bit; + + /* printf("rn %p, lastb %d\n", rn, lastb);*/ + + /* + * This gets complicated because we may delete the node + * while applying the function f to it, so we need to calculate + * the successor node in advance. + */ + while (rn->rn_bit >= 0) { + rn = rn->rn_left; + } + + while (!stopping) { + /* printf("node %p (%d)\n", rn, rn->rn_bit); */ + base = rn; + /* If at right child go back up, otherwise, go right */ + while (rn->rn_parent->rn_right == rn + && !(rn->rn_flags & RNF_ROOT)) { + rn = rn->rn_parent; + + /* if went up beyond last, stop */ + if (rn->rn_bit <= lastb) { + stopping = 1; + /* printf("up too far\n"); */ + /* + * XXX we should jump to the 'Process leaves' + * part, because the values of 'rn' and 'next' + * we compute will not be used. Not a big deal + * because this loop will terminate, but it is + * inefficient and hard to understand! + */ + } + } + + /* + * At the top of the tree, no need to traverse the right + * half, prevent the traversal of the entire tree in the + * case of default route. + */ + if (rn->rn_parent->rn_flags & RNF_ROOT) + stopping = 1; + + /* Find the next *leaf* since next node might vanish, too */ + for (rn = rn->rn_parent->rn_right; rn->rn_bit >= 0;) + rn = rn->rn_left; + next = rn; + /* Process leaves */ + while ((rn = base) != 0) { + base = rn->rn_dupedkey; + /* printf("leaf %p\n", rn); */ + if (!(rn->rn_flags & RNF_ROOT) + && (error = (*f)(rn, w))) + return (error); + } + rn = next; + + if (rn->rn_flags & RNF_ROOT) { + /* printf("root, stopping"); */ + stopping = 1; + } + } + return 0; +} + + +static int +rn_walktree(struct radix_node_head *h, walktree_f_t *f, void *w) +{ + int error; + struct radix_node *base, *next; + register struct radix_node *rn = h->rnh_treetop; + /* + * This gets complicated because we may delete the node + * while applying the function f to it, so we need to calculate + * the successor node in advance. + */ + /* First time through node, go left */ + while (rn->rn_bit >= 0) { + rn = rn->rn_left; + } + for (;;) { + base = rn; + /* If at right child go back up, otherwise, go right */ + while (rn->rn_parent->rn_right == rn + && (rn->rn_flags & RNF_ROOT) == 0) { + rn = rn->rn_parent; + } + /* Find the next *leaf* since next node might vanish, too */ + for (rn = rn->rn_parent->rn_right; rn->rn_bit >= 0;) { + rn = rn->rn_left; + } + next = rn; + /* Process leaves */ + while ((rn = base)) { + base = rn->rn_dupedkey; + if (!(rn->rn_flags & RNF_ROOT) + && (error = (*f)(rn, w))) + return error; + } + rn = next; + if (rn->rn_flags & RNF_ROOT) + return 0; + } + /* NOTREACHED */ +} + + +// #pragma mark - public API + + +struct radix_node * +rn_lookup(void *v_arg, void *m_arg, struct radix_node_head *head) +{ + register struct radix_node *x; + uint8 *netmask = NULL; + + if (m_arg) { + x = rn_addmask(m_arg, 1, head->rnh_treetop->rn_offset); + if (x == 0) + return 0; + netmask = x->rn_key; + } + x = rn_match(v_arg, head); + if (x && netmask) { + while (x && x->rn_mask != netmask) + x = x->rn_dupedkey; + } + return x; +} + + +struct radix_node * +rn_match(void *v_arg, struct radix_node_head *head) +{ + caddr_t v = v_arg; + register struct radix_node *t = head->rnh_treetop, *x; + register caddr_t cp = v, cp2; + caddr_t cplim; + struct radix_node *saved_t, *top = t; + int off = t->rn_offset, vlen = LEN(cp), matched_off; + register int test, b, rn_bit; + + /* + * Open code rn_search(v, top) to avoid overhead of extra + * subroutine call. + */ + for (; t->rn_bit >= 0; ) { + if (t->rn_bmask & cp[t->rn_offset]) + t = t->rn_right; + else + t = t->rn_left; + } + /* + * See if we match exactly as a host destination + * or at least learn how many bits match, for normal mask finesse. + * + * It doesn't hurt us to limit how many bytes to check + * to the length of the mask, since if it matches we had a genuine + * match and the leaf we have is the most specific one anyway; + * if it didn't match with a shorter length it would fail + * with a long one. This wins big for class B&C netmasks which + * are probably the most common case... + */ + if (t->rn_mask) + vlen = *(u_char *)t->rn_mask; + cp += off; cp2 = t->rn_key + off; cplim = v + vlen; + for (; cp < cplim; cp++, cp2++) { + if (*cp != *cp2) + goto on1; + } + /* + * This extra grot is in case we are explicitly asked + * to look up the default. Ugh! + * + * Never return the root node itself, it seems to cause a + * lot of confusion. + */ + if (t->rn_flags & RNF_ROOT) + t = t->rn_dupedkey; + return t; +on1: + test = (*cp ^ *cp2) & 0xff; /* find first bit that differs */ + for (b = 7; (test >>= 1) > 0;) + b--; + matched_off = cp - v; + b += matched_off << 3; + rn_bit = -1 - b; + /* + * If there is a host route in a duped-key chain, it will be first. + */ + if ((saved_t = t)->rn_mask == 0) + t = t->rn_dupedkey; + for (; t; t = t->rn_dupedkey) { + /* + * Even if we don't match exactly as a host, + * we may match if the leaf we wound up at is + * a route to a net. + */ + if (t->rn_flags & RNF_NORMAL) { + if (rn_bit <= t->rn_bit) + return t; + } else if (rn_satisfies_leaf(v, t, matched_off)) + return t; + } + + t = saved_t; + /* start searching up the tree */ + do { + register struct radix_mask *m; + t = t->rn_parent; + m = t->rn_mklist; + /* + * If non-contiguous masks ever become important + * we can restore the masking and open coding of + * the search and satisfaction test and put the + * calculation of "off" back before the "do". + */ + while (m) { + if (m->rm_flags & RNF_NORMAL) { + if (rn_bit <= m->rm_bit) + return (m->rm_leaf); + } else { + off = min(t->rn_offset, matched_off); + x = rn_search_m(v, t, m->rm_mask); + while (x && x->rn_mask != m->rm_mask) + x = x->rn_dupedkey; + if (x && rn_satisfies_leaf(v, x, off)) + return x; + } + m = m->rm_mklist; + } + } while (t != top); + + return 0; +} + + +struct radix_node * +rn_addmask(void *n_arg, int search, int skip) +{ + uint8 *netmask = (uint8 *)n_arg; + register struct radix_node *x; + register uint8 *cp, *cplim; + register int b = 0, mlen, j; + int maskduplicated, m0, isnormal; + struct radix_node *saved_x; + static int last_zeroed = 0; + + if ((mlen = LEN(netmask)) > max_keylen) + mlen = max_keylen; + if (skip == 0) + skip = 1; + if (mlen <= skip) + return mask_rnhead->rnh_nodes; + if (skip > 1) + memcpy(addmask_key + 1, rn_ones + 1, skip - 1); + if ((m0 = mlen) > skip) + memcpy(addmask_key + skip, netmask + skip, mlen - skip); + /* + * Trim trailing zeroes. + */ + for (cp = addmask_key + mlen; (cp > addmask_key) && cp[-1] == 0;) + cp--; + mlen = cp - addmask_key; + if (mlen <= skip) { + if (m0 >= last_zeroed) + last_zeroed = mlen; + return (mask_rnhead->rnh_nodes); + } + if (m0 < last_zeroed) + memset(addmask_key + m0, 0, last_zeroed - m0); + *addmask_key = last_zeroed = mlen; + x = rn_search(addmask_key, rn_masktop); + if (memcmp(addmask_key, x->rn_key, mlen) != 0) + x = 0; + if (x || search) + return x; + x = (struct radix_node *)calloc(1, max_keylen + 2 * sizeof(*x)); + if ((saved_x = x) == 0) + return 0; + netmask = cp = (caddr_t)(x + 2); + memcpy(cp, addmask_key, mlen); + x = rn_insert(cp, mask_rnhead, &maskduplicated, x); + if (maskduplicated) { + dprintf("rn_addmask: mask impossibly already in tree\n"); + free(saved_x); + return x; + } + /* + * Calculate index of mask, and check for normalcy. + * First find the first byte with a 0 bit, then if there are + * more bits left (remember we already trimmed the trailing 0's), + * the pattern must be one of those in normal_chars[], or we have + * a non-contiguous mask. + */ + cplim = netmask + mlen; + isnormal = 1; + for (cp = netmask + skip; (cp < cplim) && *(u_char *)cp == 0xff;) { + cp++; + } + if (cp != cplim) { + static char normal_chars[] = { + 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff}; + + for (j = 0x80; (j & *cp) != 0; j >>= 1) + b++; + if (*cp != normal_chars[b] || cp != (cplim - 1)) + isnormal = 0; + } + b += (cp - netmask) << 3; + x->rn_bit = -1 - b; + if (isnormal) + x->rn_flags |= RNF_NORMAL; + return x; +} + + +struct radix_node * +rn_addroute(void *v_arg, void *n_arg, struct radix_node_head *head, + struct radix_node treenodes[2]) +{ + uint8 *v = (uint8 *)v_arg, *netmask = (uint8 *)n_arg; + register struct radix_node *t, *x = 0, *tt; + struct radix_node *saved_tt, *top = head->rnh_treetop; + short b = 0, b_leaf = 0; + int keyduplicated; + uint8 *mmask; + struct radix_mask *m, **mp; + + /* + * In dealing with non-contiguous masks, there may be + * many different routes which have the same mask. + * We will find it useful to have a unique pointer to + * the mask to speed avoiding duplicate references at + * nodes and possibly save time in calculating indices. + */ + if (netmask) { + if ((x = rn_addmask(netmask, 0, top->rn_offset)) == 0) + return (0); + b_leaf = x->rn_bit; + b = -1 - x->rn_bit; + netmask = x->rn_key; + } + /* + * Deal with duplicated keys: attach node to previous instance + */ + saved_tt = tt = rn_insert(v, head, &keyduplicated, treenodes); + if (keyduplicated) { + for (t = tt; tt; t = tt, tt = tt->rn_dupedkey) { + if (tt->rn_mask == netmask) + return (0); + if (netmask == 0 || + (tt->rn_mask && + ((b_leaf < tt->rn_bit) /* index(netmask) > node */ + || rn_refines(netmask, tt->rn_mask) + || rn_lexobetter(netmask, tt->rn_mask)))) + break; + } + /* + * If the mask is not duplicated, we wouldn't + * find it among possible duplicate key entries + * anyway, so the above test doesn't hurt. + * + * We sort the masks for a duplicated key the same way as + * in a masklist -- most specific to least specific. + * This may require the unfortunate nuisance of relocating + * the head of the list. + * + * We also reverse, or doubly link the list through the + * parent pointer. + */ + if (tt == saved_tt) { + struct radix_node *xx = x; + /* link in at head of list */ + (tt = treenodes)->rn_dupedkey = t; + tt->rn_flags = t->rn_flags; + tt->rn_parent = x = t->rn_parent; + t->rn_parent = tt; /* parent */ + if (x->rn_left == t) + x->rn_left = tt; + else + x->rn_right = tt; + saved_tt = tt; x = xx; + } else { + (tt = treenodes)->rn_dupedkey = t->rn_dupedkey; + t->rn_dupedkey = tt; + tt->rn_parent = t; /* parent */ + if (tt->rn_dupedkey) /* parent */ + tt->rn_dupedkey->rn_parent = tt; /* parent */ + } + tt->rn_key = (caddr_t) v; + tt->rn_bit = -1; + tt->rn_flags = RNF_ACTIVE; + } + /* + * Put mask in tree. + */ + if (netmask) { + tt->rn_mask = netmask; + tt->rn_bit = x->rn_bit; + tt->rn_flags |= x->rn_flags & RNF_NORMAL; + } + t = saved_tt->rn_parent; + if (keyduplicated) + goto on2; + b_leaf = -1 - t->rn_bit; + if (t->rn_right == saved_tt) + x = t->rn_left; + else + x = t->rn_right; + /* Promote general routes from below */ + if (x->rn_bit < 0) { + for (mp = &t->rn_mklist; x; x = x->rn_dupedkey) + if (x->rn_mask && (x->rn_bit >= b_leaf) && x->rn_mklist == 0) { + *mp = m = rn_new_radix_mask(x, 0); + if (m) + mp = &m->rm_mklist; + } + } else if (x->rn_mklist) { + /* + * Skip over masks whose index is > that of new node + */ + for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) + if (m->rm_bit >= b_leaf) + break; + t->rn_mklist = m; *mp = 0; + } +on2: + /* Add new route to highest possible ancestor's list */ + if ((netmask == 0) || (b > t->rn_bit )) + return tt; /* can't lift at all */ + b_leaf = tt->rn_bit; + do { + x = t; + t = t->rn_parent; + } while (b <= t->rn_bit && x != top); + /* + * Search through routes associated with node to + * insert new route according to index. + * Need same criteria as when sorting dupedkeys to avoid + * double loop on deletion. + */ + for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) { + if (m->rm_bit < b_leaf) + continue; + if (m->rm_bit > b_leaf) + break; + if (m->rm_flags & RNF_NORMAL) { + mmask = m->rm_leaf->rn_mask; + if (tt->rn_flags & RNF_NORMAL) { + dprintf("Non-unique normal route, mask not entered\n"); + return tt; + } + } else + mmask = m->rm_mask; + if (mmask == netmask) { + m->rm_refs++; + tt->rn_mklist = m; + return tt; + } + if (rn_refines(netmask, mmask) + || rn_lexobetter(netmask, mmask)) + break; + } + *mp = rn_new_radix_mask(tt, *mp); + return tt; +} + + +struct radix_node * +rn_delete(void *v_arg, void *netmask_arg, struct radix_node_head *head) +{ + register struct radix_node *t, *p, *x, *tt; + struct radix_mask *m, *saved_m, **mp; + struct radix_node *dupedkey, *saved_tt, *top; + uint8 *v, *netmask; + int b, head_off, vlen; + + v = v_arg; + netmask = netmask_arg; + x = head->rnh_treetop; + tt = rn_search(v, x); + head_off = x->rn_offset; + vlen = LEN(v); + saved_tt = tt; + top = x; + if (tt == 0 + || memcmp(v + head_off, tt->rn_key + head_off, vlen - head_off)) + return 0; + /* + * Delete our route from mask lists. + */ + if (netmask) { + if ((x = rn_addmask(netmask, 1, head_off)) == 0) + return 0; + netmask = x->rn_key; + while (tt->rn_mask != netmask) + if ((tt = tt->rn_dupedkey) == 0) + return 0; + } + if (tt->rn_mask == 0 || (saved_m = m = tt->rn_mklist) == 0) + goto on1; + if (tt->rn_flags & RNF_NORMAL) { + if (m->rm_leaf != tt || m->rm_refs > 0) { + dprintf("rn_delete: inconsistent annotation\n"); + return 0; /* dangling ref could cause disaster */ + } + } else { + if (m->rm_mask != tt->rn_mask) { + dprintf("rn_delete: inconsistent annotation\n"); + goto on1; + } + if (--m->rm_refs >= 0) + goto on1; + } + b = -1 - tt->rn_bit; + t = saved_tt->rn_parent; + if (b > t->rn_bit) + goto on1; /* Wasn't lifted at all */ + do { + x = t; + t = t->rn_parent; + } while (b <= t->rn_bit && x != top); + for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) + if (m == saved_m) { + *mp = m->rm_mklist; + MKFree(m); + break; + } + if (m == 0) { + dprintf("rn_delete: couldn't find our annotation\n"); + if (tt->rn_flags & RNF_NORMAL) + return 0; /* Dangling ref to us */ + } +on1: + /* + * Eliminate us from tree + */ + if (tt->rn_flags & RNF_ROOT) + return 0; + t = tt->rn_parent; + dupedkey = saved_tt->rn_dupedkey; + if (dupedkey) { + /* + * Here, tt is the deletion target and + * saved_tt is the head of the dupekey chain. + */ + if (tt == saved_tt) { + /* remove from head of chain */ + x = dupedkey; x->rn_parent = t; + if (t->rn_left == tt) + t->rn_left = x; + else + t->rn_right = x; + } else { + /* find node in front of tt on the chain */ + for (x = p = saved_tt; p && p->rn_dupedkey != tt;) + p = p->rn_dupedkey; + if (p) { + p->rn_dupedkey = tt->rn_dupedkey; + if (tt->rn_dupedkey) /* parent */ + tt->rn_dupedkey->rn_parent = p; + /* parent */ + } else + dprintf("rn_delete: couldn't find us\n"); + } + t = tt + 1; + if (t->rn_flags & RNF_ACTIVE) { + *++x = *t; + p = t->rn_parent; + if (p->rn_left == t) + p->rn_left = x; + else + p->rn_right = x; + x->rn_left->rn_parent = x; + x->rn_right->rn_parent = x; + } + goto out; + } + if (t->rn_left == tt) + x = t->rn_right; + else + x = t->rn_left; + p = t->rn_parent; + if (p->rn_right == t) + p->rn_right = x; + else + p->rn_left = x; + x->rn_parent = p; + /* + * Demote routes attached to us. + */ + if (t->rn_mklist) { + if (x->rn_bit >= 0) { + for (mp = &x->rn_mklist; (m = *mp);) + mp = &m->rm_mklist; + *mp = t->rn_mklist; + } else { + /* If there are any key,mask pairs in a sibling + duped-key chain, some subset will appear sorted + in the same order attached to our mklist */ + for (m = t->rn_mklist; m && x; x = x->rn_dupedkey) { + if (m == x->rn_mklist) { + struct radix_mask *mm = m->rm_mklist; + x->rn_mklist = 0; + if (--(m->rm_refs) < 0) + MKFree(m); + m = mm; + } + } + if (m) { + dprintf("rn_delete: Orphaned Mask %p at %p\n", + (void *)m, (void *)x); + } + } + } + /* + * We may be holding an active internal node in the tree. + */ + x = tt + 1; + if (t != x) { + *t = *x; + t->rn_left->rn_parent = t; + t->rn_right->rn_parent = t; + p = x->rn_parent; + if (p->rn_left == x) + p->rn_left = t; + else + p->rn_right = t; + } +out: + tt->rn_flags &= ~RNF_ACTIVE; + tt[1].rn_flags &= ~RNF_ACTIVE; + return tt; +} + + +int +rn_refines(void *m_arg, void *n_arg) +{ + register caddr_t m = m_arg, n = n_arg; + register caddr_t lim, lim2 = lim = n + LEN(n); + int longer = LEN(n++) - (int)LEN(m++); + int masks_are_equal = 1; + + if (longer > 0) + lim -= longer; + while (n < lim) { + if (*n & ~(*m)) + return 0; + if (*n++ != *m++) + masks_are_equal = 0; + } + + while (n < lim2) { + if (*n++) + return 0; + } + + if (masks_are_equal && (longer < 0)) { + for (lim2 = m - longer; m < lim2; ) { + if (*m++) + return 1; + } + } + + return !masks_are_equal; +} + + +/*! + Allocate and initialize an empty tree. This has 3 nodes, which are + part of the radix_node_head (in the order ) and are + marked RNF_ROOT so they cannot be freed. + The leaves have all-zero and all-one keys, with significant + bits starting at 'off'. + Return 1 on success, 0 on error. +*/ +int +rn_inithead(void **head, int off) +{ + register struct radix_node_head *rnh; + register struct radix_node *t, *tt, *ttt; + if (*head) + return 1; + rnh = (struct radix_node_head *)calloc(1, sizeof(*rnh)); + if (rnh == NULL) + return 0; + + *head = rnh; + t = rn_newpair(rn_zeros, off, rnh->rnh_nodes); + ttt = rnh->rnh_nodes + 2; + t->rn_right = ttt; + t->rn_parent = t; + tt = t->rn_left; /* ... which in turn is rnh->rnh_nodes */ + tt->rn_flags = t->rn_flags = RNF_ROOT | RNF_ACTIVE; + tt->rn_bit = -1 - off; + *ttt = *tt; + ttt->rn_key = rn_ones; + rnh->rnh_addaddr = rn_addroute; + rnh->rnh_deladdr = rn_delete; + rnh->rnh_matchaddr = rn_match; + rnh->rnh_lookup = rn_lookup; + rnh->rnh_walktree = rn_walktree; + rnh->rnh_walktree_from = rn_walktree_from; + rnh->rnh_treetop = t; + return 1; +} + + +void +rn_init() +{ + char *cp, *cplim; +#ifdef _KERNEL + struct domain *dom; + + for (dom = domains; dom; dom = dom->dom_next) + if (dom->dom_maxrtkey > max_keylen) + max_keylen = dom->dom_maxrtkey; +#endif + if (max_keylen == 0) { + dprintf("rn_init: radix functions require max_keylen be set\n"); + return; + } + rn_zeros = (char *)malloc(3 * max_keylen); + if (rn_zeros == NULL) + panic("rn_init"); + memset(rn_zeros, 0, 3 * max_keylen); + rn_ones = cp = rn_zeros + max_keylen; + addmask_key = cplim = rn_ones + max_keylen; + while (cp < cplim) + *cp++ = -1; + if (rn_inithead((void **)(void *)&mask_rnhead, 0) == 0) + panic("rn_init 2"); +} diff --git a/src/add-ons/kernel/network/stack/radix.h b/src/add-ons/kernel/network/stack/radix.h new file mode 100644 index 0000000000..828dc30197 --- /dev/null +++ b/src/add-ons/kernel/network/stack/radix.h @@ -0,0 +1,147 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ + +/* + * Copyright (c) 1988, 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#ifndef _RADIX_H_ +#define _RADIX_H_ + + +#include + + +/* + * Radix search tree node layout. + */ + +struct radix_node { + struct radix_mask *rn_mklist; /* list of masks contained in subtree */ + struct radix_node *rn_parent; /* parent */ + short rn_bit; /* bit offset; -1-index(netmask) */ + char rn_bmask; /* node: mask for bit test*/ + uint8 rn_flags; /* enumerated next */ + union { + struct { /* leaf only data: */ + uint8 *rn_Key; /* object of search */ + uint8 *rn_Mask; /* netmask, if present */ + struct radix_node *rn_Dupedkey; + } rn_leaf; + struct { /* node only data: */ + int rn_Off; /* where to start compare */ + struct radix_node *rn_L;/* progeny */ + struct radix_node *rn_R;/* progeny */ + } rn_node; + } rn_u; +}; + +#define RNF_NORMAL 1 /* leaf contains normal route */ +#define RNF_ROOT 2 /* leaf is root leaf for tree */ +#define RNF_ACTIVE 4 /* This node is alive (for rtfree) */ + +#define rn_dupedkey rn_u.rn_leaf.rn_Dupedkey +#define rn_key rn_u.rn_leaf.rn_Key +#define rn_mask rn_u.rn_leaf.rn_Mask +#define rn_offset rn_u.rn_node.rn_Off +#define rn_left rn_u.rn_node.rn_L +#define rn_right rn_u.rn_node.rn_R + +/* + * Annotations to tree concerning potential routes applying to subtrees. + */ + +struct radix_mask { + short rm_bit; /* bit offset; -1-index(netmask) */ + char rm_unused; /* cf. rn_bmask */ + uint8 rm_flags; /* cf. rn_flags */ + struct radix_mask *rm_mklist; /* more masks to try */ + union { + uint8 *rmu_mask; /* the mask */ + struct radix_node *rmu_leaf; /* for normal routes */ + } rm_rmu; + int rm_refs; /* # of references to this struct */ +}; + +#define rm_mask rm_rmu.rmu_mask +#define rm_leaf rm_rmu.rmu_leaf /* extra field would make 32 bytes */ + +typedef int walktree_f_t(struct radix_node *, void *); + +struct radix_node_head { + struct radix_node *rnh_treetop; + int rnh_addrsize; /* permit, but not require fixed keys */ + int rnh_pktsize; /* permit, but not require fixed keys */ + struct radix_node *(*rnh_addaddr) /* add based on sockaddr */ + (void *v, void *mask, + struct radix_node_head *head, struct radix_node nodes[]); + struct radix_node *(*rnh_addpkt) /* add based on packet hdr */ + (void *v, void *mask, + struct radix_node_head *head, struct radix_node nodes[]); + struct radix_node *(*rnh_deladdr) /* remove based on sockaddr */ + (void *v, void *mask, struct radix_node_head *head); + struct radix_node *(*rnh_delpkt) /* remove based on packet hdr */ + (void *v, void *mask, struct radix_node_head *head); + struct radix_node *(*rnh_matchaddr) /* locate based on sockaddr */ + (void *v, struct radix_node_head *head); + struct radix_node *(*rnh_lookup) /* locate based on sockaddr */ + (void *v, void *mask, struct radix_node_head *head); + struct radix_node *(*rnh_matchpkt) /* locate based on packet hdr */ + (void *v, struct radix_node_head *head); + int (*rnh_walktree) /* traverse tree */ + (struct radix_node_head *head, walktree_f_t *f, void *w); + int (*rnh_walktree_from) /* traverse tree below a */ + (struct radix_node_head *head, void *a, void *m, + walktree_f_t *f, void *w); + void (*rnh_close) /* do something when the last ref drops */ + (struct radix_node *rn, struct radix_node_head *head); + struct radix_node rnh_nodes[3]; /* empty tree for common case */ +}; + + +#ifdef __cplusplus +extern "{" +#endif + +void rn_init(void); +int rn_inithead(void **, int); +int rn_refines(void *, void *); +struct radix_node *rn_addmask(void *, int, int); +struct radix_node *rn_addroute (void *, void *, struct radix_node_head *, + struct radix_node [2]); +struct radix_node *rn_delete(void *, void *, struct radix_node_head *); +struct radix_node *rn_lookup (void *v_arg, void *m_arg, + struct radix_node_head *head); +struct radix_node *rn_match(void *, struct radix_node_head *); + +#ifdef __cplusplus +} +#endif + +#endif /* _RADIX_H_ */ diff --git a/src/add-ons/kernel/network/stack/routes.cpp b/src/add-ons/kernel/network/stack/routes.cpp new file mode 100644 index 0000000000..a1e303eef5 --- /dev/null +++ b/src/add-ons/kernel/network/stack/routes.cpp @@ -0,0 +1,467 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include "domains.h" +#include "routes.h" +#include "stack_private.h" + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + + +#define TRACE_ROUTES +#ifdef TRACE_ROUTES +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + + +net_route_private::net_route_private() +{ + destination = mask = gateway = NULL; +} + + +net_route_private::~net_route_private() +{ + free(destination); + free(mask); + free(gateway); +} + + +// #pragma mark - + + +static status_t +user_copy_address(const sockaddr *from, sockaddr **to) +{ + if (from == NULL) { + *to = NULL; + return B_OK; + } + + sockaddr address; + if (user_memcpy(&address, from, sizeof(struct sockaddr)) < B_OK) + return B_BAD_ADDRESS; + + *to = (sockaddr *)malloc(address.sa_len); + if (*to == NULL) + return B_NO_MEMORY; + + if (address.sa_len > sizeof(struct sockaddr)) { + if (user_memcpy(*to, from, address.sa_len) < B_OK) + return B_BAD_ADDRESS; + } else + memcpy(*to, &address, address.sa_len); + + return B_OK; +} + + +static net_route_private * +find_route(struct net_domain *_domain, const net_route *description) +{ + struct net_domain_private *domain = (net_domain_private *)_domain; + RouteList::Iterator iterator = domain->routes.GetIterator(); + + while (iterator.HasNext()) { + net_route_private *route = iterator.Next(); + + if ((route->flags & (RTF_GATEWAY | RTF_HOST | RTF_LOCAL)) == + (description->flags & (RTF_GATEWAY | RTF_HOST | RTF_LOCAL)) + && domain->address_module->equal_masked_addresses(route->destination, + description->destination, description->mask) + && domain->address_module->equal_addresses(route->mask, + description->mask) + && domain->address_module->equal_addresses(route->gateway, + description->gateway)) + return route; + } + + return NULL; +} + + +static net_route_private * +find_route(struct net_domain *_domain, const struct sockaddr *address) +{ + struct net_domain_private *domain = (net_domain_private *)_domain; + + // TODO: the following only works for IPv4 routes! + if (domain->family != AF_INET) + panic("you should have known better..."); + + // find last matching route + + RouteList::Iterator iterator = domain->routes.GetIterator(); + TRACE(("test address %s for routes...\n", AddressString(domain, address).Data())); + + while (iterator.HasNext()) { + net_route_private *route = iterator.Next(); + + bool found; + if (route->mask != NULL) { + sockaddr maskedAddress; + domain->address_module->mask_address(address, route->mask, + &maskedAddress); + found = domain->address_module->equal_addresses(&maskedAddress, + route->destination); + } else { + found = domain->address_module->equal_addresses(address, + route->destination); + } + + if (found) { + TRACE((" found route: %s, flags %lx\n", + AddressString(domain, route->destination).Data(), route->flags)); + return route; + } + } + + return NULL; +} + + +static void +put_route_internal(struct net_domain_private *domain, net_route *_route) +{ + net_route_private *route = (net_route_private *)_route; + if (route == NULL || atomic_add(&route->ref_count, -1) != 1) + return; + + // remove route + + domain->routes.Remove(route); + delete route; +} + + +struct net_route * +get_route_internal(struct net_domain_private *domain, const struct sockaddr *address) +{ + net_route_private *route = find_route(domain, address); + if (route != NULL && atomic_add(&route->ref_count, 1) == 0) { + // route has been deleted already + route = NULL; + } + + return route; +} + + +void +update_route_infos(struct net_domain_private *domain) +{ + RouteInfoList::Iterator iterator = domain->route_infos.GetIterator(); + + while (iterator.HasNext()) { + net_route_info *info = iterator.Next(); + + put_route_internal(domain, info->route); + info->route = get_route_internal(domain, &info->address); + } +} + + +// #pragma mark - + + +/*! + Determines the size of a buffer large enough to contain the whole + routing table. +*/ +uint32 +route_table_size(net_domain_private *domain) +{ + BenaphoreLocker locker(domain->lock); + uint32 size = 0; + + RouteList::Iterator iterator = domain->routes.GetIterator(); + while (iterator.HasNext()) { + net_route_private *route = iterator.Next(); + size += IF_NAMESIZE + sizeof(route_entry); + + if (route->destination) + size += route->destination->sa_len; + if (route->mask) + size += route->mask->sa_len; + if (route->gateway) + size += route->gateway->sa_len; + } + + return size; +} + + +/*! + Dumps a list of all routes into the supplied userland buffer. + If the routes don't fit into the buffer, an error (\c ENOBUFS) is + returned. +*/ +status_t +list_routes(net_domain_private *domain, void *buffer, size_t size) +{ + RouteList::Iterator iterator = domain->routes.GetIterator(); + size_t spaceLeft = size; + + sockaddr zeros; + memset(&zeros, 0, sizeof(sockaddr)); + zeros.sa_family = domain->family; + zeros.sa_len = sizeof(sockaddr); + + while (iterator.HasNext()) { + net_route *route = iterator.Next(); + + size = IF_NAMESIZE + sizeof(route_entry); + + sockaddr *destination = NULL; + sockaddr *mask = NULL; + sockaddr *gateway = NULL; + uint8 *next = (uint8 *)buffer + size; + + if (route->destination != NULL) { + destination = (sockaddr *)next; + next += route->destination->sa_len; + size += route->destination->sa_len; + } + if (route->mask != NULL) { + mask = (sockaddr *)next; + next += route->mask->sa_len; + size += route->mask->sa_len; + } + if (route->gateway != NULL) { + gateway = (sockaddr *)next; + next += route->gateway->sa_len; + size += route->gateway->sa_len; + } + + if (spaceLeft < size) + return ENOBUFS; + + ifreq request; + strlcpy(request.ifr_name, route->interface->name, IF_NAMESIZE); + request.ifr_route.destination = destination; + request.ifr_route.mask = mask; + request.ifr_route.gateway = gateway; + request.ifr_route.mtu = route->mtu; + request.ifr_route.flags = route->flags; + + if (user_memcpy(buffer, &request, size) < B_OK + || (route->destination != NULL && user_memcpy(request.ifr_route.destination, route->destination, route->destination->sa_len) < B_OK) + || (route->mask != NULL && user_memcpy(request.ifr_route.mask, route->mask, route->mask->sa_len) < B_OK) + || (route->gateway != NULL && user_memcpy(request.ifr_route.gateway, route->gateway, route->gateway->sa_len) < B_OK)) + return B_BAD_ADDRESS; + + buffer = (void *)next; + spaceLeft -= size; + } + + return B_OK; +} + + +status_t +control_routes(struct net_interface *interface, int32 option, void *argument, size_t length) +{ + net_domain_private *domain = (net_domain_private *)interface->domain; + + switch (option) { + case SIOCADDRT: + case SIOCDELRT: + { + // add or remove a route + if (length != sizeof(struct ifreq)) + return B_BAD_VALUE; + + route_entry entry; + if (user_memcpy(&entry, &((ifreq *)argument)->ifr_route, sizeof(route_entry)) != B_OK) + return B_BAD_ADDRESS; + + net_route_private route; + status_t status; + if ((status = user_copy_address(entry.destination, &route.destination)) != B_OK + || (status = user_copy_address(entry.mask, &route.mask)) != B_OK + || (status = user_copy_address(entry.gateway, &route.gateway)) != B_OK) + return status; + + route.mtu = entry.mtu; + route.flags = entry.flags; + route.interface = interface; + + if (option == SIOCADDRT) + return add_route(domain, &route); + + return remove_route(domain, &route); + } + } + return B_BAD_VALUE; +} + + +status_t +add_route(struct net_domain *_domain, const struct net_route *newRoute) +{ + struct net_domain_private *domain = (net_domain_private *)_domain; + + TRACE(("add route to domain %s: dest %s, mask %s, gw %s, flags %lx\n", + domain->name, + AddressString(domain, newRoute->destination ? newRoute->destination : NULL).Data(), + AddressString(domain, newRoute->mask ? newRoute->mask : NULL).Data(), + AddressString(domain, newRoute->gateway ? newRoute->gateway : NULL).Data(), + newRoute->flags)); + + if (domain == NULL || newRoute == NULL || newRoute->interface == NULL + || ((newRoute->flags & RTF_HOST) != 0 && newRoute->mask != NULL) + || ((newRoute->flags & RTF_DEFAULT) == 0 && newRoute->destination == NULL) + || ((newRoute->flags & RTF_GATEWAY) != 0 && newRoute->gateway == NULL) + || !domain->address_module->check_mask(newRoute->mask)) + return B_BAD_VALUE; + + net_route_private *route = find_route(domain, newRoute); + if (route != NULL) + return B_FILE_EXISTS; + + route = new (std::nothrow) net_route_private; + if (route == NULL) + return B_NO_MEMORY; + + if (domain->address_module->copy_address(newRoute->destination, + &route->destination, (newRoute->flags & RTF_DEFAULT) != 0, + newRoute->mask) != B_OK + || domain->address_module->copy_address(newRoute->mask, &route->mask, + (newRoute->flags & RTF_DEFAULT) != 0) != B_OK + || domain->address_module->copy_address(newRoute->gateway, + &route->gateway) != B_OK) { + delete route; + return B_NO_MEMORY; + } + + route->flags = newRoute->flags; + route->interface = newRoute->interface; + route->mtu = 0; + route->ref_count = 1; + + // TODO: for now... + //BenaphoreLocker locker(domain->lock); + + // Insert the route sorted by completeness of its mask + + RouteList::Iterator iterator = domain->routes.GetIterator(); + net_route_private *before = NULL; + + while ((before = iterator.Next()) != NULL) { + // if the before mask is less specific than the one of the route, + // we can insert it before that route. + if (domain->address_module->first_mask_bit(before->mask) + > domain->address_module->first_mask_bit(route->mask)) + break; + } + + domain->routes.Insert(before, route); + update_route_infos(domain); + + return B_OK; +} + + +status_t +remove_route(struct net_domain *_domain, const struct net_route *removeRoute) +{ + struct net_domain_private *domain = (net_domain_private *)_domain; + + TRACE(("remove route from domain %s: dest %s, mask %s, gw %s, flags %lx\n", + domain->name, + AddressString(domain, removeRoute->destination ? removeRoute->destination : NULL).Data(), + AddressString(domain, removeRoute->mask ? removeRoute->mask : NULL).Data(), + AddressString(domain, removeRoute->gateway ? removeRoute->gateway : NULL).Data(), + removeRoute->flags)); + + // TODO: for now... + //BenaphoreLocker locker(domain->lock); + + net_route_private *route = find_route(domain, removeRoute); + if (route == NULL) + return B_ENTRY_NOT_FOUND; + + put_route_internal(domain, route); + update_route_infos(domain); + return B_OK; +} + + +struct net_route * +get_route(struct net_domain *_domain, const struct sockaddr *address) +{ + struct net_domain_private *domain = (net_domain_private *)_domain; + BenaphoreLocker locker(domain->lock); + + return get_route_internal(domain, address); +} + + +void +put_route(struct net_domain *_domain, net_route *route) +{ + struct net_domain_private *domain = (net_domain_private *)_domain; + BenaphoreLocker locker(domain->lock); + + put_route_internal(domain, (net_route *)route); +} + + +status_t +register_route_info(struct net_domain *_domain, + struct net_route_info *info) +{ + struct net_domain_private *domain = (net_domain_private *)_domain; + BenaphoreLocker locker(domain->lock); + + domain->route_infos.Add(info); + info->route = get_route_internal(domain, &info->address); + + return B_OK; +} + + +status_t +unregister_route_info(struct net_domain *_domain, + struct net_route_info *info) +{ + struct net_domain_private *domain = (net_domain_private *)_domain; + BenaphoreLocker locker(domain->lock); + + domain->route_infos.Remove(info); + if (info->route != NULL) + put_route_internal(domain, info->route); + + return B_OK; +} + + +status_t +update_route_info(struct net_domain *domain, + struct net_route_info *info) +{ + return B_ERROR; +} + diff --git a/src/add-ons/kernel/network/stack/routes.h b/src/add-ons/kernel/network/stack/routes.h new file mode 100644 index 0000000000..2148171191 --- /dev/null +++ b/src/add-ons/kernel/network/stack/routes.h @@ -0,0 +1,48 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ +#ifndef ROUTES_H +#define ROUTES_H + + +#include +#include + +#include + + +struct net_route_private : net_route, public DoublyLinkedListLinkImpl { + int32 ref_count; + + net_route_private(); + ~net_route_private(); +}; + +typedef DoublyLinkedList RouteList; +typedef DoublyLinkedList > RouteInfoList; + + +uint32 route_table_size(struct net_domain_private *domain); +status_t list_routes(struct net_domain_private *domain, void *buffer, size_t size); +status_t control_routes(struct net_interface *interface, + int32 option, void *argument, size_t length); + +status_t add_route(struct net_domain *domain, + const struct net_route *route); +status_t remove_route(struct net_domain *domain, + const struct net_route *route); +struct net_route *get_route(struct net_domain *domain, const struct sockaddr *address); +void put_route(struct net_domain *domain, struct net_route *route); + +status_t register_route_info(struct net_domain *domain, + struct net_route_info *info); +status_t unregister_route_info(struct net_domain *domain, + struct net_route_info *info); +status_t update_route_info(struct net_domain *domain, + struct net_route_info *info); + +#endif // ROUTES_H diff --git a/src/add-ons/kernel/network/stack/stack.cpp b/src/add-ons/kernel/network/stack/stack.cpp new file mode 100644 index 0000000000..3653dc88e1 --- /dev/null +++ b/src/add-ons/kernel/network/stack/stack.cpp @@ -0,0 +1,939 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include "domains.h" +#include "interfaces.h" +#include "link.h" +#include "stack_private.h" +#include "utility.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + + +#define TRACE_STACK +#ifdef TRACE_STACK +# define TRACE(x) dprintf x +#else +# define TRACE(x) ; +#endif + +#define MAX_CHAIN_MODULES 5 + +struct chain_key { + int family; + int type; + int protocol; +}; + +struct family { + family(int type); + + void Acquire(); + void Release(); + + static int Compare(void *_family, const void *_key); + static uint32 Hash(void *_family, const void *_key, uint32 range); + static struct family *Lookup(int type); + static struct family *Add(int type); + + struct family *next; + int type; + int32 ref_count; + struct list chains; +}; + +struct chain { + chain(int family, int type, int protocol); + ~chain(); + + status_t Acquire(); + void Release(); + void Uninitialize(); + + static int Compare(void *_chain, const void *_key); + static uint32 Hash(void *_chain, const void *_key, uint32 range); + static struct chain *Lookup(hash_table *chains, int family, int type, + int protocol); + static struct chain *Add(hash_table *chains, int family, int type, + int protocol, va_list modules); + static struct chain *Add(hash_table *chains, int family, int type, + int protocol, ...); + static void DeleteChains(hash_table *chains); + + chain *next; + struct list_link family_link; + struct family *parent; + + int family; + int type; + int protocol; + + int32 ref_count; + uint32 flags; + const char *modules[MAX_CHAIN_MODULES + 1]; + module_info *infos[MAX_CHAIN_MODULES + 1]; +}; + +#define CHAIN_MISSING_MODULE 0x02 +#define CHAIN_INITIALIZED 0x01 + +static benaphore sChainLock; +static benaphore sInitializeChainLock; +static hash_table *sProtocolChains; +static hash_table *sDatalinkProtocolChains; +static hash_table *sReceivingProtocolChains; +static hash_table *sFamilies; +static bool sInitialized; + + +family::family(int _type) + : + type(_type), + ref_count(0) +{ + list_init_etc(&chains, offsetof(struct chain, family_link)); +} + + +void +family::Acquire() +{ + atomic_add(&ref_count, 1); +} + + +void +family::Release() +{ + if (atomic_add(&ref_count, -1) > 1) + return; + + TRACE(("family %d unused, uninit chains\n", type)); + BenaphoreLocker locker(&sChainLock); + + struct chain *chain = NULL; + while (true) { + chain = (struct chain *)list_get_next_item(&chains, chain); + if (chain == NULL) + break; + + chain->Uninitialize(); + } +} + + +/*static*/ int +family::Compare(void *_family, const void *_key) +{ + struct family *family = (struct family *)_family; + int key = (int)_key; + + if (family->type == key) + return 0; + + return 1; +} + + +/*static*/ uint32 +family::Hash(void *_family, const void *_key, uint32 range) +{ + struct family *family = (struct family *)_family; + int key = (int)_key; + + if (family != NULL) + return family->type % range; + + return key % range; +} + + +/*static*/ struct family * +family::Lookup(int type) +{ + return (struct family *)hash_lookup(sFamilies, (void *)type); +} + + +/*static*/ struct family * +family::Add(int type) +{ + struct family *family = new (std::nothrow) ::family(type); + if (family == NULL) + return NULL; + + if (hash_insert(sFamilies, family) != B_OK) { + delete family; + return NULL; + } + + return family; +} + + +// #pragma mark - + + +chain::chain(int _family, int _type, int _protocol) + : + family(_family), + type(_type), + protocol(_protocol), + ref_count(0), + flags(0) +{ + parent = ::family::Lookup(family); + if (parent == NULL) + parent = ::family::Add(family); + + for (int32 i = 0; i < MAX_CHAIN_MODULES; i++) { + modules[i] = NULL; + infos[i] = NULL; + } +} + + +chain::~chain() +{ + for (int32 i = 0; i < MAX_CHAIN_MODULES; i++) { + free((char *)modules[i]); + } +} + + +status_t +chain::Acquire() +{ + if (atomic_add(&ref_count, 1) > 0) { + if (flags & CHAIN_MISSING_MODULE) { + atomic_add(&ref_count, -1); + return EAFNOSUPPORT; + } + + while ((flags & CHAIN_INITIALIZED) == 0) { + benaphore_lock(&sInitializeChainLock); + benaphore_unlock(&sInitializeChainLock); + } + return B_OK; + } + + parent->Acquire(); + + if ((flags & CHAIN_INITIALIZED) != 0) + return B_OK; + + TRACE(("initializing chain %d.%d.%d\n", family, type, protocol)); + BenaphoreLocker locker(&sInitializeChainLock); + + for (int32 i = 0; modules[i] != NULL; i++) { + if (get_module(modules[i], &infos[i]) < B_OK) { + flags |= CHAIN_MISSING_MODULE; + + // put already opened modules + while (i-- > 0) { + put_module(modules[i]); + } + return EAFNOSUPPORT; + } + } + + flags |= CHAIN_INITIALIZED; + return B_OK; +} + + +void +chain::Release() +{ + if (atomic_add(&ref_count, -1) > 1) + return; + + TRACE(("chain %d.%d.%d unused\n", family, type, protocol)); + parent->Release(); +} + + +void +chain::Uninitialize() +{ + if ((flags & CHAIN_INITIALIZED) == 0) + return; + + TRACE(("uninit chain %d.%d.%d\n", family, type, protocol)); + BenaphoreLocker locker(sInitializeChainLock); + + for (int32 i = 0; modules[i] != NULL; i++) { + put_module(modules[i]); + } + + flags &= ~CHAIN_INITIALIZED; +} + + +/*static*/ int +chain::Compare(void *_chain, const void *_key) +{ + const chain_key *key = (const chain_key *)_key; + struct chain *chain = (struct chain *)_chain; + + if (chain->family == key->family + && chain->type == key->type + && chain->protocol == key->protocol) + return 0; + + return 1; +} + + +/*static*/ uint32 +chain::Hash(void *_chain, const void *_key, uint32 range) +{ + const chain_key *key = (const chain_key *)_key; + struct chain *chain = (struct chain *)_chain; + +// TODO: check if this makes a good hash... +#define HASH(o) ((uint32)(((o)->family) ^ ((o)->type) ^ ((o)->protocol)) % range) + TRACE(("%d.%d.%d: Hash: %lu\n", chain ? chain->family : key->family, + chain ? chain->type : key->type, chain ? chain->protocol : key->protocol, + chain ? HASH(chain) : HASH(key))); + + if (chain != NULL) + return HASH(chain); + + return HASH(key); +#undef HASH +} + + +/*static */ struct chain * +chain::Lookup(hash_table *chains, int family, int type, int protocol) +{ + struct chain_key key = { family, type, protocol }; + return (struct chain *)hash_lookup(chains, &key); +} + + +/*static*/ struct chain * +chain::Add(hash_table *chains, int family, int type, int protocol, va_list modules) +{ + struct chain *chain = new (std::nothrow) ::chain(family, type, protocol); + if (chain == NULL) + return NULL; + + if (chain->parent == NULL || hash_insert(chains, chain) != B_OK) { + delete chain; + return NULL; + } + + TRACE(("Add chain %d.%d.%d:\n", family, type, protocol)); + const char *module; + int32 count = 0; + + while (true) { + module = va_arg(modules, const char *); + if (module == NULL) + break; + + TRACE((" [%ld] %s\n", count, module)); + chain->modules[count] = strdup(module); + if (chain->modules[count] == NULL + || ++count >= MAX_CHAIN_MODULES) { + hash_remove(chains, chain); + delete chain; + return NULL; + } + } + + if (chains == sProtocolChains && count == 0) { + hash_remove(chains, chain); + delete chain; + return NULL; + } + + return chain; +} + + +/*static*/ struct chain * +chain::Add(hash_table *chains, int family, int type, int protocol, ...) +{ + va_list modules; + va_start(modules, protocol); + + struct chain *chain = Add(chains, family, type, 0, modules); + + va_end(modules); + return chain; +} + + +/*static*/ void +chain::DeleteChains(hash_table *chains) +{ + uint32 cookie = 0; + while (true) { + struct chain *chain = (struct chain *)hash_remove_first(chains, &cookie); + if (chain == NULL) + break; + + chain->Uninitialize(); + delete chain; + } +} + + +// #pragma mark - + + +static void +uninit_domain_protocols(net_socket *socket) +{ + net_protocol *protocol = socket->first_protocol; + while (protocol != NULL) { + net_protocol *next = protocol->next; + protocol->module->uninit_protocol(protocol); + + protocol = next; + } + + socket->first_protocol = NULL; + socket->first_info = NULL; +} + + +status_t +get_domain_protocols(net_socket *socket) +{ + struct chain *chain; + + { + BenaphoreLocker locker(&sChainLock); + + chain = chain::Lookup(sProtocolChains, socket->family, socket->type, + socket->type == SOCK_RAW ? 0 : socket->protocol); + // in SOCK_RAW mode, we ignore the protocol information + if (chain == NULL) { + // TODO: if we want to be POSIX compatible, we should also support + // the error codes EPROTONOSUPPORT and EPROTOTYPE. + return EAFNOSUPPORT; + } + } + + // create net_protocol objects for the protocols in the chain + + status_t status = chain->Acquire(); + if (status < B_OK) + return status; + + net_protocol *last = NULL; + + for (int32 i = 0; chain->infos[i] != NULL; i++) { + net_protocol *protocol = + ((net_protocol_module_info *)chain->infos[i])->init_protocol(socket); + if (protocol == NULL) { + // free protocols we already initialized + uninit_domain_protocols(socket); + chain->Release(); + return B_NO_MEMORY; + } + + protocol->module = (net_protocol_module_info *)chain->infos[i]; + protocol->socket = socket; + protocol->next = NULL; + + if (last == NULL) { + socket->first_protocol = protocol; + socket->first_info = protocol->module; + } else + last->next = protocol; + + last = protocol; + } + + return B_OK; +} + + +status_t +put_domain_protocols(net_socket *socket) +{ + struct chain *chain; + + { + BenaphoreLocker locker(&sChainLock); + + chain = chain::Lookup(sProtocolChains, socket->family, socket->type, + socket->protocol); + if (chain == NULL) + return B_ERROR; + } + + uninit_domain_protocols(socket); + chain->Release(); + return B_OK; +} + + +static void +uninit_domain_datalink_protocols(net_interface *interface) +{ + net_datalink_protocol *protocol = interface->first_protocol; + while (protocol != NULL) { + net_datalink_protocol *next = protocol->next; + protocol->module->uninit_protocol(protocol); + + protocol = next; + } + + interface->first_protocol = NULL; + interface->first_info = NULL; +} + + +status_t +get_domain_datalink_protocols(net_interface *_interface) +{ + struct net_interface_private *interface = (net_interface_private *)_interface; + struct chain *chain; + + { + BenaphoreLocker locker(&sChainLock); + + chain = chain::Lookup(sDatalinkProtocolChains, interface->domain->family, + interface->device_interface->device->type, 0); + if (chain == NULL) + return EAFNOSUPPORT; + } + + // create net_protocol objects for the protocols in the chain + + status_t status = chain->Acquire(); + if (status < B_OK) + return status; + + net_datalink_protocol *last = NULL; + + for (int32 i = 0; chain->infos[i] != NULL; i++) { + net_datalink_protocol *protocol; + status_t status = ((net_datalink_protocol_module_info *)chain->infos[i])->init_protocol( + interface, &protocol); + if (status < B_OK) { + // free protocols we already initialized + uninit_domain_datalink_protocols(interface); + chain->Release(); + return status; + } + + protocol->module = (net_datalink_protocol_module_info *)chain->infos[i]; + protocol->interface = interface; + protocol->next = NULL; + + if (last == NULL) { + interface->first_protocol = protocol; + interface->first_info = protocol->module; + } else + last->next = protocol; + + last = protocol; + } + + return B_OK; +} + + +status_t +put_domain_datalink_protocols(net_interface *_interface) +{ + struct net_interface_private *interface = (net_interface_private *)_interface; + struct chain *chain; + + { + BenaphoreLocker locker(&sChainLock); + + chain = chain::Lookup(sDatalinkProtocolChains, interface->domain->family, + interface->device_interface->device->type, 0); + if (chain == NULL) + return B_ERROR; + } + + uninit_domain_datalink_protocols(interface); + chain->Release(); + return B_OK; +} + + +status_t +get_domain_receiving_protocol(net_domain *_domain, uint32 type, + net_protocol_module_info **_module) +{ + struct net_domain_private *domain = (net_domain_private *)_domain; + struct chain *chain; + + TRACE(("get_domain_receiving_protocol(family %d, type %lu)\n", domain->family, type)); + { + BenaphoreLocker locker(&sChainLock); + + chain = chain::Lookup(sReceivingProtocolChains, domain->family, + type, 0); + if (chain == NULL) + return EAFNOSUPPORT; + } + + status_t status = chain->Acquire(); + if (status < B_OK) + return status; + + *_module = (net_protocol_module_info *)chain->infos[0]; + return B_OK; +} + + +status_t +put_domain_receiving_protocol(net_domain *_domain, uint32 type) +{ + struct net_domain_private *domain = (net_domain_private *)_domain; + struct chain *chain; + + { + BenaphoreLocker locker(&sChainLock); + + chain = chain::Lookup(sReceivingProtocolChains, domain->family, + type, 0); + if (chain == NULL) + return B_ERROR; + } + + chain->Release(); + return B_OK; +} + + +status_t +register_domain_protocols(int family, int type, int protocol, ...) +{ + if (type == SOCK_RAW) { + // in SOCK_RAW mode, we ignore the protocol information + protocol = 0; + } + + BenaphoreLocker locker(&sChainLock); + + struct chain *chain = chain::Lookup(sProtocolChains, family, type, protocol); + if (chain != NULL) + return B_OK; + + va_list modules; + va_start(modules, protocol); + + chain = chain::Add(sProtocolChains, family, type, protocol, modules); + + va_end(modules); + + if (chain == NULL) + return B_NO_MEMORY; + + return B_OK; +} + + +status_t +register_domain_datalink_protocols(int family, int type, ...) +{ + TRACE(("register_domain_datalink_protocol(%d.%d)\n", family, type)); + BenaphoreLocker locker(&sChainLock); + + struct chain *chain = chain::Lookup(sDatalinkProtocolChains, family, type, 0); + if (chain != NULL) + return B_OK; + + va_list modules; + va_start(modules, type); + + chain = chain::Add(sDatalinkProtocolChains, family, type, 0, modules); + + va_end(modules); + + if (chain == NULL) + return B_NO_MEMORY; + + // Add datalink interface protocol as the last protocol in the chain; it's name + // stays unset, so that it won't be part of the release/acquire process. + + uint32 count = 0; + while (chain->modules[count] != NULL) { + count++; + } + + chain->infos[count] = (module_info *)&gDatalinkInterfaceProtocolModule; + return B_OK; +} + + +static status_t +register_domain_receiving_protocol(int family, int type, const char *moduleName) +{ + TRACE(("register_domain_receiving_protocol(%d.%d, %s)\n", family, type, + moduleName)); + + BenaphoreLocker locker(&sChainLock); + + struct chain *chain = chain::Lookup(sReceivingProtocolChains, family, type, 0); + if (chain != NULL) + return B_OK; + + chain = chain::Add(sReceivingProtocolChains, family, type, 0, moduleName, NULL); + if (chain == NULL) + return B_NO_MEMORY; + + return B_OK; +} + + +static void +scan_modules(const char *path) +{ + void *cookie = open_module_list(path); + if (cookie == NULL) + return; + + while (true) { + char name[B_FILE_NAME_LENGTH]; + size_t length = sizeof(name); + if (read_next_module_name(cookie, name, &length) != B_OK) + break; + + TRACE(("scan %s\n", name)); + + module_info *module; + if (get_module(name, &module) == B_OK) { + // we don't need the module right now, but we give it a chance + // to register itself + put_module(name); + } + } +} + + +static status_t +init_stack() +{ + status_t status = init_domains(); + if (status < B_OK) + return status; + + status = init_interfaces(); + if (status < B_OK) + goto err1; + + status = init_timers(); + if (status < B_OK) + goto err2; + + if (benaphore_init(&sChainLock, "net chains") < B_OK) + goto err3; + if (benaphore_init(&sInitializeChainLock, "net intialize chains") < B_OK) + goto err4; + + sFamilies = hash_init(10, offsetof(struct family, next), + &family::Compare, &family::Hash); + if (sFamilies == NULL) { + status = B_NO_MEMORY; + goto err5; + } + + sProtocolChains = hash_init(10, offsetof(struct chain, next), + &chain::Compare, &chain::Hash); + if (sProtocolChains == NULL) { + status = B_NO_MEMORY; + goto err6; + } + + sDatalinkProtocolChains = hash_init(10, offsetof(struct chain, next), + &chain::Compare, &chain::Hash); + if (sDatalinkProtocolChains == NULL) { + status = B_NO_MEMORY; + goto err7; + } + + sReceivingProtocolChains = hash_init(10, offsetof(struct chain, next), + &chain::Compare, &chain::Hash); + if (sReceivingProtocolChains == NULL) { + status = B_NO_MEMORY; + goto err8; + } + + sInitialized = true; + + link_init(); + scan_modules("network/protocols"); + scan_modules("network/datalink_protocols"); + + // TODO: for now! + register_domain_datalink_protocols(AF_INET, IFT_LOOP, NULL); + register_domain_datalink_protocols(AF_INET, IFT_ETHER, + "network/datalink_protocols/arp/v1", + "network/datalink_protocols/ethernet_frame/v1", + NULL); + + return B_OK; + +err8: + hash_uninit(sDatalinkProtocolChains); +err7: + hash_uninit(sProtocolChains); +err6: + hash_uninit(sFamilies); +err5: + benaphore_destroy(&sInitializeChainLock); +err4: + benaphore_destroy(&sChainLock); +err3: + uninit_timers(); +err2: + uninit_interfaces(); +err1: + uninit_domains(); + return status; +} + + +status_t +uninit_stack() +{ + TRACE(("Unloading network stack\n")); + + uninit_timers(); + uninit_interfaces(); + uninit_domains(); + + benaphore_destroy(&sChainLock); + benaphore_destroy(&sInitializeChainLock); + + // remove chains and families + + chain::DeleteChains(sProtocolChains); + chain::DeleteChains(sDatalinkProtocolChains); + chain::DeleteChains(sReceivingProtocolChains); + + uint32 cookie = 0; + while (true) { + struct family *family = (struct family *)hash_remove_first(sFamilies, &cookie); + if (family == NULL) + break; + + delete family; + } + + hash_uninit(sProtocolChains); + hash_uninit(sDatalinkProtocolChains); + hash_uninit(sReceivingProtocolChains); + hash_uninit(sFamilies); + + return B_OK; +} + + +static status_t +starter_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + return init_stack(); + case B_MODULE_UNINIT: + return uninit_stack(); + + default: + return B_ERROR; + } +} + + +static status_t +stack_std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + return sInitialized ? B_OK : B_BUSY; + case B_MODULE_UNINIT: + return B_OK; + + default: + return B_ERROR; + } +} + + +static net_stack_module_info sNetStackModule = { + { + NET_STACK_MODULE_NAME, + 0, + stack_std_ops + }, + register_domain, + unregister_domain, + get_domain, + + register_domain_protocols, + register_domain_datalink_protocols, + register_domain_receiving_protocol, + + get_domain_receiving_protocol, + put_domain_receiving_protocol, + + register_device_deframer, + unregister_device_deframer, + register_domain_device_handler, + register_device_handler, + unregister_device_handler, + register_device_monitor, + unregister_device_monitor, + device_removed, + + checksum, + + init_fifo, + uninit_fifo, + fifo_enqueue_buffer, + fifo_dequeue_buffer, + clear_fifo, + + init_timer, + set_timer, +}; + +static module_info sNetStarterModule = { + NET_STARTER_MODULE_NAME, + 0, + starter_std_ops +}; + +module_info *modules[] = { + (module_info *)&sNetStackModule, + (module_info *)&sNetStarterModule, + (module_info *)&gNetBufferModule, + (module_info *)&gNetSocketModule, + (module_info *)&gNetDatalinkModule, + (module_info *)&gLinkModule, + NULL +}; diff --git a/src/add-ons/kernel/network/stack/stack_private.h b/src/add-ons/kernel/network/stack/stack_private.h new file mode 100644 index 0000000000..5e2e906199 --- /dev/null +++ b/src/add-ons/kernel/network/stack/stack_private.h @@ -0,0 +1,34 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ +#ifndef STACK_PRIVATE_H +#define STACK_PRIVATE_H + + +#include +#include +#include +#include +#include + + +#define NET_STARTER_MODULE_NAME "network/stack/starter/v1" + +extern net_buffer_module_info gNetBufferModule; +extern net_socket_module_info gNetSocketModule; +extern net_datalink_module_info gNetDatalinkModule; +extern net_datalink_protocol_module_info gDatalinkInterfaceProtocolModule; + +// stack.cpp +status_t register_domain_datalink_protocols(int family, int type, ...); +status_t register_domain_protocols(int family, int type, int protocol, ...); +status_t get_domain_protocols(net_socket *socket); +status_t put_domain_protocols(net_socket *socket); +status_t get_domain_datalink_protocols(net_interface *interface); +status_t put_domain_datalink_protocols(net_interface *interface); + +#endif // STACK_PRIVATE_H diff --git a/src/add-ons/kernel/network/stack/utility.cpp b/src/add-ons/kernel/network/stack/utility.cpp new file mode 100644 index 0000000000..e769d85abb --- /dev/null +++ b/src/add-ons/kernel/network/stack/utility.cpp @@ -0,0 +1,348 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ + + +#include "stack_private.h" +#include "utility.h" + +#include +#include + +#include +#include + + +static struct list sTimers; +static benaphore sTimerLock; +static sem_id sTimerWaitSem; +static thread_id sTimerThread; + + +uint16 +compute_checksum(uint8 *_buffer, size_t length) +{ + uint16 *buffer = (uint16 *)_buffer; + uint32 sum = 0; + + // TODO: unfold loop for speed + // TODO: write processor dependent version for speed + while (length >= 2) { + sum += *buffer++; + length -= 2; + } + + if (length) { + // give the last byte it's proper endian-aware treatment +#if B_HOST_IS_LENDIAN + sum += *(uint8 *)buffer; +#else + uint8 ordered[2]; + ordered[0] = *(uint8 *)buffer; + ordered[1] = 0; + sum += *(uint16 *)ordered; +#endif + } + + while (sum >> 16) { + sum = (sum & 0xffff) + (sum >> 16); + } + + return sum; +} + + +uint16 +checksum(uint8 *buffer, size_t length) +{ + return ~compute_checksum(buffer, length); +} + + +// #pragma mark - FIFOs + + +status_t +init_fifo(net_fifo *fifo, const char *name, size_t maxBytes) +{ + status_t status = benaphore_init(&fifo->lock, name); + if (status < B_OK) + return status; + + fifo->notify = create_sem(1, name); + if (fifo->notify < B_OK) { + benaphore_destroy(&fifo->lock); + return fifo->notify; + } + + fifo->max_bytes = maxBytes; + fifo->current_bytes = 0; + fifo->waiting = 0; + + list_init(&fifo->buffers); + + return B_OK; +} + + +void +uninit_fifo(net_fifo *fifo) +{ + clear_fifo(fifo); + + benaphore_destroy(&fifo->lock); + delete_sem(fifo->notify); +} + + +status_t +fifo_enqueue_buffer(net_fifo *fifo, net_buffer *buffer) +{ + BenaphoreLocker locker(fifo->lock); + + if (fifo->max_bytes > 0 && fifo->current_bytes + buffer->size > fifo->max_bytes) + return ENOBUFS; + + list_add_item(&fifo->buffers, buffer); + fifo->current_bytes += buffer->size; + + if (fifo->waiting > 0) { + fifo->waiting--; + release_sem_etc(fifo->notify, 1, B_DO_NOT_RESCHEDULE); + // we still hold the benaphore lock, so it makes no sense + // to reschedule after having released the sync semaphore + } + + return B_OK; +} + + +/*! + Gets the first buffer from the FIFO. If there is no buffer, it + will wait depending on the \a flags and \a timeout. + The following flags are supported (the rest is ignored): + MSG_DONTWAIT - ignores the timeout and never wait for a buffer; if your + socket is O_NONBLOCK, you should specify this flag. A \a timeout of + zero is equivalent to this flag, though. + MSG_PEEK - returns a clone of the buffer and keep the original + in the FIFO. +*/ +ssize_t +fifo_dequeue_buffer(net_fifo *fifo, uint32 flags, bigtime_t timeout, + net_buffer **_buffer) +{ + benaphore_lock(&fifo->lock); + bool dontWait = (flags & MSG_DONTWAIT) != 0 || timeout == 0; + status_t status; + + while (true) { + net_buffer *buffer = (net_buffer *)list_get_first_item(&fifo->buffers); + if (buffer != NULL) { + if ((flags & MSG_PEEK) != 0) { + // we need to clone the buffer for inspection; we can't give a + // handle to a buffer that we're still using + buffer = gNetBufferModule.clone(buffer, false); + if (buffer == NULL) { + status = B_NO_MEMORY; + break; + } + } else + list_remove_item(&fifo->buffers, buffer); + + *_buffer = buffer; + status = B_OK; + break; + } + + if (!dontWait) + fifo->waiting++; + + // we need to wait until a new buffer becomes available + benaphore_unlock(&fifo->lock); + + if (dontWait) + return B_WOULD_BLOCK; + + status = acquire_sem_etc(fifo->notify, 1, + B_CAN_INTERRUPT | B_RELATIVE_TIMEOUT, timeout); + if (status < B_OK) + return status; + + // try again + benaphore_lock(&fifo->lock); + } + + if ((flags & MSG_PEEK) != 0 && fifo->waiting > 0) { + // another thread is waiting for data, since we didn't eat the + // buffer, it gets it + fifo->waiting--; + release_sem_etc(fifo->notify, 1, B_DO_NOT_RESCHEDULE); + } + + benaphore_unlock(&fifo->lock); + return status; +} + + +status_t +clear_fifo(net_fifo *fifo) +{ + BenaphoreLocker locker(fifo->lock); + + while (true) { + net_buffer *buffer = (net_buffer *)list_remove_head_item(&fifo->buffers); + if (buffer == NULL) + break; + + gNetBufferModule.free(buffer); + } + + fifo->current_bytes = 0; + return B_OK; +} + + +// #pragma mark - Timer + + +static status_t +timer_thread(void * /*data*/) +{ + status_t status = B_OK; + + do { + bigtime_t timeout = B_INFINITE_TIMEOUT; + + if (status == B_TIMED_OUT || status == B_OK) { + // scan timers for new timeout and/or execute a timer + if (benaphore_lock(&sTimerLock) < B_OK) + return B_OK; + + struct net_timer *timer = NULL; + while (true) { + timer = (net_timer *)list_get_next_item(&sTimers, timer); + if (timer == NULL) + break; + + if (timer->due < system_time()) { + // execute timer + list_remove_item(&sTimers, timer); + timer->due = -1; + + benaphore_unlock(&sTimerLock); + timer->hook(timer, timer->data); + benaphore_lock(&sTimerLock); + + timer = NULL; + // restart scanning as we unlocked the list + } else { + // calculate new timeout + if (timer->due < timeout) + timeout = timer->due; + } + } + + benaphore_unlock(&sTimerLock); + } + + status = acquire_sem_etc(sTimerWaitSem, 1, B_ABSOLUTE_TIMEOUT, timeout); + // the wait sem normally can't be acquired, so we + // have to look at the status value the call returns: + // + // B_OK - a new timer has been added or canceled + // B_TIMED_OUT - look for timers to be executed + // B_BAD_SEM_ID - we are asked to quit + } while (status != B_BAD_SEM_ID); + + return B_OK; +} + + +/*! + Initializes a timer before use. You can also use this function to change + a timer later on, but make sure you have canceled it before using set_timer(). +*/ +void +init_timer(net_timer *timer, net_timer_func hook, void *data) +{ + timer->hook = hook; + timer->data = data; + timer->due = 0; +} + + +/*! + Sets or cancels a timer. When the \a delay is below zero, an eventually running + timer is canceled, if not, it is scheduled to be executed after the specified + \a delay. + You need to have initialized the timer before calling this function. + In case you need to change a running timer, you have to cancel it first, before + making any changes. +*/ +void +set_timer(net_timer *timer, bigtime_t delay) +{ + BenaphoreLocker locker(sTimerLock); + + if (timer->due > 0) { + // this timer is already scheduled, cancel it + list_remove_item(&sTimers, timer); + } + + if (delay >= 0) { + // add this timer + timer->due = system_time() + delay; + list_add_item(&sTimers, timer); + } + + // notify timer about the change + release_sem(sTimerWaitSem); +} + + +status_t +init_timers(void) +{ + list_init(&sTimers); + + status_t status = benaphore_init(&sTimerLock, "net timer"); + if (status < B_OK) + return status; + + sTimerWaitSem = create_sem(0, "net timer wait"); + if (sTimerWaitSem < B_OK) { + status = sTimerWaitSem; + goto err1; + } + + sTimerThread = spawn_kernel_thread(timer_thread, "net timer", + B_NORMAL_PRIORITY, NULL); + if (sTimerThread < B_OK) { + status = sTimerThread; + goto err2; + } + + return resume_thread(sTimerThread); + +err1: + benaphore_destroy(&sTimerLock); +err2: + delete_sem(sTimerWaitSem); + return status; +} + + +void +uninit_timers(void) +{ + benaphore_destroy(&sTimerLock); + delete_sem(sTimerWaitSem); + + status_t status; + wait_for_thread(sTimerThread, &status); +} + diff --git a/src/add-ons/kernel/network/stack/utility.h b/src/add-ons/kernel/network/stack/utility.h new file mode 100644 index 0000000000..003a40e043 --- /dev/null +++ b/src/add-ons/kernel/network/stack/utility.h @@ -0,0 +1,33 @@ +/* + * Copyright 2006, Haiku, Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Axel Dörfler, axeld@pinc-software.de + */ +#ifndef NET_UTILITY_H +#define NET_UTILITY_H + + +#include + + +// checksums +uint16 compute_checksum(uint8 *_buffer, size_t length); +uint16 checksum(uint8 *buffer, size_t length); + +// fifos +status_t init_fifo(net_fifo *fifo, const char *name, size_t maxBytes); +void uninit_fifo(net_fifo *fifo); +status_t fifo_enqueue_buffer(net_fifo *fifo, struct net_buffer *buffer); +ssize_t fifo_dequeue_buffer(net_fifo *fifo, uint32 flags, bigtime_t timeout, + struct net_buffer **_buffer); +status_t clear_fifo(net_fifo *fifo); + +// timer +void init_timer(net_timer *timer, net_timer_func hook, void *data); +void set_timer(net_timer *timer, bigtime_t delay); +status_t init_timers(void); +void uninit_timers(void); + +#endif // NET_UTILITY_H diff --git a/src/kits/network/Jamfile b/src/kits/network/Jamfile new file mode 100644 index 0000000000..9e9948fc20 --- /dev/null +++ b/src/kits/network/Jamfile @@ -0,0 +1,20 @@ +SubDir HAIKU_TOP src kits network ; + +UsePrivateHeaders net ; + +SharedLibrary libnetwork.so : + interfaces.cpp + socket.cpp + : + dns_dst.o + dns_inet.o + dns_irs.o + dns_isc.o + dns_nameser.o + dns_resolv.o + dns_private.o + + be +; + +SubInclude HAIKU_TOP src kits network dns ;