diff mbox

Contribution - L2TPv3 - resubmit

Message ID 53170860.2040100@cisco.com
State New
Headers show

Commit Message

Anton Ivanov (antivano) March 5, 2014, 11:20 a.m. UTC
Hi all,

This is a functional driver which will interoperate versus most L2TPv3 
implementations. It cannot be split into smaller patches - this is an 
entire transport.

It will get one small incremental update at a later date. Some broken 
l2tpv3 implementations require a counter field but do not process it 
correctly so for them you need a special workaround - keep the counter 
field always at zero (this unfortunately includes at least some versions 
of the l2tpv3 stack in the linux kernel).

I believe I have addressed most comments so far. If I have missed 
something I apologize. The only one I cannot address is the possible 
mis-aligned access faults for header checks on platforms that may 
penalize it. I do not know the right qemu way of doing that.

Best Regards,

A.

Comments

Andreas Färber March 5, 2014, 11:45 a.m. UTC | #1
Hi,

Am 05.03.2014 12:20, schrieb Anton Ivanov (antivano):
> This is a functional driver which will interoperate versus most L2TPv3 
> implementations. It cannot be split into smaller patches - this is an 
> entire transport.
[snip]

Please see http://wiki.qemu.org/Contribute/SubmitAPatch - in particular
the patch should be sent as inline text rather than as attachment (so
that we can comment inline on it), also it is missing a Signed-off-by
(without it patches cannot get accepted) and the commit message should
be suitable for committing. Any other text can go into a cover letter
0/1 or below --- line. You also forgot to CC the net maintainer.

Regards,
Andreas
Stefan Hajnoczi March 5, 2014, 1:35 p.m. UTC | #2
On Wed, Mar 05, 2014 at 12:45:30PM +0100, Andreas Färber wrote:
> Hi,
> 
> Am 05.03.2014 12:20, schrieb Anton Ivanov (antivano):
> > This is a functional driver which will interoperate versus most L2TPv3 
> > implementations. It cannot be split into smaller patches - this is an 
> > entire transport.
> [snip]
> 
> Please see http://wiki.qemu.org/Contribute/SubmitAPatch - in particular
> the patch should be sent as inline text rather than as attachment (so
> that we can comment inline on it), also it is missing a Signed-off-by
> (without it patches cannot get accepted) and the commit message should
> be suitable for committing. Any other text can go into a cover letter
> 0/1 or below --- line. You also forgot to CC the net maintainer.

Besides the guidelines on the wiki a quick tip:

Dry run git-send-email(1) by sending to your own email address first.
Pick patch at random on the QEMU mailing list and compare whether the
structure/style matches yours.

Once you're ready, send to qemu-devel@nongnu.org and CC
stefanha@redhat.com, afaerber@suse.de, eblake@redhat.com,
pbonzini@redhat.com.

If you get stuck, feel free to hop on #qemu on irc.oftc.net and we can
chat about how to configure git-send-email(1).

Stefan
Anton Ivanov (antivano) March 5, 2014, 2:10 p.m. UTC | #3
ire transport.

>> [snip]
>>
>> Please see http://wiki.qemu.org/Contribute/SubmitAPatch - in particular
>> the patch should be sent as inline text rather than as attachment (so
>> that we can comment inline on it), also it is missing a Signed-off-by
>> (without it patches cannot get accepted) and the commit message should
>> be suitable for committing. Any other text can go into a cover letter
>> 0/1 or below --- line. You also forgot to CC the net maintainer.
> Besides the guidelines on the wiki a quick tip:
>
> Dry run git-send-email(1) by sending to your own email address first.
> Pick patch at random on the QEMU mailing list and compare whether the
> structure/style matches yours.

I tried :) It will not play nicely with a davmail + exchange combo (at 
least in my config). Throws all kind of weird and wonderful errors. At 
some point, I will look at it and fix it, but not today - it proved 
easier to get it to work versus my personal email server.

I just finished re-subscribing from my own address, a patch resubmission 
will follow shortly.

>
> Once you're ready, send to qemu-devel@nongnu.org and CC
> stefanha@redhat.com, afaerber@suse.de, eblake@redhat.com,
> pbonzini@redhat.com.
Thanks, will do shortly.

>
> If you get stuck, feel free to hop on #qemu on irc.oftc.net and we can
> chat about how to configure git-send-email(1).

I unstuck myself by choosing not to use corporate infra for the official 
contribution sponsor which is not ideal. If I have the time I will look 
through the use of the perl smtp module in git-send and fix it.

A.
Stefan Hajnoczi March 5, 2014, 2:26 p.m. UTC | #4
On Wed, Mar 5, 2014 at 3:10 PM, Anton Ivanov (antivano)
<antivano@cisco.com> wrote:
> I unstuck myself by choosing not to use corporate infra for the official
> contribution sponsor which is not ideal. If I have the time I will look
> through the use of the perl smtp module in git-send and fix it.

Don't worry, git preserves the @cisco.com authorship even if you send
it via a private email address.  The important thing is that the git
commit was made with your Cisco identity.

In the past I had issues with an SMTP server that used TLS but didn't
do authentication.  git-send-email(1) couldn't handle that and I had
to hack the Perl script to skip the username/password part :(.

Anyway, what's the point of decentralized development if you're tied
to centralized corporate infrastructure? :-)

Stefan
Peter Maydell March 5, 2014, 5 p.m. UTC | #5
On 5 March 2014 14:10, Anton Ivanov (antivano) <antivano@cisco.com> wrote:
> I tried :) It will not play nicely with a davmail + exchange combo (at
> least in my config). Throws all kind of weird and wonderful errors. At
> some point, I will look at it and fix it, but not today - it proved
> easier to get it to work versus my personal email server.

Yes, sending patchmail through Exchange servers is not recommended;
they have a tendency to eat whitespace and otherwise mangle things.

thanks
-- PMM
diff mbox

Patch

diff --git a/net/Makefile.objs b/net/Makefile.objs
index 4854a14..160214e 100644
--- a/net/Makefile.objs
+++ b/net/Makefile.objs
@@ -2,6 +2,7 @@  common-obj-y = net.o queue.o checksum.o util.o hub.o
 common-obj-y += socket.o
 common-obj-y += dump.o
 common-obj-y += eth.o
+common-obj-$(CONFIG_LINUX) += l2tpv3.o
 common-obj-$(CONFIG_POSIX) += tap.o
 common-obj-$(CONFIG_LINUX) += tap-linux.o
 common-obj-$(CONFIG_WIN32) += tap-win32.o
diff --git a/net/clients.h b/net/clients.h
index 7793294..bbf177c 100644
--- a/net/clients.h
+++ b/net/clients.h
@@ -47,6 +47,8 @@  int net_init_tap(const NetClientOptions *opts, const char *name,
 int net_init_bridge(const NetClientOptions *opts, const char *name,
                     NetClientState *peer);
 
+int net_init_l2tpv3(const NetClientOptions *opts, const char *name,
+                    NetClientState *peer);
 #ifdef CONFIG_VDE
 int net_init_vde(const NetClientOptions *opts, const char *name,
                  NetClientState *peer);
diff --git a/net/l2tpv3.c b/net/l2tpv3.c
new file mode 100644
index 0000000..3348b5d
--- /dev/null
+++ b/net/l2tpv3.c
@@ -0,0 +1,554 @@ 
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ * Copyright (c) 2012-2014 Cisco Systems
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <linux/ip.h>
+#include <netdb.h>
+#include "config-host.h"
+#include "net/net.h"
+#include "clients.h"
+#include "monitor/monitor.h"
+#include "qemu-common.h"
+#include "qemu/error-report.h"
+#include "qemu/option.h"
+#include "qemu/sockets.h"
+#include "qemu/iov.h"
+#include "qemu/main-loop.h"
+
+
+/* The buffer size needs to be investigated for optimum numbers and
+ * optimum means of paging in on different systems. This size is
+ * chosen to be sufficient to accommodate one packet with some headers
+ */
+
+#define BUFFER_ALIGN sysconf(_SC_PAGESIZE)
+#define BUFFER_SIZE 2048
+#define IOVSIZE 2
+#define MAX_L2TPV3_MSGCNT 32 
+#define MAX_L2TPV3_IOVCNT (MAX_L2TPV3_MSGCNT * IOVSIZE)
+
+#ifndef IPPROTO_L2TP
+#define IPPROTO_L2TP 0x73
+#endif
+
+typedef struct NetL2TPV3State {
+    NetClientState nc;
+    int fd;
+    int state; 
+    unsigned int index;
+    unsigned int packet_len;
+
+    /* 
+     *	these are used for xmit - that happens packet a time
+     *	and for first sign of life packet (easier to parse that once)
+     */ 
+
+    uint8_t *header_buf;
+    struct iovec *vec;
+
+    /* 
+     * these are used for receive - try to "eat" up to 32 packets at a time
+     */
+
+    struct mmsghdr *msgvec;
+
+    /*
+     * peer address
+     */
+
+    struct sockaddr_storage *dgram_dst; 
+    uint32_t dst_size;
+
+    /*
+     * L2TPv3 parameters
+     */
+
+    uint64_t rx_cookie;
+    uint64_t tx_cookie;
+    uint32_t rx_session;
+    uint32_t tx_session;
+    uint32_t header_size;
+    uint32_t counter;
+
+    /*
+     * Precomputed offsets
+     */
+
+    uint32_t offset;   
+    uint32_t cookie_offset;
+    uint32_t counter_offset;
+    uint32_t session_offset;
+
+    /* Flags */
+
+    bool ipv6;
+    bool udp;
+    bool has_counter;
+    bool cookie;
+    bool cookie_is_64;
+
+} NetL2TPV3State;
+
+typedef struct NetL2TPV3ListenState {
+    NetClientState nc;
+    char *model;
+    char *name;
+    int fd;
+} NetL2TPV3ListenState;
+
+static int l2tpv3_form_header(NetL2TPV3State *s) 
+{
+    uint32_t *header;
+    uint32_t *session;
+    uint64_t *cookie64;
+    uint32_t *cookie32;
+    uint32_t *counter;
+
+    if (s->udp) {
+	header = (uint32_t *) s->header_buf;
+	stl_be_p(header, 0x30000);
+    }
+    session = (uint32_t *) (s->header_buf + s->session_offset);
+    stl_be_p(session, s->tx_session);
+
+    if (s->cookie) {
+	if (s->cookie_is_64) {
+	    cookie64 = (uint64_t *)(s->header_buf + s->cookie_offset);
+	    stq_be_p(cookie64, s->tx_cookie);
+	} else {
+	    cookie32 = (uint32_t *) (s->header_buf + s->cookie_offset);
+	    stl_be_p(cookie32, s->tx_cookie);
+	}
+    }
+
+    if (s->has_counter) {
+	counter = (uint32_t *)(s->header_buf + s->counter_offset);
+	stl_be_p(counter, ++s->counter);
+    }
+    return 0;
+}
+
+static ssize_t net_l2tpv3_receive_dgram_iov(NetClientState *nc, 
+				    const struct iovec *iov, 
+				    int iovcnt)
+{
+    NetL2TPV3State *s = DO_UPCAST(NetL2TPV3State, nc, nc);
+
+    struct msghdr message;
+    int ret;
+
+    if (iovcnt > MAX_L2TPV3_IOVCNT - 1) {
+	error_report("iovec too long %d > %d, change l2tpv3.h\n", iovcnt, MAX_L2TPV3_IOVCNT);
+	return -1;
+    }
+    l2tpv3_form_header(s);
+    memcpy(s->vec + 1, iov, iovcnt * sizeof(struct iovec));
+    s->vec->iov_base = s->header_buf;
+    s->vec->iov_len = s->offset;
+    message.msg_name = s->dgram_dst;
+    message.msg_namelen = s->dst_size;
+    message.msg_iov = (struct iovec *) s->vec;
+    message.msg_iovlen = iovcnt + 1; 
+    message.msg_control = NULL;
+    message.msg_controllen = 0;
+    message.msg_flags = 0;
+    do {
+	ret = sendmsg(s->fd, &message, 0);
+    } while ((ret == -1) && (errno == EINTR));
+    if (ret > 0) {
+	ret -= s->offset; 
+    } else if (ret == 0) {
+	ret = iov_size(iov, iovcnt);
+    } 
+    return ret;
+}
+
+static ssize_t net_l2tpv3_receive_dgram(NetClientState *nc, 
+					const uint8_t *buf, 
+					size_t size)
+{
+    NetL2TPV3State *s = DO_UPCAST(NetL2TPV3State, nc, nc);
+
+    struct iovec * vec;
+    struct msghdr message;
+    ssize_t ret = 0;
+
+    l2tpv3_form_header(s);
+    vec = s->vec;
+    vec->iov_base = s->header_buf;
+    vec->iov_len = s->offset;
+    vec++;
+    vec->iov_base = (void *) buf;
+    vec->iov_len = size;
+    message.msg_name = s->dgram_dst;
+    message.msg_namelen = s->dst_size;
+    message.msg_iov = (struct iovec *) s->vec;
+    message.msg_iovlen = 2;
+    message.msg_control = NULL;
+    message.msg_controllen = 0;
+    message.msg_flags = 0;
+    do {
+	ret = sendmsg(s->fd, &message, 0);
+    } while ((ret == -1) && (errno == EINTR));
+    if (ret > 0) {
+	ret -= s->offset; 
+    } else if (ret == 0) {
+	ret = size;
+    } 
+    return ret;
+}
+
+static int l2tpv3_verify_header(NetL2TPV3State *s, 
+				    uint8_t *buf) 
+{
+
+    uint64_t *cookie64;
+    uint32_t *cookie32;
+    uint32_t *session;
+
+    if ((!s->udp) && (!s->ipv6)){
+	buf += sizeof(struct iphdr) /* fix for ipv4 raw */;
+    } 
+    if (s->cookie) {
+	if (s->cookie_is_64) {
+	    /* 64 bit cookie */
+	    cookie64 = (uint64_t *)(buf + s->cookie_offset);
+	    if (ldq_be_p(cookie64) != s->rx_cookie) {
+		error_report("unknown cookie id\n");
+		return -1; 
+	    }
+	} else {
+	    cookie32 = (uint32_t *)(buf + s->cookie_offset);
+	    if (ldl_be_p(cookie32) != * (uint32_t *) &s->rx_cookie) {
+		error_report("unknown cookie id\n");
+		return -1 ; 
+	    }
+	}
+    }
+    session = (uint32_t *) (buf + s->session_offset);
+    if (ldl_be_p(session) != s->rx_session) {
+	error_report("session mismatch\n");
+	return -1;
+    }	
+    return 0;
+}
+
+static void net_l2tpv3_send(void *opaque)
+{
+    NetL2TPV3State *s = opaque;
+
+    int i, count, offset;
+    struct mmsghdr * msgvec;
+    struct iovec * vec;
+
+    msgvec = s->msgvec;
+    offset = s->offset;
+    if ((!s->udp) && (!s->ipv6)){
+	offset +=   sizeof(struct iphdr);
+    }  
+    count = recvmmsg(s->fd, msgvec, MAX_L2TPV3_MSGCNT, MSG_DONTWAIT, NULL);
+    for (i=0;i<count;i++) {
+	if (msgvec->msg_len > 0) {
+	    vec = msgvec->msg_hdr.msg_iov;
+	    if (
+		(msgvec->msg_len > offset) && 
+		(l2tpv3_verify_header(s, vec->iov_base) == 0)
+		) {
+		vec++;
+		qemu_send_packet(&s->nc, vec->iov_base, msgvec->msg_len - offset);
+	    } else {
+		error_report("l2tpv3 header verification failed\n");
+		vec++; 
+	    }
+	} 
+	msgvec++;
+    }
+}
+
+static void destroy_vector(struct mmsghdr * msgvec, int count, int iovcount) 
+{
+    int i, j;
+    struct iovec * iov;
+    struct mmsghdr * cleanup = msgvec;
+    if (cleanup) {
+	for (i=0;i<count;i++) {
+	    if (cleanup->msg_hdr.msg_iov) {
+		iov = cleanup->msg_hdr.msg_iov;
+		for (j=0;j<iovcount;j++) {
+		    if (iov->iov_base) {
+			g_free(iov->iov_base);
+		    }
+		    iov++;
+		}
+		g_free(cleanup->msg_hdr.msg_iov);
+	    }
+	    cleanup++;
+	}
+	g_free(msgvec);
+    }
+}
+
+static struct mmsghdr * build_l2tpv3_vector(NetL2TPV3State *s, int count) 
+{
+    int i;
+    struct iovec * iov;
+    struct mmsghdr * msgvec, *result;
+
+    msgvec = g_malloc(sizeof(struct mmsghdr) * count);
+    result = msgvec;
+    for (i=0;i < count ;i++) {
+	msgvec->msg_hdr.msg_name = NULL;
+	msgvec->msg_hdr.msg_namelen = 0;
+	iov =  g_malloc(sizeof(struct iovec) * IOVSIZE);
+	msgvec->msg_hdr.msg_iov = iov;
+	if ((!s->udp) && (!s->ipv6)){
+	    /* fix for ipv4 raw */;
+	    iov->iov_base = g_malloc(s->offset + sizeof(struct iphdr)); 
+	    iov->iov_len = s->offset + sizeof (struct iphdr);
+	} else {
+	    iov->iov_base = g_malloc(s->offset);
+	    iov->iov_len = s->offset;
+	}
+	iov++ ;
+	iov->iov_base = qemu_memalign(BUFFER_ALIGN,BUFFER_SIZE);
+	iov->iov_len = BUFFER_SIZE;
+	msgvec->msg_hdr.msg_iovlen = 2;
+	msgvec->msg_hdr.msg_control = NULL;
+	msgvec->msg_hdr.msg_controllen = 0;
+	msgvec->msg_hdr.msg_flags = 0;
+	msgvec++;
+    }
+    return result;
+}
+
+static void net_l2tpv3_cleanup(NetClientState *nc) 
+{
+    NetL2TPV3State *s = DO_UPCAST(NetL2TPV3State, nc, nc);
+    qemu_set_fd_handler(s->fd, NULL, NULL, NULL);
+    close(s->fd);
+    destroy_vector(s->msgvec, MAX_L2TPV3_MSGCNT, IOVSIZE);
+    g_free(s->header_buf);
+    g_free(s->dgram_dst);
+}
+
+static NetClientInfo net_l2tpv3_info = {
+    .type = NET_CLIENT_OPTIONS_KIND_L2TPV3,
+    .size = sizeof(NetL2TPV3State),
+    .receive = net_l2tpv3_receive_dgram,
+    .receive_iov = net_l2tpv3_receive_dgram_iov,
+    .cleanup = net_l2tpv3_cleanup,
+};
+
+int net_init_l2tpv3(const NetClientOptions *opts,
+                    const char *name,
+                    NetClientState *peer) 
+{
+
+
+    const NetdevL2TPv3Options * l2tpv3;
+    NetL2TPV3State *s;
+    NetClientState *nc;
+    int fd, gairet;
+    struct addrinfo hints;
+    struct addrinfo * result = NULL;
+    char * srcport, * dstport;
+
+    nc = qemu_new_net_client(&net_l2tpv3_info, peer, "l2tpv3", name);
+
+    s = DO_UPCAST(NetL2TPV3State, nc, nc);
+
+    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_L2TPV3);
+    l2tpv3 = opts->l2tpv3;
+
+    if (l2tpv3->has_ipv6 && l2tpv3->ipv6) {
+	s->ipv6 = l2tpv3->ipv6;
+    } else {
+	s->ipv6 = false;
+    }
+
+    if (l2tpv3->has_rxcookie || l2tpv3->has_txcookie) {
+	if (l2tpv3->has_rxcookie && l2tpv3->has_txcookie) {
+	    s->cookie = true;
+	} else {
+	    return -1;
+	}
+    } else {
+	s->cookie = false;
+    }
+
+    if (l2tpv3->has_cookie64 || l2tpv3->cookie64) {
+	s->cookie_is_64  = true;
+    } else {
+	s->cookie_is_64  = false;
+    }
+
+    if (l2tpv3->has_udp && l2tpv3->udp) {
+	s->udp = true;
+	if (!(l2tpv3->has_srcport && l2tpv3->has_dstport)) {
+	    error_report("l2tpv3_open : need both src and dst port for udp\n");
+	    return -1;
+	} else {
+	    srcport = l2tpv3->srcport;
+	    dstport = l2tpv3->dstport;
+	}
+    } else {
+	s->udp = false;
+	srcport = NULL;
+	dstport = NULL;
+    }
+
+
+    s->offset = 4;
+    s->session_offset = 0;
+    s->cookie_offset = 4;
+    s->counter_offset = 4;
+
+    s->tx_session = l2tpv3->txsession;
+    if (l2tpv3->has_rxsession) {
+	s->rx_session = l2tpv3->rxsession;
+    } else {
+	s->rx_session = s->tx_session;
+    }
+
+    if (s->cookie) {
+	s->rx_cookie = l2tpv3->rxcookie;
+	s->tx_cookie = l2tpv3->txcookie;
+	if (s->cookie_is_64 == true) {
+	    /* 64 bit cookie */
+	    s->offset += 8;
+	    s->counter_offset += 8;
+	} else {
+	    /* 32 bit cookie */
+	    s->offset += 4;
+	    s->counter_offset +=4;
+	}
+    }
+
+    memset(&hints, 0, sizeof(hints));
+    
+    if (s->ipv6) {
+	hints.ai_family = AF_INET6;
+    } else {
+	hints.ai_family = AF_INET;
+    }
+    if (s->udp) {
+	hints.ai_socktype = SOCK_DGRAM;
+	hints.ai_protocol = 0;
+	s->offset += 4;
+	s->counter_offset += 4;
+	s->session_offset += 4;
+	s->cookie_offset += 4;
+    } else {
+	hints.ai_socktype = SOCK_RAW;
+	hints.ai_protocol = IPPROTO_L2TP;
+    }
+    
+    gairet= getaddrinfo(l2tpv3->src, srcport, &hints, &result);
+
+    if ((gairet !=0) || (result == NULL)) {
+	error_report("l2tpv3_open : could not resolve src, errno = %s\n", gai_strerror(gairet));
+	return -1;
+    }
+
+    if ((fd = socket(result->ai_family, result->ai_socktype, result->ai_protocol)) == -1) {
+	fd = -errno;
+	error_report("l2tpv3_open : socket creation failed, errno = %d\n", -fd);
+	freeaddrinfo(result);
+	return fd;
+    } 
+    if (bind(fd, (struct sockaddr *) result->ai_addr, result->ai_addrlen)) {
+	error_report("l2tpv3_open :  could not bind socket err=%i\n", errno);
+	close(fd);
+	return -1;
+    }
+
+    freeaddrinfo(result);
+
+    memset(&hints, 0, sizeof(hints));
+    
+    if (s->ipv6) {
+	hints.ai_family = AF_INET6;
+    } else {
+	hints.ai_family = AF_INET;
+    }
+    if (s->udp) {
+	hints.ai_socktype = SOCK_DGRAM;
+	hints.ai_protocol = 0;
+    } else {
+	hints.ai_socktype = SOCK_RAW;
+	hints.ai_protocol = IPPROTO_L2TP;
+    }
+
+    gairet= getaddrinfo(l2tpv3->dst, dstport, &hints, &result);
+    if ((gairet !=0) || (result == NULL)) {
+	error_report("l2tpv3_open : could not resolve dst, error = %s\n", gai_strerror(gairet));
+	return -1;
+    }
+
+    s->dgram_dst = g_malloc(sizeof(struct sockaddr_storage));
+    memset(s->dgram_dst, '\0' , sizeof(struct sockaddr_storage));
+    memcpy(s->dgram_dst, result->ai_addr, result->ai_addrlen);
+    s->dst_size = result->ai_addrlen;
+
+    freeaddrinfo(result);
+
+    if (l2tpv3->has_counter && l2tpv3->counter) {
+	s->has_counter = true;
+	s->offset += 4;
+    } else {
+	s->has_counter = false;
+    }
+
+    if (l2tpv3->has_offset) {
+	/* extra offset */
+	s->offset += l2tpv3->offset;
+    }
+
+    s->msgvec = build_l2tpv3_vector(s, MAX_L2TPV3_MSGCNT);
+    s->vec = g_malloc(sizeof(struct iovec) * MAX_L2TPV3_IOVCNT);    
+    if ((!s->udp) && (!s->ipv6)){
+	s->header_buf = g_malloc(s->offset + sizeof (struct iphdr));    
+    } else {
+	s->header_buf = g_malloc(s->offset);    
+    }
+
+    qemu_set_nonblock(fd);
+
+    if (fd < 0)
+	return -1;
+
+    s->fd = fd;
+    s->counter = 0;
+
+    qemu_set_fd_handler(s->fd, net_l2tpv3_send, NULL, s);
+
+    if (!s) {
+	error_report("l2tpv3_open : failed to set fd handler\n");
+	return -1;
+    }
+    snprintf(s->nc.info_str, sizeof(s->nc.info_str),
+             "l2tpv3: connected");
+    return 0;
+}
+
diff --git a/net/net.c b/net/net.c
index 0a88e68..ef40bfe 100644
--- a/net/net.c
+++ b/net/net.c
@@ -132,6 +132,43 @@  int parse_host_port(struct sockaddr_in *saddr, const char *str)
     return 0;
 }
 
+int parse_host_port6(struct sockaddr_in6 *saddr, const char *str)
+{
+    char buf[512];
+    char *p, *ip, *port;
+
+    strncpy((char *) &buf, str, 511);
+    ip = (char *) &buf;
+    port = NULL;
+    int portint;
+
+
+    for (p = (char *) &buf; (p < (char *) &buf + strlen(str)) || (p < (char *) &buf + 512); p++){
+      if (*p == '[') {
+	 ip = p + 1;
+      }
+      if (*p == ']') {
+	 *p = '\0';
+        if (*(p + 1) == ':') {
+	    port = p + 2;
+	}
+	break;
+      }
+    } 
+
+    saddr->sin6_family = AF_INET6;
+    if (!inet_pton(AF_INET6, ip, &saddr->sin6_addr)) {
+         return -1;
+    } 
+    if (port) {
+      sscanf(port, "%i", &portint);
+      saddr->sin6_port = htons(portint);
+    }
+    return 0;
+}
+
+
+
 void qemu_format_nic_info_str(NetClientState *nc, uint8_t macaddr[6])
 {
     snprintf(nc->info_str, sizeof(nc->info_str),
@@ -731,6 +768,9 @@  static int (* const net_client_init_fun[NET_CLIENT_OPTIONS_KIND_MAX])(
         [NET_CLIENT_OPTIONS_KIND_BRIDGE]    = net_init_bridge,
 #endif
         [NET_CLIENT_OPTIONS_KIND_HUBPORT]   = net_init_hubport,
+#ifdef CONFIG_LINUX
+        [NET_CLIENT_OPTIONS_KIND_L2TPV3]    = net_init_l2tpv3,
+#endif
 };
 
 
diff --git a/qapi-schema.json b/qapi-schema.json
index 83fa485..7d9f69f 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2940,6 +2940,57 @@ 
     '*localaddr': 'str',
     '*udp':       'str' } }
 
+# @NetdevL2TPv3Options
+#
+# Connect the VLAN to Ethernet over L2TPv3 Static tunnel
+#
+# @src :source address
+#
+# @dst :destination address
+#
+# @srcport :#optional source port - mandatory for udp, optional for ip
+#
+# @dstport :#optional destination port - mandatory for udp, optional for ip
+#
+# @ipv6 :#optional - force the use of ipv6 
+#
+# @udp :#optional - use the udp version of l2tpv3 encapsulation
+#
+# @cookie64:#optional - use 64 bit coookies
+#
+# @counter :#optional have sequence counter
+# 
+# @txcookie :#optional 32 or 64 bit transmit cookie 
+# 
+# @rxcookie :#optional 32 or 64 bit receive cookie 
+# 
+# @txsession : 32 bit transmit session
+# 
+# @rxsession : 32 bit receive session - if not specified set to the same value as transmit
+# 
+# @optional : additional offset - allows the insertion of additional application-specific data before the packet payload
+# 
+#
+# Since 1.2
+##
+##
+{ 'type': 'NetdevL2TPv3Options',
+  'data': {
+    'src':	  'str', 
+    'dst':	  'str', 
+    '*srcport':	  'str', 
+    '*dstport':	  'str', 
+    '*ipv6':	  'bool', 
+    '*udp':	  'bool', 
+    '*cookie64':  'bool', 
+    '*counter':   'bool', 
+    '*txcookie':  'uint64',
+    '*rxcookie':  'uint64',
+    'txsession': 'uint32',
+    '*rxsession': 'uint32',
+    '*offset':  'uint32' } }
+
+##
 ##
 # @NetdevVdeOptions
 #
@@ -3014,13 +3065,16 @@ 
 # A discriminated record of network device traits.
 #
 # Since 1.2
-##
+#
+# Added in 2.0 - l2tpv3 
+#
 { 'union': 'NetClientOptions',
   'data': {
     'none':     'NetdevNoneOptions',
     'nic':      'NetLegacyNicOptions',
     'user':     'NetdevUserOptions',
     'tap':      'NetdevTapOptions',
+    'l2tpv3':   'NetdevL2TPv3Options',
     'socket':   'NetdevSocketOptions',
     'vde':      'NetdevVdeOptions',
     'dump':     'NetdevDumpOptions',