Using UDP sockets

Table of contents Table of contents

Introduction

The main data transmission protocol used in embeNET is UDP. Most application level communication is done using this protocol. In order to transmit and receive UDP data, the Nodes as well as the Border Router are expected to register UDP sockets, through which the communication will be carried. A socket is a logical endpoint in the networked device, that allows to easily dispatch network traffic to various services running in the device. We may think of the socket as a filter that selects only particular network traffic and feeds it into a software component (service) that owns the socket. Typically various services running in the node will use different ports and thus will register their own sockets. Such sockets will be used for transmitting and receiving data. In embeNET each socket is associated with a port number and may have three types of behavior concerning the way the receiving (destination) device is addressed.

Understanding types of sockets, ports and addressing

In order to understand types of sockets in embeNET we first need to understand the IPv6 addressing principles employed in embeNET. There are basically two types of network traffic supported:

  • unicast traffic, where we send the data to one particular destination node which is described by its own unicast address
  • multicast traffic, where we send the data to a group of nodes using group addressing (Note, that each node may belong to none, one or more than one group, and the groups it belongs to may vary from node to node)

Apart from that, the traffic is directed to a given port number in the destination device. Thus the destination IPv6 address (unicast or multicast) and destination port identify the actual data recipient.

In embeNET we can register sockets that:

This type of socket behavior is defined during socket registration (see EMBENET_UDP_RegisterSocket). There is no way to change that behavior other than unregister the given socket and register another one.

Example

Let's assume that we have a network with the network prefix set to 2001:0db8:0000:0000. Within that network there is a node with UID = 0000:0000:1234:5678 This node belongs to two groups number 12 and 15

In order to address this node using unicast addressing we should use an address constructed in the following way:

// Assemble an IPv6 unicast address
EMBENET_IPV6 unicastAddr = EMBENET_AssembleUnicastIpv6(0x20010db800000000, 0x0000000012345678);
EMBENET_IPV6 EMBENET_AssembleUnicastIpv6(EMBENET_NetworkPrefix nwkPrefix, EMBENET_EUI64 uid)
Assembles Network prefix and Node's UID into IPv6 Address.
IPv6 address.
Definition: embenet_brc/embenet_defs.h:58

In order to address this node as a member of the group number 12 we should use an address constructed in the following way:

// Assemble an IPv6 multicast address to group 12
EMBENET_IPV6 multicastAddr12 = EMBENET_AssembleMulticastIpv6(0x20010db800000000, 12);
// Assemble an IPv6 multicast address to group 15
EMBENET_IPV6 multicastAddr15 = EMBENET_AssembleMulticastIpv6(0x20010db800000000, 15);
EMBENET_IPV6 EMBENET_AssembleMulticastIpv6(EMBENET_NetworkPrefix nwkPrefix, EMBENET_GroupId gid)
Assembles Network prefix and Multicast group ID into IPv6 Address.

Let's register three sockets in this node - all of them on the same port number = 2222, so that:

So now:

  • if we send data to port 2222 using unicast addressing (unicastAddr - from the above example), our node will receive the same data on socket U and A
  • if we send data to port 2222 using multicast addressing to group 12 (multicastAddr12 - from the above example), our node will receive the same data on socket M and A
  • if we send data to port 2222 using multicast addressing to group 15 (multicastAddr15 - from the above example), our node will receive the same data only on socket A

Now let's explore some corner cases:

  • if we register socket N handling multicast traffic to group 13 the socket will not receive any data until the node joins this group
  • similarly, when the node will leave group 13, socket N will stop receiving data
  • if there are more sockets registered on the same port, they also will receive data (data will be "duplicated" and fed to each socket)

Registering a socket

Before using a socket we must register it through a call to EMBENET_UDP_RegisterSocket. This call takes a pointer to a EMBENET_UDP_SocketDescriptor structure that describes the socket. The following example shows how to register a socket that accepts all types of traffic.

socket.port = 1234;
socket.socketType = EMBENET_UDP_TRAFFIC_ALL;
socket.rxHandler = receptionHandler;
socket.groupId = 0; // not used for this type of socket type (used only for multicast sockets)
socket.userContext = NULL;
// Socket registered successfully
} else {
// Failed to register a socket
}
@ EMBENET_RESULT_OK
Definition: embenet_brc/embenet_defs.h:26
EMBENET_Result EMBENET_UDP_RegisterSocket(EMBENET_UDP_SocketDescriptor *socket)
Registers a new UDP socket.
@ EMBENET_UDP_TRAFFIC_ALL
The socket listens both on nodes unicast address or any matching multicast address - equivalent to IP...
Definition: embenet_udp.h:254
Structure describing an UDP Socket.
Definition: embenet_udp.h:263
EMBENET_GroupId groupId
Definition: embenet_udp.h:280
uint16_t port
Definition: embenet_udp.h:267
void * userContext
Definition: embenet_udp.h:289

Receiving data

In the above example the socket is created with a data reception function called 'receptionHandler'. This function may look like this:

void receptionHandler(EMBENET_UDP_SocketDescriptor const* socket, EMBENET_IPV6 const* address, void const* data, size_t dataLength)
{
printf("Got %z bytes\n", dataLenght);
}

This function will be called every time a UDP packet through a given socket.

Sending data

In order to send some data through a registered socket a call to EMBENET_UDP_Send has to be made. The following example illustrates the data sending mechanism by sending 5 bytes ('Hello') through a registered socket to a destination port 2000 in the destination node described by an IPv6 address.

// prepare destination IPv6 address
EMBENET_IPV6 destinationAddress = EMBENET_AssembleUnicastIpv6(0x20010db800000000, 0x0000000012345678);
if (EMBENET_RESULT_OK == EMBENET_UDP_Send(&socket, &destinationAddress, 2000, "Hello", 5) {
// Data was scheduled to be send
} else {
// Unable to send the data
}
EMBENET_Result EMBENET_UDP_Send(EMBENET_UDP_SocketDescriptor const *socket, EMBENET_IPV6 const *destinationAddress, uint16_t destinationPort, const void *data, size_t dataSize)
Sends UDP datagram from the given socket.

Determining the amount of data that can be sent

The EMBENET_UDP_Send schedules a single UDP datagram to be sent. This call does not provide any fragmentation capabilities. The maximum amount of data that can be sent by a single call to EMBENET_UDP_Send can be determined using EMBENET_UDP_GetMaxDataSize function:

size_t maxUDPDataSize = EMBENET_UDP_GetMaxDatagramSize();

Unregistering the socket

Once the socket is not needed anymore it can be unregistered by a call to EMBENET_UDP_UnregisterSocket. The following code illustrates this idea:

EMBENET_Result EMBENET_UDP_UnregisterSocket(EMBENET_UDP_SocketDescriptor *socket)
Unregisters socket from interface.

User context

Each socket can be assigned with a user defined pointer called userContext. This variable can point to any user-defined data to you want to bind with the socket. The intended use case is to pass a user context to the data reception handler. Below is an example. Let's assume that we want the reception handler to store the received value in a buffer, that will be set during socket registration:

char userBuffer[64];

We can register the socket with a user context pointing to that buffer

socket.port = 1234;
socket.socketType = EMBENET_UDP_TRAFFIC_ALL;
socket.rxHandler = receptionHandler;
socket.groupId = 0;
socket.userContext = userBuffer;
// Socket registered successfully
} else {
// Failed to register a socket
}

Now, when the receptionHandler is called, we can retrieve the buffer and use it:

void receptionHandler(EMBENET_UDP_SocketDescriptor const* socket, EMBENET_IPV6 const* address, void const* data, size_t dataLength)
{
printf("Got %z bytes\n", dataLenght);
memcpy(socket.userContext, data, dataLength);
}