xref: /openbsd-src/sys/net/if_gre.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*      $OpenBSD: if_gre.c,v 1.69 2014/07/12 18:44:22 tedu Exp $ */
2 /*	$NetBSD: if_gre.c,v 1.9 1999/10/25 19:18:11 drochner Exp $ */
3 
4 /*
5  * Copyright (c) 1998 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Heiko W.Rupp <hwr@pilhuhn.de>
10  *
11  * IPv6-over-GRE contributed by Gert Doering <gert@greenie.muc.de>
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
23  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
24  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
25  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
26  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  */
34 
35 /*
36  * Encapsulate L3 protocols into IP, per RFC 1701 and 1702.
37  * See gre(4) for more details.
38  * Also supported: IP in IP encapsulation (proto 55) per RFC 2004.
39  */
40 
41 #include "gre.h"
42 #if NGRE > 0
43 
44 #include "bpfilter.h"
45 #include "pf.h"
46 
47 #include <sys/param.h>
48 #include <sys/mbuf.h>
49 #include <sys/socket.h>
50 #include <sys/sockio.h>
51 #include <sys/kernel.h>
52 #include <sys/systm.h>
53 #include <sys/timeout.h>
54 
55 #include <net/if.h>
56 #include <net/if_types.h>
57 #include <net/netisr.h>
58 #include <net/route.h>
59 
60 #ifdef INET
61 #include <netinet/in.h>
62 #include <netinet/in_systm.h>
63 #include <netinet/ip.h>
64 #include <netinet/ip_var.h>
65 #include <netinet/if_ether.h>
66 #else
67 #error "if_gre used without inet"
68 #endif
69 
70 #if NBPFILTER > 0
71 #include <net/bpf.h>
72 #endif
73 
74 #if NPF > 0
75 #include <net/pfvar.h>
76 #endif
77 
78 #include <net/if_gre.h>
79 
80 #ifndef GRE_RECURSION_LIMIT
81 #define GRE_RECURSION_LIMIT	3   /* How many levels of recursion allowed */
82 #endif /* GRE_RECURSION_LIMIT */
83 
84 /*
85  * It is not easy to calculate the right value for a GRE MTU.
86  * We leave this task to the admin and use the same default that
87  * other vendors use.
88  */
89 #define GREMTU 1476
90 
91 int	gre_clone_create(struct if_clone *, int);
92 int	gre_clone_destroy(struct ifnet *);
93 
94 struct gre_softc_head gre_softc_list;
95 struct if_clone gre_cloner =
96     IF_CLONE_INITIALIZER("gre", gre_clone_create, gre_clone_destroy);
97 
98 /*
99  * We can control the acceptance of GRE and MobileIP packets by
100  * altering the sysctl net.inet.gre.allow and net.inet.mobileip.allow values
101  * respectively. Zero means drop them, all else is acceptance.  We can also
102  * control acceptance of WCCPv1-style GRE packets through the
103  * net.inet.gre.wccp value, but be aware it depends upon normal GRE being
104  * allowed as well.
105  *
106  */
107 int gre_allow = 0;
108 int gre_wccp = 0;
109 int ip_mobile_allow = 0;
110 
111 void gre_keepalive(void *);
112 void gre_send_keepalive(void *);
113 void gre_link_state(struct gre_softc *);
114 
115 void
116 greattach(int n)
117 {
118 	LIST_INIT(&gre_softc_list);
119 	if_clone_attach(&gre_cloner);
120 }
121 
122 int
123 gre_clone_create(struct if_clone *ifc, int unit)
124 {
125 	struct gre_softc *sc;
126 	int s;
127 
128 	sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT|M_ZERO);
129 	if (!sc)
130 		return (ENOMEM);
131 	snprintf(sc->sc_if.if_xname, sizeof sc->sc_if.if_xname, "%s%d",
132 	    ifc->ifc_name, unit);
133 	sc->sc_if.if_softc = sc;
134 	sc->sc_if.if_type = IFT_TUNNEL;
135 	sc->sc_if.if_addrlen = 0;
136 	sc->sc_if.if_hdrlen = 24; /* IP + GRE */
137 	sc->sc_if.if_mtu = GREMTU;
138 	sc->sc_if.if_flags = IFF_POINTOPOINT|IFF_MULTICAST;
139 	sc->sc_if.if_output = gre_output;
140 	sc->sc_if.if_ioctl = gre_ioctl;
141 	sc->sc_if.if_collisions = 0;
142 	sc->sc_if.if_ierrors = 0;
143 	sc->sc_if.if_oerrors = 0;
144 	sc->sc_if.if_ipackets = 0;
145 	sc->sc_if.if_opackets = 0;
146 	sc->g_dst.s_addr = sc->g_src.s_addr = INADDR_ANY;
147 	sc->g_proto = IPPROTO_GRE;
148 	sc->sc_if.if_flags |= IFF_LINK0;
149 	sc->sc_ka_state = GRE_STATE_UKNWN;
150 
151 	timeout_set(&sc->sc_ka_hold, gre_keepalive, sc);
152 	timeout_set(&sc->sc_ka_snd, gre_send_keepalive, sc);
153 
154 	if_attach(&sc->sc_if);
155 	if_alloc_sadl(&sc->sc_if);
156 
157 #if NBPFILTER > 0
158 	bpfattach(&sc->sc_if.if_bpf, &sc->sc_if, DLT_LOOP, sizeof(u_int32_t));
159 #endif
160 	s = splnet();
161 	LIST_INSERT_HEAD(&gre_softc_list, sc, sc_list);
162 	splx(s);
163 
164 	return (0);
165 }
166 
167 int
168 gre_clone_destroy(struct ifnet *ifp)
169 {
170 	struct gre_softc *sc = ifp->if_softc;
171 	int s;
172 
173 	s = splnet();
174 	timeout_del(&sc->sc_ka_snd);
175 	timeout_del(&sc->sc_ka_hold);
176 	LIST_REMOVE(sc, sc_list);
177 	splx(s);
178 
179 	if_detach(ifp);
180 
181 	free(sc, M_DEVBUF, 0);
182 	return (0);
183 }
184 
185 /*
186  * The output routine. Takes a packet and encapsulates it in the protocol
187  * given by sc->g_proto. See also RFC 1701 and RFC 2004.
188  */
189 
190 int
191 gre_output(struct ifnet *ifp, struct mbuf *m, struct sockaddr *dst,
192 	   struct rtentry *rt)
193 {
194 	int error = 0;
195 	struct gre_softc *sc = (struct gre_softc *) (ifp->if_softc);
196 	struct greip *gh = NULL;
197 	struct ip *inp = NULL;
198 	u_int8_t ip_tos = 0;
199 	u_int16_t etype = 0;
200 	struct mobile_h mob_h;
201 	struct m_tag *mtag;
202 
203 	if ((ifp->if_flags & IFF_UP) == 0 ||
204 	    sc->g_src.s_addr == INADDR_ANY || sc->g_dst.s_addr == INADDR_ANY) {
205 		m_freem(m);
206 		error = ENETDOWN;
207 		goto end;
208 	}
209 
210 #ifdef DIAGNOSTIC
211 	if (ifp->if_rdomain != rtable_l2(m->m_pkthdr.ph_rtableid)) {
212 		printf("%s: trying to send packet on wrong domain. "
213 		    "if %d vs. mbuf %d, AF %d\n", ifp->if_xname,
214 		    ifp->if_rdomain, rtable_l2(m->m_pkthdr.ph_rtableid),
215 		    dst->sa_family);
216 	}
217 #endif
218 
219 	/* Try to limit infinite recursion through misconfiguration. */
220 	for (mtag = m_tag_find(m, PACKET_TAG_GRE, NULL); mtag;
221 	     mtag = m_tag_find(m, PACKET_TAG_GRE, mtag)) {
222 		if (!bcmp((caddr_t)(mtag + 1), &ifp, sizeof(struct ifnet *))) {
223 			IF_DROP(&ifp->if_snd);
224 			m_freem(m);
225 			error = EIO;
226 			goto end;
227 		}
228 	}
229 
230 	mtag = m_tag_get(PACKET_TAG_GRE, sizeof(struct ifnet *), M_NOWAIT);
231 	if (mtag == NULL) {
232 		IF_DROP(&ifp->if_snd);
233 		m_freem(m);
234 		error = ENOBUFS;
235 		goto end;
236 	}
237 	bcopy(&ifp, (caddr_t)(mtag + 1), sizeof(struct ifnet *));
238 	m_tag_prepend(m, mtag);
239 
240 	m->m_flags &= ~(M_BCAST|M_MCAST);
241 
242 #if NBPFILTER > 0
243 	if (ifp->if_bpf)
244 		bpf_mtap_af(ifp->if_bpf, dst->sa_family, m, BPF_DIRECTION_OUT);
245 #endif
246 
247 	if (sc->g_proto == IPPROTO_MOBILE) {
248 		if (ip_mobile_allow == 0) {
249 			IF_DROP(&ifp->if_snd);
250 			m_freem(m);
251 			error = EACCES;
252 			goto end;
253 		}
254 
255 		if (dst->sa_family == AF_INET) {
256 			struct mbuf *m0;
257 			int msiz;
258 
259 			/*
260 			 * Make sure the complete IP header (with options)
261 			 * is in the first mbuf.
262 			 */
263 			if (m->m_len < sizeof(struct ip)) {
264 				m = m_pullup(m, sizeof(struct ip));
265 				if (m == NULL) {
266 					IF_DROP(&ifp->if_snd);
267 					error = ENOBUFS;
268 					goto end;
269 				} else
270 					inp = mtod(m, struct ip *);
271 
272 				if (m->m_len < inp->ip_hl << 2) {
273 					m = m_pullup(m, inp->ip_hl << 2);
274 					if (m == NULL) {
275 						IF_DROP(&ifp->if_snd);
276 						error = ENOBUFS;
277 						goto end;
278 					}
279 				}
280 			}
281 
282 			inp = mtod(m, struct ip *);
283 
284 			bzero(&mob_h, MOB_H_SIZ_L);
285 			mob_h.proto = (inp->ip_p) << 8;
286 			mob_h.odst = inp->ip_dst.s_addr;
287 			inp->ip_dst.s_addr = sc->g_dst.s_addr;
288 
289 			/*
290 			 * If the packet comes from our host, we only change
291 			 * the destination address in the IP header.
292 			 * Otherwise we need to save and change the source.
293 			 */
294 			if (inp->ip_src.s_addr == sc->g_src.s_addr) {
295 				msiz = MOB_H_SIZ_S;
296 			} else {
297 				mob_h.proto |= MOB_H_SBIT;
298 				mob_h.osrc = inp->ip_src.s_addr;
299 				inp->ip_src.s_addr = sc->g_src.s_addr;
300 				msiz = MOB_H_SIZ_L;
301 			}
302 
303 			HTONS(mob_h.proto);
304 			mob_h.hcrc = gre_in_cksum((u_int16_t *) &mob_h, msiz);
305 
306 			/* Squeeze in the mobility header */
307 			if ((m->m_data - msiz) < m->m_pktdat) {
308 				/* Need new mbuf */
309 				MGETHDR(m0, M_DONTWAIT, MT_HEADER);
310 				if (m0 == NULL) {
311 					IF_DROP(&ifp->if_snd);
312 					m_freem(m);
313 					error = ENOBUFS;
314 					goto end;
315 				}
316 				M_MOVE_HDR(m0, m);
317 
318 				m0->m_len = msiz + (inp->ip_hl << 2);
319 				m0->m_data += max_linkhdr;
320 				m0->m_pkthdr.len = m->m_pkthdr.len + msiz;
321 				m->m_data += inp->ip_hl << 2;
322 				m->m_len -= inp->ip_hl << 2;
323 
324 				bcopy((caddr_t) inp, mtod(m0, caddr_t),
325 				    sizeof(struct ip));
326 
327 				m0->m_next = m;
328 				m = m0;
329 			} else {  /* we have some space left in the old one */
330 				m->m_data -= msiz;
331 				m->m_len += msiz;
332 				m->m_pkthdr.len += msiz;
333 				bcopy(inp, mtod(m, caddr_t),
334 				    inp->ip_hl << 2);
335 			}
336 
337 			/* Copy Mobility header */
338 			inp = mtod(m, struct ip *);
339 			bcopy(&mob_h, (caddr_t)(inp + 1), (unsigned) msiz);
340 			inp->ip_len = htons(ntohs(inp->ip_len) + msiz);
341 		} else {  /* AF_INET */
342 			IF_DROP(&ifp->if_snd);
343 			m_freem(m);
344 			error = EINVAL;
345 			goto end;
346 		}
347 	} else if (sc->g_proto == IPPROTO_GRE) {
348 		if (gre_allow == 0) {
349 			IF_DROP(&ifp->if_snd);
350 			m_freem(m);
351 			error = EACCES;
352 			goto end;
353 		}
354 
355 		switch(dst->sa_family) {
356 		case AF_INET:
357 			if (m->m_len < sizeof(struct ip)) {
358 				m = m_pullup(m, sizeof(struct ip));
359 				if (m == NULL) {
360 					IF_DROP(&ifp->if_snd);
361 					error = ENOBUFS;
362 					goto end;
363 				}
364 			}
365 
366 			inp = mtod(m, struct ip *);
367 			ip_tos = inp->ip_tos;
368 			etype = ETHERTYPE_IP;
369 			break;
370 #ifdef INET6
371 		case AF_INET6:
372 			etype = ETHERTYPE_IPV6;
373 			break;
374 #endif
375 #ifdef MPLS
376 		case AF_MPLS:
377 			if (m->m_flags & (M_BCAST | M_MCAST))
378 				etype = ETHERTYPE_MPLS_MCAST;
379 			else
380 				etype = ETHERTYPE_MPLS;
381 			break;
382 #endif
383 		default:
384 			IF_DROP(&ifp->if_snd);
385 			m_freem(m);
386 			error = EAFNOSUPPORT;
387 			goto end;
388 		}
389 
390 		M_PREPEND(m, sizeof(struct greip), M_DONTWAIT);
391 	} else {
392 		IF_DROP(&ifp->if_snd);
393 		m_freem(m);
394 		error = EINVAL;
395 		goto end;
396 	}
397 
398 	if (m == NULL) {
399 		IF_DROP(&ifp->if_snd);
400 		error = ENOBUFS;
401 		goto end;
402 	}
403 
404 	gh = mtod(m, struct greip *);
405 	if (sc->g_proto == IPPROTO_GRE) {
406 		/* We don't support any GRE flags for now */
407 
408 		bzero((void *) &gh->gi_g, sizeof(struct gre_h));
409 		gh->gi_ptype = htons(etype);
410 	}
411 
412 	gh->gi_pr = sc->g_proto;
413 	if (sc->g_proto != IPPROTO_MOBILE) {
414 		gh->gi_src = sc->g_src;
415 		gh->gi_dst = sc->g_dst;
416 		((struct ip *) gh)->ip_hl = (sizeof(struct ip)) >> 2;
417 		((struct ip *) gh)->ip_ttl = ip_defttl;
418 		((struct ip *) gh)->ip_tos = ip_tos;
419 		gh->gi_len = htons(m->m_pkthdr.len);
420 	}
421 
422 	ifp->if_opackets++;
423 	ifp->if_obytes += m->m_pkthdr.len;
424 
425 
426 	m->m_pkthdr.ph_rtableid = sc->g_rtableid;
427 
428 #if NPF > 0
429 	pf_pkt_addr_changed(m);
430 #endif
431 
432 	/* Send it off */
433 	error = ip_output(m, NULL, &sc->route, 0, NULL, NULL, 0);
434   end:
435 	if (error)
436 		ifp->if_oerrors++;
437 	return (error);
438 }
439 
440 int
441 gre_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
442 {
443 
444 	struct ifreq *ifr = (struct ifreq *)data;
445 	struct ifaddr *ifa = (struct ifaddr *)data;
446 	struct if_laddrreq *lifr = (struct if_laddrreq *)data;
447 	struct ifkalivereq *ikar = (struct ifkalivereq *)data;
448 	struct gre_softc *sc = ifp->if_softc;
449 	int s;
450 	struct sockaddr_in si;
451 	struct sockaddr *sa = NULL;
452 	int error = 0;
453 	struct proc *prc = curproc;		/* XXX */
454 
455 	s = splnet();
456 	switch(cmd) {
457 	case SIOCSIFADDR:
458 		ifp->if_flags |= IFF_UP;
459 		ifa->ifa_rtrequest = p2p_rtrequest;
460 		break;
461 	case SIOCSIFDSTADDR:
462 		break;
463 	case SIOCSIFFLAGS:
464 		if ((ifr->ifr_flags & IFF_LINK0) != 0)
465 			sc->g_proto = IPPROTO_GRE;
466 		else
467 			sc->g_proto = IPPROTO_MOBILE;
468 		break;
469 	case SIOCSIFMTU:
470 		if (ifr->ifr_mtu < 576) {
471 			error = EINVAL;
472 			break;
473 		}
474 		ifp->if_mtu = ifr->ifr_mtu;
475 		break;
476 	case SIOCGIFMTU:
477 		ifr->ifr_mtu = sc->sc_if.if_mtu;
478 		break;
479 	case SIOCGIFHARDMTU:
480 		ifr->ifr_hardmtu = sc->sc_if.if_hardmtu;
481 		break;
482 	case SIOCADDMULTI:
483 	case SIOCDELMULTI:
484 		break;
485 	case GRESPROTO:
486 		/* Check for superuser */
487 		if ((error = suser(prc, 0)) != 0)
488 			break;
489 
490 		sc->g_proto = ifr->ifr_flags;
491 		switch (sc->g_proto) {
492 		case IPPROTO_GRE:
493 			ifp->if_flags |= IFF_LINK0;
494 			break;
495 		case IPPROTO_MOBILE:
496 			ifp->if_flags &= ~IFF_LINK0;
497 			break;
498 		default:
499 			error = EPROTONOSUPPORT;
500 			break;
501 		}
502 		break;
503 	case GREGPROTO:
504 		ifr->ifr_flags = sc->g_proto;
505 		break;
506 	case GRESADDRS:
507 	case GRESADDRD:
508 		/* Check for superuser */
509 		if ((error = suser(prc, 0)) != 0)
510 			break;
511 
512 		/*
513 		 * set tunnel endpoints and mark if as up
514 		 */
515 		sa = &ifr->ifr_addr;
516 		if (cmd == GRESADDRS )
517 			sc->g_src = (satosin(sa))->sin_addr;
518 		if (cmd == GRESADDRD )
519 			sc->g_dst = (satosin(sa))->sin_addr;
520 recompute:
521 		if ((sc->g_src.s_addr != INADDR_ANY) &&
522 		    (sc->g_dst.s_addr != INADDR_ANY)) {
523 			if (sc->route.ro_rt != 0)
524 				RTFREE(sc->route.ro_rt);
525 			/* ip_output() will do the lookup */
526 			bzero(&sc->route, sizeof(sc->route));
527 			ifp->if_flags |= IFF_UP;
528 		}
529 		break;
530 	case GREGADDRS:
531 		bzero(&si, sizeof(si));
532 		si.sin_family = AF_INET;
533 		si.sin_len = sizeof(struct sockaddr_in);
534 		si.sin_addr.s_addr = sc->g_src.s_addr;
535 		sa = sintosa(&si);
536 		ifr->ifr_addr = *sa;
537 		break;
538 	case GREGADDRD:
539 		bzero(&si, sizeof(si));
540 		si.sin_family = AF_INET;
541 		si.sin_len = sizeof(struct sockaddr_in);
542 		si.sin_addr.s_addr = sc->g_dst.s_addr;
543 		sa = sintosa(&si);
544 		ifr->ifr_addr = *sa;
545 		break;
546 	case SIOCSETKALIVE:
547 		if ((error = suser(prc, 0)) != 0)
548 			break;
549 		if (ikar->ikar_timeo < 0 || ikar->ikar_timeo > 86400 ||
550 		    ikar->ikar_cnt < 0 || ikar->ikar_cnt > 256) {
551 			error = EINVAL;
552 			break;
553 		}
554 		sc->sc_ka_timout = ikar->ikar_timeo;
555 		sc->sc_ka_cnt = ikar->ikar_cnt;
556 		if (sc->sc_ka_timout == 0 || sc->sc_ka_cnt == 0) {
557 			sc->sc_ka_timout = 0;
558 			sc->sc_ka_cnt = 0;
559 			sc->sc_ka_state = GRE_STATE_UKNWN;
560 			gre_link_state(sc);
561 			break;
562 		}
563 		if (!timeout_pending(&sc->sc_ka_snd)) {
564 			sc->sc_ka_holdmax = sc->sc_ka_cnt;
565 			timeout_add(&sc->sc_ka_snd, 1);
566 			timeout_add_sec(&sc->sc_ka_hold, sc->sc_ka_timout *
567 			    sc->sc_ka_cnt);
568 		}
569 		break;
570 	case SIOCGETKALIVE:
571 		ikar->ikar_timeo = sc->sc_ka_timout;
572 		ikar->ikar_cnt = sc->sc_ka_cnt;
573 		break;
574 	case SIOCSLIFPHYADDR:
575 		if ((error = suser(prc, 0)) != 0)
576 			break;
577 		if (lifr->addr.ss_family != AF_INET ||
578 		    lifr->dstaddr.ss_family != AF_INET) {
579 			error = EAFNOSUPPORT;
580 			break;
581 		}
582 		if (lifr->addr.ss_len != sizeof(si) ||
583 		    lifr->dstaddr.ss_len != sizeof(si)) {
584 			error = EINVAL;
585 			break;
586 		}
587 		sc->g_src = ((struct sockaddr_in *)&lifr->addr)->sin_addr;
588 		sc->g_dst = ((struct sockaddr_in *)&lifr->dstaddr)->sin_addr;
589 		goto recompute;
590 	case SIOCDIFPHYADDR:
591 		if ((error = suser(prc, 0)) != 0)
592 			break;
593 		sc->g_src.s_addr = INADDR_ANY;
594 		sc->g_dst.s_addr = INADDR_ANY;
595 		break;
596 	case SIOCGLIFPHYADDR:
597 		if (sc->g_src.s_addr == INADDR_ANY ||
598 		    sc->g_dst.s_addr == INADDR_ANY) {
599 			error = EADDRNOTAVAIL;
600 			break;
601 		}
602 		bzero(&si, sizeof(si));
603 		si.sin_family = AF_INET;
604 		si.sin_len = sizeof(struct sockaddr_in);
605 		si.sin_addr.s_addr = sc->g_src.s_addr;
606 		memcpy(&lifr->addr, &si, sizeof(si));
607 		si.sin_addr.s_addr = sc->g_dst.s_addr;
608 		memcpy(&lifr->dstaddr, &si, sizeof(si));
609 		break;
610 	case SIOCSLIFPHYRTABLE:
611 		if ((error = suser(prc, 0)) != 0)
612 			break;
613 		if (ifr->ifr_rdomainid < 0 ||
614 		    ifr->ifr_rdomainid > RT_TABLEID_MAX ||
615 		    !rtable_exists(ifr->ifr_rdomainid)) {
616 			error = EINVAL;
617 			break;
618 		}
619 		sc->g_rtableid = ifr->ifr_rdomainid;
620 		goto recompute;
621 	case SIOCGLIFPHYRTABLE:
622 		ifr->ifr_rdomainid = sc->g_rtableid;
623 		break;
624 	default:
625 		error = ENOTTY;
626 	}
627 
628 	splx(s);
629 	return (error);
630 }
631 
632 /*
633  * do a checksum of a buffer - much like in_cksum, which operates on
634  * mbufs.
635  */
636 u_int16_t
637 gre_in_cksum(u_int16_t *p, u_int len)
638 {
639 	u_int32_t sum = 0;
640 	int nwords = len >> 1;
641 
642 	while (nwords-- != 0)
643 		sum += *p++;
644 
645 	if (len & 1) {
646 		union {
647 			u_short w;
648 			u_char c[2];
649 		} u;
650 		u.c[0] = *(u_char *) p;
651 		u.c[1] = 0;
652 		sum += u.w;
653 	}
654 
655 	/* end-around-carry */
656 	sum = (sum >> 16) + (sum & 0xffff);
657 	sum += (sum >> 16);
658 	return (~sum);
659 }
660 
661 void
662 gre_keepalive(void *arg)
663 {
664 	struct gre_softc *sc = arg;
665 
666 	if (!sc->sc_ka_timout)
667 		return;
668 
669 	sc->sc_ka_state = GRE_STATE_DOWN;
670 	gre_link_state(sc);
671 }
672 
673 void
674 gre_send_keepalive(void *arg)
675 {
676 	struct gre_softc *sc = arg;
677 	struct mbuf *m;
678 	struct ip *ip;
679 	struct gre_h *gh;
680 	struct sockaddr dst;
681 	int s;
682 
683 	if (sc->sc_ka_timout)
684 		timeout_add_sec(&sc->sc_ka_snd, sc->sc_ka_timout);
685 
686 	if (sc->g_proto != IPPROTO_GRE)
687 		return;
688 	if ((sc->sc_if.if_flags & IFF_UP) == 0 ||
689 	    sc->g_src.s_addr == INADDR_ANY || sc->g_dst.s_addr == INADDR_ANY)
690 		return;
691 
692 	MGETHDR(m, M_DONTWAIT, MT_DATA);
693 	if (m == NULL) {
694 		sc->sc_if.if_oerrors++;
695 		return;
696 	}
697 
698 	m->m_len = m->m_pkthdr.len = sizeof(*ip) + sizeof(*gh);
699 	MH_ALIGN(m, m->m_len);
700 
701 	/* build the ip header */
702 	ip = mtod(m, struct ip *);
703 
704 	ip->ip_v = IPVERSION;
705 	ip->ip_hl = sizeof(*ip) >> 2;
706 	ip->ip_tos = IPTOS_LOWDELAY;
707 	ip->ip_len = htons(m->m_pkthdr.len);
708 	ip->ip_id = htons(ip_randomid());
709 	ip->ip_off = htons(IP_DF);
710 	ip->ip_ttl = ip_defttl;
711 	ip->ip_p = IPPROTO_GRE;
712 	ip->ip_src.s_addr = sc->g_dst.s_addr;
713 	ip->ip_dst.s_addr = sc->g_src.s_addr;
714 	ip->ip_sum = 0;
715 	ip->ip_sum = in_cksum(m, sizeof(*ip));
716 
717 	gh = (struct gre_h *)(ip + 1);
718 	/* We don't support any GRE flags for now */
719 	bzero(gh, sizeof(*gh));
720 
721 	bzero(&dst, sizeof(dst));
722 	dst.sa_family = AF_INET;
723 
724 	s = splsoftnet();
725 	/* should we care about the error? */
726 	gre_output(&sc->sc_if, m, &dst, NULL);
727 	splx(s);
728 }
729 
730 void
731 gre_recv_keepalive(struct gre_softc *sc)
732 {
733 	if (!sc->sc_ka_timout)
734 		return;
735 
736 	/* link state flap dampening */
737 	switch (sc->sc_ka_state) {
738 	case GRE_STATE_UKNWN:
739 	case GRE_STATE_DOWN:
740 		sc->sc_ka_state = GRE_STATE_HOLD;
741 		sc->sc_ka_holdcnt = sc->sc_ka_holdmax;
742 		sc->sc_ka_holdmax = MIN(sc->sc_ka_holdmax * 2,
743 		    16 * sc->sc_ka_cnt);
744 		break;
745 	case GRE_STATE_HOLD:
746 		if (--sc->sc_ka_holdcnt < 1) {
747 			sc->sc_ka_state = GRE_STATE_UP;
748 			gre_link_state(sc);
749 		}
750 		break;
751 	case GRE_STATE_UP:
752 		sc->sc_ka_holdmax--;
753 		sc->sc_ka_holdmax = MAX(sc->sc_ka_holdmax, sc->sc_ka_cnt);
754 		break;
755 	}
756 
757 	/* rescedule hold timer */
758 	timeout_add_sec(&sc->sc_ka_hold, sc->sc_ka_timout * sc->sc_ka_cnt);
759 }
760 
761 void
762 gre_link_state(struct gre_softc *sc)
763 {
764 	struct ifnet *ifp = &sc->sc_if;
765 	int link_state = LINK_STATE_UNKNOWN;
766 
767 	if (sc->sc_ka_state == GRE_STATE_UP)
768 		link_state = LINK_STATE_UP;
769 	else if (sc->sc_ka_state != GRE_STATE_UKNWN)
770 		link_state = LINK_STATE_KALIVE_DOWN;
771 
772 	if (ifp->if_link_state != link_state) {
773 		ifp->if_link_state = link_state;
774 		if_link_state_change(ifp);
775 	}
776 }
777 #endif
778