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;
