[ovs-dev,RFC,v2,5/6] Conntrack: Add SIP end-to-end and unit tests.

Message ID 20180212230829.31624-6-tiagolam@gmail.com
State New
Headers show
Series
  • Initial support for new SIP Alg.
Related show

Commit Message

Tiago Lam Feb. 12, 2018, 11:08 p.m.
End-to-end tests have been added to system-traffic.at. They set up a NS1
that acts as a UAS and another NS2 that acts as a UAC, set up the
appropriate flows in each direction and finally verify if the traffic
between the two NS' is as expected. These tests make use of the SIPp
tool and the scenarios needed by SIPp have been added to the new sipp/
folder under tests/.

Unit tests have also been added to test-sip.c, and are being called from
conntrack-sip.at. These tests call functions like sip_parse_sdp,
sip_parse_strt_line and sip_parse_tcp, passing both valid and invalid
inputs, then comparing with the expected results. This includes tests
for validating the supported TCP framing.

Signed-off-by: Tiago Lam <tiagolam@gmail.com>
---
 tests/atlocal.in                       |   3 +
 tests/automake.mk                      |   8 +
 tests/conntrack-sip.at                 |   7 +
 tests/sipp/uac_happy_case_scenario.xml | 126 +++++++++++++
 tests/sipp/uas_happy_case_scenario.xml | 125 +++++++++++++
 tests/system-traffic.at                |  71 +++++++
 tests/test-sip.c                       | 327 +++++++++++++++++++++++++++++++++
 tests/testsuite.at                     |   1 +
 8 files changed, 668 insertions(+)
 create mode 100644 tests/conntrack-sip.at
 create mode 100644 tests/sipp/uac_happy_case_scenario.xml
 create mode 100644 tests/sipp/uas_happy_case_scenario.xml
 create mode 100644 tests/test-sip.c

Patch

diff --git a/tests/atlocal.in b/tests/atlocal.in
index 55f9333ee..023337b5c 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -168,6 +168,9 @@  fi
 # Set HAVE_TCPDUMP
 find_command tcpdump
 
+# Set HAVE_SIPP
+find_command sipp
+
 CURL_OPT="-g -v --max-time 1 --retry 2 --retry-delay 1 --connect-timeout 1"
 
 # Turn off proxies.
diff --git a/tests/automake.mk b/tests/automake.mk
index 18698ebc3..fc2efe255 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -94,6 +94,7 @@  TESTSUITE_AT = \
 	tests/vlog.at \
 	tests/vtep-ctl.at \
 	tests/auto-attach.at \
+	tests/conntrack-sip.at \
 	tests/ovn.at \
 	tests/ovn-northd.at \
 	tests/ovn-nbctl.at \
@@ -335,6 +336,7 @@  tests_ovstest_SOURCES = \
 	tests/test-ccmap.c \
 	tests/test-cmap.c \
 	tests/test-conntrack.c \
+	tests/test-sip.c \
 	tests/test-csum.c \
 	tests/test-flows.c \
 	tests/test-hash.c \
@@ -405,6 +407,11 @@  PYCOV_CLEAN_FILES += $(CHECK_PYFILES:.py=.py,cover) .coverage
 
 FLAKE8_PYFILES += $(CHECK_PYFILES)
 
+# SIPP scenarios
+EXTRA_DIST += \
+	tests/sipp/uac_happy_case_scenario.xml \
+	tests/sipp/uas_happy_case_scenario.xml
+
 if HAVE_OPENSSL
 TESTPKI_FILES = \
 	tests/testpki-cacert.pem \
@@ -446,4 +453,5 @@  CLEAN_LOCAL += clean-pki
 clean-pki:
 	rm -f tests/pki/stamp
 	rm -rf tests/pki
+
 endif
diff --git a/tests/conntrack-sip.at b/tests/conntrack-sip.at
new file mode 100644
index 000000000..17eff915c
--- /dev/null
+++ b/tests/conntrack-sip.at
@@ -0,0 +1,7 @@ 
+AT_BANNER([conntrack-sip unit tests])
+
+AT_SETUP([conntrack-sip])
+AT_KEYWORDS([conntrack-sip])
+AT_CHECK(ovstest test-sip, [], [ignore], [ignore])
+
+AT_CLEANUP
diff --git a/tests/sipp/uac_happy_case_scenario.xml b/tests/sipp/uac_happy_case_scenario.xml
new file mode 100644
index 000000000..ee728dfc1
--- /dev/null
+++ b/tests/sipp/uac_happy_case_scenario.xml
@@ -0,0 +1,126 @@ 
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<!-- This program is free software; you can redistribute it and/or      -->
+<!-- modify it under the terms of the GNU General Public License as     -->
+<!-- published by the Free Software Foundation; either version 2 of the -->
+<!-- License, or (at your option) any later version.                    -->
+<!--                                                                    -->
+<!-- This program is distributed in the hope that it will be useful,    -->
+<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of     -->
+<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the      -->
+<!-- GNU General Public License for more details.                       -->
+<!--                                                                    -->
+<!-- You should have received a copy of the GNU General Public License  -->
+<!-- along with this program; if not, write to the                      -->
+<!-- Free Software Foundation, Inc.,                                    -->
+<!-- 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA             -->
+<!--                                                                    -->
+<!--                 Sipp default 'uac' scenario.                       -->
+<!--                                                                    -->
+
+<scenario name="Basic Sipstone UAC">
+  <!-- In client mode (sipp placing calls), the Call-ID MUST be         -->
+  <!-- generated by sipp. To do so, use [call_id] keyword.                -->
+  <!--
+      Content-Type: application/sdp
+  -->
+  <send retrans="500">
+    <![CDATA[
+
+      INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
+      To: sut <sip:[service]@[remote_ip]:[remote_port]>
+      Call-ID: [call_id]
+      CSeq: 1 INVITE
+      Contact: sip:sipp@[local_ip]:[local_port]
+      Max-Forwards: 70
+      Subject: Performance Test
+      Content-Type: application/sdp
+      Content-Length: [len]
+
+      v=0
+      o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
+      s=-
+      c=IN IP[media_ip_type] [media_ip]
+      t=0 0
+      m=audio [rtpstream_audio_port] RTP/AVP 0
+      a=rtpmap:0 PCMU/8000
+
+    ]]>
+  </send>
+
+  <recv response="100"
+        optional="true">
+  </recv>
+
+  <recv response="180" optional="true">
+  </recv>
+
+  <recv response="183" optional="true">
+  </recv>
+
+  <!-- By adding rrs="true" (Record Route Sets), the route sets         -->
+  <!-- are saved and used for following messages sent. Useful to test   -->
+  <!-- against stateful SIP proxies/B2BUAs.                             -->
+  <recv response="200" rtd="true">
+  </recv>
+
+  <!-- Packet lost can be simulated in any send/recv message by         -->
+  <!-- by adding the 'lost = "10"'. Value can be [1-100] percent.       -->
+  <send>
+    <![CDATA[
+
+      ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
+      To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
+      Call-ID: [call_id]
+      CSeq: 1 ACK
+      Contact: sip:sipp@[local_ip]:[local_port]
+      Max-Forwards: 70
+      Subject: Performance Test
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <nop>
+    <action>
+      <exec rtp_stream="/home/lam/Downloads/a2002011001-e02-16kHz.wav"/>
+    </action>
+  </nop>
+
+  <!-- This delay can be customized by the -d command-line option       -->
+  <!-- or by adding a 'milliseconds = "value"' option here.             -->
+  <pause/>
+
+  <!-- The 'crlf' option inserts a blank line in the statistics report. -->
+  <send retrans="500">
+    <![CDATA[
+
+      BYE sip:[service]@[remote_ip]:[remote_port] SIP/2.0
+      Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
+      From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
+      To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
+      Call-ID: [call_id]
+      CSeq: 2 BYE
+      Contact: sip:sipp@[local_ip]:[local_port]
+      Max-Forwards: 70
+      Subject: Performance Test
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <recv response="200" crlf="true">
+  </recv>
+
+  <!-- definition of the response time repartition table (unit is ms)   -->
+  <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
+
+  <!-- definition of the call length repartition table (unit is ms)     -->
+  <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
+
+</scenario>
diff --git a/tests/sipp/uas_happy_case_scenario.xml b/tests/sipp/uas_happy_case_scenario.xml
new file mode 100644
index 000000000..beb2110c2
--- /dev/null
+++ b/tests/sipp/uas_happy_case_scenario.xml
@@ -0,0 +1,125 @@ 
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE scenario SYSTEM "sipp.dtd">
+
+<!-- This program is free software; you can redistribute it and/or      -->
+<!-- modify it under the terms of the GNU General Public License as     -->
+<!-- published by the Free Software Foundation; either version 2 of the -->
+<!-- License, or (at your option) any later version.                    -->
+<!--                                                                    -->
+<!-- This program is distributed in the hope that it will be useful,    -->
+<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of     -->
+<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the      -->
+<!-- GNU General Public License for more details.                       -->
+<!--                                                                    -->
+<!-- You should have received a copy of the GNU General Public License  -->
+<!-- along with this program; if not, write to the                      -->
+<!-- Free Software Foundation, Inc.,                                    -->
+<!-- 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA             -->
+<!--                                                                    -->
+<!--                 Sipp default 'uas' scenario.                       -->
+<!--                                                                    -->
+
+<scenario name="Basic UAS responder">
+  <!-- By adding rrs="true" (Record Route Sets), the route sets         -->
+  <!-- are saved and used for following messages sent. Useful to test   -->
+  <!-- against stateful SIP proxies/B2BUAs.                             -->
+  <recv request="INVITE" crlf="true">
+  </recv>
+
+  <!-- The '[last_*]' keyword is replaced automatically by the          -->
+  <!-- specified header if it was present in the last message received  -->
+  <!-- (except if it was a retransmission). If the header was not       -->
+  <!-- present or if no message has been received, the '[last_*]'       -->
+  <!-- keyword is discarded, and all bytes until the end of the line    -->
+  <!-- are also discarded.                                              -->
+  <!--                                                                  -->
+  <!-- If the specified header was present several times in the         -->
+  <!-- message, all occurences are concatenated (CRLF seperated)        -->
+  <!-- to be used in place of the '[last_*]' keyword.                   -->
+
+  <!-- No need for 180
+  <send>
+    <![CDATA[
+
+      SIP/2.0 180 Ringing
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[pid]SIPpTag01[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Length: 0
+
+    ]]>
+  </send>
+  -->
+
+  <send retrans="500">
+    <![CDATA[
+
+      SIP/2.0 200 OK
+      [last_Via:]
+      [last_From:]
+      [last_To:];tag=[pid]SIPpTag01[call_number]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Type: application/sdp
+      Content-Length: [len]
+
+      v=0
+      o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
+      s=-
+      c=IN IP[media_ip_type] [media_ip]
+      t=0 0
+      m=audio [rtpstream_audio_port] RTP/AVP 0
+      a=rtpmap:0 PCMU/8000
+
+    ]]>
+  </send>
+
+  <recv request="ACK"
+        rtd="true"
+        crlf="true">
+  </recv>
+
+  <nop>
+    <action>
+      <exec rtp_stream="/home/lam/Downloads/a2002011001-e02-16kHz.wav"/>
+    </action>
+  </nop>
+
+  <!-- This delay can be customized by the -d command-line option       -->
+  <!-- or by adding a 'milliseconds = "value"' option here.             -->
+  <pause/>
+
+  <recv request="BYE">
+  </recv>
+
+  <send>
+    <![CDATA[
+
+      SIP/2.0 200 OK
+      [last_Via:]
+      [last_From:]
+      [last_To:]
+      [last_Call-ID:]
+      [last_CSeq:]
+      Contact: <sip:[local_ip]:[local_port];transport=[transport]>
+      Content-Length: 0
+
+    ]]>
+  </send>
+
+  <!-- Keep the call open for a while in case the 200 is lost to be     -->
+  <!-- able to retransmit it if we receive the BYE again.               -->
+  <timewait milliseconds="4000"/>
+
+
+  <!-- definition of the response time repartition table (unit is ms)   -->
+  <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/>
+
+  <!-- definition of the call length repartition table (unit is ms)     -->
+  <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/>
+
+</scenario>
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index dbd56405d..e8dba721a 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -2870,6 +2870,77 @@  udp,orig=(src=10.1.1.1,dst=10.1.1.2,sport=<cleared>,dport=<cleared>),reply=(src=
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
 
+dnl Setups two namespaces, at_ns0 and at_ns1, connected to a bridge, br0, and
+dnl runs sipp in both in the following form:
+dnl - at_ns0 loads an UAS scenario and waits for requests;
+dnl - at_ns1 loads an UAC scenario, which will connect to at_ns0.
+dnl
+dnl Aside from RTP (which is over UDP), this tests sends SIP over TCP over
+dnl IPv4 and no NAT is performed.
+AT_SETUP([conntrack - SIP over TCP over IPv4])
+AT_SKIP_IF([test $HAVE_SIPP = no])
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_ALG()
+
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.0.1.10/24")
+NS_CHECK_EXEC([at_ns0], [ip link set dev p0 address 00:00:00:00:01:10])
+ADD_VETH(p1, at_ns1, br0, "10.0.2.10/24")
+NS_CHECK_EXEC([at_ns1], [ip link set dev p1 address 00:00:00:00:02:10])
+NS_CHECK_EXEC([at_ns0], [ip route add 10.0.2.0/24 via 10.0.1.10 dev p0])
+NS_CHECK_EXEC([at_ns1], [ip route add 10.0.1.0/24 via 10.0.2.10 dev p1])
+
+dnl Allow any traffic from at_ns1->at_ns0.
+dnl Only allow return traffic from at_ns0->at_ns1 (and arp, icmp).
+AT_DATA([flows.txt], [dnl
+dnl Track all IPv4 traffic
+table=0,priority=1,action=drop
+table=0,priority=10,arp,action=normal
+table=0,priority=10,icmp,action=normal
+table=0,priority=100,in_port=2,tcp,action=ct(alg=sip,commit),1
+table=0,priority=100,in_port=1,tcp,action=ct(table=1)
+table=0,priority=100,in_port=2,udp,action=ct(table=2)
+table=0,priority=100,in_port=1,udp,action=ct(table=2)
+dnl
+dnl Table 1
+dnl
+dnl Allow new TCPv4 TCP connections.
+table=1,in_port=1,tcp,ct_state=+trk+est,action=2
+table=1,in_port=1,tcp,ct_state=+trk+rel,action=2
+dnl
+dnl Table 2
+dnl
+dnl Allow RTP (UDP) connections through.
+table=2,in_port=1,udp,ct_state=+rel,action=2
+table=2,in_port=2,udp,ct_state=+rel,action=1
+])
+
+AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
+
+NETNS_DAEMONIZE([at_ns0], [sipp -t t1 -mi 10.0.1.10 -m 1 -d 20000 -sf $srcdir/sipp/uas_happy_case_scenario.xml], [sipp_uas.pid])
+OVS_WAIT_UNTIL([ip netns exec at_ns0 netstat -l | grep sip])
+
+dnl Traffic between at_ns1<->at_ns0 should now start flowing.
+NS_CHECK_EXEC([at_ns1], [sipp 10.0.1.10 -t t1 -rp 1s -m 1 -d 30000 -mi 10.0.2.10 -sf $srcdir/sipp/uac_happy_case_scenario.xml
+], [0], [stdout], [stderr])
+
+dnl Wait until uas in at_ns0 is done processing all calls
+dnl sipp in at_ns1 (running as uac) is finished with the calls, and sipp in
+dnl at_ns0 (running as uas) should be finished as well. If not, then something
+dnl is not right and the test should fail.
+OVS_WAIT_WHILE([ip netns exec at_ns0 netstat -l | grep sip])
+
+dnl Discards CLOSE_WAIT and CLOSING
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.1.10)], [0], [dnl
+tcp,orig=(src=10.0.2.10,dst=10.0.1.10,sport=<cleared>,dport=<cleared>),reply=(src=10.0.1.10,dst=10.0.2.10,sport=<cleared>,dport=<cleared>),protoinfo=(state=<cleared>),helper=sip
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_BANNER([conntrack - NAT])
 
 AT_SETUP([conntrack - simple SNAT])
diff --git a/tests/test-sip.c b/tests/test-sip.c
new file mode 100644
index 000000000..602f52671
--- /dev/null
+++ b/tests/test-sip.c
@@ -0,0 +1,327 @@ 
+/*
+ * Copyright (c) 2017 Avaya, Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+#undef NDEBUG
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "ovstest.h"
+#include "openvswitch/ofp-actions.h"
+#include "conntrack-sip.h"
+
+/* Count of tests run */
+static int num_tests = 0;
+
+static const struct eth_addr DUMMY_ETH_ADDR = ETH_ADDR_C(00,00,00,00,00,01);
+static const ovs_be32 DUMMY_IP_ADDR = 0x0a00020a;
+
+static char *valid_sdp =
+    "v=0\r\n"
+    "o=user1 53655765 2353687637 IN IP4 10.0.2.10\r\n"
+    "s=-\r\n"
+    "c=IN IP4 10.0.2.10\r\n"
+    "t=0 0\r\n"
+    "m=audio 8192 RTP/AVP 0\r\n"
+    "a=rtpmap:0 PCMU/8000\r\n";
+
+static char *invalid_sdp = "invalid sdp";
+
+static char *sip_invite =
+    "INVITE sip:service@10.0.1.10:5060 SIP/2.0\r\n"
+    "Via: SIP/2.0/TCP 10.0.2.10:5060;branch=z9hG4bK-26201-1-0\r\n"
+    "From: sipp <sip:sipp@10.0.2.10:5060>;tag=26201SIPpTag001\r\n"
+    "To: sut <sip:service@10.0.1.10:5060>\r\n"
+    "Call-ID: 1-26201@10.0.2.10\r\n"
+    "CSeq: 1 INVITE\r\n"
+    "Contact: sip:sipp@10.0.2.10:5060\r\n"
+    "Max-Forwards: 70\r\n"
+    "Content-Length:   0\r\n"
+    "\r\n";
+
+static char *sip_invite_with_sdp =
+    "INVITE sip:service@10.0.1.10:5060 SIP/2.0\r\n"
+    "Via: SIP/2.0/TCP 10.0.2.10:5060;branch=z9hG4bK-26201-1-0\r\n"
+    "From: sipp <sip:sipp@10.0.2.10:5060>;tag=26201SIPpTag001\r\n"
+    "To: sut <sip:service@10.0.1.10:5060>\r\n"
+    "Call-ID: 1-26201@10.0.2.10\r\n"
+    "CSeq: 1 INVITE\r\n"
+    "Contact: sip:sipp@10.0.2.10:5060\r\n"
+    "Max-Forwards: 70\r\n"
+    "Content-Type: application/sdp\r\n"
+    "Content-Length:   129\r\n"
+    "\r\n";
+
+static char *sip_invite_missing_cr =
+    "INVITE sip:service@10.0.1.10:5060 SIP/2.0\r\n"
+    "Via: SIP/2.0/TCP 10.0.2.10:5060;branch=z9hG4bK-26201-1-0\r\n"
+    "From: sipp <sip:sipp@10.0.2.10:5060>;tag=26201SIPpTag001\r\n"
+    "To: sut <sip:service@10.0.1.10:5060>\n"
+    "Call-ID: 1-26201@10.0.2.10\r\n"
+    "CSeq: 1 INVITE\r\n"
+    "Contact: sip:sipp@10.0.2.10:5060\r\n"
+    "Max-Forwards: 70\n"
+    "Content-Length:   0\r\n"
+    "\r\n";
+
+static void
+test_valid_sdp(void)
+{
+    struct sip_sdp *sdp;
+    sdp = sip_parse_sdp(valid_sdp, strlen(valid_sdp));
+
+    ovs_assert(sdp != NULL);
+    ovs_assert(sdp->orig == 0x0a02000a);
+    ovs_assert(sdp->conn == 0x0a02000a);
+    ovs_assert(sdp->port == 0x2000);
+
+    free(sdp);
+}
+
+static void
+test_invalid_sdp(void)
+{
+    struct sip_sdp *sdp;
+    sdp = sip_parse_sdp(invalid_sdp, strlen(invalid_sdp));
+
+    ovs_assert(sdp == NULL);
+
+    free(sdp);
+}
+
+static void
+test_parse_strt_ln(void)
+{
+    struct sip_strt_ln *strt_ln;
+    strt_ln = sip_parse_strt_ln(sip_invite, strlen(sip_invite));
+
+    ovs_assert(strt_ln != NULL);
+
+    ovs_assert(strt_ln->type == REQUEST_LINE);
+    ovs_assert(strt_ln->reqs_ln->mthd == INVITE);
+}
+
+static void
+test_parse_strt_ln_missing_cr(void)
+{
+    struct sip_strt_ln *strt_ln;
+    strt_ln = sip_parse_strt_ln(sip_invite_missing_cr,
+                                strlen(sip_invite_missing_cr));
+
+    ovs_assert(strt_ln != NULL);
+
+    ovs_assert(strt_ln->type == REQUEST_LINE);
+    ovs_assert(strt_ln->reqs_ln->mthd == INVITE);
+}
+
+static void *
+compose_dummy_ipv4(struct dp_packet *packet, uint8_t proto,
+                   ovs_be32 ipv4_src, ovs_be32 ipv4_dst,
+                   uint8_t tos, uint8_t ttl, int proto_hdr_len,
+                   int proto_len)
+{
+    struct ip_header *l3_hdr;
+    void *data;
+
+    l3_hdr = dp_packet_l3(packet);
+    l3_hdr->ip_proto = proto;
+    l3_hdr->ip_ihl_ver = IP_HEADER_LEN / 4;
+    l3_hdr->ip_tot_len = htons(IP_HEADER_LEN + proto_hdr_len + proto_len);
+    packet_set_ipv4(packet, ipv4_src, ipv4_dst, tos, ttl);
+    data = dp_packet_put_zeros(packet, proto_hdr_len);
+    dp_packet_set_l4(packet, data);
+    return data;
+}
+
+static void *
+compose_dummy_tcp(struct dp_packet *pkt, const struct eth_addr eth_src,
+                  const struct eth_addr eth_dst, ovs_be32 ipv4_src,
+                  ovs_be32 ipv4_dst, ovs_be16 port_src, ovs_be16 port_dst,
+                  int size)
+{
+    struct tcp_header *th;
+
+    eth_compose(pkt, eth_dst, eth_src, ETH_TYPE_IP, IP_HEADER_LEN);
+    th = compose_dummy_ipv4(pkt, IPPROTO_TCP, ipv4_src, ipv4_dst, 0, 1,
+                            TCP_HEADER_LEN, size);
+
+    packet_set_tcp_port(pkt, port_src, port_dst);
+    th->tcp_ctl = htons((TCP_HEADER_LEN / 4) << 12);
+
+    return th + TCP_HEADER_LEN;
+}
+
+/* Test sip_parse_tcp function against multiple cases, including:
+ * - A single TCP packet transporting a single SIP message;
+ * - A single TCP packet transporting multiple SIP messages;
+ * - A SIP message being carried across multiple TCP packets, i.e., a first
+ *   TCP packet arrives transporting an incomplete SIP message, followed by
+ *   another TCP packet wich carries the rest of the SIP message. */
+
+static void
+test_handle_single_tcp_single_sip(void)
+{
+    uint32_t stub[128 / 4];
+    struct dp_packet packet;
+
+    dp_packet_use_stub(&packet, stub, sizeof stub);
+    dp_packet_clear(&packet);
+
+    compose_dummy_tcp(&packet, DUMMY_ETH_ADDR, DUMMY_ETH_ADDR,
+                      htonl(DUMMY_IP_ADDR),htonl(DUMMY_IP_ADDR),
+                      htons(IPPORT_SIP), htons(IPPORT_SIP),
+                      strlen(sip_invite_with_sdp) + strlen(valid_sdp));
+    /* Compose an SIP message. */
+    dp_packet_put(&packet, sip_invite_with_sdp, strlen(sip_invite_with_sdp));
+    dp_packet_put(&packet, valid_sdp, strlen(valid_sdp));
+
+    struct ip_header *l3_hdr = dp_packet_l3(&packet);
+    struct tcp_header *th = dp_packet_l4(&packet);
+    size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4;
+    size_t ip_hdr_len = (IP_IHL(l3_hdr->ip_ihl_ver) * 4);
+    size_t tcp_len = ntohs(l3_hdr->ip_tot_len) - ip_hdr_len - tcp_hdr_len;
+    /* Move to beginning of TCP payload, where the SIP payload is */
+    char *sip = ((char *) th) + tcp_hdr_len;
+    size_t sip_len = tcp_len;
+    struct ovs_list *out_msgs = NULL;
+
+    out_msgs = sip_parse_tcp(sip, sip_len);
+
+    ovs_assert(ovs_list_size(out_msgs) == 1);
+
+    struct sip_msg *out_msg;
+    struct sip_msg *next;
+    LIST_FOR_EACH_SAFE (out_msg, next, node, out_msgs) {
+        struct sip_strt_ln *strt_ln = out_msg->strt_ln;
+        sip_len = out_msg->bdy_len;
+
+        ovs_assert(strt_ln != NULL);
+        ovs_assert(strt_ln->type == REQUEST_LINE);
+        ovs_assert(strt_ln->reqs_ln->mthd == INVITE);
+        ovs_assert(sip_len == strlen(valid_sdp));
+
+        free_sip_msg(out_msg);
+    }
+    free(out_msgs);
+    dp_packet_uninit(&packet);
+}
+
+static void
+test_handle_single_tcp_multiple_sip(void)
+{
+    uint32_t stub[128 / 4];
+    struct dp_packet packet;
+
+    dp_packet_use_stub(&packet, stub, sizeof stub);
+    dp_packet_clear(&packet);
+
+    compose_dummy_tcp(&packet, DUMMY_ETH_ADDR, DUMMY_ETH_ADDR,
+                      htonl(DUMMY_IP_ADDR),htonl(DUMMY_IP_ADDR),
+                      htons(IPPORT_SIP), htons(IPPORT_SIP),
+                      2 * (strlen(sip_invite_with_sdp) + strlen(valid_sdp)));
+    /* Insert a first SIP message */
+    dp_packet_put(&packet, sip_invite_with_sdp, strlen(sip_invite_with_sdp));
+    dp_packet_put(&packet, valid_sdp, strlen(valid_sdp));
+    /* Insert a second SIP message */
+    dp_packet_put(&packet, sip_invite_with_sdp, strlen(sip_invite_with_sdp));
+    dp_packet_put(&packet, valid_sdp, strlen(valid_sdp));
+
+    struct ip_header *l3_hdr = dp_packet_l3(&packet);
+    struct tcp_header *th = dp_packet_l4(&packet);
+    size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4;
+    size_t ip_hdr_len = (IP_IHL(l3_hdr->ip_ihl_ver) * 4);
+    size_t tcp_len = ntohs(l3_hdr->ip_tot_len) - ip_hdr_len - tcp_hdr_len;
+    /* Move to beginning of TCP payload, where the SIP payload is */
+    char *sip = ((char *) th) + tcp_hdr_len;
+    size_t sip_len = tcp_len;
+    struct ovs_list *out_msgs = NULL;
+
+    out_msgs = sip_parse_tcp(sip, sip_len);
+
+    ovs_assert(ovs_list_size(out_msgs) == 2);
+
+    struct sip_msg *out_msg;
+    struct sip_msg *next;
+    LIST_FOR_EACH_SAFE (out_msg, next, node, out_msgs) {
+        struct sip_strt_ln *strt_ln = out_msg->strt_ln;
+        sip_len = out_msg->bdy_len;
+
+        ovs_assert(strt_ln != NULL);
+        ovs_assert(strt_ln->type == REQUEST_LINE);
+        ovs_assert(strt_ln->reqs_ln->mthd == INVITE);
+        ovs_assert(sip_len == strlen(valid_sdp));
+        free_sip_msg(out_msg);
+    }
+    free(out_msgs);
+    dp_packet_uninit(&packet);
+}
+
+static void
+test_handle_single_tcp_incomplete_sip(void)
+{
+    uint32_t stub[128 / 4];
+    struct dp_packet packet;
+
+    dp_packet_use_stub(&packet, stub, sizeof stub);
+    dp_packet_clear(&packet);
+
+    compose_dummy_tcp(&packet, DUMMY_ETH_ADDR, DUMMY_ETH_ADDR,
+                      htonl(DUMMY_IP_ADDR),htonl(DUMMY_IP_ADDR),
+                      htons(IPPORT_SIP), htons(IPPORT_SIP),
+                      strlen(sip_invite_with_sdp));
+    dp_packet_put(&packet, sip_invite_with_sdp, strlen(sip_invite_with_sdp));
+
+    struct ip_header *l3_hdr = dp_packet_l3(&packet);
+    struct tcp_header *th = dp_packet_l4(&packet);
+    size_t tcp_hdr_len = TCP_OFFSET(th->tcp_ctl) * 4;
+    size_t ip_hdr_len = (IP_IHL(l3_hdr->ip_ihl_ver) * 4);
+    size_t tcp_len = ntohs(l3_hdr->ip_tot_len) - ip_hdr_len - tcp_hdr_len;
+    /* Move to beginning of TCP payload, where the SIP payload is */
+    char *sip = ((char *) th) + tcp_hdr_len;
+    size_t sip_len = tcp_len;
+    struct ovs_list *out_msgs = NULL;
+
+    out_msgs = sip_parse_tcp(sip, sip_len);
+
+    ovs_assert(ovs_list_size(out_msgs) == 0);
+
+    free(out_msgs);
+    dp_packet_uninit(&packet);
+}
+
+static void
+run_test(void (*function)(void))
+{
+    function();
+    num_tests++;
+    printf(".");
+}
+
+static void
+test_sip_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    run_test(test_valid_sdp);
+    run_test(test_invalid_sdp);
+    run_test(test_parse_strt_ln);
+    run_test(test_parse_strt_ln_missing_cr);
+    run_test(test_handle_single_tcp_single_sip);
+    run_test(test_handle_single_tcp_multiple_sip);
+    run_test(test_handle_single_tcp_incomplete_sip);
+
+    printf("executed %d tests\n", num_tests);
+}
+OVSTEST_REGISTER("test-sip", test_sip_main);
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 15c385e2c..05297e7a7 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -71,6 +71,7 @@  m4_include([tests/rstp.at])
 m4_include([tests/vlog.at])
 m4_include([tests/vtep-ctl.at])
 m4_include([tests/auto-attach.at])
+m4_include([tests/conntrack-sip.at])
 m4_include([tests/ovn.at])
 m4_include([tests/ovn-northd.at])
 m4_include([tests/ovn-nbctl.at])