diff mbox series

[ovs-dev,RFC,v2,6/6] Conntrack: Add UDP support for SIP.

Message ID 20180212230829.31624-7-tiagolam@gmail.com
State RFC
Headers show
Series Initial support for new SIP Alg. | expand

Commit Message

Tiago Lam Feb. 12, 2018, 11:08 p.m. UTC
The network protocol was already being verified in handle_sip in order
to check the transport was indeed TCP. This commit adds support for UDP
by checking the network protocol, and in case of UDP, a new function,
handle_sip_udp, is called to handle the UDP traffic. This function also
takes into account the framing of the SIP message under UDP.

An end-to-end test has been added to system-traffic.at to cover the UDP
transport. Similar to the TCP case, it also sets up two NS', NS1 and
NS2, sets up the expected flows and then verifies that the traffic was
handled correctly. More tests have also been added to test-sip.c to
validate the correct handling of the UDP framing (if a "Content-Length"
header exists then it must be taken into account).

Signed-off-by: Tiago Lam <tiagolam@gmail.com>
---
 lib/conntrack-sip.c     |  94 +++++++++++++++++++++++++++-
 lib/conntrack-sip.h     |   2 +
 lib/conntrack.c         |   4 ++
 tests/system-traffic.at |  71 +++++++++++++++++++++
 tests/test-sip.c        | 160 +++++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 328 insertions(+), 3 deletions(-)
diff mbox series

Patch

diff --git a/lib/conntrack-sip.c b/lib/conntrack-sip.c
index c4ae8dc50..129ee5e7f 100644
--- a/lib/conntrack-sip.c
+++ b/lib/conntrack-sip.c
@@ -777,6 +777,95 @@  sip_parse_tcp_msg(char *sip, size_t sip_len) {
     return out_msg;
 }
 
+/* Same as 'sip_parse_tcp' function  above, but for SIP messages transported
+ * over UDP. Given a pointer to the beginning of a SIP message that has been
+ * transmitted over UDP, parses the SIP message. In case the message is not
+ * well framed or considered invalid, NULL is returned.
+ *
+ * 'sip' should point to the beginning of the SIP message.
+ * 'sip_len' the size of the whole SIP message.
+ *
+ * Returns a 'sip_msg' struct, which the caller is responsible for freeing.
+ * The returned 'sip_msg' has pointers to the parsed message-line, message-
+ * header, and message-body, as well as appropriate lengths set */
+struct sip_msg *
+sip_parse_udp(char *sip, size_t sip_len) {
+    size_t orig_len = sip_len;
+    size_t cur_len = orig_len;
+
+    struct sip_strt_ln *strt_ln = sip_parse_strt_ln(sip, sip_len);
+    if (strt_ln == NULL) {
+        return NULL;
+    }
+
+    /* Pointer to the beginning of SIP message-header */
+    char *msg_hdr = sip_msg_hdr(sip, &sip_len);
+
+    size_t vlen;
+    int cont_len;
+    char *val = sip_get_hdr_val(msg_hdr, sip_len, SIPH_CONT_LEN, &vlen);
+    if (val == NULL) {
+        cont_len = 0;
+    } else {
+        cont_len = sip_hdr_to_uint(val, vlen);
+        if (cont_len < 0) {
+            return NULL;
+        }
+    }
+
+    /* Pointer to the beginning of SIP message-body */
+    char *msg_bdy = sip_msg_bdy(msg_hdr, &sip_len);
+    if (msg_bdy == NULL) {
+        return NULL;
+    }
+
+    cur_len -= sip_len;
+
+    size_t miss_len;
+    if (cont_len == 0) {
+        miss_len = orig_len - cur_len;
+    } else {
+        /* If a "Content-Length" header exists "the message body is assumed to
+         * contain that many bytes" ("18.3 Framing", rfc3261) */
+        size_t exp_len = cur_len + cont_len;
+        if (exp_len > orig_len) {
+            /* Error: "Content-Length" points to more data than available */
+            return NULL;
+        } else {
+            /* rfc3261, additional bytes in the transport packet beyond th end
+             * of the body MUST be discarded */
+            miss_len = exp_len - cur_len;
+        }
+    }
+
+    /* At most, UDP transports a single SIP message */
+    struct sip_msg *out_msg = xmalloc(sizeof(*out_msg));
+    out_msg->strt_ln = strt_ln;
+    out_msg->msg_hdr = msg_hdr;
+    out_msg->msg_bdy = msg_bdy;
+    out_msg->bdy_len = miss_len;
+
+    return out_msg;
+}
+
+static void
+handle_sip_udp(struct conntrack *ct, struct dp_packet *pkt,
+               const struct conn *conn, long long now) {
+    struct udp_header *uh = dp_packet_l4(pkt);
+    size_t udp_len = ntohs(uh->udp_len) - UDP_HEADER_LEN;
+    char *udp_hdr = (char *) uh;
+
+    /* Move to beginning of UDP payload, where the SIP payload is */
+    char *sip = udp_hdr + UDP_HEADER_LEN;
+    size_t sip_len = udp_len;
+
+    struct sip_msg *out_msg = sip_parse_udp(sip, sip_len);
+
+    sip_process_msg(ct, pkt, conn, now, out_msg);
+
+    free(out_msg);
+}
+
 /* Parses SIP messages over TCP. It relies on the "Content-Length" header to
  * know the full length of each SIP message. It returns a list of sip_msg
  * structs, with pointers to each parsed message-line, message-header, and
@@ -842,8 +931,6 @@  handle_sip_tcp(struct conntrack *ct, struct dp_packet *pkt,
 
 /* XXX Only handles IPv4 for now, must include IPv6 in the future */
 /* XXX NAT support hasn't been properly tested yet */
-/* XXX No support for SIP over UDP either (not tested at least), which might
- * be required in the future */
 void
 handle_sip(struct conntrack *ct,
            const struct conn_lookup_ctx *ctx OVS_UNUSED,
@@ -861,6 +948,9 @@  handle_sip(struct conntrack *ct,
     if (conn->key.nw_proto == IPPROTO_TCP) {
         handle_sip_tcp(ct, pkt, conn, ctx->reply, now);
         return;
+    } else if (conn->key.nw_proto == IPPROTO_UDP) {
+        handle_sip_udp(ct, pkt, conn, now);
+        return;
     } else {
         /* Unrecognised transport for a SIP message */
         pkt->md.ct_state |= CS_TRACKED | CS_INVALID;
diff --git a/lib/conntrack-sip.h b/lib/conntrack-sip.h
index 755ded565..ce284f443 100644
--- a/lib/conntrack-sip.h
+++ b/lib/conntrack-sip.h
@@ -128,6 +128,8 @@  sip_process_msgs(struct conntrack *ct, struct dp_packet *pkt,
                  struct ovs_list *out_msgs);
 void
 free_sip_msg(struct sip_msg *out_msg);
+struct sip_msg *
+sip_parse_udp(char *sip, size_t sip_len);
 struct ovs_list *
 sip_parse_tcp(char *sip, size_t sip_len);
 void
diff --git a/lib/conntrack.c b/lib/conntrack.c
index 9387b0165..31ca5b39b 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -493,8 +493,12 @@  get_alg_ctl_type(const struct dp_packet *pkt, ovs_be16 tp_src, ovs_be16 tp_dst,
     } else if (ip_proto == IPPROTO_TCP &&
                (th->tcp_src == sip_src_port || th->tcp_dst == sip_dst_port)) {
         return CT_ALG_CTL_SIP;
+    } else if (ip_proto == IPPROTO_UDP &&
+               (uh->udp_src == sip_src_port || uh->udp_dst == sip_dst_port)) {
+        return CT_ALG_CTL_SIP;
     }
 
+
     return CT_ALG_CTL_NONE;
 }
 
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index e8dba721a..0445f67f8 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -2941,6 +2941,77 @@  tcp,orig=(src=10.0.2.10,dst=10.0.1.10,sport=<cleared>,dport=<cleared>),reply=(sr
 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 UDP over
+dnl IPv4 and no NAT is performed.
+AT_SETUP([conntrack - SIP over UDP 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,udp,action=ct(alg=sip,commit),1
+table=0,priority=100,in_port=1,udp,action=ct(table=1)
+table=0,priority=50,in_port=1,udp,action=ct(table=2)
+table=0,priority=50,in_port=2,udp,action=ct(table=2)
+dnl
+dnl Table 1
+dnl
+dnl Allow new TCPv4 TCP connections.
+table=1,in_port=1,udp,ct_state=+trk+est,action=2
+table=1,in_port=1,udp,ct_state=+trk+rel,action=2
+dnl
+dnl Table 2
+dnl
+dnl Allow RTP (UDP) connections through.
+table=2,in_port=2,udp,ct_state=+rel,action=1
+table=2,in_port=1,udp,ct_state=+rel,action=2
+])
+
+AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
+
+NETNS_DAEMONIZE([at_ns0], [sipp -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 -rp 1s -m 1 -d 23000 -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
+udp,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>),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
index 602f52671..78249379e 100644
--- a/tests/test-sip.c
+++ b/tests/test-sip.c
@@ -78,6 +78,30 @@  static char *sip_invite_missing_cr =
     "Content-Length:   0\r\n"
     "\r\n";
 
+static char *sip_invite_no_cont_length =
+    "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"
+    "\r\n";
+
+static char *sip_invite_big_cont_length =
+    "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:   500\r\n"
+    "\r\n";
+
 static void
 test_valid_sdp(void)
 {
@@ -165,7 +189,138 @@  compose_dummy_tcp(struct dp_packet *pkt, const struct eth_addr eth_src,
     return th + TCP_HEADER_LEN;
 }
 
-/* Test sip_parse_tcp function against multiple cases, including:
+static void *
+compose_dummy_udp(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 udp_header *uh;
+
+    eth_compose(pkt, eth_dst, eth_src, ETH_TYPE_IP, IP_HEADER_LEN);
+    uh = compose_dummy_ipv4(pkt, IPPROTO_UDP, ipv4_src, ipv4_dst, 0, 1,
+                            UDP_HEADER_LEN, size);
+
+    packet_set_udp_port(pkt, port_src, port_dst);
+    uh->udp_len = htons(UDP_HEADER_LEN + size);
+
+    return uh + UDP_HEADER_LEN;
+}
+
+/* Test sip_parse_udp function against multiple cases, including:
+ * - A SIP message with the "Content-Length" header set, checking if it is
+ *   bigger than the transport packet or not;
+ * - A SIP message without the "Content-Length" set, which means teh whole UDP
+ *   payload should be taken into account. */
+
+static void
+test_handle_udp_no_content_length(void)
+{
+    uint32_t stub[128 / 4];
+    struct dp_packet packet;
+
+    dp_packet_use_stub(&packet, stub, sizeof stub);
+    dp_packet_clear(&packet);
+
+    compose_dummy_udp(&packet, DUMMY_ETH_ADDR, DUMMY_ETH_ADDR,
+                      htonl(DUMMY_IP_ADDR),htonl(DUMMY_IP_ADDR),
+                      htons(IPPORT_SIP), htons(IPPORT_SIP),
+                      strlen(sip_invite_no_cont_length) + strlen(valid_sdp));
+    /* Compose an SIP message. */
+    dp_packet_put(&packet, sip_invite_no_cont_length,
+                  strlen(sip_invite_no_cont_length));
+    dp_packet_put(&packet, valid_sdp, strlen(valid_sdp));
+
+    struct udp_header *uh = dp_packet_l4(&packet);
+    size_t udp_len = ntohs(uh->udp_len) - UDP_HEADER_LEN;
+    char *udp_hdr = (char *) uh;
+    /* Move to beginning of UDP payload, where the SIP payload is */
+    char *sip = udp_hdr + UDP_HEADER_LEN;
+    size_t sip_len = udp_len;
+
+    struct sip_msg *out_msg = sip_parse_udp(sip, sip_len);
+
+    ovs_assert(out_msg != NULL);
+
+    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));
+}
+
+static void
+test_handle_udp_with_short_content_length(void)
+{
+    uint32_t stub[128 / 4];
+    struct dp_packet packet;
+
+    dp_packet_use_stub(&packet, stub, sizeof stub);
+    dp_packet_clear(&packet);
+
+    compose_dummy_udp(&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));
+    /* Add more data available than needed */
+    dp_packet_put_zeros(&packet, 100);
+
+    struct udp_header *uh = dp_packet_l4(&packet);
+    size_t udp_len = ntohs(uh->udp_len) - UDP_HEADER_LEN;
+    char *udp_hdr = (char *) uh;
+    /* Move to beginning of UDP payload, where the SIP payload is */
+    char *sip = udp_hdr + UDP_HEADER_LEN;
+    size_t sip_len = udp_len;
+
+    struct sip_msg *out_msg = sip_parse_udp(sip, sip_len);
+
+    ovs_assert(out_msg != NULL);
+
+    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));
+}
+
+static void
+test_handle_udp_with_big_content_length(void)
+{
+    uint32_t stub[128 / 4];
+    struct dp_packet packet;
+
+    dp_packet_use_stub(&packet, stub, sizeof stub);
+    dp_packet_clear(&packet);
+
+    compose_dummy_udp(&packet, DUMMY_ETH_ADDR, DUMMY_ETH_ADDR,
+                      htonl(DUMMY_IP_ADDR),htonl(DUMMY_IP_ADDR),
+                      htons(IPPORT_SIP), htons(IPPORT_SIP),
+                      strlen(sip_invite_big_cont_length) + strlen(valid_sdp));
+    /* Compose an SIP message. */
+    dp_packet_put(&packet, sip_invite_big_cont_length,
+                  strlen(sip_invite_big_cont_length));
+    dp_packet_put(&packet, valid_sdp, strlen(valid_sdp));
+
+    struct udp_header *uh = dp_packet_l4(&packet);
+    size_t udp_len = ntohs(uh->udp_len) - UDP_HEADER_LEN;
+    char *udp_hdr = (char *) uh;
+    /* Move to beginning of UDP payload, where the SIP payload is */
+    char *sip = udp_hdr + UDP_HEADER_LEN;
+    size_t sip_len = udp_len;
+
+    struct sip_msg *out_msg = sip_parse_udp(sip, sip_len);
+
+    ovs_assert(out_msg == NULL);
+}
+
+/* Test handle_sip_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
@@ -321,6 +476,9 @@  test_sip_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
     run_test(test_handle_single_tcp_single_sip);
     run_test(test_handle_single_tcp_multiple_sip);
     run_test(test_handle_single_tcp_incomplete_sip);
+    run_test(test_handle_udp_no_content_length);
+    run_test(test_handle_udp_with_short_content_length);
+    run_test(test_handle_udp_with_big_content_length);
 
     printf("executed %d tests\n", num_tests);
 }