[ovs-dev,2/3] Conntrack: Add initial support for new SIP Alg.

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

Commit Message

Tiago Lam Dec. 22, 2017, 7:53 p.m.
Handle_sip, in conntrack.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.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

Signed-off-by: Tiago Lam <tiagolam@gmail.com>
---
 include/openvswitch/ofp-actions.h |   4 +
 lib/conntrack-private.h           |   2 +
 lib/conntrack-sip.c               |  14 +++
 lib/conntrack-sip.h               |  11 ++
 lib/conntrack.c                   | 228 +++++++++++++++++++++++++++++++++++++-
 lib/ofp-parse.c                   |   5 +
 ofproto/ofproto-dpif-xlate.c      |   3 +
 7 files changed, 266 insertions(+), 1 deletion(-)

Comments

Mark Michelson Jan. 15, 2018, 4:01 p.m. | #1
On 12/22/2017 01:53 PM, Tiago Lam wrote:
> Handle_sip, in conntrack.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.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
> 
> Signed-off-by: Tiago Lam <tiagolam@gmail.com>
> ---
>   include/openvswitch/ofp-actions.h |   4 +
>   lib/conntrack-private.h           |   2 +
>   lib/conntrack-sip.c               |  14 +++
>   lib/conntrack-sip.h               |  11 ++
>   lib/conntrack.c                   | 228 +++++++++++++++++++++++++++++++++++++-
>   lib/ofp-parse.c                   |   5 +
>   ofproto/ofproto-dpif-xlate.c      |   3 +
>   7 files changed, 266 insertions(+), 1 deletion(-)
> 
> 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;

When sending SIP over TCP, it is semi-common for the boundaries of SIP 
messages not to correspond with the contents of a TCP read/write. You 
might see a whole SIP message plus the start of another, or you might 
get only part of a message in a TCP read/write. Therefore, setting the 
length of the SIP message the same as the TCP length can be incorrect.

Typically SIP implementations will maintain a buffer of TCP content and 
then divide those up into individual SIP messages to be handled. You can 
use the Content-Length header as a guide to tell you where the end of 
one SIP message is and where the beginning of the next is. You may end 
up with a partial message in the buffer afterwards, but the next 
read/write should hopefully complete that partial message.

> +
> +    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);

Similar to what you've stated in the ACK case below, it is not 
guaranteed that an INVITE will contain an 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);

If the INVITE had no SDP, then these references to peer[0].sdp will 
likely crash.

> +                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;
>

Patch

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;