[ovs-dev,RFC,v2,4/6] Conntrack: Add initial support for new SIP Alg.

Message ID 20180212230829.31624-5-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.
Handle_sip, in conntrack-sip.c, is the new function handler that handles
the SIP messages received in the userspace conntrack (detected in
process_one).  It parses the SIP messages (and SDPs) using the newly
introduced SIP API (conntrack-sip.{c,h}) and accordingly creates the
expectations necessary to let the RTP traffic through.  To identify the
new SIP Alg, a new IPPROTO_SIP has also been introduced, which maps to
port 5060, the SIP assigned port.

As of now, this preliminary support mainly handles the following happy
case, exchanged between a UAC and UAS:
- UAC ----> UAS | SIP INVITE (SDP offer);
- UAC <---- UAS | SIP 200    (SDP answer);
- UAC ----> UAS | SIP ACK;
- UAC ----> UAS | SIP BYE;
- UAC <---- UAS | SIP 200;
It also assumes the SIP messages are coming over TCP and IPv4 (although
SIP's main usage is over UDP).

This integration has been tested manually by setting the following
flows, which let's tcp traffic from port 1 to flow to port 0, but only
+ESTABLISHED or +RELATED traffic on the opposite direction (from port 0
to port 1). It also allows UDP +RELATED to pass through in both
directions (which is needed for the RTP packets):
- 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=1,tcp,action=ct(alg=sip,commit),0
- table=0,priority=100,in_port=0,tcp,action=ct(table=1)
- table=0,priority=100,in_port=1,udp,action=ct(table=2)
- table=0,priority=100,in_port=0,udp,action=ct(table=2)
- table=1,in_port=0,tcp,ct_state=+trk+est,action=1
- table=1,in_port=0,tcp,ct_state=+trk+rel,action=1
- table=2,in_port=0,udp,ct_state=+rel,action=1
- table=2,in_port=1,udp,ct_state=+rel,action=0

It is also worth noting that although basic TCP framing support has been
added, it however will fail to work when a single SIP message might span
multiple TCP packets.

Signed-off-by: Tiago Lam <tiagolam@gmail.com>
---
 include/openvswitch/ofp-actions.h |   4 +
 lib/conntrack-private.h           |  30 +++
 lib/conntrack-sip.c               | 518 +++++++++++++++++++++++++++++++++-----
 lib/conntrack-sip.h               |  76 +++++-
 lib/conntrack.c                   |  78 +++---
 lib/ofp-parse.c                   |   5 +
 ofproto/ofproto-dpif-xlate.c      |   3 +
 7 files changed, 609 insertions(+), 105 deletions(-)

Patch

diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h
index cba027b1d..558861262 100644
--- a/include/openvswitch/ofp-actions.h
+++ b/include/openvswitch/ofp-actions.h
@@ -606,6 +606,10 @@  enum nx_conntrack_flags {
 #define IPPORT_TFTP  69
 #endif
 
+#if !defined(IPPORT_SIP)
+#define IPPORT_SIP  5060
+#endif
+
 /* OFPACT_CT.
  *
  * Used for NXAST_CT. */
diff --git a/lib/conntrack-private.h b/lib/conntrack-private.h
index bf1d0773b..68afd3f3d 100644
--- a/lib/conntrack-private.h
+++ b/lib/conntrack-private.h
@@ -57,6 +57,23 @@  struct conn_key {
     uint8_t nw_proto;
 };
 
+struct conn_lookup_ctx {
+    struct conn_key key;
+    struct conn *conn;
+    uint32_t hash;
+    bool reply;
+    bool icmp_related;
+};
+
+enum ftp_ctl_pkt {
+    /* Control packets with address and/or port specifiers. */
+    CT_FTP_CTL_INTEREST,
+    /* Control packets without address and/or port specifiers. */
+    CT_FTP_CTL_OTHER,
+    CT_FTP_CTL_INVALID,
+};
+
+
 struct nat_conn_key_node {
     struct hmap_node node;
     struct conn_key key;
@@ -107,6 +124,8 @@  struct conn {
     uint8_t seq_skew_dir;
     /* True if alg data connection. */
     uint8_t alg_related;
+    /* SIP specific data */
+    struct sip_alg_data *sip_alg;
 };
 
 enum ct_update_res {
@@ -139,6 +158,17 @@  extern struct ct_l4_proto ct_proto_icmp6;
 
 extern long long ct_timeout_val[];
 
+void
+expectation_create_outband(struct conntrack *ct, struct ct_addr src_addr,
+                           struct ct_addr dst_addr, ovs_be16 dst_port,
+                           const struct conn *master_conn, bool reply,
+                           bool src_ip_wc, bool skip_nat, uint8_t nw_proto);
+
+uint32_t conn_key_hash(const struct conn_key *, uint32_t basis);
+struct conn *
+conn_lookup(struct conntrack *ct, const struct conn_key *key, long long now);
+unsigned hash_to_bucket(uint32_t hash);
+
 static inline void
 conn_init_expiration(struct conntrack_bucket *ctb, struct conn *conn,
                         enum ct_timeout tm, long long now)
diff --git a/lib/conntrack-sip.c b/lib/conntrack-sip.c
index 7239bdad6..c4ae8dc50 100644
--- a/lib/conntrack-sip.c
+++ b/lib/conntrack-sip.c
@@ -1,5 +1,5 @@ 
 /*
- * Copyright (c) 2017 Nicira, Inc.
+ * Copyright (c) 2018 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -54,34 +54,26 @@  enum sip_mthd sip_mthd_to_enum(char *mthd) {
  * Returns false to anything that falls outside of the ranges above, and true
  * otherwise. */
 bool is_valid_status(uint32_t status) {
-    if ((status >= 100 && status < 200) ||
-        (status >= 200 && status < 300) ||
-        (status >= 300 && status < 400) ||
-        (status >= 400 && status < 500) ||
-        (status >= 500 && status < 600) ||
-        (status >= 600 && status < 700)) {
-        return true;
-    }
-
-    return false;
+    return status >= 100 && status < 700;
 }
 
-/* Find and validate the beginning of the CRLF ending, returning a pointer to
- * the found CR, or NULL otherwise. */
+/* Find and validate the beginning of the CRLF (or LF only) ending, returning a
+ * pointer to the found CR (or LF), or NULL otherwise. */
 char * sip_ln_find_crlf(char *sip, size_t len) {
     if (sip == NULL) {
         return NULL;
     }
 
     char *cr = memchr(sip, '\r', len);
-    if (cr == NULL) {
-        return NULL;
-    }
     char *lf = memchr(sip, '\n', len);
     if (lf == NULL) {
         return NULL;
     }
 
+    if (cr == NULL) {
+        return lf;
+    }
+
     if (lf - cr != 1) {
         return NULL;
     }
@@ -91,27 +83,30 @@  char * sip_ln_find_crlf(char *sip, size_t len) {
 
 /* Returns the size of the line until the next CRLF ending (not inclusive). If
  * CRLF is not found it returns -1. */
-size_t sip_ln_len(char *sip, size_t len) {
-    char *cr = sip_ln_find_crlf(sip, len);
-    if (cr == NULL) {
-        return -1;
+int sip_ln_len(char *sip, size_t len) {
+    char *chr = sip_ln_find_crlf(sip, len);
+    if (chr == NULL) {
+        return -EINVAL;
     }
 
-    return cr - sip;
+    return chr - sip;
 }
 
-/* Ignores the next CRLF ending and moves to CRLF + 1. After returning, 'sip'
- * is modified to point to the next line after CRLF (CRLF + 1), and 'len' has
- * been updated accordingly. If CRLF is not found, 'sip' and 'len' are
- * untouched */
-void sip_ln_skip(char **sip, size_t *len) {
-    char *cr = sip_ln_find_crlf(*sip, *len);
-    if (cr == NULL) {
-        return;
+/* Skips the next CRLF ending and moves to CRLF + 1. After returning, 'sip' is
+ * modified to point to the next line after CRLF (CRLF + 1), and 'len' has been
+ * updated accordingly. If CRLF is not found, 'sip' and 'len' are untouched.
+ * Returns false if the line doesn't end with a CRLF sequence, and true
+ * otherwise. In the former case, the line isn't skipped */
+bool sip_ln_skip(char **sip, size_t *len) {
+    char *chr = sip_ln_find_crlf(*sip, *len);
+    if (chr == NULL) {
+        return false;
     }
-    size_t sz = cr - *sip;
-    *sip += sz + 2;
-    *len -= sz + 2;
+    size_t sz = chr - *sip;
+    *sip += sz + sip_len_to_end(*chr) + 1;
+    *len -= sz + sip_len_to_end(*chr) + 1;
+
+    return true;
 }
 
 /* Iterate through each word in the line at 'sip', where each word is divided
@@ -129,9 +124,8 @@  bool sip_ln_cpy_nxt_word(char **sip, size_t *len, char **dst) {
 
     size_t wlen = s - *sip;
     if (dst != NULL) {
-        /* Store found word int the pointed to 'dst' */
-        *dst = xmalloc(sizeof(char) * (wlen + 1));
-        ovs_strzcpy(*dst, *sip, wlen + 1);
+        /* Store found word in the pointed to 'dst' */
+        *dst = xmemdup0(*sip, wlen);
     }
 
     /* Move 'sip' to the beginning of next word */
@@ -141,6 +135,79 @@  bool sip_ln_cpy_nxt_word(char **sip, size_t *len, char **dst) {
     return true;
 }
 
+/* Find in 'sip', of size 'len', the SIP header in 'hdr'.
+ *
+ * 'sip' points to the beginning of the header of the SIP message.
+ * 'len' the size of the SIP header it points.
+ * 'hdr' points to the SIP header to be searched for in 'sip'.
+ *
+ * Returns a char* pointer to the value associated with the found header
+ * ('hdr'). If not found, NULL is returned.
+ *
+ * Example of a SIP header and value - field-name: field-value */
+char * sip_get_hdr_val(char *sip, size_t len, char *hdr, size_t *vlen) {
+    while (len > 0 && strncasecmp(sip, hdr, strlen(hdr))) {
+        /* Double CRLF detected */
+        if (sip_ln_len(sip, len) == 0) {
+            return NULL;
+        }
+        sip_ln_skip(&sip, &len);
+    }
+
+    if (len <= 0) {
+        return NULL;
+    }
+
+    len = sip_ln_len(sip, len);
+    if (len <= 0) {
+        return NULL;
+    }
+
+    /* Find the beginning of value. There can be multiple single spaces between
+     * "field-name" and the colon, and the colon and the "field-value". */
+
+    char *c = memchr(sip, ':', len);
+    if (c == NULL) {
+        return NULL;
+    }
+
+    len -= c - sip;
+    if (len == 0) {
+        return NULL;
+    }
+
+    while (len > 0) {
+        c += 1;
+        len -= 1;
+
+        if (*c != ' ') {
+            break;
+        }
+    }
+
+    *vlen = len;
+    return c;
+}
+
+/* Converts an header value pointed to by 'hdr', of size 'hlen', to an unsigned
+ * int */
+unsigned int sip_hdr_to_uint(char *hdr, size_t hlen) {
+    if (hdr == NULL || hlen == 0) {
+        return -EINVAL;
+    }
+
+    char *s = xmemdup0(hdr, hlen);
+
+    unsigned int val;
+    if (!str_to_uint(s, 10, &val)) {
+        free(s);
+        return -EINVAL;
+    }
+
+    free(s);
+    return val;
+}
+
 /* Parse an 'o' line from the SDP.
  *
  * 'conn' should point to the beginning of the line, after 'o='.
@@ -159,8 +226,7 @@  char * sdp_get_origin(char *orig, size_t len) {
         }
     }
 
-    ip_addr = xmalloc(sizeof(char) * (len + 1));
-    ovs_strzcpy(ip_addr, o_ln, len + 1);
+    ip_addr = xmemdup0(o_ln, len);
 
     return ip_addr;
 }
@@ -183,8 +249,7 @@  char * sdp_get_conn(char *conn, size_t len) {
         }
     }
 
-    ip_addr = xmalloc(sizeof(char) * (len + 1));
-    ovs_strzcpy(ip_addr, c_ln, len + 1);
+    ip_addr = xmemdup0(c_ln, len);
 
     return ip_addr;
 }
@@ -227,7 +292,7 @@  struct sip_sdp * sip_parse_sdp(char *sdp, size_t len) {
     uint32_t hport;
     uint32_t haddr;
     struct sip_sdp *sip_sdp;
-    size_t ln_len;
+    int ln_len;
 
     if (sip == NULL) {
        return NULL;
@@ -235,10 +300,11 @@  struct sip_sdp * sip_parse_sdp(char *sdp, size_t len) {
 
     sip_sdp = xmalloc(sizeof(struct sip_sdp));
 
-    for (int i = 0; i < len; i++) {
+    while (len > 0) {
         ln_len = sip_ln_len(sip, len);
-        if (ln_len == -1) {
-            /* Message is invalid, most likely no CRLF found */
+        /* Message is invalid if size < 3, meaning the line wouldn't have the
+         * format: "x=y" */
+        if (ln_len < 3) {
             goto error;
         }
 
@@ -249,7 +315,7 @@  struct sip_sdp * sip_parse_sdp(char *sdp, size_t len) {
                 free(addr_s);
                 goto error;
             }
-            sip_sdp->orig = ntohl(haddr);
+            sip_sdp->orig = haddr;
             free(addr_s);
             break;
         case 'c':
@@ -258,7 +324,7 @@  struct sip_sdp * sip_parse_sdp(char *sdp, size_t len) {
                 free(addr_s);
                 goto error;
             }
-            sip_sdp->conn = ntohl(haddr);
+            sip_sdp->conn = haddr;
             free(addr_s);
             break;
         case 'm':
@@ -293,25 +359,18 @@  int sip_skip_to_body(char **sip_hdr, size_t *len) {
         return -EINVAL;
     }
 
-    char *cr;
-    int sz;
     while (*len >= 2) {
         /* Detect the lone CRLF which divides the SIP message header from the
-         * the SIP message body */
-        if ((*sip_hdr)[0] == '\r' && (*sip_hdr)[1] == '\n') {
+         * SIP message body */
+        if (sip_ln_len(*sip_hdr, *len) == 0) {
             /* Skip 2 bytes for CRLF */
-            *sip_hdr += 2;
-            *len -= 2;
+            sip_ln_skip(sip_hdr, len);
             return 0;
         }
 
-        cr = sip_ln_find_crlf(*sip_hdr, *len);
-        if (cr == NULL) {
+        if (!sip_ln_skip(sip_hdr, len)) {
             return -EINVAL;
         }
-        sz = cr - *sip_hdr;
-        *sip_hdr += sz + 2;
-        *len -= sz + 2;
     }
 
     return -EINVAL;
@@ -339,12 +398,12 @@  void free_strt_ln(struct sip_strt_ln *strt_ln) {
 struct sip_strt_ln *
 sip_parse_strt_ln(char *strt_ln_str, size_t len) {
     struct sip_strt_ln *strt_ln;
-    size_t ln_len;
+    int ln_len;
 
     /* Calculate the length of the SIP star-line */
     ln_len = sip_ln_len(strt_ln_str, len);
-    if (ln_len == -1) {
-        /* Message is invalid, most likely no CRLF found */
+    if (ln_len < 0) {
+        /* Message is invalid */
         return NULL;
     }
 
@@ -418,8 +477,7 @@  sip_parse_stat_ln(char *start_ln, size_t len) {
         goto error;
     }
 
-    stat_ln->reason = xmalloc(sizeof(char) * (len + 1));
-    ovs_strzcpy(stat_ln->reason, start_ln, len + 1);
+    stat_ln->reason = xmemdup0(start_ln, len);
 
     return stat_ln;
 
@@ -464,8 +522,7 @@  sip_parse_reqs_ln(char *start_ln, size_t len) {
         goto error;
     }
 
-    reqs_ln->ver = xmalloc(sizeof(char) * (len + 1));
-    ovs_strzcpy(reqs_ln->ver, start_ln, len + 1);
+    reqs_ln->ver = xmemdup0(start_ln, len);
 
     return reqs_ln;
 
@@ -475,3 +532,338 @@  error:
 
     return NULL;
 }
+
+///
+
+static void
+free_sip_state(struct sip_state *state) {
+    if (state == NULL) {
+        return;
+    }
+
+    free(state->peer[0].sdp);
+    free(state->peer[1].sdp);
+    free(state);
+}
+
+static struct sip_state *
+new_sip_state(void) {
+    struct sip_state *sip_state = xmalloc(sizeof(*sip_state));
+
+    sip_state->peer[0].sdp = NULL;
+    sip_state->peer[0].was_bye = false;
+
+    sip_state->peer[1].sdp = NULL;
+    sip_state->peer[1].was_bye = false;
+
+    return sip_state;
+}
+
+void
+free_sip_alg(struct sip_alg_data *sip_alg) {
+    if (sip_alg == NULL) {
+        return;
+    }
+
+    free_sip_state(sip_alg->state);
+    free(sip_alg);
+}
+
+static void
+init_sip_alg(struct conntrack *ct, const struct conn_key *key, long long now)
+{
+    uint32_t hash = conn_key_hash(key, ct->hash_basis);
+    unsigned bucket = hash_to_bucket(hash);
+    ct_lock_lock(&ct->buckets[bucket].lock);
+    struct conn *conn = conn_lookup(ct, key, now);
+    conn->sip_alg = xmalloc(sizeof(*conn->sip_alg));
+    conn->sip_alg->state = new_sip_state();
+    ct_lock_unlock(&ct->buckets[bucket].lock);
+}
+
+static void
+sip_set_state(struct conntrack *ct, const struct conn_key *key, long long now,
+              struct sip_sdp *sdp)
+{
+    uint32_t hash = conn_key_hash(key, ct->hash_basis);
+    unsigned bucket = hash_to_bucket(hash);
+    ct_lock_lock(&ct->buckets[bucket].lock);
+    struct conn *conn = conn_lookup(ct, key, now);
+    conn->sip_alg->state->peer[0].sdp = sdp;
+    ct_lock_unlock(&ct->buckets[bucket].lock);
+}
+
+/* Setups two expectations for the RTP connections, one in each direction.
+ * Example: If an SDP answer announces it is listenning at IP address 10.0.1.
+ * 10:6000 and an the SDP offer at 10.0.2.10:6000, then:
+ * - Sets a UDP expectation going from 10.0.1.10:0 to 10.0.2.10:6000:
+ * - Sets another UDP expectation going from 10.0.2.10:0 to 10.0.1.10:6000;
+ *
+ * Thus, this allows traffic from a different port than the one the host is
+ * listenning to (the one it announced in the SDP).
+ *
+ * XXX According to rfc4961, hosts might send RTP packets from a different IP
+ * address and / or port (i.e. asymmetrically). Since this function supports
+ * the sending of packets from a different port, consider supporting the
+ * sending from a different IP address.
+ */
+static void
+sip_expectations_setup(struct conntrack *ct,
+                       const struct sip_sdp *offer_sdp,
+                       const struct sip_sdp *answer_sdp,
+                       const struct conn *conn) {
+    ovs_be32 offer_addr = offer_sdp->conn;
+    ovs_be16 offer_port = htons(offer_sdp->port);
+    ovs_be32 answer_addr = answer_sdp->conn;
+    ovs_be16 answer_port = htons(answer_sdp->port);
+
+    /* Set src address (from SDP's 'c') */
+    struct ct_addr src_addr;
+    memset(&src_addr, 0, sizeof src_addr);
+    src_addr.ipv4_aligned = answer_addr;
+    /* Set dst address (from SDP's 'c') */
+    struct ct_addr dst_addr;
+    memset(&dst_addr, 0, sizeof dst_addr);
+    dst_addr.ipv4_aligned = offer_addr;
+
+    /* Set expectation from SDP answer -> SDP offer */
+    expectation_create_outband(ct, src_addr, dst_addr, offer_port, conn,
+                               false, true, false, IPPROTO_UDP);
+
+    /* Set expectation from SDP offer -> SDP answer */
+    expectation_create_outband(ct, dst_addr, src_addr, answer_port, conn,
+                               false, true, false, IPPROTO_UDP);
+}
+
+void
+sip_process_msg(struct conntrack *ct, struct dp_packet *pkt,
+                const struct conn *conn, long long now,
+                struct sip_msg *out_msg) {
+    struct sip_strt_ln *strt_ln = out_msg->strt_ln;
+    char *msg_hdr = out_msg->msg_hdr;
+    char *msg_bdy = out_msg->msg_bdy;
+    size_t sip_len = out_msg->bdy_len;
+
+    if (strt_ln == NULL || msg_hdr == NULL || msg_bdy == NULL) {
+        /* Couldn't properly parse the SIP message */
+        pkt->md.ct_state |= CS_TRACKED | CS_INVALID;
+        return;
+    }
+
+    if (strt_ln->type == REQUEST_LINE) {
+        enum sip_mthd mthd = strt_ln->reqs_ln->mthd;
+        if (mthd == INVITE) {
+            struct sip_sdp *sdp = sip_parse_sdp(msg_bdy, sip_len);
+            /* We now have the SDP offer, initialise the SIP state */
+            sip_set_state(ct, &conn->key, now, sdp);
+        } else if (mthd == ACK) {
+            /* A SIP ACK may carry an SDP answer, when the offer is carried in
+             *  the 200 OK */
+
+            /* XXX Consider supporting the above with the SDP answer */
+        } else if (mthd == BYE) {
+            /* Once a BYE is received the RTP connnections are torn down from
+             * the UAC side, and then from the UAS side when it sends either
+             * 481 or 2XX response.
+             * Record the state here, deleting the connection, including
+             * expectations, when the response is received */
+            conn->sip_alg->state->peer[0].was_bye = true;
+        }
+    } else {
+        /* XXX In the future we will also need to confirm if this SDP is
+         *     an SDP offer or an SDP answer. */
+        struct sip_stat_ln *stat_ln = strt_ln->stat_ln;
+        if (stat_ln->status != 200) {
+            /* Currently we don't support anything else than 200 OK */
+            return;
+        } else {
+            if (conn->sip_alg->state->peer[0].was_bye) {
+                /* Received a 200 OK for the previous BYE, connection  will be
+                 * deleted */
+                return;
+            }
+
+            /* Check if this SIP reply has an SDP. */
+            struct sip_sdp *sdp = sip_parse_sdp(msg_bdy, sip_len);
+            if (sdp == NULL) {
+                if (conn->sip_alg->state->peer[0].sdp != NULL &&
+                    conn->sip_alg->state->peer[1].sdp == NULL) {
+                    /* No SDP is carried here, but we did expect one since the
+                     * previous message was a SIP INVITE */
+                    pkt->md.ct_state |= CS_TRACKED | CS_INVALID;
+                }
+                return;
+            } else {
+                conn->sip_alg->state->peer[1].sdp = sdp;
+
+                /* Received a SIP 200 OK from peer, we are now able to set up
+                 * the expectations for the data connections, based on the SDP
+                 * offer and answer */
+                sip_expectations_setup(ct, conn->sip_alg->state->peer[0].sdp,
+                                       conn->sip_alg->state->peer[1].sdp,
+                                       conn);
+            }
+        }
+    }
+}
+
+void
+sip_process_msgs(struct conntrack *ct, struct dp_packet *pkt,
+                 const struct conn *conn, long long now,
+                 struct ovs_list *out_msgs) {
+    struct sip_msg *sip_msg, *next;
+    LIST_FOR_EACH_SAFE (sip_msg, next, node, out_msgs) {
+        sip_process_msg(ct, pkt, conn, now, sip_msg);
+    }
+}
+
+void
+free_sip_msg(struct sip_msg *out_msg) {
+    if (out_msg == NULL) {
+        return;
+    }
+
+    free_strt_ln(out_msg->strt_ln);
+    free(out_msg);
+}
+
+/* Given a pointer to the beginning of a SIP message that has been transmitted
+ * over TCP, 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 */
+static struct sip_msg *
+sip_parse_tcp_msg(char *sip, size_t sip_len) {
+    struct sip_strt_ln *strt_ln = sip_parse_strt_ln(sip, sip_len);
+    if (strt_ln == NULL) {
+        return NULL;
+    }
+
+    size_t cur_len = sip_len;
+    size_t orig_len = sip_len;
+
+    char *msg_hdr = sip_msg_hdr(sip, &sip_len);
+
+    /* Over TCP, "Content-Length" tells us the size of the body to retrieve */
+    size_t vlen;
+    char *val = sip_get_hdr_val(msg_hdr, sip_len, SIPH_CONT_LEN, &vlen);
+    int cont_len = sip_hdr_to_uint(val, vlen);
+    if (cont_len < 0) {
+        return NULL;
+    }
+
+    char *msg_bdy = sip_msg_bdy(msg_hdr, &sip_len);
+
+    /* Size of SIP status line + SIP header + SIP body so far */
+    cur_len = (cur_len - sip_len) + cont_len;
+
+    if (cur_len > orig_len) {
+        /* The full SIP message is not available yet */
+        return NULL;
+    }
+
+    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->msg_len = cur_len;
+    out_msg->bdy_len = cont_len;
+
+    return 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
+ * message-body as well as appropriate lengths set. The list will contain as
+ * many messages as full SIP messages found, and in case of none or error it
+ * will be empty. The caller detains the ownership of the list andf must
+ * destroy it.
+ *
+ * XXX The function doesn't comply to SIP messages than span across multiple
+ * TCP packets for now. However, it doesn't discard those packets either and
+ * they are just ignored. As such, this might means those messages might not be
+ * taken into account and cause problems (like not setting up expectations
+ * correctly) */
+struct ovs_list *
+sip_parse_tcp(char *sip, size_t sip_len) {
+    struct sip_msg *out_msg = NULL;
+    struct ovs_list *out_msgs = xmalloc(sizeof(*out_msgs));
+    ovs_list_init(out_msgs);
+    while ((out_msg = sip_parse_tcp_msg(sip, sip_len)) != NULL) {
+        sip += out_msg->msg_len;
+        sip_len -= out_msg->msg_len;
+
+        ovs_list_push_back(out_msgs, &out_msg->node);
+    }
+
+    return out_msgs;
+}
+
+static void
+handle_sip_tcp(struct conntrack *ct, struct dp_packet *pkt,
+               const struct conn *conn, bool reply, long long now) {
+    struct ip_header *l3_hdr = dp_packet_l3(pkt);
+    struct tcp_header *th = dp_packet_l4(pkt);
+    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;
+    if (!tcp_len) {
+        /* Packet of no interest, with no TCP payload */
+        return;
+    }
+
+    /* 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;
+    if (reply) {
+        out_msgs = sip_parse_tcp(sip, sip_len);
+    } else {
+        out_msgs = sip_parse_tcp(sip, sip_len);
+    }
+
+    sip_process_msgs(ct, pkt, conn, now, out_msgs);
+
+    /* Free list and all items inside */
+    struct sip_msg *sip_msg, *next;
+    LIST_FOR_EACH_SAFE (sip_msg, next, node, out_msgs) {
+        free_sip_msg(sip_msg);
+    }
+
+    free(out_msgs);
+}
+
+/* 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,
+           struct dp_packet *pkt,
+           const struct conn *conn_for_expectation,
+           long long now, enum ftp_ctl_pkt ftp_ctl OVS_UNUSED,
+           bool nat OVS_UNUSED)
+{
+    const struct conn *conn = conn_for_expectation;
+
+    if (conn->sip_alg == NULL) {
+        init_sip_alg(ct, &conn->key, now);
+    }
+
+    if (conn->key.nw_proto == IPPROTO_TCP) {
+        handle_sip_tcp(ct, pkt, conn, ctx->reply, now);
+        return;
+    } else {
+        /* Unrecognised transport for a SIP message */
+        pkt->md.ct_state |= CS_TRACKED | CS_INVALID;
+        return;
+    }
+}
diff --git a/lib/conntrack-sip.h b/lib/conntrack-sip.h
index 134f35f3a..755ded565 100644
--- a/lib/conntrack-sip.h
+++ b/lib/conntrack-sip.h
@@ -1,5 +1,5 @@ 
 /*
- * Copyright (c) 2017 Nicira, Inc.
+ * Copyright (c) 2018 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,10 +17,14 @@ 
 #include <sys/types.h>
 
 #include "dp-packet.h"
+#include "conntrack.h"
+#include "conntrack-private.h"
+
+#define SIPH_CONT_LEN "Content-Length"
 
 struct sip_sdp {
-    uint32_t orig;
-    uint32_t conn;
+    ovs_be32 orig;
+    ovs_be32 conn;
     uint16_t port;
 };
 
@@ -32,6 +36,12 @@  enum sip_mthd {
     BYE,
     OPTIONS,
     INVALID,
+    PRACK,
+    UPDATE,
+    INFO,
+    SUBSCRIBE,
+    NOTIFY,
+    PUBLISH,
 };
 
 struct sip_reqs_ln {
@@ -59,15 +69,40 @@  struct sip_strt_ln {
     enum sip_strt_type type;
 };
 
+struct sip_peer {
+    struct sip_sdp *sdp;
+    bool was_bye;
+};
+
+struct sip_state {
+    struct sip_peer peer[2];
+};
+
+struct sip_alg_data {
+    struct sip_state *state;
+};
+
+struct sip_msg {
+    struct sip_strt_ln *strt_ln;
+    char *msg_hdr;
+    char *msg_bdy;
+    size_t bdy_len;
+    size_t msg_len;
+    struct ovs_list node;
+};
+
 enum sip_mthd sip_mthd_to_enum(char *sip_mthd);
 int sip_skip_to_body(char **sip_hdr, size_t *len);
 uint32_t sip_body_len(char *sip_hdr);
 char * sip_ln_find_crlf(char *sip, size_t len);
-size_t sip_ln_len(char *sip, size_t len);
+int sip_ln_len(char *sip, size_t len);
 bool sip_ln_cpy_nxt_word(char **sip, size_t *len, char **dst);
 bool is_valid_status(uint32_t status);
-void sip_ln_skip(char **sdp, size_t *len);
+bool sip_ln_skip(char **sdp, size_t *len);
 
+void free_sip_alg(struct sip_alg_data *sip_alg);
+char * sip_get_hdr_val(char *sip, size_t len, char *hdr, size_t *vlen);
+unsigned int sip_hdr_to_uint(char *hdr, size_t hlen);
 char * sdp_get_origin(char *orig, size_t len);
 char * sdp_get_conn(char *conn, size_t len);
 char * sdp_get_media(char *media, size_t len);
@@ -83,11 +118,38 @@  void free_reqs_ln(struct sip_reqs_ln *reqs_ln);
 struct sip_reqs_ln *
 sip_parse_reqs_ln(char *start_ln, size_t len);
 
+void
+sip_process_msg(struct conntrack *ct, struct dp_packet *pkt,
+                const struct conn *conn, long long now,
+                struct sip_msg *out_msg);
+void
+sip_process_msgs(struct conntrack *ct, struct dp_packet *pkt,
+                 const struct conn *conn, long long now,
+                 struct ovs_list *out_msgs);
+void
+free_sip_msg(struct sip_msg *out_msg);
+struct ovs_list *
+sip_parse_tcp(char *sip, size_t sip_len);
+void
+handle_sip(struct conntrack *ct,
+           const struct conn_lookup_ctx *ctx OVS_UNUSED,
+           struct dp_packet *pkt OVS_UNUSED,
+           const struct conn *conn_for_expectation,
+           long long now, enum ftp_ctl_pkt ftp_ctl OVS_UNUSED,
+           bool nat OVS_UNUSED);
+
+/* Given input characters CR or LF, return the length needed to reach the end
+ * of the line. Otherwise returns 0 */
+static inline size_t
+sip_len_to_end(char chr) {
+    return chr == '\r' ? 1 : 0;
+}
+
 /* Given a pointer to the beginning of the SIP message, return a pointer to
  * the beginning of its message-header.
  * This modifies the passed 'len' to the right size now that the SIP start-line
  * has been discarded */
-static inline void *
+static inline char *
 sip_msg_hdr(char *sip, size_t *len) {
 
     char *msg_hdr = sip;
@@ -101,7 +163,7 @@  sip_msg_hdr(char *sip, size_t *len) {
  * the beginning of its message-body.
  * This modifies the passed 'len' to the right size now that both the SIP start
  * -line and message-header have been discarded */
-static inline void *
+static inline char *
 sip_msg_bdy(char *sip, size_t *len) {
 
     char *msg_bdy = sip;
diff --git a/lib/conntrack.c b/lib/conntrack.c
index 5c91f374b..9387b0165 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -25,6 +25,7 @@ 
 #include "bitmap.h"
 #include "conntrack.h"
 #include "conntrack-private.h"
+#include "conntrack-sip.h"
 #include "coverage.h"
 #include "csum.h"
 #include "ct-dpif.h"
@@ -40,27 +41,12 @@ 
 #include "random.h"
 #include "timeval.h"
 
+
 VLOG_DEFINE_THIS_MODULE(conntrack);
 
 COVERAGE_DEFINE(conntrack_full);
 COVERAGE_DEFINE(conntrack_long_cleanup);
 
-struct conn_lookup_ctx {
-    struct conn_key key;
-    struct conn *conn;
-    uint32_t hash;
-    bool reply;
-    bool icmp_related;
-};
-
-enum ftp_ctl_pkt {
-    /* Control packets with address and/or port specifiers. */
-    CT_FTP_CTL_INTEREST,
-    /* Control packets without address and/or port specifiers. */
-    CT_FTP_CTL_OTHER,
-    CT_FTP_CTL_INVALID,
-};
-
 enum ct_alg_mode {
     CT_FTP_MODE_ACTIVE,
     CT_FTP_MODE_PASSIVE,
@@ -71,15 +57,12 @@  enum ct_alg_ctl_type {
     CT_ALG_CTL_NONE,
     CT_ALG_CTL_FTP,
     CT_ALG_CTL_TFTP,
-    /* SIP is not enabled through Openflow and presently only used as
-     * an example of an alg that allows a wildcard src ip. */
     CT_ALG_CTL_SIP,
 };
 
 static bool conn_key_extract(struct conntrack *, struct dp_packet *,
                              ovs_be16 dl_type, struct conn_lookup_ctx *,
                              uint16_t zone);
-static uint32_t conn_key_hash(const struct conn_key *, uint32_t basis);
 static void conn_key_reverse(struct conn_key *);
 static void conn_key_lookup(struct conntrack_bucket *ctb,
                             struct conn_lookup_ctx *ctx,
@@ -186,6 +169,7 @@  static alg_helper alg_helpers[] = {
     [CT_ALG_CTL_NONE] = NULL,
     [CT_ALG_CTL_FTP] = handle_ftp_ctl,
     [CT_ALG_CTL_TFTP] = handle_tftp_ctl,
+    [CT_ALG_CTL_SIP] = handle_sip,
 };
 
 long long ct_timeout_val[] = {
@@ -383,7 +367,7 @@  conntrack_destroy(struct conntrack *ct)
     ct_rwlock_destroy(&ct->resources_lock);
 }
 
-static unsigned hash_to_bucket(uint32_t hash)
+unsigned hash_to_bucket(uint32_t hash)
 {
     /* Extracts the most significant bits in hash. The least significant bits
      * are already used internally by the hmap implementation. */
@@ -475,22 +459,29 @@  get_alg_ctl_type(const struct dp_packet *pkt, ovs_be16 tp_src, ovs_be16 tp_dst,
      * the external dependency. */
     enum { CT_IPPORT_FTP = 21 };
     enum { CT_IPPORT_TFTP = 69 };
+    enum { CT_IPPORT_SIP = 5060 };
     uint8_t ip_proto = get_ip_proto(pkt);
     struct udp_header *uh = dp_packet_l4(pkt);
     struct tcp_header *th = dp_packet_l4(pkt);
     ovs_be16 ftp_src_port = htons(CT_IPPORT_FTP);
     ovs_be16 ftp_dst_port = htons(CT_IPPORT_FTP);
     ovs_be16 tftp_dst_port = htons(CT_IPPORT_TFTP);
+    ovs_be16 sip_src_port = htons(CT_IPPORT_SIP);
+    ovs_be16 sip_dst_port = htons(CT_IPPORT_SIP);
 
     if (OVS_UNLIKELY(tp_dst)) {
         if (helper && !strncmp(helper, "ftp", strlen("ftp"))) {
             ftp_dst_port = tp_dst;
         } else if (helper && !strncmp(helper, "tftp", strlen("tftp"))) {
             tftp_dst_port = tp_dst;
+        } else if (helper && !strncmp(helper, "sip", strlen("sip"))) {
+            sip_dst_port = tp_dst;
         }
     } else if (OVS_UNLIKELY(tp_src)) {
         if (helper && !strncmp(helper, "ftp", strlen("ftp"))) {
             ftp_src_port = tp_src;
+        } else if (helper && !strncmp(helper, "sip", strlen("sip"))) {
+            sip_src_port = tp_src;
         }
     }
 
@@ -499,7 +490,11 @@  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 == ftp_src_port || th->tcp_dst == ftp_dst_port)) {
         return CT_ALG_CTL_FTP;
+    } else if (ip_proto == IPPROTO_TCP &&
+               (th->tcp_src == sip_src_port || th->tcp_dst == sip_dst_port)) {
+        return CT_ALG_CTL_SIP;
     }
+
     return CT_ALG_CTL_NONE;
 }
 
@@ -742,7 +737,7 @@  un_nat_packet(struct dp_packet *pkt, const struct conn *conn,
  * this is because the bucket lock needs to be held for lookup
  * and a hash would have already been needed. Hence, this function
  * is just intended for code clarity. */
-static struct conn *
+struct conn *
 conn_lookup(struct conntrack *ct, const struct conn_key *key, long long now)
 {
     struct conn_lookup_ctx ctx;
@@ -833,6 +828,9 @@  ct_verify_helper(const char *helper, enum ct_alg_ctl_type ct_alg_ctl)
         } else if ((ct_alg_ctl == CT_ALG_CTL_TFTP) &&
                    !strncmp(helper, "tftp", strlen("tftp"))) {
             return true;
+        } else if ((ct_alg_ctl == CT_ALG_CTL_SIP) &&
+                   !strncmp(helper, "sip", strlen("sip"))) {
+            return true;
         } else {
             return false;
         }
@@ -1179,6 +1177,7 @@  process_one(struct conntrack *ct, struct dp_packet *pkt,
     conn = ctx->conn;
 
     /* Delete found entry if in wrong direction. 'force' implies commit. */
+
     if (conn && force && ctx->reply) {
         conn_clean(ct, conn, &ct->buckets[bucket]);
         conn = NULL;
@@ -1219,7 +1218,6 @@  process_one(struct conntrack *ct, struct dp_packet *pkt,
 
     enum ct_alg_ctl_type ct_alg_ctl = get_alg_ctl_type(pkt, tp_src, tp_dst,
                                                        helper);
-
     if (OVS_LIKELY(conn)) {
         if (OVS_LIKELY(!conn_update_state_alg(ct, pkt, ctx, conn,
                                               nat_action_info,
@@ -2001,7 +1999,7 @@  ct_endpoint_hash_add(uint32_t hash, const struct ct_endpoint *ep)
 }
 
 /* Symmetric */
-static uint32_t
+uint32_t
 conn_key_hash(const struct conn_key *key, uint32_t basis)
 {
     uint32_t hsrc, hdst, hash;
@@ -2362,6 +2360,8 @@  delete_conn(struct conn *conn)
 {
     free(conn->nat_info);
     free(conn->alg);
+    /* Clean sip data associated with the connection */
+    free_sip_alg(conn->sip_alg);
     free(conn);
 }
 
@@ -2624,15 +2624,14 @@  expectation_clean(struct conntrack *ct, const struct conn_key *master_key,
             free(node);
         }
     }
-
     ct_rwlock_unlock(&ct->resources_lock);
 }
 
-void
-expectation_create_outband(struct conntrack *ct, struct ct_addr src_addr,
-                           struct ct_addr dst_addr, ovs_be16 dst_port,
-                           const struct conn *master_conn, bool reply,
-                           bool src_ip_wc, bool skip_nat, uint8_t nw_proto)
+static void
+expectation_create_core(struct conntrack *ct, struct ct_addr src_addr,
+                        struct ct_addr dst_addr, ovs_be16 dst_port,
+                        const struct conn *master_conn, bool reply,
+                        bool src_ip_wc, bool skip_nat, uint8_t nw_proto)
 {
     struct alg_exp_node *alg_exp_node = xzalloc(sizeof *alg_exp_node);
     struct ct_addr alg_nat_repl_addr;
@@ -2652,6 +2651,9 @@  expectation_create_outband(struct conntrack *ct, struct ct_addr src_addr,
             alg_nat_repl_addr = master_conn->key.src.addr;
         }
     }
+    if (src_ip_wc) {
+        memset(&src_addr, 0, sizeof src_addr);
+    }
 
     alg_exp_node->key.dl_type = master_conn->key.dl_type;
     alg_exp_node->key.nw_proto = nw_proto;
@@ -2698,13 +2700,19 @@  expectation_create(struct conntrack *ct, ovs_be16 dst_port,
         src_addr = master_conn->rev_key.src.addr;
         dst_addr = master_conn->rev_key.dst.addr;
     }
-    if (src_ip_wc) {
-        memset(&src_addr, 0, sizeof src_addr);
-    }
 
-    expectation_create_outband(ct, src_addr, dst_addr, dst_port, master_conn,
-                               reply, src_ip_wc, skip_nat,
-                               master_conn->key.nw_proto);
+    expectation_create_core(ct, src_addr, dst_addr, dst_port, master_conn,
+                            reply, src_ip_wc, skip_nat,
+                            master_conn->key.nw_proto);
+}
+
+void
+expectation_create_outband(struct conntrack *ct, struct ct_addr src_addr,
+                           struct ct_addr dst_addr, ovs_be16 dst_port,
+                           const struct conn *master_conn, bool reply,
+                           bool src_ip_wc, bool skip_nat, uint8_t nw_proto) {
+    expectation_create_core(ct, src_addr, dst_addr, dst_port, master_conn,
+                            reply, src_ip_wc, skip_nat, nw_proto);
 }
 
 static uint8_t
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 1e30c20f4..5886e2452 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -186,6 +186,11 @@  str_to_connhelper(const char *str, uint16_t *alg)
         *alg = IPPORT_TFTP;
         return NULL;
     }
+    if (!strcmp(str, "sip")) {
+        *alg = IPPORT_SIP;
+        return NULL;
+    }
+
     return xasprintf("invalid conntrack helper \"%s\"", str);
 }
 
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index cc450a896..ebd255ae5 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -5624,6 +5624,9 @@  put_ct_helper(struct xlate_ctx *ctx,
         case IPPORT_TFTP:
             nl_msg_put_string(odp_actions, OVS_CT_ATTR_HELPER, "tftp");
             break;
+        case IPPORT_SIP:
+            nl_msg_put_string(odp_actions, OVS_CT_ATTR_HELPER, "sip");
+            break;
         default:
             xlate_report_error(ctx, "cannot serialize ct_helper %d", ofc->alg);
             break;