summaryrefslogtreecommitdiff
path: root/sys/netinet6/ipv6_trans.c
blob: 6493cc696b17b3c37ef67ec4b8b0716ecdb5e50a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
/*
%%% copyright-nrl-95
This software is Copyright 1995-1998 by Randall Atkinson, Ronald Lee,
Daniel McDonald, Bao Phan, and Chris Winters. All Rights Reserved. All
rights under this copyright have been assigned to the US Naval Research
Laboratory (NRL). The NRL Copyright Notice and License Agreement Version
1.1 (January 17, 1995) applies to this software.
You should have received a copy of the license with this software. If you
didn't get a copy, you may request one from <license@ipv6.nrl.navy.mil>.

*/

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/socket.h>
#include <sys/protosw.h>
#include <sys/domain.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <sys/socketvar.h>

#include <net/if.h>
#include <net/route.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/ip_icmp.h>

#include <netinet6/in6.h>
#include <netinet6/in6_var.h>
#include <netinet6/ipv6.h>
#include <netinet6/ipv6_var.h>

#if __OpenBSD__
#undef IPSEC
#ifdef NRL_IPSEC
#define IPSEC 1
#endif /* NRL_IPSEC */
#endif /* __OpenBSD__ */

#ifdef DEBUG_NRL
#include <sys/debug.h>
#else /* DEBUG_NRL */
#if __OpenBSD__
#include <netinet6/debug.h>
#else /* __OpenBSD__ */
#include <sys/debug.h>
#endif /* __OpenBSD__ */
#endif /* DEBUG_NRL */

/*
 * External globals.
 */

extern struct in6_ifaddr *in6_ifaddr;
extern int ipv6_defhoplmt;

int ipv6_trans_mtu __P((struct mbuf **, int, int));
int ipv6_trans_output __P((struct mbuf *, struct sockaddr_in *, struct rtentry *));
int ipv6_encaps_output __P((struct mbuf *, struct sockaddr_in6 *, struct rtentry *));
int ipv6_tunnel_output __P((struct mbuf *, struct sockaddr_in6 *, struct rtentry *));
int ipv4_trans_output __P((struct mbuf *, struct sockaddr_in6 *, struct rtentry *));
int ipv4_encaps_output __P((struct mbuf *, struct sockaddr_in *, struct rtentry *));
int ipv4_tunnel_output __P((struct mbuf *, struct sockaddr_in *, struct rtentry *));

/*----------------------------------------------------------------------
 * Called from ip_icmp.c, this function will reduce the tunnel path MTU
 * precisely.  I know I have enough to reconstruct the IPv6 header, which
 * is all I care about for this case.  Return 1 if m0 is intact, and 0 if
 * m0 is corrupted somehow.  Don't forget to update m0.
 ----------------------------------------------------------------------*/

int
ipv6_trans_mtu(m0, newmtu, len)
     struct mbuf **m0;
     int newmtu,len;
{
  struct ip *ip,*iip;
  struct ipv6 *ipv6;
  struct icmp *icp;
  struct rtentry *rt;
  struct sockaddr_in6 sin6;
  struct in6_ifaddr *i6a;

  /*
   * Make packet contiguous into one block of memory.  If the IPv6 header is
   * beyond MCLBYTES into the packet, then I'm in big trouble.
   */
  *m0 = m_pullup2(*m0,min(len,MCLBYTES));
  if (*m0 == NULL)
    return 0;

  ip = mtod(*m0,struct ip *);
  icp = (struct icmp *) ((caddr_t)ip + (ip->ip_hl << 2));
  iip = &icp->icmp_ip;
  ipv6 = (struct ipv6 *) ((caddr_t)iip + (iip->ip_hl << 2));

  /*
   * Verify source is one of mine?
   */
  for (i6a = in6_ifaddr; i6a != NULL; i6a = i6a->i6a_next)
    if (IN6_ARE_ADDR_EQUAL(&i6a->i6a_addr.sin6_addr, &ipv6->ipv6_src))
      break;
  if (i6a == NULL)
    {
      /* Packet didn't originate with me.  Drop it. */
      return 1;
    }

  /*
   * Find route for this destination and update it.
   */
  sin6.sin6_family = AF_INET6;
  sin6.sin6_len = sizeof(sin6);
  sin6.sin6_port = 0;
  sin6.sin6_flowinfo = 0;
  sin6.sin6_addr = ipv6->ipv6_dst;

#ifdef __FreeBSD__
  rt = rtalloc1((struct sockaddr *)&sin6, 0, 0UL);
#else /* __FreeBSD__ */
  rt = rtalloc1((struct sockaddr *)&sin6, 0);
#endif /* __FreeBSD__ */

  if (rt == NULL)
    return 1;
  rt->rt_refcnt--;
  /*
   * Update path MTU.
   */
  if (!(rt->rt_flags & RTF_HOST))
    return 1;   /* Can't update path MTU on non-host-route. */
  if (rt->rt_rmx.rmx_mtu < newmtu - sizeof(struct ip))
    panic("MTU WEIRDNESS !!!");
  rt->rt_rmx.rmx_mtu = newmtu - sizeof(struct ip);
  return 1;
}

/*----------------------------------------------------------------------
 * Handle ICMP errors for IPv6-in-IPv4 tunnels.
 *
 * Security processing should be put in here, as it was with the other
 * ctlinput() functions, but with current ICMP implementations returning
 * only sizeof(struct ip) + 64 bits of offending packet.
 ----------------------------------------------------------------------*/
#if __OpenBSD__
#ifdef NRL_IPSEC
void *ipv6_trans_ctlinput(int cmd, struct sockaddr *sa, void *vp, struct mbuf *incoming)
#else /* NRL_IPSEC */
void *ipv6_trans_ctlinput(int cmd, struct sockaddr *sa, void *vp)
#endif /* NRL_IPSEC */
#else /* __OpenBSD__ */
#ifdef IPSEC
void ipv6_trans_ctlinput(int cmd, struct sockaddr *sa, register struct ip *ip, struct mbuf *incoming)
#else /* IPSEC */
void ipv6_trans_ctlinput(int cmd, struct sockaddr *sa, register struct ip *ip)
#endif /* IPSEC */
#endif /* __OpenBSD__ */
{
  struct sockaddr_in *sin = (struct sockaddr_in *)sa;
  struct sockaddr_in6 sin6;
  struct ipv6 *ipv6;
  struct rtentry *rt;
  struct in6_ifaddr *i6a;
#if __OpenBSD__
  struct ip *ip = (struct ip *)vp;
#endif /* __OpenBSD__ */

  sin6.sin6_family = AF_INET6;
  sin6.sin6_len = sizeof(sin6);
  sin6.sin6_port = 0;
  sin6.sin6_flowinfo = 0;
  DPRINTF(IDL_EVENT,("Entered ipv6_trans_ctlinput().\n"));

  /*
   * Do standard checks to see that all parameters are here.
   */
  if ((unsigned)cmd > PRC_NCMDS || sa->sa_family != AF_INET ||
      sin->sin_addr.s_addr == INADDR_ANY || ip == NULL)
    {
      DPRINTF(IDL_EVENT,("Failed one of the four checks.  Returning.\n"));
#ifdef __OpenBSD__
      return NULL;
#else /* __OpenBSD__ */
      return;
#endif /* __OpenBSD__ */
    }

  /*
   * Okay, at this point I have a contiguous IPv6 in IPv4 datagram.
   * I achieved this effect by convincing ip_icmp.[ch] to pull up
   * more than the first 64 bits.
   */

  ipv6 = (struct ipv6 *) ((caddr_t)ip + (ip->ip_hl << 2));
  /*
   * Verify source address is one of mine.
   */
  for (i6a = in6_ifaddr; i6a != NULL; i6a = i6a->i6a_next)
    if (IN6_ARE_ADDR_EQUAL(&i6a->i6a_addr.sin6_addr, &ipv6->ipv6_src))
      break;
  if (i6a == NULL)
    {
      /*
       * Packet didn't originate with me.  Drop it.
       */
#ifdef __OpenBSD__
      return NULL;
#else /* __OpenBSD__ */
      return;
#endif /* __OpenBSD__ */
    }

  sin6.sin6_addr = ipv6->ipv6_dst;
#ifdef __FreeBSD__
  rt = rtalloc1((struct sockaddr *)&sin6, 0, 0UL);
#else /* __FreeBSD__ */
  rt = rtalloc1((struct sockaddr *)&sin6, 0);
#endif /* __FreeBSD__ */
  if (rt == NULL)
#ifdef __OpenBSD__
    return NULL;
#else /* __OpenBSD__ */
    return;
#endif /* __OpenBSD__ */
  rt->rt_refcnt--;

  switch (cmd)
    {
    case PRC_MSGSIZE:
      /*
       * This function was called because the actual MTU wasn't grokked 
       * from the ICMP packet.
       *
       * If I get this, drop to IPV6_MINMTU.  If the actual MTU was in the
       * ICMP packet and was read correctly, it went up a different codepath.
       *
       * RFC 1191 talks about a plateau table.   Here's the place to do it,
       * either that, or on increase.
       */
      if (rt->rt_flags & RTF_HOST)
	{
	  /*
	   * Only attempt path MTU update if I'm a host.
	   */
	  if (rt->rt_rmx.rmx_mtu == IPV6_MINMTU)
	    panic("Too big on v6 MTU of 576!!!");
	  rt->rt_rmx.rmx_mtu = IPV6_MINMTU;
	}
      break;
    case PRC_UNREACH_NET:
      rt->rt_flags &= ~RTF_HOST;  /* Is this wise?  I'm doing this to return
				     the right error on future requests. */
      /* FALLTHROUGH */
    case PRC_UNREACH_HOST:
    case PRC_UNREACH_PROTOCOL:
      /*
       * Other end isn't a v6/v4 node.
       */
      rt->rt_flags |= RTF_REJECT;  /* Don't want to send any packets. */
      break;
    default:
      break;
    }
#ifdef __OpenBSD__
    return NULL;
#endif /* __OpenBSD__ */
}

/*----------------------------------------------------------------------
 *"IPv6 in IPv4 tunnelling."
 *
 * Output routine for IPv6 in IPv4.  Uses M_PREPEND to prepend an IPv4
 * header, and call ip_output().
 *
 * Called in: (only) ipv6_tunnel_output() from this file.
 ----------------------------------------------------------------------*/

int
ipv6_trans_output(outgoing, v4dst, v6rt)
     struct mbuf *outgoing;
     struct sockaddr_in *v4dst;
     struct rtentry *v6rt;
{
  struct route v4route;
  struct ip *ip;
  struct ipv6 *ipv6 = mtod(outgoing,struct ipv6 *);
  int rc;
#ifdef IPSEC
  struct socket *socket;
#endif /* IPSEC */

  /*
   * Like all below-IP(v6) output routines, check RTF_REJECT flag.
   */
  if (v6rt->rt_flags & RTF_REJECT)
    {
      m_freem(outgoing);
      return (v6rt->rt_flags & RTF_HOST) ? EHOSTUNREACH : ENETUNREACH;
    }

  if (v6rt->rt_gwroute)
    v6rt->rt_gwroute->rt_refcnt++;
  v4route.ro_rt = v6rt->rt_gwroute;
  bcopy(v4dst,&v4route.ro_dst,v4dst->sin_len);

  /*
   * Prepend IPv4 header.
   */
  M_PREPEND(outgoing,sizeof(struct ip), M_DONTWAIT);
  if (outgoing == NULL)
    return ENOBUFS;

  ip = mtod(outgoing,struct ip *);
  bzero(ip,sizeof(*ip));

  /*
   * Following four lines are done here rather than ip_output() because of
   * the *&*&%^^& don't fragment bit.
   */
  ip->ip_v = IPVERSION;
#if __OpenBSD__
  ip->ip_id = ip_randomid();
#else /* __OpenBSD__ */
  ip->ip_id = htons(ip_id++);
#endif /* __OpenBSD__ */
  ip->ip_hl = sizeof(*ip)>>2;
  if (v6rt->rt_rmx.rmx_mtu > IPV6_MINMTU)
    ip->ip_off |= IP_DF;
  ipstat.ips_localout++;

  if (v6rt->rt_flags & (RTF_HOST|RTF_GATEWAY))
    ip->ip_dst = v4dst->sin_addr;
  else
    {
      /*
       * If I'm in here, this means I'm not a host route, but when I was
       * supposed to clone, I was supposed to change the v4dst addr.
       *
       * This will only happen if I'm a v6-in-v4 router-to-host route,
       * in which case I have to do the translation on the fly, based on
       * the data in the IPv6 header.
       */
      if (!IN6_IS_ADDR_V4COMPAT(&ipv6->ipv6_dst))
	{
	  printf("Oooh boy, v6-in-v4 tunnel ( trans_output() ) trouble!!!\n");
	}
      ip->ip_dst.s_addr = ipv6->ipv6_dst.s6_addr32[3];
    }

  ip->ip_src.s_addr = INADDR_ANY;
  ip->ip_p = IPPROTO_IPV6;
  ip->ip_ttl = ip_defttl;
  ip->ip_len = outgoing->m_pkthdr.len;

#ifdef IPSEC
  if (v6rt->rt_flags & (RTF_CRYPT|RTF_AUTH)) {
    /*
     * A secure route has hanging off its rt_netproc field something which
     * can be tagged onto an outgoing mbuf such that ipv4_output can
     * secure the IPv6-in-IPv4 packet.
     */
    DPRINTF(IDL_EVENT,("Secure route, sending cheesy socket.\n"));
    socket = v6rt->rt_netproc;
  } else
    socket = NULL;

  rc = ip_output(outgoing, NULL, &v4route, IP_RAWOUTPUT, NULL, socket);
#else /* IPSEC */
#if __bsdi__
  rc = ip_output(outgoing, NULL, &v4route, IP_RAWOUTPUT, NULL, NULL);
#else /* __bsdi__ */
  rc = ip_output(outgoing, NULL, &v4route, IP_RAWOUTPUT, NULL);
#endif /* __bsdi__ */
#endif /* IPSEC */

  if (rc == EMSGSIZE)
    {
      DPRINTF(IDL_ERROR,("Path MTU adjustment needed in trans_output().\n"));
    }
  if (v4route.ro_rt != NULL)
    RTFREE(v4route.ro_rt);
  return rc;
}

/*----------------------------------------------------------------------
 * "IPv6 in IPv6 tunnelling."
 *
 * Encapsulate IPv6 packet in another IPv6 packet.  This, in combination
 * with passing on a fake socket with a security request, can enable a
 * configured secure tunnel.
 *
 * Called in: (only) ipv6_tunnel_output() from this file.
 ----------------------------------------------------------------------*/

int
ipv6_encaps_output(outgoing, tundst, tunrt)
     struct mbuf *outgoing;
     struct sockaddr_in6 *tundst;
     struct rtentry *tunrt;
{
  struct route6 actroute;
  struct ipv6 *ipv6;
  int rc;
#ifdef IPSEC
  struct socket *socket;
#endif /* IPSEC */

DPRINTF(IDL_GROSS_EVENT,("\n\nipv6_encaps_output():0000-Hey!  I'm in IPV6_in_IPV6 tunnelling code!!\n"));
  if (tunrt->rt_flags & RTF_REJECT)
    {
      m_freem(outgoing);
      return (tunrt->rt_flags & RTF_HOST) ? EHOSTUNREACH : ENETUNREACH;
    }

  if (tunrt->rt_gwroute)
    tunrt->rt_gwroute->rt_refcnt++;
  actroute.ro_rt = tunrt->rt_gwroute;
  bcopy(tundst,&actroute.ro_dst,tundst->sin6_len);

  M_PREPEND(outgoing,sizeof(struct ipv6), M_DONTWAIT);
  if (outgoing == NULL)
    return ENOBUFS;

  ipv6 = mtod(outgoing,struct ipv6 *);
  bzero(ipv6,sizeof(*ipv6));
  
  ipv6->ipv6_versfl = htonl(0x60000000);
  ipv6->ipv6_length = outgoing->m_pkthdr.len - sizeof(struct ipv6);
  ipv6->ipv6_nexthdr = IPPROTO_IPV6;
  ipv6->ipv6_hoplimit = ipv6_defhoplmt;
  ipv6->ipv6_dst = tundst->sin6_addr;

#ifdef IPSEC
  if (tunrt->rt_flags & (RTF_CRYPT|RTF_AUTH)) {
    /*
     * A secure route has hanging off its rt_netproc field something which
     * can be tagged onto an outgoing mbuf such that ipv6_output can
     * secure the IPv6-in-IPv6 packet.
     */
    DPRINTF(IDL_EVENT,("ipv6_encaps_output():0500-Secure route, sending cheesy socket.\n"));
    socket = tunrt->rt_netproc;
  } else 
    socket = NULL;

  rc = ipv6_output(outgoing, &actroute, IPV6_RAWOUTPUT, NULL, NULL, socket);
#else /* IPSEC */
  rc = ipv6_output(outgoing, &actroute, IPV6_RAWOUTPUT, NULL, NULL, NULL);
#endif /* IPSEC */

  if (rc == EMSGSIZE)
    {
      DPRINTF(IDL_ERROR,("Path MTU adjustment needed in trans_output().\n"));
    }
  if (actroute.ro_rt != NULL)
    RTFREE(actroute.ro_rt);
  return rc;
  
}
/*----------------------------------------------------------------------
 * "IPv4 in IPv4 tunnelling"
 *
 * Output routine for IPv4 in IPv4.  Uses M_PREPEND to prepend an IPv4
 * header (i.e. the tunnel header), and then call ip_output() to
 * send the tunnel packet.  The v4 in v4 tunnel seems redundant, but is
 * useful for setting up secure tunnels.
 *
 * Called in: (only) ipv4_tunnel_output() from this file.
 ----------------------------------------------------------------------*/

int
ipv4_encaps_output(outgoing, v4tundst, v4rt)
     struct mbuf *outgoing;
     struct sockaddr_in *v4tundst;   /* Goto to destination tunnel endpoint. */
     struct rtentry *v4rt;           /* The encapsulated (i.e. passenger) IPv4
                                      *   packet's route.                 */
{
  struct route v4tunroute; /* The tunnel- (i.e. actual) route to the tunnel's
                            *   destination endpoint (i.e. to the other
                            *   side of the tunnel). */
  struct ip *ip;           /* For setting up the IPv4 data needed for the
                            *   tunnel IPv4 packet. */
  int rc;                  /* Return Code */
#ifdef IPSEC
  struct socket *socket;
#endif /* IPSEC */

  /*
   * Like all below-IP(v4) output routines, check RTF_REJECT flag.
   * Why?  We need to make sure the logical (i.e. encapsulated packet's)
   * route to the final destination is reachable.
   */
  if (v4rt->rt_flags & RTF_REJECT)
    {
      m_freem(outgoing);
      return (v4rt->rt_flags & RTF_HOST) ? EHOSTUNREACH : ENETUNREACH;
    }

  if (v4rt->rt_gwroute)
    v4rt->rt_gwroute->rt_refcnt++;
  v4tunroute.ro_rt = v4rt->rt_gwroute;
  bcopy(v4tundst,&v4tunroute.ro_dst,v4tundst->sin_len);

  /*
   * Prepend IPv4 header.
   */
  M_PREPEND(outgoing,sizeof(struct ip), M_DONTWAIT);
  if (outgoing == NULL)
    return ENOBUFS;

  ip = mtod(outgoing,struct ip *);
  bzero(ip,sizeof(*ip));

  /*
   *  Initialization of IP header:  We'll let  ip_output()  fill in
   *  IP's version, ip, offset, and header length as well as update
   *  ipstat.ips_localout.
   *
   *  Of course, we need to do the rest ...
   */

  ip->ip_src.s_addr = INADDR_ANY;
  ip->ip_p = IPPROTO_IPV4;
  ip->ip_ttl = ip_defttl;
  ip->ip_len = outgoing->m_pkthdr.len;

  ip->ip_dst = v4tundst->sin_addr;

#ifdef IPSEC
  if (v4rt->rt_flags & (RTF_CRYPT|RTF_AUTH)) {
    /*
     * A secure route has hanging off its rt_netproc field something which
     * can be tagged onto an outgoing mbuf such that ipv4_output can
     * secure the IPv4-in-IPv4 packet.
     */
    DPRINTF(IDL_EVENT,("ipv4_encaps_output():0500-Secure route, sending cheesy socket.\n"));
    socket = v4rt->rt_netproc;
  } else
    socket = NULL;

  rc = ip_output(outgoing, NULL, &v4tunroute, 0, NULL, socket);
#else /* IPSEC */
#if __bsdi__
  rc = ip_output(outgoing, NULL, &v4tunroute, 0, NULL, NULL);
#else /* __bsdi__ */
  rc = ip_output(outgoing, NULL, &v4tunroute, 0, NULL);
#endif /* __bsdi__ */
#endif /* IPSEC */

  if (v4tunroute.ro_rt != NULL)
    RTFREE(v4tunroute.ro_rt);
  return rc;
}

/*----------------------------------------------------------------------
 *"IPv4 in IPv6"  
 *
 * Encapsulate IPv4 packet in a IPv6 packet.
 *
 * Called in: (only) ipv4_tunnel_output() from this file.
 *---------------------------------------------------------------------*/

int
ipv4_trans_output(outgoing, tunv6dst, v4rt)
     struct mbuf *outgoing;
     struct sockaddr_in6 *tunv6dst;
     struct rtentry *v4rt;
{
  struct route6 tunv6route;
  struct ipv6 *ipv6;
  int rc;
#ifdef IPSEC
  struct socket *socket;
#endif /* IPSEC */

  if (v4rt->rt_flags & RTF_REJECT)
    {
      m_freem(outgoing);
      return (v4rt->rt_flags & RTF_HOST) ? EHOSTUNREACH : ENETUNREACH;
    }

  if (v4rt->rt_gwroute)
    v4rt->rt_gwroute->rt_refcnt++;
  tunv6route.ro_rt = v4rt->rt_gwroute;
  bcopy(tunv6dst,&tunv6route.ro_dst,tunv6dst->sin6_len);

  M_PREPEND(outgoing,sizeof(struct ipv6), M_DONTWAIT);
  if (outgoing == NULL)
    return ENOBUFS;

  ipv6 = mtod(outgoing,struct ipv6 *);
  bzero(ipv6,sizeof(*ipv6));

  ipv6->ipv6_versfl = htonl(0x60000000);
  ipv6->ipv6_length = outgoing->m_pkthdr.len - sizeof(struct ipv6);
  ipv6->ipv6_nexthdr = IPPROTO_IPV4;
  ipv6->ipv6_hoplimit = ipv6_defhoplmt;
  ipv6->ipv6_dst = tunv6dst->sin6_addr;

#ifdef IPSEC
  if (v4rt->rt_flags & (RTF_CRYPT|RTF_AUTH)) {
    /*
     * A secure route has hanging off its rt_netproc field something which
     * can be tagged onto an outgoing mbuf such that ipv6_output can
     * secure the IPv4-in-IPv6 packet.
     */
    DPRINTF(IDL_EVENT,("ipv4_trans_output():0500-Secure route, sending cheesy socket.\n"));
    socket = v4rt->rt_netproc;
  } else
    socket = NULL;

  rc = ipv6_output(outgoing, &tunv6route, IPV6_RAWOUTPUT, NULL, NULL, socket);
#else /* IPSEC */
  rc = ipv6_output(outgoing, &tunv6route, IPV6_RAWOUTPUT, NULL, NULL, NULL);
#endif /* IPSEC */

  if (rc == EMSGSIZE)
    {
      DPRINTF(IDL_ERROR,("Path MTU adjustment needed in trans_output().\n"));
    }
  if (tunv6route.ro_rt != NULL)
    RTFREE(tunv6route.ro_rt);
  return rc;
}
  

/*----------------------------------------------------------------------
 * Called by ipv6_output if the RTF_TUNNEL bit is set on a route,
 * this function examines the route, and sees what sort of encapsulation is
 * needed.  Often, the rt->rt_gateway sockaddr is used to figure this out.
 ----------------------------------------------------------------------*/

int
ipv6_tunnel_output(outgoing, dst, rt)
     struct mbuf *outgoing;
     struct sockaddr_in6 *dst;
     struct rtentry *rt;
{
  DPRINTF(IDL_EVENT,("\n\nipv6_tunnel_output():0000-Just entered.\n"));

  /*
   * Determine what type of tunnel it is with rt.  Perform correct kind
   * of encapsulation (in IPv4, ESP, etc.) and call output routine of
   * what you want encapsulated.
   */

  /* IPv6 in IPv4. */
  if (rt->rt_gateway != NULL && rt->rt_gateway->sa_family == AF_INET)
    return ipv6_trans_output(outgoing,(struct sockaddr_in *)rt->rt_gateway,rt);

  /* IPv6 in IPv6. */
  if (rt->rt_gateway != NULL && rt->rt_gateway->sa_family == AF_INET6)
    return ipv6_encaps_output(outgoing,(struct sockaddr_in6 *)rt->rt_gateway,rt);

  m_freem(outgoing);
  return EHOSTUNREACH;
}


/*----------------------------------------------------------------------
 * Called by ip_output if the RTF_TUNNEL bit is set on a route,
 * this function examines the route, and sees what sort of encapsulation is
 * needed.  Often, the rt->rt_gateway sockaddr is used to figure this out.
 ----------------------------------------------------------------------*/

int
ipv4_tunnel_output(outgoing, dst, rt)
     struct mbuf *outgoing;
     struct sockaddr_in *dst;
     struct rtentry *rt;
{
  DPRINTF(IDL_EVENT,("\n\nipv4_tunnel_output():0000-Just entered.\n"));

  /*
   * Determine what type of tunnel it is with rt.  Perform correct kind
   * of encapsulation (in IPv4, ESP, etc.) and call output routine of
   * what you want encapsulated.
   */

  /* IPv4 in IPv6. */
  if (rt->rt_gateway != NULL && rt->rt_gateway->sa_family == AF_INET6)
    return ipv4_trans_output(outgoing,(struct sockaddr_in6 *)rt->rt_gateway,rt);

  /* IPv4 in IPv4. */
  if (rt->rt_gateway != NULL && rt->rt_gateway->sa_family == AF_INET)
    return ipv4_encaps_output(outgoing,(struct sockaddr_in *)rt->rt_gateway,rt);

  m_freem(outgoing);
  return EHOSTUNREACH;
}