From 895973531b600b0df504cd2a9c13def95020feee Mon Sep 17 00:00:00 2001
From: David Girault <dgirault@sevenhugs.com>
Date: Fri, 6 Jul 2018 11:40:57 +0200
Subject: [PATCH 1/2] lwiperf: added support for UDP client/server mode

- support fo dualtest/tradeoff option in UDP client
- fix bad assert and abort not closing sockets
- allocate buffer from POOL for UDP client

  This improve performance by removing malloc/free calls and by using
  SRAM instead of SDRAM which had lot of latency.

- support automatic join/leave if UDP server bount to multicast IP
- ensure tcp_pcb isn't freed two times and fix report callback call
---
 src/apps/lwiperf/lwiperf.c      | 745 +++++++++++++++++++++++++++++++-
 src/include/lwip/apps/lwiperf.h |  22 +-
 2 files changed, 749 insertions(+), 18 deletions(-)

diff --git a/src/apps/lwiperf/lwiperf.c b/src/apps/lwiperf/lwiperf.c
index dbec4dae..3f7dc3d5 100644
--- a/src/apps/lwiperf/lwiperf.c
+++ b/src/apps/lwiperf/lwiperf.c
@@ -1,21 +1,20 @@
 /**
  * @file
- * lwIP iPerf server implementation
+ * lwIP iPerf client/server implementation
  */
 
 /**
- * @defgroup iperf Iperf server
+ * @defgroup iperf Iperf client/server
  * @ingroup apps
  *
- * This is a simple performance measuring client/server to check your bandwith using
- * iPerf2 on a PC as server/client.
- * It is currently a minimal implementation providing a TCP client/server only.
+ * This is a simple performance measuring client/server to check your bandwidth using
+ * iPerf2 on a PC as client/server.
  *
  * @todo:
- * - implement UDP mode
  * - protect combined sessions handling (via 'related_master_state') against reallocation
  *   (this is a pointer address, currently, so if the same memory is allocated again,
  *    session pairs (tx/rx) can be confused on reallocation)
+ *
  */
 
 /*
@@ -51,20 +50,24 @@
 
 #include "lwip/apps/lwiperf.h"
 
+#include "lwip/udp.h"
 #include "lwip/tcp.h"
 #include "lwip/sys.h"
+#include "lwip/timeouts.h"
+#include "lwip/igmp.h"
+#include "lwip/mld6.h"
 
 #include <string.h>
 
-/* Currently, only TCP is implemented */
-#if LWIP_TCP && LWIP_CALLBACK_API
+/* Only use callback API for TCP & UDP */
+#if LWIP_CALLBACK_API && LWIP_TCP && LWIP_UDP
 
 /** Specify the idle timeout (in seconds) after that the test fails */
-#ifndef LWIPERF_TCP_MAX_IDLE_SEC
-#define LWIPERF_TCP_MAX_IDLE_SEC    10U
+#ifndef LWIPERF_MAX_IDLE_SEC
+#define LWIPERF_MAX_IDLE_SEC    10U
 #endif
-#if LWIPERF_TCP_MAX_IDLE_SEC > 255
-#error LWIPERF_TCP_MAX_IDLE_SEC must fit into an u8_t
+#if LWIPERF_MAX_IDLE_SEC > 255
+#error LWIPERF_MAX_IDLE_SEC must fit into an u8_t
 #endif
 
 /** Change this if you don't want to lwiperf to listen to any IP version */
@@ -87,6 +90,7 @@
 /** This is the Iperf settings struct sent from the client */
 typedef struct _lwiperf_settings {
 #define LWIPERF_FLAGS_ANSWER_TEST 0x80000000
+#define LWIPERF_FLAGS_EXTEND      0x40000000
 #define LWIPERF_FLAGS_ANSWER_NOW  0x00000001
   u32_t flags;
   u32_t num_threads; /* unused for now */
@@ -96,6 +100,41 @@ typedef struct _lwiperf_settings {
   u32_t amount; /* pos. value: bytes?; neg. values: time (unit is 10ms: 1/100 second) */
 } lwiperf_settings_t;
 
+typedef struct _lwiperf_settings_ext {
+#define LWIPERF_EFLAGS_UNITS_PPS 0x00000001
+  lwiperf_settings_t base;
+  s32_t type;
+  s32_t len; /* length from flags to real_time */
+  s32_t eflags;
+  s32_t version_u;
+  s32_t version_l;
+  s32_t reserved;
+  s32_t rate;
+  s32_t UDP_rate_units;
+  s32_t real_time;
+} lwiperf_settings_ext_t;
+
+/** This is the header structure for all UDP datagram */
+struct UDP_datagram {
+  int32_t id;
+  uint32_t tv_sec;
+  uint32_t tv_usec;
+};
+
+/** This is the Iperf reporting struct sent to the client */
+typedef struct _lwiperf_udp_report {
+  int32_t flags; /* LWIPERF_FLAGS_* */
+  int32_t total_len1;
+  int32_t total_len2;
+  int32_t stop_sec;
+  int32_t stop_usec;
+  int32_t error_cnt;
+  int32_t outorder_cnt;
+  int32_t datagrams;
+  int32_t jitter1;
+  int32_t jitter2;
+} lwiperf_udp_report_t;
+
 /** Basic connection handle */
 struct _lwiperf_state_base;
 typedef struct _lwiperf_state_base lwiperf_state_base_t;
@@ -110,6 +149,31 @@ struct _lwiperf_state_base {
   lwiperf_state_base_t *related_master_state;
 };
 
+/** Connection handle for a UDP iperf session */
+typedef struct _lwiperf_state_udp {
+  lwiperf_state_base_t base;
+  struct udp_pcb *pcb;
+  struct pbuf *reported;
+  ip_addr_t remote_addr;
+  u16_t remote_port;
+  u8_t report_count;
+  u8_t have_settings_buf;
+  lwiperf_settings_ext_t settings;
+  u32_t delay_target;
+  u32_t time_started;
+  u32_t bytes_transferred;
+  lwiperf_report_fn report_fn;
+  void *report_arg;
+  struct timespec udp_lastpkt;
+  u32_t udp_seq;
+  u32_t udp_rx_lost;
+  u32_t udp_rx_outorder;
+  u32_t udp_rx_total_pkt;
+  u32_t udp_rx_total_size;
+  u32_t udp_last_transit;
+  long jitter;
+} lwiperf_state_udp_t;
+
 /** Connection handle for a TCP iperf session */
 typedef struct _lwiperf_state_tcp {
   lwiperf_state_base_t base;
@@ -131,6 +195,7 @@ typedef struct _lwiperf_state_tcp {
 
 /** List of active iperf sessions */
 static lwiperf_state_base_t *lwiperf_all_connections;
+
 /** A const buffer to send from: we want to measure sending, not copying! */
 static const u8_t lwiperf_txbuf_const[1600] = {
   '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
@@ -179,8 +244,23 @@ static err_t lwiperf_tcp_poll(void *arg, struct tcp_pcb *tpcb);
 static void lwiperf_tcp_err(void *arg, err_t err);
 static err_t lwiperf_start_tcp_server_impl(const ip_addr_t *local_addr, u16_t local_port,
                                            lwiperf_report_fn report_fn, void *report_arg,
-                                           lwiperf_state_base_t *related_master_state, lwiperf_state_tcp_t **state);
-
+                                           lwiperf_state_base_t *related_master_state,
+                                           lwiperf_state_tcp_t **state);
+static void lwiperf_udp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p,
+                             const ip_addr_t *addr, u16_t port);
+static err_t lwiperf_udp_tx_start(lwiperf_state_udp_t *conn);
+
+static inline void
+diff_ts(const struct timespec *start, const struct timespec *stop, struct timespec *result)
+{
+  if ((stop->tv_nsec - start->tv_nsec) < 0) {
+    result->tv_sec = stop->tv_sec - start->tv_sec - 1;
+    result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
+  } else {
+    result->tv_sec = stop->tv_sec - start->tv_sec;
+    result->tv_nsec = stop->tv_nsec - start->tv_nsec;
+  }
+}
 
 /** Add an iperf session to the 'active' list */
 static void
@@ -812,9 +892,636 @@ void* lwiperf_start_tcp_client(const ip_addr_t* remote_addr, u16_t remote_port,
   return NULL;
 }
 
+/** This is called when a new client connects for an iperf udp session */
+static lwiperf_state_udp_t *
+lwiperf_udp_new_client(lwiperf_state_udp_t *s)
+{
+  lwiperf_state_udp_t *conn;
+  if (s == NULL)
+    return NULL;
+  conn = (lwiperf_state_udp_t *)LWIPERF_ALLOC(lwiperf_state_udp_t);
+  if (conn == NULL)
+    return NULL;
+  memset(conn, 0, sizeof(lwiperf_state_udp_t));
+  conn->base.tcp = 0;
+  conn->base.server = 1;
+  conn->base.related_master_state = &s->base;
+  conn->pcb = NULL;
+  conn->time_started = sys_now();
+  conn->report_fn = s->report_fn;
+  conn->report_arg = s->report_arg;
+  lwiperf_list_add(&conn->base);
+  return conn;
+}
+
+/** This is called when a new client connects for an iperf udp session */
+static lwiperf_state_udp_t *
+lwiperf_udp_search_client(lwiperf_state_udp_t *s,
+                          const ip_addr_t *addr, u16_t port)
+{
+  lwiperf_state_udp_t *iter;
+  for (iter = (lwiperf_state_udp_t *)lwiperf_all_connections;
+       iter != NULL;
+       iter = (lwiperf_state_udp_t *)iter->base.next) {
+    if (iter->base.tcp || !iter->base.related_master_state)
+      continue;
+    if (iter->base.related_master_state != (lwiperf_state_base_t *)s)
+      continue;
+    if (ip_addr_cmp(addr, &iter->remote_addr) && (port == iter->remote_port))
+      break;
+  }
+  return iter;
+}
+
+
+/** Call the report function of an iperf tcp session */
+static void
+lwip_udp_conn_report(lwiperf_state_udp_t *conn, enum lwiperf_report_type report_type)
+{
+  if ((conn != NULL) && (conn->report_fn != NULL)) {
+    u32_t now, duration_ms, bandwidth_kbitpsec;
+    now = sys_now();
+    duration_ms = now - conn->time_started;
+    if (duration_ms == 0) {
+      bandwidth_kbitpsec = 0;
+    } else {
+      bandwidth_kbitpsec = (u32_t)((8ull * conn->bytes_transferred) / duration_ms);
+    }
+    conn->report_fn(conn->report_arg, report_type,
+                    &conn->pcb->local_ip, conn->pcb->local_port,
+                    &conn->remote_addr, conn->remote_port,
+                    conn->bytes_transferred, duration_ms, bandwidth_kbitpsec);
+  }
+}
+
+/** Close an iperf udp session */
+static void
+lwiperf_udp_close(lwiperf_state_udp_t *conn, enum lwiperf_report_type report_type)
+{
+  lwip_udp_conn_report(conn, report_type);
+  lwiperf_list_remove(&conn->base);
+  if (conn->pcb != NULL) {
+    ip_addr_t *local_addr = &conn->pcb->local_ip;
+    if (ip_addr_ismulticast(local_addr)) {
+      if (IP_IS_V6 (local_addr)) {
+        mld6_leavegroup (IP6_ADDR_ANY6, ip_2_ip6 (local_addr));
+      }
+      else {
+        igmp_leavegroup (IP4_ADDR_ANY4, ip_2_ip4 (local_addr));
+      }
+    }
+    udp_remove(conn->pcb);
+    conn->pcb = NULL;
+  }
+  LWIPERF_FREE(lwiperf_state_udp_t, conn);
+}
+
+/** Receive data on an iperf udp session */
+static void
+lwiperf_udp_send_report(lwiperf_state_udp_t *conn)
+{
+    lwiperf_state_udp_t *s = (lwiperf_state_udp_t *)conn->base.related_master_state;
+    struct pbuf *q = conn->reported;
+    LWIP_ASSERT("no report buffer!", q != NULL);
+    udp_sendto(s->pcb, q, &conn->remote_addr, conn->remote_port);
+    conn->report_count++;
+    if  (conn->report_count < 2) {
+      /* Send twice after a little delay! */
+      sys_timeout(10, (sys_timeout_handler)lwiperf_udp_send_report, conn);
+    } else {
+      pbuf_free(q);
+      conn->reported = NULL;
+      /* reported twice -> test done */
+      if (conn->settings.base.flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST)) {
+        if ((conn->settings.base.flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_NOW)) == 0) {
+          /* client requested transmission after end of test */
+          LWIP_PLATFORM_DIAG(("client requested transmission after end of test\n"));
+          lwiperf_udp_tx_start(conn);
+        }
+      }
+      lwiperf_udp_close(conn, LWIPERF_UDP_DONE_SERVER);
+      if (s->base.server & 0x80) {
+        /* this is a temporary server for tradeoff or dualtest, ensure no report for this temporary server */
+        s->report_fn = NULL;
+        lwiperf_udp_close(s, LWIPERF_UDP_DONE_SERVER);
+      }
+    }
+}
+
+/** Try to send more data on an iperf udp session */
+/* Must be called in main loop */
+static void
+lwiperf_udp_client_send_more(lwiperf_state_udp_t *conn)
+{
+  struct pbuf *p;
+  struct timespec ts, dt;
+  err_t err;
+  int ending = 0;
+
+  LWIP_ASSERT("conn invalid", (conn != NULL) && !conn->base.tcp && (conn->base.server == 0));
+
+  do {
+    if (conn->settings.base.amount & PP_HTONL(0x80000000)) {
+      /* this session is time-limited */
+      u32_t now = sys_now();
+      u32_t diff_ms = now - conn->time_started;
+      u32_t time = (u32_t) - (s32_t)lwip_ntohl(conn->settings.base.amount);
+      u32_t time_ms = time * 10;
+      if (diff_ms >= time_ms) {
+        ending = 1;
+        if (diff_ms > (time_ms + 500))
+          ending ++;
+      }
+    } else {
+      /* this session is byte-limited */
+      u32_t amount_bytes = lwip_ntohl(conn->settings.base.amount);
+      if (conn->bytes_transferred >= amount_bytes) {
+        ending = 1;
+        if (conn->bytes_transferred >= (amount_bytes + 4096))
+          ending++;
+      }
+    }
+    if (ending && (ending > 1 || conn->report_count > 0)) {
+      lwiperf_udp_close(conn, LWIPERF_UDP_DONE_CLIENT);
+      break;
+    }
+    /* check time/bw */
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    diff_ts(&conn->udp_lastpkt, &ts, &dt);
+    if ((uint32_t)((dt.tv_sec * 1000000) + (dt.tv_nsec / 1000)) < (conn->delay_target-5))
+      break;
+    /* make pbuf to transmit */
+    p = pbuf_alloc(PBUF_TRANSPORT, lwip_ntohl(conn->settings.base.buffer_len), PBUF_POOL);
+    if (p) {
+      struct UDP_datagram *pkt = (struct UDP_datagram *)p->payload;
+      int hsz = (conn->settings.base.flags & PP_HTONL(LWIPERF_FLAGS_EXTEND) ?
+                 sizeof(conn->settings) : sizeof(conn->settings.base));
+      int offset = sizeof(struct UDP_datagram);
+      /* set UDP datagram header */
+      if (ending)
+        pkt->id = htonl(-conn->udp_seq);
+      else
+        pkt->id = htonl(conn->udp_seq);
+      pkt->tv_sec  = htonl(ts.tv_sec);
+      pkt->tv_usec = htonl(ts.tv_nsec / 1000);
+      /* save last packet time */
+      conn->udp_lastpkt = ts;
+      /* add settings in all buffer sent */
+      pbuf_take_at(p, &conn->settings, hsz, offset);
+      offset += hsz;
+      /* add data */
+      pbuf_take_at(p, &lwiperf_txbuf_const[0], p->tot_len - offset, offset);
+      /* send it */
+      err = udp_send(conn->pcb, p);
+      if (err == ERR_OK) {
+        conn->udp_seq++;
+        conn->bytes_transferred += p->tot_len;
+      } else {
+        lwiperf_udp_close(conn, LWIPERF_UDP_ABORTED_LOCAL_TXERROR);
+      }
+      /* release pbuf */
+      pbuf_free(p);
+      /* adjust delay for ending retries */
+      if (ending) {
+        conn->delay_target = 250000; /* ending retry delay : 250ms */
+      }
+      break;
+    } else {
+      lwiperf_udp_close(conn, LWIPERF_UDP_ABORTED_LOCAL);
+      break;
+    }
+  }
+  while (1);
+}
+
+/** Create a new UDP connection back to the client.
+ */
+static lwiperf_state_udp_t *
+lwiperf_udp_tx_new(lwiperf_state_udp_t *conn)
+{
+  lwiperf_state_udp_t *client_conn;
+  struct udp_pcb *newpcb;
+
+  client_conn = (lwiperf_state_udp_t *)LWIPERF_ALLOC(lwiperf_state_udp_t);
+  if (client_conn == NULL) {
+    return NULL;
+  }
+  memset(client_conn, 0, sizeof(lwiperf_state_udp_t));
+  newpcb = udp_new();
+  if (newpcb == NULL) {
+    LWIPERF_FREE(lwiperf_state_udp_t, client_conn);
+    return NULL;
+  }
+  if (conn) {
+    MEMCPY(client_conn, conn, sizeof(lwiperf_state_udp_t));
+  }
+  client_conn->base.tcp = 0;
+  client_conn->base.server = 0;
+  client_conn->pcb = newpcb;
+  client_conn->time_started = sys_now();
+  client_conn->bytes_transferred = 0;
+  client_conn->settings.base.flags = 0; /* prevent the remote side starting back as client again */
+
+  udp_recv(newpcb, lwiperf_udp_recv, client_conn);
+  return client_conn;
+}
+
+/** Start UDP connection back to the client (either parallel or after the
+ * receive test has finished).
+ */
+static err_t
+lwiperf_udp_tx_start(lwiperf_state_udp_t *conn)
+{
+  lwiperf_state_udp_t *cc;
+  u32_t buf_len;
+  err_t err;
+
+  cc = lwiperf_udp_tx_new(conn);
+  if (cc == NULL) {
+    return ERR_MEM;
+  }
+  /* take remote port to use from received settings */
+  cc->remote_port = (u16_t)lwip_htonl(cc->settings.base.remote_port);
+  /* buffer_len depend on address type */
+  buf_len = (u32_t)(IP_IS_V6(&cc->remote_addr) ? 1450 : 1470);
+  cc->settings.base.buffer_len = lwip_htonl(buf_len);
+  /* compute delay for bandwidth restriction, constrained to [0,1000] milliseconds */
+  /* 10Mbit/s by default if not extended settings */
+  if (cc->settings.base.flags & PP_HTONL(LWIPERF_FLAGS_EXTEND)) {
+    cc->delay_target = ((buf_len * 8 * 1000000ull) / lwip_ntohl(cc->settings.rate));
+  }
+  else {
+    cc->delay_target = ((buf_len * 8 * 1000000ull) / (1024 * 1024));
+  }
+
+  err = udp_connect(cc->pcb, &cc->remote_addr, cc->remote_port);
+  if (err != ERR_OK) {
+    lwiperf_udp_close(cc, LWIPERF_TCP_ABORTED_LOCAL);
+    return err;
+  }
+  lwiperf_list_add(&cc->base);
+  /* start sending immediately */
+  lwiperf_udp_client_send_more(cc);
+  return ERR_OK;
+}
+
+/** Receive data on an iperf udp session
+ * If client session, will receive final FIN from server,
+ * else, will receive all server traffic.
+ */
+static void
+lwiperf_udp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p,
+                 const ip_addr_t *addr, u16_t port)
+{
+  lwiperf_state_udp_t *server = (lwiperf_state_udp_t *)arg;
+  lwiperf_state_udp_t *conn = NULL;
+  struct UDP_datagram *pkt;
+  int32_t datagramID;
+  u16_t tot_len = p->tot_len;
+
+  LWIP_ASSERT("pcb mismatch", server->pcb == pcb);
+  LWIP_UNUSED_ARG(pcb);
+
+  /* lookup client using remote addr */
+  if (server->base.server)
+    conn = lwiperf_udp_search_client(server, addr, port);
+  else
+    conn = server;
+
+  /* Read packet iperf data. */
+  pkt = (struct UDP_datagram *)p->payload;
+  datagramID = ntohl(pkt->id);
+
+  if (conn && !conn->base.server) {
+    /* received server reports for client instances -> close it. */
+    if (!conn->report_count) {
+      lwiperf_udp_report_t *hdr;
+      pkt = (struct UDP_datagram *)p->payload;
+      pkt->id = htonl(datagramID);
+      hdr = (lwiperf_udp_report_t *)(pkt + 1);
+      if (hdr->flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST)) {
+        /* Adjust bytes transferred with the one from server report */
+        LWIP_PLATFORM_DIAG(("Received report from server (0x%x).\n", lwip_ntohl(hdr->flags)));
+        /*LWIP_PLATFORM_DIAG(("Stop %ld.%03ld sec, ", ntohl(hdr->stop_sec), ntohl(hdr->stop_usec)/1000));
+          LWIP_PLATFORM_DIAG(("Total %ldKB, ", ntohl(hdr->total_len2)/1024));*/
+        LWIP_PLATFORM_DIAG(("Jitter %ld.%03ld, ", ntohl(hdr->jitter1), ntohl(hdr->jitter2)));
+        LWIP_PLATFORM_DIAG(("Lost %ld/%ld datagrams, OoO %ld\n",
+                            ntohl(hdr->error_cnt), ntohl(hdr->datagrams), ntohl(hdr->outorder_cnt)));
+        conn->bytes_transferred = ntohl(hdr->total_len2);
+      }
+      if (hdr->flags & PP_HTONL(LWIPERF_FLAGS_EXTEND)) {
+        LWIP_PLATFORM_DIAG(("Extended report unsupported yet.\n"));
+      }
+    }
+    conn->report_count++;
+  }
+  else if (datagramID >= 0) {
+    struct timespec ts, dt;
+    uint32_t transit;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    if (!conn || !conn->have_settings_buf) {
+      /* allocate struct for a new client */
+      if (!conn) {
+        conn = lwiperf_udp_new_client(server);
+        if (!conn) {
+          pbuf_free(p);
+          return;
+        }
+      }
+      /* wait for 24-byte header */
+      if (p->tot_len < (sizeof(struct UDP_datagram) + sizeof(lwiperf_settings_t))) {
+        lwiperf_udp_close(conn, LWIPERF_UDP_ABORTED_LOCAL_DATAERROR);
+        pbuf_free(p);
+        return;
+      }
+      /* copy settings */
+      if (pbuf_copy_partial(p, &conn->settings, sizeof(lwiperf_settings_t), sizeof(struct UDP_datagram))
+          != sizeof(lwiperf_settings_t)) {
+        lwiperf_udp_close(conn, LWIPERF_UDP_ABORTED_LOCAL);
+        pbuf_free(p);
+        return;
+      }
+      conn->have_settings_buf = 1;
+      LWIP_PLATFORM_DIAG(("New UDP client (settings flags 0x%x)\n", lwip_ntohl(conn->settings.base.flags)));
+      /* Save client data. */
+      ip_addr_copy(conn->remote_addr, *addr);
+      conn->remote_port = port;
+      conn->time_started = sys_now();
+      /* Reset tradeoff & dualtest setting if temporary server */
+      if (server->base.server & 0x80) {
+        conn->settings.base.flags &= ~PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST|LWIPERF_FLAGS_ANSWER_NOW);
+      }
+      /* check if dualtest requested */
+      if (conn->settings.base.flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST)) {
+        if (conn->settings.base.flags & PP_HTONL(LWIPERF_FLAGS_ANSWER_NOW)) {
+          /* client requested parallel transmission test */
+          LWIP_PLATFORM_DIAG(("client requested parallel transmission test\n"));
+          err_t err2 = lwiperf_udp_tx_start(conn);
+          if (err2 != ERR_OK) {
+            lwiperf_udp_close(conn, LWIPERF_UDP_ABORTED_LOCAL_TXERROR);
+            pbuf_free(p);
+            return;
+          }
+        }
+      }
+    }
+    /* Update stats. */
+    conn->udp_lastpkt.tv_sec = ntohl(pkt->tv_sec);
+    conn->udp_lastpkt.tv_nsec = ntohl(pkt->tv_usec) * 1000;
+    if (conn->udp_seq != (uint32_t) datagramID) {
+      conn->udp_rx_lost += (uint32_t) datagramID - conn->udp_seq;
+      conn->udp_seq = datagramID + 1;
+      conn->udp_rx_outorder += 1;
+    } else {
+      conn->bytes_transferred += tot_len;
+      conn->udp_rx_total_pkt += 1;
+      conn->udp_seq += 1;
+    }
+    /* Jitter calculation
+     * from RFC 1889, Real Time Protocol (RTP)
+     *   J = J + ( | D(i-1,i) | - J ) / Compute jitter
+     */
+    diff_ts (&conn->udp_lastpkt, &ts, &dt);
+    transit = (uint32_t)((dt.tv_sec * 1000000) + (dt.tv_nsec / 1000));
+    if (conn->udp_last_transit) {
+      long deltaTransit = (long)(transit - conn->udp_last_transit);
+      if ( deltaTransit < 0.0 ) {
+        deltaTransit = -deltaTransit;
+      }
+      conn->jitter += (deltaTransit - conn->jitter) >> 4;
+    }
+    else {
+      conn->udp_last_transit = transit;
+    }
+  } else {
+    if (conn && conn->have_settings_buf && !conn->report_count) {
+      lwiperf_udp_report_t *hdr;
+      u32_t now, duration_ms;
+      now = sys_now();
+      duration_ms = now - conn->time_started;
+      /* Copy packet and send report back. */
+      struct pbuf *q = pbuf_clone(PBUF_TRANSPORT, PBUF_POOL, p);
+      LWIP_ASSERT("can't clone buffer", q != NULL);
+      pkt = (struct UDP_datagram *)q->payload;
+      pkt->id = htonl(datagramID);
+      hdr = (lwiperf_udp_report_t *)(pkt + 1);
+      hdr->flags        = PP_HTONL(LWIPERF_FLAGS_ANSWER_TEST);
+      hdr->total_len1   = htonl(0);
+      hdr->total_len2   = htonl(conn->bytes_transferred);
+      hdr->stop_sec     = htonl(duration_ms / 1000);
+      hdr->stop_usec    = htonl((duration_ms % 1000) * 1000);
+      hdr->error_cnt    = htonl(conn->udp_rx_lost);
+      hdr->outorder_cnt = htonl(conn->udp_rx_outorder);
+      hdr->datagrams    = htonl(conn->udp_rx_total_pkt);
+      hdr->jitter1      = htonl(conn->jitter / 1000000);
+      hdr->jitter2      = htonl((conn->jitter % 1000000) / 1000);
+      /* Adjust bytes transferred with the one from server report */
+      LWIP_PLATFORM_DIAG(("Sending report back to client (0x%x).\n", hdr->flags));
+      /*LWIP_PLATFORM_DIAG(("Stop %ld.%03ld sec, ", ntohl(hdr->stop_sec), ntohl(hdr->stop_usec)/1000));
+        LWIP_PLATFORM_DIAG(("Total %ldKB, ", ntohl(hdr->total_len2)/1024));*/
+      LWIP_PLATFORM_DIAG(("Jitter %ld.%03ld, ", ntohl(hdr->jitter1), ntohl(hdr->jitter2)));
+      LWIP_PLATFORM_DIAG(("Lost %ld/%ld datagrams, OoO %ld\n",
+                          ntohl(hdr->error_cnt), ntohl(hdr->datagrams), ntohl(hdr->outorder_cnt)));
+      /* Store report buffer in conn structure */
+      conn->reported     = q;
+      /* Send report to client. */
+      lwiperf_udp_send_report(conn);
+    }
+  }
+  pbuf_free(p);
+}
+
+/**
+ * @ingroup iperf
+ * Start an UDP iperf server on a specific IP address and port and listen for
+ * incoming datagrams from iperf clients.
+ *
+ * @returns a connection handle that can be used to abort the server
+ *          by calling @ref lwiperf_abort()
+ */
+void *
+lwiperf_start_udp_server(const ip_addr_t *local_addr, u16_t local_port,
+                         lwiperf_report_fn report_fn, void *report_arg)
+{
+  lwiperf_state_udp_t *s;
+  err_t err;
+
+  LWIP_ASSERT_CORE_LOCKED();
+
+  if (local_addr == NULL)
+    return NULL;
+  s = (lwiperf_state_udp_t *)LWIPERF_ALLOC(lwiperf_state_udp_t);
+  if (s == NULL)
+    return NULL;
+  do {
+    memset(s, 0, sizeof(lwiperf_state_udp_t));
+    s->base.tcp = 0;
+    s->base.server = 1;
+    s->report_fn = report_fn;
+    s->report_arg = report_arg;
+    /* allocate udp pcb */
+    s->pcb = udp_new();
+    if (s->pcb == NULL)
+      break;
+    /* bind to locat port/address */
+    err = udp_bind(s->pcb, local_addr, local_port);
+    if (err != ERR_OK)
+      break;
+    /* join group if multicast address */
+    if (ip_addr_ismulticast(local_addr)) {
+      if (IP_IS_V6 (local_addr)) {
+        err = mld6_joingroup (IP6_ADDR_ANY6, ip_2_ip6 (local_addr));
+      }
+      else {
+        err = igmp_joingroup (IP4_ADDR_ANY4, ip_2_ip4 (local_addr));
+      }
+      if (err != ERR_OK)
+        break;
+    }
+    /* start receiving datagrams */
+    udp_recv(s->pcb, lwiperf_udp_recv, s);
+    /* finally, add to list */
+    lwiperf_list_add(&s->base);
+    return s;
+  } while (0);
+  /* error occurs, cleanup */
+  if (s->pcb)
+    udp_remove(s->pcb);
+  LWIPERF_FREE(lwiperf_state_udp_t, s);
+  return NULL;
+}
+
 /**
  * @ingroup iperf
- * Abort an iperf session (handle returned by lwiperf_start_tcp_server*())
+ * Start a UDP iperf client connected to a specific IP address and port.
+ *
+ * @returns a connection handle that can be used to abort the client
+ *          by calling @ref lwiperf_abort()
+ */
+void *
+lwiperf_start_udp_client(const ip_addr_t *local_addr, u16_t local_port,
+                         const ip_addr_t *remote_addr, u16_t remote_port,
+                         enum lwiperf_client_type type, int amount, s32_t rate, u8_t tos,
+                         lwiperf_report_fn report_fn, void *report_arg)
+{
+  err_t err;
+  lwiperf_state_udp_t *c;
+  u32_t buf_len, flags;
+  u16_t sport = 0;
+
+  switch (type) {
+  case LWIPERF_CLIENT:
+    /* Unidirectional tx only test */
+    flags = 0;
+    break;
+  case LWIPERF_DUAL:
+    /* Do a bidirectional test simultaneously */
+    flags = htonl(LWIPERF_FLAGS_ANSWER_TEST | LWIPERF_FLAGS_ANSWER_NOW);
+    break;
+  case LWIPERF_TRADEOFF:
+    /* Do a bidirectional test individually */
+    flags = htonl(LWIPERF_FLAGS_ANSWER_TEST);
+    break;
+  default:
+    /* invalid argument */
+    return NULL;
+  }
+  if (type != LWIPERF_CLIENT) {
+    /* tradeoff or dualtest requested. need to start a new server on another port */
+    lwiperf_state_udp_t *s = lwiperf_start_udp_server(
+      local_addr ? local_addr : IP4_ADDR_ANY, 0,
+      report_fn, report_arg);
+    if (s) {
+      s->base.server |= 0x80; /* put a temporary server mark */
+      sport = s->pcb->local_port; /* retrieve this server port */
+      LWIP_PLATFORM_DIAG(("Dualtest port: %u\n", sport));
+    }
+    else {
+      LWIP_PLATFORM_DIAG(("Dualtest disabled!\n"));
+    }
+  }
+  /* Create client */
+  c = lwiperf_udp_tx_new(NULL);
+  if (c == NULL)
+    return NULL;
+  c->report_fn = report_fn;
+  c->report_arg = report_arg;
+  /* save remote */
+  ip_addr_copy(c->remote_addr, *remote_addr);
+  c->remote_port = remote_port;
+  /* save client settings, to be copied in each packets */
+  c->have_settings_buf = 1;
+  c->settings.base.amount = lwip_htonl((uint32_t)amount);
+  buf_len = (u32_t)(IP_IS_V6(remote_addr) ? 1450 : 1470);
+  c->settings.base.buffer_len = lwip_htonl(buf_len);
+  if (rate != (1024 * 1024)) { /* 1Mb/s is the default if not specified. */
+    c->settings.rate = lwip_htonl(rate);
+    c->settings.base.flags |= PP_HTONL(LWIPERF_FLAGS_EXTEND);
+  }
+  if (sport) {
+    /* tradeoff or dualtest requested need to put server port in settings */
+    c->settings.base.flags |= flags;
+    c->settings.base.remote_port = lwip_htonl(sport);
+  }
+  /* compute delay for bandwidth restriction, constrained to [0,1]s in microseconds */
+  /* 10Mbit/s by default if not extended settings */
+  c->delay_target = (uint32_t)((buf_len * 8 * 1000000ull) / rate);
+  LWIP_PLATFORM_DIAG(("Frame delay: %lu us\n", c->delay_target));
+  /* set tos if specified */
+  if (tos)
+    c->pcb->tos = tos;
+  do {
+    if (local_addr) {
+      /* bind to local address if specified */
+      err = udp_bind(c->pcb, local_addr, 0);
+      if (err != ERR_OK)
+        break;
+      /* join multicast group? */
+      if (ip_addr_ismulticast(local_addr)) {
+        if (IP_IS_V6 (local_addr)) {
+          err = mld6_joingroup (IP6_ADDR_ANY6, ip_2_ip6 (local_addr));
+        }
+        else {
+          err = igmp_joingroup (IP4_ADDR_ANY4, ip_2_ip4 (local_addr));
+        }
+        if (err != ERR_OK)
+          break;
+      }
+    }
+    /* set pseudo-connect parameters */
+    err = udp_connect(c->pcb, remote_addr, remote_port);
+    if (err != ERR_OK)
+      break;
+    /* and add to instance list */
+    lwiperf_list_add(&c->base);
+    /* start sending immediately */
+    lwiperf_udp_client_send_more(c);
+    return c;
+  } while (0);
+  /* error occurs, cleanup */
+  if (c->pcb)
+    udp_remove(c->pcb);
+  LWIPERF_FREE(lwiperf_state_udp_t, c);
+  return NULL;
+}
+
+/**
+ * @ingroup iperf
+ * Poll all running UDP client to send more according to specified BW.
+ */
+void
+lwiperf_poll_udp_client(void)
+{
+  lwiperf_state_base_t *c;
+  for (c = lwiperf_all_connections; c != NULL; c = c->next) {
+    if (!c->server && !c->tcp)
+      lwiperf_udp_client_send_more((lwiperf_state_udp_t *)c);
+  }
+}
+
+/**
+ * @ingroup iperf
+ * Abort an iperf session (handle returned by `lwiperf_start_(tcp|udp)_server`)
  */
 void
 lwiperf_abort(void *lwiperf_session)
@@ -830,7 +1537,11 @@ lwiperf_abort(void *lwiperf_session)
       if (last != NULL) {
         last->next = i;
       }
-      LWIPERF_FREE(lwiperf_state_tcp_t, dealloc); /* @todo: type? */
+      if (dealloc->tcp) {
+        lwiperf_tcp_close((lwiperf_state_tcp_t *)dealloc, LWIPERF_TCP_ABORTED_LOCAL);
+      } else {
+        lwiperf_udp_close((lwiperf_state_udp_t *)dealloc, LWIPERF_UDP_ABORTED_LOCAL);
+      }
     } else {
       last = i;
       i = i->next;
@@ -838,4 +1549,4 @@ lwiperf_abort(void *lwiperf_session)
   }
 }
 
-#endif /* LWIP_TCP && LWIP_CALLBACK_API */
+#endif /* LWIP_CALLBACK_API && LWIP_TCP && LWIP_UDP */
diff --git a/src/include/lwip/apps/lwiperf.h b/src/include/lwip/apps/lwiperf.h
index cc86e7f8..4b4a34fe 100644
--- a/src/include/lwip/apps/lwiperf.h
+++ b/src/include/lwip/apps/lwiperf.h
@@ -60,7 +60,19 @@ enum lwiperf_report_type
   /** Transmit error lead to test abort */
   LWIPERF_TCP_ABORTED_LOCAL_TXERROR,
   /** Remote side aborted the test */
-  LWIPERF_TCP_ABORTED_REMOTE
+  LWIPERF_TCP_ABORTED_REMOTE,
+  /** The server side test is done */
+  LWIPERF_UDP_DONE_SERVER,
+  /** The client side test is done */
+  LWIPERF_UDP_DONE_CLIENT,
+  /** Local error lead to test abort */
+  LWIPERF_UDP_ABORTED_LOCAL,
+  /** Data check error lead to test abort */
+  LWIPERF_UDP_ABORTED_LOCAL_DATAERROR,
+  /** Transmit error lead to test abort */
+  LWIPERF_UDP_ABORTED_LOCAL_TXERROR,
+  /** Remote side aborted the test */
+  LWIPERF_UDP_ABORTED_REMOTE
 };
 
 /** Control */
@@ -90,6 +102,14 @@ void* lwiperf_start_tcp_client(const ip_addr_t* remote_addr, u16_t remote_port,
 void* lwiperf_start_tcp_client_default(const ip_addr_t* remote_addr,
                                lwiperf_report_fn report_fn, void* report_arg);
 
+void* lwiperf_start_udp_client(const ip_addr_t *local_addr, u16_t local_port,
+                               const ip_addr_t *remote_addr, u16_t remote_port,
+                               enum lwiperf_client_type type, int amount, s32_t rate, u8_t tos,
+                               lwiperf_report_fn report_fn, void *report_arg);
+void* lwiperf_start_udp_server(const ip_addr_t* local_addr, u16_t local_port,
+                               lwiperf_report_fn report_fn, void* report_arg);
+void lwiperf_poll_udp_client(void);
+
 void  lwiperf_abort(void* lwiperf_session);
 
 
-- 
2.17.1

