diff --git a/include/openvswitch/ofp-actions.h b/include/openvswitch/ofp-actions.h
index 25f61ef93..fccd3a61c 100644
--- a/include/openvswitch/ofp-actions.h
+++ b/include/openvswitch/ofp-actions.h
@@ -604,6 +604,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 ac0198f34..5205252d0 100644
--- a/lib/conntrack-private.h
+++ b/lib/conntrack-private.h
@@ -105,6 +105,8 @@ struct conn {
     uint8_t conn_type;
     /* TCP sequence skew due to NATTing of FTP control messages. */
     uint8_t seq_skew_dir;
+    /* XXX: Move SIP specific state to a more appropriate place. */
+    struct sip_state *sip_state;
     /* True if alg data connection. */
     uint8_t alg_related;
 };
diff --git a/lib/conntrack-sip.c b/lib/conntrack-sip.c
index 7239bdad6..4c850b5c0 100644
--- a/lib/conntrack-sip.c
+++ b/lib/conntrack-sip.c
@@ -22,6 +22,20 @@
 #include "dp-packet.h"
 #include "util.h"
 
+void free_sip_state(struct sip_state *sip_state) {
+    free(sip_state->peer[0].sdp);
+    free(sip_state->peer[1].sdp);
+    free(sip_state);
+}
+
+void sip_state_init(struct sip_state *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;
+}
+
 enum sip_mthd sip_mthd_to_enum(char *mthd) {
     if (mthd == NULL) {
         return INVALID;
diff --git a/lib/conntrack-sip.h b/lib/conntrack-sip.h
index 134f35f3a..37abaad10 100644
--- a/lib/conntrack-sip.h
+++ b/lib/conntrack-sip.h
@@ -59,6 +59,15 @@ 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];
+};
+
 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);
@@ -68,6 +77,8 @@ 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);
 
+void free_sip_state(struct sip_state *sip_state);
+void sip_state_init(struct sip_state *sip_state);
 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);
diff --git a/lib/conntrack.c b/lib/conntrack.c
index 6d078f5a8..65d10dd08 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,6 +41,7 @@
 #include "random.h"
 #include "timeval.h"
 
+
 VLOG_DEFINE_THIS_MODULE(conntrack);
 
 COVERAGE_DEFINE(conntrack_full);
@@ -71,6 +73,7 @@ enum ct_alg_ctl_type {
     CT_ALG_CTL_NONE,
     CT_ALG_CTL_FTP,
     CT_ALG_CTL_TFTP,
+    CT_ALG_CTL_SIP,
 };
 
 static bool conn_key_extract(struct conntrack *, struct dp_packet *,
@@ -169,6 +172,14 @@ handle_tftp_ctl(struct conntrack *ct,
                 long long now, enum ftp_ctl_pkt ftp_ctl OVS_UNUSED,
                 bool nat OVS_UNUSED);
 
+static 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);
+
 typedef void (*alg_helper)(struct conntrack *ct,
                            const struct conn_lookup_ctx *ctx,
                            struct dp_packet *pkt,
@@ -180,6 +191,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[] = {
@@ -470,22 +482,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;
         }
     }
 
@@ -494,7 +513,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;
 }
 
@@ -831,6 +854,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;
         }
@@ -1178,6 +1204,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;
@@ -1218,7 +1245,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,
@@ -2582,6 +2608,62 @@ expectation_lookup(struct hmap *alg_expectations,
     return NULL;
 }
 
+static void
+sip_expectation_create(struct conntrack *ct,
+                       const ovs_be32 offer_addr,
+                       const ovs_be32 answer_addr,
+                       const ovs_be16 offer_port,
+                       const long long now,
+                       const struct conn *master_conn)
+{
+    /* Set src address coming from answer SDP 'c' */
+    struct ct_addr src_addr;
+    memset(&src_addr, 0, sizeof src_addr);
+    src_addr.ipv4_aligned = answer_addr;
+    /* Set dst address coming from offer SDP 'c' */
+    struct ct_addr dst_addr;
+    memset(&dst_addr, 0, sizeof dst_addr);
+    dst_addr.ipv4_aligned = offer_addr;
+
+    struct alg_exp_node *alg_exp_node =
+        xzalloc(sizeof *alg_exp_node);
+    alg_exp_node->key.dl_type = master_conn->key.dl_type;
+    /* nw_proto might won't be the same as SIP, since RTP is over UDP - hence
+     * set it to UDP explicitly */
+    alg_exp_node->key.nw_proto = IPPROTO_UDP;
+    alg_exp_node->key.zone = master_conn->key.zone;
+    alg_exp_node->key.src.addr = src_addr;
+    alg_exp_node->key.dst.addr = dst_addr;
+    alg_exp_node->key.src.port = ALG_WC_SRC_PORT;
+    alg_exp_node->key.dst.port = offer_port;
+    alg_exp_node->master_mark = master_conn->mark;
+    alg_exp_node->master_label = master_conn->label;
+    alg_exp_node->master_key = master_conn->key;
+    /* Take the write lock here because it is almost 100%
+     * likely that the lookup will fail and
+     * expectation_create() will be called below. */
+    ct_rwlock_wrlock(&ct->resources_lock);
+    struct alg_exp_node *alg_exp = expectation_lookup(
+        &ct->alg_expectations, &alg_exp_node->key, ct->hash_basis);
+    if (alg_exp) {
+        free(alg_exp_node);
+        ct_rwlock_unlock(&ct->resources_lock);
+        return;
+    }
+
+    /* TODO(tlam): NAT not supported yet */
+
+    uint32_t alg_exp_conn_key_hash =
+        conn_key_hash(&alg_exp_node->key,
+                      ct->hash_basis);
+    hmap_insert(&ct->alg_expectations,
+                &alg_exp_node->node,
+                alg_exp_conn_key_hash);
+
+    alg_exp_init_expiration(ct, alg_exp_node, now);
+    ct_rwlock_unlock(&ct->resources_lock);
+}
+
 static void
 expectation_create(struct conntrack *ct,
                    ovs_be16 dst_port,
@@ -3186,3 +3268,147 @@ handle_tftp_ctl(struct conntrack *ct,
                        CT_TFTP_MODE, conn_for_expectation);
     return;
 }
+
+static void
+sip_delete_conn(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);
+    /* Free sip_state associated with the connection */
+    free_sip_state(conn->sip_state);
+    conn_clean(ct, conn, &ct->buckets[bucket]);
+    ct_lock_unlock(&ct->buckets[bucket].lock);
+}
+
+
+static void
+sip_set_conn_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);
+    struct sip_state *sip_state = xmalloc(sizeof(struct sip_state));
+    sip_state_init(sip_state);
+    conn->sip_state = sip_state;
+    conn->sip_state->peer[0].sdp = sdp;
+    ct_lock_unlock(&ct->buckets[bucket].lock);
+}
+
+/* XXX Only handles IPv4 for now, must include IPv6 in the future */
+/* XXX Doesn't deal with NAT either */
+/* XXX No support for SIP over UDP either (not tested at least), which might
+ * be required in the future */
+static 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)
+{
+    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;
+    }
+
+    char *tcp_hdr = (char *) th;
+    /* Move to beginning of TCP payload, where the SIP payload is */
+    char *sip = tcp_hdr + tcp_hdr_len;
+
+    size_t sip_len = tcp_len;
+
+    struct sip_strt_ln *strt_ln = sip_parse_strt_ln(sip, sip_len);
+    if (strt_ln == NULL) {
+        pkt->md.ct_state |= CS_TRACKED | CS_INVALID;
+        return;
+    }
+
+    const struct conn *conn = conn_for_expectation;
+
+    /* Pointer to the beginning of SIP message-header */
+    char *msg_hdr = sip_msg_hdr(sip, &sip_len);
+
+    /* XXX Validate "Content-Length" header to know exact size of body */
+
+    /* Pointer to the beginning of SIP message-body */
+    char *msg_bdy = sip_msg_bdy(msg_hdr, &sip_len);
+    if (msg_bdy == NULL) {
+        /* Couldn't parse the SIP message-body */
+        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, et the connection state */
+            sip_set_conn_state(ct, &conn_for_expectation->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_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_state->peer[0].was_bye) {
+                /* Received a 200 OK for the previous BYE, delete connection */
+                sip_delete_conn(ct, &conn_for_expectation->key, now);
+                return;
+            }
+
+            /* Check if this SIP reply has an SDP. */
+            struct sip_sdp *sdp = sip_parse_sdp(msg_bdy, sip_len);
+            ovs_be32 offer_addr;
+            ovs_be32 offer_port;
+            ovs_be32 answer_addr;
+            if (sdp == NULL) {
+                if (conn->sip_state->peer[0].sdp != NULL &&
+                    conn->sip_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_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 */
+                offer_addr = htonl(conn->sip_state->peer[0].sdp->conn);
+                offer_port = htons(conn->sip_state->peer[0].sdp->port);
+                answer_addr = htonl(conn->sip_state->peer[1].sdp->conn);
+
+                sip_expectation_create(ct, offer_addr, answer_addr, offer_port,
+                                       now, conn);
+            }
+        }
+    }
+
+    free_strt_ln(strt_ln);
+}
diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c
index 0c68495bf..dc7ab1e49 100644
--- a/lib/ofp-parse.c
+++ b/lib/ofp-parse.c
@@ -185,6 +185,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 a2b4fdb3b..3a3e1f0f5 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -5693,6 +5693,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;
