diff --git a/lib/conntrack-sip.c b/lib/conntrack-sip.c
index c4ae8dc50..129ee5e7f 100644
--- a/lib/conntrack-sip.c
+++ b/lib/conntrack-sip.c
@@ -777,6 +777,95 @@ sip_parse_tcp_msg(char *sip, size_t sip_len) {
     return out_msg;
 }
 
+/* Same as 'sip_parse_tcp' function  above, but for SIP messages transported
+ * over UDP. Given a pointer to the beginning of a SIP message that has been
+ * transmitted over UDP, parses the SIP message. In case the message is not
+ * well framed or considered invalid, NULL is returned.
+ *
+ * 'sip' should point to the beginning of the SIP message.
+ * 'sip_len' the size of the whole SIP message.
+ *
+ * Returns a 'sip_msg' struct, which the caller is responsible for freeing.
+ * The returned 'sip_msg' has pointers to the parsed message-line, message-
+ * header, and message-body, as well as appropriate lengths set */
+struct sip_msg *
+sip_parse_udp(char *sip, size_t sip_len) {
+    size_t orig_len = sip_len;
+    size_t cur_len = orig_len;
+
+    struct sip_strt_ln *strt_ln = sip_parse_strt_ln(sip, sip_len);
+    if (strt_ln == NULL) {
+        return NULL;
+    }
+
+    /* Pointer to the beginning of SIP message-header */
+    char *msg_hdr = sip_msg_hdr(sip, &sip_len);
+
+    size_t vlen;
+    int cont_len;
+    char *val = sip_get_hdr_val(msg_hdr, sip_len, SIPH_CONT_LEN, &vlen);
+    if (val == NULL) {
+        cont_len = 0;
+    } else {
+        cont_len = sip_hdr_to_uint(val, vlen);
+        if (cont_len < 0) {
+            return NULL;
+        }
+    }
+
+    /* Pointer to the beginning of SIP message-body */
+    char *msg_bdy = sip_msg_bdy(msg_hdr, &sip_len);
+    if (msg_bdy == NULL) {
+        return NULL;
+    }
+
+    cur_len -= sip_len;
+
+    size_t miss_len;
+    if (cont_len == 0) {
+        miss_len = orig_len - cur_len;
+    } else {
+        /* If a "Content-Length" header exists "the message body is assumed to
+         * contain that many bytes" ("18.3 Framing", rfc3261) */
+        size_t exp_len = cur_len + cont_len;
+        if (exp_len > orig_len) {
+            /* Error: "Content-Length" points to more data than available */
+            return NULL;
+        } else {
+            /* rfc3261, additional bytes in the transport packet beyond th end
+             * of the body MUST be discarded */
+            miss_len = exp_len - cur_len;
+        }
+    }
+
+    /* At most, UDP transports a single SIP message */
+    struct sip_msg *out_msg = xmalloc(sizeof(*out_msg));
+    out_msg->strt_ln = strt_ln;
+    out_msg->msg_hdr = msg_hdr;
+    out_msg->msg_bdy = msg_bdy;
+    out_msg->bdy_len = miss_len;
+
+    return out_msg;
+}
+
+static void
+handle_sip_udp(struct conntrack *ct, struct dp_packet *pkt,
+               const struct conn *conn, long long now) {
+    struct udp_header *uh = dp_packet_l4(pkt);
+    size_t udp_len = ntohs(uh->udp_len) - UDP_HEADER_LEN;
+    char *udp_hdr = (char *) uh;
+
+    /* Move to beginning of UDP payload, where the SIP payload is */
+    char *sip = udp_hdr + UDP_HEADER_LEN;
+    size_t sip_len = udp_len;
+
+    struct sip_msg *out_msg = sip_parse_udp(sip, sip_len);
+
+    sip_process_msg(ct, pkt, conn, now, out_msg);
+
+    free(out_msg);
+}
+
 /* Parses SIP messages over TCP. It relies on the "Content-Length" header to
  * know the full length of each SIP message. It returns a list of sip_msg
  * structs, with pointers to each parsed message-line, message-header, and
@@ -842,8 +931,6 @@ handle_sip_tcp(struct conntrack *ct, struct dp_packet *pkt,
 
 /* XXX Only handles IPv4 for now, must include IPv6 in the future */
 /* XXX NAT support hasn't been properly tested yet */
-/* XXX No support for SIP over UDP either (not tested at least), which might
- * be required in the future */
 void
 handle_sip(struct conntrack *ct,
            const struct conn_lookup_ctx *ctx OVS_UNUSED,
@@ -861,6 +948,9 @@ handle_sip(struct conntrack *ct,
     if (conn->key.nw_proto == IPPROTO_TCP) {
         handle_sip_tcp(ct, pkt, conn, ctx->reply, now);
         return;
+    } else if (conn->key.nw_proto == IPPROTO_UDP) {
+        handle_sip_udp(ct, pkt, conn, now);
+        return;
     } else {
         /* Unrecognised transport for a SIP message */
         pkt->md.ct_state |= CS_TRACKED | CS_INVALID;
diff --git a/lib/conntrack-sip.h b/lib/conntrack-sip.h
index 755ded565..ce284f443 100644
--- a/lib/conntrack-sip.h
+++ b/lib/conntrack-sip.h
@@ -128,6 +128,8 @@ sip_process_msgs(struct conntrack *ct, struct dp_packet *pkt,
                  struct ovs_list *out_msgs);
 void
 free_sip_msg(struct sip_msg *out_msg);
+struct sip_msg *
+sip_parse_udp(char *sip, size_t sip_len);
 struct ovs_list *
 sip_parse_tcp(char *sip, size_t sip_len);
 void
diff --git a/lib/conntrack.c b/lib/conntrack.c
index 9387b0165..31ca5b39b 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -493,8 +493,12 @@ 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 == sip_src_port || th->tcp_dst == sip_dst_port)) {
         return CT_ALG_CTL_SIP;
+    } else if (ip_proto == IPPROTO_UDP &&
+               (uh->udp_src == sip_src_port || uh->udp_dst == sip_dst_port)) {
+        return CT_ALG_CTL_SIP;
     }
 
+
     return CT_ALG_CTL_NONE;
 }
 
diff --git a/tests/system-traffic.at b/tests/system-traffic.at
index e8dba721a..0445f67f8 100644
--- a/tests/system-traffic.at
+++ b/tests/system-traffic.at
@@ -2941,6 +2941,77 @@ tcp,orig=(src=10.0.2.10,dst=10.0.1.10,sport=<cleared>,dport=<cleared>),reply=(sr
 OVS_TRAFFIC_VSWITCHD_STOP
 AT_CLEANUP
 
+dnl Setups two namespaces, at_ns0 and at_ns1, connected to a bridge, br0, and
+dnl runs sipp in both in the following form:
+dnl - at_ns0 loads an UAS scenario and waits for requests;
+dnl - at_ns1 loads an UAC scenario, which will connect to at_ns0.
+dnl
+dnl Aside from RTP (which is over UDP), this tests sends SIP over UDP over
+dnl IPv4 and no NAT is performed.
+AT_SETUP([conntrack - SIP over UDP over IPv4])
+AT_SKIP_IF([test $HAVE_SIPP = no])
+CHECK_CONNTRACK()
+CHECK_CONNTRACK_ALG()
+
+OVS_TRAFFIC_VSWITCHD_START()
+
+ADD_NAMESPACES(at_ns0, at_ns1)
+
+ADD_VETH(p0, at_ns0, br0, "10.0.1.10/24")
+NS_CHECK_EXEC([at_ns0], [ip link set dev p0 address 00:00:00:00:01:10])
+ADD_VETH(p1, at_ns1, br0, "10.0.2.10/24")
+NS_CHECK_EXEC([at_ns1], [ip link set dev p1 address 00:00:00:00:02:10])
+NS_CHECK_EXEC([at_ns0], [ip route add 10.0.2.0/24 via 10.0.1.10 dev p0])
+NS_CHECK_EXEC([at_ns1], [ip route add 10.0.1.0/24 via 10.0.2.10 dev p1])
+
+dnl Allow any traffic from at_ns1->at_ns0.
+dnl Only allow return traffic from at_ns0->at_ns1 (and arp, icmp).
+AT_DATA([flows.txt], [dnl
+dnl track all IPv4 traffic
+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=2,udp,action=ct(alg=sip,commit),1
+table=0,priority=100,in_port=1,udp,action=ct(table=1)
+table=0,priority=50,in_port=1,udp,action=ct(table=2)
+table=0,priority=50,in_port=2,udp,action=ct(table=2)
+dnl
+dnl Table 1
+dnl
+dnl Allow new TCPv4 TCP connections.
+table=1,in_port=1,udp,ct_state=+trk+est,action=2
+table=1,in_port=1,udp,ct_state=+trk+rel,action=2
+dnl
+dnl Table 2
+dnl
+dnl Allow RTP (UDP) connections through.
+table=2,in_port=2,udp,ct_state=+rel,action=1
+table=2,in_port=1,udp,ct_state=+rel,action=2
+])
+
+AT_CHECK([ovs-ofctl --bundle add-flows br0 flows.txt])
+
+NETNS_DAEMONIZE([at_ns0], [sipp -mi 10.0.1.10 -m 1 -d 20000 -sf $srcdir/sipp/uas_happy_case_scenario.xml], [sipp_uas.pid])
+OVS_WAIT_UNTIL([ip netns exec at_ns0 netstat -l | grep sip])
+
+dnl Traffic between at_ns1<->at_ns0 should now start flowing.
+NS_CHECK_EXEC([at_ns1], [sipp 10.0.1.10 -rp 1s -m 1 -d 23000 -mi 10.0.2.10 -sf $srcdir/sipp/uac_happy_case_scenario.xml
+], [0], [stdout], [stderr])
+
+dnl Wait until uas in at_ns0 is done processing all calls.
+dnl sipp in at_ns1 (running as uac) is finished with the calls, and sipp in
+dnl at_ns0 (running as uas) should be finished as well. If not, then something
+dnl is not right and the test should fail.
+OVS_WAIT_WHILE([ip netns exec at_ns0 netstat -l | grep sip])
+
+dnl Discards CLOSE_WAIT and CLOSING
+AT_CHECK([ovs-appctl dpctl/dump-conntrack | FORMAT_CT(10.0.1.10)], [0], [dnl
+udp,orig=(src=10.0.2.10,dst=10.0.1.10,sport=<cleared>,dport=<cleared>),reply=(src=10.0.1.10,dst=10.0.2.10,sport=<cleared>,dport=<cleared>),helper=sip
+])
+
+OVS_TRAFFIC_VSWITCHD_STOP
+AT_CLEANUP
+
 AT_BANNER([conntrack - NAT])
 
 AT_SETUP([conntrack - simple SNAT])
diff --git a/tests/test-sip.c b/tests/test-sip.c
index 602f52671..78249379e 100644
--- a/tests/test-sip.c
+++ b/tests/test-sip.c
@@ -78,6 +78,30 @@ static char *sip_invite_missing_cr =
     "Content-Length:   0\r\n"
     "\r\n";
 
+static char *sip_invite_no_cont_length =
+    "INVITE sip:service@10.0.1.10:5060 SIP/2.0\r\n"
+    "Via: SIP/2.0/TCP 10.0.2.10:5060;branch=z9hG4bK-26201-1-0\r\n"
+    "From: sipp <sip:sipp@10.0.2.10:5060>;tag=26201SIPpTag001\r\n"
+    "To: sut <sip:service@10.0.1.10:5060>\r\n"
+    "Call-ID: 1-26201@10.0.2.10\r\n"
+    "CSeq: 1 INVITE\r\n"
+    "Contact: sip:sipp@10.0.2.10:5060\r\n"
+    "Max-Forwards: 70\r\n"
+    "\r\n";
+
+static char *sip_invite_big_cont_length =
+    "INVITE sip:service@10.0.1.10:5060 SIP/2.0\r\n"
+    "Via: SIP/2.0/TCP 10.0.2.10:5060;branch=z9hG4bK-26201-1-0\r\n"
+    "From: sipp <sip:sipp@10.0.2.10:5060>;tag=26201SIPpTag001\r\n"
+    "To: sut <sip:service@10.0.1.10:5060>\r\n"
+    "Call-ID: 1-26201@10.0.2.10\r\n"
+    "CSeq: 1 INVITE\r\n"
+    "Contact: sip:sipp@10.0.2.10:5060\r\n"
+    "Max-Forwards: 70\r\n"
+    "Content-Type: application/sdp\r\n"
+    "Content-Length:   500\r\n"
+    "\r\n";
+
 static void
 test_valid_sdp(void)
 {
@@ -165,7 +189,138 @@ compose_dummy_tcp(struct dp_packet *pkt, const struct eth_addr eth_src,
     return th + TCP_HEADER_LEN;
 }
 
-/* Test sip_parse_tcp function against multiple cases, including:
+static void *
+compose_dummy_udp(struct dp_packet *pkt, const struct eth_addr eth_src,
+                  const struct eth_addr eth_dst, ovs_be32 ipv4_src,
+                  ovs_be32 ipv4_dst, ovs_be16 port_src, ovs_be16 port_dst,
+                  int size)
+{
+    struct udp_header *uh;
+
+    eth_compose(pkt, eth_dst, eth_src, ETH_TYPE_IP, IP_HEADER_LEN);
+    uh = compose_dummy_ipv4(pkt, IPPROTO_UDP, ipv4_src, ipv4_dst, 0, 1,
+                            UDP_HEADER_LEN, size);
+
+    packet_set_udp_port(pkt, port_src, port_dst);
+    uh->udp_len = htons(UDP_HEADER_LEN + size);
+
+    return uh + UDP_HEADER_LEN;
+}
+
+/* Test sip_parse_udp function against multiple cases, including:
+ * - A SIP message with the "Content-Length" header set, checking if it is
+ *   bigger than the transport packet or not;
+ * - A SIP message without the "Content-Length" set, which means teh whole UDP
+ *   payload should be taken into account. */
+
+static void
+test_handle_udp_no_content_length(void)
+{
+    uint32_t stub[128 / 4];
+    struct dp_packet packet;
+
+    dp_packet_use_stub(&packet, stub, sizeof stub);
+    dp_packet_clear(&packet);
+
+    compose_dummy_udp(&packet, DUMMY_ETH_ADDR, DUMMY_ETH_ADDR,
+                      htonl(DUMMY_IP_ADDR),htonl(DUMMY_IP_ADDR),
+                      htons(IPPORT_SIP), htons(IPPORT_SIP),
+                      strlen(sip_invite_no_cont_length) + strlen(valid_sdp));
+    /* Compose an SIP message. */
+    dp_packet_put(&packet, sip_invite_no_cont_length,
+                  strlen(sip_invite_no_cont_length));
+    dp_packet_put(&packet, valid_sdp, strlen(valid_sdp));
+
+    struct udp_header *uh = dp_packet_l4(&packet);
+    size_t udp_len = ntohs(uh->udp_len) - UDP_HEADER_LEN;
+    char *udp_hdr = (char *) uh;
+    /* Move to beginning of UDP payload, where the SIP payload is */
+    char *sip = udp_hdr + UDP_HEADER_LEN;
+    size_t sip_len = udp_len;
+
+    struct sip_msg *out_msg = sip_parse_udp(sip, sip_len);
+
+    ovs_assert(out_msg != NULL);
+
+    struct sip_strt_ln *strt_ln = out_msg->strt_ln;
+    sip_len = out_msg->bdy_len;
+
+    ovs_assert(strt_ln != NULL);
+    ovs_assert(strt_ln->type == REQUEST_LINE);
+    ovs_assert(strt_ln->reqs_ln->mthd == INVITE);
+    ovs_assert(sip_len == strlen(valid_sdp));
+}
+
+static void
+test_handle_udp_with_short_content_length(void)
+{
+    uint32_t stub[128 / 4];
+    struct dp_packet packet;
+
+    dp_packet_use_stub(&packet, stub, sizeof stub);
+    dp_packet_clear(&packet);
+
+    compose_dummy_udp(&packet, DUMMY_ETH_ADDR, DUMMY_ETH_ADDR,
+                      htonl(DUMMY_IP_ADDR),htonl(DUMMY_IP_ADDR),
+                      htons(IPPORT_SIP), htons(IPPORT_SIP),
+                      strlen(sip_invite_with_sdp) + strlen(valid_sdp));
+    /* Compose an SIP message. */
+    dp_packet_put(&packet, sip_invite_with_sdp, strlen(sip_invite_with_sdp));
+    dp_packet_put(&packet, valid_sdp, strlen(valid_sdp));
+    /* Add more data available than needed */
+    dp_packet_put_zeros(&packet, 100);
+
+    struct udp_header *uh = dp_packet_l4(&packet);
+    size_t udp_len = ntohs(uh->udp_len) - UDP_HEADER_LEN;
+    char *udp_hdr = (char *) uh;
+    /* Move to beginning of UDP payload, where the SIP payload is */
+    char *sip = udp_hdr + UDP_HEADER_LEN;
+    size_t sip_len = udp_len;
+
+    struct sip_msg *out_msg = sip_parse_udp(sip, sip_len);
+
+    ovs_assert(out_msg != NULL);
+
+    struct sip_strt_ln *strt_ln = out_msg->strt_ln;
+    sip_len = out_msg->bdy_len;
+
+    ovs_assert(strt_ln != NULL);
+    ovs_assert(strt_ln->type == REQUEST_LINE);
+    ovs_assert(strt_ln->reqs_ln->mthd == INVITE);
+    ovs_assert(sip_len == strlen(valid_sdp));
+}
+
+static void
+test_handle_udp_with_big_content_length(void)
+{
+    uint32_t stub[128 / 4];
+    struct dp_packet packet;
+
+    dp_packet_use_stub(&packet, stub, sizeof stub);
+    dp_packet_clear(&packet);
+
+    compose_dummy_udp(&packet, DUMMY_ETH_ADDR, DUMMY_ETH_ADDR,
+                      htonl(DUMMY_IP_ADDR),htonl(DUMMY_IP_ADDR),
+                      htons(IPPORT_SIP), htons(IPPORT_SIP),
+                      strlen(sip_invite_big_cont_length) + strlen(valid_sdp));
+    /* Compose an SIP message. */
+    dp_packet_put(&packet, sip_invite_big_cont_length,
+                  strlen(sip_invite_big_cont_length));
+    dp_packet_put(&packet, valid_sdp, strlen(valid_sdp));
+
+    struct udp_header *uh = dp_packet_l4(&packet);
+    size_t udp_len = ntohs(uh->udp_len) - UDP_HEADER_LEN;
+    char *udp_hdr = (char *) uh;
+    /* Move to beginning of UDP payload, where the SIP payload is */
+    char *sip = udp_hdr + UDP_HEADER_LEN;
+    size_t sip_len = udp_len;
+
+    struct sip_msg *out_msg = sip_parse_udp(sip, sip_len);
+
+    ovs_assert(out_msg == NULL);
+}
+
+/* Test handle_sip_tcp function against multiple cases, including:
  * - A single TCP packet transporting a single SIP message;
  * - A single TCP packet transporting multiple SIP messages;
  * - A SIP message being carried across multiple TCP packets, i.e., a first
@@ -321,6 +476,9 @@ test_sip_main(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
     run_test(test_handle_single_tcp_single_sip);
     run_test(test_handle_single_tcp_multiple_sip);
     run_test(test_handle_single_tcp_incomplete_sip);
+    run_test(test_handle_udp_no_content_length);
+    run_test(test_handle_udp_with_short_content_length);
+    run_test(test_handle_udp_with_big_content_length);
 
     printf("executed %d tests\n", num_tests);
 }
