Subject: rfc: gre over udp [patch]
To: None <tech-net@netbsd.org>
From: David Young <dyoung@pobox.com>
List: tech-net
Date: 08/26/2006 02:18:04
--5oH/S/bF6lOfqCQb
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

I need to tunnel packets through NAT routers to a tunnel concentrator at
my office.  To that end, I am extending gre(4) to put tunnel packets into
UDP datagrams.  I have attached a patch that contains my work in progress.
I request your feedback.

In UDP mode, gre(4) puts a GRE header onto transmitted packets, and hands
them to a UDP socket for transmission.  That is, the encapsulation looks
like IP+UDP+GRE+encapsulated packet.

There are two ways to set up a UDP gre(4) instance.  One way is to tell
the source and destination IP+port to gre(4), and let gre(4) create
the socket:

# ifconfig gre0 create link2
# ifconfig gre0 tunnel 192.168.1.107,1025 192.168.1.101,1025
# ifconfig gre0 inet 192.168.49.2 192.168.49.1
# ifconfig gre0
gre0: flags=d051<UP,POINTOPOINT,RUNNING,LINK0,LINK2,MULTICAST> mtu 1476
        tunnel inet 192.168.1.107,1025 --> 192.168.1.101,1025
        inet 192.168.49.2 -> 192.168.49.1 netmask 0xffffff00
        inet6 fe80::202:6fff:fe20:f62e%gre0 ->  prefixlen 64 scopeid 0x6

Note that to specify the UDP port number, some ifconfig(8) changes
were necessary.  Those are in the patch.

The other way to create a UDP instance is for userland to "delegate" a
UDP socket to the kernel.  I have only started to program that feature;
see case GRESSOCK in gre_ioctl().  I intend to use this feature in a
server that will run on the tunnel concentrator, authenticating remote
tunnel endpoints and setting up tunnel privacy (perhaps using IPSec)
before delegating the socket to the tunnel interface.

Dave

-- 
David Young             OJC Technologies
dyoung@ojctech.com      Urbana, IL * (217) 278-3933

--5oH/S/bF6lOfqCQb
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="gre.patch"

Index: sys/net/if_gre.c
===================================================================
RCS file: /cvsroot/src/sys/net/if_gre.c,v
retrieving revision 1.61
diff -u -p -u -p -r1.61 if_gre.c
--- sys/net/if_gre.c	23 Jul 2006 22:06:12 -0000	1.61
+++ sys/net/if_gre.c	26 Aug 2006 07:11:14 -0000
@@ -56,11 +56,14 @@ __KERNEL_RCSID(0, "$NetBSD: if_gre.c,v 1
 
 #ifdef INET
 #include <sys/param.h>
+#include <sys/file.h>
+#include <sys/filedesc.h>
 #include <sys/malloc.h>
 #include <sys/mbuf.h>
 #include <sys/proc.h>
 #include <sys/protosw.h>
 #include <sys/socket.h>
+#include <sys/socketvar.h>
 #include <sys/ioctl.h>
 #include <sys/queue.h>
 #if __NetBSD__
@@ -68,6 +71,8 @@ __KERNEL_RCSID(0, "$NetBSD: if_gre.c,v 1
 #include <sys/kauth.h>
 #endif
 
+#include <sys/kthread.h>
+
 #include <machine/cpu.h>
 
 #include <net/ethertypes.h>
@@ -126,6 +131,42 @@ static int	gre_ioctl(struct ifnet *, u_l
 
 static int	gre_compute_route(struct gre_softc *sc);
 
+static int gre_getsockname(struct socket *, struct mbuf *, struct lwp *);
+#ifdef GRESSOCK
+static int gre_getpeername(struct socket *, struct mbuf *, struct lwp *);
+static int gre_getnames(struct socket *, struct lwp *, struct sockaddr_in *,
+    struct sockaddr_in *);
+#endif /* GRESSOCK */
+
+static void
+gre_stop(int *running)
+{
+	*running = 0;
+	wakeup(running);
+}
+
+static void
+gre_join(int *running)
+{
+	int s;
+
+	s = splnet();
+	while (*running != 0) {
+		splx(s);
+		tsleep(running, PSOCK, "grejoin", 0);
+		s = splnet();
+	}
+	splx(s);
+}
+
+static void
+gre_wakeup(int *waitchan)
+{
+	printf("%s: enter\n", __func__);
+	*waitchan = 1;
+	wakeup(waitchan);
+}
+
 static int
 gre_clone_create(struct if_clone *ifc, int unit)
 {
@@ -146,7 +187,10 @@ gre_clone_create(struct if_clone *ifc, i
 	sc->sc_if.if_output = gre_output;
 	sc->sc_if.if_ioctl = gre_ioctl;
 	sc->g_dst.s_addr = sc->g_src.s_addr = INADDR_ANY;
+	sc->g_dstport = sc->g_srcport = 0;
 	sc->g_proto = IPPROTO_GRE;
+	IFQ_SET_READY(&sc->sc_snd);
+	IFQ_SET_MAXLEN(&sc->sc_snd, IFQ_MAXLEN);
 	sc->sc_if.if_flags |= IFF_LINK0;
 	if_attach(&sc->sc_if);
 	if_alloc_sadl(&sc->sc_if);
@@ -167,11 +211,365 @@ gre_clone_destroy(struct ifnet *ifp)
 	bpfdetach(ifp);
 #endif
 	if_detach(ifp);
+	gre_wakeup(&sc->sc_waitchan);
+	gre_join(&sc->sc_thread);
 	free(sc, M_DEVBUF);
 
 	return (0);
 }
 
+static void
+gre_receive(struct socket *so, caddr_t arg, int waitflag)
+{
+	struct gre_softc *sc = (struct gre_softc *)arg;
+
+	printf("%s: enter\n", __func__);
+
+	gre_wakeup(&sc->sc_waitchan);
+}
+
+static void
+gre_sodestroy(struct socket **sop)
+{
+	printf("%s: shutting down\n", __func__);
+	soshutdown(*sop, SHUT_RDWR);
+	printf("%s: closing\n", __func__);
+	soclose(*sop);
+	*sop = NULL;
+}
+
+static struct mbuf *
+gre_getsockmbuf(struct socket *so)
+{
+	struct mbuf *m;
+
+	m = m_get(M_WAIT, MT_SONAME);
+	if (m != NULL)
+		MCLAIM(m, so->so_mowner);
+	return m;
+}
+
+static void
+gre_upcallsetup(struct socket *so, caddr_t arg)
+{
+	/* XXX What if the kernel already set an upcall? */
+	so->so_upcallarg = arg;
+	so->so_upcall = gre_receive;
+	so->so_rcv.sb_flags |= SB_UPCALL;
+}
+
+static int
+gre_socreate1(struct gre_softc *sc, struct lwp *l, struct gre_soparm *sp,
+    struct socket **sop)
+{
+	int rc;
+	struct mbuf *m;
+	struct sockaddr_in *sin;
+	struct socket *so;
+
+	printf("%s: enter\n", __func__);
+	rc = socreate(AF_INET, sop, SOCK_DGRAM, IPPROTO_UDP, l);
+	if (rc != 0) {
+		printf("%s: socreate failed\n", __func__);
+		return rc;
+	}
+
+	so = *sop;
+
+	gre_upcallsetup(so, (caddr_t)sc);
+	if ((m = gre_getsockmbuf(so)) == NULL) {
+		rc = ENOBUFS;
+		goto out;
+	}
+	sin = mtod(m, struct sockaddr_in *);
+	sin->sin_len = m->m_len = sizeof(struct sockaddr_in);
+	sin->sin_family = AF_INET;
+	sin->sin_addr = sc->g_src;
+	sin->sin_port = sc->g_srcport;
+
+	printf("%s: bind 0x%08" PRIx32 " port %d\n", __func__,
+	    sin->sin_addr.s_addr, ntohs(sin->sin_port));
+	if ((rc = sobind(so, m, l)) != 0) {
+		printf("%s: sobind failed\n", __func__);
+		goto out;
+	}
+
+	if (sc->g_srcport == 0) {
+		if (gre_getsockname(so, m, l) != 0) {
+			printf("%s: gre_getsockname failed\n", __func__);
+			goto out;
+		}
+		sc->g_srcport = sin->sin_port;
+	}
+
+	sin->sin_addr = sc->g_dst;
+	sin->sin_port = sc->g_dstport;
+
+	rc = soconnect(so, m, l);
+
+	if (rc != 0) {
+		printf("%s: soconnect failed\n", __func__);
+		goto out;
+	}
+
+	*mtod(m, int *) = ip_gre_ttl;
+	rc = (*so->so_proto->pr_ctloutput)(PRCO_SETOPT, so, IPPROTO_IP, IP_TTL,
+	    &m);
+	m = NULL;
+	if (rc != 0) {
+		printf("%s: setopt ttl failed\n", __func__);
+		rc = 0;
+	}
+out:
+	m_freem(m);
+
+	if (rc != 0)
+		gre_sodestroy(sop);
+	else
+		*sp = sc->sc_sp;
+
+	return rc;
+}
+
+static void
+gre_thread1(struct gre_softc *sc, struct lwp *l)
+{
+	int flags, rc, s;
+	const struct gre_h *gh;
+	struct ifnet *ifp = &sc->sc_if;
+	struct mbuf *m;
+	struct socket *so;
+	struct uio uio;
+	struct gre_soparm sp;
+
+	printf("%s: enter\n", __func__);
+	s = splnet();
+
+	if (gre_socreate1(sc, l, &sp, &so) != 0)
+		goto out;
+
+	memset(&sp, 0, sizeof(sp));
+	memset(&uio, 0, sizeof(uio));
+
+	ifp->if_flags |= IFF_RUNNING;
+
+	for (;;) {
+		printf("%s: sleeping\n", __func__);
+		while (sc->sc_waitchan == 0) {
+			splx(s);
+			tsleep(&sc->sc_waitchan, PSOCK, "grewait", 0);
+			s = splnet();
+		}
+		sc->sc_waitchan = 0;
+		printf("%s: woke\n", __func__);
+		if ((ifp->if_flags & IFF_UP) != IFF_UP) {
+			printf("%s: not up & running; exiting\n", __func__);
+			break;
+		}
+		if (sc->g_proto != IPPROTO_UDP) {
+			printf("%s: not udp; exiting\n", __func__);
+			break;
+		}
+		/* XXX optimize */ 
+		if (memcmp(&sp, &sc->sc_sp, sizeof(sp)) != 0) {
+			printf("%s: parameters changed\n", __func__);
+#ifdef GRESSOCK
+			if (sp.sp_fp != NULL) {
+				FILE_UNUSE(sp.sp_fp, NULL);
+				sp.sp_fp = NULL;
+				so = NULL;
+			} else
+#endif /* GRESSOCK */
+				gre_sodestroy(&so);
+#ifdef GRESSOCK
+			if (sc->sc_fp != NULL) {
+				so = (struct socket *)sc->sc_fp->f_data;
+				gre_upcallsetup(so, (caddr_t)sc);
+				sp = sc->sc_sp;
+				FILE_USE(sp.sp_fp);
+			} else
+#endif /* GRESSOCK */
+			if (gre_socreate1(sc, l, &sp, &so) != 0)
+				goto out;
+		}
+		for (;;) {
+			flags = MSG_DONTWAIT;
+			uio.uio_resid = 1000000;
+			rc = (*so->so_receive)(so, NULL, &uio, &m, NULL,
+			    &flags);
+			/* TBD Back off if ECONNREFUSED (indicates
+			 * ICMP Port Unreachable)?
+			 */
+			if (rc == EWOULDBLOCK) {
+				printf("%s: so_receive EWOULDBLOCK\n",
+				    __func__);
+				break;
+			} else if (rc != 0 || m == NULL) {
+				printf("%s: rc %d m %p\n",
+				    ifp->if_xname, rc, (void *)m);
+				break;
+			}
+			if (m->m_len < sizeof(*gh) &&
+			    (m = m_pullup(m, sizeof(*gh))) == NULL) {
+				printf("%s: m_pullup failed\n", __func__);
+				continue;
+			}
+			gh = mtod(m, const struct gre_h *);
+
+			if (gre_input3(sc, m, 0, IPPROTO_GRE, gh) == 0) {
+				printf("%s: dropping unsupported\n", __func__);
+				m_freem(m);
+			}
+		}
+		for (;;) {
+			IF_DEQUEUE(&sc->sc_snd, m);
+			if (m == NULL)
+				break;
+			printf("%s: dequeue\n", __func__);
+			if ((so->so_state & SS_ISCONNECTED) == 0) {
+				printf("%s: not connected\n", __func__);
+				m_freem(m);
+				continue;
+			}
+			rc = (*so->so_send)(so, NULL, NULL, m, NULL, 0, l);
+			/* XXX handle ENOBUFS? */
+			if (rc != 0)
+				printf("%s: so_send failed\n", __func__);
+		}
+	}
+#ifdef GRESSOCK
+	if (sp.sp_fp != NULL) {
+		FILE_UNUSE(sp.sp_fp, NULL);
+		sp.sp_fp = NULL;
+	} else
+#endif /* GRESSOCK */
+		gre_sodestroy(&so);
+out:
+	printf("%s: stopping\n", __func__);
+	if (sc->g_proto == IPPROTO_UDP)
+		ifp->if_flags &= ~IFF_RUNNING;
+	IFQ_PURGE(&sc->sc_snd);
+	gre_stop(&sc->sc_thread);
+	/* must not touch sc after this! */
+	printf("%s: restore ipl\n", __func__);
+	splx(s);
+}
+
+static void
+gre_thread(void *arg)
+{
+	struct gre_softc *sc = (struct gre_softc *)arg;
+
+	gre_thread1(sc, curlwp);
+	/* must not touch sc after this! */
+	kthread_exit(0);
+}
+
+int
+gre_input3(struct gre_softc *sc, struct mbuf *m, int hlen, u_char proto,
+    const struct gre_h *gh)
+{
+	u_int16_t flags;
+#if NBPFILTER > 0
+	u_int32_t af = AF_INET;		/* af passed to BPF tap */
+#endif
+	int s, isr;
+	struct ifqueue *ifq;
+
+	sc->sc_if.if_ipackets++;
+	sc->sc_if.if_ibytes += m->m_pkthdr.len;
+
+	switch (proto) {
+	case IPPROTO_GRE:
+		hlen += sizeof(struct gre_h);
+
+		/* process GRE flags as packet can be of variable len */
+		flags = ntohs(gh->flags);
+
+		/* Checksum & Offset are present */
+		if ((flags & GRE_CP) | (flags & GRE_RP))
+			hlen += 4;
+		/* We don't support routing fields (variable length) */
+		if (flags & GRE_RP)
+			return (0);
+		if (flags & GRE_KP)
+			hlen += 4;
+		if (flags & GRE_SP)
+			hlen += 4;
+
+		switch (ntohs(gh->ptype)) { /* ethertypes */
+		case ETHERTYPE_IP: /* shouldn't need a schednetisr(), as */
+			ifq = &ipintrq;          /* we are in ip_input */
+			isr = NETISR_IP;
+			break;
+#ifdef NS
+		case ETHERTYPE_NS:
+			ifq = &nsintrq;
+			isr = NETISR_NS;
+#if NBPFILTER > 0
+			af = AF_NS;
+#endif
+			break;
+#endif
+#ifdef NETATALK
+		case ETHERTYPE_ATALK:
+			ifq = &atintrq1;
+			isr = NETISR_ATALK;
+#if NBPFILTER > 0
+			af = AF_APPLETALK;
+#endif
+			break;
+#endif
+#ifdef INET6
+		case ETHERTYPE_IPV6:
+#ifdef GRE_DEBUG
+			printf("%s: IPv6 packet\n", __func__);
+#endif
+			ifq = &ip6intrq;
+			isr = NETISR_IPV6;
+#if NBPFILTER > 0
+			af = AF_INET6;
+#endif
+			break;
+#endif
+		default:	   /* others not yet supported */
+			printf("%s: unhandled ethertype 0x%04x\n", __func__,
+			    ntohs(gh->ptype));
+			return (0);
+		}
+		break;
+	default:
+		/* others not yet supported */
+		return (0);
+	}
+
+	if (hlen > m->m_pkthdr.len) {
+		m_freem(m);
+		return (EINVAL);
+	}
+	m_adj(m, hlen);
+
+#if NBPFILTER > 0
+	if (sc->sc_if.if_bpf != NULL)
+		bpf_mtap_af(sc->sc_if.if_bpf, af, m);
+#endif /*NBPFILTER > 0*/
+
+	m->m_pkthdr.rcvif = &sc->sc_if;
+
+	s = splnet();		/* possible */
+	if (IF_QFULL(ifq)) {
+		IF_DROP(ifq);
+		m_freem(m);
+	} else {
+		IF_ENQUEUE(ifq, m);
+	}
+	/* we need schednetisr since the address family may change */
+	schednetisr(isr);
+	splx(s);
+
+	return (1);	/* packet is done, no further processing needed */
+}
+
 /*
  * The output routine. Takes a packet and encapsulates it in the protocol
  * given by sc->g_proto. See also RFC 1701 and RFC 2004
@@ -180,10 +578,11 @@ static int
 gre_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
 	   struct rtentry *rt)
 {
-	int error = 0;
+	int error = 0, hlen;
 	struct gre_softc *sc = ifp->if_softc;
-	struct greip *gh;
-	struct ip *ip;
+	struct greip *gi;
+	struct gre_h *gh;
+	struct ip *eip, *ip;
 	u_int8_t ip_tos = 0;
 	u_int16_t etype = 0;
 	struct mobile_h mob_h;
@@ -195,7 +594,7 @@ gre_output(struct ifnet *ifp, struct mbu
 		goto end;
 	}
 
-	gh = NULL;
+	gi = NULL;
 	ip = NULL;
 
 #if NBPFILTER >0
@@ -205,11 +604,16 @@ gre_output(struct ifnet *ifp, struct mbu
 
 	m->m_flags &= ~(M_BCAST|M_MCAST);
 
-	if (sc->g_proto == IPPROTO_MOBILE) {
+	switch (sc->g_proto) {
+	case IPPROTO_MOBILE:
 		if (dst->sa_family == AF_INET) {
-			struct mbuf *m0;
 			int msiz;
 
+			if (M_UNWRITABLE(m, sizeof(*ip)) &&
+			    (m = m_pullup(m, sizeof(*ip))) == NULL) {
+				error = ENOBUFS;
+				goto end;
+			}
 			ip = mtod(m, struct ip *);
 
 			memset(&mob_h, 0, MOB_H_SIZ_L);
@@ -233,31 +637,16 @@ gre_output(struct ifnet *ifp, struct mbu
 			HTONS(mob_h.proto);
 			mob_h.hcrc = gre_in_cksum((u_int16_t *)&mob_h, msiz);
 
-			if ((m->m_data - msiz) < m->m_pktdat) {
-				/* need new mbuf */
-				MGETHDR(m0, M_DONTWAIT, MT_HEADER);
-				if (m0 == NULL) {
-					IF_DROP(&ifp->if_snd);
-					m_freem(m);
-					error = ENOBUFS;
-					goto end;
-				}
-				m0->m_next = m;
-				m->m_data += sizeof(struct ip);
-				m->m_len -= sizeof(struct ip);
-				m0->m_pkthdr.len = m->m_pkthdr.len + msiz;
-				m0->m_len = msiz + sizeof(struct ip);
-				m0->m_data += max_linkhdr;
-				memcpy(mtod(m0, caddr_t), (caddr_t)ip,
-				       sizeof(struct ip));
-				m = m0;
-			} else {  /* we have some space left in the old one */
-				m->m_data -= msiz;
-				m->m_len += msiz;
-				m->m_pkthdr.len += msiz;
-				memmove(mtod(m, caddr_t), ip,
-					sizeof(struct ip));
+			M_PREPEND(m, msiz, M_DONTWAIT);
+			if (m == NULL) {
+				error = ENOBUFS;
+				goto end;
 			}
+			/* XXX Assuming that ip does not dangle after
+			 * M_PREPEND.  In practice, that's true, but
+			 * that's in M_PREPEND's contract.
+			 */
+			memmove(mtod(m, caddr_t), ip, sizeof(*ip));
 			ip = mtod(m, struct ip *);
 			memcpy((caddr_t)(ip + 1), &mob_h, (unsigned)msiz);
 			ip->ip_len = htons(ntohs(ip->ip_len) + msiz);
@@ -267,10 +656,11 @@ gre_output(struct ifnet *ifp, struct mbu
 			error = EINVAL;
 			goto end;
 		}
-	} else if (sc->g_proto == IPPROTO_GRE) {
+		break;
+	case IPPROTO_UDP:
+	case IPPROTO_GRE:
 #ifdef GRE_DEBUG
-               printf( "start gre_output/GRE, dst->sa_family=%d\n",
-                        dst->sa_family );
+               printf("%s: dst->sa_family=%d\n", __func__, dst->sa_family);
 #endif
 		switch (dst->sa_family) {
 		case AF_INET:
@@ -299,52 +689,184 @@ gre_output(struct ifnet *ifp, struct mbu
 			error = EAFNOSUPPORT;
 			goto end;
 		}
-		M_PREPEND(m, sizeof(struct greip), M_DONTWAIT);
-	} else {
+		break;
+	default:
 		IF_DROP(&ifp->if_snd);
 		m_freem(m);
 		error = EINVAL;
 		goto end;
 	}
 
-	if (m == NULL) {	/* impossible */
+	switch (sc->g_proto) {
+	case IPPROTO_GRE:
+		hlen = sizeof(struct greip);
+		break;
+	case IPPROTO_UDP:
+		hlen = sizeof(struct gre_h);
+		break;
+	default:
+		hlen = 0;
+		break;
+	}
+
+	M_PREPEND(m, hlen, M_DONTWAIT);
+
+	if (m == NULL) {
 		IF_DROP(&ifp->if_snd);
 		error = ENOBUFS;
 		goto end;
 	}
 
-	gh = mtod(m, struct greip *);
-	if (sc->g_proto == IPPROTO_GRE) {
+	switch (sc->g_proto) {
+	case IPPROTO_UDP:
+		gh = mtod(m, struct gre_h *);
+		memset(gh, 0, sizeof(*gh));
+		gh->ptype = htons(etype);
+		/* XXX Need to handle IP ToS.  Look at how I handle IP TTL. */
+		break;
+	case IPPROTO_GRE:
+		gi = mtod(m, struct greip *);
+		gh = &gi->gi_g;
+		eip = &gi->gi_i;
 		/* we don't have any GRE flags for now */
-
-		memset((void *)&gh->gi_g, 0, sizeof(struct gre_h));
-		gh->gi_ptype = htons(etype);
-	}
-
-	gh->gi_pr = sc->g_proto;
-	if (sc->g_proto != IPPROTO_MOBILE) {
-		gh->gi_src = sc->g_src;
-		gh->gi_dst = sc->g_dst;
-		((struct ip*)gh)->ip_hl = (sizeof(struct ip)) >> 2;
-		((struct ip*)gh)->ip_ttl = ip_gre_ttl;
-		((struct ip*)gh)->ip_tos = ip_tos;
-		gh->gi_len = htons(m->m_pkthdr.len);
+		memset(gh, 0, sizeof(*gh));
+		gh->ptype = htons(etype);
+		eip->ip_src = sc->g_src;
+		eip->ip_dst = sc->g_dst;
+		eip->ip_hl = (sizeof(struct ip)) >> 2;
+		eip->ip_ttl = ip_gre_ttl;
+		eip->ip_tos = ip_tos;
+		eip->ip_len = htons(m->m_pkthdr.len);
+		eip->ip_p = sc->g_proto;
+		break;
+	case IPPROTO_MOBILE:
+		eip = mtod(m, struct ip *);
+		eip->ip_p = sc->g_proto;
+		break;
+	default:
+		error = EPROTONOSUPPORT;
+		m_freem(m);
+		goto end;
 	}
 
 	ifp->if_opackets++;
 	ifp->if_obytes += m->m_pkthdr.len;
+
 	/* send it off */
-	error = ip_output(m, NULL, &sc->route, 0,
-	    (struct ip_moptions *)NULL, (struct socket *)NULL);
+	if (sc->g_proto == IPPROTO_UDP) {
+		if (IF_QFULL(&sc->sc_snd)) {
+			IF_DROP(&sc->sc_snd);
+			error = ENOBUFS;
+			m_freem(m);
+		} else {
+			IF_ENQUEUE(&sc->sc_snd, m);
+			gre_wakeup(&sc->sc_waitchan);
+			error = 0;
+		}
+	} else {
+		error = ip_output(m, NULL, &sc->route, 0,
+		    (struct ip_moptions *)NULL, (struct socket *)NULL);
+	}
   end:
 	if (error)
 		ifp->if_oerrors++;
 	return (error);
 }
 
+/* Must be called at IPL_NET. */
+static int
+gre_kick(struct gre_softc *sc)
+{
+	int rc;
+	struct ifnet *ifp = &sc->sc_if;
+
+	if (sc->g_proto == IPPROTO_UDP && (ifp->if_flags & IFF_UP) == IFF_UP &&
+	    !sc->sc_thread) {
+		sc->sc_thread = 1;
+		rc = kthread_create1(gre_thread, (void *)sc, NULL,
+		    ifp->if_xname);
+		if (rc != 0)
+			gre_stop(&sc->sc_thread);
+		return rc;
+	} else {
+		gre_wakeup(&sc->sc_waitchan);
+		return 0;
+	}
+}
+
+static int
+gre_getname(struct socket *so, int req, struct mbuf *nam, struct lwp *l)
+{
+	int s, error;
+
+	printf("%s: enter\n", __func__);
+	s = splsoftnet();
+	error = (*so->so_proto->pr_usrreq)(so, req, (struct mbuf *)0,
+	    nam, (struct mbuf *)0, l);
+	splx(s);
+	return error;
+}
+
+static int
+gre_getsockname(struct socket *so, struct mbuf *nam, struct lwp *l)
+{
+	printf("%s: enter\n", __func__);
+	return gre_getname(so, PRU_SOCKADDR, nam, l);
+}
+
+#ifdef GRESSOCK
+static int
+gre_getpeername(struct socket *so, struct mbuf *nam, struct lwp *l)
+{
+	printf("%s: enter\n", __func__);
+	return gre_getname(so, PRU_PEERADDR, nam, l);
+}
+
+static int
+gre_getnames(struct socket *so, struct lwp *l, struct sockaddr_in *src,
+    struct sockaddr_in *dst)
+{
+	struct mbuf *m;
+	struct sockaddr_in *sin;
+	int rc;
+
+	if ((m = gre_getsockmbuf(so)) == NULL)
+		return ENOBUFS;
+
+	sin = mtod(m, struct sockaddr_in *);
+
+	if ((rc = gre_getsockname(so, m, l)) != 0)
+		goto out;
+	if (sin->sin_family != AF_INET) {
+		rc = EAFNOSUPPORT;
+		goto out;
+	}
+	*src = *sin;
+
+	if ((rc = gre_getpeername(so, m, l)) != 0)
+		goto out;
+	if (sin->sin_family != AF_INET) {
+		rc = EAFNOSUPPORT;
+		goto out;
+	}
+	*dst = *sin;
+
+out:
+	m_freem(m);
+	return rc;
+}
+#endif /* GRESSOCK */
+
 static int
 gre_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
 {
+	u_char oproto;
+#ifdef GRESSOCK
+	struct file *fp, *ofp;
+	struct socket *so;
+	struct sockaddr_in dst, src;
+#endif /* GRESSOCK */
+	struct proc *p = curproc;	/* XXX */
 	struct lwp *l = curlwp;	/* XXX */
 	struct ifreq *ifr = (struct ifreq *)data;
 	struct if_laddrreq *lifr = (struct if_laddrreq *)data;
@@ -360,6 +882,8 @@ gre_ioctl(struct ifnet *ifp, u_long cmd,
 	case GRESPROTO:
 	case GRESADDRD:
 	case GRESADDRS:
+	case GRESSOCK:
+	case GREDSOCK:
 	case SIOCSLIFPHYADDR:
 	case SIOCDIFPHYADDR:
 		if ((error = kauth_authorize_generic(l->l_cred,
@@ -375,14 +899,28 @@ gre_ioctl(struct ifnet *ifp, u_long cmd,
 	switch (cmd) {
 	case SIOCSIFADDR:
 		ifp->if_flags |= IFF_UP;
+		error = gre_kick(sc);
 		break;
 	case SIOCSIFDSTADDR:
 		break;
 	case SIOCSIFFLAGS:
-		if ((ifr->ifr_flags & IFF_LINK0) != 0)
+		oproto = sc->g_proto;
+		switch (ifr->ifr_flags & (IFF_LINK0|IFF_LINK2)) {
+		case IFF_LINK0|IFF_LINK2:
+			sc->g_proto = IPPROTO_UDP;
+			if (oproto != IPPROTO_UDP)
+				ifp->if_flags &= ~IFF_RUNNING;
+			error = gre_kick(sc);
+			break;
+		case IFF_LINK0:
 			sc->g_proto = IPPROTO_GRE;
-		else
+			gre_wakeup(&sc->sc_waitchan);
+			goto recompute;
+		case 0:
 			sc->g_proto = IPPROTO_MOBILE;
+			gre_wakeup(&sc->sc_waitchan);
+			goto recompute;
+		}
 		break;
 	case SIOCSIFMTU:
 		if (ifr->ifr_mtu < 576) {
@@ -415,14 +953,22 @@ gre_ioctl(struct ifnet *ifp, u_long cmd,
 		}
 		break;
 	case GRESPROTO:
+		oproto = sc->g_proto;
 		sc->g_proto = ifr->ifr_flags;
 		switch (sc->g_proto) {
+		case IPPROTO_UDP:
+			ifp->if_flags |= IFF_LINK0|IFF_LINK2;
+			if (oproto != IPPROTO_UDP)
+				ifp->if_flags &= ~IFF_RUNNING;
+			error = gre_kick(sc);
+			break;
 		case IPPROTO_GRE:
 			ifp->if_flags |= IFF_LINK0;
-			break;
+			ifp->if_flags &= ~IFF_LINK2;
+			goto recompute;
 		case IPPROTO_MOBILE:
-			ifp->if_flags &= ~IFF_LINK0;
-			break;
+			ifp->if_flags &= ~(IFF_LINK0|IFF_LINK2);
+			goto recompute;
 		default:
 			error = EPROTONOSUPPORT;
 			break;
@@ -438,16 +984,36 @@ gre_ioctl(struct ifnet *ifp, u_long cmd,
 		 * to the remote end and mark if as up
 		 */
 		sa = &ifr->ifr_addr;
-		if (cmd == GRESADDRS)
+		if (cmd == GRESADDRS) {
 			sc->g_src = (satosin(sa))->sin_addr;
-		if (cmd == GRESADDRD)
+			sc->g_srcport = satosin(sa)->sin_port;
+		}
+		if (cmd == GRESADDRD) {
+			if (sc->g_proto == IPPROTO_UDP &&
+			    satosin(sa)->sin_port == 0) {
+				error = EINVAL;
+				break;
+			}
 			sc->g_dst = (satosin(sa))->sin_addr;
+			sc->g_dstport = satosin(sa)->sin_port;
+		}
 	recompute:
-		if ((sc->g_src.s_addr != INADDR_ANY) &&
-		    (sc->g_dst.s_addr != INADDR_ANY)) {
-			if (sc->route.ro_rt != 0) /* free old route */
+		if (sc->g_proto == IPPROTO_UDP ||
+		    (sc->g_src.s_addr != INADDR_ANY &&
+		     sc->g_dst.s_addr != INADDR_ANY)) {
+#ifdef GRESSOCK
+			if (sc->sc_fp != NULL) {
+				FILE_UNUSE(sc->sc_fp, NULL);
+				sc->sc_fp = NULL;
+			}
+#endif /* GRESSOCK */
+			if (sc->route.ro_rt != NULL) {
 				RTFREE(sc->route.ro_rt);
-			if (gre_compute_route(sc) == 0)
+				sc->route.ro_rt = NULL;
+			}
+			if (sc->g_proto == IPPROTO_UDP)
+				error = gre_kick(sc);
+			else if (gre_compute_route(sc) == 0)
 				ifp->if_flags |= IFF_RUNNING;
 			else
 				ifp->if_flags &= ~IFF_RUNNING;
@@ -469,6 +1035,47 @@ gre_ioctl(struct ifnet *ifp, u_long cmd,
 		sa = sintosa(&si);
 		ifr->ifr_addr = *sa;
 		break;
+#ifdef GRESSOCK
+	case GREDSOCK:
+		if (sc->g_proto != IPPROTO_UDP)
+			return EINVAL;
+		FILE_UNUSE(sc->sc_fp, NULL);
+		sc->sc_fp = NULL;
+		error = gre_kick(sc);
+		break;
+	case GRESSOCK:
+		if (sc->g_proto != IPPROTO_UDP)
+			return EINVAL;
+		/* getsock() will use the descriptor for us */
+		if ((error = getsock(p->p_fd, (int)ifr->ifr_value, &fp)) != 0)
+			break;
+		so = (struct socket *)fp->f_data;
+		if (so->so_type != SOCK_DGRAM) {
+			FILE_UNUSE(fp, NULL);
+			error = EINVAL;
+			break;
+		}
+		/* check address */
+		if ((error = gre_getnames(so, curlwp, &src, &dst)) != 0) {
+			FILE_UNUSE(fp, NULL);
+			break;
+		}
+
+		ofp = sc->sc_fp;
+		sc->sc_fp = fp;
+		if ((error = gre_kick(sc)) != 0) {
+			FILE_UNUSE(fp, NULL);
+			sc->sc_fp = ofp;
+			break;
+		}
+		sc->g_src = src.sin_addr;
+		sc->g_srcport = src.sin_port;
+		sc->g_dst = dst.sin_addr;
+		sc->g_dstport = dst.sin_port;
+		if (ofp != NULL)
+			FILE_UNUSE(ofp, NULL);
+		break;
+#endif /* GRESSOCK */
 	case SIOCSLIFPHYADDR:
 		if (lifr->addr.ss_family != AF_INET ||
 		    lifr->dstaddr.ss_family != AF_INET) {
@@ -480,14 +1087,15 @@ gre_ioctl(struct ifnet *ifp, u_long cmd,
 			error = EINVAL;
 			break;
 		}
-		sc->g_src = (satosin((struct sockadrr *)&lifr->addr))->sin_addr;
-		sc->g_dst =
-		    (satosin((struct sockadrr *)&lifr->dstaddr))->sin_addr;
+		sc->g_src = satosin(&lifr->addr)->sin_addr;
+		sc->g_dst = satosin(&lifr->dstaddr)->sin_addr;
+		sc->g_srcport = satosin(&lifr->addr)->sin_port;
+		sc->g_dstport = satosin(&lifr->dstaddr)->sin_port;
 		goto recompute;
 	case SIOCDIFPHYADDR:
 		sc->g_src.s_addr = INADDR_ANY;
 		sc->g_dst.s_addr = INADDR_ANY;
-		break;
+		goto recompute;
 	case SIOCGLIFPHYADDR:
 		if (sc->g_src.s_addr == INADDR_ANY ||
 		    sc->g_dst.s_addr == INADDR_ANY) {
@@ -497,16 +1105,19 @@ gre_ioctl(struct ifnet *ifp, u_long cmd,
 		memset(&si, 0, sizeof(si));
 		si.sin_family = AF_INET;
 		si.sin_len = sizeof(struct sockaddr_in);
-		si.sin_addr.s_addr = sc->g_src.s_addr;
+		si.sin_addr = sc->g_src;
+		if (sc->g_proto == IPPROTO_UDP)
+			si.sin_port = sc->g_srcport;
 		memcpy(&lifr->addr, &si, sizeof(si));
-		si.sin_addr.s_addr = sc->g_dst.s_addr;
+		si.sin_addr = sc->g_dst;
+		if (sc->g_proto == IPPROTO_UDP)
+			si.sin_port = sc->g_dstport;
 		memcpy(&lifr->dstaddr, &si, sizeof(si));
 		break;
 	default:
 		error = EINVAL;
 		break;
 	}
-
 	splx(s);
 	return (error);
 }
@@ -580,9 +1191,8 @@ gre_compute_route(struct gre_softc *sc)
 		((struct sockaddr_in *)&ro->ro_dst)->sin_addr = sc->g_dst;
 
 #ifdef DIAGNOSTIC
-	printf(", choosing %s with gateway %s", ro->ro_rt->rt_ifp->if_xname,
+	printf(", choosing %s with gateway %s\n", ro->ro_rt->rt_ifp->if_xname,
 	    inet_ntoa(((struct sockaddr_in *)(ro->ro_rt->rt_gateway))->sin_addr));
-	printf("\n");
 #endif
 
 	return 0;
Index: sys/net/if_gre.h
===================================================================
RCS file: /cvsroot/src/sys/net/if_gre.h,v
retrieving revision 1.16
diff -u -p -u -p -r1.16 if_gre.h
--- sys/net/if_gre.h	11 Dec 2005 23:05:25 -0000	1.16
+++ sys/net/if_gre.h	26 Aug 2006 07:11:14 -0000
@@ -41,18 +41,37 @@
 
 #include <sys/queue.h>
 
+#if 1
+#define GRESSOCK	_IOW('i' , 107, struct ifreq)
+#define GREDSOCK	_IOW('i' , 108, struct ifreq)
+#endif
+
+struct gre_soparm {
+	struct in_addr	sp_src;		/* source address of gre packets */
+	struct in_addr	sp_dst;		/* destination address of gre packets */
+	in_port_t	sp_srcport;	/* source port of gre packets */
+	in_port_t	sp_dstport;	/* destination port of gre packets */
+#ifdef GRESSOCK
+	struct file	*sp_fp;
+#endif /* GRESSOCK */
+};
+
 struct gre_softc {
-	struct ifnet sc_if;
+	struct ifnet		sc_if;
+	int			sc_waitchan;
+	int			sc_thread;
+	struct ifqueue		sc_snd;
+	struct gre_soparm	sc_sp;
 	LIST_ENTRY(gre_softc) sc_list;
-	int gre_unit;
-	int gre_flags;
-	struct in_addr g_src;	/* source address of gre packets */
-	struct in_addr g_dst;	/* destination address of gre packets */
 	struct route route;	/* routing entry that determines, where a
 				   encapsulated packet should go */
 	u_char g_proto;		/* protocol of encapsulator */
 };
-
+#define	g_src		sc_sp.sp_src
+#define	g_srcport	sc_sp.sp_srcport
+#define	g_dst		sc_sp.sp_dst
+#define	g_dstport	sc_sp.sp_dstport
+#define	sc_fp		sc_sp.sp_fp
 
 struct gre_h {
 	u_int16_t flags;	/* GRE flags */
@@ -152,6 +171,8 @@ LIST_HEAD(gre_softc_head, gre_softc);
 extern struct gre_softc_head gre_softc_list;
 
 u_int16_t gre_in_cksum(u_short *, u_int);
+int gre_input3(struct gre_softc *, struct mbuf *, int, u_char,
+    const struct gre_h *);
 #endif /* _KERNEL */
 
 #endif /* !_NET_IF_GRE_H_ */
Index: sys/netinet/ip_gre.c
===================================================================
RCS file: /cvsroot/src/sys/netinet/ip_gre.c,v
retrieving revision 1.40
diff -u -p -u -p -r1.40 ip_gre.c
--- sys/netinet/ip_gre.c	28 Jul 2006 17:34:13 -0000	1.40
+++ sys/netinet/ip_gre.c	26 Aug 2006 07:11:14 -0000
@@ -147,13 +147,7 @@ int
 gre_input2(struct mbuf *m, int hlen, u_char proto)
 {
 	const struct greip *gip;
-	int s, isr;
-	struct ifqueue *ifq;
 	struct gre_softc *sc;
-	u_int16_t flags;
-#if NBPFILTER > 0
-	u_int32_t af = AF_INET;		/* af passed to BPF tap */
-#endif
 
 	if ((sc = gre_lookup(m, proto)) == NULL) {
 		/* No matching tunnel or tunnel is down. */
@@ -167,97 +161,7 @@ gre_input2(struct mbuf *m, int hlen, u_c
 	}
 	gip = mtod(m, const struct greip *);
 
-	sc->sc_if.if_ipackets++;
-	sc->sc_if.if_ibytes += m->m_pkthdr.len;
-
-	switch (proto) {
-	case IPPROTO_GRE:
-		hlen += sizeof(struct gre_h);
-
-		/* process GRE flags as packet can be of variable len */
-		flags = ntohs(gip->gi_flags);
-
-		/* Checksum & Offset are present */
-		if ((flags & GRE_CP) | (flags & GRE_RP))
-			hlen += 4;
-		/* We don't support routing fields (variable length) */
-		if (flags & GRE_RP)
-			return (0);
-		if (flags & GRE_KP)
-			hlen += 4;
-		if (flags & GRE_SP)
-			hlen += 4;
-
-		switch (ntohs(gip->gi_ptype)) { /* ethertypes */
-		case ETHERTYPE_IP: /* shouldn't need a schednetisr(), as */
-			ifq = &ipintrq;          /* we are in ip_input */
-			isr = NETISR_IP;
-			break;
-#ifdef NS
-		case ETHERTYPE_NS:
-			ifq = &nsintrq;
-			isr = NETISR_NS;
-#if NBPFILTER > 0
-			af = AF_NS;
-#endif
-			break;
-#endif
-#ifdef NETATALK
-		case ETHERTYPE_ATALK:
-			ifq = &atintrq1;
-			isr = NETISR_ATALK;
-#if NBPFILTER > 0
-			af = AF_APPLETALK;
-#endif
-			break;
-#endif
-#ifdef INET6
-		case ETHERTYPE_IPV6:
-#ifdef GRE_DEBUG
-			printf( "ip_gre.c/gre_input2: IPv6 packet\n" );
-#endif
-			ifq = &ip6intrq;
-			isr = NETISR_IPV6;
-#if NBPFILTER > 0
-			af = AF_INET6;
-#endif
-			break;
-#endif
-		default:	   /* others not yet supported */
-			printf( "ip_gre.c/gre_input2: unhandled ethertype 0x%04x\n", (int) ntohs(gip->gi_ptype) );
-			return (0);
-		}
-		break;
-	default:
-		/* others not yet supported */
-		return (0);
-	}
-
-	if (hlen > m->m_pkthdr.len) {
-		m_freem(m);
-		return (EINVAL);
-	}
-	m_adj(m, hlen);
-
-#if NBPFILTER > 0
-	if (sc->sc_if.if_bpf != NULL)
-		bpf_mtap_af(sc->sc_if.if_bpf, af, m);
-#endif /*NBPFILTER > 0*/
-
-	m->m_pkthdr.rcvif = &sc->sc_if;
-
-	s = splnet();		/* possible */
-	if (IF_QFULL(ifq)) {
-		IF_DROP(ifq);
-		m_freem(m);
-	} else {
-		IF_ENQUEUE(ifq, m);
-	}
-	/* we need schednetisr since the address family may change */
-	schednetisr(isr);
-	splx(s);
-
-	return (1);	/* packet is done, no further processing needed */
+	return gre_input3(sc, m, hlen, proto, &gip->gi_g);
 }
 
 /*
@@ -273,6 +177,7 @@ gre_mobile_input(struct mbuf *m, ...)
 	struct mobip_h *mip;
 	struct ifqueue *ifq;
 	struct gre_softc *sc;
+	uint8_t *hdr;
 	int hlen, s;
 	va_list ap;
 	int msiz;
@@ -293,6 +198,7 @@ gre_mobile_input(struct mbuf *m, ...)
 			return;
 	}
 	ip = mtod(m, struct ip *);
+	/* XXX what if there are IP options? */
 	mip = mtod(m, struct mobip_h *);
 
 	sc->sc_if.if_ipackets++;
@@ -304,8 +210,8 @@ gre_mobile_input(struct mbuf *m, ...)
 	} else
 		msiz = MOB_H_SIZ_S;
 
-	if (m->m_len < (ip->ip_hl << 2) + msiz) {
-		m = m_pullup(m, (ip->ip_hl << 2) + msiz);
+	if (M_UNWRITABLE(m, hlen + msiz)) {
+		m = m_pullup(m, hlen + msiz);
 		if (m == NULL)
 			return;
 		ip = mtod(m, struct ip *);
@@ -320,14 +226,14 @@ gre_mobile_input(struct mbuf *m, ...)
 		return;
 	}
 
-	memmove(ip + (ip->ip_hl << 2), ip + (ip->ip_hl << 2) + msiz,
-		m->m_len - msiz - (ip->ip_hl << 2));
+	hdr = mtod(m, uint8_t *);
+	memmove(hdr + hlen, hdr + hlen + msiz, m->m_len - msiz - hlen);
 	m->m_len -= msiz;
 	ip->ip_len = htons(ntohs(ip->ip_len) - msiz);
 	m->m_pkthdr.len -= msiz;
 
 	ip->ip_sum = 0;
-	ip->ip_sum = in_cksum(m, (ip->ip_hl << 2));
+	ip->ip_sum = in_cksum(m, hlen);
 
 #if NBPFILTER > 0
 	if (sc->sc_if.if_bpf != NULL)
Index: sbin/ifconfig/tunnel.c
===================================================================
RCS file: /cvsroot/src/sbin/ifconfig/tunnel.c,v
retrieving revision 1.6
diff -u -p -u -p -r1.6 tunnel.c
--- sbin/ifconfig/tunnel.c	16 Jun 2006 23:48:35 -0000	1.6
+++ sbin/ifconfig/tunnel.c	26 Aug 2006 07:11:14 -0000
@@ -50,6 +50,7 @@ __RCSID("$NetBSD: tunnel.c,v 1.6 2006/06
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <util.h>
 
 #include "extern.h"
 #include "tunnel.h"
@@ -59,9 +60,10 @@ __RCSID("$NetBSD: tunnel.c,v 1.6 2006/06
 #endif
 
 void
-settunnel(const char *src, const char *dst)
+settunnel(const char *src0, const char *dst0)
 {
 	struct addrinfo hints, *srcres, *dstres;
+	char *dst, *dstport, *src, *srcport;
 	int ecode;
 	struct if_laddrreq req;
 
@@ -69,11 +71,21 @@ settunnel(const char *src, const char *d
 	hints.ai_family = afp->af_af;
 	hints.ai_socktype = SOCK_DGRAM; /*dummy*/
 
-	if ((ecode = getaddrinfo(src, NULL, &hints, &srcres)) != 0)
+	if ((src = strdup(src0)) == NULL)
+		err(EXIT_FAILURE, "%s: strdup", __func__);
+	if ((dst = strdup(dst0)) == NULL)
+		err(EXIT_FAILURE, "%s: strdup", __func__);
+
+	srcport = src;
+	(void)strsep(&srcport, ",");
+	dstport = dst;
+	(void)strsep(&dstport, ",");
+
+	if ((ecode = getaddrinfo(src, srcport, &hints, &srcres)) != 0)
 		errx(EXIT_FAILURE, "error in parsing address string: %s",
 		    gai_strerror(ecode));
 
-	if ((ecode = getaddrinfo(dst, NULL, &hints, &dstres)) != 0)
+	if ((ecode = getaddrinfo(dst, dstport, &hints, &dstres)) != 0)
 		errx(EXIT_FAILURE, "error in parsing address string: %s",
 		    gai_strerror(ecode));
 
@@ -123,6 +135,8 @@ settunnel(const char *src, const char *d
 
 	freeaddrinfo(srcres);
 	freeaddrinfo(dstres);
+	free(src);
+	free(dst);
 }
 
 /*ARGSUSED*/
@@ -134,9 +148,19 @@ deletetunnel(const char *ifname, int par
 		err(EXIT_FAILURE, "SIOCDIFPHYADDR");
 }
 
+#if 0
+static int
+goodport(const char *port)
+{
+	return strcmp(port, ",0") != 0 && strcmp(port, ",N/A") != 0;
+}
+#endif
+
 void
 tunnel_status(void)
 {
+	char dstserv[sizeof(",65535")];
+	char srcserv[sizeof(",65535")];
 	char psrcaddr[NI_MAXHOST];
 	char pdstaddr[NI_MAXHOST];
 	const int niflag = NI_NUMERICHOST;
@@ -155,15 +179,20 @@ tunnel_status(void)
 		in6_fillscopeid((struct sockaddr_in6 *)&req.addr);
 #endif /* INET6 */
 	getnameinfo((struct sockaddr *)&req.addr, req.addr.ss_len,
-	    psrcaddr, sizeof(psrcaddr), 0, 0, niflag);
+	    psrcaddr, sizeof(psrcaddr), &srcserv[1], sizeof(srcserv) - 1,
+	    niflag);
 
 #ifdef INET6
 	if (req.dstaddr.ss_family == AF_INET6)
 		in6_fillscopeid((struct sockaddr_in6 *)&req.dstaddr);
 #endif
 	getnameinfo((struct sockaddr *)&req.dstaddr, req.dstaddr.ss_len,
-	    pdstaddr, sizeof(pdstaddr), 0, 0, niflag);
+	    pdstaddr, sizeof(pdstaddr), &dstserv[1], sizeof(dstserv) - 1,
+	    niflag);
+
+	srcserv[0] = (strcmp(srcserv, "0") == 0) ? '\0' : ',';
+	dstserv[0] = (strcmp(dstserv, "0") == 0) ? '\0' : ',';
 
-	printf("\ttunnel %s %s --> %s\n", lafp ? lafp->af_name : "???",
-	    psrcaddr, pdstaddr);
+	printf("\ttunnel %s %s%s --> %s%s\n", lafp ? lafp->af_name : "???",
+	    psrcaddr, srcserv, pdstaddr, dstserv);
 }

--5oH/S/bF6lOfqCQb--