diff mbox series

[ovs-dev,1/3] Conntrack: Add new API for future SIP Alg.

Message ID 20171222195337.28983-2-tiagolam@gmail.com
State Changes Requested
Headers show
Series Initial support for new SIP Alg. | expand

Commit Message

Tiago Lam Dec. 22, 2017, 7:53 p.m. UTC
The new API (in conntrack-sip.h and conntrack-sip.c) defines preliminary
functions, helpers and strucures to move through and parse SIP messages,
including SDP in the message bodies, if present.

Note that this is still a preliminary version of the API, and future
work is still needed to improve the SIP and SDP handling. When parsing
an SDP, for example, the order of the 'm', 'c' and 'o' sections is not
enforced, and only the ports and IP addresses are stored at the moment.

This API will be used by a future commit in order to implement the new
SIP Alg in userspace conntrack.

Signed-off-by: Tiago Lam <tiagolam@gmail.com>
---
 lib/automake.mk     |   2 +
 lib/conntrack-sip.c | 477 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/conntrack-sip.h | 112 ++++++++++++
 3 files changed, 591 insertions(+)
 create mode 100644 lib/conntrack-sip.c
 create mode 100644 lib/conntrack-sip.h

Comments

Mark Michelson Jan. 15, 2018, 4:01 p.m. UTC | #1
On 12/22/2017 01:53 PM, Tiago Lam wrote:
> The new API (in conntrack-sip.h and conntrack-sip.c) defines preliminary
> functions, helpers and strucures to move through and parse SIP messages,
> including SDP in the message bodies, if present.
> 
> Note that this is still a preliminary version of the API, and future
> work is still needed to improve the SIP and SDP handling. When parsing
> an SDP, for example, the order of the 'm', 'c' and 'o' sections is not
> enforced, and only the ports and IP addresses are stored at the moment.
> 
> This API will be used by a future commit in order to implement the new
> SIP Alg in userspace conntrack.
> 
> Signed-off-by: Tiago Lam <tiagolam@gmail.com>
> ---
>   lib/automake.mk     |   2 +
>   lib/conntrack-sip.c | 477 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>   lib/conntrack-sip.h | 112 ++++++++++++
>   3 files changed, 591 insertions(+)
>   create mode 100644 lib/conntrack-sip.c
>   create mode 100644 lib/conntrack-sip.h
> 
> diff --git a/lib/automake.mk b/lib/automake.mk
> index effe5b5c2..0dc068b9e 100644
> --- a/lib/automake.mk
> +++ b/lib/automake.mk
> @@ -51,6 +51,8 @@ lib_libopenvswitch_la_SOURCES = \
>   	lib/connectivity.h \
>   	lib/conntrack-icmp.c \
>   	lib/conntrack-private.h \
> +	lib/conntrack-sip.c \
> +	lib/conntrack-sip.h \
>   	lib/conntrack-tcp.c \
>   	lib/conntrack-other.c \
>   	lib/conntrack.c \
> diff --git a/lib/conntrack-sip.c b/lib/conntrack-sip.c
> new file mode 100644
> index 000000000..7239bdad6
> --- /dev/null
> +++ b/lib/conntrack-sip.c
> @@ -0,0 +1,477 @@
> +/*
> + * Copyright (c) 2017 Nicira, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +#include <errno.h>
> +#include <sys/types.h>
> +
> +#include "conntrack-sip.h"
> +#include "dp-packet.h"
> +#include "util.h"
> +
> +enum sip_mthd sip_mthd_to_enum(char *mthd) {
> +    if (mthd == NULL) {
> +        return INVALID;
> +    }
> +
> +    if (!strcmp(mthd, "INVITE")) {
> +        return INVITE;
> +    } else if (!strcmp(mthd, "REGISTER")) {
> +        return REGISTER;
> +    } else if (!strcmp(mthd, "ACK")) {
> +        return ACK;
> +    } else if (!strcmp(mthd, "CANCEL")) {
> +        return CANCEL;
> +    } else if (!strcmp(mthd, "BYE")) {
> +        return BYE;
> +    } else if (!strcmp(mthd, "OPTIONS")) {
> +        return OPTIONS;
> +    }
> +
> +    return INVALID;
> +}
> +/* A valid SIP status falls in between the following ranges (rfc3261):
> + * - 1xx: Provisional;
> + * - 2xx: Success;
> + * - 3xx: Redirection;
> + * - 4xx: Client Error;
> + * - 5xx: Server Error;
> + * - 6xx: Global Failure.
> + *
> + * 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;
> +}

Since the range between 100 and 700 is uninterrupted, it seems like this 
could be reduced to:

return status >= 100 && status < 700

> +
> +/* Find and validate the beginning of the CRLF ending, returning a pointer to
> + * the found CR, 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;
> +    }

Most SIP parsers I've seen will be lenient in the absence of a carriage 
return character.

> +    char *lf = memchr(sip, '\n', len);
> +    if (lf == NULL) {
> +        return NULL;
> +    }
> +
> +    if (lf - cr != 1) {
> +        return NULL;
> +    }
> +
> +    return cr;
> +}
> +
> +/* 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;

size_t is an unsigned type, so returning -1 does not work here.

> +    }
> +
> +    return cr - 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;
> +    }
> +    size_t sz = cr - *sip;
> +    *sip += sz + 2;
> +    *len -= sz + 2;
> +}
> +
> +/* Iterate through each word in the line at 'sip', where each word is divided
> + * by the space char ' ', and store the resulting word in 'dst', if not NULL.
> + *
> + * After returning 'sip' is pointing to the next word in the line, and 'len'
> + * has been updated accordingly. */
> +bool sip_ln_cpy_nxt_word(char **sip, size_t *len, char **dst) {
> +    char *s;
> +
> +    s = memchr(*sip, ' ', *len);
> +    if (s == NULL) {
> +        return false;
> +    }
> +
> +    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);

You can replace the above two lines with a call to xmemdup0:

*dst = xmemdup0(*sip, wlen);

There are a few other places in this file where the same substitution 
can be made.

> +    }
> +
> +    /* Move 'sip' to the beginning of next word */
> +    *sip += wlen + 1;
> +    *len -= wlen + 1;
> +
> +    return true;
> +}
> +
> +/* Parse an 'o' line from the SDP.
> + *
> + * 'conn' should point to the beginning of the line, after 'o='.
> + * 'len' is the size the 'o' line, excluding 'o=' and the CRLF ending.
> + *
> + * XXX Returns only the origin's IP address for now.
> + *
> + * Example: o=user1 53655765 2353687637 IN IP4 127.0.1.1\r\n */
> +char * sdp_get_origin(char *orig, size_t len) {
> +    char *o_ln = orig;
> +    char *ip_addr;
> +
> +    for (int i = 0; i < 5; i ++) {
> +        if (!sip_ln_cpy_nxt_word(&o_ln, &len, NULL)) {
> +            return NULL;
> +        }
> +    }
> +
> +    ip_addr = xmalloc(sizeof(char) * (len + 1));
> +    ovs_strzcpy(ip_addr, o_ln, len + 1);
> +
> +    return ip_addr;
> +}
> +
> +/* Parse an 'c' line from the SDP.
> + *
> + * 'conn' should point to the beginning of the line, after 'c='.
> + * 'len' is the size the 'c' line, excluding 'c=' and the CRLF ending.
> + *
> + * XXX Returns only the connection's IP address for now.
> + *
> + * Example: c=IN IP4 10.0.2.128\r\n */
> +char * sdp_get_conn(char *conn, size_t len) {
> +    char *c_ln = conn;
> +    char *ip_addr;
> +
> +    for (int i = 0; i < 2; i ++) {
> +        if (!sip_ln_cpy_nxt_word(&c_ln, &len, NULL)) {
> +            return NULL;
> +        }
> +    }
> +
> +    ip_addr = xmalloc(sizeof(char) * (len + 1));
> +    ovs_strzcpy(ip_addr, c_ln, len + 1);
> +
> +    return ip_addr;
> +}
> +
> +/* Parse an 'm' line from the SDP.
> + *
> + * 'media' should point to the beginning of the line, after 'm='.
> + * 'len' is the size the 'm' line, excluding 'm=' and the CRLF ending.
> + *
> + * XXX Returns only the media's port.
> + *
> + * Example: m=audio 8192 RTP/AVP 0 */
> +char * sdp_get_media(char *media, size_t len) {
> +    char *m_ln = media;
> +    char *port;
> +
> +    if (!sip_ln_cpy_nxt_word(&m_ln, &len, NULL)) {
> +        return NULL;
> +    }
> +
> +    if (!sip_ln_cpy_nxt_word(&m_ln, &len, &port)) {
> +        return NULL;
> +    }
> +
> +    return port;
> +}
> +
> +/* Given a pointer to the beginning of an SDP, parses it accordingly.
> + *
> + * 'sdp' should point to the beginning of the SDP.
> + * 'len' should be the size of the whole SIP message after start-line and
> + * headers have been parsed.
> + *
> + * Returns a 'sip_sdp' struct, which the caller is responsible for freeing.
> + * XXX No order is enforced and only supports one 'm' line for now */
> +struct sip_sdp * sip_parse_sdp(char *sdp, size_t len) {
> +    char *sip = sdp;
> +    char *addr_s = NULL;
> +    char *port_s = NULL;
> +    uint32_t hport;
> +    uint32_t haddr;
> +    struct sip_sdp *sip_sdp;
> +    size_t ln_len;
> +
> +    if (sip == NULL) {
> +       return NULL;
> +    }
> +
> +    sip_sdp = xmalloc(sizeof(struct sip_sdp));
> +
> +    for (int i = 0; i < len; i++) {

I'm confused about the use of the for loop here. Would

while (len > 0) {

work?

> +        ln_len = sip_ln_len(sip, len);
> +        if (ln_len == -1) {
> +            /* Message is invalid, most likely no CRLF found */
> +            goto error;
> +        }
> +
> +        switch (sip[0]) {
> +        case 'o':
> +            addr_s = sdp_get_origin(sip + 2, ln_len - 2);
The sip + 2 here seems dangerous. If someone sends a SIP message that 
ends with a line with a single 'o' character, then this would go out of 
bounds. You can use ln_len to ensure that the line is long enough.

The same finding exists for the other types of SDP lines below.

> +            if (!ip_parse(addr_s, &haddr)) {
> +                free(addr_s);
> +                goto error;
> +            }
> +            sip_sdp->orig = ntohl(haddr);

Storing IP addresses in host byte order is unorthodox. I could see this 
leading to confusion.

> +            free(addr_s);
> +            break;
> +        case 'c':
> +            addr_s = sdp_get_conn(sip + 2, ln_len - 2);
> +            if (!ip_parse(addr_s, &haddr)) {
> +                free(addr_s);
> +                goto error;
> +            }
> +            sip_sdp->conn = ntohl(haddr);
> +            free(addr_s);
> +            break;
> +        case 'm':
> +            port_s = sdp_get_media(sip + 2, ln_len - 2);
> +            if (!str_to_uint(port_s, 10, &hport)) {
> +                free(port_s);
> +                goto error;
> +            }
> +            sip_sdp->port = hport;
> +            free(port_s);
> +            break;
> +        }
> +
> +        sip_ln_skip(&sip, &len);
> +    }
> +
> +    return sip_sdp;
> +
> +error:
> +    free(sip_sdp);
> +
> +    return NULL;
> +}
> +
> +/* generic-message  =  start-line
> + *                     *message-header
> + *                     CRLF
> + *                     [ message-body ]
> + * start-line       =  Request-Line / Status-Line */
> +int sip_skip_to_body(char **sip_hdr, size_t *len) {
> +    if (*sip_hdr == NULL) {
> +        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') {
> +            /* Skip 2 bytes for CRLF */
> +            *sip_hdr += 2;
> +            *len -= 2;
> +            return 0;
> +        }

> +
> +        cr = sip_ln_find_crlf(*sip_hdr, *len);
> +        if (cr == NULL) {
> +            return -EINVAL;
> +        }
> +        sz = cr - *sip_hdr;
> +        *sip_hdr += sz + 2;
> +        *len -= sz + 2;
> +    }

I think you can reduce the body of this while loop down to something like:

if (sip_ln_len(*sip_hdr, len) == 0) {
     sip_ln_skip(sip_hdr, len);
     return 0;
}
sip_ln_skip(sip_hdr, len);

> +
> +    return -EINVAL;
> +}
> +
> +void free_strt_ln(struct sip_strt_ln *strt_ln) {
> +    if (strt_ln->type == STATUS_LINE) {
> +        free_stat_ln(strt_ln->stat_ln);
> +    } else {
> +        free_reqs_ln(strt_ln->reqs_ln);
> +    }
> +
> +    free(strt_ln);
> +}
> +
> +/* Given a pointer to the beginning of the SIP message (start-line), detects
> + * if it is a request-line or a status-line and parses accordingly.
> + *
> + * 'start_ln' should point to the beginning of the SIP message.
> + * 'len' may be the size of the whole SIP message, but must be at least the
> + * size of the start-line, including with the CRLF ending.
> + *
> + * Returns a 'sip_strt_ln' struct, which the caller is responsible for
> + * freeing. */
> +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;
> +
> +    /* 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 */
> +        return NULL;
> +    }
> +
> +    strt_ln = xzalloc(sizeof(struct sip_strt_ln));
> +
> +    if (!strncmp(strt_ln_str, "SIP", 3)) {
> +        struct sip_stat_ln *stat_ln = sip_parse_stat_ln(strt_ln_str, ln_len);
> +        if (stat_ln == NULL) {
> +            goto error;
> +        }
> +
> +        strt_ln->stat_ln = stat_ln;
> +        strt_ln->type = STATUS_LINE;
> +    } else if (!strncmp(strt_ln_str, "INVITE", 6) ||
> +               !strncmp(strt_ln_str, "ACK", 3) ||
> +               !strncmp(strt_ln_str, "BYE", 3)) {

> +        struct sip_reqs_ln *reqs_ln = sip_parse_reqs_ln(strt_ln_str, ln_len);
> +        if (reqs_ln == NULL) {
> +            goto error;
> +        }
> +
> +        strt_ln->reqs_ln = reqs_ln;
> +        strt_ln->type = REQUEST_LINE;
> +    } else {
> +        /* Message not yet supported */
> +        goto error;
> +    }
> +
> +    return strt_ln;
> +
> +error:
> +    free(strt_ln);
> +
> +    return NULL;
> +}
> +
> +void free_stat_ln(struct sip_stat_ln *stat_ln) {
> +    free(stat_ln->ver);
> +    free(stat_ln->reason);
> +    free(stat_ln);
> +}
> +
> +/* Parses the SIP status-line.
> + *
> + * 'start_ln' should point to the beginning of the SIP message.
> + * 'len' is the size of the status-line, not including the CRLF ending.
> + *
> + * Returns a 'sip_stat_ln' struct, which the caller is responsible for freeing.
> + *
> + * Example SIP status-line: SIP/2.0 200 OK\r\n */
> +struct sip_stat_ln *
> +sip_parse_stat_ln(char *start_ln, size_t len) {
> +    struct sip_stat_ln *stat_ln;
> +
> +    stat_ln = xzalloc(sizeof(struct sip_stat_ln));
> +
> +    if (!sip_ln_cpy_nxt_word(&start_ln, &len, &stat_ln->ver)) {
> +        goto error;
> +    }
> +
> +    char *status = NULL;
> +
> +    if (!sip_ln_cpy_nxt_word(&start_ln, &len, &status)) {
> +        free(status);
> +        goto error;
> +    }
> +
> +    if (!str_to_uint(status, 10, &stat_ln->status) ||
> +        !is_valid_status(stat_ln->status)) {
> +        free(status);
> +        goto error;
> +    }
> +
> +    stat_ln->reason = xmalloc(sizeof(char) * (len + 1));
> +    ovs_strzcpy(stat_ln->reason, start_ln, len + 1);
> +
> +    return stat_ln;
> +
> +error:
> +    free_stat_ln(stat_ln);
> +
> +    return NULL;
> +}
> +
> +void free_reqs_ln(struct sip_reqs_ln *reqs_ln) {
> +    free(reqs_ln->req_uri);
> +    free(reqs_ln->ver);
> +    free(reqs_ln);
> +}
> +
> +/* Parses the SIP request-line.
> + *
> + * 'start_ln' should point to the beginning of the SIP message.
> + * 'len' is the size of the request-line, not including the CRLF ending.
> + *
> + * Returns a 'sip_reqs_ln' struct, which the caller is responsible for freeing.
> + *
> + * Example SIP request-line: INVITE sip:service@10.0.1.128:5060 SIP/2.0\r\n */
> +struct sip_reqs_ln *
> +sip_parse_reqs_ln(char *start_ln, size_t len) {
> +    struct sip_reqs_ln *reqs_ln;
> +    char *sip_mthd = NULL;
> +
> +    reqs_ln = xzalloc(sizeof(struct sip_reqs_ln));
> +
> +    if (!sip_ln_cpy_nxt_word(&start_ln, &len, &sip_mthd)) {
> +        goto error;
> +    }
> +
> +    reqs_ln->mthd = sip_mthd_to_enum(sip_mthd);
> +
> +    if (reqs_ln->mthd == INVALID) {
> +        goto error;
> +    }
> +
> +    if (!sip_ln_cpy_nxt_word(&start_ln, &len, &reqs_ln->req_uri)) {
> +        goto error;
> +    }
> +
> +    reqs_ln->ver = xmalloc(sizeof(char) * (len + 1));
> +    ovs_strzcpy(reqs_ln->ver, start_ln, len + 1);
> +
> +    return reqs_ln;
> +
> +error:
> +    free(sip_mthd);
> +    free_reqs_ln(reqs_ln);
> +
> +    return NULL;
> +}
> diff --git a/lib/conntrack-sip.h b/lib/conntrack-sip.h
> new file mode 100644
> index 000000000..134f35f3a
> --- /dev/null
> +++ b/lib/conntrack-sip.h
> @@ -0,0 +1,112 @@
> +/*
> + * Copyright (c) 2017 Nicira, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <sys/types.h>
> +
> +#include "dp-packet.h"
> +
> +struct sip_sdp {
> +    uint32_t orig;
> +    uint32_t conn;
> +    uint16_t port;
> +};

One thing that this patch series doesn't take into account is RTCP. A 
SIP offer-answer will create an RTCP session for each RTP session. 
Typically the port number for the RTCP session will be one higher than 
the RTP port number. However, it is also possible to explicitly signal 
the RTCP port with an a=rtcp:<port_num> line.

> +
> +enum sip_mthd {
> +    INVITE,
> +    REGISTER,
> +    ACK,
> +    CANCEL,
> +    BYE,
> +    OPTIONS,
> +    INVALID,
> +};

There are other SIP methods besides these. Specifically, there are 
PRACK, UPDATE, INFO, SUBSCRIBE, NOTIFY, and PUBLISH. Of those, UPDATE 
will be the most important since it can contain SDP alterations beyond 
what was sent in an INVITE.

> +
> +struct sip_reqs_ln {
> +    enum sip_mthd mthd;
> +    char *req_uri;
> +    char *ver;
> +};
> +
> +struct sip_stat_ln {
> +    char *ver;
> +    uint32_t status;
> +    char *reason;
> +};
> +
> +enum sip_strt_type {
> +    REQUEST_LINE,
> +    STATUS_LINE,
> +};
> +
> +struct sip_strt_ln {
> +    union {
> +        struct sip_reqs_ln *reqs_ln;
> +        struct sip_stat_ln *stat_ln;
> +    };
> +    enum sip_strt_type type;
> +};
> +
> +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);
> +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);
> +
> +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);
> +struct sip_sdp *
> +sip_parse_sdp(char *sdp, size_t len);
> +void free_strt_ln(struct sip_strt_ln *strt_ln);
> +struct sip_strt_ln *
> +sip_parse_strt_ln(char *start_line, size_t len);
> +void free_stat_ln(struct sip_stat_ln *stat_ln);
> +struct sip_stat_ln *
> +sip_parse_stat_ln(char *start_ln, size_t len);
> +void free_reqs_ln(struct sip_reqs_ln *reqs_ln);
> +struct sip_reqs_ln *
> +sip_parse_reqs_ln(char *start_ln, size_t len);
> +
> +/* 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 *
> +sip_msg_hdr(char *sip, size_t *len) {
> +
> +    char *msg_hdr = sip;
> +
> +    sip_ln_skip(&msg_hdr, len);
> +
> +    return msg_hdr;
> +}
> +
> +/* Given a pointer to the beginning of the SIP message, return a pointer to
> + * 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 *
> +sip_msg_bdy(char *sip, size_t *len) {
> +
> +    char *msg_bdy = sip;
> +
> +    sip_skip_to_body(&msg_bdy, len);
> +
> +    return msg_bdy;
> +}
> 

Why do these final two functions return void pointers?
diff mbox series

Patch

diff --git a/lib/automake.mk b/lib/automake.mk
index effe5b5c2..0dc068b9e 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -51,6 +51,8 @@  lib_libopenvswitch_la_SOURCES = \
 	lib/connectivity.h \
 	lib/conntrack-icmp.c \
 	lib/conntrack-private.h \
+	lib/conntrack-sip.c \
+	lib/conntrack-sip.h \
 	lib/conntrack-tcp.c \
 	lib/conntrack-other.c \
 	lib/conntrack.c \
diff --git a/lib/conntrack-sip.c b/lib/conntrack-sip.c
new file mode 100644
index 000000000..7239bdad6
--- /dev/null
+++ b/lib/conntrack-sip.c
@@ -0,0 +1,477 @@ 
+/*
+ * Copyright (c) 2017 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include "conntrack-sip.h"
+#include "dp-packet.h"
+#include "util.h"
+
+enum sip_mthd sip_mthd_to_enum(char *mthd) {
+    if (mthd == NULL) {
+        return INVALID;
+    }
+
+    if (!strcmp(mthd, "INVITE")) {
+        return INVITE;
+    } else if (!strcmp(mthd, "REGISTER")) {
+        return REGISTER;
+    } else if (!strcmp(mthd, "ACK")) {
+        return ACK;
+    } else if (!strcmp(mthd, "CANCEL")) {
+        return CANCEL;
+    } else if (!strcmp(mthd, "BYE")) {
+        return BYE;
+    } else if (!strcmp(mthd, "OPTIONS")) {
+        return OPTIONS;
+    }
+
+    return INVALID;
+}
+/* A valid SIP status falls in between the following ranges (rfc3261):
+ * - 1xx: Provisional;
+ * - 2xx: Success;
+ * - 3xx: Redirection;
+ * - 4xx: Client Error;
+ * - 5xx: Server Error;
+ * - 6xx: Global Failure.
+ *
+ * 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;
+}
+
+/* Find and validate the beginning of the CRLF ending, returning a pointer to
+ * the found CR, 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 (lf - cr != 1) {
+        return NULL;
+    }
+
+    return cr;
+}
+
+/* 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;
+    }
+
+    return cr - 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;
+    }
+    size_t sz = cr - *sip;
+    *sip += sz + 2;
+    *len -= sz + 2;
+}
+
+/* Iterate through each word in the line at 'sip', where each word is divided
+ * by the space char ' ', and store the resulting word in 'dst', if not NULL.
+ *
+ * After returning 'sip' is pointing to the next word in the line, and 'len'
+ * has been updated accordingly. */
+bool sip_ln_cpy_nxt_word(char **sip, size_t *len, char **dst) {
+    char *s;
+
+    s = memchr(*sip, ' ', *len);
+    if (s == NULL) {
+        return false;
+    }
+
+    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);
+    }
+
+    /* Move 'sip' to the beginning of next word */
+    *sip += wlen + 1;
+    *len -= wlen + 1;
+
+    return true;
+}
+
+/* Parse an 'o' line from the SDP.
+ *
+ * 'conn' should point to the beginning of the line, after 'o='.
+ * 'len' is the size the 'o' line, excluding 'o=' and the CRLF ending.
+ *
+ * XXX Returns only the origin's IP address for now.
+ *
+ * Example: o=user1 53655765 2353687637 IN IP4 127.0.1.1\r\n */
+char * sdp_get_origin(char *orig, size_t len) {
+    char *o_ln = orig;
+    char *ip_addr;
+
+    for (int i = 0; i < 5; i ++) {
+        if (!sip_ln_cpy_nxt_word(&o_ln, &len, NULL)) {
+            return NULL;
+        }
+    }
+
+    ip_addr = xmalloc(sizeof(char) * (len + 1));
+    ovs_strzcpy(ip_addr, o_ln, len + 1);
+
+    return ip_addr;
+}
+
+/* Parse an 'c' line from the SDP.
+ *
+ * 'conn' should point to the beginning of the line, after 'c='.
+ * 'len' is the size the 'c' line, excluding 'c=' and the CRLF ending.
+ *
+ * XXX Returns only the connection's IP address for now.
+ *
+ * Example: c=IN IP4 10.0.2.128\r\n */
+char * sdp_get_conn(char *conn, size_t len) {
+    char *c_ln = conn;
+    char *ip_addr;
+
+    for (int i = 0; i < 2; i ++) {
+        if (!sip_ln_cpy_nxt_word(&c_ln, &len, NULL)) {
+            return NULL;
+        }
+    }
+
+    ip_addr = xmalloc(sizeof(char) * (len + 1));
+    ovs_strzcpy(ip_addr, c_ln, len + 1);
+
+    return ip_addr;
+}
+
+/* Parse an 'm' line from the SDP.
+ *
+ * 'media' should point to the beginning of the line, after 'm='.
+ * 'len' is the size the 'm' line, excluding 'm=' and the CRLF ending.
+ *
+ * XXX Returns only the media's port.
+ *
+ * Example: m=audio 8192 RTP/AVP 0 */
+char * sdp_get_media(char *media, size_t len) {
+    char *m_ln = media;
+    char *port;
+
+    if (!sip_ln_cpy_nxt_word(&m_ln, &len, NULL)) {
+        return NULL;
+    }
+
+    if (!sip_ln_cpy_nxt_word(&m_ln, &len, &port)) {
+        return NULL;
+    }
+
+    return port;
+}
+
+/* Given a pointer to the beginning of an SDP, parses it accordingly.
+ *
+ * 'sdp' should point to the beginning of the SDP.
+ * 'len' should be the size of the whole SIP message after start-line and
+ * headers have been parsed.
+ *
+ * Returns a 'sip_sdp' struct, which the caller is responsible for freeing.
+ * XXX No order is enforced and only supports one 'm' line for now */
+struct sip_sdp * sip_parse_sdp(char *sdp, size_t len) {
+    char *sip = sdp;
+    char *addr_s = NULL;
+    char *port_s = NULL;
+    uint32_t hport;
+    uint32_t haddr;
+    struct sip_sdp *sip_sdp;
+    size_t ln_len;
+
+    if (sip == NULL) {
+       return NULL;
+    }
+
+    sip_sdp = xmalloc(sizeof(struct sip_sdp));
+
+    for (int i = 0; i < len; i++) {
+        ln_len = sip_ln_len(sip, len);
+        if (ln_len == -1) {
+            /* Message is invalid, most likely no CRLF found */
+            goto error;
+        }
+
+        switch (sip[0]) {
+        case 'o':
+            addr_s = sdp_get_origin(sip + 2, ln_len - 2);
+            if (!ip_parse(addr_s, &haddr)) {
+                free(addr_s);
+                goto error;
+            }
+            sip_sdp->orig = ntohl(haddr);
+            free(addr_s);
+            break;
+        case 'c':
+            addr_s = sdp_get_conn(sip + 2, ln_len - 2);
+            if (!ip_parse(addr_s, &haddr)) {
+                free(addr_s);
+                goto error;
+            }
+            sip_sdp->conn = ntohl(haddr);
+            free(addr_s);
+            break;
+        case 'm':
+            port_s = sdp_get_media(sip + 2, ln_len - 2);
+            if (!str_to_uint(port_s, 10, &hport)) {
+                free(port_s);
+                goto error;
+            }
+            sip_sdp->port = hport;
+            free(port_s);
+            break;
+        }
+
+        sip_ln_skip(&sip, &len);
+    }
+
+    return sip_sdp;
+
+error:
+    free(sip_sdp);
+
+    return NULL;
+}
+
+/* generic-message  =  start-line
+ *                     *message-header
+ *                     CRLF
+ *                     [ message-body ]
+ * start-line       =  Request-Line / Status-Line */
+int sip_skip_to_body(char **sip_hdr, size_t *len) {
+    if (*sip_hdr == NULL) {
+        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') {
+            /* Skip 2 bytes for CRLF */
+            *sip_hdr += 2;
+            *len -= 2;
+            return 0;
+        }
+
+        cr = sip_ln_find_crlf(*sip_hdr, *len);
+        if (cr == NULL) {
+            return -EINVAL;
+        }
+        sz = cr - *sip_hdr;
+        *sip_hdr += sz + 2;
+        *len -= sz + 2;
+    }
+
+    return -EINVAL;
+}
+
+void free_strt_ln(struct sip_strt_ln *strt_ln) {
+    if (strt_ln->type == STATUS_LINE) {
+        free_stat_ln(strt_ln->stat_ln);
+    } else {
+        free_reqs_ln(strt_ln->reqs_ln);
+    }
+
+    free(strt_ln);
+}
+
+/* Given a pointer to the beginning of the SIP message (start-line), detects
+ * if it is a request-line or a status-line and parses accordingly.
+ *
+ * 'start_ln' should point to the beginning of the SIP message.
+ * 'len' may be the size of the whole SIP message, but must be at least the
+ * size of the start-line, including with the CRLF ending.
+ *
+ * Returns a 'sip_strt_ln' struct, which the caller is responsible for
+ * freeing. */
+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;
+
+    /* 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 */
+        return NULL;
+    }
+
+    strt_ln = xzalloc(sizeof(struct sip_strt_ln));
+
+    if (!strncmp(strt_ln_str, "SIP", 3)) {
+        struct sip_stat_ln *stat_ln = sip_parse_stat_ln(strt_ln_str, ln_len);
+        if (stat_ln == NULL) {
+            goto error;
+        }
+
+        strt_ln->stat_ln = stat_ln;
+        strt_ln->type = STATUS_LINE;
+    } else if (!strncmp(strt_ln_str, "INVITE", 6) ||
+               !strncmp(strt_ln_str, "ACK", 3) ||
+               !strncmp(strt_ln_str, "BYE", 3)) {
+        struct sip_reqs_ln *reqs_ln = sip_parse_reqs_ln(strt_ln_str, ln_len);
+        if (reqs_ln == NULL) {
+            goto error;
+        }
+
+        strt_ln->reqs_ln = reqs_ln;
+        strt_ln->type = REQUEST_LINE;
+    } else {
+        /* Message not yet supported */
+        goto error;
+    }
+
+    return strt_ln;
+
+error:
+    free(strt_ln);
+
+    return NULL;
+}
+
+void free_stat_ln(struct sip_stat_ln *stat_ln) {
+    free(stat_ln->ver);
+    free(stat_ln->reason);
+    free(stat_ln);
+}
+
+/* Parses the SIP status-line.
+ *
+ * 'start_ln' should point to the beginning of the SIP message.
+ * 'len' is the size of the status-line, not including the CRLF ending.
+ *
+ * Returns a 'sip_stat_ln' struct, which the caller is responsible for freeing.
+ *
+ * Example SIP status-line: SIP/2.0 200 OK\r\n */
+struct sip_stat_ln *
+sip_parse_stat_ln(char *start_ln, size_t len) {
+    struct sip_stat_ln *stat_ln;
+
+    stat_ln = xzalloc(sizeof(struct sip_stat_ln));
+
+    if (!sip_ln_cpy_nxt_word(&start_ln, &len, &stat_ln->ver)) {
+        goto error;
+    }
+
+    char *status = NULL;
+
+    if (!sip_ln_cpy_nxt_word(&start_ln, &len, &status)) {
+        free(status);
+        goto error;
+    }
+
+    if (!str_to_uint(status, 10, &stat_ln->status) ||
+        !is_valid_status(stat_ln->status)) {
+        free(status);
+        goto error;
+    }
+
+    stat_ln->reason = xmalloc(sizeof(char) * (len + 1));
+    ovs_strzcpy(stat_ln->reason, start_ln, len + 1);
+
+    return stat_ln;
+
+error:
+    free_stat_ln(stat_ln);
+
+    return NULL;
+}
+
+void free_reqs_ln(struct sip_reqs_ln *reqs_ln) {
+    free(reqs_ln->req_uri);
+    free(reqs_ln->ver);
+    free(reqs_ln);
+}
+
+/* Parses the SIP request-line.
+ *
+ * 'start_ln' should point to the beginning of the SIP message.
+ * 'len' is the size of the request-line, not including the CRLF ending.
+ *
+ * Returns a 'sip_reqs_ln' struct, which the caller is responsible for freeing.
+ *
+ * Example SIP request-line: INVITE sip:service@10.0.1.128:5060 SIP/2.0\r\n */
+struct sip_reqs_ln *
+sip_parse_reqs_ln(char *start_ln, size_t len) {
+    struct sip_reqs_ln *reqs_ln;
+    char *sip_mthd = NULL;
+
+    reqs_ln = xzalloc(sizeof(struct sip_reqs_ln));
+
+    if (!sip_ln_cpy_nxt_word(&start_ln, &len, &sip_mthd)) {
+        goto error;
+    }
+
+    reqs_ln->mthd = sip_mthd_to_enum(sip_mthd);
+
+    if (reqs_ln->mthd == INVALID) {
+        goto error;
+    }
+
+    if (!sip_ln_cpy_nxt_word(&start_ln, &len, &reqs_ln->req_uri)) {
+        goto error;
+    }
+
+    reqs_ln->ver = xmalloc(sizeof(char) * (len + 1));
+    ovs_strzcpy(reqs_ln->ver, start_ln, len + 1);
+
+    return reqs_ln;
+
+error:
+    free(sip_mthd);
+    free_reqs_ln(reqs_ln);
+
+    return NULL;
+}
diff --git a/lib/conntrack-sip.h b/lib/conntrack-sip.h
new file mode 100644
index 000000000..134f35f3a
--- /dev/null
+++ b/lib/conntrack-sip.h
@@ -0,0 +1,112 @@ 
+/*
+ * Copyright (c) 2017 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <sys/types.h>
+
+#include "dp-packet.h"
+
+struct sip_sdp {
+    uint32_t orig;
+    uint32_t conn;
+    uint16_t port;
+};
+
+enum sip_mthd {
+    INVITE,
+    REGISTER,
+    ACK,
+    CANCEL,
+    BYE,
+    OPTIONS,
+    INVALID,
+};
+
+struct sip_reqs_ln {
+    enum sip_mthd mthd;
+    char *req_uri;
+    char *ver;
+};
+
+struct sip_stat_ln {
+    char *ver;
+    uint32_t status;
+    char *reason;
+};
+
+enum sip_strt_type {
+    REQUEST_LINE,
+    STATUS_LINE,
+};
+
+struct sip_strt_ln {
+    union {
+        struct sip_reqs_ln *reqs_ln;
+        struct sip_stat_ln *stat_ln;
+    };
+    enum sip_strt_type type;
+};
+
+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);
+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);
+
+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);
+struct sip_sdp *
+sip_parse_sdp(char *sdp, size_t len);
+void free_strt_ln(struct sip_strt_ln *strt_ln);
+struct sip_strt_ln *
+sip_parse_strt_ln(char *start_line, size_t len);
+void free_stat_ln(struct sip_stat_ln *stat_ln);
+struct sip_stat_ln *
+sip_parse_stat_ln(char *start_ln, size_t len);
+void free_reqs_ln(struct sip_reqs_ln *reqs_ln);
+struct sip_reqs_ln *
+sip_parse_reqs_ln(char *start_ln, size_t len);
+
+/* 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 *
+sip_msg_hdr(char *sip, size_t *len) {
+
+    char *msg_hdr = sip;
+
+    sip_ln_skip(&msg_hdr, len);
+
+    return msg_hdr;
+}
+
+/* Given a pointer to the beginning of the SIP message, return a pointer to
+ * 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 *
+sip_msg_bdy(char *sip, size_t *len) {
+
+    char *msg_bdy = sip;
+
+    sip_skip_to_body(&msg_bdy, len);
+
+    return msg_bdy;
+}