diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06dc7f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +.libs +.deps +Makefile.in +glob:Makefile +aclocal.m4 +autom4te.cache +config.h.in +config.h +autotools/ +glob:configure +missing +glob:*.pc +glob:*.lo +glob:*.la +glob:*.o +stamp-h1 +depcomp +install-sh +libtool +ltmain.sh +m4 +doc/html +configure diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..59878ff --- /dev/null +++ b/AUTHORS @@ -0,0 +1,2 @@ +Copyright © 2011, Nicolas Pouillon, Freebox SAS + Contact, questions: sdk@freebox.fr diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..aaefd34 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,4 @@ +2011-10-06 Nicolas Pouillon + + * First public version + diff --git a/LICENSE.TXT b/LICENSE.TXT new file mode 100644 index 0000000..176fcb4 --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,25 @@ +Copyright © 2011 Freebox SA. 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. + +This software is provided ``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 author 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. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..ac84b4e --- /dev/null +++ b/Makefile.am @@ -0,0 +1,9 @@ + +ACLOCAL_AMFLAGS = -I autotools + +noinst_DATA = ChangeLog + +SUBDIRS=include src test doc + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = rudp.pc diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..8dbe66e --- /dev/null +++ b/configure.ac @@ -0,0 +1,55 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.61]) +AC_INIT([librudp], [0.3], [sdk@freebox.fr]) +AC_CONFIG_MACRO_DIR([autotools]) +AC_CONFIG_AUX_DIR([autotools]) + +AC_CONFIG_SRCDIR([src/rudp.c]) +AC_CONFIG_HEADERS([config.h]) + +AM_INIT_AUTOMAKE([1.10 foreign dist-bzip2]) + +dnl AM_SILENT_RULES([yes]) + +# Checks for programs. +AC_PROG_CC + +if test "x$GCC" = "xyes"; then + GCC_CFLAGS="-Wall -g -Wstrict-prototypes -Wmissing-prototypes -fvisibility=hidden" +fi +AC_SUBST(GCC_CFLAGS) + +# Initialize libtool +LT_PREREQ([2.2]) +LT_INIT + +# Checks for header files. +AC_CHECK_HEADERS([stdint.h stdlib.h]) + +AC_PATH_PROG([mkdoc], [mkdoc]) +AM_CONDITIONAL(HAVE_MKDOC, test x$mkdoc != x) + +# Checks for typedefs, structures, and compiler characteristics. +AC_C_INLINE +AC_TYPE_UINT32_T + +AM_PROG_LIBTOOL + +# Checks for libraries. +PKG_CHECK_MODULES(ELA, ela) + +# Checks for library functions. +AC_FUNC_MALLOC + +AC_CONFIG_FILES([ + rudp.pc + Makefile + doc/Makefile + include/Makefile + include/rudp/Makefile + src/Makefile + test/Makefile + ]) +AC_OUTPUT diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000..278eb83 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,19 @@ + +if HAVE_MKDOC + +html-local: + $(mkdoc) \ + --conf-file $(srcdir)/mkdoc.conf \ + --doc-path $(srcdir)/. \ + -I $(top_srcdir)/include \ + rudp/compiler.h rudp/error.h rudp/time.h \ + rudp/list.h rudp/rudp.h rudp/packet.h \ + rudp/address.h \ + rudp/endpoint.h rudp/peer.h rudp/client.h \ + rudp/server.h + + +clean-local: + -rm -r html + +endif diff --git a/doc/html_title.tmpl b/doc/html_title.tmpl new file mode 100644 index 0000000..ccad814 --- /dev/null +++ b/doc/html_title.tmpl @@ -0,0 +1,6 @@ +

@@doc_title@@

+

@@doc_subtitle@@

+

© Freebox, + @@doc_author@@, + @@doc_date@@ +

diff --git a/doc/mkdoc.conf b/doc/mkdoc.conf new file mode 100644 index 0000000..06ca10c --- /dev/null +++ b/doc/mkdoc.conf @@ -0,0 +1,13 @@ +@setopt output_filename {liblea} +@setopt doc_title {Reliable UDP transport library} +@setopt doc_author {Nicolas Pouillon} +@setopt show_type_keyword {1} +@setopt function_overloading {0} +@setopt hide_internal +@setopt hide_experimental +@setopt output_path {.} +@setopt html_single_js {1} +@setopt html_embed_css {1} +@setopt html_css_optimize {1} +@setopt titled_refs {1} +@setopt top_file {top.mkdoc} diff --git a/doc/modules.mkdoc b/doc/modules.mkdoc new file mode 100644 index 0000000..e69de29 diff --git a/doc/template.mkdoc b/doc/template.mkdoc new file mode 100644 index 0000000..2514875 --- /dev/null +++ b/doc/template.mkdoc @@ -0,0 +1,126 @@ +@c +@c Default template.mkdoc file for C projects copied by mkdoc +@c + +@c ===== member doc ===== + +@macro _member_doc id + @insert {@id@} decl_warn_sentence + @insert {@id@} decl_location_sentence + @insert {@id@} decl_homonyms_sentence + @insert {@id@} decl_cpp_condition_sentence + @insert {@id@} decl_involved_macros_sentence + + @insert {@id@} decl_inline_doc + @insert {@id@} decl_content + @insert {@id@} see_related_typedefs + + @insert {No documentation available} on_empty_section +@end macro + +@c ===== compound members detail ===== + +@macro _compound_details id + + @foreach mid {typedef,enum,struct,function,variable,macro} {@id@} {1} + @section {} {@mid@} + @invoke {@mid@} _member_doc + @end section + @end foreach + +@end macro + +@c ===== compound documentation macro for internal use ===== + +@macro _compound_doc id + + @c short member doc with link to detailed description + @insert {@id@} decl_short_desc @exref {Description} {More} + + @ifsymtype {@id@} {module} + @section e {Related headers} + @insert {header} {1} {@id@} compound_table + @end section + @end if + + @ifsymtype {@id@} {header} + @section e {Header inclusion} + @c @insert {@id@} {D} inclusion_diagram + + @c @section e {Included files} + @insert {@id@} include_list + @c @end section + + @c @section e {Including files} + @c @insert {@id@} included_by_list + @c @end section + @end section + @end if + + @section e {Members} + @insert {@id@} compound_member_list + @end section + + @section e {Description} + @insert {@id@} decl_inline_doc + @end section + +@end macro + +@c ===== compound section macro for internal use ===== + +@macro _compound_section type id name + + @section P {@name@ @type@ reference} {@id@} + @invoke {@id@} _compound_doc + + @c change this to specify where member details must be written + @c @ifsymtype {@id@} {namespace} + @ifsymtype {@id@} {header} + @section e {Members detail} + @invoke {@id@} _compound_details + @end section + @end if + + @end section + +@end macro + +@c ===== compound section with documentation and member details ===== + +@macro member_section name + + @lookup id {@name@} + @section {} {@id@} + @invoke {@id@} _member_doc + @end section + @end lookup + +@end macro + +@c ===== compound section with documentation and member details ===== + +@macro compound_section name + + @lookup id mname mtype {@name@} + @invoke {@mtype@} {@id@} {@mname@} _compound_section + @end lookup + +@end macro + +@c ===== global scope section with documentation and member details ===== + +@macro global_scope_doc + @invoke {::} _compound_doc +@end macro + +@c ===== documentation sections for all compounds of given types ===== + +@macro all_compound_sections types scope + + @foreach id name type {@types@} {@scope@} {0} + @invoke {@type@} {@id@} {@name@} _compound_section + @end foreach + +@end macro + diff --git a/doc/top.mkdoc b/doc/top.mkdoc new file mode 100644 index 0000000..c99da7b --- /dev/null +++ b/doc/top.mkdoc @@ -0,0 +1,390 @@ +@include template.mkdoc + +@c +@c Modules definitions +@c + +@moduledef{Errors} + @short Common error representation + @order 104 +@end moduledef + +@moduledef{Master state} + @short Master library state + @order 105 +@end moduledef + +@moduledef{Time} + @short Abstract time reference + @order 103 +@end moduledef + +@moduledef{Address} + @short Abstract network address representation + @order 102 +@end moduledef + +@moduledef{Packet} + @short Packet datatypes + @order 101 +@end moduledef + +@moduledef{Endpoint} + @short Input/output endpoint + @order 100 +@end moduledef + +@moduledef{Peer} + @short Peer representation + @order 99 +@end moduledef + +@moduledef{Client} + @short Client implementation + @order 98 +@end moduledef + +@moduledef{Server} + @short Server implementation + @order 98 +@end moduledef + +@c @moduledef{internal} +@c @short Internal structures +@c @order 42 +@c @end moduledef + +@insert title + +@c +@c Plain Documentation +@c + +@section H {Overview} + Rudp is a library aimed at adding reliability features to UDP. + + @list + @item Deliver packets in order + @item Mark certain packets as reliable, retransmit them + @item Drop unordered packets + @item Implement server and client models + @end list +@end section + +@section H {Table of contents} + @insert toc +@end section + +@section HTP {Objects management} + @section {Life cycle} + @label {olc} + Complex objects of the library have a common live cycle: + @list + @item Allocation is left to the user. + @item Initialization is done with + @tt{rudp_}@em{}@tt{_init}, it initializes internal + state and may allocate private data. An initialized object is + valid for usage, and can be reused. + @item Deinitialization is done with + @tt{rudp_}@em{}@tt{_deinit}. This function must be + called before memory relase. + @item Memory release is left to the user. + @end list + @end section + + @section {Callbacks} + When Librudp objects have to hand data back to the application + code, they use a callback mechanism. Callbacks are registered on + object initialization. + + For instance with a server, an user code could be: + + @code C +static +void my_handle_packet( + struct rudp_server *server, + struct rudp_peer *peer, + struct rudp_packet_chain *packet) +{ + /* Do something with the packet */ +} + +static +void my_link_info( + struct rudp_server *server, + struct rudp_peer *peer, + struct rudp_link_info *info) +{ + /* Do something with the link info */ +} + +static +void my_peer_new( + struct rudp_server *server, + struct rudp_peer *peer) +{ + /* Do something with the link peer */ +} + +static +void my_peer_dropped( + struct rudp_server *server, + struct rudp_peer *peer) +{ + /* Do something with the link peer */ +} + +static const struct rudp_server_handler my_server_handler = +{ + .handle_packet = my_handle_packet, + .link_info = my_link_info, + .peer_dropped = my_peer_dropped, + .peer_new = my_peer_new, +}; + +/* Server initialization */ +void my_server_init() +{ + struct rudp_server *server = malloc(sizeof(*server)); + struct ela_el *el = ...; + + rudp_error_t err = rudp_server_init(server, el, &my_server_handler); +} + @end code + @end section + + @section {Memory management} + Memory allocation for the objects can be done as user likes most. + User may embed rudp objects it allocates itself in a bigger + structure like: + + @code C +struct my_server +{ + struct rudp_server server; + struct my_user_data; +}; + +static void my_handle_packet( + struct rudp_server *_server, + struct rudp_peer *peer, + struct rudp_packet_chain *packet) +{ + struct my_server *server = (struct my_server *)_server; + + /* Do something with server->my_user_data and packet */ +} + +/* Initialize the server */ + +void my_server_init() +{ + struct my_server *server; + + /* allocate space for server */ + server = malloc(sizeof(*server)); + + /* check allocation error */ + + /* find an ELA implementation */ + struct ela_el *el = ...; + + rudp_error_t err = + rudp_server_init(&server->server, el, &my_server_handler); + + /* handle errors, be happy */ +} + @end code + @end section +@end section + +@section HTP {User API} + @section {Master state} + @insert {@rudp/rudp.h} decl_inline_doc + @end section + + @section {Utilities} + @section {Time} + @insert {@rudp/time.h} decl_inline_doc + @end section + + @section {Erros} + @insert {@rudp/error.h} decl_inline_doc + @end section + + @section {Addresses} + @insert {@rudp/address.h} decl_inline_doc + @end section + @end section + + @section {Client/server model} + @section {Client} + @insert {@rudp/client.h} decl_inline_doc + @end section + + @section {Server} + @insert {@rudp/server.h} decl_inline_doc + @end section + @end section +@end section + +@section HTP {Internal library implementation} + @section {Peer} + @insert {@rudp/peer.h} decl_inline_doc + @end section + + @section {Endpoint} + @insert {@rudp/endpoint.h} decl_inline_doc + @end section +@end section + +@section HTP {Protocol} + @section {Network protocol} + @section {Features} + Rudp has the following features: + @list + @item Strict in-order delivery of packets. + @item Packets may be reliable or not, this is a per-packet + setting. + @item Packets cannot be delivered to the application twice. + @end list + @end section + + @section {Packet header} + @table 4 + @item Offset (bit) @item Size (bits) @item Name @item Description + @item 0 @item 8 @item CMD @item Command + @item 8 @item 5 @item RES @item Reserved + @item 13 @item 1 @item RET @item Retransmitted flag + @item 14 @item 1 @item ACK @item Acknowledge flag + @item 15 @item 1 @item REL @item Reliable flag + @item 16 @item 16 @item ACK_SEQ @item Acknowledge sequence number + @item 32 @item 16 @item REL_SEQ @item Reliable sequence number + @item 48 @item 16 @item UNR_SEQ @item Unreliable sequence number + @end table + + On each transmitted packet, REL is optional. If set, packet is + reliable and must be acknowledged by peer. + + A reliable packet must have a REL_SEQ immediately after the + previous reliable packet sent. UNR_SEQ must be 0. + + Acknowledge of a reliable packet may be piggybacked in any + packet going the other way (reliable or not). It is present when + ACK is set. Acknowledged sequence number is in ACK_SEQ. + + An unreliable packet has no REL flag set. It has the same + REL_SEQ number as the latest reliable packet transmitted, and + is assigned next UNR_SEQ number. + + RET flag is present for reliable packets that were already sent + on the wire at least once before. + @end section + + @section {Retransmits} + Receiver may loose any unreliable packet harmlessly (reason may + be loss of packet, unsequenced packet, etc.). + + Reliable packets may be lost any time, but must be retransmitted + by the Sender until acknowledged. + + If a Receiver gets an out-of-sequence reliable packet, it must + drop it until the transmitter resends the expected in-sequence + packet. + + If a Receiver gets a reliable packet twice (or more), it must + acknowledge its sequence number again, but must not interpret + the contents again. + @end section + + @section {Connection handshake} + Low-level protocol is peer-to-peer. As UDP is not connected, one + of the two involved peers must send a packet first. Any of the + two can do it. This packet is expected to be containing a @ref + RUDP_CMD_CONN_REQ command with reliable sequence number to a + random value.. This packet should be reliable, i.e. imply + retransmits in the sender's code. + + Peer is expected to answer with an unreliable packet containing + both an ACK and a @ref RUDP_CMD_CONN_RSP packet. Its sequence + number must be random as well. If the + response packet is lost in transit, handshake will fail and must + be started over. This is intended. + + After these two packets are exchanged, connection is established. + Each peer takes the sequence number it received in first packet + as granted. This is only true for first packet. + + On an established connection, 3 main types of packets may transit: + @list + @item Ping/Pong packets + @item Noop packets + @item Data packets + @end list + @end section + + @section {Packet types} + @section {Noop} + Noop packets have the @ref RUDP_CMD_NOOP command value. They + serve no applicative purpose and should contain no data. + Nevertheless, Noop packets can be reliably sent (forcing it to + be acknowledged), and conveys an acknowledge sequence number. + + This packet type is well suited for feeding acknowledges. + @end section + + @section {Ping/Pong} + Ping packets have the @ref RUDP_CMD_PING command value. They + should be sent reliably, but do not necessarily imply a PONG + packet. An acknowledge is still expected though: + + Ping payload is optional and must be copied verbatim in the + matching Pong packet. Ping Sender (thus Pong receiver) can use + it for statistics purposes. + + If Ping packet had to be retransmitted by the sender to reach + its peer (see @ref #RUDP_OPT_RETRANSMITTED), it may contain + outdated information. Therefore, a peer is expected NOT to + send Pong packet marked retransmitted. Nevertheless, Peer + must always acknowledge the packet sequence. + + This way, Ping is actually an "echo" request. It makes a + payload go both ways. If the sender stack needs an + acknowledge, it must say so. + + Implementation detail: + Current peer implementation uses Ping to evaluate the link RTT + and check the link validity at the same time. It puts a + timestamp in the sent packet and waits for it to come back. + This behaviour may evolve in future revisions of the library. + @end section + + @section {Data} + Data packets have the @ref RUDP_CMD_APP (or any superior) + command value. They are sent reliably or not, depending on + user's needs. Their semantics are left to the user. + @end section + @end section + + @section {Packet C structure} + @insert {@rudp/packet.h} decl_inline_doc + @end section + + @end section +@end section + +@section HTP {Headers} + + @section {Header list} + @insert {header} compound_table + @end section + + @insert {header} all_compound_sections + + @section {All declarations} + @insert global_scope_doc + @end section + +@end section + +@c @insert global_scope_section diff --git a/include/Makefile.am b/include/Makefile.am new file mode 100644 index 0000000..1ce1ec2 --- /dev/null +++ b/include/Makefile.am @@ -0,0 +1,2 @@ + +SUBDIRS = rudp diff --git a/include/rudp/Makefile.am b/include/rudp/Makefile.am new file mode 100644 index 0000000..a27a5d1 --- /dev/null +++ b/include/rudp/Makefile.am @@ -0,0 +1,4 @@ + +pkgincludedir = $(includedir)/rudp +pkginclude_HEADERS = address.h client.h endpoint.h error.h list.h \ +packet.h peer.h rudp.h server.h time.h compiler.h diff --git a/include/rudp/address.h b/include/rudp/address.h new file mode 100644 index 0000000..435878e --- /dev/null +++ b/include/rudp/address.h @@ -0,0 +1,211 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_ADDRESS_H_ +/** @hidden */ +#define RUDP_ADDRESS_H_ + +/** + @file + @module {Address} + @short Internet address (IPv4/IPv6) abstraction, with resolving + + Addresses are abstracted in the library. Addresses are + represented through the @ref rudp_address. + + Addresses are objects with the same @xref {olc} {life cycle} as + most other complex objects of the library. Before use, address + structures must be initialized with @ref rudp_address_init, and + after use, they must be cleaned with @ref rudp_address_deinit. + + Addresses may be set 3 ways: + @list + @item with a constant IPv4 address (@ref rudp_address_set_ipv4) + @item with a constant IPv6 address (@ref rudp_address_set_ipv6) + @item with an hostname to lookup (@ref rudp_address_set_hostname) + @end list + + After the address is correctly setup, user code may use @ref + rudp_address_get to retrieve a @tt {struct sockaddr} containing + the address. + + In order to walk through the resolved addresses, user may call + @ref rudp_address_next. This loops through the available addresses + corresponding to an hostname. + */ + +#include +#include +#include +#include + +#include + +#define RUDP_NO_IPV4 2 +#define RUDP_NO_IPV6 1 +#define RUDP_IPV4_ONLY RUDP_NO_IPV6 +#define RUDP_IPV6_ONLY RUDP_NO_IPV4 +#define RUDP_IP_ANY 0 + +/** + @this is an abstract representation of an address. + + Addresses are objects with the same @xref {olc} {life cycle} as + most other complex objects of the library. Before use, address + structures must be initialized with @ref rudp_address_init, and + after use, they must be cleaned with @ref rudp_address_deinit. + + @hidecontent + */ +struct rudp_address +{ + /** Rudp context */ + struct rudp *rudp; + /** Low-level address structure */ + struct sockaddr_storage *addr; + /** High_level address structure */ + struct addrinfo *addrinfo; + /** Walk pointer */ + struct addrinfo *cur; + /** Hostname (if symbolic resolving) */ + char *hostname; + /** Flags about IP versions allowed in lookup */ + uint8_t allowed_ip_version; + /** Internal resolver state */ + uint8_t resolver_state; + /** Port associated with the address */ + uint16_t port; + char text[INET6_ADDRSTRLEN+6]; +}; + +/** + @this specifies a hostname to be resolved with system resolver, and + uses the results in turn. This can implement a round-roubin service + usage. + + @param addr The address structure + @param hostname Hostname to resolve + @param port Numeric target port (machine order) + @param ip_flags RUDP_IPV4_ONLY, RUDP_IPV6_ONLY or RUDP_IP_ANY + @returns 0 on success, RUDP_EBACKEND if resolution failed, + RUDP_ENOMEM if allocation failed + */ +RUDP_EXPORT +rudp_error_t rudp_address_set_hostname( + struct rudp_address *addr, + const char *hostname, + const uint16_t port, + uint32_t ip_flags); + +/** + @this gets a pointer to a textual representation of the address. + + As this function only takes a reference, this function has a cost + only on the first call, and then has a o(1) complexity and can be + called as often as needed. + + @param addr Address structure + @returns a valid constant string + */ +RUDP_EXPORT +const char *rudp_address_text(struct rudp_address *addr); + +/** + @this takes the next usable address in the resolver state. This + call only makes sense for addresses using @ref + rudp_address_set_hostname. + + @param addr Address to walk in + @returns 0 on success, RUDP_ENOADDR if resolution failed, + RUDP_EBACKEND on unhandled errors + */ +RUDP_EXPORT +rudp_error_t rudp_address_next( + struct rudp_address *addr); + +/** + @this gets a pointer to the socket() api-compatible + structure. Pointer is owner by the address structure and is valid + until next call to @tt rudp_address_set_* or @ref rudp_address_next. + + As this function only takes a reference, this function has a o(1) + complexity and can be called as often as needed. + + @param addr Address to get the sockaddr structure from + @param address (out) System-compatible address structure + @param size (out) Size of the address structure + @returns 0 on success, RUDP_ENOADDR if resolution failed, + RUDP_EBACKEND on unhandled errors + */ +RUDP_EXPORT +rudp_error_t rudp_address_get( + const struct rudp_address *addr, + const struct sockaddr_storage **address, + socklen_t *size); + +/** + @this specifies an IPv4 address and port + + @param addr The address structure + @param address IPv4 to use (usual @tt {struct in_addr} order) + @param port Numeric target port (machine order) + */ +RUDP_EXPORT +void rudp_address_set_ipv4( + struct rudp_address *addr, + const struct in_addr *address, + const uint16_t port); + +/** + @this specifies an IPv6 address and port + + @param addr The address structure + @param address IPv6 to use (usual @tt {struct in6_addr} order) + @param port Numeric target port (machine order) + */ +RUDP_EXPORT +void rudp_address_set_ipv6( + struct rudp_address *addr, + const struct in6_addr *address, + const uint16_t port); + +/** + @this initializes an address structure for future usage. This + function must be called before any other address-related function + on the structure. + + @param addr The address structure to initialize + @param rudp A valid rudp context + */ +RUDP_EXPORT +void rudp_address_init(struct rudp_address *addr, struct rudp *rudp); + +/** + @this releases all data internally referenced by an address + structure. + + @param addr The address structure to deinit + */ +RUDP_EXPORT +void rudp_address_deinit(struct rudp_address *addr); + +/** + @this compares the address against another address + + @param address Address structure to compare + @param addr System address to compare to + @returns 0 if they match, another value otherwise + */ +RUDP_EXPORT +int rudp_address_compare(const struct rudp_address *address, + const struct sockaddr_storage *addr); + +#endif diff --git a/include/rudp/client.h b/include/rudp/client.h new file mode 100644 index 0000000..1706c6b --- /dev/null +++ b/include/rudp/client.h @@ -0,0 +1,278 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_CLIENT_H_ +/** @hidden */ +#define RUDP_CLIENT_H_ + +/** + @file + @module {Client} + @short Client implementation + + Client connects to a server. As long as the connection is + established, data can be exchanged both ways, either reliably or + not. + + For connecting, It either uses an explicit constant IPv4 or IPv6 + address, or an hostname to resolve. + + Client contexts use the same @xref {olc} {life cycle} as other + complex objects of the library. Before use, client contexts must + be initialized with @ref rudp_client_init, and after use, they must + be cleaned with @ref rudp_client_deinit. + + An initialized but not yet connected Client context must have its + server address setup with any of the following functions: @ref + rudp_client_set_hostname, @ref rudp_client_set_ipv4 or @ref + rudp_client_set_ipv6. + + Once the server address is setup properly, Client context can be + connected with @ref rudp_client_connect. After successful + handshake with the server, the @ref rudp_client_handler::connected + handler is called back. + + Either because of a connection timeout or because of server + disconnection the @ref rudp_client_handler::server_lost handler + is called back. + + Anytime afer call to @ref rudp_client_connect and until the @ref + rudp_client_handler::server_lost handler is called, user may ask + for disconnection with @ref rudp_client_close. + + Sample usage: + @code + // assuming you have a valid rudp context here: + struct rudp *rudp; + + struct rudp_client client; + + rudp_client_init(&client, rudp, &my_client_handlers); + + // set the server address and connect + rudp_client_set_ipv4(&client, ...); + rudp_client_connect(&client); + + // run your event loop + + rudp_client_close(&client); + rudp_client_deinit(&client); + @end code + */ + +#include +#include +#include +#include +#include +#include +#include + +struct rudp_client; +struct rudp_link_info; +struct rudp_peer; + +/** + Client handler code callbacks + */ +struct rudp_client_handler +{ + /** + @this is called on packet reception. Packet is already decoded + and is ensured to be an application packet (@ref RUDP_CMD_APP + command or equivalent). + + Packet chain ownership is not given to handler, handler must + copy data and forget the chain afterwards. + + @param client Client context + @param command User command used + @param data Data buffer + @param len Useful data length + */ + void (*handle_packet)( + struct rudp_client *client, + int command, const void *data, size_t len); + + /** + @this is called anytime when link statistics are updated. + + @param client Client context + @param info Link quality updated information + */ + void (*link_info)(struct rudp_client *client, struct rudp_link_info *info); + /** + @this is called when client gets connected. When this handler + is called, client is guaranteed to have established a valid + connection to the server. + + Client will stay valid until @ref + rudp_client_handler::server_lost is called. + + @param client Client context + */ + void (*connected)(struct rudp_client *client); + + /** + @this is called when connection to the server drops, either + explicitely or because of a timeout. + + After this handler is called, client is left in the initialized + state, like after @ref rudp_client_close is called. Handler + code can either use @ref rudp_client_connect or @ref + rudp_client_deinit afterwards. + + @param client Client context + */ + void (*server_lost)(struct rudp_client *client); +}; + +/** + @this is a client context structure. User should not use its + fields directly. + + Clients are objects with the same @xref {olc} {life cycle} as + most other complex objects of the library. Before use, client + contexts must be initialized with @ref rudp_client_init, and after + use, they must be cleaned with @ref rudp_client_deinit. + + An initialized client context can try connection to a server with + @ref rudp_client_connect, and a connected client context can be + closed with @ref rudp_client_close. A closed client context stays + initialized. + + @hidecontent + */ +struct rudp_client +{ + const struct rudp_client_handler *handler; + struct rudp_peer peer; + struct rudp_endpoint endpoint; + struct rudp_address address; + struct rudp *rudp; + char connected; +}; + +struct rudp_peer; + +/** + @this initializes the client context. + + @param client Client unitialized context structure + @param rudp A valid rudp context + @param handler A client handler descriptor + + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_client_init( + struct rudp_client *client, + struct rudp *rudp, + const struct rudp_client_handler *handler); + +/** + @this tries to establish a connection with the server. Server + address can be set with any of the @ref rudp_client_set_hostname, + @ref rudp_client_set_ipv4 or @ref rudp_client_set_ipv6. + + @param client An initialized client context structure + + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_client_connect(struct rudp_client *client); + +/** + @this cleanly drops connection to the server and closes all + associated sockets. + + @param client A connected (or currently trying to connect) client + context structure + + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_client_close(struct rudp_client *client); + +/** + @this frees all internally allocated client data. + + @param client An initialized and unconnected client context + + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_client_deinit(struct rudp_client *client); + +/** + @this specifies a hostname to connect to. Actual underlying address + will be resolved. @see rudp_address_set_hostname for details. + + @param client An initialized client context structure + @param hostname Hostname to resolve + @param port Numeric target port (machine order) + @param ip_flags RUDP_IPV4_ONLY, RUDP_IPV6_ONLY or RUDP_IP_ANY + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_client_set_hostname( + struct rudp_client *client, + const char *hostname, + const uint16_t port, + uint32_t ip_flags); + +/** + @this specifies an IPv4 address to connect to. @see + rudp_address_set_ipv4 for details. + + @param client An initialized client context structure + @param address IPv4 to use (usual @tt {struct in_addr} order) + @param port Numeric target port (machine order) + @returns a possible error + */ +RUDP_EXPORT +void rudp_client_set_ipv4( + struct rudp_client *client, + const struct in_addr *address, + const uint16_t port); + +/** + @this specifies an IPv6 address to connect to. @see + rudp_address_set_ipv6 for details. + + @param client An initialized client context structure + @param address IPv6 to use (usual @tt {struct in6_addr} order) + @param port Numeric target port (machine order) + @returns a possible error + */ +RUDP_EXPORT +void rudp_client_set_ipv6( + struct rudp_client *client, + const struct in6_addr *address, + const uint16_t port); + +/** + @this sends data to remote server + + @param client Source client + @param reliable Whether to send the payload reliably + @param command User command code. It may be between 0 and RUDP_CMD_APP_MAX. + @param data Payload + @param size Total payload size + + @returns An error level + */ +RUDP_EXPORT +rudp_error_t rudp_client_send(struct rudp_client *client, + int reliable, int command, + const void *data, const size_t size); + +#endif diff --git a/include/rudp/compiler.h b/include/rudp/compiler.h new file mode 100644 index 0000000..0ad2f43 --- /dev/null +++ b/include/rudp/compiler.h @@ -0,0 +1,28 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_COMPILER_H_ +/** @hidden */ +#define RUDP_COMPILER_H_ + +/** + @file + @hidden +*/ + +/* GCC visibility */ +#if defined(__GNUC__) && __GNUC__ >= 4 /** mkdoc:skip */ +#define RUDP_EXPORT __attribute__ ((visibility("default"))) +#else +#define RUDP_EXPORT +#endif + +#endif diff --git a/include/rudp/endpoint.h b/include/rudp/endpoint.h new file mode 100644 index 0000000..e96eef9 --- /dev/null +++ b/include/rudp/endpoint.h @@ -0,0 +1,210 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_ENDPOINT_H_ +/** @hidden */ +#define RUDP_ENDPOINT_H_ + +/** + @file + @module {Endpoint} + @short Network endpoint (socket handling) + + Endpoint is an abstration over a system socket. It handles sending + and receiving of packets. + + Endpoint contexts use the same @xref {olc} {life cycle} as other + complex objects of the library. Before use, endpoint contexts must + be initialized with @ref rudp_endpoint_init, and after use, they must + be cleaned with @ref rudp_endpoint_deinit. +*/ + +#include +#include +#include + +struct rudp_peer; +struct rudp_endpoint; +struct rudp_packet_chain; + +/** + Endpoint handler code callbacks + */ +struct rudp_endpoint_handler +{ + /** + @this is called on packet reception. Packet contains raw data. + + Packet chain ownership is not given to handler, handler must + copy data and forget the chain afterwards. + + @param endpoint Endpoint context + @param addr Remote address the packet was received from + @param packet Packet descriptor structure + */ + void (*handle_packet)( + struct rudp_endpoint *endpoint, + const struct sockaddr_storage *addr, + struct rudp_packet_chain *packet); +}; + +/** + @this is a endpoint context structure. User must not use its + fields directly. + + Endpoints are objects with the same @xref {olc} {life cycle} as + most other complex objects of the library. Before use, endpoint + contexts must be initialized with @ref rudp_endpoint_init, and + after use, they must be cleaned with @ref rudp_endpoint_deinit. + + An initialized endpoint context can be bound with @ref + rudp_endpoint_bind to its address, and a bound endpoint context can + be closed with @ref rudp_endpoint_close. A close endpoint context + stays initialized. + + @hidecontent + */ +struct rudp_endpoint +{ + const struct rudp_endpoint_handler *handler; + struct rudp_address addr; + struct rudp *rudp; + struct ela_event_source *ela_source; + int socket_fd; +}; + +/** + @this initializes a endpoint structure. + + @param endpoint Endpoint structure to initialize + @param rudp A valid rudp context + @param handler A endpoint handler descriptor + */ +RUDP_EXPORT +void rudp_endpoint_init( + struct rudp_endpoint *endpoint, + struct rudp *rudp, + const struct rudp_endpoint_handler *handler); + +/** + @this frees all data associated to a endpoint structure + + The endpoint should have no references left. + + @param endpoint Endpoint structure to deinit + */ +RUDP_EXPORT +void rudp_endpoint_deinit(struct rudp_endpoint *endpoint); + +/** + @this specifies a hostname to bind to. Actual underlying address + will be resolved. @see rudp_address_set_hostname for details. + + @param endpoint An initialized endpoint context structure + @param hostname Hostname to resolve + @param port Numeric target port (machine order) + @param ip_flags RUDP_IPV4_ONLY, RUDP_IPV6_ONLY or RUDP_IP_ANY + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_endpoint_set_hostname( + struct rudp_endpoint *endpoint, + const char *hostname, + const uint16_t port, + uint32_t ip_flags); + +/** + @this specifies an IPv4 address to bind to. @see + rudp_address_set_ipv4 for details. + + @param endpoint An initialized endpoint context structure + @param address IPv4 to use (usual @tt {struct in_addr} order) + @param port Numeric target port (machine order) + @returns a possible error + */ +RUDP_EXPORT +void rudp_endpoint_set_ipv4( + struct rudp_endpoint *endpoint, + const struct in_addr *address, + const uint16_t port); + +/** + @this specifies an IPv6 address to bind to. @see + rudp_address_set_ipv6 for details. + + @param endpoint An initialized endpoint context structure + @param address IPv6 to use (usual @tt {struct in6_addr} order) + @param port Numeric target port (machine order) + @returns a possible error + */ +RUDP_EXPORT +void rudp_endpoint_set_ipv6( + struct rudp_endpoint *endpoint, + const struct in6_addr *address, + const uint16_t port); + +/** + @this open and binds an endpoint to its address + + @param endpoint Endpoint to bind + */ +RUDP_EXPORT +rudp_error_t rudp_endpoint_bind(struct rudp_endpoint *endpoint); + +/** + @this closes the socket associated to this endpoint + + @param endpoint Endpoint to close + */ +RUDP_EXPORT +void rudp_endpoint_close(struct rudp_endpoint *endpoint); + +/** + @this sends data from the endpoint to the designated remote address. + + @param endpoint Enpoint to use as source + @param addr Destination address + @param data Data pointer + @param len Length of data + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_endpoint_send(struct rudp_endpoint *endpoint, + const struct rudp_address *addr, + const void *data, size_t len); + +/** + @this receives data from the associated socket. + + @param endpoint Endpoint + @param data Data buffer + @param len (inout) On call: size avaiable in @tt data. On return: + size actually read + @param addr (out) Peer address + @returns a possible error, 0 if received + */ +RUDP_EXPORT +rudp_error_t rudp_endpoint_recv(struct rudp_endpoint *endpoint, + void *data, size_t *len, + struct sockaddr_storage *addr); + +/** + @this compares the endpoint address with another address + + @param endpoint Endpoint to compare address from + @param addr Address to compare to + @returns 0 if they match, another value otherwise + */ +RUDP_EXPORT +int rudp_endpoint_address_compare(const struct rudp_endpoint *endpoint, + const struct sockaddr_storage *addr); + +#endif diff --git a/include/rudp/error.h b/include/rudp/error.h new file mode 100644 index 0000000..4836e1a --- /dev/null +++ b/include/rudp/error.h @@ -0,0 +1,34 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_ERROR_H_ +/** @hidden */ +#define RUDP_ERROR_H_ + +#include + +/** + @file + @module {Errors} + @short Common error representation + + Error handling avoids usage of the @tt errno C global. Error codes + are directly returned from functions where relevant. They use the + common @ref rudp_error_t type. This type only means the value is + an error. Error codes are taken from errno definition. + */ + +/** + @this is an error code type + */ +typedef int rudp_error_t; + +#endif diff --git a/include/rudp/list.h b/include/rudp/list.h new file mode 100644 index 0000000..cd461b6 --- /dev/null +++ b/include/rudp/list.h @@ -0,0 +1,33 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_LIST_H_ +/** @hidden */ +#define RUDP_LIST_H_ + +/** + @file + @hidden +*/ + +struct rudp_list; + +#if 1 /* mkdoc:skip */ + +struct rudp_list +{ + struct rudp_list *next; + struct rudp_list *prev; +}; + +#endif + +#endif diff --git a/include/rudp/packet.h b/include/rudp/packet.h new file mode 100644 index 0000000..a9ca948 --- /dev/null +++ b/include/rudp/packet.h @@ -0,0 +1,219 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_PACKET_H_ +/** @hidden */ +#define RUDP_PACKET_H_ + +/** + @file + @module{Packet} + @short Network packet structure + + Librudp packet format is designed to support the @xref {features} + {features of the protocol}. All packets contain a fixed header + containing: + @list + @item a command + @item some flags + @item a field for acking received packets sequence numbers + @item two fields for outgoing sequence numbers. + @end list + + Data follows the header, in an arbitrary format. Some commands + require fixed data. See relevant @ref {rudp_command} {command + definitions}. + + As the 1-byte command code is under-used, user can use any code + equal or above @ref RUDP_CMD_APP. + */ + +#include +#include +#include +#include + +/** + Command definition codes + */ +enum rudp_command +{ + /** + @table 2 + @item @item + @item Relevant field @item none + @item Semantic @item useless payload + @item Notes @item May not be RELIABLE. Useful for keepalives (NAT, etc). + @end table + */ + RUDP_CMD_NOOP = 0, + + /** + @table 2 + @item @item + @item Relevant field @item none. + @item Semantic @item Close the session + @item Expected answer @item None (ACK does the job) + @item Notes @item May be RELIABLE. + @end table + */ + RUDP_CMD_CLOSE = 1, + + /** + @table 2 + @item @item + @item Relevant field @item conn_req. + @item Semantic @item + @item Expected answer @item conn_rsp + @item Notes @item Must be RELIABLE + @end table + */ + RUDP_CMD_CONN_REQ = 2, + + /** + @table 2 + @item @item + @item Relevant field @item conn_rsp. + @item Semantic @item Acknoledges CONN_REQ + @item Expected answer @item None + @item Notes @item Must not be RELIABLE + @end table + */ + RUDP_CMD_CONN_RSP = 3, + + /** + @table 2 + @item @item + @item Relevant field @item data. + @item Semantic @item Must answer + @item Expected answer @item PONG packet with same data + @item Notes @item May be RELIABLE. Must not be answered for + if it was retransmitted. Must still be + acknoledged if reliable. + @end table + */ + RUDP_CMD_PING = 4, + + /** + @table 2 + @item @item + @item Relevant field @item data. + @item Semantic @item PING answer + @item Expected answer @item None + @item Notes @item Must not be RELIABLE. + @end table + */ + RUDP_CMD_PONG = 5, + + /** + @table 2 + @item @item + @item Relevant field @item data + @item Semantic @item Normal data packet + @item Notes @item May be RELIABLE or not, if RELIABLE, must be acked. + Any packet command number >= RUDP_CMD_APP is permitted + @end table + */ + RUDP_CMD_APP = 0x10, +}; + +/** @mgroup{Flags} + Packet delivery is reliable. If emission fails, retransmit is + performed. */ +#define RUDP_OPT_RELIABLE 1 + +/** @mgroup{Flags} + Packet also contains acknoledge of received packet. ack sequence + id is in reliable_ack. */ +#define RUDP_OPT_ACK 2 + +/** @mgroup{Flags} + Packet was retransmitted at least once. */ +#define RUDP_OPT_RETRANSMITTED 4 + +#define RUDP_CMD_APP_MAX (0xff - RUDP_CMD_APP) + +/** + Packet header structure. All fields in this structure should be + transmitted in network order. + */ +struct rudp_packet_header +{ + uint8_t command; + uint8_t opt; + uint16_t reliable_ack; + uint16_t reliable; + uint16_t unreliable; +}; + +/** + Connection request packet (@xref {protocol}). + */ +struct rudp_packet_conn_req +{ + struct rudp_packet_header header; + uint32_t data; +}; + +/** + Connection response packet (@xref {protocol}). + */ +struct rudp_packet_conn_rsp +{ + struct rudp_packet_header header; + uint32_t accepted; +}; + +/** + Data packet (@xref {protocol}). + */ +struct rudp_packet_data +{ + struct rudp_packet_header header; + uint8_t data[0]; +}; + +/** + Structure factoring all the possible packet types. + */ +struct rudp_packet +{ + union { + struct rudp_packet_header header; + struct rudp_packet_conn_req conn_req; + struct rudp_packet_conn_rsp conn_rsp; + struct rudp_packet_data data; + }; +}; + +/** + Packet chain structure + */ +struct rudp_packet_chain +{ + struct rudp_list chain_item; + struct rudp_packet *packet; + size_t alloc_size; + size_t len; +}; + +/** + @this retrieves a string matching a command type. This is + guaranteed to return a valid string even for undefined or user + command codes. + + @param cmd A command value + @returns a command name string + */ +RUDP_EXPORT +const char *rudp_command_name(enum rudp_command cmd); + +#endif diff --git a/include/rudp/peer.h b/include/rudp/peer.h new file mode 100644 index 0000000..13f362a --- /dev/null +++ b/include/rudp/peer.h @@ -0,0 +1,257 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_PEER_H_ +/** @hidden */ +#define RUDP_PEER_H_ + +/** + @file + @module {Peer} + @short Peer implementation + + Peer is the symmetrical implementation of the protocol. A Peer + context handles the low-level protocol (with retransmits and ACKs). + + Peer context does not directly handle the transport and is abstract + from the network code. + + Peer contexts use the same @xref {olc} {life cycle} as other + complex objects of the library. Before use, peer contexts must be + initialized with @ref rudp_peer_init, and after use, they must be + cleaned with @ref rudp_peer_deinit. + + Either because of a connection timeout or because of relevant + command is received, disconnection can happend and the @tt + rudp_peer_handler::dropped handler is called back. + */ + +#include +#include +#include +#include + +struct rudp_peer; +struct rudp_endpoint; +struct rudp_link_info; +struct rudp_packet_header; +struct rudp_packet_chain; + +/** + Peer handler code callbacks + */ +struct rudp_peer_handler +{ + /** + @this is called on packet reception. Packet is already decoded + and is ensured to be an application packet (@ref RUDP_CMD_APP + command or equivalent). + + Packet chain ownership is not given to handler, handler must + copy data and forget the chain afterwards. + + @param peer Peer context + @param packet Packet descriptor structure + */ + void (*handle_packet)( + struct rudp_peer *peer, + struct rudp_packet_chain *packet); + + /** + @this is called anytime when link statistics are updated. + + @param peer Peer context + @param info Link quality updated information + */ + void (*link_info)(struct rudp_peer *peer, struct rudp_link_info *info); + + /** + @this is called when connection to the peer drops, either + explicitely or because of a timeout. + + After this handler is called, peer is left in the initialized + state. Handler code can either resend packets (which will use + reset sequence numbers) or call @ref rudp_peer_deinit. + + @param peer Peer context + */ + void (*dropped)(struct rudp_peer *peer); +}; + +/** + @this is a peer context structure. User must not use its fields + directly. + + Peers are objects with the same @xref {olc} {life cycle} as + most other complex objects of the library. Before use, peer + contexts must be initialized with @ref rudp_peer_init, and after + use, they must be cleaned with @ref rudp_peer_deinit. + + An initialized peer context can send packets to its peer address, + and can handle incoming packets according to the protocol. It + handles all the timeout and retransmit logic. + + @hidecontent + */ +struct rudp_peer +{ + const struct rudp_peer_handler *handler; + struct rudp_address address; + struct rudp_endpoint *endpoint; + rudp_time_t abs_timeout_deadline; + rudp_time_t last_out_time; + rudp_time_t srtt; + rudp_time_t rttvar; + rudp_time_t rto; + uint16_t in_seq_reliable; + uint16_t in_seq_unreliable; + uint16_t out_seq_reliable; + uint16_t out_seq_unreliable; + uint16_t out_seq_acked; + uint8_t must_ack:1; + uint8_t scheduled:1; + uint8_t state; + struct rudp_list sendq; + struct rudp *rudp; + struct ela_event_source *service_source; + rudp_error_t sendto_err; +}; + +/** + @this initializes a peer structure. + + @param peer Peer structure to initialize + @param rudp A valid rudp context + @param handler A peer handler descriptor + @param endpoint Associated endpoint to send the outgoing packets + through + */ +RUDP_EXPORT +void rudp_peer_init( + struct rudp_peer *peer, + struct rudp *rudp, + const struct rudp_peer_handler *handler, + struct rudp_endpoint *endpoint); + +/** + @this initializes a peer structure knowning its remote + address. This function is an alternative to calling @ref + rudp_peer_init. + + @param peer Peer structure to initialize + @param rudp A valid rudp context + @param addr Peer's address + @param handler A peer handler descriptor + @param endpoint Associated endpoint to send the outgoing packets + through + */ +RUDP_EXPORT +void rudp_peer_from_sockaddr( + struct rudp_peer *peer, + struct rudp *rudp, + const struct sockaddr_storage *addr, + const struct rudp_peer_handler *handler, + struct rudp_endpoint *endpoint); + +/** + @this frees all data associated to a peer structure + + @param peer Peer context to deinit + */ +RUDP_EXPORT +void rudp_peer_deinit(struct rudp_peer *peer); + +/** + @this resets a peer context. + + This resets sequence numbers, state, timers and send queue. + Resetting a peer can be useful on lost connection, in order to + start over. + + @param peer Peer context to reset + */ +RUDP_EXPORT +void rudp_peer_reset(struct rudp_peer *peer); + +/** + @this compares the peer address against another address. @see + rudp_address_compare. + + @param peer Peer to compare + @param addr Address to compare + @returns 0 if they match, another value otherwise + */ +RUDP_EXPORT +int rudp_peer_address_compare(const struct rudp_peer *peer, + const struct sockaddr_storage *addr); + +/** + @this passes an incoming packet to the peer handler code. + + @param peer Peer context + @param pc Packet descriptor structure + @returns a possible error value + */ +RUDP_EXPORT +rudp_error_t rudp_peer_incoming_packet( + struct rudp_peer *peer, + struct rudp_packet_chain *pc); + +/** + @this sends unreliable data to a peer. + + Calling code doesn't have to initialize the packet header. + + @param peer Destination peer + @param pc Packet chain element + + Peer handling code becomes owner of the @tt pc pointer, and may + free it when needed. + */ +RUDP_EXPORT +rudp_error_t rudp_peer_send_unreliable( + struct rudp_peer *peer, + struct rudp_packet_chain *pc); + +/** + @this sends reliable data to a peer. + + Calling code doesn't have to initialize the packet header. + + @param peer Destination peer + @param pc Packet chain element + + Peer handling code becomes owner of the @tt pc pointer, and may + free it when needed. + */ +RUDP_EXPORT +rudp_error_t rudp_peer_send_reliable( + struct rudp_peer *peer, + struct rudp_packet_chain *pc); + +/** + @this sends a reliable connect packet to a peer. + + @param peer Destination peer + */ +RUDP_EXPORT +rudp_error_t rudp_peer_send_connect(struct rudp_peer *peer); + +/** + @this immediately sends an unreliable @ref RUDP_CMD_CLOSE command, + bypassing the current retransmit queue. + + @param peer Destination peer + */ +RUDP_EXPORT +rudp_error_t rudp_peer_send_close_noqueue(struct rudp_peer *peer); + +#endif diff --git a/include/rudp/rudp.h b/include/rudp/rudp.h new file mode 100644 index 0000000..517367a --- /dev/null +++ b/include/rudp/rudp.h @@ -0,0 +1,187 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_RUDP_H_ +/** @hidden */ +#define RUDP_RUDP_H_ + +/** + @file + @module {Master state} + @short Master library state + + Master library state holds context used for administrative purposes + of the library. It handles: + @list + @item Event loop abstraction handling (though libela) + @item Memory management + @item Logging + @end list + + Master library state must be passed to all library objects + initialization. + + A libela context must be initialized before the rudp context. See + libela documentation for more information. + + When a handler is defined for logging, it gets all the messages + from the library. If user wants to filter messages, it can use the + @tt level parameter. @see rudp_log_level. + + Memory allocation is handler through alloc/free-like functions. + + @see rudp_handler for functions to implement. + + Librudp provides sane defaults for the needed handlers, not + reporting any log messages, and using @tt malloc and @tt free from + the system's libc. Such handler is defined as @ref + #RUDP_HANDLER_DEFAULT. + + Sample usage: + @code + struct rudp rudp; + struct ela_el *el = ...; + + rudp_init(&rudp, el, RUDP_HANDLER_DEFAULT); + + // allocate and initialize some objects + + // run your event loop + + // deinitialize and free the objects + + rudp_deinit(&rudp); + @end code + */ + +#include +#include +#include + +#include +#include +#include + +/** + @this defines log levels of log messages. + */ +enum rudp_log_level +{ + RUDP_LOG_IO, + RUDP_LOG_DEBUG, + RUDP_LOG_INFO, + RUDP_LOG_WARN, + RUDP_LOG_ERROR, +}; + +struct rudp; + +/** + Master state handler code callbacks + */ +struct rudp_handler +{ + /** + @this can be called anytime in the state life, to gather + information about the library actions. + + Setting NULL in this callback is permitted to get no messages + at all. + + @param rudp The rudp context + @param level Log level of the message + @param fmt Message printf-like format + @param arg Argument list + */ + void (*log)( + struct rudp *rudp, + enum rudp_log_level level, const char *fmt, va_list arg); + + /** + @this is called each time the library needs memory. + + @param rudp The rudp context + @param size Size of buffer needed + @returns A newly allocated buffer, or NULL + */ + void *(*mem_alloc)(struct rudp *rudp, size_t size); + + /** + @this is called each time the library frees a previously + allocated buffer. + + @param rudp The rudp context + @param buffer Previously allocated buffer to release + */ + void (*mem_free)(struct rudp *rudp, void *buffer); +}; + +extern const struct rudp_handler rudp_handler_default; + +/** + Can be used where default allocators and no logging output is + needed for @ref rudp_init. + */ +#define RUDP_HANDLER_DEFAULT &rudp_handler_default + +/** + @this is a rudp context + + Rudp have the same @xref {olc} {life cycle} as most other complex + objects of the library. Before use, rudp contexts must be + initialized with @ref rudp_init, and after use, they must be + cleaned with @ref rudp_deinit. + + @hidecontent + */ +struct rudp +{ + const struct rudp_handler *handler; + struct ela_el *el; + struct rudp_list free_packet_list; + unsigned int seed; + uint16_t allocated_packets; + uint16_t free_packets; +}; + +/** + @this initializes a master library state structure + + @param rudp Rudp context to initialize + @param el A valid event loop abstraction context + @param handler A rudp handler descriptor. Use @ref + #RUDP_HANDLER_DEFAULT if no specific behavior is intended. + + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_init( + struct rudp *rudp, + struct ela_el *el, + const struct rudp_handler *handler); + +/** + @this initializes a master library state structure + + @param rudp Rudp context to deinitialize + */ +RUDP_EXPORT +void rudp_deinit(struct rudp *rudp); + +/** + @this generates a 16 bit random value + + @param rudp Rudp context to deinitialize + */ +RUDP_EXPORT +uint16_t rudp_random(struct rudp *rudp); + +#endif diff --git a/include/rudp/server.h b/include/rudp/server.h new file mode 100644 index 0000000..ae28c9b --- /dev/null +++ b/include/rudp/server.h @@ -0,0 +1,344 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_SERVER_H_ +/** @hidden */ +#define RUDP_SERVER_H_ + +/** + @file + @module {Server} + @short Server implementation + + Server binds to a given socket address and waits for clients to + connect. For binding, It either uses an explicit constant IPv4 or + IPv6 address, or an hostname to resolve. + + When a client connects, as long as the connection is established, + data can be exchanged both ways, either reliably or not. User code + gets notified of clients (un)connecting. + + Server contexts use the same @xref {olc} {life cycle} as other + complex objects of the library. Before use, server contexts must + be initialized with @ref rudp_server_init, and after use, they must + be cleaned with @ref rudp_server_deinit. + + An initialized but not yet bound Server context must have its + server address setup with any of the following functions: @ref + rudp_server_set_hostname, @ref rudp_server_set_ipv4 or @ref + rudp_server_set_ipv6. + + Once the server address is setup properly, Server context can be + bound with @ref rudp_server_bind. After successful binding, the + server waits for clients and data packets. + + @label {peer_user_data} + + User code may store an user-handled user data pointer inside the + peer structures associated to a server context. This can be done + through the @ref rudp_server_peer_data_set and @ref + rudp_server_peer_data_get functions. + + Sample usage: + @code + // assuming you have a valid rudp context here: + struct rudp *rudp; + + struct rudp_server server; + + rudp_server_init(&server, rudp, &my_server_handlers); + + // set the address and bind + rudp_server_set_ipv4(&server, ...); + rudp_server_bind(&server); + + // run your event loop + + rudp_server_close(&server); + rudp_server_deinit(&server); + @end code +*/ + +#include +#include +#include +#include +#include + +struct rudp_server; +struct rudp_link_info; +struct rudp_peer; + +/** + Server handler code callbacks + */ +struct rudp_server_handler +{ + /** + @this is called on packet reception. Packet is already decoded + and is ensured to be an application packet (@ref RUDP_CMD_APP + command or equivalent). + + Packet chain ownership is not given to handler, handler must + copy data and forget the chain afterwards. + + @param server Server context + @param peer Relevant peer + @param command User command used + @param data Data buffer + @param len Useful data length + */ + void (*handle_packet)(struct rudp_server *server, struct rudp_peer *peer, + int command, const void *data, size_t len); + + /** + @this is called anytime when link statistics are updated. + + @param server Server context + @param peer Relevant peer + @param info Link quality updated information + */ + void (*link_info)( + struct rudp_server *server, + struct rudp_peer *peer, + struct rudp_link_info *info); + + /** + @this is called when a peer drops, either explicitely or + because of a timeout. + + After this handler is called, handler code must forget its + references to the peer. The peer structure is not valid + afterwards. + + @param server Server context + @param peer Relevant peer + */ + void (*peer_dropped)(struct rudp_server *server, struct rudp_peer *peer); + + /** + @this is called when a new peer gets connected. When this + handler is called, peer is guaranteed to have established a + valid connection. + + Handler code may retain pointer references to @tt peer, and may + even store @xref {peer_user_data} {user data} pointer inside + it. + + Peer will stay valid until @ref + rudp_server_handler::peer_dropped is called. + + @param server Server context + @param peer New peer + */ + void (*peer_new)(struct rudp_server *server, struct rudp_peer *peer); +}; + +/** + @this is a server context structure. User should not use its + fields directly. + + Servers are objects with the same @xref {olc} {life cycle} as + most other complex objects of the library. Before use, server + contexts must be initialized with @ref rudp_server_init, and after + use, they must be cleaned with @ref rudp_server_deinit. + + An initialized server context can be bound with @ref + rudp_server_bind to a port, and a bound server context can be + closed with @ref rudp_server_close. A closed server context stays + initialized. + + @hidecontent + */ +struct rudp_server +{ + const struct rudp_server_handler *handler; + struct rudp_list peer_list; + struct rudp_endpoint endpoint; + struct rudp *rudp; +}; + +struct rudp_peer; + +/** + @this initializes the server context. + + @param server Server unitialized context structure + @param rudp A valid rudp context + @param handler A server handler descriptor + + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_server_init( + struct rudp_server *server, + struct rudp *rudp, + const struct rudp_server_handler *handler); + +/** + @this binds the server context to an address. This both creates a + socket and binds it to the relevant address/port set with any of + the @ref rudp_server_set_hostname, @ref rudp_server_set_ipv4 or + @ref rudp_server_set_ipv6. + + @param server An initialized server context structure + + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_server_bind(struct rudp_server *server); + +/** + @this unbinds the server context from an address, closes all + associated sockets and drops all peers. A @ref + rudp_server_handler::peer_dropped handler will be called for each + present peer. + + @param server A bound server context structure + + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_server_close(struct rudp_server *server); + +/** + @this frees all internally allocated server data. + + @param server An initialized and unbound server context + + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_server_deinit(struct rudp_server *server); + +/** + @this specifies a hostname to bind to. Actual underlying address + will be resolved. @see rudp_address_set_hostname for details. + + @param server An initialized server context structure + @param hostname Hostname to resolve + @param port Numeric target port (machine order) + @param ip_flags RUDP_IPV4_ONLY, RUDP_IPV6_ONLY or RUDP_IP_ANY + @returns a possible error + */ +RUDP_EXPORT +rudp_error_t rudp_server_set_hostname( + struct rudp_server *server, + const char *hostname, + const uint16_t port, + uint32_t ip_flags); + +/** + @this specifies an IPv4 address to bind to. @see + rudp_address_set_ipv4 for details. + + @param server An initialized server context structure + @param address IPv4 to use (usual @tt {struct in_addr} order) + @param port Numeric target port (machine order) + @returns a possible error + */ +RUDP_EXPORT +void rudp_server_set_ipv4( + struct rudp_server *server, + const struct in_addr *address, + const uint16_t port); + +/** + @this specifies an IPv6 address to bind to. @see + rudp_address_set_ipv6 for details. + + @param server An initialized server context structure + @param address IPv6 to use (usual @tt {struct in6_addr} order) + @param port Numeric target port (machine order) + @returns a possible error + */ +RUDP_EXPORT +void rudp_server_set_ipv6( + struct rudp_server *server, + const struct in6_addr *address, + const uint16_t port); + +/** + @this sends data from this server to a peer. + + @param server Source server + @param peer Destination peer + @param reliable Whether to send the payload reliably + @param command User command code. It may be between 0 and RUDP_CMD_APP_MAX. + @param data Payload + @param size Total packet size + + @returns An error level + */ +RUDP_EXPORT +rudp_error_t rudp_server_send( + struct rudp_server *server, + struct rudp_peer *peer, + int reliable, int command, + const void *data, const size_t size); + +/** + @this sends data from this server to all peers. + + @param server Source server + @param reliable Whether to send the payload reliably + @param command User command code. It may be between 0 and RUDP_CMD_APP_MAX. + @param data Payload + @param size Total packet size + + @returns An error level + */ +RUDP_EXPORT +rudp_error_t rudp_server_send_all( + struct rudp_server *server, + int reliable, int command, + const void *data, const size_t size); + +/** + @this retrieves user code private data pointer associated to a + peer. + + @param server Server context this peer belongs to + @param peer Peer context pointer is associated to + @returns the latest user pointer set for this peer (NULL if never + set before) + */ +RUDP_EXPORT +void *rudp_server_peer_data_get( + struct rudp_server *server, + struct rudp_peer *peer); + +/** + @this sets user code private data pointer associated to a peer. + + @param server Server context this peer belongs to + @param peer Peer context to associate the pointer to + @param data User pointer + */ +RUDP_EXPORT +void rudp_server_peer_data_set( + struct rudp_server *server, + struct rudp_peer *peer, + void *data); + +/** + @this cleanly drops connection to one client. + + @param server Source server + @param peer Destination peer corresponding to client + + */ +RUDP_EXPORT +void rudp_server_client_close( + struct rudp_server *server, + struct rudp_peer *peer); + +#endif diff --git a/include/rudp/time.h b/include/rudp/time.h new file mode 100644 index 0000000..cbe781b --- /dev/null +++ b/include/rudp/time.h @@ -0,0 +1,65 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_TIME_H_ +/** @hidden */ +#define RUDP_TIME_H_ + +/** + @file + @module{Time} + @short Uniform time representation + + Time handling is done through the @ref rudp_time_t type. This type + is a scalar and can be manipulated with usual scalar operations. + + The only functions declared for usage with @ref rudp_time_t are + @ref rudp_timestamp and @ref rudp_timestamp_to_timeval. +*/ + +#include +#include + +/** + @this is an abstract time type definition. It contains miliseconds + since a common time reference. + */ +typedef int64_t rudp_time_t; + +#define RUDP_TIME_MAX INT64_MAX + +/** + @this retrieves the current library timestamp + + @returns a timestamp + */ +static inline +rudp_time_t rudp_timestamp(void) +{ + struct timeval tv; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +/** + @this converts a milisecond value to a @tt {struct timeval}. + + @param tv (out) Timeval structure + @param ts Timestamp + */ +static inline +void rudp_timestamp_to_timeval(struct timeval *tv, rudp_time_t ts) +{ + tv->tv_sec = ts / 1000; + tv->tv_usec = (ts % 1000) * 1000; +} + +#endif diff --git a/rudp.pc.in b/rudp.pc.in new file mode 100644 index 0000000..e7af83e --- /dev/null +++ b/rudp.pc.in @@ -0,0 +1,10 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +Name: ela +Description: Reliable UDP library +Version: @VERSION@ +Requires: ela +Libs: -L${libdir} -lrudp +Cflags: -I${includedir} diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..62f09c3 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,8 @@ + +lib_LTLIBRARIES = librudp.la + +librudp_la_SOURCES = address.c server.c rudp_list.h peer.c \ +endpoint.c client.c packet.c rudp.c rudp_rudp.h rudp_error.h rudp_packet.h +librudp_la_LIBADD = $(ELA_LIBS) +librudp_la_CFLAGS = -I$(top_srcdir)/src \ +-I$(top_srcdir)/include $(GCC_CFLAGS) $(ELA_CFLAGS) diff --git a/src/address.c b/src/address.c new file mode 100644 index 0000000..0c7ccef --- /dev/null +++ b/src/address.c @@ -0,0 +1,281 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include "rudp_rudp.h" + +enum resolver_state { + RUDP_RESOLV_NONE, + RUDP_RESOLV_ADDR, + RUDP_RESOLV_ERROR, + RUDP_RESOLV_DONE, + RUDP_RESOLV_FAILED, +}; + +rudp_error_t rudp_address_set_hostname( + struct rudp_address *rua, + const char *hostname, + const uint16_t port, + uint32_t ip_flags) +{ + rua->text[0] = 0; + + if ( hostname == NULL ) + return EINVAL; + + if ( rua->hostname ) + free(rua->hostname); + + rua->port = port; + + rua->hostname = strdup(hostname); + if ( rua->hostname == NULL ) + return ENOMEM; + + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + + if ( ip_flags & RUDP_NO_IPV4 ) + hints.ai_family = AF_INET6; + else if ( ip_flags & RUDP_NO_IPV6 ) + hints.ai_family = AF_INET; + else + hints.ai_family = AF_UNSPEC; + + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + + rua->cur = NULL; + + int s = getaddrinfo(hostname, NULL, &hints, &rua->addrinfo); + if (s != 0) { + rua->resolver_state = RUDP_RESOLV_ERROR; + return errno; + } + + rua->resolver_state = RUDP_RESOLV_DONE; + + rudp_address_next(rua); + + return 0; +} + +void rudp_address_deinit(struct rudp_address *rua) +{ + switch ( (enum resolver_state)rua->resolver_state ) + { + case RUDP_RESOLV_NONE: + case RUDP_RESOLV_ADDR: + case RUDP_RESOLV_FAILED: + case RUDP_RESOLV_ERROR: + break; + case RUDP_RESOLV_DONE: + freeaddrinfo(rua->addrinfo); + } + + if ( rua->hostname ) + free(rua->hostname); + if ( rua->addr ) + rudp_free(rua->rudp, rua->addr); + rua->hostname = NULL; + rua->resolver_state = RUDP_RESOLV_NONE; + rua->text[0] = 0; +} + +void rudp_address_init(struct rudp_address *rua, struct rudp *rudp) +{ + memset(rua, 0, sizeof(*rua)); + rua->text[0] = 0; + + rua->rudp = rudp; + rua->resolver_state = RUDP_RESOLV_NONE; + rua->addr = rudp_alloc(rudp, sizeof(struct sockaddr_storage)); + memset(rua->addr, 0, sizeof(struct sockaddr_storage)); +} + +void rudp_address_set_ipv4( + struct rudp_address *rua, + const struct in_addr *in_addr, + const uint16_t port) +{ + struct sockaddr_in *addr = (struct sockaddr_in *)rua->addr; + + rua->text[0] = 0; + + rua->resolver_state = RUDP_RESOLV_ADDR; + + rua->port = port; + addr->sin_family = AF_INET; + addr->sin_port = htons(port); + memcpy(&addr->sin_addr, in_addr, sizeof(struct in_addr)); +} + + +void rudp_address_set_ipv6( + struct rudp_address *rua, + const struct in6_addr *in6_addr, + const uint16_t port) +{ + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)rua->addr; + + rua->text[0] = 0; + + memset(addr, 0, sizeof(*addr)); + + rua->resolver_state = RUDP_RESOLV_ADDR; + + rua->port = port; + addr->sin6_family = AF_INET6; + addr->sin6_port = htons(port); + memcpy(&addr->sin6_addr, in6_addr, sizeof(struct in6_addr)); +} + +rudp_error_t rudp_address_next( + struct rudp_address *rua) +{ + rua->text[0] = 0; + + switch ( (enum resolver_state)rua->resolver_state ) + { + case RUDP_RESOLV_ADDR: + return 0; + + case RUDP_RESOLV_DONE: + if ( rua->cur == NULL || rua->cur->ai_next == NULL ) + rua->cur = rua->addrinfo; + else + rua->cur = rua->cur->ai_next; + + if ( rua->cur->ai_family == AF_INET6 ) + { + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)rua->addr; + memcpy(addr, rua->cur->ai_addr, sizeof(struct sockaddr_in6)); + addr->sin6_port = htons(rua->port); + } + else + { + struct sockaddr_in *addr = (struct sockaddr_in *)rua->addr; + memcpy(addr, rua->cur->ai_addr, sizeof(struct sockaddr_in)); + addr->sin_port = htons(rua->port); + } + + return 0; + + case RUDP_RESOLV_NONE: + return EDESTADDRREQ; + + case RUDP_RESOLV_FAILED: + case RUDP_RESOLV_ERROR: + return ENXIO; + } + return EINVAL; +} + +rudp_error_t rudp_address_get( + const struct rudp_address *rua, + const struct sockaddr_storage **addr, + socklen_t *addrsize) +{ + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)rua->addr; + int family = addr4->sin_family; + + switch ( (enum resolver_state)rua->resolver_state ) + { + case RUDP_RESOLV_ADDR: + case RUDP_RESOLV_DONE: + *addr = rua->addr; + *addrsize = family == AF_INET ? + sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); + return 0; + + case RUDP_RESOLV_NONE: + return EDESTADDRREQ; + + case RUDP_RESOLV_FAILED: + case RUDP_RESOLV_ERROR: + return ENXIO; + } + return EINVAL; +} + +int rudp_address_compare(const struct rudp_address *rua, + const struct sockaddr_storage *addr) +{ + struct sockaddr_in6 *left6 = (struct sockaddr_in6 *)rua->addr; + struct sockaddr_in *left = (struct sockaddr_in *)rua->addr; + + struct sockaddr_in6 *right6 = (struct sockaddr_in6 *)addr; + struct sockaddr_in *right = (struct sockaddr_in *)addr; + + if ( left->sin_family != right->sin_family ) + return 1; + + if ( left->sin_family == AF_INET ) + { + if ( left->sin_port != right->sin_port ) + return 1; + + return memcmp( + &left->sin_addr, + &right->sin_addr, + sizeof(struct in_addr)); + } + else + { + if ( left6->sin6_port != right6->sin6_port ) + return 1; + + return memcmp( + &left6->sin6_addr, + &right6->sin6_addr, + sizeof(struct in6_addr)); + } +} + +const char *rudp_address_text(struct rudp_address *rua) +{ + if ( rua->text[0] ) + return rua->text; + + const struct sockaddr_storage *addr; + socklen_t size; + rudp_error_t err = rudp_address_get(rua, &addr, &size); + if ( err ) + return ""; + + const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr; + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr; + const void *sin = addr4->sin_family == AF_INET + ? (void*)&addr4->sin_addr + : (void*)&addr6->sin6_addr; + + if ( inet_ntop(addr4->sin_family, sin, rua->text, size) == NULL ) + return ""; + + char *end = rua->text + strlen(rua->text); + snprintf(end, 7, ":%d", (int)rua->port); + + return rua->text; +} diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..8e6f822 --- /dev/null +++ b/src/client.c @@ -0,0 +1,193 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#include +#include +#include + +#include +#include +#include +#include "rudp_list.h" +#include "rudp_packet.h" + +static const struct rudp_endpoint_handler client_endpoint_handler; +static const struct rudp_peer_handler client_peer_handler; + +rudp_error_t rudp_client_init( + struct rudp_client *client, + struct rudp *rudp, + const struct rudp_client_handler *handler) +{ + rudp_endpoint_init(&client->endpoint, rudp, &client_endpoint_handler); + rudp_address_init(&client->address, rudp); + client->rudp = rudp; + client->handler = handler; + client->connected = 0; + return 0; +} + +rudp_error_t rudp_client_connect(struct rudp_client *client) +{ + const struct sockaddr_storage *addr; + socklen_t size; + + rudp_error_t err = rudp_address_get(&client->address, &addr, &size); + if ( err ) + return err; + + rudp_peer_from_sockaddr(&client->peer, client->rudp, + addr, + &client_peer_handler, &client->endpoint); + + rudp_peer_send_connect(&client->peer); + + return rudp_endpoint_bind(&client->endpoint); +} + +rudp_error_t rudp_client_close(struct rudp_client *client) +{ + rudp_peer_send_close_noqueue(&client->peer); + + rudp_peer_deinit(&client->peer); + + rudp_endpoint_close(&client->endpoint); + return 0; +} + +rudp_error_t rudp_client_deinit(struct rudp_client *client) +{ + rudp_endpoint_deinit(&client->endpoint); + rudp_address_deinit(&client->address); + return 0; +} + +static +void client_handle_data_packet( + struct rudp_peer *peer, + struct rudp_packet_chain *pc) +{ + struct rudp_client *client = __container_of(peer, client, peer); + struct rudp_packet_data *header = &pc->packet->data; + + client->handler->handle_packet( + client, header->header.command - RUDP_CMD_APP, + header->data, pc->len - sizeof(header)); +} + +static +void client_link_info(struct rudp_peer *peer, struct rudp_link_info *info) +{ + struct rudp_client *client = __container_of(peer, client, peer); + + client->handler->link_info(client, info); +} + +static +void client_peer_dropped(struct rudp_peer *peer) +{ + struct rudp_client *client = __container_of(peer, client, peer); + + client->connected = 0; + + rudp_peer_deinit(&client->peer); + + rudp_endpoint_close(&client->endpoint); + + client->handler->server_lost(client); +} + +static const struct rudp_peer_handler client_peer_handler = { + .handle_packet = client_handle_data_packet, + .link_info = client_link_info, + .dropped = client_peer_dropped, +}; + +/* + - socket watcher + - endpoint packet reader + - client packet handler <=== + - peer packet handler + */ +static +void client_handle_endpoint_packet(struct rudp_endpoint *endpoint, + const struct sockaddr_storage *addr, + struct rudp_packet_chain *pc) +{ + struct rudp_client *client = __container_of(endpoint, client, endpoint); + + rudp_log_printf(client->rudp, RUDP_LOG_INFO, + "Endpoint handling packet\n"); + + rudp_error_t err = rudp_peer_incoming_packet(&client->peer, pc); + if ( err == 0 && client->connected == 0 ) + { + client->connected = 1; + client->handler->connected(client); + } +} + +static const struct rudp_endpoint_handler client_endpoint_handler = { + .handle_packet = client_handle_endpoint_packet, +}; + +rudp_error_t rudp_client_send( + struct rudp_client *client, + int reliable, int command, + const void *data, + const size_t size) +{ + if ( (command + RUDP_CMD_APP) > 255 ) + return EINVAL; + + if ( !client->connected ) + return EINVAL; + + struct rudp_packet_chain *pc = rudp_packet_chain_alloc( + client->rudp, sizeof(struct rudp_packet_header) + size); + if ( pc == NULL ) + return ENOMEM; + + memcpy(&pc->packet->data.data[0], data, size); + + pc->packet->header.command = RUDP_CMD_APP + command; + + if ( reliable ) + return rudp_peer_send_reliable(&client->peer, pc); + else + return rudp_peer_send_unreliable(&client->peer, pc); +} + +rudp_error_t rudp_client_set_hostname( + struct rudp_client *client, + const char *hostname, + const uint16_t port, + uint32_t ip_flags) +{ + return rudp_address_set_hostname(&client->address, + hostname, port, ip_flags); +} + +void rudp_client_set_ipv4( + struct rudp_client *client, + const struct in_addr *address, + const uint16_t port) +{ + return rudp_address_set_ipv4(&client->address, address, port); +} + +void rudp_client_set_ipv6( + struct rudp_client *client, + const struct in6_addr *address, + const uint16_t port) +{ + return rudp_address_set_ipv6(&client->address, address, port); +} diff --git a/src/endpoint.c b/src/endpoint.c new file mode 100644 index 0000000..bdeace7 --- /dev/null +++ b/src/endpoint.c @@ -0,0 +1,208 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#include +#include +#include +#include +#include +#include "rudp_packet.h" +#include "rudp_error.h" + +static void _endpoint_handle_incoming(struct ela_event_source *src, + int fd, uint32_t mask, void *data); + +void rudp_endpoint_init( + struct rudp_endpoint *endpoint, + struct rudp *rudp, + const struct rudp_endpoint_handler *handler) +{ + rudp_address_init(&endpoint->addr, rudp); + endpoint->socket_fd = -1; + endpoint->rudp = rudp; + endpoint->handler = handler; + ela_source_alloc(rudp->el, _endpoint_handle_incoming, + endpoint, &endpoint->ela_source); +} + + +void rudp_endpoint_deinit(struct rudp_endpoint *endpoint) +{ + rudp_address_deinit(&endpoint->addr); + ela_source_free(endpoint->rudp->el, endpoint->ela_source); +} + +/* + - socket watcher + - endpoint packet reader <=== + - server/client packet handler + */ +static void +_endpoint_handle_incoming(struct ela_event_source *src, + int fd, uint32_t mask, void *data) +{ + struct rudp_endpoint *endpoint = data; + struct rudp_packet_chain *pc = rudp_packet_chain_alloc( + endpoint->rudp, RUDP_RECV_BUFFER_SIZE); + struct sockaddr_storage addr; + + rudp_error_t ret = rudp_endpoint_recv( + endpoint, pc->packet, &pc->len, &addr); + + if ( ret != 0 ) + rudp_packet_chain_free(endpoint->rudp, pc); + + endpoint->handler->handle_packet(endpoint, &addr, pc); + + rudp_packet_chain_free(endpoint->rudp, pc); +} + +rudp_error_t rudp_endpoint_bind(struct rudp_endpoint *endpoint) +{ + const struct sockaddr_storage *addr; + socklen_t size; + rudp_error_t err = rudp_address_get(&endpoint->addr, &addr, &size); + + switch (err) { + case 0: + break; + case EDESTADDRREQ: + addr = NULL; + break; + default: + return err; + } + + int family = AF_INET; + + if ( addr ) + family = ((const struct sockaddr_in *)addr)->sin_family; + + int ret = socket(family, SOCK_DGRAM, 0); + + if ( ret == -1 ) + return errno; + + endpoint->socket_fd = ret; + + if ( addr ) + ret = bind(endpoint->socket_fd, + (const struct sockaddr *)addr, + size); + + if ( ret == -1 ) { + rudp_error_t e = errno; + + rudp_endpoint_close(endpoint); + + return e; + } + + ela_error_t eerr = ela_set_fd( + endpoint->rudp->el, endpoint->ela_source, + endpoint->socket_fd, + ELA_EVENT_READABLE); + + if ( eerr ) + goto ela_err; + + eerr = ela_add(endpoint->rudp->el, endpoint->ela_source); + + if ( eerr ) + goto ela_err; + + return 0; + +ela_err: + rudp_endpoint_close(endpoint); + return rudp_error_from_ela(eerr); +} + + +void rudp_endpoint_close(struct rudp_endpoint *endpoint) +{ + ela_remove(endpoint->rudp->el, endpoint->ela_source); + close(endpoint->socket_fd); + endpoint->socket_fd = -1; +} + +rudp_error_t rudp_endpoint_recv(struct rudp_endpoint *endpoint, + void *data, size_t *len, + struct sockaddr_storage *addr) +{ + int ret; + + size_t available = *len; + socklen_t slen = sizeof(*addr); + + ret = recvfrom(endpoint->socket_fd, data, available, 0, + (struct sockaddr *)addr, &slen); + + if ( ret == -1 ) + return errno; + + *len = ret; + + return 0; +} + +rudp_error_t rudp_endpoint_send(struct rudp_endpoint *endpoint, + const struct rudp_address *addr, + const void *data, size_t len) +{ + const struct sockaddr_storage *address; + socklen_t size; + + rudp_error_t err = rudp_address_get(addr, &address, &size); + if ( err ) + return err; + + int ret = sendto(endpoint->socket_fd, data, len, 0, + (const struct sockaddr *)address, + size); + + if ( ret == -1 ) + return errno; + + return 0; +} + +void rudp_endpoint_set_ipv4( + struct rudp_endpoint *endpoint, + const struct in_addr *address, + const uint16_t port) +{ + return rudp_address_set_ipv4(&endpoint->addr, address, port); +} + +void rudp_endpoint_set_ipv6( + struct rudp_endpoint *endpoint, + const struct in6_addr *address, + const uint16_t port) +{ + return rudp_address_set_ipv6(&endpoint->addr, address, port); +} + +rudp_error_t rudp_endpoint_set_hostname( + struct rudp_endpoint *endpoint, + const char *hostname, + const uint16_t port, + uint32_t ip_flags) +{ + return rudp_address_set_hostname(&endpoint->addr, + hostname, port, ip_flags); +} + +int rudp_endpoint_address_compare(const struct rudp_endpoint *endpoint, + const struct sockaddr_storage *addr) +{ + return rudp_address_compare(&endpoint->addr, addr); +} diff --git a/src/packet.c b/src/packet.c new file mode 100644 index 0000000..0fb1cf2 --- /dev/null +++ b/src/packet.c @@ -0,0 +1,89 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#include +#include "rudp_packet.h" +#include "rudp_list.h" + +const char *rudp_command_name(enum rudp_command cmd) +{ + switch (cmd) { + case RUDP_CMD_NOOP: return "RUDP_CMD_NOOP"; + case RUDP_CMD_CLOSE: return "RUDP_CMD_CLOSE"; + case RUDP_CMD_CONN_REQ: return "RUDP_CMD_CONN_REQ"; + case RUDP_CMD_CONN_RSP: return "RUDP_CMD_CONN_RSP"; + case RUDP_CMD_PING: return "RUDP_CMD_PING"; + case RUDP_CMD_PONG: return "RUDP_CMD_PONG"; + case RUDP_CMD_APP: return "RUDP_CMD_APP"; + default: + if ( (int) cmd < RUDP_CMD_APP ) + return "RUDP_CMD_invalid"; + else + return "RUDP_CMD_APP_CUSTOM"; + } +} + +#define DEFAULT_ALLOC_SIZE (RUDP_RECV_BUFFER_SIZE) +#define FREE_PACKET_POOL 10 + +struct rudp_packet_chain *rudp_packet_chain_alloc( + struct rudp *rudp, + size_t asked) +{ + size_t alloc = asked; + struct rudp_packet_chain *pc; + + if ( alloc < DEFAULT_ALLOC_SIZE ) + alloc = DEFAULT_ALLOC_SIZE; + + if ( alloc == DEFAULT_ALLOC_SIZE ) { + // We'll pass this is the list is empty as well + rudp_list_for_each(pc, &rudp->free_packet_list, chain_item) { + rudp_list_remove(&pc->chain_item); + rudp->free_packets--; + goto found; + } + } + + pc = rudp_alloc(rudp, sizeof(*pc)+alloc); + if ( pc == NULL ) + return NULL; + + rudp->allocated_packets++; + + pc->packet = (void*)(pc+1); + pc->alloc_size = alloc; + +found: + pc->len = asked; + return pc; +} + +void rudp_packet_chain_free(struct rudp *rudp, struct rudp_packet_chain *pc) +{ + if ( pc->alloc_size == DEFAULT_ALLOC_SIZE ) { + rudp_list_insert(&rudp->free_packet_list, &pc->chain_item); + rudp->free_packets++; + } else { + rudp_free(rudp, pc); + rudp->allocated_packets--; + } + + while ( rudp->free_packets > FREE_PACKET_POOL ) { + rudp_list_for_each(pc, &rudp->free_packet_list, chain_item) { + rudp_list_remove(&pc->chain_item); + rudp->free_packets--; + rudp->allocated_packets--; + rudp_free(rudp, pc); + break; + } + } +} diff --git a/src/peer.c b/src/peer.c new file mode 100644 index 0000000..7895b95 --- /dev/null +++ b/src/peer.c @@ -0,0 +1,738 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#define _BSD_SOURCE + +#include +#include +#include +#include + +#include +#include +#include +#include +#include "rudp_list.h" +#include "rudp_packet.h" + +/* Declarations */ + +static void peer_post_ack(struct rudp_peer *peer); +static rudp_error_t peer_send_raw( + struct rudp_peer *peer, + const void *data, size_t len); +static int peer_handle_ack(struct rudp_peer *peer, uint16_t ack); + +static void peer_service(struct rudp_peer *peer); +static void _peer_service(struct ela_event_source *src, + int fd, uint32_t mask, void *data); +static void peer_service_schedule(struct rudp_peer *peer); + +#define ACTION_TIMEOUT 5000 +#define DROP_TIMEOUT (ACTION_TIMEOUT * 2) +#define MAX_RTO 3000 + +enum peer_state +{ + PEER_NEW, + PEER_RUN, + PEER_CONNECTING, + PEER_DEAD, +}; + +/* Object management */ + +void rudp_peer_reset(struct rudp_peer *peer) +{ + struct rudp_packet_chain *pc, *tmp; + rudp_list_for_each_safe(pc, tmp, &peer->sendq, chain_item) + { + rudp_list_remove(&pc->chain_item); + rudp_packet_chain_free(peer->rudp, pc); + } + + if ( peer->scheduled ) + ela_remove(peer->rudp->el, peer->service_source); + peer->scheduled = 0; + + peer->abs_timeout_deadline = rudp_timestamp() + DROP_TIMEOUT; + peer->in_seq_reliable = (uint16_t)-1; + peer->in_seq_unreliable = 0; + peer->out_seq_reliable = rudp_random(peer->rudp); + peer->out_seq_unreliable = 0; + peer->out_seq_acked = peer->out_seq_reliable - 1; + peer->state = PEER_NEW; + peer->last_out_time = rudp_timestamp(); + peer->srtt = 100; + peer->rttvar = peer->srtt / 2; + peer->rto = MAX_RTO; + peer->must_ack = 0; + peer->sendto_err = 0; +} + +void rudp_peer_init( + struct rudp_peer *peer, + struct rudp *rudp, + const struct rudp_peer_handler *handler, + struct rudp_endpoint *endpoint) +{ + rudp_list_init(&peer->sendq); + rudp_address_init(&peer->address, rudp); + peer->endpoint = endpoint; + peer->rudp = rudp; + peer->handler = handler; + ela_source_alloc(rudp->el, _peer_service, peer, &peer->service_source); + peer->scheduled = 0; + + rudp_peer_reset(peer); + + peer_service_schedule(peer); +} + +void rudp_peer_deinit(struct rudp_peer *peer) +{ + rudp_peer_reset(peer); + rudp_address_deinit(&peer->address); + ela_source_free(peer->rudp->el, peer->service_source); + peer->service_source = NULL; +} + +static void peer_update_rtt(struct rudp_peer *peer, rudp_time_t last_rtt) +{ + peer->rttvar = (3 * peer->rttvar + labs(peer->srtt - last_rtt)) / 4; + peer->srtt = (7 * peer->srtt + last_rtt) / 8; + peer->rto = peer->srtt; + if ( peer->rto > MAX_RTO ) + peer->rto = MAX_RTO; + + rudp_log_printf(peer->rudp, RUDP_LOG_INFO, + "Timeout state: rttvar %d srtt %d rto %d\n", + (int)peer->rttvar, (int)peer->srtt, (int)peer->rto); +} + +static void peer_rto_backoff(struct rudp_peer *peer) +{ + peer->rto *= 2; + if ( peer->rto > MAX_RTO ) + peer->rto = MAX_RTO; + + rudp_log_printf(peer->rudp, RUDP_LOG_INFO, + "Timeout state: rttvar %d srtt %d rto %d\n", + (int)peer->rttvar, (int)peer->srtt, (int)peer->rto); +} + +void rudp_peer_from_sockaddr( + struct rudp_peer *peer, + struct rudp *rudp, + const struct sockaddr_storage *addr, + const struct rudp_peer_handler *handler, + struct rudp_endpoint *endpoint) +{ + rudp_peer_init(peer, rudp, handler, endpoint); + + struct sockaddr_in6 *p6 = (struct sockaddr_in6 *)addr; + struct sockaddr_in *p = (struct sockaddr_in *)addr; + + if ( p->sin_family == AF_INET ) + rudp_address_set_ipv4(&peer->address, + &p->sin_addr, ntohs(p->sin_port)); + else + rudp_address_set_ipv6(&peer->address, + &p6->sin6_addr, ntohs(p6->sin6_port)); +} + +/* Sync handling */ + +enum packet_state +{ + SEQUENCED, + UNSEQUENCED, + RETRANSMITTED, +}; + +static +enum packet_state peer_analyse_reliable( + struct rudp_peer *peer, + uint16_t reliable_seq) +{ + if ( peer->in_seq_reliable == reliable_seq ) + return RETRANSMITTED; + + if ( (uint16_t)(peer->in_seq_reliable + 1) != reliable_seq ) { + rudp_log_printf(peer->rudp, RUDP_LOG_WARN, + "%s unsequenced last seq %04x packet %04x\n", + __FUNCTION__, peer->in_seq_reliable, reliable_seq); + return UNSEQUENCED; + } + + peer->in_seq_reliable = reliable_seq; + peer->in_seq_unreliable = 0; + + return SEQUENCED; +} + +static +enum packet_state peer_analyse_unreliable( + struct rudp_peer *peer, + uint16_t reliable_seq, + uint16_t unreliable_seq) +{ + rudp_log_printf(peer->rudp, RUDP_LOG_IO, + "%s rel %04x == %04x, unrel %04x >= %04x\n", + __FUNCTION__, + peer->in_seq_reliable, reliable_seq, + unreliable_seq, peer->in_seq_unreliable); + + if ( peer->in_seq_reliable != reliable_seq ) + return UNSEQUENCED; + + int16_t unreliable_delta = unreliable_seq - peer->in_seq_unreliable; + + if ( unreliable_delta <= 0 ) + return UNSEQUENCED; + + peer->in_seq_unreliable = unreliable_seq; + + return SEQUENCED; +} + +/* Send function */ + +static void peer_ping(struct rudp_peer *peer) +{ + struct rudp_packet_chain *pc = rudp_packet_chain_alloc( + peer->rudp, + sizeof(struct rudp_packet_header) + sizeof(rudp_time_t) + ); + struct rudp_packet_data *data = &pc->packet->data; + + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + "%s pushing PING\n", __FUNCTION__); + + rudp_time_t now = rudp_timestamp(); + data->header.command = RUDP_CMD_PING; + memcpy(data->data, &now, sizeof(now)); + + rudp_peer_send_reliable(peer, pc); +} + +/* Receiver functions */ + +static +void peer_handle_ping( + struct rudp_peer *peer, + const struct rudp_packet_chain *in) +{ + /* + We cant take RTT stats from retransmitted packets. + Generic calling code still generates an ACK. + */ + if ( in->packet->header.opt & RUDP_OPT_RETRANSMITTED ) + return; + + struct rudp_packet_chain *out = + rudp_packet_chain_alloc(peer->rudp, in->len); + struct rudp_packet_header *header = &out->packet->header; + + header->command = RUDP_CMD_PONG; + header->opt = 0; + + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + "%s answering to ping\n", __FUNCTION__); + + memcpy(&out->packet->data.data[0], + &in->packet->data.data[0], + in->len - sizeof(struct rudp_packet_header)); + + rudp_peer_send_unreliable(peer, out); +} + +static +void peer_handle_pong( + struct rudp_peer *peer, + const struct rudp_packet_chain *pc) +{ + rudp_time_t orig, delta; + memcpy(&orig, pc->packet->data.data, sizeof(orig)); + + delta = rudp_timestamp() - orig; + + peer_update_rtt(peer, delta); +} + +static void peer_service_schedule(struct rudp_peer *peer) +{ + // If nothing in sendq: reschedule service for later + rudp_time_t delta = ACTION_TIMEOUT; + + // just abuse for_each to get head, if it exists + struct rudp_packet_chain *head; + rudp_list_for_each(head, &peer->sendq, chain_item) + { + struct rudp_packet_header *header = &head->packet->header; + + if ( header->opt & RUDP_OPT_RETRANSMITTED ) + // already transmitted head, wait for rto + delta = rudp_timestamp() - peer->last_out_time + peer->rto; + else + // transmit asap + delta = 0; + + // We dont really want to iterate after head + break; + } + + rudp_time_t to_delta = peer->abs_timeout_deadline - rudp_timestamp(); + + if ( to_delta < delta ) + delta = to_delta; + + if ( delta <= 0 ) + delta = 1; + + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + "%s:%d Idle, service scheduled for %d\n", + __FUNCTION__, __LINE__, + (int)delta); + + struct timeval tv; + rudp_timestamp_to_timeval(&tv, delta); + + ela_set_timeout(peer->rudp->el, peer->service_source, &tv, ELA_EVENT_ONCE); + ela_add(peer->rudp->el, peer->service_source); + peer->scheduled = 1; +} + +static +void peer_handle_connreq( + struct rudp_peer *peer, + const struct rudp_packet_header *header) +{ + struct rudp_packet_chain *pc = + rudp_packet_chain_alloc(peer->rudp, + sizeof(struct rudp_packet_conn_rsp)); + struct rudp_packet_conn_rsp *response = &pc->packet->conn_rsp; + + response->header.command = RUDP_CMD_CONN_RSP; + response->header.opt = 0; + response->accepted = htonl(1); + + rudp_log_printf(peer->rudp, RUDP_LOG_INFO, + "%s answering to connreq\n", __FUNCTION__); + + rudp_peer_send_unreliable(peer, pc); + + peer_service_schedule(peer); +} + +/* + - socket watcher + - endpoint packet reader + - server packet handler + - peer packet handler <=== + */ +rudp_error_t rudp_peer_incoming_packet( + struct rudp_peer *peer, struct rudp_packet_chain *pc) +{ + const struct rudp_packet_header *header = &pc->packet->header; + + rudp_log_printf(peer->rudp, RUDP_LOG_IO, + "<<< incoming [%d] %s %s (%d) %04x:%04x\n", + peer->state, + (header->opt & RUDP_OPT_RELIABLE) + ? "reliable" : "unreliable", + rudp_command_name(header->command), header->command, + ntohs(header->reliable), ntohs(header->unreliable)); + + if ( header->opt & RUDP_OPT_ACK ) { + rudp_log_printf(peer->rudp, RUDP_LOG_IO, + " has ACK flag, %04x\n", + (int)ntohs(header->reliable_ack)); + int broken = peer_handle_ack(peer, ntohs(header->reliable_ack)); + if ( broken ) { + rudp_log_printf(peer->rudp, RUDP_LOG_WARN, + " broken ACK flag, ignoring packet\n"); + return EINVAL; + } + } + + enum packet_state state; + + if ( header->opt & RUDP_OPT_RELIABLE ) + state = peer_analyse_reliable(peer, ntohs(header->reliable)); + else + state = peer_analyse_unreliable(peer, ntohs(header->reliable), + ntohs(header->unreliable)); + + switch ( state ) { + case UNSEQUENCED: + if (peer->state == PEER_NEW + && header->command == RUDP_CMD_CONN_REQ) { + // Server side, handling new client + peer_handle_connreq(peer, header); + peer->in_seq_reliable = ntohs(header->reliable); + peer->state = PEER_RUN; + } else if (peer->state == PEER_CONNECTING + && header->command == RUDP_CMD_CONN_RSP) { + // Client side, handling new server + peer->in_seq_reliable = ntohs(header->reliable); + peer_handle_ack(peer, ntohs(header->reliable_ack)); + peer->state = PEER_RUN; + } else { + rudp_log_printf(peer->rudp, RUDP_LOG_WARN, + " unsequenced packet in state %d, ignored\n", + peer->state); + } + break; +// return RUDP_EINVAL; + + case RETRANSMITTED: + peer->abs_timeout_deadline = rudp_timestamp() + DROP_TIMEOUT; + break; + + case SEQUENCED: + peer->abs_timeout_deadline = rudp_timestamp() + DROP_TIMEOUT; + + switch ( header->command ) + { + case RUDP_CMD_CLOSE: + peer->state = PEER_DEAD; + peer->handler->dropped(peer); + rudp_log_printf(peer->rudp, RUDP_LOG_INFO, + " peer dropped\n"); + return 0; + + break; + + case RUDP_CMD_PING: + if ( peer->state == PEER_RUN ) { + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + " ping\n"); + peer_handle_ping(peer, pc); + } else { + rudp_log_printf(peer->rudp, RUDP_LOG_WARN, + " ping while not running\n"); + } + break; + + case RUDP_CMD_PONG: + if ( peer->state == PEER_RUN ) { + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + " pong\n"); + peer_handle_pong(peer, pc); + } else { + rudp_log_printf(peer->rudp, RUDP_LOG_WARN, + " pong while not running\n"); + } + break; + + case RUDP_CMD_NOOP: + case RUDP_CMD_CONN_REQ: + case RUDP_CMD_CONN_RSP: + break; + + default: + if ( peer->state != PEER_RUN ) { + rudp_log_printf(peer->rudp, RUDP_LOG_WARN, + " user payload while not running\n"); + break; + } + + if ( header->command >= RUDP_CMD_APP ) + peer->handler->handle_packet(peer, pc); + } + } + + if ( header->opt & RUDP_OPT_RELIABLE ) { + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + " reliable packet, posting ack\n"); + peer_post_ack(peer); + } + + return 0; +} + + +/* Ack handling function */ + + +static +int peer_handle_ack(struct rudp_peer *peer, uint16_t ack) +{ + int16_t ack_delta = (ack - peer->out_seq_acked); + int16_t adv_delta = (ack - peer->out_seq_reliable); + + if ( ack_delta < 0 ) + // ack in past + return 0; + + if ( adv_delta > 0 ) + // packet acking an unsent seq no -- broken packet + return 1; + + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + "%s acked seqno is now %04x\n", __FUNCTION__, ack); + + peer->out_seq_acked = ack; + + struct rudp_packet_chain *pc, *tmp; + rudp_list_for_each_safe(pc, tmp, &peer->sendq, chain_item) + { + struct rudp_packet_header *header = &pc->packet->header; + uint16_t seqno = ntohs(header->reliable); + int16_t delta = (seqno - ack); + + // not transmitted yet: + // - unreliable packets, if they are still here + // - reliable packet not marked retransmitted + if ( ! (header->opt & RUDP_OPT_RELIABLE) + || ! (header->opt & RUDP_OPT_RETRANSMITTED) ) + break; + + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + "%s (ack=%04x) considering unqueueing" + " packet id %04x, delta %d: %s\n", + __FUNCTION__, + ack, seqno, delta, delta > 0 ? "no": "yes"); + + if ( delta > 0 ) + break; + + rudp_list_remove(&pc->chain_item); + rudp_packet_chain_free(peer->rudp, pc); + } + + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + "%s left in queue:\n", + __FUNCTION__); + rudp_list_for_each(pc, &peer->sendq, chain_item) { + struct rudp_packet_header *header = &pc->packet->header; + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + "%s - %04x:%04x\n", + __FUNCTION__, + ntohs(header->reliable), + ntohs(header->unreliable)); + } + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + "%s ---\n", + __FUNCTION__); + + return 0; +} + + +/* + Ack field is present in all headers. Therefore any packet can be an + ack. If the send queue is empty, we cant afford to wait for a new + one, so we send a new NOOP. + */ +static +void peer_post_ack(struct rudp_peer *peer) +{ + peer->must_ack = 1; + + if ( ! rudp_list_empty(&peer->sendq) ) { + return; + } + + struct rudp_packet_chain *pc = rudp_packet_chain_alloc( + peer->rudp, sizeof(struct rudp_packet_conn_rsp)); + struct rudp_packet_header *header = &pc->packet->header; + + rudp_log_printf(peer->rudp, RUDP_LOG_DEBUG, + "%s pushing NOOP ACK\n", __FUNCTION__); + + header->command = RUDP_CMD_NOOP; + + rudp_peer_send_unreliable(peer, pc); + + peer_service_schedule(peer); +} + + +/* Sender functions */ + +rudp_error_t rudp_peer_send_unreliable( + struct rudp_peer *peer, + struct rudp_packet_chain *pc) +{ + pc->packet->header.opt = 0; + pc->packet->header.reliable = htons(peer->out_seq_reliable); + pc->packet->header.unreliable = htons(++(peer->out_seq_unreliable)); + + rudp_log_printf(peer->rudp, RUDP_LOG_IO, + ">>> outgoing unreliable %s (%d) %04x:%04x\n", + rudp_command_name(pc->packet->header.command), + pc->packet->header.command, + ntohs(pc->packet->header.reliable), + ntohs(pc->packet->header.unreliable)); + + rudp_list_append(&peer->sendq, &pc->chain_item); + peer_service_schedule(peer); + return peer->sendto_err; +} + +rudp_error_t rudp_peer_send_reliable( + struct rudp_peer *peer, + struct rudp_packet_chain *pc) +{ + pc->packet->header.opt = RUDP_OPT_RELIABLE; + pc->packet->header.reliable = htons(++(peer->out_seq_reliable)); + pc->packet->header.unreliable = 0; + peer->out_seq_unreliable = 0; + + rudp_log_printf(peer->rudp, RUDP_LOG_IO, + ">>> outgoing reliable %s (%d) %04x:%04x\n", + rudp_command_name(pc->packet->header.command), + pc->packet->header.command, + ntohs(pc->packet->header.reliable), + ntohs(pc->packet->header.unreliable)); + + rudp_list_append(&peer->sendq, &pc->chain_item); + peer_service_schedule(peer); + return peer->sendto_err; +} + +static +rudp_error_t peer_send_raw( + struct rudp_peer *peer, + const void *data, size_t len) +{ + peer->sendto_err = rudp_endpoint_send( + peer->endpoint, &peer->address, data, len); + + peer->last_out_time = rudp_timestamp(); + return peer->sendto_err; +} + +rudp_error_t rudp_peer_send_connect(struct rudp_peer *peer) +{ + struct rudp_packet_chain *pc = rudp_packet_chain_alloc( + peer->rudp, sizeof(struct rudp_packet_conn_req)); + + pc->packet->header.command = RUDP_CMD_CONN_REQ; + + peer->state = PEER_CONNECTING; + + return rudp_peer_send_reliable(peer, pc); +} + +rudp_error_t rudp_peer_send_close_noqueue(struct rudp_peer *peer) +{ + struct rudp_packet_header header = { + .command = RUDP_CMD_CLOSE, + .opt = 0, + }; + + header.opt = 0; + header.reliable = htons(peer->out_seq_reliable); + header.unreliable = htons(++(peer->out_seq_unreliable)); + + rudp_log_printf(peer->rudp, RUDP_LOG_IO, + ">>> outgoing noqueue %s (%d) %04x:%04x\n", + rudp_command_name(header.command), + ntohs(header.reliable), + header.reliable, + ntohs(header.unreliable)); + + return peer_send_raw(peer, &header, sizeof(header)); +} + +/* Worker functions */ + +static void peer_send_queue(struct rudp_peer *peer) +{ + struct rudp_packet_chain *pc, *tmp; + rudp_list_for_each_safe(pc, tmp, &peer->sendq, chain_item) + { + struct rudp_packet_header *header = &pc->packet->header; + + if ( peer->must_ack ) { + header->opt |= RUDP_OPT_ACK; + header->reliable_ack = htons(peer->in_seq_reliable); +// peer->must_ack = 0; + } + + rudp_log_printf(peer->rudp, RUDP_LOG_IO, + ">>>>>> %ssend %sreliable %s %04x:%04x %s %04x\n", + header->opt & RUDP_OPT_RETRANSMITTED ? "RE" : "", + header->opt & RUDP_OPT_RELIABLE ? "" : "un", + rudp_command_name(pc->packet->header.command), + ntohs(pc->packet->header.reliable), + ntohs(pc->packet->header.unreliable), + header->opt & RUDP_OPT_ACK ? "ack" : "noack", + ntohs(pc->packet->header.reliable_ack)); + + peer_send_raw(peer, header, pc->len); + + if ( (header->opt & RUDP_OPT_RELIABLE) + && (header->opt & RUDP_OPT_RETRANSMITTED) ) { + peer_rto_backoff(peer); + break; + } + + if ( header->opt & RUDP_OPT_RELIABLE ) { + header->opt |= RUDP_OPT_RETRANSMITTED; +// break; + } else { + rudp_list_remove(&pc->chain_item); + rudp_packet_chain_free(peer->rudp, pc); + } + } +} + + + +/* + Two reasons may bring us here: + + - There are some (un)reliable items in the send queue, either + - we are retransmitting + - we just enqueued something and we need to send + + - There is nothing in the send queue and we want to ensure the peer + is still up + */ +static void peer_service(struct rudp_peer *peer) +{ + peer->scheduled = 0; + + if ( peer->abs_timeout_deadline < rudp_timestamp() ) { + peer->handler->dropped(peer); + return; + } + + if ( rudp_list_empty(&peer->sendq) ) { + /* + Nothing was in the send queue, so we may be in a timeout + situation. Handle retries and final timeout. + */ + rudp_time_t out_delta = rudp_timestamp() - peer->last_out_time; + if ( out_delta > ACTION_TIMEOUT ) + peer_ping(peer); + } + + peer_send_queue(peer); + + peer_service_schedule(peer); +} + +static void _peer_service(struct ela_event_source *src, + int fd, uint32_t mask, void *data) +{ + return peer_service((struct rudp_peer *)data); +} + +int rudp_peer_address_compare(const struct rudp_peer *peer, + const struct sockaddr_storage *addr) +{ + return rudp_address_compare(&peer->address, addr); +} diff --git a/src/rudp.c b/src/rudp.c new file mode 100644 index 0000000..194d24d --- /dev/null +++ b/src/rudp.c @@ -0,0 +1,72 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#include +#include +#include +#include "rudp_list.h" +#include "rudp_rudp.h" + +#include + +rudp_error_t rudp_init( + struct rudp *rudp, + struct ela_el *el, + const struct rudp_handler *handler) +{ + rudp->handler = handler; + rudp->el = el; + + rudp_list_init(&rudp->free_packet_list); + rudp->free_packets = 0; + rudp->allocated_packets = 0; + + rudp->seed = rudp_timestamp(); + rudp_random(rudp); + rudp_random(rudp); + rudp_random(rudp); + + return 0; +} + +static +void *_rudp_default_alloc(struct rudp *rudp, size_t len) +{ + return malloc(len); +} + +static +void _rudp_default_free(struct rudp *rudp, void *buffer) +{ + return free(buffer); +} + +RUDP_EXPORT +const struct rudp_handler rudp_handler_default = +{ + .log = NULL, + .mem_alloc = _rudp_default_alloc, + .mem_free = _rudp_default_free, +}; + +void rudp_deinit(struct rudp *rudp) +{ + struct rudp_packet_chain *pc, *tmp; + rudp_list_for_each_safe(pc, tmp, &rudp->free_packet_list, chain_item) { + rudp_list_remove(&pc->chain_item); + rudp_free(rudp, pc); + } +} + +uint16_t rudp_random(struct rudp *rudp) +{ + return rand_r(&rudp->seed); +} diff --git a/src/rudp_error.h b/src/rudp_error.h new file mode 100644 index 0000000..81e72cf --- /dev/null +++ b/src/rudp_error.h @@ -0,0 +1,21 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_ERROR_IMPL_H +#define RUDP_ERROR_IMPL_H + +static inline +rudp_error_t rudp_error_from_ela(ela_error_t e) +{ + return e; +} + +#endif diff --git a/src/rudp_list.h b/src/rudp_list.h new file mode 100644 index 0000000..8d8aa0c --- /dev/null +++ b/src/rudp_list.h @@ -0,0 +1,96 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_LIST_IMPL_H_ +#define RUDP_LIST_IMPL_H_ + +#include + +#define __container_of(ptr, sample, member) \ + (void *)((char *)(ptr) - \ + ((char *)&(sample)->member - (char *)(sample))) + +#define rudp_list_for_each(pos, head, member) \ + for (pos = 0, pos = __container_of((head)->next, pos, member); \ + &pos->member != (head); \ + pos = __container_of(pos->member.next, pos, member)) + +#define rudp_list_for_each_safe(pos, tmp, head, member) \ + for (pos = 0, tmp = 0, \ + pos = __container_of((head)->next, pos, member), \ + tmp = __container_of((pos)->member.next, tmp, member); \ + &pos->member != (head); \ + pos = tmp, \ + tmp = __container_of(pos->member.next, tmp, member)) + +#define rudp_list_for_each_reverse(pos, head, member) \ + for (pos = 0, pos = __container_of((head)->prev, pos, member); \ + &pos->member != (head); \ + pos = __container_of(pos->member.prev, pos, member)) + +static inline void +rudp_list_init(struct rudp_list *list) +{ + list->prev = list; + list->next = list; +} + +static inline void +rudp_list_insert(struct rudp_list *list, struct rudp_list *elm) +{ + elm->prev = list; + elm->next = list->next; + list->next = elm; + elm->next->prev = elm; +} + +static inline void +rudp_list_append(struct rudp_list *list, struct rudp_list *elm) +{ + elm->next = list; + elm->prev = list->prev; + list->prev = elm; + elm->prev->next = elm; +} + +static inline void +rudp_list_remove(struct rudp_list *elm) +{ + elm->prev->next = elm->next; + elm->next->prev = elm->prev; +} + +static inline int +rudp_list_length(struct rudp_list *list) +{ + struct rudp_list *e; + int count; + + count = 0; + e = list->next; + while (e != list) { + e = e->next; + count++; + } + + return count; +} + +static inline int +rudp_list_empty(struct rudp_list *elm) +{ + return elm->next == elm; +} + +#define rudp_list_head(head, type, member) \ + (type*)__container_of((head)->next, (type*)0, member); + +#endif diff --git a/src/rudp_packet.h b/src/rudp_packet.h new file mode 100644 index 0000000..1a608d1 --- /dev/null +++ b/src/rudp_packet.h @@ -0,0 +1,31 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_PACKET_IMPL_H +#define RUDP_PACKET_IMPL_H + +#include +#include +#include +#include "rudp_rudp.h" +#include "rudp_packet.h" + +#define RUDP_RECV_BUFFER_SIZE 4096 + +struct rudp_packet_chain *rudp_packet_chain_alloc( + struct rudp *rudp, + size_t alloc); + +void rudp_packet_chain_free( + struct rudp *rudp, + struct rudp_packet_chain *pc); + +#endif diff --git a/src/rudp_rudp.h b/src/rudp_rudp.h new file mode 100644 index 0000000..34edddc --- /dev/null +++ b/src/rudp_rudp.h @@ -0,0 +1,46 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#ifndef RUDP_LOG_IMPL_H +#define RUDP_LOG_IMPL_H + +#include + +static inline +void rudp_log_printf( + struct rudp *rudp, + const enum rudp_log_level level, + const char *fmt, ...) +{ + if ( rudp->handler->log == NULL ) + return; + + va_list arg; + va_start(arg, fmt); + + rudp->handler->log(rudp, level, fmt, arg); + + va_end(arg); +} + +static inline +void *rudp_alloc(struct rudp *rudp, size_t len) +{ + return rudp->handler->mem_alloc(rudp, len); +} + +static inline +void rudp_free(struct rudp *rudp, void *buffer) +{ + return rudp->handler->mem_free(rudp, buffer); +} + +#endif diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..5028e6a --- /dev/null +++ b/src/server.c @@ -0,0 +1,328 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + + +#include +#include +#include + +#include +#include +#include +#include "rudp_list.h" +#include "rudp_packet.h" +#include "rudp_rudp.h" + +struct server_peer +{ + struct rudp_peer base; + struct rudp_list server_item; + struct rudp_server *server; + void *user_data; +}; + +static const struct rudp_endpoint_handler server_endpoint_handler; + +rudp_error_t rudp_server_init( + struct rudp_server *server, + struct rudp *rudp, + const struct rudp_server_handler *handler) +{ + rudp_endpoint_init(&server->endpoint, rudp, &server_endpoint_handler); + rudp_list_init(&server->peer_list); + server->handler = handler; + server->rudp = rudp; + return 0; +} + +rudp_error_t rudp_server_bind(struct rudp_server *server) +{ + rudp_error_t err = rudp_endpoint_bind(&server->endpoint); + + if ( err ) + rudp_log_printf(server->endpoint.rudp, RUDP_LOG_ERROR, + "Binding of server to %s failed\n", + rudp_address_text(&server->endpoint.addr)); + else + rudp_log_printf(server->endpoint.rudp, RUDP_LOG_INFO, + "Bound server to %s\n", + rudp_address_text(&server->endpoint.addr)); + + return err; +} + +static void server_peer_forget(struct rudp_server *server, + struct server_peer *peer) +{ + rudp_list_remove(&peer->server_item); + rudp_peer_deinit(&peer->base); + rudp_free(server->rudp, peer); +} + +void rudp_server_client_close(struct rudp_server *server, + struct rudp_peer *_peer) +{ + struct server_peer *peer = (struct server_peer *)_peer; + + server_peer_forget(server, peer); +} + +rudp_error_t rudp_server_close(struct rudp_server *server) +{ + struct server_peer *peer, *tmp; + rudp_list_for_each_safe(peer, tmp, &server->peer_list, server_item) + { + peer->server->handler->peer_dropped(peer->server, &peer->base); + rudp_server_client_close(server, &peer->base); + } + + rudp_endpoint_close(&server->endpoint); + return 0; +} + +rudp_error_t rudp_server_deinit(struct rudp_server *server) +{ + rudp_endpoint_deinit(&server->endpoint); + rudp_list_init(&server->peer_list); + return 0; +} + +static +struct server_peer *rudp_server_peer_lookup(struct rudp_server *server, + const struct sockaddr_storage *addr) +{ + struct server_peer *peer; + rudp_list_for_each(peer, &server->peer_list, server_item) + { + if ( ! rudp_peer_address_compare(&peer->base, addr) ) + return peer; + } + return NULL; +} + +static +void server_handle_data_packet(struct rudp_peer *_peer, + struct rudp_packet_chain *pc) +{ + struct server_peer *peer = (struct server_peer *)_peer; + struct rudp_packet_data *header = &pc->packet->data; + + peer->server->handler->handle_packet( + peer->server, &peer->base, + header->header.command - RUDP_CMD_APP, + header->data, pc->len - sizeof(header)); +} + +static +void server_link_info(struct rudp_peer *_peer, + struct rudp_link_info *info) +{ + struct server_peer *peer = (struct server_peer *)_peer; + + peer->server->handler->link_info(peer->server, _peer, info); +} + +static +void server_peer_dropped(struct rudp_peer *_peer) +{ + struct server_peer *peer = (struct server_peer *)_peer; + + rudp_log_printf(peer->base.rudp, RUDP_LOG_INFO, "Peer dropped\n"); + + peer->server->handler->peer_dropped(peer->server, _peer); + + server_peer_forget(peer->server, peer); +} + +static const struct rudp_peer_handler server_peer_handler = { + .handle_packet = server_handle_data_packet, + .link_info = server_link_info, + .dropped = server_peer_dropped, +}; + +static struct server_peer *server_peer_new(struct rudp_server *server, + const struct sockaddr_storage *addr) +{ + struct server_peer *peer = rudp_alloc(server->rudp, sizeof(*peer)); + + if ( peer == NULL ) + return NULL; + + rudp_peer_from_sockaddr( + &peer->base, server->rudp, + addr, &server_peer_handler, + &server->endpoint); + + rudp_log_printf(server->rudp, RUDP_LOG_INFO, "New connection\n"); + + rudp_list_insert(&server->peer_list, &peer->server_item); + + peer->server = server; + peer->user_data = NULL; + + return peer; +} + +/* + - socket watcher + - endpoint packet reader + - server packet handler <=== + - new peer + - peer packet handler + + If we can't find the source address, packet may be from a new peer + or maybe garbage data. We wont know until we pass the handshaking. + Create a peer and answer unless we are absolutely sure it is + garbage. + */ +static +void server_handle_endpoint_packet(struct rudp_endpoint *endpoint, + const struct sockaddr_storage *addr, + struct rudp_packet_chain *pc) +{ + struct rudp_server *server = __container_of(endpoint, server, endpoint); + struct server_peer *peer = rudp_server_peer_lookup(server, addr); + rudp_error_t err; + + if ( peer != NULL ) { + rudp_peer_incoming_packet(&peer->base, pc); + return; + } + + struct rudp_packet_header *header = &pc->packet->header; + + if ( pc->len != sizeof(struct rudp_packet_conn_req) + || header->command != RUDP_CMD_CONN_REQ ) + goto garbage; + + peer = server_peer_new(server, addr); + if ( peer == NULL ) + return; + + err = rudp_peer_incoming_packet(&peer->base, pc); + if ( err == 0 ) + server->handler->peer_new(server, &peer->base); + else + server_peer_forget(server, peer); + return; + +garbage: + rudp_log_printf(server->rudp, RUDP_LOG_DEBUG, "Garbage data\n"); +} + +static const struct rudp_endpoint_handler server_endpoint_handler = { + .handle_packet = server_handle_endpoint_packet, +}; + +/***/ + +rudp_error_t rudp_server_send( + struct rudp_server *server, + struct rudp_peer *peer, + int reliable, int command, + const void *data, + const size_t size) +{ + if ( (command + RUDP_CMD_APP) > 255 ) + return EINVAL; + + struct rudp_packet_chain *pc = rudp_packet_chain_alloc( + server->rudp, sizeof(struct rudp_packet_header) + size); + + if ( pc == NULL ) + return ENOMEM; + + memcpy(&pc->packet->data.data[0], data, size); + + pc->packet->header.command = RUDP_CMD_APP + command; + + if ( reliable ) + return rudp_peer_send_reliable(peer, pc); + else + return rudp_peer_send_unreliable(peer, pc); +} + +rudp_error_t rudp_server_send_all( + struct rudp_server *server, + int reliable, int command, + const void *data, + const size_t size) +{ + if ( (command + RUDP_CMD_APP) > 255 ) + return EINVAL; + + struct server_peer *peer, *tmp; + rudp_list_for_each_safe(peer, tmp, &server->peer_list, server_item) + { + rudp_server_send(server, &peer->base, reliable, command, data, size); + } + return 0; +} + + +/* + For the two following functions, server context pointer is actually + useless. + + It is only here for safety purposes: Users eager to store private + data for a peer may be tempted to use a function like + rudp_server_peer_data_set(peer, data) even if peer were not from a + server. + + Asking users to give reference to the server make them think + something is wrong if they call rudp_server_peer_data_set(NULL, + peer, data) + */ + +void *rudp_server_peer_data_get( + struct rudp_server *server, + struct rudp_peer *_peer) +{ + (void)server; + struct server_peer *peer = (struct server_peer *)_peer; + return peer->user_data; +} + +void rudp_server_peer_data_set( + struct rudp_server *server, + struct rudp_peer *_peer, + void *data) +{ + (void)server; + struct server_peer *peer = (struct server_peer *)_peer; + peer->user_data = data; +} + +rudp_error_t rudp_server_set_hostname( + struct rudp_server *server, + const char *hostname, + const uint16_t port, + uint32_t ip_flags) +{ + return rudp_endpoint_set_hostname(&server->endpoint, + hostname, port, ip_flags); +} + +void rudp_server_set_ipv4( + struct rudp_server *server, + const struct in_addr *address, + const uint16_t port) +{ + return rudp_endpoint_set_ipv4(&server->endpoint, address, port); +} + +void rudp_server_set_ipv6( + struct rudp_server *server, + const struct in6_addr *address, + const uint16_t port) +{ + return rudp_endpoint_set_ipv6(&server->endpoint, address, port); +} diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000..67513d2 --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,10 @@ + +bin_PROGRAMS = test-server test-client + +test_server_SOURCES = test-server.c verbose.c +test_server_LDADD = $(top_builddir)/src/librudp.la $(ELA_LIBS) +test_server_CFLAGS = -I$(top_srcdir)/include $(ELA_CFLAGS) + +test_client_SOURCES = test-client.c verbose.c +test_client_LDADD = $(top_builddir)/src/librudp.la $(ELA_LIBS) +test_client_CFLAGS = -I$(top_srcdir)/include $(ELA_CFLAGS) diff --git a/test/test-client.c b/test/test-client.c new file mode 100644 index 0000000..1ee45a1 --- /dev/null +++ b/test/test-client.c @@ -0,0 +1,123 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#include +#include +#include +#include + +#include +#include + +#define display_err(statement,r) \ + do { \ + rudp_error_t err = statement; \ + printf("%s:%d %s: %s\n", __FILE__, __LINE__, #statement, strerror(err)); \ + if (err) return r; \ + } while(0) + +static +void handle_packet( + struct rudp_client *client, + int command, const void *data, size_t len) +{ + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + printf(">>> command %d, message '''", command); + fwrite(data, 1, len, stdout); + printf("'''\n"); + if ( !strncmp((const char *)data, "quit", 4) ) + ela_exit(client->rudp->el); +} + +static +void link_info(struct rudp_client *client, struct rudp_link_info *info) +{ + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); +} + +static +void server_lost(struct rudp_client *client) +{ + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + + display_err( rudp_client_connect(client), ); +} + +static +void connected(struct rudp_client *client) +{ + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); +} + +static const struct rudp_client_handler handler = { + .handle_packet = handle_packet, + .link_info = link_info, + .server_lost = server_lost, + .connected = connected, +}; + +static +void handle_stdin( + struct ela_event_source *source, + int fd, uint32_t mask, void *data) +{ + struct rudp_client *client = data; + char buffer[512], *tmp; + + tmp = fgets(buffer, 512, stdin); + ssize_t size = strlen(tmp); + rudp_client_send(client, 1, 0, tmp, size); +} + +extern const struct rudp_handler verbose_handler; + +int main(int argc, char **argv) +{ + struct rudp_client client; + struct ela_el *el = ela_create(NULL); + struct ela_event_source *source; + struct rudp rudp; + const struct rudp_handler *my_handler = RUDP_HANDLER_DEFAULT; + const char *peer = "127.0.0.1"; + int peer_no = 1; + + if ( argc > 1 && !strcmp(argv[1], "-v") ) { + my_handler = &verbose_handler; + peer_no = 2; + } + + if ( argc > peer_no ) + peer = argv[peer_no]; + + rudp_init(&rudp, el, my_handler); + + ela_source_alloc(el, handle_stdin, &client, &source); + + display_err( rudp_client_init(&client, &rudp, &handler) , 1); + rudp_client_set_hostname(&client, peer, 4242, 0); + display_err( rudp_client_connect(&client) , 1); + + ela_set_fd(el, source, 0, ELA_EVENT_READABLE); + ela_add(el, source); + + ela_run(el); + + ela_remove(el, source); + ela_source_free(el, source); + + display_err( rudp_client_close(&client) , 1); + display_err( rudp_client_deinit(&client) , 1); + + rudp_deinit(&rudp); + ela_close(el); + + return 0; +} diff --git a/test/test-server.c b/test/test-server.c new file mode 100644 index 0000000..e336aaf --- /dev/null +++ b/test/test-server.c @@ -0,0 +1,119 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#include +#include +#include +#include + +#include +#include + +#define display_err(statement) \ + do { \ + rudp_error_t err = statement; \ + printf("%s:%d %s: %s\n", __FILE__, __LINE__, #statement, strerror(err)); \ + } while(0) + +static +void handle_packet(struct rudp_server *server, + struct rudp_peer *peer, + int command, const void *data, size_t len) +{ + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + printf(">>> command %d message '''", command); + fwrite(data, 1, len, stdout); + printf("'''\n"); + if ( !strncmp((const char *)data, "quit", 4) ) + ela_exit(server->rudp->el); +} + +static +void link_info( + struct rudp_server *server, + struct rudp_peer *peer, + struct rudp_link_info *info) +{ + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); +} + +static +void peer_dropped(struct rudp_server *server, struct rudp_peer *peer) +{ + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); +} + +static +void peer_new(struct rudp_server *server, struct rudp_peer *peer) +{ + printf("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); +} + +static const struct rudp_server_handler handler = { + .handle_packet = handle_packet, + .link_info = link_info, + .peer_dropped = peer_dropped, + .peer_new = peer_new, +}; + +static +void handle_stdin( + struct ela_event_source *source, + int fd, uint32_t mask, void *data) +{ + struct rudp_server *server = data; + char buffer[512], *tmp; + + tmp = fgets(buffer, 512, stdin); + ssize_t size = strlen(tmp); + rudp_server_send_all(server, 1, 0, tmp, size); +} + +extern const struct rudp_handler verbose_handler; + +int main(int argc, char **argv) +{ + struct rudp_server server; + struct in_addr address; + struct ela_el *el = ela_create(NULL); + struct ela_event_source *source; + struct rudp rudp; + const struct rudp_handler *my_handler = RUDP_HANDLER_DEFAULT; + + if ( argc > 1 && !strcmp(argv[1], "-v") ) + my_handler = &verbose_handler; + + rudp_init(&rudp, el, my_handler); + + ela_source_alloc(el, handle_stdin, &server, &source); + + address.s_addr = INADDR_ANY; + + display_err( rudp_server_init(&server, &rudp, &handler) ); + rudp_server_set_ipv4(&server, &address, 4242); + display_err( rudp_server_bind(&server) ); + + ela_set_fd(el, source, 0, ELA_EVENT_READABLE); + ela_add(el, source); + + ela_run(el); + + ela_remove(el, source); + ela_source_free(el, source); + + display_err( rudp_server_close(&server) ); + display_err( rudp_server_deinit(&server) ); + + rudp_deinit(&rudp); + ela_close(el); + + return 0; +} diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..d7e94ac --- /dev/null +++ b/test/test.py @@ -0,0 +1,12 @@ + +import socket +import time + +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +r = open("/dev/urandom", "r") + +s.sendto("\x02\x01"+"\x00"*10, ('127.0.0.1', 4242)) + +while True: + s.sendto(r.read(12), ('127.0.0.1', 4242)) + time.sleep(.05) diff --git a/test/verbose.c b/test/verbose.c new file mode 100644 index 0000000..7c1d3e3 --- /dev/null +++ b/test/verbose.c @@ -0,0 +1,49 @@ +/* + Librudp, a reliable UDP transport library. + + This file is part of FOILS, the Freebox Open Interface + Libraries. This file is distributed under a 2-clause BSD license, + see LICENSE.TXT for details. + + Copyright (c) 2011, Freebox SAS + See AUTHORS for details + */ + +#include +#include + +#include + +static +void *_rudp_default_alloc(struct rudp *rudp, size_t len) +{ + void *b = malloc(len); + printf("%s: %d bytes allocated at %p\n", + __FUNCTION__, (int)len, b); + return b; +} + +static +void _rudp_default_free(struct rudp *rudp, void *buffer) +{ + printf("%s: freed buffer at %p\n", + __FUNCTION__, buffer); + return free(buffer); +} + +static +void _rudp_log(struct rudp *rudp, + enum rudp_log_level level, + const char *fmt, + va_list arg) +{ + printf("%d ", level); + vprintf(fmt, arg); +} + +const struct rudp_handler verbose_handler = +{ + .log = _rudp_log, + .mem_alloc = _rudp_default_alloc, + .mem_free = _rudp_default_free, +};