patch-2.1.8 linux/net/ipv6/af_inet6.c
Next file: linux/net/ipv6/datagram.c
Previous file: linux/net/ipv6/addrconf.c
Back to the patch index
Back to the overall index
- Lines: 866
- Date:
Sun Nov 3 11:04:46 1996
- Orig file:
v2.1.7/linux/net/ipv6/af_inet6.c
- Orig date:
Thu Jan 1 02:00:00 1970
diff -u --recursive --new-file v2.1.7/linux/net/ipv6/af_inet6.c linux/net/ipv6/af_inet6.c
@@ -0,0 +1,865 @@
+/*
+ * AF_INET6 socket family
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <roque@di.fc.ul.pt>
+ *
+ * Adapted from linux/net/ipv4/af_inet.c
+ *
+ * $Id: af_inet6.c,v 1.13 1996/10/31 19:47:17 roque Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/arp.h>
+#include <net/rarp.h>
+#include <net/route.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/raw.h>
+#include <net/icmp.h>
+#include <linux/icmpv6.h>
+#include <net/inet_common.h>
+#include <net/transp_v6.h>
+#include <net/ndisc.h>
+#include <net/ipv6_route.h>
+#include <net/sit.h>
+#include <linux/ip_fw.h>
+#include <net/addrconf.h>
+
+/*
+ * Default callbacks for user INET sockets. These just wake up
+ * the user owning the socket.
+ */
+
+static void def_callback1(struct sock *sk)
+{
+ if(!sk->dead)
+ wake_up_interruptible(sk->sleep);
+}
+
+static void def_callback2(struct sock *sk,int len)
+{
+ if(!sk->dead)
+ {
+ wake_up_interruptible(sk->sleep);
+ sock_wake_async(sk->socket, 1);
+ }
+}
+
+static void def_callback3(struct sock *sk)
+{
+ long wmem;
+
+ wmem = (long) sk->wmem_alloc;
+
+ if (wmem < 0) {
+ printk(KERN_DEBUG "bug wmem_alloc < 0\n");
+ sk->wmem_alloc = 0;
+ }
+
+ if(!sk->dead && sk->wmem_alloc*2 <= sk->sndbuf)
+ {
+ wake_up_interruptible(sk->sleep);
+ sock_wake_async(sk->socket, 2);
+ }
+}
+
+struct sock * rawv6_sock_array[SOCK_ARRAY_SIZE];
+
+static int inet6_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+ struct proto *prot;
+ int err;
+
+ sk = (struct sock *) kmalloc(sizeof(*sk), GFP_KERNEL);
+ if (sk == NULL)
+ return(-ENOBUFS);
+
+ /* Efficient way to set most fields to zero */
+ memset(sk,0,sizeof(*sk));
+
+ /*
+ * Note for tcp that also wiped the dummy_th block for us.
+ */
+
+ switch(sock->type)
+ {
+ case SOCK_STREAM:
+ case SOCK_SEQPACKET:
+ if (protocol && protocol != IPPROTO_TCP)
+ {
+ kfree_s((void *)sk, sizeof(*sk));
+ return(-EPROTONOSUPPORT);
+ }
+ protocol = IPPROTO_TCP;
+ sk->no_check = TCP_NO_CHECK;
+ prot = &tcpv6_prot;
+ break;
+
+ case SOCK_DGRAM:
+ if (protocol && protocol != IPPROTO_UDP)
+ {
+ kfree_s((void *)sk, sizeof(*sk));
+ return(-EPROTONOSUPPORT);
+ }
+ protocol = IPPROTO_UDP;
+ sk->no_check = UDP_NO_CHECK;
+ prot=&udpv6_prot;
+ break;
+
+ case SOCK_RAW:
+ if (!suser())
+ {
+ kfree_s((void *)sk, sizeof(*sk));
+ return(-EPERM);
+ }
+ if (!protocol)
+ {
+ kfree_s((void *)sk, sizeof(*sk));
+ return(-EPROTONOSUPPORT);
+ }
+ prot = &rawv6_prot;
+ sk->reuse = 1;
+ sk->num = protocol;
+ break;
+ default:
+ kfree_s((void *)sk, sizeof(*sk));
+ return(-ESOCKTNOSUPPORT);
+ }
+
+ sk->socket = sock;
+
+ sk->family = AF_INET6;
+ sk->type = sock->type;
+ sk->protocol = protocol;
+ sk->allocation = GFP_KERNEL;
+ sk->sndbuf = SK_WMEM_MAX;
+ sk->rcvbuf = SK_RMEM_MAX;
+ sk->priority = 1;
+
+ sk->prot = prot;
+ sk->backlog_rcv = prot->backlog_rcv;
+
+ sk->sleep = sock->wait;
+ sock->data =(void *) sk;
+
+ sk->state = TCP_CLOSE;
+
+ skb_queue_head_init(&sk->write_queue);
+ skb_queue_head_init(&sk->receive_queue);
+ skb_queue_head_init(&sk->back_log);
+
+ sk->timer.data = (unsigned long)sk;
+ sk->timer.function = &net_timer;
+ init_timer(&sk->timer);
+
+ sk->state_change = def_callback1;
+ sk->data_ready = def_callback2;
+ sk->write_space = def_callback3;
+ sk->error_report = def_callback1;
+
+ sk->net_pinfo.af_inet6.hop_limit = ipv6_hop_limit;
+ sk->net_pinfo.af_inet6.mcast_hops = IPV6_DEFAULT_MCASTHOPS;
+ sk->net_pinfo.af_inet6.mc_loop = 1;
+
+ /*
+ * init the ipv4 part of the socket since
+ * we can have sockets using v6 API for ipv4
+ */
+
+ sk->ip_ttl=64;
+
+#ifdef CONFIG_IP_MULTICAST
+ sk->ip_mc_loop=1;
+ sk->ip_mc_ttl=1;
+ *sk->ip_mc_name=0;
+ sk->ip_mc_list=NULL;
+#endif
+
+
+ if (sk->type==SOCK_RAW && protocol==IPPROTO_RAW)
+ sk->ip_hdrincl=1;
+
+ if (sk->num)
+ {
+ /*
+ * It assumes that any protocol which allows
+ * the user to assign a number at socket
+ * creation time automatically
+ * shares.
+ */
+
+ inet_put_sock(sk->num, sk);
+ sk->dummy_th.source = ntohs(sk->num);
+ }
+
+ if (sk->prot->init)
+ {
+ err = sk->prot->init(sk);
+ if (err != 0)
+ {
+ destroy_sock(sk);
+ return(err);
+ }
+ }
+ MOD_INC_USE_COUNT;
+ return(0);
+}
+
+static int inet6_dup(struct socket *newsock, struct socket *oldsock)
+{
+ return(inet6_create(newsock,
+ ((struct sock *)(oldsock->data))->protocol));
+}
+
+
+/*
+ * bind for INET6 API
+ */
+
+static int inet6_bind(struct socket *sock, struct sockaddr *uaddr,
+ int addr_len)
+{
+ struct sockaddr_in6 *addr=(struct sockaddr_in6 *)uaddr;
+ struct sock *sk=(struct sock *)sock->data, *sk2;
+ __u32 v4addr = 0;
+ unsigned short snum = 0;
+ int addr_type = 0;
+
+ /*
+ * If the socket has its own bind function then use it.
+ */
+
+ if(sk->prot->bind)
+ return sk->prot->bind(sk, uaddr, addr_len);
+
+ /* check this error. */
+ if (sk->state != TCP_CLOSE)
+ return(-EIO);
+
+ if(addr_len < sizeof(struct sockaddr_in6))
+ return -EINVAL;
+
+ if(sock->type != SOCK_RAW)
+ {
+ if (sk->num != 0)
+ return(-EINVAL);
+
+ snum = ntohs(addr->sin6_port);
+
+ if (snum == 0)
+ snum = get_new_socknum(sk->prot, 0);
+
+ if (snum < PROT_SOCK && !suser())
+ return(-EACCES);
+ }
+
+ addr_type = ipv6_addr_type(&addr->sin6_addr);
+
+ if ((addr_type & IPV6_ADDR_MULTICAST) && sock->type == SOCK_STREAM)
+ {
+ return(-EINVAL);
+ }
+
+ /*
+ * check if the address belongs to the host
+ */
+
+ if (addr_type == IPV6_ADDR_MAPPED)
+ {
+ v4addr = addr->sin6_addr.s6_addr32[3];
+
+ if (ip_chk_addr(v4addr) != IS_MYADDR)
+ return(-EADDRNOTAVAIL);
+ }
+ else
+ {
+ if (addr_type != IPV6_ADDR_ANY)
+ {
+ /*
+ * ipv4 addr of the socket is invalid.
+ * only the unpecified and mapped address
+ * have a v4 equivalent.
+ */
+
+ v4addr = LOOPBACK4_IPV6;
+
+ if (!(addr_type & IPV6_ADDR_MULTICAST))
+ {
+ if (ipv6_chk_addr(&addr->sin6_addr) == NULL)
+ return(-EADDRNOTAVAIL);
+ }
+ }
+ }
+
+ sk->rcv_saddr = v4addr;
+ sk->saddr = v4addr;
+
+ memcpy(&sk->net_pinfo.af_inet6.rcv_saddr, &addr->sin6_addr,
+ sizeof(struct in6_addr));
+
+ if (!(addr_type & IPV6_ADDR_MULTICAST))
+ memcpy(&sk->net_pinfo.af_inet6.saddr, &addr->sin6_addr,
+ sizeof(struct in6_addr));
+
+ if(sock->type != SOCK_RAW)
+ {
+ /* Make sure we are allowed to bind here. */
+ cli();
+ for(sk2 = sk->prot->sock_array[snum & (SOCK_ARRAY_SIZE -1)];
+ sk2 != NULL; sk2 = sk2->next)
+ {
+ /*
+ * Hash collision or real match ?
+ */
+
+ if (sk2->num != snum)
+ continue;
+
+ /*
+ * Either bind on the port is wildcard means
+ * they will overlap and thus be in error.
+ * We use the sk2 v4 address to test the
+ * other socket since addr_any in av4 implies
+ * addr_any in v6
+ */
+
+ if (addr_type == IPV6_ADDR_ANY || (!sk2->rcv_saddr))
+ {
+ /*
+ * Allow only if both are setting reuse.
+ */
+ if(sk2->reuse && sk->reuse && sk2->state!=TCP_LISTEN)
+ continue;
+ sti();
+ return(-EADDRINUSE);
+ }
+
+ /*
+ * Two binds match ?
+ */
+
+ if (ipv6_addr_cmp(&sk->net_pinfo.af_inet6.rcv_saddr,
+ &sk2->net_pinfo.af_inet6.rcv_saddr))
+
+ continue;
+ /*
+ * Reusable port ?
+ */
+
+ if (!sk->reuse)
+ {
+ sti();
+ return(-EADDRINUSE);
+ }
+
+ /*
+ * Reuse ?
+ */
+
+ if (!sk2->reuse || sk2->state==TCP_LISTEN)
+ {
+ sti();
+ return(-EADDRINUSE);
+ }
+ }
+ sti();
+
+ inet_remove_sock(sk);
+
+ /*
+ if(sock->type==SOCK_DGRAM)
+ udp_cache_zap();
+ if(sock->type==SOCK_STREAM)
+ tcp_cache_zap();
+ */
+ inet_put_sock(snum, sk);
+ sk->dummy_th.source = ntohs(sk->num);
+ sk->dummy_th.dest = 0;
+ sk->daddr = 0;
+ }
+
+ return(0);
+}
+
+static int inet6_release(struct socket *sock, struct socket *peer)
+{
+ MOD_DEC_USE_COUNT;
+ return inet_release(sock, peer);
+}
+
+static int inet6_socketpair(struct socket *sock1, struct socket *sock2)
+{
+ return(-EOPNOTSUPP);
+}
+
+/*
+ * This does both peername and sockname.
+ */
+
+static int inet6_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ struct sockaddr_in6 *sin=(struct sockaddr_in6 *)uaddr;
+ struct sock *sk;
+
+ sin->sin6_family = AF_INET6;
+ sk = (struct sock *) sock->data;
+ if (peer)
+ {
+ if (!tcp_connected(sk->state))
+ return(-ENOTCONN);
+ sin->sin6_port = sk->dummy_th.dest;
+ memcpy(&sin->sin6_addr, &sk->net_pinfo.af_inet6.daddr,
+ sizeof(struct in6_addr));
+ }
+ else
+ {
+ if (ipv6_addr_type(&sk->net_pinfo.af_inet6.rcv_saddr) ==
+ IPV6_ADDR_ANY)
+ memcpy(&sin->sin6_addr,
+ &sk->net_pinfo.af_inet6.saddr,
+ sizeof(struct in6_addr));
+
+ else
+ memcpy(&sin->sin6_addr,
+ &sk->net_pinfo.af_inet6.rcv_saddr,
+ sizeof(struct in6_addr));
+
+ sin->sin6_port = sk->dummy_th.source;
+
+ }
+
+ *uaddr_len = sizeof(*sin);
+ return(0);
+}
+
+static int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk=(struct sock *)sock->data;
+ int err;
+ int pid;
+
+ switch(cmd)
+ {
+ case FIOSETOWN:
+ case SIOCSPGRP:
+ err = get_user(pid, (int *) arg);
+ if(err)
+ return err;
+
+ /* see inet_fcntl */
+ if (current->pid != pid && current->pgrp != -pid && !suser())
+ return -EPERM;
+ sk->proc = pid;
+ return(0);
+ case FIOGETOWN:
+ case SIOCGPGRP:
+ err = put_user(sk->proc,(int *)arg);
+ if(err)
+ return err;
+ return(0);
+ case SIOCGSTAMP:
+ if(sk->stamp.tv_sec==0)
+ return -ENOENT;
+ err = copy_to_user((void *)arg, &sk->stamp,
+ sizeof(struct timeval));
+ if (err)
+ return -EFAULT;
+ return 0;
+
+ case SIOCADDRT:
+ case SIOCDELRT:
+
+ return(ipv6_route_ioctl(cmd,(void *)arg));
+
+ case SIOCGIFCONF:
+ case SIOCGIFFLAGS:
+ case SIOCSIFFLAGS:
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+/*
+
+ this ioctls deal with addresses
+ must process the addr info before
+ calling dev_ioctl to perform dev specific functions
+
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+
+
+ case SIOCGIFDSTADDR:
+
+ case SIOCGIFBRDADDR:
+ case SIOCSIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCSIFNETMASK:
+ */
+
+ case SIOCGIFMETRIC:
+ case SIOCSIFMETRIC:
+ case SIOCGIFMEM:
+ case SIOCSIFMEM:
+ case SIOCGIFMTU:
+ case SIOCSIFMTU:
+ case SIOCSIFLINK:
+ case SIOCGIFHWADDR:
+ case SIOCSIFHWADDR:
+ case SIOCSIFMAP:
+ case SIOCGIFMAP:
+ case SIOCSIFSLAVE:
+ case SIOCGIFSLAVE:
+
+ return(dev_ioctl(cmd,(void *) arg));
+
+ return -EINVAL;
+
+ case SIOCSIFADDR:
+ return addrconf_add_ifaddr((void *) arg);
+ case SIOCSIFDSTADDR:
+ return addrconf_set_dstaddr((void *) arg);
+ default:
+ if ((cmd >= SIOCDEVPRIVATE) &&
+ (cmd <= (SIOCDEVPRIVATE + 15)))
+ return(dev_ioctl(cmd,(void *) arg));
+
+ if (sk->prot->ioctl==NULL)
+ return(-EINVAL);
+ return(sk->prot->ioctl(sk, cmd, arg));
+ }
+ /*NOTREACHED*/
+ return(0);
+}
+
+/*
+ * This routine must find a socket given a TCP or UDP header.
+ * Everything is assumed to be in net order.
+ *
+ * We give priority to more closely bound ports: if some socket
+ * is bound to a particular foreign address, it will get the packet
+ * rather than somebody listening to any address..
+ */
+
+struct sock *inet6_get_sock(struct proto *prot,
+ struct in6_addr *loc_addr,
+ struct in6_addr *rmt_addr,
+ unsigned short loc_port,
+ unsigned short rmt_port)
+{
+ struct sock *s;
+ struct sock *result = NULL;
+ int badness = -1;
+ unsigned short hnum;
+ struct ipv6_pinfo *np;
+ hnum = ntohs(loc_port);
+
+ /*
+ * SOCK_ARRAY_SIZE must be a power of two. This will work better
+ * than a prime unless 3 or more sockets end up using the same
+ * array entry. This should not be a problem because most
+ * well known sockets don't overlap that much, and for
+ * the other ones, we can just be careful about picking our
+ * socket number when we choose an arbitrary one.
+ */
+
+ for(s = prot->sock_array[hnum & (SOCK_ARRAY_SIZE - 1)];
+ s != NULL; s = s->next)
+ {
+ int score = 0;
+
+ if ((s->num != hnum) || s->family != AF_INET6)
+ continue;
+
+ if(s->dead && (s->state == TCP_CLOSE))
+ {
+ printk(KERN_DEBUG "dead or closed socket\n");
+ continue;
+ }
+
+ np = &s->net_pinfo.af_inet6;
+
+ /* remote port matches? */
+
+ if (s->dummy_th.dest) {
+ if (s->dummy_th.dest != rmt_port)
+ {
+ continue;
+ }
+ score++;
+ }
+
+ /* local address matches? */
+
+ if (!ipv6_addr_any(&np->rcv_saddr))
+ {
+ if (ipv6_addr_cmp(&np->rcv_saddr, loc_addr))
+ {
+ continue;
+ }
+ score++;
+ }
+
+ /* remote address matches? */
+ if (!ipv6_addr_any(&np->daddr))
+ {
+ if (ipv6_addr_cmp(&np->daddr, rmt_addr))
+ {
+ continue;
+ }
+ score++;
+ }
+
+ /* perfect match? */
+ if (score == 3)
+ return s;
+ /* no, check if this is the best so far.. */
+ if (score <= badness)
+ continue;
+ result = s;
+ badness = score;
+ }
+ return result;
+}
+
+static int __inline__ inet6_mc_check(struct sock *sk, struct in6_addr *addr)
+{
+ struct ipv6_mc_socklist *mc;
+
+ for (mc = sk->net_pinfo.af_inet6.ipv6_mc_list; mc; mc=mc->next)
+ {
+ if (ipv6_addr_cmp(&mc->addr, addr) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Deliver a datagram to raw sockets.
+ */
+
+struct sock *inet6_get_sock_raw(struct sock *sk, unsigned short num,
+ struct in6_addr *loc_addr,
+ struct in6_addr *rmt_addr)
+
+{
+ struct sock *s;
+ struct ipv6_pinfo *np;
+ int addr_type = 0;
+
+ s=sk;
+
+ addr_type = ipv6_addr_type(loc_addr);
+
+ for(; s != NULL; s = s->next)
+ {
+ if (s->num != num)
+ continue;
+
+ if(s->dead && (s->state == TCP_CLOSE))
+ continue;
+
+ np = &s->net_pinfo.af_inet6;
+
+ if (!ipv6_addr_any(&np->daddr) &&
+ ipv6_addr_cmp(&np->daddr, rmt_addr))
+ {
+ continue;
+ }
+
+ if (!ipv6_addr_any(&np->rcv_saddr))
+ {
+ if (ipv6_addr_cmp(&np->rcv_saddr, loc_addr) == 0)
+ return(s);
+
+ if ((addr_type & IPV6_ADDR_MULTICAST) &&
+ inet6_mc_check(s, loc_addr))
+ return (s);
+
+ continue;
+ }
+
+ return(s);
+ }
+ return(NULL);
+}
+
+/*
+ * inet6_get_sock_mcast for UDP sockets.
+ */
+
+struct sock *inet6_get_sock_mcast(struct sock *sk,
+ unsigned short num, unsigned short rmt_port,
+ struct in6_addr *loc_addr,
+ struct in6_addr *rmt_addr)
+{
+ struct sock *s;
+ struct ipv6_pinfo *np;
+
+ s=sk;
+
+ for(; s != NULL; s = s->next)
+ {
+ if (s->num != num)
+ continue;
+
+ if(s->dead && (s->state == TCP_CLOSE))
+ continue;
+
+ np = &s->net_pinfo.af_inet6;
+
+ if (s->dummy_th.dest) {
+ if (s->dummy_th.dest != rmt_port)
+ {
+ continue;
+ }
+ }
+
+ if (!ipv6_addr_any(&np->daddr) &&
+ ipv6_addr_cmp(&np->daddr, rmt_addr))
+ {
+ continue;
+ }
+
+
+ if (!ipv6_addr_any(&np->rcv_saddr))
+ {
+ if (ipv6_addr_cmp(&np->rcv_saddr, loc_addr) == 0)
+ return(s);
+ }
+
+ if (!inet6_mc_check(s, loc_addr))
+ {
+ continue;
+ }
+
+ return(s);
+ }
+ return(NULL);
+}
+
+
+static struct proto_ops inet6_proto_ops = {
+ AF_INET6,
+
+ inet6_create,
+ inet6_dup,
+ inet6_release,
+ inet6_bind,
+ inet_connect, /* ok */
+ inet6_socketpair, /* a do nothing */
+ inet_accept, /* ok */
+ inet6_getname,
+ inet_select, /* ok */
+ inet6_ioctl, /* must change */
+ inet_listen, /* ok */
+ inet_shutdown, /* ok */
+ inet_setsockopt, /* ok */
+ inet_getsockopt, /* ok */
+ inet_fcntl, /* ok */
+ inet_sendmsg, /* ok */
+ inet_recvmsg /* ok */
+};
+
+#ifdef MODULE
+int init_module(void)
+#else
+void inet6_proto_init(struct net_proto *pro)
+#endif
+{
+ int i;
+
+ printk(KERN_INFO "IPv6 v0.1\n");
+
+ sock_register(inet6_proto_ops.family, &inet6_proto_ops);
+
+ for(i = 0; i < SOCK_ARRAY_SIZE; i++)
+ {
+ rawv6_sock_array[i] = NULL;
+ }
+
+ /*
+ * ipngwg API draft makes clear that the correct semantics
+ * for TCP and UDP is to consider one TCP and UDP instance
+ * in a host availiable by both INET and INET6 APIs and
+ * hable to communicate via both network protocols.
+ */
+
+ tcpv6_prot.inuse = 0;
+ tcpv6_prot.highestinuse = 0;
+ tcpv6_prot.sock_array = tcp_sock_array;
+
+ udpv6_prot.inuse = 0;
+ udpv6_prot.highestinuse = 0;
+ udpv6_prot.sock_array = udp_sock_array;
+
+ rawv6_prot.inuse = 0;
+ rawv6_prot.highestinuse = 0;
+ rawv6_prot.sock_array = rawv6_sock_array;
+
+ ipv6_init();
+
+ icmpv6_init(&inet6_proto_ops);
+ ndisc_init(&inet6_proto_ops);
+
+ addrconf_init();
+
+ sit_init();
+
+ /* init v6 transport protocols */
+
+ udpv6_init();
+ /* add /proc entries here */
+
+ tcpv6_init();
+
+#ifdef MODULE
+ return 0;
+#endif
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ sit_cleanup();
+ ipv6_cleanup();
+ sock_unregister(AF_INET6);
+}
+#endif
+
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov