Network Core Programming Guide#
Once you have the proper configuration in place and your network interfaces are up , the next step is to start programming. This is where you delve into the programming of your application.
To help you get started, this guide covers:
It also touches on the topic of lower-level programming functions, including:
High-level application programmingDon't forget to take a look at our Application Modules for more information about programming with high-level applications such as the HTTP Server. |
---|
DNS Client Programming#
This page shows how to use the DNS Client of the Network module. The DNS client module is part of the network core module of Micrium OS.
DNS Client Configuration#
Compile-Time Configuration#
To use the DNS Client module, it must be enabled at compile-time in the Network Core configuration file (net_cfg.h) through NET_DNS_CLIENT_CFG_MODULE_EN. See section Core Modules Configuration for more details.
Configuration Structure#
You must set the DNS Client module configuration before calling Net_Init(). To do so, use the API function Net_ConfigureDNS_Client(). Table - DNS Client Configuration Structure in the DNS Client Programming page shows the configuration fields. See DNS Client Run-Time Configurations for further details.
Below is an example of how the DNS client configuration can be overridden prior to network initialization.
Listing - Example of call to Net_Init()#
DNSc_CFG dns_cfg;
RTOS_ERR err;
dns_cfg = Net_InitCfgDflt.DNSc_Cfg; /* Get the default DNS configuration values */
dns_cfg.DfltServerAddrFallbackEn = DEF_DISABLED; /* Modify only what is needed */
Net_ConfigureDNS_Client(&dns_cfg); /* Set the new DNS configuration value */
Net_Init(&err);
if (err.Code != RTOS_ERR_NONE) {
/* An error occurred. Error handling should be added here. */
}
DNS Server Address Setup#
The DNS server address can be configured automatically by using the DNS server option information received during the DHCP (IPv4) client process or during the IPv6 SLAAC.
The DNS server address can also be configured manually by using one of the two available API functions: DNSc_CfgServerByStr() or DNSc_CfgServerByAddr(). A manually-configured DNS server address will always have precedence over an auto-configured DNS server address.
Furthermore, the DNS client module offers the possibility to fall back on a default DNS server address (8.8.8.8) if no other DNS server address is available. This last option can be disabled in the DNS client run-time configuration structure.
Domain Name Translation#
To retrieve the IP address of a hostname, you can use tThe API function DNSc_GetHost().
The network module also offers a high-level API functions to open TCP or UDP client sockets by passing the destination hostname instead of the IP address: NetApp_ClientStreamOpenByHostname() and NetApp_ClientDatagramOpenByHostname(). Those functions perform the DNS translation.
DHCP Client Programming#
This page describes how to use the DHCP Client of the Network module. The DHCP client module is part of the network core module of Micrium OS.
The DHCP Client module is for only IPv4 addressing. Furthermore, the DHCP Client process operates independently of the IPv4 Static Addressing on a network interface. Therefore the two can be present on a network interface at the same time.
Each network interface on which you want to use DHCP will have its own DHCP process.
DHCP Client Configuration#
Compile-Time Configuration#
First, to use the DHCP Client module, it must be enabled at compile-time in the net_cfg.h configuration file. See section Core Modules Configuration for more details.
Configuration Structure#
Because each network interface can have its own DHCP process, each interface has its own configuration structure. Table - DHCPc_CFG Configuration Structure in the DHCP Client Programming page shows the configuration fields.
Your application will need to declare and define a variable of this structure type, and pass it to the DHCPc_IF_Add() function. This function call must be performed for each network interface you wish to use DHCP on. See DHCP Client Run-Time Configuration for further information about run-time configuration.
Callback Function#
An optional callback function can also be passed to the DHCPc_IF_Add() function. This callback must be defined by the application and will be called by the network module upon the end of the DHCP process. Thus, the application can use this callback to receive a notification when the DHCP process is completed, to validate that the process was successful, and to recover the IPv4 address that was configured. Listing - DHCPc_ON_COMPLETE_HOOK Signature in the DHCP Client Programming page below shows the callback function prototype and provides details on the different arguments that will be passed to this callback.
Listing - DHCPc_ON_COMPLETE_HOOK Signature#
void Ex_DHCPc_Callback (NET_IF_NBR if_nbr,
DHCPc_STATUS status,
NET_IPv4_ADDR addr,
NET_IPv4_ADDR mask,
NET_IPv4_ADDR gateway,
RTOS_ERR err);
Argument | Description |
---|---|
if_nbr | Network interface number on which the DHCP process has complete. |
status | Status of the DHCP process:DHCPc_STATUS_SUCCESSDHCPc_STATUS_FAIL_ADDR_USEDDHCPc_STATUS_FAIL_OFFER_DECLINEDHCPc_STATUS_FAIL_NAK_RXDHCPc_STATUS_FAIL_NO_SERVERDHCPc_STATUS_FAIL_ERR_FAULT |
addr | IPv4 address that has been configured via the DHCP process. NET_IPv4_ADDR_NONE if the process was not a success. |
mask | IPv4 address mask of the configured address. NET_IPv4_ADDR_NONE if the process was not a success. |
gateway | IPv4 address of the network gateway. NET_IPv4_ADDR_NONE if the process was not a success. |
err | Error value returned by the DHCP process if a fault error occurred. |
Start DHCP Client Process#
There are two approaches to starting a DHCP process on a network interface:
The DHCP process can be started when calling the NetIF_xx_Start() function. This function will start the network interface and also start the DHCP process automatically when the interface link goes up. This approach is covered in the section Starting a Network Interface ,.
The process can be started manually with the function DHCPc_IF_Add().
Once a network interface is added by the application, it is then possible to add a DHCP process to it by calling the function DHCPc_IF_Add(). This will ensure that once the network interface goes up after being started , a DHCP process will start to retrieve an IPv4 address from a DHCP server present on the network.
Listing - example of call to DHCPc_IF_Add() in the DHCP Client Programming page shows a simple example.
DHCPc_IF_Add() takes as the first argument the network interface number on which to start the DHCP process.
The second argument is a pointer to the configuration structure for the DHCP process. See Table - DHCPc_CFG Configuration Structure in the DHCP Client Programming page for more details on the configuration fields. A null pointer can be passed instead and in this case, the DHCP process will use the default configuration values.
The third argument is the callback function to notify the application upon the end of the DHCP process. See Listing - DHCPc_ON_COMPLETE_HOOK Signature in the DHCP Client Programming page for more details on the callback function. This parameter can also be a null pointer and in this case, the application will not be notified.
Listing - Example of call to DHCPc_IF_Add()#
NET_IF_NBR if_nbr;
DHCPc_CFG cfg;
RTOS_ERR err;
cfg = {
.ServerPortNbr = DHCPc_CFG_PORT_SERVER_DFLT,
.ClientPortNbr = DHCPc_CFG_PORT_CLIENT_DFLT,
.TxRetryNbr = DHCPc_CFG_TX_RETRY_NBR_DFLT,
.TxTimeout_ms = DHCPc_CFG_TX_WAIT_TIMEOUT_MS_DFLT,
.ValidateAddr = DEF_NO
};
if_nbr = Net_Ether_Add(DEF_NULL, &err);
if (err.Code != RTOS_ERR_NONE) {
/* An error occurred. Error handling should be added here. */
}
DHCPc_IF_Add(if_nbr, &cfg, Ex_DHCPc_Callback, &err);
if (err.Code != RTOS_ERR_NONE) {
/* An error occurred. Error handling should be added here. */
}
Reboot DHCP Client Process#
The rebooting of the DHCP Client process occurs automatically under two circumstances:
The network link state goes from DOWN to UP.
The lease has expired or is about to expire.
In some cases, however, you may want to force a reboot to renew an address lease earlier. To do this, simply call the DHCPc_IF_Reboot() function, specifying the interface number for which to reboot the DHCP Client process.
Stop DHCP Client Process#
The DHCP Client process can be stopped with DHCPc_IF_Remove(), specifying the interface number for which to stop the DHCP Client process.
IP Address Programming#
The following sections provide sample code describing how to manipulate IP address (IPv4 and IPv6).
Include Files#
Wherever you want to manipulate IP address, you should include one or many of these files:
Include file | Description |
---|---|
rtos/net/include/net_ipv4.h | Functions used for IPv4 API. |
rtos/net/include/net_ipv6.h | Functions used for IPv6 API. |
rtos/net/include/net_ascii.h | Functions and Macro used for conversion utilities API. |
Configuration#
Some parameters should be configured and/or optimized for your project requirements. See the section IPv4 Layer Configuration and IPv6 Layer Configuration under Network Core Compile-Time Configurations for further details.
API Reference#
IP Address Configuration Functions#
Those functions are related to the configuration and removal of IP address on an Interface.
Interface Configured IP Address Functions#
These functions are associated to information on interface configured IP address.
Generic IP Address Information Functions#
Those functions gives you generic information on a specific IP address.
IP Address Conversion Utilities#
Those functions allows conversion from a string representation of an IP address to the TCP/IP stack address representation and vice-versa.
Function name | Description |
---|---|
NetASCII_Str_to_IP() | Convert a string of an IPv4 or IPv6 address in their respective decimal notation to an IPv4 or IPv6 address. |
NetASCII_Str_to_IPv4() | Convert a string of an IPv4 address in dotted-decimal notation to an IPv4 address in host-order. |
NetASCII_Str_to_IPv6() | Convert a string of an IPv6 address in common-decimal notation to an IPv6 address. |
NetASCII_IPv4_to_Str() | Convert an IPv4 address in host-order into an IPv4 dotted-decimal notation ASCII string. |
NetASCII_IPv6_to_Str() | Convert an IPv6 address into an IPv6 colon-decimal notation ASCII string. |
IP Address Conversion#
The Network module contains functions to perform various string operations on IP addresses.
The following example shows how to use the NetASCII module in order to convert IP addresses to and from their dotted-decimal representations:
Listing - IP address string conversion#
NET_IPv4_ADDR ipv4_addr;
NET_IPv6_ADDR ipv6_addr;
CPU_INT08U ipv4_str[16];
CPU_INT08U ipv6_str[40];
RTOS_ERR err;
/* IPv4 */
NetASCII_Str_to_IP("192.168.1.65", &ipv4_addr, NET_IPv4_ADDR_SIZE, &err);
NetASCII_IPv4_to_Str(ipv4_addr, &ipv4_str[0], DEF_NO, &err);
/* IPv6 */
NetASCII_Str_to_IP("fe80::1111:1111", &ipv6_addr, NET_IPv6_ADDR_SIZE, &err);
NetASCII_IPv6_to_Str(&ipv6_addr, &ipv6_str[0], DEF_NO, &err);
Static IP Address Configuration On An Interface#
See section IPv4 - Assign an Address to a Network Interface and IPv6 - Assign an Address to a Network Interface for examples on how to configure a static IPv4 or IPv6 address on a specific Interface.
Dynamic IP Address Configuration On An Interface (IPv4 only)#
IPv4 address can be configured dynamically on an interface by using the DHCP Client module.
If you want to develop your own network application to setup dynamic addressing, the IPv4 functions NetIPv4_CfgAddrAddDynamic(), NetIPv4_CfgAddrAddDynamicStart(), NetIPv4_CfgAddrAddDynamicStop() should be used.
Dynamic address configuration is not yet available for IPv6.
Stateless Address Auto-Configuration (IPv6 only)#
The IPv6 protocol defines an Auto-Configuration procedure allowing a network interface to set itself an IPv6 Link-Local address based on its Interface ID and also a IPv6 global address if an IPv6 router is present on the local network.
Refer to section IPv6 - Stateless Address Auto configuration (SLAAC) for more details.
IP Addressing with Socket Programming#
When developing a network application that sends and/or receives data, IP address handling will be required.
When you setup a server application, you MUST bind to an already configured IP address. This address will be used as the destination address for all the clients communicating with the server and will be used as the source address for the packets sent by the server.
You can use the functionNetIPv4_GetAddrHost() or NetIPv6_GetAddrHost() to recover all the IPv4 or IPv6 addresses configured on a specific Interface.
You also have the option to bind to the wildcard address (0.0.0.0 for IPv4 and :: for IPv6). In that case, the server socket is not bound to any IP address in particular, therefore any packets send to the corresponding server port number will be received by the server regardless of the destination address. When sending packets, the Network module TCP/IP stack will take care of setting the best source address for the destination.
When you setup a client application, you MAY want to bind to a specific configured IP address. Knowing the destination IP address, you can use the functionNetIPv4_GetAddrSrc() orNetIPv6_GetAddrSrc() to find the best suited configured IP address for your destination.
If you don't bind to an IP address, the Network module TCP/IP stack will take care of setting the source address of packets to send.
Refer to section Socket Programming for more information on using network sockets.
IPv4 - Assign an Address to a Network Interface#
The following sections provide sample code describing how to configure an IPv4 address.
For IPv4, the address configuration may be performed using Micrium OS DHCP Client or manually during run-time.
If the manual configuration is chosen, the following functions may be used to set the IPv4, network mask, and gateway addresses for a specific interface.
NetASCII_Str_to_IP() NetIPv4_CfgAddrAdd()
More than one set of IPv4 addresses may be configured for a specific network interface by calling the functions above. The constant NET_IPv4_CFG_IF_MAX_NBR_ADDR specified in net_cfg.h determines the maximum number of IPv4 addresses that may be assigned to an interface.
Note that on the default interface, the first IPv4 address added will be the default address used for all default IPv4 communication.
The first function aids the developer by converting a string format IPv4 address such as “192.168.1.2” to its hexadecimal equivalent. The second function is used to configure an interface with the specified IPv4, network mask and gateway addresses. An example is shown in listing Address Configuration Example .
Listing - IPv4 Address Configuration Example#
CPU_BOOLEAN cfg_success;
NET_IPv4_ADDR ipv4_addr;
NET_IPv4_ADDR ipv4_msk;
NET_IPv4_ADDR ipv4_gateway;
RTOS_ERR err;
NetASCII_Str_to_IP((CPU_CHAR*)"192.168.1.2", &ipv4_addr, NET_IPv4_ADDR_SIZE, &err); /* See Note #1 */
NetASCII_Str_to_IP((CPU_CHAR*)"255.255.255.0", &ipv4_msk, NET_IPv4_ADDR_SIZE, &err);
NetASCII_Str_to_IP((CPU_CHAR*)"192.168.1.1", &ipv4_gateway, NET_IPv4_ADDR_SIZE, &err);
cfg_success = NetIPv4_CfgAddrAdd(if_nbr, /* See Note #2 */
ipv4_addr, /* See Note #3 */
ipv4_msk, /* See Note #4 */
ipv4_gateway, /* See Note #5 */
&err); /* See Note #6 */
NetASCII_Str_to_IP() requires four arguments. The first function argument is a string representing a valid IP address. The second argument is a pointer to the IP address object that will received the conversion result. The third argument is the size of the address object and the last argument is a pointer to a RTOS_ERR to contain the return error code. Upon successful conversion, the return error will contain the value RTOS_ERR_NONE and the function will return a variable of type NET_IP_ADDR_FAMILY containing the family type (IPv4 or IPv6) of the address converted.
The first argument is the number representing the network interface that is to be configured. This value is obtained as the result of a successful call to NetIF_Ether_Add() or NetIF_WiFi_Add().
The second argument is the NET_IPv4_ADDR value representing the IPv4 address to be configured.
The third argument is the NET_IPv4_ADDR value representing the subnet mask address that is to be configured.
The fourth argument is the NET_IPv4_ADDR value representing the default gateway IPv4 address that is to be configured.
The fifth argument is a pointer to a RTOS_ERR variable containing the return error code for the function. If the interface address information is configured successfully, then the return error code will contain the value RTOS_ERR_NONE. Additionally, function returns a Boolean value of DEF_OK or DEF_FAIL depending on the result. Either the return value or the RTOS_ERR variable may be checked for return status; however, the RTOS_ERR contains more detailed information and should therefore be the preferred check.
IPv4 - Link-Local Address Setup#
The Link-Local process assigns an IPv4 local address to an interface when no other IPv4 addresses are configured on the interface. This would be the case under the following circumstances:
No static address was configured on the interface, and
No DHCP client process is running on the interface, or a DHCP client process is running but has failed to assign an address.
The Link-Local process is started automatically if configured accordingly when starting the network interface .
Here are some use cases for Link-Local:
If the network start configuration specifies (a) no static IP address, (b) DHCP enabled and (c) Link-Local enabled, then a Link-Local IP address will automatically be assigned if the DHCP process fails.
If the network start configuration specifies (a) no static IP address, (b) DHCP disabled and (c) Link-Local enabled, then a Link-Local IP address will automatically be assigned.
If Link-Local is not enabled at the time the interface is started, it can be done manually, as explained in the following section.
Starting the Link-Local Process Manually#
When the Link-Local process is not started through the network interface start, it must be launched manually when your application detects the absence of any local IP address on an interface. The Link-Local process is not a task that runs in the background, so it will not be invoked automatically. It is a good practice to check for the presence of an IP address on an interface when (a) the DHCP process fails and (b) a static IP address is removed from the interface. In either case, if the interface is left with no local IP address, the Link-Local process should be launched if required by your application.
To start the Link-Local process, you must call NetIPv4_AddrLinkLocalCfg() and provide the interface number on which to run the process, as well a callback function to be called when the process completes.
Your application's callback function must have the following signature, as defined by NET_IPv4_LINK_LOCAL_COMPLETE_HOOK:
Listing - NET_IPv4_LINK_LOCAL_COMPLETE_HOOK signature#
static void NetIF_IPv4_LinkLocalCompleteHook (NET_IF_NBR if_nbr,
NET_IPv4_ADDR link_local_addr,
NET_IPv4_LINK_LOCAL_STATUS status,
RTOS_ERR err)
When the callback function is called, the local IP address assigned to the interface will be available in link_local_addr, provided that the status is NET_IPv4_LINK_LOCAL_STATUS_SUCCEEDED.
Here is an example that shows starting a Link-Local process when a DHCP Client process has failed. It takes place inside the hook function of the DHCP Client process (Ex_DHCPc_Hook(), in this example).
Listing - Example of manually starting the Link Local process#
static void Ex_IPv4_LinkLocalAddrCfgResult (NET_IF_NBR if_nbr,
NET_IPv4_ADDR link_local_addr,
NET_IPv4_LINK_LOCAL_STATUS status,
RTOS_ERR err)
{
CPU_CHAR addr_str[NET_ASCII_LEN_MAX_ADDR_IPv4];
RTOS_ERR local_err;
switch (status) {
case NET_IPv4_LINK_LOCAL_STATUS_SUCCEEDED:
if (link_local_addr != NET_IPv4_ADDR_NONE) {
NetASCII_IPv4_to_Str(link_local_addr, addr_str, DEF_YES, &local_err);
}
EX_TRACE("IPv4 link local address: %s, was configured successfully!\n", addr_str);
break;
case NET_IPv4_LINK_LOCAL_STATUS_FAILED:
/* Handle Link Local address assignation failure */
default:
break;
}
}
static void Ex_DHCPc_Hook (NET_IF_NBR if_nbr,
DHCPc_STATUS status,
NET_IPv4_ADDR addr,
NET_IPv4_ADDR mask,
NET_IPv4_ADDR gateway,
RTOS_ERR err)
{
/* Process the DHCP callback */
if (status != DHCPc_STATUS_SUCCESS) {
NetIPv4_AddrLinkLocalCfg(if_nbr, Ex_IPv4_LinkLocalAddrCfgResult, &local_err);
}
}
Stopping the Link-Local Process#
You can stop the Link-Local process by calling NetIPv4_AddrLinkLocalCfgRemove() and specifying the interface number on which to stop the process.This will also remove the configured Link-Local address on the network interface.
IPv6 - Assign an Address to a Network Interface#
The following functions may be utilized to set the IPv6 address for a specific interface:
NetASCII_Str_to_IP() NetIPv6_CfgAddrAdd() NetIPv6_AddrSubscribe()
More than one set of IPv6 addresses may be configured for a specific network interface by calling the functions above. The constant NET_IPv6_CFG_IF_MAX_NBR_ADDR specified in net_cfg.h determines the maximum number of IPv6 addresses that may be assigned to an interface.
Note that on the default interface, the first IPv6 address added will be the default address used for all default IPv6 communication.
The first function aids the developer by converting a string format IPv6 address such as “fe80::1111:1111” to its network equivalent. The second function is used to configure an interface with the specified IPv6 address. An example is shown in listing Address Configuration Example .
Listing - IPv6 Address Configuration Example#
CPU_BOOLEAN cfg_success;
NET_IPv6_ADDR ipv6_addr;
NET_FLAGS ipv6_flags;
RTOS_ERR err;
NetASCII_Str_to_IP((CPU_CHAR *)"fe80::1111:1111", /* See Note #1 */
&ipv6_addr,
NET_IPv6_ADDR_SIZE,
&err);
ipv6_flags = 0;
DEF_BIT_SET(ipv6_flags, NET_IPv6_FLAG_BLOCK_EN); /* See Note #2 */
DEF_BIT_SET(ipv6_flags, NET_IPv6_FLAG_DAD_EN); /* See Note #3 */
cfg_success = NetIPv6_CfgAddrAdd(if_nbr, /* See Note #4 */
&ipv6_addr, /* See Note #5 */
64, /* See Note #6 */
ipv6_flags, /* See Note #7 */
&err); /* See Note #8 */
See NetASCII_Str_to_IP for more details.
Set Address Configuration as blocking.
Enable DAD with Address Configuration.
The first argument is the number representing the network interface that is to be configured. This value is obtained as the result of a successful call to NetIF_Ether_Add() or NetIF_WiFi_Add().
The second argument is the pointer to the NET_IPv6_ADDR value representing the IPv6 address to be configured.
The third argument is the IPv6 prefix length of the address to configured.
The fourth argument is a set of network flags holding options specific to the address configuration process.
The fifth argument is a pointer to a RTOS_ERR variable containing the return error code for the function. If the interface address information is configured successfully, then the return error code will contain the value RTOS_ERR_NONE. Additionally, function returns a Boolean value of DEF_OK or DEF_FAIL depending on the result. Either the return value or the RTOS_ERR variable may be checked for return status; however, the RTOS_ERR contains more detailed information and should therefore be the preferred check.
As shown in Address Configuration Example , the NetIPv6_CfgAddrAdd() function can take as an argument a set of network flags. The following options are available:
Flags | Description |
---|---|
NET_IPv6_FLAG_BLOCK_EN | Enables blocking mode. |
NET_IPv6_FLAG_DAD_EN | Enables Duplication Address Detection (DAD) with the address configuration process. |
It is therefore possible to make the function blocking or not, or to enable Duplication Address Detection with the address configuration.
If the function is made non-blocking, it is possible to set a hook function to notify the application when the address configuration process has finished. The API function NetIPv6_AddrSubscribe() can be used to set the hook function. Refer to section IPv6 Address Configuration Hook Function for all the details on the hook function format and usage.
Address Configuration Example in the IPv6 - Assign an Address to a Network Interface page shows an example of a non-blocking IPv6 static address configuration.
Listing - Non-Blocking IPv6 Address Configuration Example#
CPU_BOOLEAN cfg_success;
NET_IPv6_ADDR ipv6_addr;
NET_FLAGS ipv6_flags;
RTOS_ERR err;
NetASCII_Str_to_IP((CPU_CHAR *)"fe80::1111:1111", /* Convert IPv6 string address to 128 bit address. */
&ipv6_addr,
NET_IPv6_ADDR_SIZE,
&err);
/* Set hook function to received addr cfg result. */
NetIPv6_AddrSubscribe(&App_AddrCfgResult, /* TODO update pointer to hook fnct implemented in App. */
&err);
ipv6_flags = 0;
DEF_BIT_CLR(ipv6_flags, NET_IPv6_FLAG_BLOCK_EN); /* Set Address Configuration as non-blocking. */
DEF_BIT_SET(ipv6_flags, NET_IPv6_FLAG_DAD_EN); /* Enable DAD with Address Configuration. */
cfg_success = NetIPv6_CfgAddrAdd(if_nbr, /* Add a statically-configured IPv6 host address to ... */
&ipv6_addr, /* ... the interface. */
64,
ipv6_flags,
&err);
IPv6 - Stateless Address Autoconfiguration (SLAAC)#
The IPv6 protocol defines an address Auto-Configuration procedure allowing a network interface to set itself an IPv6 Link-Local address based on its Interface ID. The Auto-Configuration process will also query the local network to find an IPv6 router that could send prefix information to set an IPv6 global address.
The TCP/IP stack supports only the EUI-64 format for interface ID. This format creates a 64 bits ID based on the 48 bits MAC address of the interface. Those 64 bits will become the 64 least significant bits of the IPv6 addresses configured with the Stateless Auto-Configuration process.
There are two methods for configuring the IPv6 Stateless Auto-Configuration process:
Using the configuration structure NET_IF_ETHER_CFG or NET_IF_WIFI_CFG, as explained in Network Interface Start Setup and Starting a Network Interface , or
Using the following functions manually or after the interface has been started:
NetIPv6_AddrSubscribe() NetIPv6_AddrAutoCfgEn()
Below is an example of manually starting the Auto-configuration process for an IPv6 network interface.
Listing - Starting the Auto-configuration process#
NetIPv6_AddrAutoCfgEn(if_nbr,
DEF_YES,
&err);
The IPv6 Auto-Configuration procedure in the Network module is a non-blocking process. To retrieve the result of the Auto-Configuration, a hook function can be configured that will be called by the TCP/IP stack each time an address has been configured on an interface. The API function used to set the hook function is NetIPv6_AddrSubscribe() .
Note: the hook function is called each time an address has been configured, for any reason, and not just when Auto-configuration is complete. The response must be analyzed within the hook function to determine the type of configured address. More detail on how to handle the hook response may be found in IPv6 - Address Configuration Hook Function . |
---|
IPv6 - Address Configuration Hook Function#
When doing any sort of address assignment to an IPv6 interface, a hook function will be called at the end of the assignment process, provided that the NetIPv6_AddrSubscribe() function was used.
Listing - Setting an IPv6 Hook function#
RTOS_ERR err;
NetIPv6_AddrSubscribe(&Ex_IPv6_AddrCfgResult,
&err);
The hook function will be called at the end of each of three possible address configuration processes:
Static address assignment
Auto-configuration of a Link Local address
Auto-configuration of a global address
The hook function will be called upon either success or failure of the process. It is the application's responsibility to analyze the status of the response and the IPv6 address type that has been configured.
Below is an example of handling an IPv6 address configuration hook function call.
Listing - handling an IPv6 address configuration hook function call#
static void Ex_IPv6_AddrCfgResult ( NET_IF_NBR if_nbr,
NET_IPv6_CFG_ADDR_TYPE addr_type,
const NET_IPv6_ADDR *p_addr_cfgd,
NET_IPv6_ADDR_CFG_STATUS addr_cfg_status)
{
CPU_CHAR ip_string[NET_ASCII_LEN_MAX_ADDR_IPv6];
RTOS_ERR err;
PP_UNUSED_PARAM(if_nbr);
if (p_addr_cfgd != DEF_NULL) {
NetASCII_IPv6_to_Str((NET_IPv6_ADDR *)p_addr_cfgd, ip_string, DEF_NO, DEF_YES, &err);
EX_TRACE("IPv6 Address Link Local: %s\n", ip_string);
}
switch (addr_type) {
case NET_IPv6_CFG_ADDR_TYPE_STATIC:
switch (addr_cfg_status) {
case NET_IPv6_ADDR_CFG_STATUS_SUCCEED:
EX_TRACE("IPv6 Address Static: %s, configured successfully\n", ip_string);
break;
case NET_IPv6_ADDR_CFG_STATUS_DUPLICATE:
EX_TRACE("IPv6 Address Static already exists on the network\n");
break;
default:
EX_TRACE("IPv6 Address Static configuration failed.\n");
break;
}
break;
case NET_IPv6_CFG_ADDR_TYPE_AUTO_CFG_LINK_LOCAL:
switch (addr_cfg_status) {
case NET_IPv6_ADDR_CFG_STATUS_SUCCEED:
EX_TRACE("IPv6 Address Link Local: %s, configured successfully\n", ip_string);
break;
case NET_IPv6_ADDR_CFG_STATUS_DUPLICATE:
EX_TRACE("IPv6 Address Link Local already exists on the network\n");
break;
default:
printf("IPv6 Address Link Local configuration failed.\n");
break;
}
break;
case NET_IPv6_CFG_ADDR_TYPE_AUTO_CFG_GLOBAL:
switch (addr_cfg_status) {
case NET_IPv6_ADDR_CFG_STATUS_SUCCEED:
EX_TRACE("IPv6 Address Global: %s, configured successfully\n", ip_string);
break;
case NET_IPv6_ADDR_CFG_STATUS_DUPLICATE:
EX_TRACE("IPv6 Address Global already exists on the network\n");
break;
default:
EX_TRACE("IPv6 Address Global configuration failed.\n");
break;
}
break;
default:
break;
}
}
Interface Programming#
This section describe how to use and control Interface's options such:
Configuration#
Some parameters should be configured and/or optimized for your project requirements. See the section Network Interface Start Setup for further details.
Using Network Interface Programming API#
Wherever you want to configure or access an Interface option, you should include one or many of these files:
Include file | Description |
---|---|
rtos/net/include/net_if.h | Functions used for Interface API |
rtos/net/include/net_if_ether.h | Ethernet Interface's API reference (used with NetIF_Ether_Add()). |
rtos/net/include/net_if_wifi.h | Wireless Interface's API reference (used with NetIF_WiFi_Add()). |
API Reference#
All Interface APIs are presented in the section Network Interface Functions .
Function Name | Description |
---|---|
Add a network device and hardware as a network interface. | |
Get network interface’s hardware address. | |
Validate a network interface hardware address. | |
Set network interface’s hardware address. | |
Configure the network interface Performance Monitor Handler timeout. | |
Configure network interface Physical Link State Handler timeout. | |
Get an aligned pointer into a receive application data buffer. | |
Get an aligned pointer into a transmit application data buffer. | |
Handle network interface and/or device-specific (I/O) control(s). | |
Validate network interface as enabled. | |
Validate configured network interface as enabled. | |
Handle a network interface’s device interrupts. | |
Validate network interface number. | |
Validate configured network interface number. | |
Get network interface’s last known physical link state. | |
Subscribe to get notified when an interface link state changes. | |
Unsubscribe to get notified when interface link state changes. | |
Wait for a network interface's physical link state to be UP. | |
Get network interface’s MTU. | |
Set network interface’s MTU. | |
Start a network interface. | |
Stop a network interface. |
Network Interface Hardware Address (MAC)#
Default MAC Address#
When starting an Interface, the Network module can automatically use a MAC address either defined in the configuration or already loaded in MAC controller. As specified in Network Interface Start Setup each device configuration contains a variable which specifies whether the MAC address must be:
loaded from the MAC controller (indicated by a null or empty string in the configuration structure)
loaded from a custom MAC address obtained at run-time
loaded as is from a string in the configuration structure
Getting the MAC Address#
Many types of network interface hardware require the use of a link layer protocol address. In the case of Ethernet, this address is sometimes known as the hardware address or MAC address. In some applications, it may be desirable to get the current configured hardware address for a specific interface. This may be performed by calling NetIF_AddrHW_Get() with the appropriate arguments.
Listing - Calling NetIF_AddrHW_Get()#
NetIF_AddrHW_Get( if_nbr, (1)
&addr_hw_sender[0], (2)
&addr_hw_len, (3)
&err); (4)
The first argument specifies the interface number from which to get the hardware address. The interface number is acquired upon the successful addition of the interface.
The second argument is a pointer to a CPU_INT08U array used to provide storage for the returned hardware address. This array must be sized large enough to hold the returned number of bytes for the given interface’s hardware address. The lowest index number in the hardware address array represents the most significant byte of the hardware address.
The third function is a pointer to a CPU_INT08U variable that the function returns the length of the specified interface’s hardware address.
The fourth argument is a pointer to a RTOS_ERR variable containing the return error code for the function. If the hardware address is successfully obtained, then the return error code will contain the value RTOS_ERR_NONE.
Setting MAC Address#
Some applications prefer to configure the hardware device’s hardware address via software during run-time as opposed to a run-time auto-loading EEPROM as is common for many Ethernet devices. If the application is to set or change the hardware address during run-time, this may be performed by calling NetIF_AddrHW_Set() with the appropriate arguments. Alternatively, the hardware address may be statically configured via the device configuration structure and later changed during run-time.
Listing - Calling NetIF_AddrHW_Set()#
NetIF_AddrHW_Set( if_nbr, (1)
&addr_hw[0], (2)
&addr_hw_len, (3)
&err); (4)
The first argument specifies the interface number to set the hardware address. The interface number is acquired upon the successful addition of the interface.
The second argument is a pointer to a CPU_INT08U array which contains the desired hardware address to set. The lowest index number in the hardware address array represents the most significant byte of the hardware address.
The third function is a pointer to a CPU_INT08U variable that specifies the length of the hardware address being set. In most cases, this can be specified as sizeof(addr_hw) assuming addr_hw is declared as an array of CPU_INT08U.
The fourth argument is a pointer to a RTOS_ERR variable containing the return error code for the function. If the hardware address is successfully obtained, then the return error code will contain the value RTOS_ERR_NONE.
Note: In order to set the hardware address for a particular interface, it must first be stopped. The hardware address may then be set, and the interface re-started.
Getting a Host MAC Address on the Network#
In order to determine the MAC address of a host on the network, the Network Protocol Stack must have an ARP cache entry for the specified host protocol address. An application may check to see if an ARP cache entry is present by calling NetARP_CacheGetAddrHW() .
If an ARP cache entry is not found, the application may call NetARP_CacheProbeAddrOnNet() to send an ARP request to all hosts on the network. If the target host is present, an ARP reply will be received shortly; the application should wait and then call NetARP_CacheGetAddrHW() to determine if the ARP reply has been entered into the ARP cache.
The following example shows how to obtain the Ethernet MAC address of a host on the local area network:
Listing - Obtaining the Ethernet MAC address of a host#
void AppGetRemoteHW_Addr (NET_IF_NBR if_nbr)
{
NET_IPv4_ADDR addr_ipv4_local;
NET_IPv4_ADDR addr_ipv4_remote;
CPU_CHAR *p_addr_ipv4_local;
CPU_CHAR *p_addr_ipv4_remote;
CPU_CHAR addr_hw_str[NET_IF_ETHER_ADDR_SIZE_STR];
CPU_INT08U addr_hw[NET_IF_ETHER_ADDR_SIZE];
RTOS_ERR err;
/* ------------- PREPARE IPv4 ADDRs ------------- */
p_addr_ipv4_local = "10.10.1.10"; /* MUST be one of host's configured IPv4 addrs. */
addr_ipv4_local = NetASCII_Str_to_IPv4(p_addr_ipv4_local, &err);
if (err.Code != RTOS_ERR_NONE) {
printf(" Error #%d converting IPv4 address %s", err.Code, p_addr_ipv4_local);
return;
}
p_addr_ipv4_remote = "10.10.1.50"; /* Remote host's IPv4 addr to get hardware addr. */
addr_ipv4_remote = NetASCII_Str_to_IPv4(p_addr_ipv4_remote, &err);
if (err.Code != RTOS_ERR_NONE) {
printf(" Error #%d converting IPv4 address %s", err.Code, p_addr_ipv4_remote);
return;
}
addr_ipv4_local = NET_UTIL_HOST_TO_NET_32(addr_ipv4_local);
addr_ipv4_remote = NET_UTIL_HOST_TO_NET_32(addr_ipv4_remote);
/* ------------- PROBE ADDR ON NET -------------- */
NetARP_CacheProbeAddrOnNet(NET_PROTOCOL_TYPE_IP_V4,
(CPU_INT08U *)&addr_ipv4_local,
(CPU_INT08U *)&addr_ipv4_remote,
sizeof(addr_ipv4_remote),
&err);
if (err.Code != RTOS_ERR_NONE) {
printf(" Error #%d probing address %s on network", err.Code, addr_ipv4_remote);
return;
}
OSTimeDly(2); /* Delay short time for ARP to probe network. */
/* ----- QUERY ARP CACHE FOR REMOTE HW ADDR ----- */
(void)NetARP_CacheGetAddrHW(if_nbr,
&addr_hw[0],
sizeof(addr_hw_str),
(CPU_INT08U *)&addr_ipv4_remote,
sizeof(addr_ipv4_remote),
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
NetASCII_MAC_to_Str(&addr_hw[0],
&addr_hw_str[0],
DEF_NO,
DEF_YES,
&err);
if (err.Code != RTOS_ERR_NONE) {
printf(" Error #%d converting hardware address", err.Code);
return;
}
printf(" Remote IPv4 Addr %s @ HW Addr %s\n\r", p_addr_ipv4_remote, &addr_hw_str[0]);
break;
case RTOS_ERR_NOT_FOUND:
printf(" Remote IPv4 Addr %s NOT found on network\n\r", p_addr_ipv4_remote);
break;
case RTOS_ERR_NET_ADDR_UNRESOLVED:
printf(" Remote IPv4 Addr %s NOT YET found on network\n\r", p_addr_ipv4_remote);
break;
default:
printf(" Error #%d querying ARP cache", err.Code);
break;
}
}
Since ARP module is only used in affiliation with IPv4, getting the MAC address associated with an IP address is only supported for IPv4 address. IPv6 layer uses NDP (Neighbor Discovery Protocol) to associate IPv6 address with Link-Layer address. Unfortunately, NDP does not yet provides API function to get the MAC address associated with an IPv6 address. |
---|
Network Interface Link State#
Getting the Current Link State for an Interface#
The Network module provides two mechanisms for obtaining interface link state.
A function which reads a global variable that is periodically updated.
A function which reads the current link state from the hardware.
Method 1 provides the fastest mechanism to obtain link state since it does not require communication with the physical layer device. For most applications, this mechanism is suitable and if necessary, the polling rate can be increased by calling NetIF_CfgPhyLinkPeriod() . In order to utilize Method 1, the application may call NetIF_LinkStateGet() which returns NET_IF_LINK_UP or NET_IF_LINK_DOWN.
The accuracy of Method 1 can be improved by using a physical layer device and driver combination that supports link state change interrupts. In this circumstance, the value of the global variable containing the link state is updated immediately following a link state change. Therefore, the polling rate can be reduced further if desired and a call to NetIF_LinkStateGet() will return the actual link state.
Method 2 requires the application to call NetIF_IO_Ctrl() with the option parameter set to either NET_IF_IO_CTRL_LINK_STATE_GET or NET_IF_IO_CTRL_LINK_STATE_GET_INFO.
If the application specifies NET_IF_IO_CTRL_LINK_STATE_GET, then NET_IF_LINK_UP or NET_IF_LINK_DOWN will be returned.
Alternatively, if the application specifies NET_IF_IO_CTRL_LINK_STATE_GET_INFO, the link state details such as speed and duplex will be returned.
The advantage to Method 2 is that the link state returned is the actual link state as reported by the hardware at the time of the function call. However, the overhead of communicating with the physical layer device may be high and therefore some cycles may be wasted waiting for the result since the connection bus between the CPU and the physical layer device is often only a couple of MHz.
Calling NetIF_IO_Ctrl() will poll the hardware for the current link state. Alternatively, NetIF_LinkStateGet() gets the approximate link state by reading the interface link state flag. Polling the Ethernet hardware for link state takes significantly longer due to the speed and latency of the MII bus. Consequently, it may not be desirable to poll the hardware in a tight loop. Reading the interface flag is fast, but the flag is only periodically updated by the Net IF every 250ms (default) when using the generic Ethernet PHY driver. PHY drivers that implement link state change interrupts may change the value of the interface flag immediately upon link state change detection. In this scenario, calling NetIF_LinkStateGet() is ideal for these interfaces.
Listing - Calling NetIF_IO_Ctrl()#
NetIF_IO_Ctrl( if_nbr, (1)
NET_IF_IO_CTRL_LINK_STATE_GET_INFO, (2)
&link_state, (3)
&err);
The first argument specifies the interface number from which to get the physical link state.
The second argument specifies the desired function that NetIF_IO_Ctrl() will perform. In order to get the current interfaces’ link state, the application should specify this argument as either:
NET_IF_IO_CTRL_LINK_STATE_GET NET_IF_IO_CTRL_LINK_STATE_GET_INFO
The third argument is a pointer to a link state variable that must be declared by the application and passed to NetIF_IO_Ctrl() .
Wait Until the Cable is Connected (Link Up)#
Sometime the application might want to wait until the link is up before starting doing things on the network. The function NetIF_LinkStateWaitUntilUp() can be use to do such functionality.
Subscribe / Unsubscribe to Link State Changes#
The Network stack offers two API functions: NetIF_LinkStateSubscribe() , NetIF_LinkStateUnsubscribe() , allowing the customer application to implement a callback function that will be called when a link state change occurs.
Increasing the Rate of Link State Polling#
The application may increase the link state polling rate by calling NetIF_CfgPhyLinkPeriod() . The default value is 250ms.
Forcing an Ethernet PHY to a Specific Link State#
The generic PHY driver that comes with the Network module does not provide a mechanism for disabling auto-negotiation and specifying a desired link state. This restriction is required in order to remain MII register block-compliant with all (R)MII compliant physical layer devices.
However, the Network module does provide a mechanism for coaching the physical layer device into advertising only the desired auto-negotiation states. This may be achieved by adjusting the physical layer device configuration structure, NET_PHY_CFG_ETHER, with alternative link speed and duplex values. This configuration structure is generally located in the BSP file for the device (bsp_net_ether_xxx.c)
The following is an example physical layer device configuration structure.
NET_PHY_CFG_ETHER NetPhy_Cfg_Generic_0 = {
0,
NET_PHY_BUS_MODE_MII,
NET_PHY_TYPE_EXT,
NET_PHY_SPD_AUTO,
NET_PHY_DUPLEX_AUTO
};
The parameters NET_PHY_SPD_AUTO and NET_PHY_DUPLEX_AUTO may be changed to match any of the following settings:
NET_PHY_SPD_10
NET_PHY_SPD_100
NET_PHY_SPD_1000
NET_PHY_SPD_AUTO
NET_PHY_DUPLEX_HALF
NET_PHY_DUPLEX_FULL
NET_PHY_DUPLEX_AUTO
This mechanism is only effective when both the physical layer device attached to the target and the remote link state partner support auto-negotiation.
Refer to section PHY Configuration and Information for detailed information on the PHY device configuration.
Network Interface Maximum Transmit Unit (MTU)#
Getting the MTU#
On occasion, it may be desirable to have the application aware of an interface’s Maximum Transmission Unit. The MTU for a particular interface may be acquired by calling NetIF_MTU_Get() with the appropriate arguments.
Listing - Calling NetIF_MTU_Get()#
mtu = NetIF_MTU_Get(if_nbr, &err); (1)
1. NetIF_MTU_Get() requires two arguments. The first function argument is the interface number to get the current configured MTU, and the second argument is a pointer to a NET_ERR to contain the return error code. The interface number is acquired upon the successful addition of the interface, and upon the successful return of the function, the return error variable will contain the value NET_IF_ERR_NONE. The result is returned into a local variable of type NET_MTU.
#### Setting the MTU
Some networks prefer to operate with a non-standard MTU. If this is the case, the application may specify the MTU for a particular interface by calling [NetIF_MTU_Set()](../network-api/01-network-core-api.md#net-if-mtu-set) with the appropriate arguments.
###### Listing - Calling NetIF_MTU_Set()
```C
NetIF_MTU_Set(if_nbr, mtu, &err); (1)
NetIF_MTU_Set() requires three arguments. The first function argument is the interface number of the interface to set the specified MTU. The second argument is the desired MTU to set, and the third argument is a pointer to a NET_ERR variable that will contain the return error code. The interface number is acquired upon the successful addition of the interface, and upon the successful return of the function, the return error variable will contain the value NET_IF_ERR_NONE and the specified MTU will be set.
Note: The configured MTU cannot be greater than the largest configured transmit buffer size associated with the specified interfaces’ device minus overhead. Transmit buffer sizes are specified in the device configuration structure for the specified interface. For more information about configuring device buffer sizes, refer to section Network Interface Controller Configuration .
Socket Programming#
The following sections provide sample code describing how sockets work.
For those interested in BSD socket programming, there are plenty of books, online references, and articles dedicated to this subject.
The sockets API provides many configuration options, and it's very difficult to cover all variations of its use. The examples have been designed to be as simple as possible. Hence, only basic error checking is performed. When it comes to building real applications, those checks should be extended to deliver a product that is as robust as possible.
Socket Description#
A network socket is an inter-process communication implementation using an IP stack on a network. Sockets allow one application process to communicate with another whether it is local on the same system or remote over the network. You use and control a socket using an application programming interface (API) provided by the Network stack and which is usually based on Berkeley sockets standard.
Two socket types are identified: Datagram sockets and Stream sockets and using the following standard protocol:
Protocol | Description |
---|---|
IP | Internet Protocol provides network routing using IPv4 or IPv6 addressing. |
UDP | User Datagram Protocol - Datagram sockets (Connectionless sockets) |
TCP | Transmission Control Protocol - Stream sockets (Connection-oriented sockets) |
Configuration#
Some parameters should be configured and/or optimized for your project requirements. See the section Socket Layer Configuration for further details.
Using Network Socket Programming API#
Wherever you want to configure, access or use a socket you should include one or many of these files:
Include file | Description |
---|---|
rtos/net/include/net_app.h | Functions used for NetApp API |
rtos/net/include/net_sock.h | Functions used for NetSock API |
rtos/net/include/net_bsd.h | Functions used for BSD API |
rtos/net/include/net_tcp.h | Functions used for Stream Socket Option API |
/rtos/net/include/net_util.h | Functions and Macro used for conversion utilities API |
/rtos/net/include/net_ascii.h | Functions and Macro used for conversion utilities API |
/rtos/net/include/net_ipv4.h | Functions used for IPv4 options API |
/rtos/net/include/net_ipv6.h | Functions used for IPv6 options API |
API Reference#
In addition to the BSD 4.x sockets application interface (API), the Network stack gives the developer the opportunity to use Micrium’s own socket functions with which to interact.
Although there is a great deal of similarity between the two APIs, the parameters of the two sets of functions differ slightly. The purpose of the following sections is o give developers a first look at Micrium’s functions by providing concrete examples of how to use the API.
The Network module sockets can be accessed and controlled using 3 different APIs set:
BSD APIs are following BSD 4.x functions prototype and there are presented in the section BSD Functions .
Net Sock APIs are the low-level socket API. They are based on BSD sockets but with more arguments and it can return the error cause. Net Sock APIs are presented in the section Network Socket Functions .
Net APP APIs include more parameters of the Net Sock API, to accomplish some operation that should be always performed when calling Socket API, such as setting timeout, retry, apply delay and such. Basically, there a wrapper of Net Sock APIs. Net APP APIs are presented in the section Network Application Interface Functions .
Functions#
The following functions must be used to open, connect, receive, transmit, close and so on:
BSD | Net Sock | Net App | Description |
---|---|---|---|
socket() | NetSock_Open() | NetApp_SockOpen() | Create a datagram (i.e., UDP) or stream (i.e., TCP) type socket. |
bind() | NetSock_Bind() | NetApp_SockBind() | Assign network addresses to sockets. |
connect() | NetSock_Conn() | NetApp_SockConn() | Connect a local socket to a remote socket address. |
listen() (TCP only) | NetSock_Listen() | NetApp_SockListen() | Set a socket to accept incoming connections. |
accept() (TCP only) | NetSock_Accept() | NetApp_SockAccept() | Wait for new socket connections on a listening server socket. |
recv() / recvfrom() | NetSock_RxData() / NetSock_RxDataFrom() | NetApp_SockRx() | Copy up to a specified number of bytes received from a remote socket into an application memory buffer. |
send() / sendto() | NetSock_TxData() / NetSock_TxDataTo() | NetApp_SockTx() | Copy bytes from an application memory buffer into a socket to send to a remote socket. |
close() | NetSock_Close() | NetApp_SockConn() | Terminate communication and free a socket. |
select() | NetSock_Sel() | N/A | Check if any sockets are ready for available read or write operations or error conditions. |
N/A | NetSock_SelAbort() | N/A | Abort a select (i.e., unblock task(s) waiting on socket(s) using the select) |
Conversion Utilities#
The following functions should be used when converting 16 or 32-bit values from or to network order or even when converting IP address.
BSD | Micrium Network Functions | Description |
---|---|---|
htonl() | NET_UTIL_HOST_TO_NET_32() | Convert 32-bit integer values from network-order to CPU host-order. |
htons() | NET_UTIL_HOST_TO_NET_16() | Convert 16-bit integer values from network-order to CPU host-order. |
ntohl() | NET_UTIL_NET_TO_HOST_32() | Convert 32-bit integer values from network-order to CPU host-order. |
ntohs() | NET_UTIL_NET_TO_HOST_16() | Convert 16-bit integer values from network-order to CPU host-order. |
inet_addr() (IPv4 only) | NetASCII_Str_to_IPv4() | Convert a string of an IPv4 address in dotted-decimal notation to an IPv4 address in host-order. |
inet_aton() (IPv4 only) | NetASCII_Str_to_IPv4() | Convert an IPv4 address in ASCII dotted-decimal notation to a network protocol IPv4 address in network-order. |
inet_ntoa() (IPv4 only) | NetASCII_IPv4_to_Str() | Convert an IPv4 address in host-order into an IPv4 dotted-decimal notation ASCII string. |
Socket option function#
It is possible to configure many options on a socket, with the followings function you could configure majority of socket's option but some other option can be accessed using some specific API, see Socket Options section.
BSD | Net Sock | Description |
---|---|---|
Get a specific option value on a specific socket. | ||
Set a specific option value on a specific socket. |
Select() Macro#
The following functions should be used when select() is used. It helps to configure and read descriptors to a select() calls.
BSD | Net Sock | Description |
---|---|---|
FD_CLR() | NET_SOCK_DESC_CLR() | Remove a socket file descriptor ID as a member of a file descriptor set. |
FD_ISSET() | NET_SOCK_DESC_IS_SET() | Check if a socket file descriptor ID is a member of a file descriptor set. |
FD_SET() | NET_SOCK_DESC_SET() | Add a socket file descriptor ID as a member of a file descriptor set. |
FD_ZERO() | NET_SOCK_DESC_INIT() | Initialize/zero-clear a file descriptor set. |
Miscellaneous#
Here is a list of other APIs functions that can help in certain conditions:
Function | Description |
---|---|
NetApp_SetSockAddr() | Setup a socket address from an IPv4 or an IPv6 address. |
NetApp_ClientStreamOpen() | Open a stream socket for a client using an IPv4 or IPv6 address. |
NetApp_ClientStreamOpenByHostname() | Open a stream socket for a client using a hostname (resolve the hostname using DNS + Autoselect remote address) |
NetApp_TimeDly_ms() | Delay for a specified time, in milliseconds. |
NetSock_GetConnTransportID() | Gets a socket’s transport layer connection handle ID (e.g., TCP connection ID) if available. |
NetSock_IsConn() | Check if a socket is connected to a remote socket. |
NetSock_PoolStatResetMaxUsed() | Reset Network Sockets’ statistics pool’s maximum number of entries used. |
Socket Error Codes#
When socket functions return error codes, the error codes should be inspected to determine if the error is a temporary, non-fault condition (such as no data to receive) or fatal (such as the socket has been closed).
The following errors are not fatal and if they occur, we recommend trying again the operation after a delay if the socket is in a none-blocking mode.
Error Code | Description |
---|---|
RTOS_ERR_IF_LINK_DOWN | The network interface link is down. |
RTOS_ERR_POOL_EMPTY | One of the network pool from which the stack is trying to get an element is empty. Probably a network buffer pool. |
RTOS_ERR_TIMEOUT | The operation timed out. |
RTOS_ERR_WOULD_BLOCK | The operation would have timed out if the socket was in blocking mode. |
For any other error code returned, we recommend closing the socket.
Network Sockets Concepts#
Byte-Ordering#
TCP/IP has an universal standard to allow communication between any platforms. Thus, it's necessary to have method arranging information so that big-endian and little-endian machines can understand each other. On platforms where data is already correctly ordered, the functions do nothing and are frequently macro'd into empty statements. Byte-ordering functions should always be used as they do not impact performance on systems that are already correctly ordered and they promote code portability.
BSD | Micrium Native Functions | Description |
---|---|---|
htonl() | NET_UTIL_HOST_TO_NET_32 | Convert 32-bit integer values from network-order to CPU host-order. |
htons() | NET_UTIL_HOST_TO_NET_16() | Convert 16-bit integer values from network-order to CPU host-order. |
ntohl() | NET_UTIL_NET_TO_HOST_32() | Convert 32-bit integer values from network-order to CPU host-order. |
ntohs() | NET_UTIL_NET_TO_HOST_16() | Convert 16-bit integer values from network-order to CPU host-order. |
The four byte-ordering functions stand for host to network short, host to network long, network to host short, and network to host long. htons() translates a short integer from host byte-order to network byte-order. htonl is similar, translating a long integer. The other two functions do the reverse, translating from network byte-order to host byte-order.
IP Addresses and Data Structures#
Communication using sockets requires configuring or reading network addresses from network socket address structures.
IP Address Versions#
There are two version of IP. The most known version is the IPv4 address which is 4 bytes long and commonly written in dots and numbers format, such as
192.168.1.1
The next generation of IP is IPv6 which is also supported by the Network module. The IPv6 address is 128 bits long and it is represented using numbers, letters and semi-colons such as:
fe80:14f:c9d2:12::51
IPv4 Addresses#
The BSD socket API defines a generic socket address structure as a blank template with no address-specific configuration.
Listing - Generic (non-address-specific) address structures#
struct sockaddr { /* Generic BSD socket address structure */
CPU_INT16U sa_family; /* Socket address family */
CPU_CHAR sa_data[14]; /* Protocol-specific address information */
};
typedef struct net_sock_addr { /* Generic Micrium socket address structure */
NET_SOCK_ADDR_FAMILY AddrFamily;
CPU_INT08U Addr[14];
} NET_SOCK_ADDR;
…as well as specific socket address structures to configure each specific protocol address family’s network address configuration (e.g., IPv4 socket addresses):
Listing - Internet (IPv4) address structures#
struct in_addr {
NET_IP_ADDR s_addr; /* IPv4 address (32 bits) */
};
struct sockaddr_in { /* BSD IPv4 socket address structure */
CPU_INT16U sin_family; /* Internet address family (e.g., AF_INET) */
CPU_INT16U sin_port; /* Socket address port number (16 bits) */
struct in_addr sin_addr; /* IPv4 address (32 bits) */
CPU_CHAR sin_zero[8]; /* Not used (all zeros) */
};
typedef struct net_sock_addr_ipv4 { /* Micrium socket address structure */
NET_SOCK_ADDR_FAMILY AddrFamily;
NET_PORT_NBR Port;
NET_IPv4_ADDR Addr;
CPU_INT08U Unused[8];
} NET_SOCK_ADDR_IPv4;
A socket address structure’s AddrFamily/sa_family/sin_family value must be read/written in host CPU byte order, while all Addr/sa_data values must be read/written in network byte order (big endian).
Even though socket functions – both Micrium and BSD – pass pointers to the generic socket address structure, applications must declare and pass an instance of the specific protocol’s socket address structure (e.g., an IPv4 address structure). For microprocessors, that requires data access to be aligned to appropriate word boundaries. This forces compilers to declare an appropriately-aligned socket address structure so that all socket address members are correctly aligned to their appropriate word boundaries.
Caution: Applications should avoid, or be cautious when, declaring and configuring a generic byte array as a socket address structure, since the compiler may not correctly align the array or the socket address structure’s members to appropriate word boundaries.
The figure below shows an example IPv4 instance of the Network module NET_SOCK_ADDR_IPv4 (sockaddr_in) structure overlaid on top of NET_SOCK_ADDR (sockaddr) the structure.
Figure - NET_SOCK_ADDR_IP is the IPv4 specific instance of the generic NET_SOCK_ADDR data structure#
A socket could configure the example socket address structure in the figure below to bind on IPv4 address 10.10.1.65 and port number 49876 with the following code:
Listing - Bind on 10.10.1.65#
NET_SOCK_ADDR_IPv4 addr_local;
NET_IPv4_ADDR addr_ip;
NET_PORT_NBR addr_port;
NET_SOCK_RTN_CODE rtn_code;
NET_ERR err;
addr_ip = NetASCII_Str_to_IPv4("10.10.1.65", &err);
addr_port = 49876;
Mem_Clr(&addr_local,
sizeof(addr_local));
addr_local.AddrFamily = NET_SOCK_ADDR_FAMILY_IP_V4; /\* = AF_INET†† Figure 9-1 \*/
addr_local.Addr = NET_UTIL_HOST_TO_NET_32(addr_ip);
addr_local.Port = NET_UTIL_HOST_TO_NET_16(addr_port);
rtn_code = NetSock_Bind( sock_id,
(NET_SOCK_ADDR \*)&addr_local, /\* Cast to generic addr† \*/
sizeof(addr_local),
&err);
† The address of the specific IPv4 socket address structure is cast to a pointer to the generic socket address structure.
IPv6 Addresses#
Socket addresses structures for IPv6 are similar to IPv4.
Listing - Internet (IPv4) address structures#
struct in6_addr {
in6_addr_t s_addr[16];
};
struct sock_addr_in6 {
sa_family_t sin6_family; /* Internet address family (AF_INET6) */
in_port_t sin6_port; /* Socket address port number (16 bits) */
CPU_INT32U sin6_flowinfo; /* Flow info. */
struct in6_addr sin6_addr; /* IPv6 address. (128 bits) */
CPU_INT32U sin6_scope_id; /* Scope zone ix. */
};
typedef struct net_sock_addr_ipv6 {
NET_SOCK_ADDR_FAMILY AddrFamily;
NET_PORT_NBR Port;
CPU_INT32U FlowInfo;
NET_IPv6_ADDR Addr;
CPU_INT32U ScopeID;
} NET_SOCK_ADDR_IPv6;
A socket could configure the example socket address structure in the figure below to bind on IPv6 address FE80::1111:AAAA and port number 49876 with the following code:
Listing - Bind on 10.10.1.65#
NET_SOCK_ADDR_IPv6 addr_local;
NET_IPv6_ADDR addr_ip;
NET_PORT_NBR addr_port;
NET_SOCK_RTN_CODE rtn_code;
NET_ERR err;
addr_ip = NetASCII_Str_to_IPv6("FE80::1111:AAAA", &err);
addr_port = 49876;
Mem_Clr(&addr_local,
sizeof(addr_local));
addr_local.AddrFamily = NET_SOCK_ADDR_FAMILY_IP_V6; /\* = AF_INET6 \*/
Mem_Copy((void \*)&addr_local-\>Addr,
(void \*)&addr_ip,
(CPU_SIZE_T) sizeof(addr_local));
addr_local.Port = NET_UTIL_HOST_TO_NET_16(addr_port);
rtn_code = NetSock_Bind( sock_id,
(NET_SOCK_ADDR \*)&addr_local, /\* Cast to generic addr† \*/
sizeof(addr_local),
&err);
† The address of the specific IPv6 socket address structure is cast to a pointer to the generic socket address structure.
Complete send() Operation#
send() returns the number of bytes actually sent out. This might be less than the number that are available to send. The function will send as much of the data as it can. The developer must make sure that the rest of the packet is sent later.
Listing - Completing a send()#
{
int total = 0; /\* how many bytes we've sent \*/
int bytesleft = \*len; /\* how many we have left to send \*/
int n;
while (total \< \*len) {
n = send(s, buf + total, bytesleft, 0); (1)
if (n == -1) {
break;
}
total += n; (2)
bytesleft -= n; (3)
}
}
(1) Send as many bytes as there are transmit network buffers available.
(2) Increase the number of bytes sent.
(3) Calculate how many bytes are left to send.
This is another example that, for a TCP/IP stack to operate smoothly, sufficient memory to define enough buffers for transmission and reception is a design decision that requires attention if optimum performance for the given hardware is desired.
IPv4 to IPv6#
For a client migrating from IPv4 to IPv6 is basically just using the correct structure following the IP address version to use. For a server, if both IP address must be supported at same time, the application might require some modification, depending it's design since two listening socket will be required one for IPv4 and another for IPv6.
Loopback for Inter-Process Communication#
It is possible for tasks to communicate with sockets via the localhost interface which must be enabled. See Network Core Configuration .
Micrium Native Socket Error Codes#
When socket functions return error codes, the error codes should be inspected to determine if the error is a temporary, non-fault condition (such as no data to receive) or fatal (such as the socket has been closed).
Handling Socket Error Codes#
Whenever any of the following transitory error codes are returned by any Network module's socket function,
RTOS_ERR_POOL_EMPTY
RTOS_ERR_NET_IF_LINK_DOWN
RTOS_ERR_TIMEOUT
RTOS_ERR_WOULD_BLOCK
The following action must be taken:
If the socket is a blocking socket, the operation must be retried.
If the socket is a non-blocking socket, the application must delay and then the operation must be retried.
Otherwise, if the error code is something other than
RTOS_ERR_NONE
Then that socket must be immediately closed() ’d without further access by any other socket functions:
Datagram Socket (UDP)#
The figure below reproduces a diagram that introduces sample code using the typical socket functions for a UDP client-server application. The example uses the Micrium proprietary socket API function calls. A similar example could be written using the BSD socket API.
Figure - Socket calls used in a typical UDP client-server application#
The code in the listing below implements a UDP server. It opens a socket and binds an IP address, listens and waits for a packet to arrive at the specified port.
Datagram Server (UDP Server)#
Listing - Datagram Server#
#include <rtos/net/include/net_cfg_net.h>
#include <rtos/net/include/net_type.h>
#include <rtos/net/include/net_sock.h>
#include <rtos/net/include/net_app.h>
#include <rtos/net/include/net_util.h>
#ifdef NET_IPv4_MODULE_EN
#include <rtos/net/include/net_ipv4.h>
#endif
#ifdef NET_IPv6_MODULE_EN
#include <rtos/net/include/net_ipv6.h>
#endif
#define UDP_SERVER_PORT 10001
#define RX_BUF_SIZE 15
/*
*********************************************************************************************************
* Ex_Net_SockUDP_ServerIPv4()
*
* Description : UDP Echo server:
*
* (a) Open a socket.
* (b) Configure socket's address.
* (c) Bind that socket.
* (d) Receive data on the socket.
* (e) Transmit to source the data received.
* (f) Close socket on fatal fault error.
*
* Argument(s) : None.
*
* Return(s) : None.
*
* Note(s) : None.
*********************************************************************************************************
*/
#ifdef NET_IPv4_MODULE_EN
void Ex_Net_SockUDP_ServerIPv4 (void)
{
NET_SOCK_ID sock;
NET_SOCK_ADDR_IPv4 server_sock_addr_ip;
NET_SOCK_ADDR_IPv4 client_sock_addr_ip;
NET_SOCK_ADDR_LEN client_sock_addr_ip_size;
NET_SOCK_RTN_CODE rx_size;
NET_SOCK_RTN_CODE tx_size;
NET_SOCK_DATA_SIZE tx_rem;
CPU_CHAR rx_buf[RX_BUF_SIZE];
CPU_INT32U addr_any = NET_IPv4_ADDR_ANY;
CPU_INT08U *p_buf;
CPU_BOOLEAN fault_err;
RTOS_ERR err;
/* ---------------- OPEN IPV4 SOCKET ----------------- */
sock = NetSock_Open(NET_SOCK_PROTOCOL_FAMILY_IP_V4,
NET_SOCK_TYPE_DATAGRAM,
NET_SOCK_PROTOCOL_UDP,
&err);
if (err.Code != RTOS_ERR_NONE) {
return;
}
/* ----------- CONFIGURE SOCKET'S ADDRESS ------------ */
NetApp_SetSockAddr((NET_SOCK_ADDR *)&server_sock_addr_ip,
NET_SOCK_ADDR_FAMILY_IP_V4,
UDP_SERVER_PORT,
(CPU_INT08U *)&addr_any,
NET_IPv4_ADDR_SIZE,
&err);
if (err.Code != RTOS_ERR_NONE) {
NetSock_Close(sock, &err);
return;
}
/* ------------------ BIND SOCKET -------------------- */
NetSock_Bind( sock,
(NET_SOCK_ADDR *)&server_sock_addr_ip,
NET_SOCK_ADDR_SIZE,
&err);
if (err.Code != RTOS_ERR_NONE) {
NetSock_Close(sock, &err);
return;
}
fault_err = DEF_NO;
do {
/* ----- WAIT UNTIL RECEIVING DATA FROM A CLIENT ----- */
client_sock_addr_ip_size = sizeof(client_sock_addr_ip);
rx_size = NetSock_RxDataFrom( sock,
rx_buf,
RX_BUF_SIZE,
NET_SOCK_FLAG_NONE,
(NET_SOCK_ADDR *)&client_sock_addr_ip,
&client_sock_addr_ip_size,
DEF_NULL,
DEF_NULL,
DEF_NULL,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
tx_rem = rx_size;
p_buf = (CPU_INT08U *)rx_buf;
/* ---- TRANSMIT THE DATA RECEIVED TO THE CLIENT ----- */
do {
tx_size = NetSock_TxDataTo( sock,
p_buf,
tx_rem,
NET_SOCK_FLAG_NONE,
(NET_SOCK_ADDR *)&client_sock_addr_ip,
client_sock_addr_ip_size,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
tx_rem -= tx_size;
p_buf = (CPU_INT08U *)(p_buf + tx_size);
break;
case RTOS_ERR_POOL_EMPTY:
case RTOS_ERR_NET_IF_LINK_DOWN:
case RTOS_ERR_TIMEOUT:
case RTOS_ERR_WOULD_BLOCK:
OSTimeDlyHMSM(0, 0, 0, 5, OS_OPT_TIME_DLY, &err);
break;
default:
fault_err = DEF_YES;
break;
}
} while (tx_rem > 0);
break;
case RTOS_ERR_WOULD_BLOCK:
case RTOS_ERR_TIMEOUT:
break;
default:
fault_err = DEF_YES;
break;
}
} while (fault_err == DEF_NO);
/* ------------- FATAL FAULT SOCKET ERROR ------------ */
NetSock_Close(sock, &err); /* This function should be reached only when a fatal */
/* fault error has occurred. */
}
#endif
/*
*********************************************************************************************************
* Ex_Net_SockUDP_ServerIPv6()
*
* Description : UDP Echo server:
*
* (a) Open a socket.
* (b) Configure socket's address.
* (c) Bind that socket.
* (d) Receive data on the socket.
* (e) Transmit to source the data received.
* (f) Close socket on fatal fault error.
*
* Argument(s) : None.
*
* Return(s) : None.
*
* Note(s) : None.
*********************************************************************************************************
*/
#ifdef NET_IPv6_MODULE_EN
void Ex_Net_SockUDP_ServerIPv6 (void)
{
NET_SOCK_ID sock;
NET_SOCK_ADDR_IPv6 server_sock_addr_ip;
NET_SOCK_ADDR_IPv6 client_sock_addr_ip;
NET_SOCK_ADDR_LEN client_sock_addr_ip_size;
NET_SOCK_RTN_CODE rx_size;
NET_SOCK_RTN_CODE tx_size;
NET_SOCK_DATA_SIZE tx_rem;
CPU_CHAR rx_buf[RX_BUF_SIZE];
CPU_INT08U *p_buf;
CPU_BOOLEAN fault_err;
RTOS_ERR err;
/* ------------- OPEN IPV6 SOCKET ------------ */
sock = NetSock_Open(NET_SOCK_PROTOCOL_FAMILY_IP_V6, /* IPv6 Socket family. */
NET_SOCK_TYPE_DATAGRAM, /* Datagram socket. */
NET_SOCK_PROTOCOL_UDP, /* UDP protocol. */
&err);
if (err.Code != RTOS_ERR_NONE) {
return;
}
/* -------- CONFIGURE SOCKET'S ADDRESS ------- */
/* Populate the NET_SOCK_ADDR_IP structure for */
/* the server address and port, and convert */
/* it to network order. */
NetApp_SetSockAddr((NET_SOCK_ADDR *)&server_sock_addr_ip,
NET_SOCK_ADDR_FAMILY_IP_V6,
UDP_SERVER_PORT,
(CPU_INT08U *)&NET_IPv6_ADDR_ANY,
NET_IPv6_ADDR_SIZE,
&err);
if (err.Code != RTOS_ERR_NONE) {
NetSock_Close(sock, &err);
return;
}
/* ---------------- BIND SOCKET -------------- */
NetSock_Bind( sock, /* Bind the newly-created socket to the */
(NET_SOCK_ADDR *)&server_sock_addr_ip, /* address and port specified by */
NET_SOCK_ADDR_SIZE, /* server_sock_addr_ip. */
&err);
if (err.Code != RTOS_ERR_NONE) {
NetSock_Close(sock, &err);
return;
}
fault_err = DEF_NO;
do {
/* - WAIT UNTIL RECEIVING DATA FROM A CLIENT - */
client_sock_addr_ip_size = sizeof(client_sock_addr_ip);
rx_size = NetSock_RxDataFrom( sock, /* Receive data on port UDP_SERVER_PORT. */
(void *)rx_buf,
RX_BUF_SIZE,
NET_SOCK_FLAG_NONE,
(NET_SOCK_ADDR *)&client_sock_addr_ip,
&client_sock_addr_ip_size,
DEF_NULL,
DEF_NULL,
DEF_NULL,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
tx_rem = rx_size;
p_buf = (CPU_INT08U *)rx_buf;
do {
/* ------ TRANSMIT RECEIVED DATA TO THE CLIENT ------- */
/* Transmit data to IP address and port of the client. */
tx_size = NetSock_TxDataTo( sock,
(void *)p_buf,
tx_rem,
NET_SOCK_FLAG_NONE,
(NET_SOCK_ADDR *)&client_sock_addr_ip,
client_sock_addr_ip_size,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
tx_rem -= tx_size;
p_buf = (CPU_INT08U *)(p_buf + tx_size);
break;
case RTOS_ERR_POOL_EMPTY:
case RTOS_ERR_NET_IF_LINK_DOWN:
case RTOS_ERR_TIMEOUT:
case RTOS_ERR_WOULD_BLOCK:
OSTimeDlyHMSM(0, 0, 0, 5, OS_OPT_TIME_DLY, &err);
break;
default:
fault_err = DEF_YES;
break;
}
} while (tx_rem > 0);
break;
case RTOS_ERR_WOULD_BLOCK:
case RTOS_ERR_TIMEOUT:
break;
default:
fault_err = DEF_YES;
break;
}
} while (fault_err == DEF_NO);
/* ------------- FATAL FAULT SOCKET ERROR ------------ */
NetSock_Close(sock, &err); /* This function should be reached only when a fatal */
/* fault error has occurred. */
}
#endif
Datagram Client (UDP Client)#
The code in the listing below implements a UDP client. It sends a ‘Hello World!’ message to a server that listens on the UDP_SERVER_PORT.
Listing - Datagram Client#
#define UDP_SERVER_IP_ADDR "192.168.1.100"
#define UDP_SERVER_PORT 10001
#define UDP_SERVER_TX_STR "Hello World!"
CPU_BOOLEAN TestUDPClient (void)
{
NET_SOCK_ID sock;
NET_IP_ADDR server_ip_addr;
NET_SOCK_ADDR_IPv4 server_sock_addr_ip;
NET_SOCK_ADDR_LEN server_sock_addr_ip_size;
CPU_CHAR *pbuf;
CPU_INT16S buf_len;
NET_SOCK_RTN_CODE tx_size;
RTOS_ERR err;
pbuf = UDP_SERVER_TX_STR;
buf_len = Str_Len(UDP_SERVER_TX_STR);
sock = NetSock_Open( NET_SOCK_ADDR_FAMILY_IP_V4, (1)
NET_SOCK_TYPE_DATAGRAM,
NET_SOCK_PROTOCOL_UDP,
&err);
if (err.Code != RTOS_ERR_NONE) {
return (DEF_FAIL);
}
server_ip_addr = NetASCII_Str_to_IP(UDP_SERVER_IP_ADDR, &err); (2)
if (err.Code != RTOS_ERR_NONE) {
NetSock_Close(sock, &err);
return (DEF_FAIL);
}
server_sock_addr_ip_size = sizeof(server_sock_addr_ip); (3)
Mem_Clr(&server_sock_addr_ip,
server_sock_addr_ip_size);
server_sock_addr_ip.AddrFamily = NET_SOCK_ADDR_FAMILY_IP_V4;
server_sock_addr_ip.Addr = NET_UTIL_HOST_TO_NET_32(server_ip_addr);
server_sock_addr_ip.Port = NET_UTIL_HOST_TO_NET_16(UDP_SERVER_PORT);
tx_size = NetSock_TxDataTo( sock, (4)
pbuf,
buf_len,
NET_SOCK_FLAG_NONE,
(NET_SOCK_ADDR *)&server_sock_addr_ip,
sizeof(server_sock_addr_ip),
&err);
NetSock_Close(sock, &err); (5)
if (err.Code != RTOS_ERR_NONE) {
return (DEF_FAIL);
}
return (DEF_TRUE);
}
Open a datagram socket (UDP protocol).
Convert an IPv4 address from ASCII dotted-decimal notation to a network protocol IPv4 address in host-order.
Populate the NET_SOCK_ADDR_IP structure for the server address and port, and convert it to network order.
Transmit data to host DATAGRAM_SERVER_IP_ADDR on port DATAGRAM_SERVER_PORT.
Close the socket.
Stream Socket (TCP)#
The figure below uses the Micrium proprietary socket API function calls. A similar example could be written using the BSD socket API.
Typically, after a TCP server starts, TCP clients can connect and send requests to the server. A TCP server waits until client connections arrive and then creates a dedicated TCP socket connection to process the client’s requests and reply back to the client (if necessary). This continues until either the client or the server closes the dedicated client-server connection. Also while handling multiple, simultaneous client-server connections, the TCP server can wait for new client-server connections
Figure - Socket calls used in a typical TCP client-server application#
Stream Server (TCP Server)#
This example presents a very basic client-server application over a TCP connection. The server presented is simply waits for a connection and send the string ‘Hello World!’. See section Socket Functions API for a list of all socket API functions.
Listing - Stream Server#
#define TCP_SERVER_PORT 10000
#define TCP_SERVER_CONN_Q_SIZE 1
#define TCP_SERVER_TX_STR "Hello World!"
CPU_BOOLEAN TestTCPServer (void)
{
NET_SOCK_ID sock_listen;
NET_SOCK_ID sock_req;
NET_SOCK_ADDR_IPv4 server_sock_addr_ip;
NET_SOCK_ADDR_LEN server_sock_addr_ip_size;
NET_SOCK_ADDR_IPv4 client_sock_addr_ip;
NET_SOCK_ADDR_LEN client_sock_addr_ip_size;
CPU_BOOLEAN attempt_conn;
CPU_CHAR *pbuf;
CPU_INT16S buf_len;
NET_SOCK_RTN_CODE tx_size;
RTOS_ERR err;
pbuf = TCP_SERVER_TX_STR;
buf_len = Str_Len(TCP_SERVER_TX_STR);
sock_listen = NetSock_Open( NET_SOCK_ADDR_FAMILY_IP_V4, (1)
NET_SOCK_TYPE_STREAM,
NET_SOCK_PROTOCOL_TCP,
&err);
if (err.Code != RTOS_ERR_NONE) {
return (DEF_FALSE);
}
server_sock_addr_ip_size = sizeof(server_sock_addr_ip); (2)
Mem_Clr(&server_sock_addr_ip,
server_sock_addr_ip_size);
server_sock_addr_ip.AddrFamily = NET_SOCK_ADDR_FAMILY_IP_V4;
server_sock_addr_ip.Addr = NET_UTIL_HOST_TO_NET_32(NET_SOCK_ADDR_IP_WILD_CARD);
server_sock_addr_ip.Port = NET_UTIL_HOST_TO_NET_16(TCP_SERVER_PORT);
NetSock_Bind( sock_listen, (3)
(NET_SOCK_ADDR *)&server_sock_addr_ip,
NET_SOCK_ADDR_SIZE,
&err);
if (err.Code != RTOS_ERR_NONE) {
NetSock_Close(sock_listen, &err);
return (DEF_FALSE);
}
NetSock_Listen( sock_listen, (4)
TCP_SERVER_CONN_Q_SIZE,
&err);
if (err.Code != RTOS_ERR_NONE) {
NetSock_Close(sock_listen, &err);
return (DEF_FALSE);
}
do {
client_sock_addr_ip_size = sizeof(client_sock_addr_ip);
sock_req = NetSock_Accept( sock_listen, (5)
(NET_SOCK_ADDR *)&client_sock_addr_ip,
&client_sock_addr_ip_size,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
attempt_conn = DEF_NO;
break;
/* Transitory Errors */
case RTOS_ERR_POOL_EMPTY:
case RTOS_ERR_NET_IF_LINK_DOWN:
case RTOS_ERR_TIMEOUT:
case RTOS_ERR_WOULD_BLOCK:
OSTimeDlyHMSM(0, 0, 0, 5, OS_OPT_TIME_DLY, &err);
break;
default:
attempt_conn = DEF_NO;
break;
}
} while (attempt_conn == DEF_YES);
if (err.Code != RTOS_ERR_NONE) {
NetSock_Close(sock_req, &err);
return (DEF_FALSE);
}
tx_size = NetSock_TxData( sock_req, (6)
pbuf,
buf_len,
NET_SOCK_FLAG_NONE,
&err);
NetSock_Close(sock_req, &err); (7)
NetSock_Close(sock_listen, &err);
return (DEF_TRUE);
}
Open a stream socket (TCP protocol).
Populate the NET_SOCK_ADDR_IP structure for the server address and port, and convert it to network order.
Bind the newly created socket to the address and port specified by server_sock_addr_ip.
Set the socket to listen for a connection request coming on the specified port.
Accept the incoming connection request, and return a new socket for this particular connection. Note that this function call is being called from inside a loop because it might timeout (no client attempts to connect to the server).
One the connection has been established between the server and a client, transmit the message. Note that the return value of this function is not used here, but a real application should make sure all the message has been sent by comparing that value with the length of the message.
Close both listen and request sockets. When the server need to stay active, the listen socket stays open so that I can accept additional connection requests. Usually, the server will wait for a connection, accept() it, and OSTaskCreate() a task to handle it.
Stream Client (TCP Client)#
The client of the listing below connects to the specified server and receives the string the server sends.
Listing - Stream Client#
#define TCP_SERVER_IP_ADDR "192.168.1.101"
#define TCP_SERVER_PORT 10000
#define RX_BUF_SIZE 15
CPU_BOOLEAN TestTCPClient (void)
{
NET_SOCK_ID sock;
NET_IP_ADDR server_ip_addr;
NET_SOCK_ADDR_IPv4 server_sock_addr_ip;
NET_SOCK_ADDR_LEN server_sock_addr_ip_size;
NET_SOCK_RTN_CODE conn_rtn_code;
NET_SOCK_RTN_CODE rx_size;
CPU_CHAR rx_buf[RX_BUF_SIZE];
RTOS_ERR err;
sock = NetSock_Open( NET_SOCK_ADDR_FAMILY_IP_V4, (1)
NET_SOCK_TYPE_STREAM,
NET_SOCK_PROTOCOL_TCP,
&err);
if (err.Code != RTOS_ERR_NONE) {
return (DEF_FAIL);
}
server_ip_addr = NetASCII_Str_to_IP(TCP_SERVER_IP_ADDR, &err); (2)
if (err.Code != RTOS_ERR_NONE) {
NetSock_Close(sock, &err);
return (DEF_FAIL);
}
server_sock_addr_ip_size = sizeof(server_sock_addr_ip); (3)
Mem_Clr(&server_sock_addr_ip,
server_sock_addr_ip_size);
server_sock_addr_ip.AddrFamily = NET_SOCK_ADDR_FAMILY_IP_V4;
server_sock_addr_ip.Addr = NET_UTIL_HOST_TO_NET_32(server_ip_addr);
server_sock_addr_ip.Port = NET_UTIL_HOST_TO_NET_16(TCP_SERVER_PORT);
conn_rtn_code = NetSock_Conn( sock, (4)
(NET_SOCK_ADDR *)&server_sock_addr_ip,
sizeof(server_sock_addr_ip),
&err);
if (err.Code != RTOS_ERR_NONE) {
NetSock_Close(sock, &err);
return (DEF_FAIL);
}
rx_size = NetSock_RxData( sock, (5)
rx_buf,
RX_BUF_SIZE,
NET_SOCK_FLAG_NONE,
&err);
if (err.Code != RTOS_ERR_NONE) {
NetSock_Close(sock, &err);
return (DEF_FAIL);
}
NetSock_Close(sock, &err); (6)
return (DEF_TRUE);
}
Open a stream socket (TCP protocol).
Convert an IPv4 address from ASCII dotted-decimal notation to a network protocol IPv4 address in host-order.
Populate the NET_SOCK_ADDR_IPv4 structure for the server address and port, and convert it to network order.
Connect the socket to a remote host.
Receive data from the connected socket. Note that the return value for this function is not used here.However, a real application should make sure everything has been received.
Close the socket.
TCP Connection Configuration#
The Network module provides a set of APIs to configure TCP connections on an individual basis.
These APIs are listed below and detailed in TCP Functions :
NetTCP_ConnCfgMaxSegSizeLocal()
NetTCP_ConnCfgReTxMaxTh()
NetTCP_ConnCfgReTxMaxTimeout()
NetTCP_ConnCfgRxWinSize()
NetTCP_ConnCfgTxWinSize()
NetTCP_ConnCfgTxAckImmedRxdPushEn()
NetTCP_ConnCfgTxNagleEn()
NetTCP_ConnCfgTxKeepAliveEn()
NetTCP_ConnCfgTxKeepAliveTh()
NetTCP_ConnCfgTxAckDlyTimeout()
Socket Options#
Specific Socket Option API#
The Network module provides a set of APIs to configure sockets on an individual basis. These APIs are listed below and detailed in Network Socket Functions :
NetSock_CfgBlock()
NetSock_CfgSecure()
NetSock_CfgRxQ_Size()
NetSock_CfgTxQ_Size()
NetSock_CfgTxIP_TOS() (IPv4 only)
NetSock_CfgTxIP_TTL() (IPv4 only)
NetSock_CfgTxIP_TTL_Multicast() (IPv4 only)
NetSock_CfgTimeoutConnAcceptDflt()(TCP)
NetSock_CfgTimeoutConnAcceptGet_ms()
NetSock_CfgTimeoutConnAcceptSet()
NetSock_CfgTimeoutConnCloseDflt()
NetSock_CfgTimeoutConnCloseGet_ms()
NetSock_CfgTimeoutConnCloseSet()
NetSock_CfgTimeoutConnReqDflt()
NetSock_CfgTimeoutConnReqGet_ms()
NetSock_CfgTimeoutConnReqSet()
NetSock_CfgTimeoutRxQ_Dflt()
NetSock_CfgTimeoutRxQ_Get_ms()
NetSock_CfgTimeoutRxQ_Set()
NetSock_CfgTimeoutTxQ_Dflt()
NetSock_CfgTimeoutTxQ_Get_ms()
NetSock_CfgTimeoutTxQ_Set()
Generic Socket Option API#
The Network module provides two APIs to read and configure socket option values. These APIs are listed below and detailed in Network Socket Functions :
NetSock_OptGet()
NetSock_OptSet()
Their BSD equivalent are listed below. See also BSD Functions .
getsockopt() (TCP/UDP)
setsockopt() (TCP/UDP)
Controlling Socket Blocking Options#
By default all sockets are configured to block. It is possible to change that behavior by using the socket option API NetSock_CfgBlock() or when by setting a flag before calling Socket API. It is also possible to change the default configuration, see in net_cfg.h for further details.
MSL#
Maximum Segment Lifetime (MSL) is the time a TCP segment can exist in the network, and is defined as two minutes. 2MSL is twice this lifetime. It is the maximum lifetime of a TCP segment on the network because it supposes segment transmission and acknowledgment.
Currently, Micrium does not support multiple sockets with identical connection information. This prevents new sockets from binding to the same local addresses as other sockets. Thus, for TCP sockets, each close() incurs the TCP 2MSL timeout and prevents the next bind() from the same client from occurring until after the timeout expires. This is why the 2MSL value is used. This can lead to a long delay before the socket resource is released and reused. The Network module configures the TCP connection's default maximum segment lifetime (MSL) timeout value, specified in integer seconds. A starting value of 3 seconds is recommended.
If TCP connections are established and closed rapidly, it is possible that this timeout may further delay new TCP connections from becoming available. Thus, an even lower timeout value may be desirable to free TCP connections and make them available for new connections as rapidly as possible. However, a 0 second timeout prevents the Network module from performing the complete TCP connection close sequence and will instead send TCP reset (RST) segments.
For UDP sockets, the sockets close() without delay. Thus, the next bind() is not blocked.
TCP Keep-Alives#
The Network module support TCP Keep-Alives. RFC #1122 stipulate that "Keep-Alive mechanism periodically probes the other end of a connection when connection is otherwise idle, even when there is not data to be sent."
It is possible to enable the Keep-Alive option for a specific socket by using the function NetSock_OptSet() with the option NET_SOCK_OPT_SOCK_KEEP_ALIVE.
Knowing your TCP connection ID, you can also used the API function NetTCP_ConnCfgTxKeepAliveEn().
Each time the connection Idle timeout occurs, the stack will send a keep-alive message to probe the other end of the connection. The timeout value for a given TCP connection can be set with the NetSock_OptSet() function and the NET_SOCK_OPT_TCP_KEEP_IDLE option. Else, knowing your TCP connection ID, the API function NetTCP_ConnCfgIdleTimeout() can also be use.
Secure Sockets (TLS or SSL)#
If a network security module (such as Mocana - NanoSSL) is available, the Network module socket security option APIs can be used to secure sockets. The port layer developed for the network security layer is responsible for securing the sockets and applying the security strategy over typical socket programming functions. From an application point of view, the usage of the Network security manager is very simple. It requires a few simple steps depending on whether the application is a server or a client. Basically, it provides APIs to install the required keying material and to set the secure flag on a specific socket:
NetSock_CfgSecure()
NetSock_CfgSecureServerCertKeyInstall()
NetSock_CfgSecureClientCertKeyInstall()
NetSock_CfgSecureClientCommonName()
NetSock_CfgSecureClientTrustCallBack()
The stack must have been configured to support Transport layer security in net_cfg.h, see Transport Layer Security Configuration . Obviously, TLS or SSL can be used only with a TCP connection. Once the socket is configured as secure and the connection is established, all the data transferred using standard socket API are automatically encrypted between the client and the server.
Server Sample#
In order to achieve secure handshake connections, some keying material must be installed before performing any secure socket operation. The server needs to install a public key certificate / private key pair to send to the clients that want to connect. Certificate and key can be delivered by a Certificate Authority or can be generated using a tool such as OpenSSL.
The following example demonstrates how to secure a server using a PEM certificate from a constant buffer.
Listing - Server Sample#
#include <rtos/net/include/net_sock.h>
#include <rtos/net/include/net_app.h>
#include <rtos/net/include/net_util.h>
#include <rtos/net/include/net_bsd.h>
#define APP_CFG_SECURE_CERT \
"MIIEEjCCAvqgAwIBAgIBBzANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDEw9WYWxp\
Y29yZS1EQzEtQ0EwHhcNMTEwMzE4MTcwMTQyWhcNMjEwMzE1MTcwMTQyWjCBkDEL\
MAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQ8wDQYDVQQHEwZJcnZpbmUxHjAcBgNV\
BAoTFVZhbGljb3JlIFRlY2hub2xvZ2llczEhMB8GA1UEAxMYbGFuLWZ3LTAxLnZh\
bGljb3JlLmxvY2FsMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBsb2NhbGRvbWFpbjCC\
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALwGOahytiwshzz1s/ngxy1+\
+VrXZYjKSEzMYbJCUhK9xA5fz8pGtOZIXI+CasZPSbXv+ZDLGpSpeFnOL49plYRs\
vmTxg2n3AlZbP6pD9OPU8rmufsTvXAmQGxxIkdmWiXYJk0pbj+U698me6DKMV/sy\
3ekQaQC2I2nr8uQw8RhuNhhlkWyjBWdXnS2mLNLSan2Jnt8rumtAi3B+vF5Vf0Fa\
kLJNt45R0f5jjuab+qw4PKMZEQbqe0XTNzkxdD0XNRBdKlajffoZPBJ7xkfuKUA3\
cMjXKzetABoKvsv+ElfvqlrI9RXvTXy52EaQmVhiOyBHrScq4RbwtDQsd59Qmk0C\
AwEAAaOB6zCB6DAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIGQDA0BglghkgB\
hvhCAQ0EJxYlRWFzeS1SU0EgR2VuZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAd\
BgNVHQ4EFgQUrq5KF11M9rpKm75nAs+MaiK0niYwUQYDVR0jBEowSIAU2Q9eGjzS\
LZhvlRRKO6c4Q5ATtuChHqQcMBoxGDAWBgNVBAMTD1ZhbGljb3JlLURDMS1DQYIQ\
T9aBcT0uXoxJmC0ohp7oSTATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMC\
BaAwDQYJKoZIhvcNAQEFBQADggEBAAUMm/9G+mhxVIYK4anc34FMqu88NQy8lrh0\
loNfHhIEKnerzMz+nQGidf+KBg5K5U2Jo8e9gVnrzz1gh2RtUFvDjgosGIrgYZMN\
yreNUD2I7sWtuWFQyEuewbs8h2MECs2xVktkqp5KPmJGCYGhXbi+zuqi/19cIsly\
yS01kmexwcFMXyX4YOVbG+JFHy1b4zFvWgSDULj14AuKfc8RiZNvMRMWR/Jqlpr5\
xWQRSmkjuzQMFavs7soZ+kHp9vnFtY2D6gF2cailk0sdG0uuyPBVxEJ2meifG6eb\
o3FQzdtIrB6oMFHEU00P38SJq+mrDItPDRXNLa2Nrtc1EJtmjws="
#define APP_CFG_SECURE_KEY \
"MIIEogIBAAKCAQEAvAY5qHK2LCyHPPWz+eDHLX75WtdliMpITMxhskJSEr3EDl/P\
yka05khcj4Jqxk9Jte/5kMsalKl4Wc4vj2mVhGy+ZPGDafcCVls/qkP049Tyua5+\
xO9cCZAbHEiR2ZaJdgmTSluP5Tr3yZ7oMoxX+zLd6RBpALYjaevy5DDxGG42GGWR\
bKMFZ1edLaYs0tJqfYme3yu6a0CLcH68XlV/QVqQsk23jlHR/mOO5pv6rDg8oxkR\
Bup7RdM3OTF0PRc1EF0qVqN9+hk8EnvGR+4pQDdwyNcrN60AGgq+y/4SV++qWsj1\
Fe9NfLnYRpCZWGI7IEetJyrhFvC0NCx3n1CaTQIDAQABAoIBAEbbqbr7j//RwB2P\
EwZmWWmh4mMDrbYBVYHrvB2rtLZvYYVxQiOexenK92b15TtbAhJYn5qbkCbaPwrJ\
E09eoQRI3u+3vKigd/cHaFTIS2/Y/qhPRGL/OZY5Ap6EEsMHYkJjlWh+XRosQNlw\
01zJWxbFsq90ib3E5k+ypdStRQ7JQ9ntvDAP6MDp3DF2RYf22Tpr9t3Oi2mUirOl\
piOEB55wydSyIhSHusbms3sp2uvQBYJjZP7eENEQz55PebTzl9UF2dgJ0wJFS073\
rvp46fibcch1L7U6v8iUNaS47GTs3MMyO4zda73ufhYwZLU5gL8oEDY3tf/J8zuC\
mNurr0ECgYEA8i1GgstYBFSCH4bhd2mLu39UVsIvHaD38mpJE6avCNOUq3Cyz9qr\
NzewG7RyqR43HsrVqUSQKzlAGWqG7sf+jkiam3v6VW0y05yqDjs+SVW+ZN5CKyn3\
sMZV0ei4MLrfxWneQaKy/EUTJMlz3rLSDM/hpJoA/gOo9BIFRf2HPkkCgYEAxsGq\
LYU+ZEKXKehVesh8rIic4QXwzeDmpMF2wTq6GnFq2D4vWPyVGDWdORcIO2BojDWV\
EZ8e7F2SghbmeTjXGADldYXQiQyt4Wtm+oJ6d+/juKSrQ1HIPzn1qgXDNLPfjd9o\
9lX5lGlRn49Jrx/kKQAPTcnCa1IirIcsmcdiy+UCgYBEbOBwUi3zQ0Fk0QJhb/Po\
LSjSPpl7YKDN4JP3NnBcKRPngLc1HU6lElny6gA/ombmj17hLZsia1GeHMg1LVLS\
NtdgOR5ZBrqGqcwuqzSFGfHqpBXEBl6SludmoL9yHUreh3QhzWuO9aFcEoNnl9Tb\
g9z4Wf8Pxk71byYISYLt6QKBgERActjo3ZD+UPyCHQBp4m45B246ZQO9zFYdXVNj\
gE7eTatuR0IOkoBawN++6gPByoUDTWpcsvjF9S6ZAJH2E97ZR/KAfijh4r/66sTx\
k26mQRPB8FHQvqv/kj3NdsgdUJJeeqPEyEzPkcjyIoJxuB7gN2El/I5wCRon3Qf9\
sQ6FAoGAfVOaROSAtq/bq9JIL60kkhA9sr3KmX52PnOR2hW0caWi96j+2jlmPT93\
4A2LIVUo6hCsHLSCFoWWiyX9pIqyYTn5L1EmeBO0+E8BH9F/te9+ZZ53U+quwc/X\
AZ6Pseyhj7S9wkI5hZ9SO1gcK4rWrAK/UFOIzzlACr5INr723vw="
#define APP_CFG_SECURE_CERT_LEN (sizeof(APP_CFG_SECURE_CERT) - 1)
#define APP_CFG_SECURE_KEY_LEN (sizeof(APP_CFG_SECURE_KEY) - 1)
int AppServerInit (void)
{
NET_SOCK_ID sock_id;
NET_SOCK_ADDR_IPv4 addr_server_ip;
NET_SOCK_ADDR_LEN addr_len;
RTOS_ERR err;
/* --------------- OPEN SOCK ----------------- */
sock_id = NetApp_SockOpen(NET_SOCK_ADDR_FAMILY_IP_V4,
NET_SOCK_TYPE_STREAM,
NET_SOCK_PROTOCOL_TCP,
3,
5,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
break;
default:
return (-1);
}
/* ----------- CFG SOCK SECURE OPT ----------- */
(void)NetSock_CfgSecure(sock_id,
DEF_YES,
&err);
switch (err) {
case RTOS_ERR_NONE:
break;
default:
NetApp_SockClose(sock_id, 1, &err);
return (-1);
}
(void)NetSock_CfgSecureServerCertKeyInstall(sock_id,
APP_CFG_SECURE_CERT,
APP_CFG_SECURE_CERT_LEN,
APP_CFG_SECURE_KEY,
APP_CFG_SECURE_KEY_LEN,
NET_SOCK_SECURE_CERT_KEY_FMT_PEM,
DEF_NO,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
break;
default:
NetApp_SockClose(sock_id, 1, &err);
return (-1);
}
/* ---------------- BIND SOCK ---------------- */
addr_len = sizeof(addr_server_ip);
Mem_Set((void *)&addr_server_ip,
0u,
addr_len);
addr_server_ip.AddrFamily = NET_SOCK_ADDR_FAMILY_IP_V4;
addr_server_ip.Port = NET_UTIL_HOST_TO_NET_16(12345);
addr_server_ip.Addr = NET_UTIL_HOST_TO_NET_32(NET_IPv4_ADDR_NONE);
(void)NetApp_SockBind( sock_id,
(NET_SOCK_ADDR *)&addr_server_ip,
addr_len,
3,
5,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
break;
default:
NetApp_SockClose(sock_id, 1, &err);
return (-1);
}
/* --------------- LISTEN SOCK --------------- */
(void)NetApp_SockListen(sock_id, 1, &err);
switch(err.Code) {
case RTOS_ERR_NONE:
break;
default:
NetApp_SockClose(sock_id, 1, &err);
return (-1);
}
return (sock_id);
}
Client Sample#
In order to achieve secure handshake connections, some keying material must be installed before performing any secure socket operation. The client side needs to install certificate authorities to validate the identity of the public key certificate sent by the server side. Certificate authorities can be found on the web site of Certificate authorities who delivered the Server's certificate ad key. As for the server's certificate it's possible to generate the CA certificate when generating self-signed certificate-key pair using a tool such as OpenSSL.
Listing - Client Sample#
#include <rtos/net/include/net_secure_mocana.h>
#include <rtos/net/include/net_sock.h>
#include <rtos/net/include/net_app.h>
#include <rtos/net/include/net_ascii.h>
#include <rtos/net/include/net_util.h>
#include <rtos/common/include/lib_str.h>
CPU_CHAR *p_cert =
"MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT"
"MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i"
"YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG"
"EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg"
"R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9"
"9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq"
"fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv"
"iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU"
"1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+"
"bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW"
"MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA"
"ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l"
"uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn"
"Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS"
"tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF"
"PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un"
"hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV"
"5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==";
CPU_BOOLEAN AppClientCertTrustCallBackFnct (void *p_cert_dn,
NET_SOCK_SECURE_UNTRUSTED_REASON reason);
int AppClientConnect (void)
{
NET_SOCK_ID sock_id;
NET_SOCK_ADDR_IPv4 addr_server;
CPU_INT32U len_addr_server;
CPU_INT32U len;
RTOS_ERR err;
/* ---------- INSTALL CA CERTIFICATE --------- */
len = Str_Len(p_cert);
NetSecure_CA_CertIntall(p_cert, len, NET_SOCK_SECURE_CERT_KEY_FMT_PEM, &err);
/* -------------- OPEN THE SOCKET ------------ */
sock_id = NetApp_SockOpen(NET_SOCK_ADDR_FAMILY_IP_V4,
NET_SOCK_TYPE_STREAM,
NET_SOCK_PROTOCOL_TCP,
3,
5,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
break;
default:
return (-1);
}
/* ----------- CFG SOCK SECURE OPT------------ */
(void)NetSock_CfgSecure(sock_id,
DEF_YES,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
break;
default:
NetApp_SockClose(sock_id, 1, &err);
return (-1);
}
(void)NetSock_CfgSecureClientCommonName(sock_id,
"domain_name.com",
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
break;
default:
NetApp_SockClose(sock_id, 1, &err);
return (-1);
}
(void)NetSock_CfgSecureClientTrustCallBack(sock_id,
&AppClientCertTrustCallBackFnct,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
break;
default:
NetApp_SockClose(sock_id, 1, &err);
return (-1);
}
/* --------- ESTABLISH TCP CONNECTION -------- */
Mem_Clr((void *)&addr_server, NET_SOCK_ADDR_SIZE);
addr_server.AddrFamily = NetASCII_Str_to_IP( "98.139.211.125",
(void *)&addr_server.Addr,
sizeof(addr_server.Addr),
&err);
addr_server.Port = NET_UTIL_HOST_TO_NET_16(12345);
len_addr_server = sizeof(addr_server);
(void)NetApp_SockConn( sock_id,
(NET_SOCK_ADDR *)&addr_server,
len_addr_server,
3,
5,
5,
&err);
switch (err.Code) {
case RTOS_ERR_NONE:
break;
default:
NetApp_SockClose(sock_id, 1, &err);
return (-1);
}
return (sock_id);
}
CPU_BOOLEAN AppClientCertTrustCallBackFnct (void *p_cert_dn,
NET_SOCK_SECURE_UNTRUSTED_REASON reason)
{
return (DEF_YES);
}
Multicast Programmimg#
This section describes how to use and control multicast options such:
Configuration#
Some parameters should be configured and/or optimized for your project requirements. See the section Multicast Configuration for further details.
Using Network Interface Wireless Programming API#
Wherever you want to configure or access an Interface option you should include this file:
Include file | Description |
---|---|
rtos/net/include/net_igmp.h | Functions used for IPv4 Multicast API |
rtos/net/include/net_mldp.h | Functions used for IPv6 Multicast API |
API reference#
All Multicast APIs for IPv4 are presented in the section IGMP Functions and in section MLDP Functions for IPv6.
Function Name | Description |
---|---|
NetIGMP_HostGrpJoin() | Join a host group. |
NetIGMP_HostGrpLeave() | Leave a host group. |
NetMLDP_HostGrpJoin() | Join a MLDP Multicast host group. |
NetMLDP_HostGrpLeave() | Leave a MLDP host group. |
Joining and Leaving a Multicast Group#
IPv4 Multicasting#
The Network module supports IPv4 multicasting with IGMP. In order to receive packets addressed to a given IPv4 multicast group address, the stack must have been configured to support multicasting in net_cfg.h, Multicast Configuration , and that host group has to be joined.
The following examples show how to join and leave an IPv4 multicast group:
Listing - Joining and leaving an IPv4 multicast group#
NET_IF_NBR if_nbr;
NET_IP_ADDR group_ip_addr;
RTOS_ERR err;
if_nbr = NET_IF_NBR_BASE_CFGD;
group_ip_addr = NetASCII_Str_to_IP("233.0.0.1", &err);
if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
NetIGMP_HostGrpJoin(if_nbr, group_ip_addr, &err);
if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
[...]
NetIGMP_HostGrpLeave(if_nbr, group_ip_addr, &err);
if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
Refer to the functions NetIGMP_HostGrpJoin() and NetIGMP_HostGrpLeave() for more details. |
---|
IPv6 Multicasting#
The Network module supports IPv6 multicasting with MLDP. In order to receive packets addressed to a given IPv6 multicast group address, With IPv6 MLDP is always enabled but you might need to increase the number of group, see Multicast Configuration .
The following examples show how to join and leave an IPv6 multicast group:
Listing - Joining and leaving an IPv6 multicast group#
NET_IF_NBR if_nbr;
NET_IP_ADDR group_ip_addr;
RTOS_ERR err;
if_nbr = NET_IF_NBR_BASE_CFGD;
group_ip_addr = NetASCII_Str_to_IP("FF03::1", &err);
if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
NetMLDP_HostGrpJoin(if_nbr, group_ip_addr, &err);
if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
[...]
NetMLDP_HostGrpLeave(if_nbr, group_ip_addr, &err);
if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
Refer to the functions NetMLDP_HostGrpJoin() and NetMLDP_HostGrpLeave() for more details. |
---|
Transmitting/Receiving to/from a Multicast IP Group#
Transmitting to a Multicast IP Group Address#
Transmitting to an IP multicast group is identical to transmitting to a unicast or broadcast address. However, when using only IPv4, the stack must be configured to enable multicast transmit, see Multicast Configuration .
Receiving from a Multicast IP Group#
An IP multicast group must be joined before packets can be received from it from it (see Joining and Leaving a Multicast Group for more information). Once this is done, receiving from a multicast group only requires a socket bound to the NET_SOCK_ADDR_IP_WILDCARD address, as shown in the following example:
Listing - Receiving from a multicast group#
NET_SOCK_ID sock;
NET_SOCK_ADDR_IPv4 sock_addr_ip;
NET_SOCK_ADDR addr_remote;
NET_SOCK_ADDR_LEN addr_remote_len;
CPU_CHAR rx_buf[100];
CPU_INT16U rx_len;
RTOS_ERR err;
sock = NetSock_Open( NET_SOCK_ADDR_FAMILY_IP_V4,
NET_SOCK_TYPE_DATAGRAM,
NET_SOCK_PROTOCOL_UDP,
&err);
if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
Mem_Set((void)&sock_addr_ip, 0, sizeof(sock_addr_ip));
sock_addr_ip.AddrFamily = NET_SOCK_ADDR_FAMILY_IP_V4;
sock_addr_ip.Addr = NET_UTIL_HOST_TO_NET_32(NET_SOCK_ADDR_IP_WILDCARD);
sock_addr_ip.Port = NET_UTIL_HOST_TO_NET_16(10000);
NetSock_Bind( sock,
(NET_SOCK_ADDR *)&sock_addr_ip,
NET_SOCK_ADDR_SIZE,
&err);
if (err.Code != RTOS_ERR_NONE) {
/* Handle error. */
}
rx_len = NetSock_RxDataFrom( sock,
&rx_buf [0],
BUF_SIZE,
NET_SOCK_FLAG_NONE,
&addr_remote,
&addr_remote_len,
0,
0,
0,
&err);
Wireless Programming#
This section describe how to use and control Wireless options such:
Using Network Interface Wireless Programming API#
Wherever you want to configure or access an Interface option you should include this file:
Include file | Description |
---|---|
rtos/net/include/net_if_wifi.h | Functions used for Interface Wireless API |
API Reference#
All Interface APIs are presented in the section Wireless Network Interface Functions .
Function Name | Description |
---|---|
NetIF_WiFi_Add() | Add & initialize a specific instance of a network WiFi interface. |
NetIF_WiFi_Start() | Start a WiFi type interface. |
NetIF_WiFi_Scan() | Scan available wireless access point. |
NetIF_WiFi_Join() | Join an wireless access point. |
NetIF_WiFi_Leave() | Leave the access point previously joined. |
NetIF_WiFi_CreateAP() | Creates a wireless access point. |
NetIF_WiFi_GetPeerInfo() | Gets the peer info connected to the access point (when acting as an access point). |
Creating a Wireless Access Point#
To turn your device into a wireless Access Point allowing other wireless devices to connect to it, you need to use the function NetIF_WiFi_CreateAP() .
A call to NetIF_WiFi_CreateAP() is shown below :
Listing - Call to NetIF_WiFi_CreateAdhoc()#
RTOS_ERR err;
ap_ctn = NetIF_WiFi_CreateAP(if_nbr, (1)
NET_IF_WIFI_NET_TYPE_INFRASTRUCTURE (2)
NET_IF_WIFI_DATA_RATE_AUTO, (3)
NET_IF_WIFI_SECURITY_WEP, (4)
NET_IF_WIFI_PWR_LEVEL_HI, (5)
NET_IF_WIFI_CH_1 (6)
"ssid", (7)
"password", (8)
&err); (9)
NetIF_WiFi_CreateAP() requires nine arguments.
The interface number, which is acquired upon successfully adding and starting the interface.
The network type: ad hoc or infrastructure
The data rate used on the wireless network.
The wireless security type of the wireless network.
The radio power level used to communicate on the wireless network.
The wireless channel for the ad hoc network.
A pointer to a string that contains the SSID of the wireless access point.
A pointer to a string that contains the pre-shared key of the wireless access point.
A pointer to a RTOS_ERR that contains the return error code. The return error variable will contain the value RTOS_ERR_NONE if the create process has been completed successfully.
If an error occurs, you should always inspect the return error code and take the appropriate action.
Scanning for a Wireless Access Point#
When a wireless network interface is started, it becomes an active interface that is not yet capable of transmitting and receiving data since no operational link to a network medium is configured. The first (but optional) step to joining a network to have an operational link is the scan operation which consists of finding the wireless networks available in the range of the wireless module.
A wireless network interface should be able to scan any time after the network interface has been successfully started. A successful call to NetIF_WiFi_Scan() returns the wireless networks available to join which can be joined by the wireless network interface. See NetIF_WiFi_Scan() for more information.
You can scan for a wireless network by calling the NetIF_WiFi_Scan() API function with the necessary parameters. A call to NetIF_WiFi_Scan() is shown below.
Listing - Calling NetIF_Start()#
NET_IF_WIFI_AP ap_buf[NB_AP_MAX]
CPU_INT16U ap_ctn;
RTOS_ERR err;
ap_ctn = NetIF_WiFi_Scan(if_nbr, (1)
ap_buf, (2)
NB_AP_MAX, (3)
0, (4)
NET_IF_WIFI_CH_ALL, (5)
&err); (6)
NetIF_WiFi_Scan() requires six arguments. The first function argument is the interface number that the application wants to scan with. The interface number is acquired upon successful addition of the interface and upon the successful start of the interface.
The second argument is a pointer to a wireless access point buffer that contains the wireless network found in the range of the interface.
The third argument is the number of wireless access points that can be contained in the wireless access point buffer.
The fourth argument is a pointer to a string that contains the SSID of any hidden wireless access points to find.
The fifth argument is the wireless channel to scan.
The last argument is a pointer to a RTOS_ERR to contain the return error code. The return error variable will contain the value RTOS_ERR_NONE if the scan process has been completed successfully.
There are very few things that could cause a network interface to not scan properly. The application developer should always inspect the return error code and take the appropriate action if an error occurs. Once the error is resolved, the application may again attempt to call NetIF_WiFi_Scan().
Joining A Wireless Access Point#
When a wireless network interface is started, it becomes an active interface that is not yet capable of transmitting and receiving data, since no operational link to a network medium is configured. Once the interface has found a wireless network, it must join it to get an operational link. A wireless network interface should be able to join any time after the network interface has been successfully started and before having joined any other wireless access points. See NetIF_WiFi_Join() for more information.
The application developer may join a wireless network by calling the NetIF_WiFi_Join() API function with the necessary parameters. A call to NetIF_WiFi_Join() is shown below.
Listing - Calling NetIF_Start()#
RTOS_ERR err;
ap_ctn = NetIF_WiFi_Join(if_nbr, (1)
NET_IF_WIFI_NET_TYPE_INFRASTRUCTURE, (2)
NET_IF_WIFI_DATA_RATE_AUTO, (3)
NET_IF_WIFI_SECURITY_WPA2, (4)
NET_IF_WIFI_PWR_LEVEL_HI, (5)
"network_ssid", (6)
"network_password", (7)
&err); (8)
NetIF_WiFi_Join() requires height arguments. The first function argument is the interface number that the application wants to join with. The interface number is acquired upon successfully adding and starting the interface.
The second argument is the wireless network type.
The third argument is the data rate used to communicate on the wireless network.
The fourth argument is the wireless security configured for the wireless network to join.
The fifth argument is the wireless radio power level used to communicate on the wireless network.
The sixth argument is a pointer to a string that contains the SSID of the wireless access point to join.
The seventh argument is a pointer to a string that contains the pre-shared key of the wireless access point to join.
The last argument is a pointer to a RTOS_ERR that contains the return error code. The return error variable will contain the value RTOS_ERR_NONE if the join process has been completed successfully.
There are very few things that could cause a network interface to not join properly. The application developer should always inspect the return error code and take the appropriate action if an error occurs. Once the error is resolved, the application may again attempt to call NetIF_WiFi_Join().
Leaving Wireless Access Point#
When an application needs to leave a wireless access point, it can do so by calling the NetIF_WiFi_Leave() API function with the necessary parameters.
A call to NetIF_WiFi_Leave() is shown below.
Listing - Call to NetIF_WiFi_Leave()#
RTOS_ERR err;
ap_ctn = NetIF_WiFi_Leave(if_nbr, (1)
&err); (2)
NetIF_WiFi_Leave() requires two arguments. The first function argument is the interface number. The interface number is acquired upon successful addition of the interface and upon the successful start of the interface.
The last argument is a pointer to a RTOS_ERR to contain the return error code. The return error variable will contain the value RTOS_ERR_NONE if the leave process has been completed successfully.
There are very few things that could cause a network interface to leave improperly. You should always inspect the return error code and take the appropriate action if an error occurs. Once the error is resolved, the application may again attempt to call NetIF_WiFi_Leave().
Network Tasks Programming#
Core Task#
The network core task is the main task implementing the TCP/IP core module.
The task is waiting for a signal from the ISR indicating a frame was received. The task will take care of going through each layer from the Link layer to the Transport layer and remove the encapsulation, retrieve the necessary information and take actions to comply with protocol's RFCs.
If data was received that is intended for a socket application, the task will signal that data has been received and is ready to be processed on the socket.
This task, when awoken, will also take care of updating the network timers list and call the callback functions of those that have timed out.
Core Services Task#
A separate task exists for some of the core network services supported by the Network module, like DHCP and DNS client.
Custom Network Application Task#
A customer could develop his own socket application task to transmit and receive data. Since the transmission operations will be handled by this task, it will require a certain amount of stack to create the packet and do the encapsulation for all the layers from the Transport to the Link layer.
Native Network Applications#
Micrium OS Network module offers many network applications. Some applications may have their own task while others will be executed in the context of the task calling them. See the specific documentation of each network application for more details.