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

Message ID 20180212230829.31624-2-tiagolam@gmail.com
State RFC
Headers show
Series
  • Initial support for new SIP Alg.
Related show

Commit Message

Tiago Lam Feb. 12, 2018, 11:08 p.m.
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

Patch

diff --git a/lib/automake.mk b/lib/automake.mk
index 38d2a999d..1afdb5756 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;
+}