emNet SimpleServer (Sample)

From SEGGER Knowledge Base
Jump to navigation Jump to search
IP_SimpleServer_Sample.c
Requires modifications No
Download IP_SimpleServer_Sample.c

This Sample demonstrates a simple Telnet server on a port (by default: 23) that outputs the current system time for each character received.

To connect to the Target, use the command line:

    > telnet <target-ip>

Where <target-ip> represents the IP address of the target. Which depends on the configuration used in IP_X_Config().

Code

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

Purpose : Sample program for embOS & emNet
          Demonstrates a simple Telnet server on a port
          (by default: 23) that outputs
          the current system time for each character received.
          To connect to the target, use the command line:
          > telnet <target-ip>
          Where <target-ip> represents the IP address of the target,
          which depends on the configuration used in IP_X_Config() .
          The recv() function waits for a given timeout for further
          characters to receive before it disconnects the client due
          to idle timeout.

          The following is a sample of the output to the terminal window:

            MainTask - INIT: Init started.
            MainTask - DRIVER: Found PHY with Id 0x2000 at addr 0x1
            MainTask - INIT: Link is down
            MainTask - INIT: Init completed
            IP_Task - INIT: IP_Task started
            IP_Task - LINK: Link state changed: Full duplex, 100MHz
            IP_Task - NDP: Link-local IPv6 addr.: FE80:0000:0000:0000:0222:C7FF:FEFF:FF88
            IP_Task - DHCPc: Sending discover!
            IP_Task - DHCPc: IFace 0: Offer: IP: 192.168.11.231, Mask: 255.255.0.0, GW: 192.168.13.1.
            IP_Task - DHCPc: IP addr. checked, no conflicts
            IP_Task - DHCPc: Sending Request.
            IP_Task - DHCPc: IFace 0: Using IP: 192.168.11.231, Mask: 255.255.0.0, GW: 192.168.13.1.
            Telnet - New IPv4 client accepted.
            Telnet - recv() timeout after 5 seconds of inactivity!
            Telnet - Disconnecting client.
            Telnet - New IPv6 client accepted.
            Telnet - recv() timeout after 5 seconds of inactivity!
            Telnet - Disconnecting client.


Notes:
  (1) The following requirements need to be met when including this
      sample into another C-module:

        - BSP_Init() for LED routines and IP_Init() for emNet are
          called before starting APP_MainTask() .

        - The optional IP_Connect() is called and a link state hook
          is installed if necessary for the emNet interface used
          before starting APP_MainTask() .

  (2) The following application defines can be overwritten when
      including this sample into another C-module:

        - APP_MAIN_STACK_SIZE
          Stack size in bytes to use for APP_MainTask() .

        - APP_TASK_STACK_OVERHEAD
          Additional task stack size [multiples of sizeof(int)] if some
          underlying processes require more task stack than default.

        - APP_ENABLE_IPV4_PLAIN
          Provide a plain/unsecured IPv4 Telnet like server.

        - APP_ENABLE_IPV6_PLAIN
          Provide a plain/unsecured IPv6 Telnet like server.

  (3) The following symbols can be used and renamed via preprocessor
      if necessary when including this sample into another C-module:

        - MainTask
          Main starting point of the sample when used as a
          standalone sample. Can be renamed to anything else to
          avoid multiple MainTask symbols and to skip common
          initializing steps done in every sample.

        - APP_MainTask
          Functional starting point of the sample itself. Typically
          called by the MainTask in this sample. Should be renamed
          via preprocessor to a more application specific name to
          avoid having multiple APP_MainTask symbols in linker map
          files for a better overview when including multiple samples
          this way.

        - APP_MainTCB
          Task-Control-Block used for the APP_MainTask when started
          as a task from the MainTask in this sample. Can/should be
          renamed via preprocessor to a more application specific name.

        - APP_MainStack
          Task stack used for the APP_MainTask when started as a task
          from the MainTask in this sample. Can/should be renamed via
          preprocessor to a more application specific name.
*/

#include "RTOS.h"
#include "BSP.h"
#include "IP.h"
#include "TaskPrio.h"

/*********************************************************************
*
*       Defines, configurable
*
**********************************************************************
*/

#ifndef   USE_RX_TASK
  #define USE_RX_TASK  0  // 0: Packets are read in ISR, 1: Packets are read in a task of its own.
#endif

//
// Sample features enable/disable
//
#ifndef   IP_SUPPORT_IPV4
  #define IP_SUPPORT_IPV4            (1)                // Set a default if not available through IP_ConfDefaults.h or project settings.
#endif
#ifndef   IP_SUPPORT_IPV6
  #define IP_SUPPORT_IPV6            (0)                // Set a default if not available through IP_ConfDefaults.h or project settings.
#endif

#ifndef   APP_ENABLE_IPV4_PLAIN
  #define APP_ENABLE_IPV4_PLAIN      (IP_SUPPORT_IPV4)  // Enables/disables the creation and usage of IPv4 sockets in this sample.
#endif
#ifndef   APP_ENABLE_IPV6_PLAIN
  #define APP_ENABLE_IPV6_PLAIN      (IP_SUPPORT_IPV6)  // Enables/disables the creation and usage of IPv6 sockets in this sample.
#endif

//
// Telnet server sample.
//
#define TIMEOUT      5000  // Timeout for recv() [ms].
#define SERVER_PORT  23

//
// Task stack sizes that might not fit for some interfaces (multiples of sizeof(int)).
//
#ifndef   APP_MAIN_STACK_SIZE
  #define APP_MAIN_STACK_SIZE      (1024)
#endif
#ifndef   APP_TASK_STACK_OVERHEAD
  #define APP_TASK_STACK_OVERHEAD  (0)
#endif
#ifndef   STACK_SIZE_SERVER
  #define STACK_SIZE_SERVER        (APP_MAIN_STACK_SIZE + APP_TASK_STACK_OVERHEAD)
#endif

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/

#ifdef __cplusplus
extern "C" {     /* Make sure we have C-declarations in C++ programs */
#endif
void MainTask(void);
#ifdef __cplusplus
}
#endif

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

static IP_HOOK_ON_STATE_CHANGE _StateChangeHook;
static char                    _acOut[512];

//
// Task stacks and Task-Control-Blocks.
//
static OS_STACKPTR int APP_MainStack[STACK_SIZE_SERVER / sizeof(int)];      // Stack of the starting point of this sample.
static OS_TASK         APP_MainTCB;                                         // Task-Control-Block of the IP_Task.

static OS_STACKPTR int _IPStack[TASK_STACK_SIZE_IP_TASK/sizeof(int)];       // Stack of the IP_Task.
static OS_TASK         _IPTCB;                                              // Task-Control-Block of the IP_Task.

#if USE_RX_TASK
static OS_STACKPTR int _IPRxStack[TASK_STACK_SIZE_IP_RX_TASK/sizeof(int)];  // Stack of the IP_RxTask.
static OS_TASK         _IPRxTCB;                                            // Task-Control-Block of the IP_RxTask.
#endif

/*********************************************************************
*
*       Local functions
*
**********************************************************************
*/

/*********************************************************************
*
*       _OnStateChange()
*
* Function description
*   Callback that will be notified once the state of an interface
*   changes.
*
* Parameters
*   IFaceId   : Zero-based interface index.
*   AdminState: Is this interface enabled ?
*   HWState   : Is this interface physically ready ?
*/
static void _OnStateChange(unsigned IFaceId, U8 AdminState, U8 HWState) {
  //
  // Check if this is a disconnect from the peer or a link down.
  // In this case call IP_Disconnect() to get into a known state.
  //
  if (((AdminState == IP_ADMIN_STATE_DOWN) && (HWState == 1)) ||  // Typical for dial-up connection e.g. PPP when closed from peer. Link up but app. closed.
      ((AdminState == IP_ADMIN_STATE_UP)   && (HWState == 0))) {  // Typical for any Ethernet connection e.g. PPPoE. App. opened but link down.
    IP_Disconnect(IFaceId);                                       // Disconnect the interface to a clean state.
  }
}

/*********************************************************************
*
*       _CreateSocket()
*
* Function description
*   Creates a socket for the requested protocol family.
*
* Parameter
*   IPProtVer - Protocol family which should be used (PF_INET or PF_INET6).
*
* Return value
*   O.K. : Socket handle.
*   Error: SOCKET_ERROR .
*/
static int _CreateSocket(unsigned IPProtVer) {
  int hSock;

  (void)IPProtVer;

  hSock = SOCKET_ERROR;

#if (APP_ENABLE_IPV4_PLAIN != 0)
  //
  // Create IPv4 socket
  //
  if (IPProtVer == AF_INET) {
    hSock = socket(AF_INET, SOCK_STREAM, 0);
  }
#endif
#if (APP_ENABLE_IPV6_PLAIN != 0)
  //
  // Create IPv6 socket
  //
  if (IPProtVer == PF_INET6) {
    hSock = socket(AF_INET6, SOCK_STREAM, 0);
  }
#endif
  return hSock;
}

/*********************************************************************
*
*       _BindAtTcpPort()
*
* Function description
*   Binds a socket to a port.
*
* Parameter
*   IPProtVer - Protocol family which should be used (PF_INET or PF_INET6).
*   hSock     - Socket handle
*   Port      - Port which should be to wait for connections.
*
* Return value
*   O.K. : == 0
*   Error: != 0
*/
static int _BindAtTcpPort(unsigned IPProtVer, int hSock, U16 LPort) {
  int r;

  (void)IPProtVer;
  (void)hSock;
  (void)LPort;

  r = -1;

  //
  // Bind it to the port
  //
#if (APP_ENABLE_IPV4_PLAIN != 0)
  if (IPProtVer == AF_INET) {
    struct sockaddr_in Addr;

    IP_MEMSET(&Addr, 0, sizeof(Addr));
    Addr.sin_family      = AF_INET;
    Addr.sin_port        = htons(LPort);
    Addr.sin_addr.s_addr = INADDR_ANY;
    r = bind(hSock, (struct sockaddr*)&Addr, sizeof(Addr));
  }
#endif
#if (APP_ENABLE_IPV6_PLAIN != 0)
  if (IPProtVer == PF_INET6) {
    struct sockaddr_in6 Addr;

    IP_MEMSET(&Addr, 0, sizeof(Addr));
    Addr.sin6_family      = AF_INET6;
    Addr.sin6_port        = htons(LPort);
    Addr.sin6_flowinfo   = 0;
    IP_MEMSET(&Addr.sin6_addr, 0, 16);
    Addr.sin6_scope_id   = 0;
    r = bind(hSock, (struct sockaddr*)&Addr, sizeof(Addr));
  }
#endif
  return r;
}

/*********************************************************************
*
*       _ListenAtTcpPort()
*
* Function description
*   Creates a socket, binds it to a port and sets the socket into
*   listening state.
*
* Parameter
*   IPProtVer - Protocol family which should be used (PF_INET or PF_INET6).
*   Port      - Port which should be to wait for connections.
*
* Return value
*   O.K. : Socket handle.
*   Error: SOCKET_ERROR .
*/
static int _ListenAtTcpPort(unsigned IPProtVer, U16 Port) {
  int hSock;
  int r;

  //
  // Create socket
  //
  hSock = _CreateSocket(IPProtVer);
  if (hSock != SOCKET_ERROR) {
    //
    // Bind it to the port
    //
    r = _BindAtTcpPort(IPProtVer, hSock, Port);
    //
    // Start listening on the socket.
    //
    if (r != 0) {
      hSock = SOCKET_ERROR;
    } else {
      r = listen(hSock, 1);
      if (r != 0) {
        hSock = SOCKET_ERROR;
      }
    }
  }
  return hSock;
}

/*********************************************************************
*
*       _Process()
*
* Function description
*   This is the actual (very simple) Telnet server. This function
*   processes one client connection.
*
* Parameters
*   hSock: Client socket to serve.
*/
static void _Process(int hSock) {
  const char *sHello = "Hello ... Press any key.\n\r";
        U32   Timeout;
        int   Error;
        int   r;
        char  Dummy;  // Used to receive one character, key press.


  r = send(hSock, (char*)sHello, IP_STRLEN(sHello), 0);
  if (r == SOCKET_ERROR) {
    IP_Logf_Application("send() failed with socket error.");
    return;
  }
  //
  // recv() by default is a blocking function. this means it blocks
  // until it receives data. We set a timeout to guarantee that the
  // function returns after the given delay.
  //
  Timeout = TIMEOUT;
  setsockopt(hSock, SOL_SOCKET, SO_RCVTIMEO, &Timeout, sizeof(Timeout));
  do {
    r = recv(hSock, &Dummy, 1, 0);
    if (r <= 0) {
      getsockopt(hSock, SOL_SOCKET, SO_ERROR, &Error, sizeof(Error));
      if (Error == IP_ERR_TIMEDOUT) {
        IP_Logf_Application("recv() timeout after %lu seconds of inactivity!", (TIMEOUT / 1000));
      } else {
        IP_Logf_Application("recv() failed with error: %s", IP_Err2Str(Error));
      }
      IP_Logf_Application("Disconnecting client.");
      return;  // Error, receive timeout => disconnect client.
    }
    SEGGER_snprintf(_acOut, sizeof(_acOut), "OS_Time = %lu\n\r", OS_Time);
    r = send(hSock, (char*)_acOut, IP_STRLEN(_acOut), 0);
    if (r == SOCKET_ERROR) {
      IP_Logf_Application("send() failed with socket error.");
      return;
    }
  } while (1);
}

/*********************************************************************
*
*       _TelnetTask()
*
* Function description
*   Creates a parent socket and handles clients that connect to the
*   server. This sample can handle one client at the same time. Each
*   client that connects creates a child socket that is then passed
*   to the process routine to detach client handling from the server
*   functionality. This could also be used for a multi-task setup.
*/
static void _TelnetTask(void) {
  IP_fd_set ReadFds;
#if APP_ENABLE_IPV4_PLAIN
  int       hSockParent4_Plain;
#endif
#if APP_ENABLE_IPV6_PLAIN
  int       hSockParent6_Plain;
#endif
  int       hSock;
  int       r;

  hSock = 0;  // Avoid uninitialized warning.
  //
  // Try until we get valid parent sockets.
  //
#if APP_ENABLE_IPV4_PLAIN
  while (1) {
    hSockParent4_Plain = _ListenAtTcpPort(PF_INET, SERVER_PORT);
    if (hSockParent4_Plain == SOCKET_ERROR) {
      OS_Delay(2000);
      continue;  // Error, try again.
    }
    break;
  }
#endif
#if APP_ENABLE_IPV6_PLAIN
  while (1) {
    hSockParent6_Plain = _ListenAtTcpPort(PF_INET6, SERVER_PORT);
    if (hSockParent6_Plain == SOCKET_ERROR) {
      OS_Delay(2000);
      continue;  // Error, try again.
    }
    break;
  }
#endif
  //
  // Wait for a connection and process the data request after accepting the connection.
  //
  for (;;) {
    IP_FD_ZERO(&ReadFds);                     // Clear the set
#if APP_ENABLE_IPV4_PLAIN
    IP_FD_SET(hSockParent4_Plain, &ReadFds);  // Add IPv4 plain socket to the set
#endif
#if APP_ENABLE_IPV6_PLAIN
    IP_FD_SET(hSockParent6_Plain, &ReadFds);  // Add IPv6 plain socket to the set
#endif
    r = select(&ReadFds, NULL, NULL, 5000);   // Check for activity. Wait 5 seconds
    if (r <= 0) {
      continue;
    }
#if APP_ENABLE_IPV4_PLAIN
    //
    // Check if the IPv4 plain socket is ready
    //
    if (IP_FD_ISSET(hSockParent4_Plain, &ReadFds)) {
      hSock = accept(hSockParent4_Plain, NULL, NULL);
      if (hSock == SOCKET_ERROR) {
        continue;               // Error, try again.
      }
      IP_Logf_Application("New IPv4 client accepted.");
      goto HandleConnection;
    }
#endif
#if APP_ENABLE_IPV6_PLAIN
    //
    // Check if the IPv6 plain socket is ready
    //
    if (IP_FD_ISSET(hSockParent6_Plain, &ReadFds)) {
      hSock = accept(hSockParent6_Plain, NULL, NULL);
      if (hSock == SOCKET_ERROR) {
        continue;               // Error, try again.
      }
      IP_Logf_Application("New IPv6 client accepted.");
      goto HandleConnection;
    }
#endif
HandleConnection:
    _Process(hSock);     // Process the client.
    closesocket(hSock);  // Close connection to client from our side (too).
  }
}

/*********************************************************************
*
*       APP_MainTask()
*
*  Function description
*    Sample starting point.
*/
static void APP_MainTask(void) {
  OS_SetTaskName(OS_GetTaskID(), "Telnet server");
  _TelnetTask();
}

/*********************************************************************
*
*       Global functions
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask()
*
*  Function description
*    Main sample starting point when running one individual sample.
*
*  Additional information
*    Runs the sample in a task of its own to decouple it from not
*    knowing the required task stack size of this sample when
*    starting from main() . This task typically is terminated once
*    it has fulfilled its purpose.
*
*    This routine initializes everything that might be common with
*    other samples of the same kind. These initializations can then
*    be skipped by not starting from MainTask() but APP_MainTask()
*    instead.
*/
void MainTask(void) {
  int IFaceId;

  //
  // Initialize the IP stack
  //
  IP_Init();
  //
  // Start TCP/IP task
  //
  OS_CREATETASK(&_IPTCB,   "IP_Task",   IP_Task,   APP_TASK_PRIO_IP_TASK  , _IPStack);
#if USE_RX_TASK
  OS_CREATETASK(&_IPRxTCB, "IP_RxTask", IP_RxTask, APP_TASK_PRIO_IP_RXTASK, _IPRxStack);  // Start the IP_RxTask, optional.
#endif
  IP_AddStateChangeHook(&_StateChangeHook, _OnStateChange);                               // Register hook to be notified on disconnects.
  IFaceId = IP_INFO_GetNumInterfaces() - 1;                                               // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
  IP_Connect(IFaceId);                                                                    // Connect the interface if necessary.
  //
  // IPv4 address configured ?
  //
  while (IP_IFaceIsReadyEx(IFaceId) == 0) {
    BSP_ToggleLED(0);
    OS_Delay(200);
  }
  BSP_ClrLED(0);
  //
  // Start the sample itself.
  //
  OS_CREATETASK(&APP_MainTCB, "APP_MainTask", APP_MainTask, APP_TASK_PRIO_WEBSERVER_PARENT, APP_MainStack);
  OS_TASK_Terminate(NULL);
}

/*************************** End of file ****************************/