Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
This commit is contained in:
Linus Torvalds
2005-04-16 15:20:36 -07:00
commit 1da177e4c3
17291 changed files with 6718755 additions and 0 deletions

79
net/ipv6/Kconfig Normal file
View File

@@ -0,0 +1,79 @@
#
# IPv6 configuration
#
config IPV6_PRIVACY
bool "IPv6: Privacy Extensions (RFC 3041) support"
depends on IPV6
---help---
Privacy Extensions for Stateless Address Autoconfiguration in IPv6
support. With this option, additional periodically-alter
pseudo-random global-scope unicast address(es) will assigned to
your interface(s).
By default, kernel do not generate temporary addresses.
To use temporary addresses, do
echo 2 >/proc/sys/net/ipv6/conf/all/use_tempaddr
See <file:Documentation/networking/ip-sysctl.txt> for details.
config INET6_AH
tristate "IPv6: AH transformation"
depends on IPV6
select XFRM
select CRYPTO
select CRYPTO_HMAC
select CRYPTO_MD5
select CRYPTO_SHA1
---help---
Support for IPsec AH.
If unsure, say Y.
config INET6_ESP
tristate "IPv6: ESP transformation"
depends on IPV6
select XFRM
select CRYPTO
select CRYPTO_HMAC
select CRYPTO_MD5
select CRYPTO_SHA1
select CRYPTO_DES
---help---
Support for IPsec ESP.
If unsure, say Y.
config INET6_IPCOMP
tristate "IPv6: IPComp transformation"
depends on IPV6
select XFRM
select INET6_TUNNEL
select CRYPTO
select CRYPTO_DEFLATE
---help---
Support for IP Payload Compression Protocol (IPComp) (RFC3173),
typically needed for IPsec.
If unsure, say Y.
config INET6_TUNNEL
tristate "IPv6: tunnel transformation"
depends on IPV6
select XFRM
---help---
Support for generic IPv6-in-IPv6 tunnel transformation, which is
required by the IPv6-in-IPv6 tunneling module as well as tunnel mode
IPComp.
If unsure, say Y.
config IPV6_TUNNEL
tristate "IPv6: IPv6-in-IPv6 tunnel"
depends on IPV6
select INET6_TUNNEL
---help---
Support for IPv6-in-IPv6 tunnels described in RFC 2473.
If unsure, say N.

25
net/ipv6/Makefile Normal file
View File

@@ -0,0 +1,25 @@
#
# Makefile for the Linux TCP/IP (INET6) layer.
#
obj-$(CONFIG_IPV6) += ipv6.o
ipv6-objs := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o sit.o \
route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o raw.o \
protocol.o icmp.o mcast.o reassembly.o tcp_ipv6.o \
exthdrs.o sysctl_net_ipv6.o datagram.o proc.o \
ip6_flowlabel.o ipv6_syms.o
ipv6-$(CONFIG_XFRM) += xfrm6_policy.o xfrm6_state.o xfrm6_input.o \
xfrm6_output.o
ipv6-objs += $(ipv6-y)
obj-$(CONFIG_INET6_AH) += ah6.o
obj-$(CONFIG_INET6_ESP) += esp6.o
obj-$(CONFIG_INET6_IPCOMP) += ipcomp6.o
obj-$(CONFIG_INET6_TUNNEL) += xfrm6_tunnel.o
obj-$(CONFIG_NETFILTER) += netfilter/
obj-$(CONFIG_IPV6_TUNNEL) += ip6_tunnel.o
obj-y += exthdrs_core.o

3615
net/ipv6/addrconf.c Normal file

File diff suppressed because it is too large Load Diff

867
net/ipv6/af_inet6.c Normal file
View File

@@ -0,0 +1,867 @@
/*
* PF_INET6 socket protocol 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.66 2002/02/01 22:01:04 davem Exp $
*
* Fixes:
* piggy, Karl Knutson : Socket protocol table
* Hideaki YOSHIFUJI : sin6_scope_id support
* Arnaldo Melo : check proc_net_create return, cleanups
*
* 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 <linux/init.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/icmpv6.h>
#include <linux/smp_lock.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/udp.h>
#include <net/tcp.h>
#include <net/ipip.h>
#include <net/protocol.h>
#include <net/inet_common.h>
#include <net/transp_v6.h>
#include <net/ip6_route.h>
#include <net/addrconf.h>
#ifdef CONFIG_IPV6_TUNNEL
#include <net/ip6_tunnel.h>
#endif
#include <asm/uaccess.h>
#include <asm/system.h>
MODULE_AUTHOR("Cast of dozens");
MODULE_DESCRIPTION("IPv6 protocol stack for Linux");
MODULE_LICENSE("GPL");
/* IPv6 procfs goodies... */
#ifdef CONFIG_PROC_FS
extern int raw6_proc_init(void);
extern void raw6_proc_exit(void);
extern int tcp6_proc_init(void);
extern void tcp6_proc_exit(void);
extern int udp6_proc_init(void);
extern void udp6_proc_exit(void);
extern int ipv6_misc_proc_init(void);
extern void ipv6_misc_proc_exit(void);
extern int ac6_proc_init(void);
extern void ac6_proc_exit(void);
extern int if6_proc_init(void);
extern void if6_proc_exit(void);
#endif
int sysctl_ipv6_bindv6only;
#ifdef INET_REFCNT_DEBUG
atomic_t inet6_sock_nr;
#endif
/* The inetsw table contains everything that inet_create needs to
* build a new socket.
*/
static struct list_head inetsw6[SOCK_MAX];
static DEFINE_SPINLOCK(inetsw6_lock);
static void inet6_sock_destruct(struct sock *sk)
{
inet_sock_destruct(sk);
#ifdef INET_REFCNT_DEBUG
atomic_dec(&inet6_sock_nr);
#endif
}
static __inline__ struct ipv6_pinfo *inet6_sk_generic(struct sock *sk)
{
const int offset = sk->sk_prot->obj_size - sizeof(struct ipv6_pinfo);
return (struct ipv6_pinfo *)(((u8 *)sk) + offset);
}
static int inet6_create(struct socket *sock, int protocol)
{
struct inet_sock *inet;
struct ipv6_pinfo *np;
struct sock *sk;
struct list_head *p;
struct inet_protosw *answer;
struct proto *answer_prot;
unsigned char answer_flags;
char answer_no_check;
int rc;
/* Look for the requested type/protocol pair. */
answer = NULL;
rcu_read_lock();
list_for_each_rcu(p, &inetsw6[sock->type]) {
answer = list_entry(p, struct inet_protosw, list);
/* Check the non-wild match. */
if (protocol == answer->protocol) {
if (protocol != IPPROTO_IP)
break;
} else {
/* Check for the two wild cases. */
if (IPPROTO_IP == protocol) {
protocol = answer->protocol;
break;
}
if (IPPROTO_IP == answer->protocol)
break;
}
answer = NULL;
}
rc = -ESOCKTNOSUPPORT;
if (!answer)
goto out_rcu_unlock;
rc = -EPERM;
if (answer->capability > 0 && !capable(answer->capability))
goto out_rcu_unlock;
rc = -EPROTONOSUPPORT;
if (!protocol)
goto out_rcu_unlock;
sock->ops = answer->ops;
answer_prot = answer->prot;
answer_no_check = answer->no_check;
answer_flags = answer->flags;
rcu_read_unlock();
BUG_TRAP(answer_prot->slab != NULL);
rc = -ENOBUFS;
sk = sk_alloc(PF_INET6, GFP_KERNEL, answer_prot, 1);
if (sk == NULL)
goto out;
sock_init_data(sock, sk);
rc = 0;
sk->sk_no_check = answer_no_check;
if (INET_PROTOSW_REUSE & answer_flags)
sk->sk_reuse = 1;
inet = inet_sk(sk);
if (SOCK_RAW == sock->type) {
inet->num = protocol;
if (IPPROTO_RAW == protocol)
inet->hdrincl = 1;
}
sk->sk_destruct = inet6_sock_destruct;
sk->sk_family = PF_INET6;
sk->sk_protocol = protocol;
sk->sk_backlog_rcv = answer->prot->backlog_rcv;
inet_sk(sk)->pinet6 = np = inet6_sk_generic(sk);
np->hop_limit = -1;
np->mcast_hops = -1;
np->mc_loop = 1;
np->pmtudisc = IPV6_PMTUDISC_WANT;
np->ipv6only = sysctl_ipv6_bindv6only;
/* Init the ipv4 part of the socket since we can have sockets
* using v6 API for ipv4.
*/
inet->uc_ttl = -1;
inet->mc_loop = 1;
inet->mc_ttl = 1;
inet->mc_index = 0;
inet->mc_list = NULL;
if (ipv4_config.no_pmtu_disc)
inet->pmtudisc = IP_PMTUDISC_DONT;
else
inet->pmtudisc = IP_PMTUDISC_WANT;
#ifdef INET_REFCNT_DEBUG
atomic_inc(&inet6_sock_nr);
atomic_inc(&inet_sock_nr);
#endif
if (inet->num) {
/* It assumes that any protocol which allows
* the user to assign a number at socket
* creation time automatically shares.
*/
inet->sport = ntohs(inet->num);
sk->sk_prot->hash(sk);
}
if (sk->sk_prot->init) {
rc = sk->sk_prot->init(sk);
if (rc) {
sk_common_release(sk);
goto out;
}
}
out:
return rc;
out_rcu_unlock:
rcu_read_unlock();
goto out;
}
/* bind for INET6 API */
int inet6_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
struct sockaddr_in6 *addr=(struct sockaddr_in6 *)uaddr;
struct sock *sk = sock->sk;
struct inet_sock *inet = inet_sk(sk);
struct ipv6_pinfo *np = inet6_sk(sk);
__u32 v4addr = 0;
unsigned short snum;
int addr_type = 0;
int err = 0;
/* If the socket has its own bind function then use it. */
if (sk->sk_prot->bind)
return sk->sk_prot->bind(sk, uaddr, addr_len);
if (addr_len < SIN6_LEN_RFC2133)
return -EINVAL;
addr_type = ipv6_addr_type(&addr->sin6_addr);
if ((addr_type & IPV6_ADDR_MULTICAST) && sock->type == SOCK_STREAM)
return -EINVAL;
snum = ntohs(addr->sin6_port);
if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
return -EACCES;
lock_sock(sk);
/* Check these errors (active socket, double bind). */
if (sk->sk_state != TCP_CLOSE || inet->num) {
err = -EINVAL;
goto out;
}
/* Check if the address belongs to the host. */
if (addr_type == IPV6_ADDR_MAPPED) {
v4addr = addr->sin6_addr.s6_addr32[3];
if (inet_addr_type(v4addr) != RTN_LOCAL) {
err = -EADDRNOTAVAIL;
goto out;
}
} else {
if (addr_type != IPV6_ADDR_ANY) {
struct net_device *dev = NULL;
if (addr_type & IPV6_ADDR_LINKLOCAL) {
if (addr_len >= sizeof(struct sockaddr_in6) &&
addr->sin6_scope_id) {
/* Override any existing binding, if another one
* is supplied by user.
*/
sk->sk_bound_dev_if = addr->sin6_scope_id;
}
/* Binding to link-local address requires an interface */
if (!sk->sk_bound_dev_if) {
err = -EINVAL;
goto out;
}
dev = dev_get_by_index(sk->sk_bound_dev_if);
if (!dev) {
err = -ENODEV;
goto out;
}
}
/* ipv4 addr of the socket is invalid. Only the
* unspecified and mapped address have a v4 equivalent.
*/
v4addr = LOOPBACK4_IPV6;
if (!(addr_type & IPV6_ADDR_MULTICAST)) {
if (!ipv6_chk_addr(&addr->sin6_addr, dev, 0)) {
if (dev)
dev_put(dev);
err = -EADDRNOTAVAIL;
goto out;
}
}
if (dev)
dev_put(dev);
}
}
inet->rcv_saddr = v4addr;
inet->saddr = v4addr;
ipv6_addr_copy(&np->rcv_saddr, &addr->sin6_addr);
if (!(addr_type & IPV6_ADDR_MULTICAST))
ipv6_addr_copy(&np->saddr, &addr->sin6_addr);
/* Make sure we are allowed to bind here. */
if (sk->sk_prot->get_port(sk, snum)) {
inet_reset_saddr(sk);
err = -EADDRINUSE;
goto out;
}
if (addr_type != IPV6_ADDR_ANY)
sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
if (snum)
sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
inet->sport = ntohs(inet->num);
inet->dport = 0;
inet->daddr = 0;
out:
release_sock(sk);
return err;
}
int inet6_release(struct socket *sock)
{
struct sock *sk = sock->sk;
if (sk == NULL)
return -EINVAL;
/* Free mc lists */
ipv6_sock_mc_close(sk);
/* Free ac lists */
ipv6_sock_ac_close(sk);
return inet_release(sock);
}
int inet6_destroy_sock(struct sock *sk)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct sk_buff *skb;
struct ipv6_txoptions *opt;
/*
* Release destination entry
*/
sk_dst_reset(sk);
/* Release rx options */
if ((skb = xchg(&np->pktoptions, NULL)) != NULL)
kfree_skb(skb);
/* Free flowlabels */
fl6_free_socklist(sk);
/* Free tx options */
if ((opt = xchg(&np->opt, NULL)) != NULL)
sock_kfree_s(sk, opt, opt->tot_len);
return 0;
}
/*
* This does both peername and sockname.
*/
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 = sock->sk;
struct inet_sock *inet = inet_sk(sk);
struct ipv6_pinfo *np = inet6_sk(sk);
sin->sin6_family = AF_INET6;
sin->sin6_flowinfo = 0;
sin->sin6_scope_id = 0;
if (peer) {
if (!inet->dport)
return -ENOTCONN;
if (((1 << sk->sk_state) & (TCPF_CLOSE | TCPF_SYN_SENT)) &&
peer == 1)
return -ENOTCONN;
sin->sin6_port = inet->dport;
ipv6_addr_copy(&sin->sin6_addr, &np->daddr);
if (np->sndflow)
sin->sin6_flowinfo = np->flow_label;
} else {
if (ipv6_addr_any(&np->rcv_saddr))
ipv6_addr_copy(&sin->sin6_addr, &np->saddr);
else
ipv6_addr_copy(&sin->sin6_addr, &np->rcv_saddr);
sin->sin6_port = inet->sport;
}
if (ipv6_addr_type(&sin->sin6_addr) & IPV6_ADDR_LINKLOCAL)
sin->sin6_scope_id = sk->sk_bound_dev_if;
*uaddr_len = sizeof(*sin);
return(0);
}
int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
struct sock *sk = sock->sk;
int err = -EINVAL;
switch(cmd)
{
case SIOCGSTAMP:
return sock_get_timestamp(sk, (struct timeval __user *)arg);
case SIOCADDRT:
case SIOCDELRT:
return(ipv6_route_ioctl(cmd,(void __user *)arg));
case SIOCSIFADDR:
return addrconf_add_ifaddr((void __user *) arg);
case SIOCDIFADDR:
return addrconf_del_ifaddr((void __user *) arg);
case SIOCSIFDSTADDR:
return addrconf_set_dstaddr((void __user *) arg);
default:
if (!sk->sk_prot->ioctl ||
(err = sk->sk_prot->ioctl(sk, cmd, arg)) == -ENOIOCTLCMD)
return(dev_ioctl(cmd,(void __user *) arg));
return err;
}
/*NOTREACHED*/
return(0);
}
struct proto_ops inet6_stream_ops = {
.family = PF_INET6,
.owner = THIS_MODULE,
.release = inet6_release,
.bind = inet6_bind,
.connect = inet_stream_connect, /* ok */
.socketpair = sock_no_socketpair, /* a do nothing */
.accept = inet_accept, /* ok */
.getname = inet6_getname,
.poll = tcp_poll, /* ok */
.ioctl = inet6_ioctl, /* must change */
.listen = inet_listen, /* ok */
.shutdown = inet_shutdown, /* ok */
.setsockopt = sock_common_setsockopt, /* ok */
.getsockopt = sock_common_getsockopt, /* ok */
.sendmsg = inet_sendmsg, /* ok */
.recvmsg = sock_common_recvmsg, /* ok */
.mmap = sock_no_mmap,
.sendpage = tcp_sendpage
};
struct proto_ops inet6_dgram_ops = {
.family = PF_INET6,
.owner = THIS_MODULE,
.release = inet6_release,
.bind = inet6_bind,
.connect = inet_dgram_connect, /* ok */
.socketpair = sock_no_socketpair, /* a do nothing */
.accept = sock_no_accept, /* a do nothing */
.getname = inet6_getname,
.poll = udp_poll, /* ok */
.ioctl = inet6_ioctl, /* must change */
.listen = sock_no_listen, /* ok */
.shutdown = inet_shutdown, /* ok */
.setsockopt = sock_common_setsockopt, /* ok */
.getsockopt = sock_common_getsockopt, /* ok */
.sendmsg = inet_sendmsg, /* ok */
.recvmsg = sock_common_recvmsg, /* ok */
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
static struct net_proto_family inet6_family_ops = {
.family = PF_INET6,
.create = inet6_create,
.owner = THIS_MODULE,
};
#ifdef CONFIG_SYSCTL
extern void ipv6_sysctl_register(void);
extern void ipv6_sysctl_unregister(void);
#endif
/* Same as inet6_dgram_ops, sans udp_poll. */
static struct proto_ops inet6_sockraw_ops = {
.family = PF_INET6,
.owner = THIS_MODULE,
.release = inet6_release,
.bind = inet6_bind,
.connect = inet_dgram_connect, /* ok */
.socketpair = sock_no_socketpair, /* a do nothing */
.accept = sock_no_accept, /* a do nothing */
.getname = inet6_getname,
.poll = datagram_poll, /* ok */
.ioctl = inet6_ioctl, /* must change */
.listen = sock_no_listen, /* ok */
.shutdown = inet_shutdown, /* ok */
.setsockopt = sock_common_setsockopt, /* ok */
.getsockopt = sock_common_getsockopt, /* ok */
.sendmsg = inet_sendmsg, /* ok */
.recvmsg = sock_common_recvmsg, /* ok */
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
static struct inet_protosw rawv6_protosw = {
.type = SOCK_RAW,
.protocol = IPPROTO_IP, /* wild card */
.prot = &rawv6_prot,
.ops = &inet6_sockraw_ops,
.capability = CAP_NET_RAW,
.no_check = UDP_CSUM_DEFAULT,
.flags = INET_PROTOSW_REUSE,
};
void
inet6_register_protosw(struct inet_protosw *p)
{
struct list_head *lh;
struct inet_protosw *answer;
int protocol = p->protocol;
struct list_head *last_perm;
spin_lock_bh(&inetsw6_lock);
if (p->type >= SOCK_MAX)
goto out_illegal;
/* If we are trying to override a permanent protocol, bail. */
answer = NULL;
last_perm = &inetsw6[p->type];
list_for_each(lh, &inetsw6[p->type]) {
answer = list_entry(lh, struct inet_protosw, list);
/* Check only the non-wild match. */
if (INET_PROTOSW_PERMANENT & answer->flags) {
if (protocol == answer->protocol)
break;
last_perm = lh;
}
answer = NULL;
}
if (answer)
goto out_permanent;
/* Add the new entry after the last permanent entry if any, so that
* the new entry does not override a permanent entry when matched with
* a wild-card protocol. But it is allowed to override any existing
* non-permanent entry. This means that when we remove this entry, the
* system automatically returns to the old behavior.
*/
list_add_rcu(&p->list, last_perm);
out:
spin_unlock_bh(&inetsw6_lock);
return;
out_permanent:
printk(KERN_ERR "Attempt to override permanent protocol %d.\n",
protocol);
goto out;
out_illegal:
printk(KERN_ERR
"Ignoring attempt to register invalid socket type %d.\n",
p->type);
goto out;
}
void
inet6_unregister_protosw(struct inet_protosw *p)
{
if (INET_PROTOSW_PERMANENT & p->flags) {
printk(KERN_ERR
"Attempt to unregister permanent protocol %d.\n",
p->protocol);
} else {
spin_lock_bh(&inetsw6_lock);
list_del_rcu(&p->list);
spin_unlock_bh(&inetsw6_lock);
synchronize_net();
}
}
int
snmp6_mib_init(void *ptr[2], size_t mibsize, size_t mibalign)
{
if (ptr == NULL)
return -EINVAL;
ptr[0] = __alloc_percpu(mibsize, mibalign);
if (!ptr[0])
goto err0;
ptr[1] = __alloc_percpu(mibsize, mibalign);
if (!ptr[1])
goto err1;
return 0;
err1:
free_percpu(ptr[0]);
ptr[0] = NULL;
err0:
return -ENOMEM;
}
void
snmp6_mib_free(void *ptr[2])
{
if (ptr == NULL)
return;
if (ptr[0])
free_percpu(ptr[0]);
if (ptr[1])
free_percpu(ptr[1]);
ptr[0] = ptr[1] = NULL;
}
static int __init init_ipv6_mibs(void)
{
if (snmp6_mib_init((void **)ipv6_statistics, sizeof (struct ipstats_mib),
__alignof__(struct ipstats_mib)) < 0)
goto err_ip_mib;
if (snmp6_mib_init((void **)icmpv6_statistics, sizeof (struct icmpv6_mib),
__alignof__(struct icmpv6_mib)) < 0)
goto err_icmp_mib;
if (snmp6_mib_init((void **)udp_stats_in6, sizeof (struct udp_mib),
__alignof__(struct udp_mib)) < 0)
goto err_udp_mib;
return 0;
err_udp_mib:
snmp6_mib_free((void **)icmpv6_statistics);
err_icmp_mib:
snmp6_mib_free((void **)ipv6_statistics);
err_ip_mib:
return -ENOMEM;
}
static void cleanup_ipv6_mibs(void)
{
snmp6_mib_free((void **)ipv6_statistics);
snmp6_mib_free((void **)icmpv6_statistics);
snmp6_mib_free((void **)udp_stats_in6);
}
extern int ipv6_misc_proc_init(void);
static int __init inet6_init(void)
{
struct sk_buff *dummy_skb;
struct list_head *r;
int err;
#ifdef MODULE
#if 0 /* FIXME --RR */
if (!mod_member_present(&__this_module, can_unload))
return -EINVAL;
__this_module.can_unload = &ipv6_unload;
#endif
#endif
if (sizeof(struct inet6_skb_parm) > sizeof(dummy_skb->cb)) {
printk(KERN_CRIT "inet6_proto_init: size fault\n");
return -EINVAL;
}
err = proto_register(&tcpv6_prot, 1);
if (err)
goto out;
err = proto_register(&udpv6_prot, 1);
if (err)
goto out_unregister_tcp_proto;
err = proto_register(&rawv6_prot, 1);
if (err)
goto out_unregister_udp_proto;
/* Register the socket-side information for inet6_create. */
for(r = &inetsw6[0]; r < &inetsw6[SOCK_MAX]; ++r)
INIT_LIST_HEAD(r);
/* We MUST register RAW sockets before we create the ICMP6,
* IGMP6, or NDISC control sockets.
*/
inet6_register_protosw(&rawv6_protosw);
/* Register the family here so that the init calls below will
* be able to create sockets. (?? is this dangerous ??)
*/
(void) sock_register(&inet6_family_ops);
/* Initialise ipv6 mibs */
err = init_ipv6_mibs();
if (err)
goto out_unregister_raw_proto;
/*
* 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
* able to communicate via both network protocols.
*/
#ifdef CONFIG_SYSCTL
ipv6_sysctl_register();
#endif
err = icmpv6_init(&inet6_family_ops);
if (err)
goto icmp_fail;
err = ndisc_init(&inet6_family_ops);
if (err)
goto ndisc_fail;
err = igmp6_init(&inet6_family_ops);
if (err)
goto igmp_fail;
/* Create /proc/foo6 entries. */
#ifdef CONFIG_PROC_FS
err = -ENOMEM;
if (raw6_proc_init())
goto proc_raw6_fail;
if (tcp6_proc_init())
goto proc_tcp6_fail;
if (udp6_proc_init())
goto proc_udp6_fail;
if (ipv6_misc_proc_init())
goto proc_misc6_fail;
if (ac6_proc_init())
goto proc_anycast6_fail;
if (if6_proc_init())
goto proc_if6_fail;
#endif
ipv6_packet_init();
ip6_route_init();
ip6_flowlabel_init();
err = addrconf_init();
if (err)
goto addrconf_fail;
sit_init();
/* Init v6 extension headers. */
ipv6_rthdr_init();
ipv6_frag_init();
ipv6_nodata_init();
ipv6_destopt_init();
/* Init v6 transport protocols. */
udpv6_init();
tcpv6_init();
err = 0;
out:
return err;
addrconf_fail:
ip6_flowlabel_cleanup();
ip6_route_cleanup();
ipv6_packet_cleanup();
#ifdef CONFIG_PROC_FS
if6_proc_exit();
proc_if6_fail:
ac6_proc_exit();
proc_anycast6_fail:
ipv6_misc_proc_exit();
proc_misc6_fail:
udp6_proc_exit();
proc_udp6_fail:
tcp6_proc_exit();
proc_tcp6_fail:
raw6_proc_exit();
proc_raw6_fail:
#endif
igmp6_cleanup();
igmp_fail:
ndisc_cleanup();
ndisc_fail:
icmpv6_cleanup();
icmp_fail:
#ifdef CONFIG_SYSCTL
ipv6_sysctl_unregister();
#endif
cleanup_ipv6_mibs();
out_unregister_raw_proto:
proto_unregister(&rawv6_prot);
out_unregister_udp_proto:
proto_unregister(&udpv6_prot);
out_unregister_tcp_proto:
proto_unregister(&tcpv6_prot);
goto out;
}
module_init(inet6_init);
static void __exit inet6_exit(void)
{
/* First of all disallow new sockets creation. */
sock_unregister(PF_INET6);
#ifdef CONFIG_PROC_FS
if6_proc_exit();
ac6_proc_exit();
ipv6_misc_proc_exit();
udp6_proc_exit();
tcp6_proc_exit();
raw6_proc_exit();
#endif
/* Cleanup code parts. */
sit_cleanup();
ip6_flowlabel_cleanup();
addrconf_cleanup();
ip6_route_cleanup();
ipv6_packet_cleanup();
igmp6_cleanup();
ndisc_cleanup();
icmpv6_cleanup();
#ifdef CONFIG_SYSCTL
ipv6_sysctl_unregister();
#endif
cleanup_ipv6_mibs();
proto_unregister(&rawv6_prot);
proto_unregister(&udpv6_prot);
proto_unregister(&tcpv6_prot);
}
module_exit(inet6_exit);
MODULE_ALIAS_NETPROTO(PF_INET6);

478
net/ipv6/ah6.c Normal file
View File

@@ -0,0 +1,478 @@
/*
* Copyright (C)2002 USAGI/WIDE Project
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Authors
*
* Mitsuru KANDA @USAGI : IPv6 Support
* Kazunori MIYAZAWA @USAGI :
* Kunihiro Ishiguro <kunihiro@ipinfusion.com>
*
* This file is derived from net/ipv4/ah.c.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <net/ip.h>
#include <net/ah.h>
#include <linux/crypto.h>
#include <linux/pfkeyv2.h>
#include <linux/string.h>
#include <net/icmp.h>
#include <net/ipv6.h>
#include <net/xfrm.h>
#include <asm/scatterlist.h>
static int zero_out_mutable_opts(struct ipv6_opt_hdr *opthdr)
{
u8 *opt = (u8 *)opthdr;
int len = ipv6_optlen(opthdr);
int off = 0;
int optlen = 0;
off += 2;
len -= 2;
while (len > 0) {
switch (opt[off]) {
case IPV6_TLV_PAD0:
optlen = 1;
break;
default:
if (len < 2)
goto bad;
optlen = opt[off+1]+2;
if (len < optlen)
goto bad;
if (opt[off] & 0x20)
memset(&opt[off+2], 0, opt[off+1]);
break;
}
off += optlen;
len -= optlen;
}
if (len == 0)
return 1;
bad:
return 0;
}
/**
* ipv6_rearrange_rthdr - rearrange IPv6 routing header
* @iph: IPv6 header
* @rthdr: routing header
*
* Rearrange the destination address in @iph and the addresses in @rthdr
* so that they appear in the order they will at the final destination.
* See Appendix A2 of RFC 2402 for details.
*/
static void ipv6_rearrange_rthdr(struct ipv6hdr *iph, struct ipv6_rt_hdr *rthdr)
{
int segments, segments_left;
struct in6_addr *addrs;
struct in6_addr final_addr;
segments_left = rthdr->segments_left;
if (segments_left == 0)
return;
rthdr->segments_left = 0;
/* The value of rthdr->hdrlen has been verified either by the system
* call if it is locally generated, or by ipv6_rthdr_rcv() for incoming
* packets. So we can assume that it is even and that segments is
* greater than or equal to segments_left.
*
* For the same reason we can assume that this option is of type 0.
*/
segments = rthdr->hdrlen >> 1;
addrs = ((struct rt0_hdr *)rthdr)->addr;
ipv6_addr_copy(&final_addr, addrs + segments - 1);
addrs += segments - segments_left;
memmove(addrs + 1, addrs, (segments_left - 1) * sizeof(*addrs));
ipv6_addr_copy(addrs, &iph->daddr);
ipv6_addr_copy(&iph->daddr, &final_addr);
}
static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len)
{
union {
struct ipv6hdr *iph;
struct ipv6_opt_hdr *opth;
struct ipv6_rt_hdr *rth;
char *raw;
} exthdr = { .iph = iph };
char *end = exthdr.raw + len;
int nexthdr = iph->nexthdr;
exthdr.iph++;
while (exthdr.raw < end) {
switch (nexthdr) {
case NEXTHDR_HOP:
case NEXTHDR_DEST:
if (!zero_out_mutable_opts(exthdr.opth)) {
LIMIT_NETDEBUG(printk(
KERN_WARNING "overrun %sopts\n",
nexthdr == NEXTHDR_HOP ?
"hop" : "dest"));
return -EINVAL;
}
break;
case NEXTHDR_ROUTING:
ipv6_rearrange_rthdr(iph, exthdr.rth);
break;
default :
return 0;
}
nexthdr = exthdr.opth->nexthdr;
exthdr.raw += ipv6_optlen(exthdr.opth);
}
return 0;
}
static int ah6_output(struct xfrm_state *x, struct sk_buff *skb)
{
int err;
int extlen;
struct ipv6hdr *top_iph;
struct ip_auth_hdr *ah;
struct ah_data *ahp;
u8 nexthdr;
char tmp_base[8];
struct {
struct in6_addr daddr;
char hdrs[0];
} *tmp_ext;
top_iph = (struct ipv6hdr *)skb->data;
top_iph->payload_len = htons(skb->len - sizeof(*top_iph));
nexthdr = *skb->nh.raw;
*skb->nh.raw = IPPROTO_AH;
/* When there are no extension headers, we only need to save the first
* 8 bytes of the base IP header.
*/
memcpy(tmp_base, top_iph, sizeof(tmp_base));
tmp_ext = NULL;
extlen = skb->h.raw - (unsigned char *)(top_iph + 1);
if (extlen) {
extlen += sizeof(*tmp_ext);
tmp_ext = kmalloc(extlen, GFP_ATOMIC);
if (!tmp_ext) {
err = -ENOMEM;
goto error;
}
memcpy(tmp_ext, &top_iph->daddr, extlen);
err = ipv6_clear_mutable_options(top_iph,
extlen - sizeof(*tmp_ext) +
sizeof(*top_iph));
if (err)
goto error_free_iph;
}
ah = (struct ip_auth_hdr *)skb->h.raw;
ah->nexthdr = nexthdr;
top_iph->priority = 0;
top_iph->flow_lbl[0] = 0;
top_iph->flow_lbl[1] = 0;
top_iph->flow_lbl[2] = 0;
top_iph->hop_limit = 0;
ahp = x->data;
ah->hdrlen = (XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) +
ahp->icv_trunc_len) >> 2) - 2;
ah->reserved = 0;
ah->spi = x->id.spi;
ah->seq_no = htonl(++x->replay.oseq);
ahp->icv(ahp, skb, ah->auth_data);
err = 0;
memcpy(top_iph, tmp_base, sizeof(tmp_base));
if (tmp_ext) {
memcpy(&top_iph->daddr, tmp_ext, extlen);
error_free_iph:
kfree(tmp_ext);
}
error:
return err;
}
static int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_buff *skb)
{
/*
* Before process AH
* [IPv6][Ext1][Ext2][AH][Dest][Payload]
* |<-------------->| hdr_len
*
* To erase AH:
* Keeping copy of cleared headers. After AH processing,
* Moving the pointer of skb->nh.raw by using skb_pull as long as AH
* header length. Then copy back the copy as long as hdr_len
* If destination header following AH exists, copy it into after [Ext2].
*
* |<>|[IPv6][Ext1][Ext2][Dest][Payload]
* There is offset of AH before IPv6 header after the process.
*/
struct ipv6_auth_hdr *ah;
struct ah_data *ahp;
unsigned char *tmp_hdr = NULL;
u16 hdr_len;
u16 ah_hlen;
int nexthdr;
if (!pskb_may_pull(skb, sizeof(struct ip_auth_hdr)))
goto out;
/* We are going to _remove_ AH header to keep sockets happy,
* so... Later this can change. */
if (skb_cloned(skb) &&
pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
goto out;
hdr_len = skb->data - skb->nh.raw;
ah = (struct ipv6_auth_hdr*)skb->data;
ahp = x->data;
nexthdr = ah->nexthdr;
ah_hlen = (ah->hdrlen + 2) << 2;
if (ah_hlen != XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + ahp->icv_full_len) &&
ah_hlen != XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + ahp->icv_trunc_len))
goto out;
if (!pskb_may_pull(skb, ah_hlen))
goto out;
tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC);
if (!tmp_hdr)
goto out;
memcpy(tmp_hdr, skb->nh.raw, hdr_len);
if (ipv6_clear_mutable_options(skb->nh.ipv6h, hdr_len))
goto out;
skb->nh.ipv6h->priority = 0;
skb->nh.ipv6h->flow_lbl[0] = 0;
skb->nh.ipv6h->flow_lbl[1] = 0;
skb->nh.ipv6h->flow_lbl[2] = 0;
skb->nh.ipv6h->hop_limit = 0;
{
u8 auth_data[MAX_AH_AUTH_LEN];
memcpy(auth_data, ah->auth_data, ahp->icv_trunc_len);
memset(ah->auth_data, 0, ahp->icv_trunc_len);
skb_push(skb, skb->data - skb->nh.raw);
ahp->icv(ahp, skb, ah->auth_data);
if (memcmp(ah->auth_data, auth_data, ahp->icv_trunc_len)) {
LIMIT_NETDEBUG(
printk(KERN_WARNING "ipsec ah authentication error\n"));
x->stats.integrity_failed++;
goto free_out;
}
}
skb->nh.raw = skb_pull(skb, ah_hlen);
memcpy(skb->nh.raw, tmp_hdr, hdr_len);
skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
skb_pull(skb, hdr_len);
skb->h.raw = skb->data;
kfree(tmp_hdr);
return nexthdr;
free_out:
kfree(tmp_hdr);
out:
return -EINVAL;
}
static void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
int type, int code, int offset, __u32 info)
{
struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
struct ip_auth_hdr *ah = (struct ip_auth_hdr*)(skb->data+offset);
struct xfrm_state *x;
if (type != ICMPV6_DEST_UNREACH &&
type != ICMPV6_PKT_TOOBIG)
return;
x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, ah->spi, IPPROTO_AH, AF_INET6);
if (!x)
return;
NETDEBUG(printk(KERN_DEBUG "pmtu discovery on SA AH/%08x/"
"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
ntohl(ah->spi), NIP6(iph->daddr)));
xfrm_state_put(x);
}
static int ah6_init_state(struct xfrm_state *x, void *args)
{
struct ah_data *ahp = NULL;
struct xfrm_algo_desc *aalg_desc;
if (!x->aalg)
goto error;
/* null auth can use a zero length key */
if (x->aalg->alg_key_len > 512)
goto error;
if (x->encap)
goto error;
ahp = kmalloc(sizeof(*ahp), GFP_KERNEL);
if (ahp == NULL)
return -ENOMEM;
memset(ahp, 0, sizeof(*ahp));
ahp->key = x->aalg->alg_key;
ahp->key_len = (x->aalg->alg_key_len+7)/8;
ahp->tfm = crypto_alloc_tfm(x->aalg->alg_name, 0);
if (!ahp->tfm)
goto error;
ahp->icv = ah_hmac_digest;
/*
* Lookup the algorithm description maintained by xfrm_algo,
* verify crypto transform properties, and store information
* we need for AH processing. This lookup cannot fail here
* after a successful crypto_alloc_tfm().
*/
aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0);
BUG_ON(!aalg_desc);
if (aalg_desc->uinfo.auth.icv_fullbits/8 !=
crypto_tfm_alg_digestsize(ahp->tfm)) {
printk(KERN_INFO "AH: %s digestsize %u != %hu\n",
x->aalg->alg_name, crypto_tfm_alg_digestsize(ahp->tfm),
aalg_desc->uinfo.auth.icv_fullbits/8);
goto error;
}
ahp->icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8;
ahp->icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/8;
BUG_ON(ahp->icv_trunc_len > MAX_AH_AUTH_LEN);
ahp->work_icv = kmalloc(ahp->icv_full_len, GFP_KERNEL);
if (!ahp->work_icv)
goto error;
x->props.header_len = XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + ahp->icv_trunc_len);
if (x->props.mode)
x->props.header_len += sizeof(struct ipv6hdr);
x->data = ahp;
return 0;
error:
if (ahp) {
if (ahp->work_icv)
kfree(ahp->work_icv);
if (ahp->tfm)
crypto_free_tfm(ahp->tfm);
kfree(ahp);
}
return -EINVAL;
}
static void ah6_destroy(struct xfrm_state *x)
{
struct ah_data *ahp = x->data;
if (!ahp)
return;
if (ahp->work_icv) {
kfree(ahp->work_icv);
ahp->work_icv = NULL;
}
if (ahp->tfm) {
crypto_free_tfm(ahp->tfm);
ahp->tfm = NULL;
}
kfree(ahp);
}
static struct xfrm_type ah6_type =
{
.description = "AH6",
.owner = THIS_MODULE,
.proto = IPPROTO_AH,
.init_state = ah6_init_state,
.destructor = ah6_destroy,
.input = ah6_input,
.output = ah6_output
};
static struct inet6_protocol ah6_protocol = {
.handler = xfrm6_rcv,
.err_handler = ah6_err,
.flags = INET6_PROTO_NOPOLICY,
};
static int __init ah6_init(void)
{
if (xfrm_register_type(&ah6_type, AF_INET6) < 0) {
printk(KERN_INFO "ipv6 ah init: can't add xfrm type\n");
return -EAGAIN;
}
if (inet6_add_protocol(&ah6_protocol, IPPROTO_AH) < 0) {
printk(KERN_INFO "ipv6 ah init: can't add protocol\n");
xfrm_unregister_type(&ah6_type, AF_INET6);
return -EAGAIN;
}
return 0;
}
static void __exit ah6_fini(void)
{
if (inet6_del_protocol(&ah6_protocol, IPPROTO_AH) < 0)
printk(KERN_INFO "ipv6 ah close: can't remove protocol\n");
if (xfrm_unregister_type(&ah6_type, AF_INET6) < 0)
printk(KERN_INFO "ipv6 ah close: can't remove xfrm type\n");
}
module_init(ah6_init);
module_exit(ah6_fini);
MODULE_LICENSE("GPL");

594
net/ipv6/anycast.c Normal file
View File

@@ -0,0 +1,594 @@
/*
* Anycast support for IPv6
* Linux INET6 implementation
*
* Authors:
* David L Stevens (dlstevens@us.ibm.com)
*
* based heavily on net/ipv6/mcast.c
*
* 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/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/random.h>
#include <linux/string.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/sched.h>
#include <linux/net.h>
#include <linux/in6.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/route.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <net/sock.h>
#include <net/snmp.h>
#include <net/ipv6.h>
#include <net/protocol.h>
#include <net/if_inet6.h>
#include <net/ndisc.h>
#include <net/addrconf.h>
#include <net/ip6_route.h>
#include <net/checksum.h>
static int ipv6_dev_ac_dec(struct net_device *dev, struct in6_addr *addr);
/* Big ac list lock for all the sockets */
static DEFINE_RWLOCK(ipv6_sk_ac_lock);
static int
ip6_onlink(struct in6_addr *addr, struct net_device *dev)
{
struct inet6_dev *idev;
struct inet6_ifaddr *ifa;
int onlink;
onlink = 0;
read_lock(&addrconf_lock);
idev = __in6_dev_get(dev);
if (idev) {
read_lock_bh(&idev->lock);
for (ifa=idev->addr_list; ifa; ifa=ifa->if_next) {
onlink = ipv6_prefix_equal(addr, &ifa->addr,
ifa->prefix_len);
if (onlink)
break;
}
read_unlock_bh(&idev->lock);
}
read_unlock(&addrconf_lock);
return onlink;
}
/*
* socket join an anycast group
*/
int ipv6_sock_ac_join(struct sock *sk, int ifindex, struct in6_addr *addr)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct net_device *dev = NULL;
struct inet6_dev *idev;
struct ipv6_ac_socklist *pac;
int ishost = !ipv6_devconf.forwarding;
int err = 0;
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (ipv6_addr_is_multicast(addr))
return -EINVAL;
if (ipv6_chk_addr(addr, NULL, 0))
return -EINVAL;
pac = sock_kmalloc(sk, sizeof(struct ipv6_ac_socklist), GFP_KERNEL);
if (pac == NULL)
return -ENOMEM;
pac->acl_next = NULL;
ipv6_addr_copy(&pac->acl_addr, addr);
if (ifindex == 0) {
struct rt6_info *rt;
rt = rt6_lookup(addr, NULL, 0, 0);
if (rt) {
dev = rt->rt6i_dev;
dev_hold(dev);
dst_release(&rt->u.dst);
} else if (ishost) {
err = -EADDRNOTAVAIL;
goto out_free_pac;
} else {
/* router, no matching interface: just pick one */
dev = dev_get_by_flags(IFF_UP, IFF_UP|IFF_LOOPBACK);
}
} else
dev = dev_get_by_index(ifindex);
if (dev == NULL) {
err = -ENODEV;
goto out_free_pac;
}
idev = in6_dev_get(dev);
if (!idev) {
if (ifindex)
err = -ENODEV;
else
err = -EADDRNOTAVAIL;
goto out_dev_put;
}
/* reset ishost, now that we have a specific device */
ishost = !idev->cnf.forwarding;
in6_dev_put(idev);
pac->acl_ifindex = dev->ifindex;
/* XXX
* For hosts, allow link-local or matching prefix anycasts.
* This obviates the need for propagating anycast routes while
* still allowing some non-router anycast participation.
*/
if (!ip6_onlink(addr, dev)) {
if (ishost)
err = -EADDRNOTAVAIL;
if (err)
goto out_dev_put;
}
err = ipv6_dev_ac_inc(dev, addr);
if (err)
goto out_dev_put;
write_lock_bh(&ipv6_sk_ac_lock);
pac->acl_next = np->ipv6_ac_list;
np->ipv6_ac_list = pac;
write_unlock_bh(&ipv6_sk_ac_lock);
dev_put(dev);
return 0;
out_dev_put:
dev_put(dev);
out_free_pac:
sock_kfree_s(sk, pac, sizeof(*pac));
return err;
}
/*
* socket leave an anycast group
*/
int ipv6_sock_ac_drop(struct sock *sk, int ifindex, struct in6_addr *addr)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct net_device *dev;
struct ipv6_ac_socklist *pac, *prev_pac;
write_lock_bh(&ipv6_sk_ac_lock);
prev_pac = NULL;
for (pac = np->ipv6_ac_list; pac; pac = pac->acl_next) {
if ((ifindex == 0 || pac->acl_ifindex == ifindex) &&
ipv6_addr_equal(&pac->acl_addr, addr))
break;
prev_pac = pac;
}
if (!pac) {
write_unlock_bh(&ipv6_sk_ac_lock);
return -ENOENT;
}
if (prev_pac)
prev_pac->acl_next = pac->acl_next;
else
np->ipv6_ac_list = pac->acl_next;
write_unlock_bh(&ipv6_sk_ac_lock);
dev = dev_get_by_index(pac->acl_ifindex);
if (dev) {
ipv6_dev_ac_dec(dev, &pac->acl_addr);
dev_put(dev);
}
sock_kfree_s(sk, pac, sizeof(*pac));
return 0;
}
void ipv6_sock_ac_close(struct sock *sk)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct net_device *dev = NULL;
struct ipv6_ac_socklist *pac;
int prev_index;
write_lock_bh(&ipv6_sk_ac_lock);
pac = np->ipv6_ac_list;
np->ipv6_ac_list = NULL;
write_unlock_bh(&ipv6_sk_ac_lock);
prev_index = 0;
while (pac) {
struct ipv6_ac_socklist *next = pac->acl_next;
if (pac->acl_ifindex != prev_index) {
if (dev)
dev_put(dev);
dev = dev_get_by_index(pac->acl_ifindex);
prev_index = pac->acl_ifindex;
}
if (dev)
ipv6_dev_ac_dec(dev, &pac->acl_addr);
sock_kfree_s(sk, pac, sizeof(*pac));
pac = next;
}
if (dev)
dev_put(dev);
}
#if 0
/* The function is not used, which is funny. Apparently, author
* supposed to use it to filter out datagrams inside udp/raw but forgot.
*
* It is OK, anycasts are not special comparing to delivery to unicasts.
*/
int inet6_ac_check(struct sock *sk, struct in6_addr *addr, int ifindex)
{
struct ipv6_ac_socklist *pac;
struct ipv6_pinfo *np = inet6_sk(sk);
int found;
found = 0;
read_lock(&ipv6_sk_ac_lock);
for (pac=np->ipv6_ac_list; pac; pac=pac->acl_next) {
if (ifindex && pac->acl_ifindex != ifindex)
continue;
found = ipv6_addr_equal(&pac->acl_addr, addr);
if (found)
break;
}
read_unlock(&ipv6_sk_ac_lock);
return found;
}
#endif
static void aca_put(struct ifacaddr6 *ac)
{
if (atomic_dec_and_test(&ac->aca_refcnt)) {
in6_dev_put(ac->aca_idev);
dst_release(&ac->aca_rt->u.dst);
kfree(ac);
}
}
/*
* device anycast group inc (add if not found)
*/
int ipv6_dev_ac_inc(struct net_device *dev, struct in6_addr *addr)
{
struct ifacaddr6 *aca;
struct inet6_dev *idev;
struct rt6_info *rt;
int err;
idev = in6_dev_get(dev);
if (idev == NULL)
return -EINVAL;
write_lock_bh(&idev->lock);
if (idev->dead) {
err = -ENODEV;
goto out;
}
for (aca = idev->ac_list; aca; aca = aca->aca_next) {
if (ipv6_addr_equal(&aca->aca_addr, addr)) {
aca->aca_users++;
err = 0;
goto out;
}
}
/*
* not found: create a new one.
*/
aca = kmalloc(sizeof(struct ifacaddr6), GFP_ATOMIC);
if (aca == NULL) {
err = -ENOMEM;
goto out;
}
rt = addrconf_dst_alloc(idev, addr, 1);
if (IS_ERR(rt)) {
kfree(aca);
err = PTR_ERR(rt);
goto out;
}
memset(aca, 0, sizeof(struct ifacaddr6));
ipv6_addr_copy(&aca->aca_addr, addr);
aca->aca_idev = idev;
aca->aca_rt = rt;
aca->aca_users = 1;
/* aca_tstamp should be updated upon changes */
aca->aca_cstamp = aca->aca_tstamp = jiffies;
atomic_set(&aca->aca_refcnt, 2);
spin_lock_init(&aca->aca_lock);
aca->aca_next = idev->ac_list;
idev->ac_list = aca;
write_unlock_bh(&idev->lock);
dst_hold(&rt->u.dst);
if (ip6_ins_rt(rt, NULL, NULL))
dst_release(&rt->u.dst);
addrconf_join_solict(dev, &aca->aca_addr);
aca_put(aca);
return 0;
out:
write_unlock_bh(&idev->lock);
in6_dev_put(idev);
return err;
}
/*
* device anycast group decrement
*/
int __ipv6_dev_ac_dec(struct inet6_dev *idev, struct in6_addr *addr)
{
struct ifacaddr6 *aca, *prev_aca;
write_lock_bh(&idev->lock);
prev_aca = NULL;
for (aca = idev->ac_list; aca; aca = aca->aca_next) {
if (ipv6_addr_equal(&aca->aca_addr, addr))
break;
prev_aca = aca;
}
if (!aca) {
write_unlock_bh(&idev->lock);
return -ENOENT;
}
if (--aca->aca_users > 0) {
write_unlock_bh(&idev->lock);
return 0;
}
if (prev_aca)
prev_aca->aca_next = aca->aca_next;
else
idev->ac_list = aca->aca_next;
write_unlock_bh(&idev->lock);
addrconf_leave_solict(idev, &aca->aca_addr);
dst_hold(&aca->aca_rt->u.dst);
if (ip6_del_rt(aca->aca_rt, NULL, NULL))
dst_free(&aca->aca_rt->u.dst);
else
dst_release(&aca->aca_rt->u.dst);
aca_put(aca);
return 0;
}
static int ipv6_dev_ac_dec(struct net_device *dev, struct in6_addr *addr)
{
int ret;
struct inet6_dev *idev = in6_dev_get(dev);
if (idev == NULL)
return -ENODEV;
ret = __ipv6_dev_ac_dec(idev, addr);
in6_dev_put(idev);
return ret;
}
/*
* check if the interface has this anycast address
*/
static int ipv6_chk_acast_dev(struct net_device *dev, struct in6_addr *addr)
{
struct inet6_dev *idev;
struct ifacaddr6 *aca;
idev = in6_dev_get(dev);
if (idev) {
read_lock_bh(&idev->lock);
for (aca = idev->ac_list; aca; aca = aca->aca_next)
if (ipv6_addr_equal(&aca->aca_addr, addr))
break;
read_unlock_bh(&idev->lock);
in6_dev_put(idev);
return aca != 0;
}
return 0;
}
/*
* check if given interface (or any, if dev==0) has this anycast address
*/
int ipv6_chk_acast_addr(struct net_device *dev, struct in6_addr *addr)
{
if (dev)
return ipv6_chk_acast_dev(dev, addr);
read_lock(&dev_base_lock);
for (dev=dev_base; dev; dev=dev->next)
if (ipv6_chk_acast_dev(dev, addr))
break;
read_unlock(&dev_base_lock);
return dev != 0;
}
#ifdef CONFIG_PROC_FS
struct ac6_iter_state {
struct net_device *dev;
struct inet6_dev *idev;
};
#define ac6_seq_private(seq) ((struct ac6_iter_state *)(seq)->private)
static inline struct ifacaddr6 *ac6_get_first(struct seq_file *seq)
{
struct ifacaddr6 *im = NULL;
struct ac6_iter_state *state = ac6_seq_private(seq);
for (state->dev = dev_base, state->idev = NULL;
state->dev;
state->dev = state->dev->next) {
struct inet6_dev *idev;
idev = in6_dev_get(state->dev);
if (!idev)
continue;
read_lock_bh(&idev->lock);
im = idev->ac_list;
if (im) {
state->idev = idev;
break;
}
read_unlock_bh(&idev->lock);
}
return im;
}
static struct ifacaddr6 *ac6_get_next(struct seq_file *seq, struct ifacaddr6 *im)
{
struct ac6_iter_state *state = ac6_seq_private(seq);
im = im->aca_next;
while (!im) {
if (likely(state->idev != NULL)) {
read_unlock_bh(&state->idev->lock);
in6_dev_put(state->idev);
}
state->dev = state->dev->next;
if (!state->dev) {
state->idev = NULL;
break;
}
state->idev = in6_dev_get(state->dev);
if (!state->idev)
continue;
read_lock_bh(&state->idev->lock);
im = state->idev->ac_list;
}
return im;
}
static struct ifacaddr6 *ac6_get_idx(struct seq_file *seq, loff_t pos)
{
struct ifacaddr6 *im = ac6_get_first(seq);
if (im)
while (pos && (im = ac6_get_next(seq, im)) != NULL)
--pos;
return pos ? NULL : im;
}
static void *ac6_seq_start(struct seq_file *seq, loff_t *pos)
{
read_lock(&dev_base_lock);
return ac6_get_idx(seq, *pos);
}
static void *ac6_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
struct ifacaddr6 *im;
im = ac6_get_next(seq, v);
++*pos;
return im;
}
static void ac6_seq_stop(struct seq_file *seq, void *v)
{
struct ac6_iter_state *state = ac6_seq_private(seq);
if (likely(state->idev != NULL)) {
read_unlock_bh(&state->idev->lock);
in6_dev_put(state->idev);
}
read_unlock(&dev_base_lock);
}
static int ac6_seq_show(struct seq_file *seq, void *v)
{
struct ifacaddr6 *im = (struct ifacaddr6 *)v;
struct ac6_iter_state *state = ac6_seq_private(seq);
seq_printf(seq,
"%-4d %-15s "
"%04x%04x%04x%04x%04x%04x%04x%04x "
"%5d\n",
state->dev->ifindex, state->dev->name,
NIP6(im->aca_addr),
im->aca_users);
return 0;
}
static struct seq_operations ac6_seq_ops = {
.start = ac6_seq_start,
.next = ac6_seq_next,
.stop = ac6_seq_stop,
.show = ac6_seq_show,
};
static int ac6_seq_open(struct inode *inode, struct file *file)
{
struct seq_file *seq;
int rc = -ENOMEM;
struct ac6_iter_state *s = kmalloc(sizeof(*s), GFP_KERNEL);
if (!s)
goto out;
rc = seq_open(file, &ac6_seq_ops);
if (rc)
goto out_kfree;
seq = file->private_data;
seq->private = s;
memset(s, 0, sizeof(*s));
out:
return rc;
out_kfree:
kfree(s);
goto out;
}
static struct file_operations ac6_seq_fops = {
.owner = THIS_MODULE,
.open = ac6_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_private,
};
int __init ac6_proc_init(void)
{
if (!proc_net_fops_create("anycast6", S_IRUGO, &ac6_seq_fops))
return -ENOMEM;
return 0;
}
void ac6_proc_exit(void)
{
proc_net_remove("anycast6");
}
#endif

600
net/ipv6/datagram.c Normal file
View File

@@ -0,0 +1,600 @@
/*
* common UDP/RAW code
* Linux INET6 implementation
*
* Authors:
* Pedro Roque <roque@di.fc.ul.pt>
*
* $Id: datagram.c,v 1.24 2002/02/01 22:01:04 davem 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/errno.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/in6.h>
#include <linux/ipv6.h>
#include <linux/route.h>
#include <net/ipv6.h>
#include <net/ndisc.h>
#include <net/addrconf.h>
#include <net/transp_v6.h>
#include <net/ip6_route.h>
#include <linux/errqueue.h>
#include <asm/uaccess.h>
int ip6_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
struct sockaddr_in6 *usin = (struct sockaddr_in6 *) uaddr;
struct inet_sock *inet = inet_sk(sk);
struct ipv6_pinfo *np = inet6_sk(sk);
struct in6_addr *daddr, *final_p = NULL, final;
struct dst_entry *dst;
struct flowi fl;
struct ip6_flowlabel *flowlabel = NULL;
int addr_type;
int err;
if (usin->sin6_family == AF_INET) {
if (__ipv6_only_sock(sk))
return -EAFNOSUPPORT;
err = ip4_datagram_connect(sk, uaddr, addr_len);
goto ipv4_connected;
}
if (addr_len < SIN6_LEN_RFC2133)
return -EINVAL;
if (usin->sin6_family != AF_INET6)
return -EAFNOSUPPORT;
memset(&fl, 0, sizeof(fl));
if (np->sndflow) {
fl.fl6_flowlabel = usin->sin6_flowinfo&IPV6_FLOWINFO_MASK;
if (fl.fl6_flowlabel&IPV6_FLOWLABEL_MASK) {
flowlabel = fl6_sock_lookup(sk, fl.fl6_flowlabel);
if (flowlabel == NULL)
return -EINVAL;
ipv6_addr_copy(&usin->sin6_addr, &flowlabel->dst);
}
}
addr_type = ipv6_addr_type(&usin->sin6_addr);
if (addr_type == IPV6_ADDR_ANY) {
/*
* connect to self
*/
usin->sin6_addr.s6_addr[15] = 0x01;
}
daddr = &usin->sin6_addr;
if (addr_type == IPV6_ADDR_MAPPED) {
struct sockaddr_in sin;
if (__ipv6_only_sock(sk)) {
err = -ENETUNREACH;
goto out;
}
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = daddr->s6_addr32[3];
sin.sin_port = usin->sin6_port;
err = ip4_datagram_connect(sk,
(struct sockaddr*) &sin,
sizeof(sin));
ipv4_connected:
if (err)
goto out;
ipv6_addr_set(&np->daddr, 0, 0, htonl(0x0000ffff), inet->daddr);
if (ipv6_addr_any(&np->saddr)) {
ipv6_addr_set(&np->saddr, 0, 0, htonl(0x0000ffff),
inet->saddr);
}
if (ipv6_addr_any(&np->rcv_saddr)) {
ipv6_addr_set(&np->rcv_saddr, 0, 0, htonl(0x0000ffff),
inet->rcv_saddr);
}
goto out;
}
if (addr_type&IPV6_ADDR_LINKLOCAL) {
if (addr_len >= sizeof(struct sockaddr_in6) &&
usin->sin6_scope_id) {
if (sk->sk_bound_dev_if &&
sk->sk_bound_dev_if != usin->sin6_scope_id) {
err = -EINVAL;
goto out;
}
sk->sk_bound_dev_if = usin->sin6_scope_id;
if (!sk->sk_bound_dev_if &&
(addr_type & IPV6_ADDR_MULTICAST))
fl.oif = np->mcast_oif;
}
/* Connect to link-local address requires an interface */
if (!sk->sk_bound_dev_if) {
err = -EINVAL;
goto out;
}
}
ipv6_addr_copy(&np->daddr, daddr);
np->flow_label = fl.fl6_flowlabel;
inet->dport = usin->sin6_port;
/*
* Check for a route to destination an obtain the
* destination cache for it.
*/
fl.proto = sk->sk_protocol;
ipv6_addr_copy(&fl.fl6_dst, &np->daddr);
ipv6_addr_copy(&fl.fl6_src, &np->saddr);
fl.oif = sk->sk_bound_dev_if;
fl.fl_ip_dport = inet->dport;
fl.fl_ip_sport = inet->sport;
if (!fl.oif && (addr_type&IPV6_ADDR_MULTICAST))
fl.oif = np->mcast_oif;
if (flowlabel) {
if (flowlabel->opt && flowlabel->opt->srcrt) {
struct rt0_hdr *rt0 = (struct rt0_hdr *) flowlabel->opt->srcrt;
ipv6_addr_copy(&final, &fl.fl6_dst);
ipv6_addr_copy(&fl.fl6_dst, rt0->addr);
final_p = &final;
}
} else if (np->opt && np->opt->srcrt) {
struct rt0_hdr *rt0 = (struct rt0_hdr *)np->opt->srcrt;
ipv6_addr_copy(&final, &fl.fl6_dst);
ipv6_addr_copy(&fl.fl6_dst, rt0->addr);
final_p = &final;
}
err = ip6_dst_lookup(sk, &dst, &fl);
if (err)
goto out;
if (final_p)
ipv6_addr_copy(&fl.fl6_dst, final_p);
if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0) {
dst_release(dst);
goto out;
}
/* source address lookup done in ip6_dst_lookup */
if (ipv6_addr_any(&np->saddr))
ipv6_addr_copy(&np->saddr, &fl.fl6_src);
if (ipv6_addr_any(&np->rcv_saddr)) {
ipv6_addr_copy(&np->rcv_saddr, &fl.fl6_src);
inet->rcv_saddr = LOOPBACK4_IPV6;
}
ip6_dst_store(sk, dst,
ipv6_addr_equal(&fl.fl6_dst, &np->daddr) ?
&np->daddr : NULL);
sk->sk_state = TCP_ESTABLISHED;
out:
fl6_sock_release(flowlabel);
return err;
}
void ipv6_icmp_error(struct sock *sk, struct sk_buff *skb, int err,
u16 port, u32 info, u8 *payload)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct icmp6hdr *icmph = (struct icmp6hdr *)skb->h.raw;
struct sock_exterr_skb *serr;
if (!np->recverr)
return;
skb = skb_clone(skb, GFP_ATOMIC);
if (!skb)
return;
serr = SKB_EXT_ERR(skb);
serr->ee.ee_errno = err;
serr->ee.ee_origin = SO_EE_ORIGIN_ICMP6;
serr->ee.ee_type = icmph->icmp6_type;
serr->ee.ee_code = icmph->icmp6_code;
serr->ee.ee_pad = 0;
serr->ee.ee_info = info;
serr->ee.ee_data = 0;
serr->addr_offset = (u8*)&(((struct ipv6hdr*)(icmph+1))->daddr) - skb->nh.raw;
serr->port = port;
skb->h.raw = payload;
__skb_pull(skb, payload - skb->data);
if (sock_queue_err_skb(sk, skb))
kfree_skb(skb);
}
void ipv6_local_error(struct sock *sk, int err, struct flowi *fl, u32 info)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct sock_exterr_skb *serr;
struct ipv6hdr *iph;
struct sk_buff *skb;
if (!np->recverr)
return;
skb = alloc_skb(sizeof(struct ipv6hdr), GFP_ATOMIC);
if (!skb)
return;
iph = (struct ipv6hdr*)skb_put(skb, sizeof(struct ipv6hdr));
skb->nh.ipv6h = iph;
ipv6_addr_copy(&iph->daddr, &fl->fl6_dst);
serr = SKB_EXT_ERR(skb);
serr->ee.ee_errno = err;
serr->ee.ee_origin = SO_EE_ORIGIN_LOCAL;
serr->ee.ee_type = 0;
serr->ee.ee_code = 0;
serr->ee.ee_pad = 0;
serr->ee.ee_info = info;
serr->ee.ee_data = 0;
serr->addr_offset = (u8*)&iph->daddr - skb->nh.raw;
serr->port = fl->fl_ip_dport;
skb->h.raw = skb->tail;
__skb_pull(skb, skb->tail - skb->data);
if (sock_queue_err_skb(sk, skb))
kfree_skb(skb);
}
/*
* Handle MSG_ERRQUEUE
*/
int ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct sock_exterr_skb *serr;
struct sk_buff *skb, *skb2;
struct sockaddr_in6 *sin;
struct {
struct sock_extended_err ee;
struct sockaddr_in6 offender;
} errhdr;
int err;
int copied;
err = -EAGAIN;
skb = skb_dequeue(&sk->sk_error_queue);
if (skb == NULL)
goto out;
copied = skb->len;
if (copied > len) {
msg->msg_flags |= MSG_TRUNC;
copied = len;
}
err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
if (err)
goto out_free_skb;
sock_recv_timestamp(msg, sk, skb);
serr = SKB_EXT_ERR(skb);
sin = (struct sockaddr_in6 *)msg->msg_name;
if (sin) {
sin->sin6_family = AF_INET6;
sin->sin6_flowinfo = 0;
sin->sin6_port = serr->port;
sin->sin6_scope_id = 0;
if (serr->ee.ee_origin == SO_EE_ORIGIN_ICMP6) {
ipv6_addr_copy(&sin->sin6_addr,
(struct in6_addr *)(skb->nh.raw + serr->addr_offset));
if (np->sndflow)
sin->sin6_flowinfo = *(u32*)(skb->nh.raw + serr->addr_offset - 24) & IPV6_FLOWINFO_MASK;
if (ipv6_addr_type(&sin->sin6_addr) & IPV6_ADDR_LINKLOCAL)
sin->sin6_scope_id = IP6CB(skb)->iif;
} else {
ipv6_addr_set(&sin->sin6_addr, 0, 0,
htonl(0xffff),
*(u32*)(skb->nh.raw + serr->addr_offset));
}
}
memcpy(&errhdr.ee, &serr->ee, sizeof(struct sock_extended_err));
sin = &errhdr.offender;
sin->sin6_family = AF_UNSPEC;
if (serr->ee.ee_origin != SO_EE_ORIGIN_LOCAL) {
sin->sin6_family = AF_INET6;
sin->sin6_flowinfo = 0;
sin->sin6_scope_id = 0;
if (serr->ee.ee_origin == SO_EE_ORIGIN_ICMP6) {
ipv6_addr_copy(&sin->sin6_addr, &skb->nh.ipv6h->saddr);
if (np->rxopt.all)
datagram_recv_ctl(sk, msg, skb);
if (ipv6_addr_type(&sin->sin6_addr) & IPV6_ADDR_LINKLOCAL)
sin->sin6_scope_id = IP6CB(skb)->iif;
} else {
struct inet_sock *inet = inet_sk(sk);
ipv6_addr_set(&sin->sin6_addr, 0, 0,
htonl(0xffff),
skb->nh.iph->saddr);
if (inet->cmsg_flags)
ip_cmsg_recv(msg, skb);
}
}
put_cmsg(msg, SOL_IPV6, IPV6_RECVERR, sizeof(errhdr), &errhdr);
/* Now we could try to dump offended packet options */
msg->msg_flags |= MSG_ERRQUEUE;
err = copied;
/* Reset and regenerate socket error */
spin_lock_irq(&sk->sk_error_queue.lock);
sk->sk_err = 0;
if ((skb2 = skb_peek(&sk->sk_error_queue)) != NULL) {
sk->sk_err = SKB_EXT_ERR(skb2)->ee.ee_errno;
spin_unlock_irq(&sk->sk_error_queue.lock);
sk->sk_error_report(sk);
} else {
spin_unlock_irq(&sk->sk_error_queue.lock);
}
out_free_skb:
kfree_skb(skb);
out:
return err;
}
int datagram_recv_ctl(struct sock *sk, struct msghdr *msg, struct sk_buff *skb)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct inet6_skb_parm *opt = IP6CB(skb);
if (np->rxopt.bits.rxinfo) {
struct in6_pktinfo src_info;
src_info.ipi6_ifindex = opt->iif;
ipv6_addr_copy(&src_info.ipi6_addr, &skb->nh.ipv6h->daddr);
put_cmsg(msg, SOL_IPV6, IPV6_PKTINFO, sizeof(src_info), &src_info);
}
if (np->rxopt.bits.rxhlim) {
int hlim = skb->nh.ipv6h->hop_limit;
put_cmsg(msg, SOL_IPV6, IPV6_HOPLIMIT, sizeof(hlim), &hlim);
}
if (np->rxopt.bits.rxflow && (*(u32*)skb->nh.raw & IPV6_FLOWINFO_MASK)) {
u32 flowinfo = *(u32*)skb->nh.raw & IPV6_FLOWINFO_MASK;
put_cmsg(msg, SOL_IPV6, IPV6_FLOWINFO, sizeof(flowinfo), &flowinfo);
}
if (np->rxopt.bits.hopopts && opt->hop) {
u8 *ptr = skb->nh.raw + opt->hop;
put_cmsg(msg, SOL_IPV6, IPV6_HOPOPTS, (ptr[1]+1)<<3, ptr);
}
if (np->rxopt.bits.dstopts && opt->dst0) {
u8 *ptr = skb->nh.raw + opt->dst0;
put_cmsg(msg, SOL_IPV6, IPV6_DSTOPTS, (ptr[1]+1)<<3, ptr);
}
if (np->rxopt.bits.srcrt && opt->srcrt) {
struct ipv6_rt_hdr *rthdr = (struct ipv6_rt_hdr *)(skb->nh.raw + opt->srcrt);
put_cmsg(msg, SOL_IPV6, IPV6_RTHDR, (rthdr->hdrlen+1) << 3, rthdr);
}
if (np->rxopt.bits.dstopts && opt->dst1) {
u8 *ptr = skb->nh.raw + opt->dst1;
put_cmsg(msg, SOL_IPV6, IPV6_DSTOPTS, (ptr[1]+1)<<3, ptr);
}
return 0;
}
int datagram_send_ctl(struct msghdr *msg, struct flowi *fl,
struct ipv6_txoptions *opt,
int *hlimit)
{
struct in6_pktinfo *src_info;
struct cmsghdr *cmsg;
struct ipv6_rt_hdr *rthdr;
struct ipv6_opt_hdr *hdr;
int len;
int err = 0;
for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
int addr_type;
struct net_device *dev = NULL;
if (!CMSG_OK(msg, cmsg)) {
err = -EINVAL;
goto exit_f;
}
if (cmsg->cmsg_level != SOL_IPV6)
continue;
switch (cmsg->cmsg_type) {
case IPV6_PKTINFO:
if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct in6_pktinfo))) {
err = -EINVAL;
goto exit_f;
}
src_info = (struct in6_pktinfo *)CMSG_DATA(cmsg);
if (src_info->ipi6_ifindex) {
if (fl->oif && src_info->ipi6_ifindex != fl->oif)
return -EINVAL;
fl->oif = src_info->ipi6_ifindex;
}
addr_type = ipv6_addr_type(&src_info->ipi6_addr);
if (addr_type == IPV6_ADDR_ANY)
break;
if (addr_type & IPV6_ADDR_LINKLOCAL) {
if (!src_info->ipi6_ifindex)
return -EINVAL;
else {
dev = dev_get_by_index(src_info->ipi6_ifindex);
if (!dev)
return -ENODEV;
}
}
if (!ipv6_chk_addr(&src_info->ipi6_addr, dev, 0)) {
if (dev)
dev_put(dev);
err = -EINVAL;
goto exit_f;
}
if (dev)
dev_put(dev);
ipv6_addr_copy(&fl->fl6_src, &src_info->ipi6_addr);
break;
case IPV6_FLOWINFO:
if (cmsg->cmsg_len < CMSG_LEN(4)) {
err = -EINVAL;
goto exit_f;
}
if (fl->fl6_flowlabel&IPV6_FLOWINFO_MASK) {
if ((fl->fl6_flowlabel^*(u32 *)CMSG_DATA(cmsg))&~IPV6_FLOWINFO_MASK) {
err = -EINVAL;
goto exit_f;
}
}
fl->fl6_flowlabel = IPV6_FLOWINFO_MASK & *(u32 *)CMSG_DATA(cmsg);
break;
case IPV6_HOPOPTS:
if (opt->hopopt || cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_opt_hdr))) {
err = -EINVAL;
goto exit_f;
}
hdr = (struct ipv6_opt_hdr *)CMSG_DATA(cmsg);
len = ((hdr->hdrlen + 1) << 3);
if (cmsg->cmsg_len < CMSG_LEN(len)) {
err = -EINVAL;
goto exit_f;
}
if (!capable(CAP_NET_RAW)) {
err = -EPERM;
goto exit_f;
}
opt->opt_nflen += len;
opt->hopopt = hdr;
break;
case IPV6_DSTOPTS:
if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_opt_hdr))) {
err = -EINVAL;
goto exit_f;
}
hdr = (struct ipv6_opt_hdr *)CMSG_DATA(cmsg);
len = ((hdr->hdrlen + 1) << 3);
if (cmsg->cmsg_len < CMSG_LEN(len)) {
err = -EINVAL;
goto exit_f;
}
if (!capable(CAP_NET_RAW)) {
err = -EPERM;
goto exit_f;
}
if (opt->dst1opt) {
err = -EINVAL;
goto exit_f;
}
opt->opt_flen += len;
opt->dst1opt = hdr;
break;
case IPV6_RTHDR:
if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_rt_hdr))) {
err = -EINVAL;
goto exit_f;
}
rthdr = (struct ipv6_rt_hdr *)CMSG_DATA(cmsg);
/*
* TYPE 0
*/
if (rthdr->type) {
err = -EINVAL;
goto exit_f;
}
len = ((rthdr->hdrlen + 1) << 3);
if (cmsg->cmsg_len < CMSG_LEN(len)) {
err = -EINVAL;
goto exit_f;
}
/* segments left must also match */
if ((rthdr->hdrlen >> 1) != rthdr->segments_left) {
err = -EINVAL;
goto exit_f;
}
opt->opt_nflen += len;
opt->srcrt = rthdr;
if (opt->dst1opt) {
int dsthdrlen = ((opt->dst1opt->hdrlen+1)<<3);
opt->opt_nflen += dsthdrlen;
opt->dst0opt = opt->dst1opt;
opt->dst1opt = NULL;
opt->opt_flen -= dsthdrlen;
}
break;
case IPV6_HOPLIMIT:
if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
err = -EINVAL;
goto exit_f;
}
*hlimit = *(int *)CMSG_DATA(cmsg);
break;
default:
LIMIT_NETDEBUG(
printk(KERN_DEBUG "invalid cmsg type: %d\n", cmsg->cmsg_type));
err = -EINVAL;
break;
};
}
exit_f:
return err;
}

424
net/ipv6/esp6.c Normal file
View File

@@ -0,0 +1,424 @@
/*
* Copyright (C)2002 USAGI/WIDE Project
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Authors
*
* Mitsuru KANDA @USAGI : IPv6 Support
* Kazunori MIYAZAWA @USAGI :
* Kunihiro Ishiguro <kunihiro@ipinfusion.com>
*
* This file is derived from net/ipv4/esp.c
*/
#include <linux/config.h>
#include <linux/module.h>
#include <net/ip.h>
#include <net/xfrm.h>
#include <net/esp.h>
#include <asm/scatterlist.h>
#include <linux/crypto.h>
#include <linux/pfkeyv2.h>
#include <linux/random.h>
#include <net/icmp.h>
#include <net/ipv6.h>
#include <linux/icmpv6.h>
static int esp6_output(struct xfrm_state *x, struct sk_buff *skb)
{
int err;
int hdr_len;
struct ipv6hdr *top_iph;
struct ipv6_esp_hdr *esph;
struct crypto_tfm *tfm;
struct esp_data *esp;
struct sk_buff *trailer;
int blksize;
int clen;
int alen;
int nfrags;
esp = x->data;
hdr_len = skb->h.raw - skb->data +
sizeof(*esph) + esp->conf.ivlen;
/* Strip IP+ESP header. */
__skb_pull(skb, hdr_len);
/* Now skb is pure payload to encrypt */
err = -ENOMEM;
/* Round to block size */
clen = skb->len;
alen = esp->auth.icv_trunc_len;
tfm = esp->conf.tfm;
blksize = (crypto_tfm_alg_blocksize(tfm) + 3) & ~3;
clen = (clen + 2 + blksize-1)&~(blksize-1);
if (esp->conf.padlen)
clen = (clen + esp->conf.padlen-1)&~(esp->conf.padlen-1);
if ((nfrags = skb_cow_data(skb, clen-skb->len+alen, &trailer)) < 0) {
goto error;
}
/* Fill padding... */
do {
int i;
for (i=0; i<clen-skb->len - 2; i++)
*(u8*)(trailer->tail + i) = i+1;
} while (0);
*(u8*)(trailer->tail + clen-skb->len - 2) = (clen - skb->len)-2;
pskb_put(skb, trailer, clen - skb->len);
top_iph = (struct ipv6hdr *)__skb_push(skb, hdr_len);
esph = (struct ipv6_esp_hdr *)skb->h.raw;
top_iph->payload_len = htons(skb->len + alen - sizeof(*top_iph));
*(u8*)(trailer->tail - 1) = *skb->nh.raw;
*skb->nh.raw = IPPROTO_ESP;
esph->spi = x->id.spi;
esph->seq_no = htonl(++x->replay.oseq);
if (esp->conf.ivlen)
crypto_cipher_set_iv(tfm, esp->conf.ivec, crypto_tfm_alg_ivsize(tfm));
do {
struct scatterlist *sg = &esp->sgbuf[0];
if (unlikely(nfrags > ESP_NUM_FAST_SG)) {
sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC);
if (!sg)
goto error;
}
skb_to_sgvec(skb, sg, esph->enc_data+esp->conf.ivlen-skb->data, clen);
crypto_cipher_encrypt(tfm, sg, sg, clen);
if (unlikely(sg != &esp->sgbuf[0]))
kfree(sg);
} while (0);
if (esp->conf.ivlen) {
memcpy(esph->enc_data, esp->conf.ivec, crypto_tfm_alg_ivsize(tfm));
crypto_cipher_get_iv(tfm, esp->conf.ivec, crypto_tfm_alg_ivsize(tfm));
}
if (esp->auth.icv_full_len) {
esp->auth.icv(esp, skb, (u8*)esph-skb->data,
sizeof(struct ipv6_esp_hdr) + esp->conf.ivlen+clen, trailer->tail);
pskb_put(skb, trailer, alen);
}
err = 0;
error:
return err;
}
static int esp6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_buff *skb)
{
struct ipv6hdr *iph;
struct ipv6_esp_hdr *esph;
struct esp_data *esp = x->data;
struct sk_buff *trailer;
int blksize = crypto_tfm_alg_blocksize(esp->conf.tfm);
int alen = esp->auth.icv_trunc_len;
int elen = skb->len - sizeof(struct ipv6_esp_hdr) - esp->conf.ivlen - alen;
int hdr_len = skb->h.raw - skb->nh.raw;
int nfrags;
unsigned char *tmp_hdr = NULL;
int ret = 0;
if (!pskb_may_pull(skb, sizeof(struct ipv6_esp_hdr))) {
ret = -EINVAL;
goto out_nofree;
}
if (elen <= 0 || (elen & (blksize-1))) {
ret = -EINVAL;
goto out_nofree;
}
tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC);
if (!tmp_hdr) {
ret = -ENOMEM;
goto out_nofree;
}
memcpy(tmp_hdr, skb->nh.raw, hdr_len);
/* If integrity check is required, do this. */
if (esp->auth.icv_full_len) {
u8 sum[esp->auth.icv_full_len];
u8 sum1[alen];
esp->auth.icv(esp, skb, 0, skb->len-alen, sum);
if (skb_copy_bits(skb, skb->len-alen, sum1, alen))
BUG();
if (unlikely(memcmp(sum, sum1, alen))) {
x->stats.integrity_failed++;
ret = -EINVAL;
goto out;
}
}
if ((nfrags = skb_cow_data(skb, 0, &trailer)) < 0) {
ret = -EINVAL;
goto out;
}
skb->ip_summed = CHECKSUM_NONE;
esph = (struct ipv6_esp_hdr*)skb->data;
iph = skb->nh.ipv6h;
/* Get ivec. This can be wrong, check against another impls. */
if (esp->conf.ivlen)
crypto_cipher_set_iv(esp->conf.tfm, esph->enc_data, crypto_tfm_alg_ivsize(esp->conf.tfm));
{
u8 nexthdr[2];
struct scatterlist *sg = &esp->sgbuf[0];
u8 padlen;
if (unlikely(nfrags > ESP_NUM_FAST_SG)) {
sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC);
if (!sg) {
ret = -ENOMEM;
goto out;
}
}
skb_to_sgvec(skb, sg, sizeof(struct ipv6_esp_hdr) + esp->conf.ivlen, elen);
crypto_cipher_decrypt(esp->conf.tfm, sg, sg, elen);
if (unlikely(sg != &esp->sgbuf[0]))
kfree(sg);
if (skb_copy_bits(skb, skb->len-alen-2, nexthdr, 2))
BUG();
padlen = nexthdr[0];
if (padlen+2 >= elen) {
LIMIT_NETDEBUG(
printk(KERN_WARNING "ipsec esp packet is garbage padlen=%d, elen=%d\n", padlen+2, elen));
ret = -EINVAL;
goto out;
}
/* ... check padding bits here. Silly. :-) */
pskb_trim(skb, skb->len - alen - padlen - 2);
skb->h.raw = skb_pull(skb, sizeof(struct ipv6_esp_hdr) + esp->conf.ivlen);
skb->nh.raw += sizeof(struct ipv6_esp_hdr) + esp->conf.ivlen;
memcpy(skb->nh.raw, tmp_hdr, hdr_len);
skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
ret = nexthdr[1];
}
out:
kfree(tmp_hdr);
out_nofree:
return ret;
}
static u32 esp6_get_max_size(struct xfrm_state *x, int mtu)
{
struct esp_data *esp = x->data;
u32 blksize = crypto_tfm_alg_blocksize(esp->conf.tfm);
if (x->props.mode) {
mtu = (mtu + 2 + blksize-1)&~(blksize-1);
} else {
/* The worst case. */
mtu += 2 + blksize;
}
if (esp->conf.padlen)
mtu = (mtu + esp->conf.padlen-1)&~(esp->conf.padlen-1);
return mtu + x->props.header_len + esp->auth.icv_full_len;
}
static void esp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
int type, int code, int offset, __u32 info)
{
struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
struct ipv6_esp_hdr *esph = (struct ipv6_esp_hdr*)(skb->data+offset);
struct xfrm_state *x;
if (type != ICMPV6_DEST_UNREACH &&
type != ICMPV6_PKT_TOOBIG)
return;
x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, esph->spi, IPPROTO_ESP, AF_INET6);
if (!x)
return;
printk(KERN_DEBUG "pmtu discovery on SA ESP/%08x/"
"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
ntohl(esph->spi), NIP6(iph->daddr));
xfrm_state_put(x);
}
static void esp6_destroy(struct xfrm_state *x)
{
struct esp_data *esp = x->data;
if (!esp)
return;
if (esp->conf.tfm) {
crypto_free_tfm(esp->conf.tfm);
esp->conf.tfm = NULL;
}
if (esp->conf.ivec) {
kfree(esp->conf.ivec);
esp->conf.ivec = NULL;
}
if (esp->auth.tfm) {
crypto_free_tfm(esp->auth.tfm);
esp->auth.tfm = NULL;
}
if (esp->auth.work_icv) {
kfree(esp->auth.work_icv);
esp->auth.work_icv = NULL;
}
kfree(esp);
}
static int esp6_init_state(struct xfrm_state *x, void *args)
{
struct esp_data *esp = NULL;
/* null auth and encryption can have zero length keys */
if (x->aalg) {
if (x->aalg->alg_key_len > 512)
goto error;
}
if (x->ealg == NULL)
goto error;
if (x->encap)
goto error;
esp = kmalloc(sizeof(*esp), GFP_KERNEL);
if (esp == NULL)
return -ENOMEM;
memset(esp, 0, sizeof(*esp));
if (x->aalg) {
struct xfrm_algo_desc *aalg_desc;
esp->auth.key = x->aalg->alg_key;
esp->auth.key_len = (x->aalg->alg_key_len+7)/8;
esp->auth.tfm = crypto_alloc_tfm(x->aalg->alg_name, 0);
if (esp->auth.tfm == NULL)
goto error;
esp->auth.icv = esp_hmac_digest;
aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0);
BUG_ON(!aalg_desc);
if (aalg_desc->uinfo.auth.icv_fullbits/8 !=
crypto_tfm_alg_digestsize(esp->auth.tfm)) {
printk(KERN_INFO "ESP: %s digestsize %u != %hu\n",
x->aalg->alg_name,
crypto_tfm_alg_digestsize(esp->auth.tfm),
aalg_desc->uinfo.auth.icv_fullbits/8);
goto error;
}
esp->auth.icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8;
esp->auth.icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/8;
esp->auth.work_icv = kmalloc(esp->auth.icv_full_len, GFP_KERNEL);
if (!esp->auth.work_icv)
goto error;
}
esp->conf.key = x->ealg->alg_key;
esp->conf.key_len = (x->ealg->alg_key_len+7)/8;
if (x->props.ealgo == SADB_EALG_NULL)
esp->conf.tfm = crypto_alloc_tfm(x->ealg->alg_name, CRYPTO_TFM_MODE_ECB);
else
esp->conf.tfm = crypto_alloc_tfm(x->ealg->alg_name, CRYPTO_TFM_MODE_CBC);
if (esp->conf.tfm == NULL)
goto error;
esp->conf.ivlen = crypto_tfm_alg_ivsize(esp->conf.tfm);
esp->conf.padlen = 0;
if (esp->conf.ivlen) {
esp->conf.ivec = kmalloc(esp->conf.ivlen, GFP_KERNEL);
if (unlikely(esp->conf.ivec == NULL))
goto error;
get_random_bytes(esp->conf.ivec, esp->conf.ivlen);
}
if (crypto_cipher_setkey(esp->conf.tfm, esp->conf.key, esp->conf.key_len))
goto error;
x->props.header_len = sizeof(struct ipv6_esp_hdr) + esp->conf.ivlen;
if (x->props.mode)
x->props.header_len += sizeof(struct ipv6hdr);
x->data = esp;
return 0;
error:
x->data = esp;
esp6_destroy(x);
x->data = NULL;
return -EINVAL;
}
static struct xfrm_type esp6_type =
{
.description = "ESP6",
.owner = THIS_MODULE,
.proto = IPPROTO_ESP,
.init_state = esp6_init_state,
.destructor = esp6_destroy,
.get_max_size = esp6_get_max_size,
.input = esp6_input,
.output = esp6_output
};
static struct inet6_protocol esp6_protocol = {
.handler = xfrm6_rcv,
.err_handler = esp6_err,
.flags = INET6_PROTO_NOPOLICY,
};
static int __init esp6_init(void)
{
if (xfrm_register_type(&esp6_type, AF_INET6) < 0) {
printk(KERN_INFO "ipv6 esp init: can't add xfrm type\n");
return -EAGAIN;
}
if (inet6_add_protocol(&esp6_protocol, IPPROTO_ESP) < 0) {
printk(KERN_INFO "ipv6 esp init: can't add protocol\n");
xfrm_unregister_type(&esp6_type, AF_INET6);
return -EAGAIN;
}
return 0;
}
static void __exit esp6_fini(void)
{
if (inet6_del_protocol(&esp6_protocol, IPPROTO_ESP) < 0)
printk(KERN_INFO "ipv6 esp close: can't remove protocol\n");
if (xfrm_unregister_type(&esp6_type, AF_INET6) < 0)
printk(KERN_INFO "ipv6 esp close: can't remove xfrm type\n");
}
module_init(esp6_init);
module_exit(esp6_fini);
MODULE_LICENSE("GPL");

575
net/ipv6/exthdrs.c Normal file
View File

@@ -0,0 +1,575 @@
/*
* Extension Header handling for IPv6
* Linux INET6 implementation
*
* Authors:
* Pedro Roque <roque@di.fc.ul.pt>
* Andi Kleen <ak@muc.de>
* Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
*
* $Id: exthdrs.c,v 1.13 2001/06/19 15:58:56 davem 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.
*/
/* Changes:
* yoshfuji : ensure not to overrun while parsing
* tlv options.
* Mitsuru KANDA @USAGI and: Remove ipv6_parse_exthdrs().
* YOSHIFUJI Hideaki @USAGI Register inbound extension header
* handlers as inet6_protocol{}.
*/
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/sched.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/in6.h>
#include <linux/icmpv6.h>
#include <net/sock.h>
#include <net/snmp.h>
#include <net/ipv6.h>
#include <net/protocol.h>
#include <net/transp_v6.h>
#include <net/rawv6.h>
#include <net/ndisc.h>
#include <net/ip6_route.h>
#include <net/addrconf.h>
#include <asm/uaccess.h>
/*
* Parsing tlv encoded headers.
*
* Parsing function "func" returns 1, if parsing succeed
* and 0, if it failed.
* It MUST NOT touch skb->h.
*/
struct tlvtype_proc {
int type;
int (*func)(struct sk_buff *skb, int offset);
};
/*********************
Generic functions
*********************/
/* An unknown option is detected, decide what to do */
static int ip6_tlvopt_unknown(struct sk_buff *skb, int optoff)
{
switch ((skb->nh.raw[optoff] & 0xC0) >> 6) {
case 0: /* ignore */
return 1;
case 1: /* drop packet */
break;
case 3: /* Send ICMP if not a multicast address and drop packet */
/* Actually, it is redundant check. icmp_send
will recheck in any case.
*/
if (ipv6_addr_is_multicast(&skb->nh.ipv6h->daddr))
break;
case 2: /* send ICMP PARM PROB regardless and drop packet */
icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff);
return 0;
};
kfree_skb(skb);
return 0;
}
/* Parse tlv encoded option header (hop-by-hop or destination) */
static int ip6_parse_tlv(struct tlvtype_proc *procs, struct sk_buff *skb)
{
struct tlvtype_proc *curr;
int off = skb->h.raw - skb->nh.raw;
int len = ((skb->h.raw[1]+1)<<3);
if ((skb->h.raw + len) - skb->data > skb_headlen(skb))
goto bad;
off += 2;
len -= 2;
while (len > 0) {
int optlen = skb->nh.raw[off+1]+2;
switch (skb->nh.raw[off]) {
case IPV6_TLV_PAD0:
optlen = 1;
break;
case IPV6_TLV_PADN:
break;
default: /* Other TLV code so scan list */
if (optlen > len)
goto bad;
for (curr=procs; curr->type >= 0; curr++) {
if (curr->type == skb->nh.raw[off]) {
/* type specific length/alignment
checks will be performed in the
func(). */
if (curr->func(skb, off) == 0)
return 0;
break;
}
}
if (curr->type < 0) {
if (ip6_tlvopt_unknown(skb, off) == 0)
return 0;
}
break;
}
off += optlen;
len -= optlen;
}
if (len == 0)
return 1;
bad:
kfree_skb(skb);
return 0;
}
/*****************************
Destination options header.
*****************************/
static struct tlvtype_proc tlvprocdestopt_lst[] = {
/* No destination options are defined now */
{-1, NULL}
};
static int ipv6_destopt_rcv(struct sk_buff **skbp, unsigned int *nhoffp)
{
struct sk_buff *skb = *skbp;
struct inet6_skb_parm *opt = IP6CB(skb);
if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) ||
!pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
kfree_skb(skb);
return -1;
}
opt->dst1 = skb->h.raw - skb->nh.raw;
if (ip6_parse_tlv(tlvprocdestopt_lst, skb)) {
skb->h.raw += ((skb->h.raw[1]+1)<<3);
*nhoffp = opt->dst1;
return 1;
}
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
return -1;
}
static struct inet6_protocol destopt_protocol = {
.handler = ipv6_destopt_rcv,
.flags = INET6_PROTO_NOPOLICY,
};
void __init ipv6_destopt_init(void)
{
if (inet6_add_protocol(&destopt_protocol, IPPROTO_DSTOPTS) < 0)
printk(KERN_ERR "ipv6_destopt_init: Could not register protocol\n");
}
/********************************
NONE header. No data in packet.
********************************/
static int ipv6_nodata_rcv(struct sk_buff **skbp, unsigned int *nhoffp)
{
struct sk_buff *skb = *skbp;
kfree_skb(skb);
return 0;
}
static struct inet6_protocol nodata_protocol = {
.handler = ipv6_nodata_rcv,
.flags = INET6_PROTO_NOPOLICY,
};
void __init ipv6_nodata_init(void)
{
if (inet6_add_protocol(&nodata_protocol, IPPROTO_NONE) < 0)
printk(KERN_ERR "ipv6_nodata_init: Could not register protocol\n");
}
/********************************
Routing header.
********************************/
static int ipv6_rthdr_rcv(struct sk_buff **skbp, unsigned int *nhoffp)
{
struct sk_buff *skb = *skbp;
struct inet6_skb_parm *opt = IP6CB(skb);
struct in6_addr *addr;
struct in6_addr daddr;
int n, i;
struct ipv6_rt_hdr *hdr;
struct rt0_hdr *rthdr;
if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) ||
!pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
kfree_skb(skb);
return -1;
}
hdr = (struct ipv6_rt_hdr *) skb->h.raw;
if (ipv6_addr_is_multicast(&skb->nh.ipv6h->daddr) ||
skb->pkt_type != PACKET_HOST) {
IP6_INC_STATS_BH(IPSTATS_MIB_INADDRERRORS);
kfree_skb(skb);
return -1;
}
looped_back:
if (hdr->segments_left == 0) {
opt->srcrt = skb->h.raw - skb->nh.raw;
skb->h.raw += (hdr->hdrlen + 1) << 3;
opt->dst0 = opt->dst1;
opt->dst1 = 0;
*nhoffp = (&hdr->nexthdr) - skb->nh.raw;
return 1;
}
if (hdr->type != IPV6_SRCRT_TYPE_0) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->type) - skb->nh.raw);
return -1;
}
if (hdr->hdrlen & 0x01) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->hdrlen) - skb->nh.raw);
return -1;
}
/*
* This is the routing header forwarding algorithm from
* RFC 2460, page 16.
*/
n = hdr->hdrlen >> 1;
if (hdr->segments_left > n) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->segments_left) - skb->nh.raw);
return -1;
}
/* We are about to mangle packet header. Be careful!
Do not damage packets queued somewhere.
*/
if (skb_cloned(skb)) {
struct sk_buff *skb2 = skb_copy(skb, GFP_ATOMIC);
kfree_skb(skb);
/* the copy is a forwarded packet */
if (skb2 == NULL) {
IP6_INC_STATS_BH(IPSTATS_MIB_OUTDISCARDS);
return -1;
}
*skbp = skb = skb2;
opt = IP6CB(skb2);
hdr = (struct ipv6_rt_hdr *) skb2->h.raw;
}
if (skb->ip_summed == CHECKSUM_HW)
skb->ip_summed = CHECKSUM_NONE;
i = n - --hdr->segments_left;
rthdr = (struct rt0_hdr *) hdr;
addr = rthdr->addr;
addr += i - 1;
if (ipv6_addr_is_multicast(addr)) {
IP6_INC_STATS_BH(IPSTATS_MIB_INADDRERRORS);
kfree_skb(skb);
return -1;
}
ipv6_addr_copy(&daddr, addr);
ipv6_addr_copy(addr, &skb->nh.ipv6h->daddr);
ipv6_addr_copy(&skb->nh.ipv6h->daddr, &daddr);
dst_release(xchg(&skb->dst, NULL));
ip6_route_input(skb);
if (skb->dst->error) {
skb_push(skb, skb->data - skb->nh.raw);
dst_input(skb);
return -1;
}
if (skb->dst->dev->flags&IFF_LOOPBACK) {
if (skb->nh.ipv6h->hop_limit <= 1) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
icmpv6_send(skb, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT,
0, skb->dev);
kfree_skb(skb);
return -1;
}
skb->nh.ipv6h->hop_limit--;
goto looped_back;
}
skb_push(skb, skb->data - skb->nh.raw);
dst_input(skb);
return -1;
}
static struct inet6_protocol rthdr_protocol = {
.handler = ipv6_rthdr_rcv,
.flags = INET6_PROTO_NOPOLICY,
};
void __init ipv6_rthdr_init(void)
{
if (inet6_add_protocol(&rthdr_protocol, IPPROTO_ROUTING) < 0)
printk(KERN_ERR "ipv6_rthdr_init: Could not register protocol\n");
};
/*
This function inverts received rthdr.
NOTE: specs allow to make it automatically only if
packet authenticated.
I will not discuss it here (though, I am really pissed off at
this stupid requirement making rthdr idea useless)
Actually, it creates severe problems for us.
Embryonic requests has no associated sockets,
so that user have no control over it and
cannot not only to set reply options, but
even to know, that someone wants to connect
without success. :-(
For now we need to test the engine, so that I created
temporary (or permanent) backdoor.
If listening socket set IPV6_RTHDR to 2, then we invert header.
--ANK (980729)
*/
struct ipv6_txoptions *
ipv6_invert_rthdr(struct sock *sk, struct ipv6_rt_hdr *hdr)
{
/* Received rthdr:
[ H1 -> H2 -> ... H_prev ] daddr=ME
Inverted result:
[ H_prev -> ... -> H1 ] daddr =sender
Note, that IP output engine will rewrite this rthdr
by rotating it left by one addr.
*/
int n, i;
struct rt0_hdr *rthdr = (struct rt0_hdr*)hdr;
struct rt0_hdr *irthdr;
struct ipv6_txoptions *opt;
int hdrlen = ipv6_optlen(hdr);
if (hdr->segments_left ||
hdr->type != IPV6_SRCRT_TYPE_0 ||
hdr->hdrlen & 0x01)
return NULL;
n = hdr->hdrlen >> 1;
opt = sock_kmalloc(sk, sizeof(*opt) + hdrlen, GFP_ATOMIC);
if (opt == NULL)
return NULL;
memset(opt, 0, sizeof(*opt));
opt->tot_len = sizeof(*opt) + hdrlen;
opt->srcrt = (void*)(opt+1);
opt->opt_nflen = hdrlen;
memcpy(opt->srcrt, hdr, sizeof(*hdr));
irthdr = (struct rt0_hdr*)opt->srcrt;
/* Obsolete field, MBZ, when originated by us */
irthdr->bitmap = 0;
opt->srcrt->segments_left = n;
for (i=0; i<n; i++)
memcpy(irthdr->addr+i, rthdr->addr+(n-1-i), 16);
return opt;
}
/**********************************
Hop-by-hop options.
**********************************/
/* Router Alert as of RFC 2711 */
static int ipv6_hop_ra(struct sk_buff *skb, int optoff)
{
if (skb->nh.raw[optoff+1] == 2) {
IP6CB(skb)->ra = optoff;
return 1;
}
LIMIT_NETDEBUG(
printk(KERN_DEBUG "ipv6_hop_ra: wrong RA length %d\n", skb->nh.raw[optoff+1]));
kfree_skb(skb);
return 0;
}
/* Jumbo payload */
static int ipv6_hop_jumbo(struct sk_buff *skb, int optoff)
{
u32 pkt_len;
if (skb->nh.raw[optoff+1] != 4 || (optoff&3) != 2) {
LIMIT_NETDEBUG(
printk(KERN_DEBUG "ipv6_hop_jumbo: wrong jumbo opt length/alignment %d\n", skb->nh.raw[optoff+1]));
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
goto drop;
}
pkt_len = ntohl(*(u32*)(skb->nh.raw+optoff+2));
if (pkt_len <= IPV6_MAXPLEN) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff+2);
return 0;
}
if (skb->nh.ipv6h->payload_len) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff);
return 0;
}
if (pkt_len > skb->len - sizeof(struct ipv6hdr)) {
IP6_INC_STATS_BH(IPSTATS_MIB_INTRUNCATEDPKTS);
goto drop;
}
if (pkt_len + sizeof(struct ipv6hdr) < skb->len) {
__pskb_trim(skb, pkt_len + sizeof(struct ipv6hdr));
if (skb->ip_summed == CHECKSUM_HW)
skb->ip_summed = CHECKSUM_NONE;
}
return 1;
drop:
kfree_skb(skb);
return 0;
}
static struct tlvtype_proc tlvprochopopt_lst[] = {
{
.type = IPV6_TLV_ROUTERALERT,
.func = ipv6_hop_ra,
},
{
.type = IPV6_TLV_JUMBO,
.func = ipv6_hop_jumbo,
},
{ -1, }
};
int ipv6_parse_hopopts(struct sk_buff *skb, int nhoff)
{
IP6CB(skb)->hop = sizeof(struct ipv6hdr);
if (ip6_parse_tlv(tlvprochopopt_lst, skb))
return sizeof(struct ipv6hdr);
return -1;
}
/*
* Creating outbound headers.
*
* "build" functions work when skb is filled from head to tail (datagram)
* "push" functions work when headers are added from tail to head (tcp)
*
* In both cases we assume, that caller reserved enough room
* for headers.
*/
static void ipv6_push_rthdr(struct sk_buff *skb, u8 *proto,
struct ipv6_rt_hdr *opt,
struct in6_addr **addr_p)
{
struct rt0_hdr *phdr, *ihdr;
int hops;
ihdr = (struct rt0_hdr *) opt;
phdr = (struct rt0_hdr *) skb_push(skb, (ihdr->rt_hdr.hdrlen + 1) << 3);
memcpy(phdr, ihdr, sizeof(struct rt0_hdr));
hops = ihdr->rt_hdr.hdrlen >> 1;
if (hops > 1)
memcpy(phdr->addr, ihdr->addr + 1,
(hops - 1) * sizeof(struct in6_addr));
ipv6_addr_copy(phdr->addr + (hops - 1), *addr_p);
*addr_p = ihdr->addr;
phdr->rt_hdr.nexthdr = *proto;
*proto = NEXTHDR_ROUTING;
}
static void ipv6_push_exthdr(struct sk_buff *skb, u8 *proto, u8 type, struct ipv6_opt_hdr *opt)
{
struct ipv6_opt_hdr *h = (struct ipv6_opt_hdr *)skb_push(skb, ipv6_optlen(opt));
memcpy(h, opt, ipv6_optlen(opt));
h->nexthdr = *proto;
*proto = type;
}
void ipv6_push_nfrag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt,
u8 *proto,
struct in6_addr **daddr)
{
if (opt->srcrt)
ipv6_push_rthdr(skb, proto, opt->srcrt, daddr);
if (opt->dst0opt)
ipv6_push_exthdr(skb, proto, NEXTHDR_DEST, opt->dst0opt);
if (opt->hopopt)
ipv6_push_exthdr(skb, proto, NEXTHDR_HOP, opt->hopopt);
}
void ipv6_push_frag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt, u8 *proto)
{
if (opt->dst1opt)
ipv6_push_exthdr(skb, proto, NEXTHDR_DEST, opt->dst1opt);
}
struct ipv6_txoptions *
ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt)
{
struct ipv6_txoptions *opt2;
opt2 = sock_kmalloc(sk, opt->tot_len, GFP_ATOMIC);
if (opt2) {
long dif = (char*)opt2 - (char*)opt;
memcpy(opt2, opt, opt->tot_len);
if (opt2->hopopt)
*((char**)&opt2->hopopt) += dif;
if (opt2->dst0opt)
*((char**)&opt2->dst0opt) += dif;
if (opt2->dst1opt)
*((char**)&opt2->dst1opt) += dif;
if (opt2->srcrt)
*((char**)&opt2->srcrt) += dif;
}
return opt2;
}

109
net/ipv6/exthdrs_core.c Normal file
View File

@@ -0,0 +1,109 @@
/*
* IPv6 library code, needed by static components when full IPv6 support is
* not configured or static.
*/
#include <net/ipv6.h>
/*
* find out if nexthdr is a well-known extension header or a protocol
*/
int ipv6_ext_hdr(u8 nexthdr)
{
/*
* find out if nexthdr is an extension header or a protocol
*/
return ( (nexthdr == NEXTHDR_HOP) ||
(nexthdr == NEXTHDR_ROUTING) ||
(nexthdr == NEXTHDR_FRAGMENT) ||
(nexthdr == NEXTHDR_AUTH) ||
(nexthdr == NEXTHDR_NONE) ||
(nexthdr == NEXTHDR_DEST) );
}
/*
* Skip any extension headers. This is used by the ICMP module.
*
* Note that strictly speaking this conflicts with RFC 2460 4.0:
* ...The contents and semantics of each extension header determine whether
* or not to proceed to the next header. Therefore, extension headers must
* be processed strictly in the order they appear in the packet; a
* receiver must not, for example, scan through a packet looking for a
* particular kind of extension header and process that header prior to
* processing all preceding ones.
*
* We do exactly this. This is a protocol bug. We can't decide after a
* seeing an unknown discard-with-error flavour TLV option if it's a
* ICMP error message or not (errors should never be send in reply to
* ICMP error messages).
*
* But I see no other way to do this. This might need to be reexamined
* when Linux implements ESP (and maybe AUTH) headers.
* --AK
*
* This function parses (probably truncated) exthdr set "hdr"
* of length "len". "nexthdrp" initially points to some place,
* where type of the first header can be found.
*
* It skips all well-known exthdrs, and returns pointer to the start
* of unparsable area i.e. the first header with unknown type.
* If it is not NULL *nexthdr is updated by type/protocol of this header.
*
* NOTES: - if packet terminated with NEXTHDR_NONE it returns NULL.
* - it may return pointer pointing beyond end of packet,
* if the last recognized header is truncated in the middle.
* - if packet is truncated, so that all parsed headers are skipped,
* it returns NULL.
* - First fragment header is skipped, not-first ones
* are considered as unparsable.
* - ESP is unparsable for now and considered like
* normal payload protocol.
* - Note also special handling of AUTH header. Thanks to IPsec wizards.
*
* --ANK (980726)
*/
int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp, int len)
{
u8 nexthdr = *nexthdrp;
while (ipv6_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
int hdrlen;
if (len < (int)sizeof(struct ipv6_opt_hdr))
return -1;
if (nexthdr == NEXTHDR_NONE)
return -1;
hp = skb_header_pointer(skb, start, sizeof(_hdr), &_hdr);
if (hp == NULL)
BUG();
if (nexthdr == NEXTHDR_FRAGMENT) {
unsigned short _frag_off, *fp;
fp = skb_header_pointer(skb,
start+offsetof(struct frag_hdr,
frag_off),
sizeof(_frag_off),
&_frag_off);
if (fp == NULL)
return -1;
if (ntohs(*fp) & ~0x7)
break;
hdrlen = 8;
} else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hp->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hp);
nexthdr = hp->nexthdr;
len -= hdrlen;
start += hdrlen;
}
*nexthdrp = nexthdr;
return start;
}
EXPORT_SYMBOL(ipv6_ext_hdr);
EXPORT_SYMBOL(ipv6_skip_exthdr);

822
net/ipv6/icmp.c Normal file
View File

@@ -0,0 +1,822 @@
/*
* Internet Control Message Protocol (ICMPv6)
* Linux INET6 implementation
*
* Authors:
* Pedro Roque <roque@di.fc.ul.pt>
*
* $Id: icmp.c,v 1.38 2002/02/08 03:57:19 davem Exp $
*
* Based on net/ipv4/icmp.c
*
* RFC 1885
*
* 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.
*/
/*
* Changes:
*
* Andi Kleen : exception handling
* Andi Kleen add rate limits. never reply to a icmp.
* add more length checks and other fixes.
* yoshfuji : ensure to sent parameter problem for
* fragments.
* YOSHIFUJI Hideaki @USAGI: added sysctl for icmp rate limit.
* Randy Dunlap and
* YOSHIFUJI Hideaki @USAGI: Per-interface statistics support
* Kazunori MIYAZAWA @USAGI: change output process to use ip6_append_data
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#ifdef CONFIG_SYSCTL
#include <linux/sysctl.h>
#endif
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/icmpv6.h>
#include <net/ip.h>
#include <net/sock.h>
#include <net/ipv6.h>
#include <net/ip6_checksum.h>
#include <net/protocol.h>
#include <net/raw.h>
#include <net/rawv6.h>
#include <net/transp_v6.h>
#include <net/ip6_route.h>
#include <net/addrconf.h>
#include <net/icmp.h>
#include <asm/uaccess.h>
#include <asm/system.h>
DEFINE_SNMP_STAT(struct icmpv6_mib, icmpv6_statistics);
/*
* The ICMP socket(s). This is the most convenient way to flow control
* our ICMP output as well as maintain a clean interface throughout
* all layers. All Socketless IP sends will soon be gone.
*
* On SMP we have one ICMP socket per-cpu.
*/
static DEFINE_PER_CPU(struct socket *, __icmpv6_socket) = NULL;
#define icmpv6_socket __get_cpu_var(__icmpv6_socket)
static int icmpv6_rcv(struct sk_buff **pskb, unsigned int *nhoffp);
static struct inet6_protocol icmpv6_protocol = {
.handler = icmpv6_rcv,
.flags = INET6_PROTO_FINAL,
};
static __inline__ int icmpv6_xmit_lock(void)
{
local_bh_disable();
if (unlikely(!spin_trylock(&icmpv6_socket->sk->sk_lock.slock))) {
/* This can happen if the output path (f.e. SIT or
* ip6ip6 tunnel) signals dst_link_failure() for an
* outgoing ICMP6 packet.
*/
local_bh_enable();
return 1;
}
return 0;
}
static __inline__ void icmpv6_xmit_unlock(void)
{
spin_unlock_bh(&icmpv6_socket->sk->sk_lock.slock);
}
/*
* Slightly more convenient version of icmpv6_send.
*/
void icmpv6_param_prob(struct sk_buff *skb, int code, int pos)
{
icmpv6_send(skb, ICMPV6_PARAMPROB, code, pos, skb->dev);
kfree_skb(skb);
}
/*
* Figure out, may we reply to this packet with icmp error.
*
* We do not reply, if:
* - it was icmp error message.
* - it is truncated, so that it is known, that protocol is ICMPV6
* (i.e. in the middle of some exthdr)
*
* --ANK (980726)
*/
static int is_ineligible(struct sk_buff *skb)
{
int ptr = (u8*)(skb->nh.ipv6h+1) - skb->data;
int len = skb->len - ptr;
__u8 nexthdr = skb->nh.ipv6h->nexthdr;
if (len < 0)
return 1;
ptr = ipv6_skip_exthdr(skb, ptr, &nexthdr, len);
if (ptr < 0)
return 0;
if (nexthdr == IPPROTO_ICMPV6) {
u8 _type, *tp;
tp = skb_header_pointer(skb,
ptr+offsetof(struct icmp6hdr, icmp6_type),
sizeof(_type), &_type);
if (tp == NULL ||
!(*tp & ICMPV6_INFOMSG_MASK))
return 1;
}
return 0;
}
static int sysctl_icmpv6_time = 1*HZ;
/*
* Check the ICMP output rate limit
*/
static inline int icmpv6_xrlim_allow(struct sock *sk, int type,
struct flowi *fl)
{
struct dst_entry *dst;
int res = 0;
/* Informational messages are not limited. */
if (type & ICMPV6_INFOMSG_MASK)
return 1;
/* Do not limit pmtu discovery, it would break it. */
if (type == ICMPV6_PKT_TOOBIG)
return 1;
/*
* Look up the output route.
* XXX: perhaps the expire for routing entries cloned by
* this lookup should be more aggressive (not longer than timeout).
*/
dst = ip6_route_output(sk, fl);
if (dst->error) {
IP6_INC_STATS(IPSTATS_MIB_OUTNOROUTES);
} else if (dst->dev && (dst->dev->flags&IFF_LOOPBACK)) {
res = 1;
} else {
struct rt6_info *rt = (struct rt6_info *)dst;
int tmo = sysctl_icmpv6_time;
/* Give more bandwidth to wider prefixes. */
if (rt->rt6i_dst.plen < 128)
tmo >>= ((128 - rt->rt6i_dst.plen)>>5);
res = xrlim_allow(dst, tmo);
}
dst_release(dst);
return res;
}
/*
* an inline helper for the "simple" if statement below
* checks if parameter problem report is caused by an
* unrecognized IPv6 option that has the Option Type
* highest-order two bits set to 10
*/
static __inline__ int opt_unrec(struct sk_buff *skb, __u32 offset)
{
u8 _optval, *op;
offset += skb->nh.raw - skb->data;
op = skb_header_pointer(skb, offset, sizeof(_optval), &_optval);
if (op == NULL)
return 1;
return (*op & 0xC0) == 0x80;
}
static int icmpv6_push_pending_frames(struct sock *sk, struct flowi *fl, struct icmp6hdr *thdr, int len)
{
struct sk_buff *skb;
struct icmp6hdr *icmp6h;
int err = 0;
if ((skb = skb_peek(&sk->sk_write_queue)) == NULL)
goto out;
icmp6h = (struct icmp6hdr*) skb->h.raw;
memcpy(icmp6h, thdr, sizeof(struct icmp6hdr));
icmp6h->icmp6_cksum = 0;
if (skb_queue_len(&sk->sk_write_queue) == 1) {
skb->csum = csum_partial((char *)icmp6h,
sizeof(struct icmp6hdr), skb->csum);
icmp6h->icmp6_cksum = csum_ipv6_magic(&fl->fl6_src,
&fl->fl6_dst,
len, fl->proto,
skb->csum);
} else {
u32 tmp_csum = 0;
skb_queue_walk(&sk->sk_write_queue, skb) {
tmp_csum = csum_add(tmp_csum, skb->csum);
}
tmp_csum = csum_partial((char *)icmp6h,
sizeof(struct icmp6hdr), tmp_csum);
tmp_csum = csum_ipv6_magic(&fl->fl6_src,
&fl->fl6_dst,
len, fl->proto, tmp_csum);
icmp6h->icmp6_cksum = tmp_csum;
}
if (icmp6h->icmp6_cksum == 0)
icmp6h->icmp6_cksum = -1;
ip6_push_pending_frames(sk);
out:
return err;
}
struct icmpv6_msg {
struct sk_buff *skb;
int offset;
};
static int icmpv6_getfrag(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb)
{
struct icmpv6_msg *msg = (struct icmpv6_msg *) from;
struct sk_buff *org_skb = msg->skb;
__u32 csum = 0;
csum = skb_copy_and_csum_bits(org_skb, msg->offset + offset,
to, len, csum);
skb->csum = csum_block_add(skb->csum, csum, odd);
return 0;
}
/*
* Send an ICMP message in response to a packet in error
*/
void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info,
struct net_device *dev)
{
struct inet6_dev *idev = NULL;
struct ipv6hdr *hdr = skb->nh.ipv6h;
struct sock *sk = icmpv6_socket->sk;
struct ipv6_pinfo *np = inet6_sk(sk);
struct in6_addr *saddr = NULL;
struct dst_entry *dst;
struct icmp6hdr tmp_hdr;
struct flowi fl;
struct icmpv6_msg msg;
int iif = 0;
int addr_type = 0;
int len;
int hlimit;
int err = 0;
if ((u8*)hdr < skb->head || (u8*)(hdr+1) > skb->tail)
return;
/*
* Make sure we respect the rules
* i.e. RFC 1885 2.4(e)
* Rule (e.1) is enforced by not using icmpv6_send
* in any code that processes icmp errors.
*/
addr_type = ipv6_addr_type(&hdr->daddr);
if (ipv6_chk_addr(&hdr->daddr, skb->dev, 0))
saddr = &hdr->daddr;
/*
* Dest addr check
*/
if ((addr_type & IPV6_ADDR_MULTICAST || skb->pkt_type != PACKET_HOST)) {
if (type != ICMPV6_PKT_TOOBIG &&
!(type == ICMPV6_PARAMPROB &&
code == ICMPV6_UNK_OPTION &&
(opt_unrec(skb, info))))
return;
saddr = NULL;
}
addr_type = ipv6_addr_type(&hdr->saddr);
/*
* Source addr check
*/
if (addr_type & IPV6_ADDR_LINKLOCAL)
iif = skb->dev->ifindex;
/*
* Must not send if we know that source is Anycast also.
* for now we don't know that.
*/
if ((addr_type == IPV6_ADDR_ANY) || (addr_type & IPV6_ADDR_MULTICAST)) {
LIMIT_NETDEBUG(
printk(KERN_DEBUG "icmpv6_send: addr_any/mcast source\n"));
return;
}
/*
* Never answer to a ICMP packet.
*/
if (is_ineligible(skb)) {
LIMIT_NETDEBUG(
printk(KERN_DEBUG "icmpv6_send: no reply to icmp error\n"));
return;
}
memset(&fl, 0, sizeof(fl));
fl.proto = IPPROTO_ICMPV6;
ipv6_addr_copy(&fl.fl6_dst, &hdr->saddr);
if (saddr)
ipv6_addr_copy(&fl.fl6_src, saddr);
fl.oif = iif;
fl.fl_icmp_type = type;
fl.fl_icmp_code = code;
if (icmpv6_xmit_lock())
return;
if (!icmpv6_xrlim_allow(sk, type, &fl))
goto out;
tmp_hdr.icmp6_type = type;
tmp_hdr.icmp6_code = code;
tmp_hdr.icmp6_cksum = 0;
tmp_hdr.icmp6_pointer = htonl(info);
if (!fl.oif && ipv6_addr_is_multicast(&fl.fl6_dst))
fl.oif = np->mcast_oif;
err = ip6_dst_lookup(sk, &dst, &fl);
if (err)
goto out;
if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0)
goto out_dst_release;
if (ipv6_addr_is_multicast(&fl.fl6_dst))
hlimit = np->mcast_hops;
else
hlimit = np->hop_limit;
if (hlimit < 0)
hlimit = dst_metric(dst, RTAX_HOPLIMIT);
if (hlimit < 0)
hlimit = ipv6_get_hoplimit(dst->dev);
msg.skb = skb;
msg.offset = skb->nh.raw - skb->data;
len = skb->len - msg.offset;
len = min_t(unsigned int, len, IPV6_MIN_MTU - sizeof(struct ipv6hdr) -sizeof(struct icmp6hdr));
if (len < 0) {
LIMIT_NETDEBUG(
printk(KERN_DEBUG "icmp: len problem\n"));
goto out_dst_release;
}
idev = in6_dev_get(skb->dev);
err = ip6_append_data(sk, icmpv6_getfrag, &msg,
len + sizeof(struct icmp6hdr),
sizeof(struct icmp6hdr),
hlimit, NULL, &fl, (struct rt6_info*)dst,
MSG_DONTWAIT);
if (err) {
ip6_flush_pending_frames(sk);
goto out_put;
}
err = icmpv6_push_pending_frames(sk, &fl, &tmp_hdr, len + sizeof(struct icmp6hdr));
if (type >= ICMPV6_DEST_UNREACH && type <= ICMPV6_PARAMPROB)
ICMP6_INC_STATS_OFFSET_BH(idev, ICMP6_MIB_OUTDESTUNREACHS, type - ICMPV6_DEST_UNREACH);
ICMP6_INC_STATS_BH(idev, ICMP6_MIB_OUTMSGS);
out_put:
if (likely(idev != NULL))
in6_dev_put(idev);
out_dst_release:
dst_release(dst);
out:
icmpv6_xmit_unlock();
}
static void icmpv6_echo_reply(struct sk_buff *skb)
{
struct sock *sk = icmpv6_socket->sk;
struct inet6_dev *idev;
struct ipv6_pinfo *np = inet6_sk(sk);
struct in6_addr *saddr = NULL;
struct icmp6hdr *icmph = (struct icmp6hdr *) skb->h.raw;
struct icmp6hdr tmp_hdr;
struct flowi fl;
struct icmpv6_msg msg;
struct dst_entry *dst;
int err = 0;
int hlimit;
saddr = &skb->nh.ipv6h->daddr;
if (!ipv6_unicast_destination(skb))
saddr = NULL;
memcpy(&tmp_hdr, icmph, sizeof(tmp_hdr));
tmp_hdr.icmp6_type = ICMPV6_ECHO_REPLY;
memset(&fl, 0, sizeof(fl));
fl.proto = IPPROTO_ICMPV6;
ipv6_addr_copy(&fl.fl6_dst, &skb->nh.ipv6h->saddr);
if (saddr)
ipv6_addr_copy(&fl.fl6_src, saddr);
fl.oif = skb->dev->ifindex;
fl.fl_icmp_type = ICMPV6_ECHO_REPLY;
if (icmpv6_xmit_lock())
return;
if (!fl.oif && ipv6_addr_is_multicast(&fl.fl6_dst))
fl.oif = np->mcast_oif;
err = ip6_dst_lookup(sk, &dst, &fl);
if (err)
goto out;
if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0)
goto out_dst_release;
if (ipv6_addr_is_multicast(&fl.fl6_dst))
hlimit = np->mcast_hops;
else
hlimit = np->hop_limit;
if (hlimit < 0)
hlimit = dst_metric(dst, RTAX_HOPLIMIT);
if (hlimit < 0)
hlimit = ipv6_get_hoplimit(dst->dev);
idev = in6_dev_get(skb->dev);
msg.skb = skb;
msg.offset = 0;
err = ip6_append_data(sk, icmpv6_getfrag, &msg, skb->len + sizeof(struct icmp6hdr),
sizeof(struct icmp6hdr), hlimit, NULL, &fl,
(struct rt6_info*)dst, MSG_DONTWAIT);
if (err) {
ip6_flush_pending_frames(sk);
goto out_put;
}
err = icmpv6_push_pending_frames(sk, &fl, &tmp_hdr, skb->len + sizeof(struct icmp6hdr));
ICMP6_INC_STATS_BH(idev, ICMP6_MIB_OUTECHOREPLIES);
ICMP6_INC_STATS_BH(idev, ICMP6_MIB_OUTMSGS);
out_put:
if (likely(idev != NULL))
in6_dev_put(idev);
out_dst_release:
dst_release(dst);
out:
icmpv6_xmit_unlock();
}
static void icmpv6_notify(struct sk_buff *skb, int type, int code, u32 info)
{
struct in6_addr *saddr, *daddr;
struct inet6_protocol *ipprot;
struct sock *sk;
int inner_offset;
int hash;
u8 nexthdr;
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
return;
nexthdr = ((struct ipv6hdr *)skb->data)->nexthdr;
if (ipv6_ext_hdr(nexthdr)) {
/* now skip over extension headers */
inner_offset = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr, skb->len - sizeof(struct ipv6hdr));
if (inner_offset<0)
return;
} else {
inner_offset = sizeof(struct ipv6hdr);
}
/* Checkin header including 8 bytes of inner protocol header. */
if (!pskb_may_pull(skb, inner_offset+8))
return;
saddr = &skb->nh.ipv6h->saddr;
daddr = &skb->nh.ipv6h->daddr;
/* BUGGG_FUTURE: we should try to parse exthdrs in this packet.
Without this we will not able f.e. to make source routed
pmtu discovery.
Corresponding argument (opt) to notifiers is already added.
--ANK (980726)
*/
hash = nexthdr & (MAX_INET_PROTOS - 1);
rcu_read_lock();
ipprot = rcu_dereference(inet6_protos[hash]);
if (ipprot && ipprot->err_handler)
ipprot->err_handler(skb, NULL, type, code, inner_offset, info);
rcu_read_unlock();
read_lock(&raw_v6_lock);
if ((sk = sk_head(&raw_v6_htable[hash])) != NULL) {
while((sk = __raw_v6_lookup(sk, nexthdr, daddr, saddr))) {
rawv6_err(sk, skb, NULL, type, code, inner_offset, info);
sk = sk_next(sk);
}
}
read_unlock(&raw_v6_lock);
}
/*
* Handle icmp messages
*/
static int icmpv6_rcv(struct sk_buff **pskb, unsigned int *nhoffp)
{
struct sk_buff *skb = *pskb;
struct net_device *dev = skb->dev;
struct inet6_dev *idev = __in6_dev_get(dev);
struct in6_addr *saddr, *daddr;
struct ipv6hdr *orig_hdr;
struct icmp6hdr *hdr;
int type;
ICMP6_INC_STATS_BH(idev, ICMP6_MIB_INMSGS);
saddr = &skb->nh.ipv6h->saddr;
daddr = &skb->nh.ipv6h->daddr;
/* Perform checksum. */
if (skb->ip_summed == CHECKSUM_HW) {
skb->ip_summed = CHECKSUM_UNNECESSARY;
if (csum_ipv6_magic(saddr, daddr, skb->len, IPPROTO_ICMPV6,
skb->csum)) {
LIMIT_NETDEBUG(
printk(KERN_DEBUG "ICMPv6 hw checksum failed\n"));
skb->ip_summed = CHECKSUM_NONE;
}
}
if (skb->ip_summed == CHECKSUM_NONE) {
if (csum_ipv6_magic(saddr, daddr, skb->len, IPPROTO_ICMPV6,
skb_checksum(skb, 0, skb->len, 0))) {
LIMIT_NETDEBUG(
printk(KERN_DEBUG "ICMPv6 checksum failed [%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x > %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]\n",
NIP6(*saddr), NIP6(*daddr)));
goto discard_it;
}
}
if (!pskb_pull(skb, sizeof(struct icmp6hdr)))
goto discard_it;
hdr = (struct icmp6hdr *) skb->h.raw;
type = hdr->icmp6_type;
if (type >= ICMPV6_DEST_UNREACH && type <= ICMPV6_PARAMPROB)
ICMP6_INC_STATS_OFFSET_BH(idev, ICMP6_MIB_INDESTUNREACHS, type - ICMPV6_DEST_UNREACH);
else if (type >= ICMPV6_ECHO_REQUEST && type <= NDISC_REDIRECT)
ICMP6_INC_STATS_OFFSET_BH(idev, ICMP6_MIB_INECHOS, type - ICMPV6_ECHO_REQUEST);
switch (type) {
case ICMPV6_ECHO_REQUEST:
icmpv6_echo_reply(skb);
break;
case ICMPV6_ECHO_REPLY:
/* we couldn't care less */
break;
case ICMPV6_PKT_TOOBIG:
/* BUGGG_FUTURE: if packet contains rthdr, we cannot update
standard destination cache. Seems, only "advanced"
destination cache will allow to solve this problem
--ANK (980726)
*/
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
goto discard_it;
hdr = (struct icmp6hdr *) skb->h.raw;
orig_hdr = (struct ipv6hdr *) (hdr + 1);
rt6_pmtu_discovery(&orig_hdr->daddr, &orig_hdr->saddr, dev,
ntohl(hdr->icmp6_mtu));
/*
* Drop through to notify
*/
case ICMPV6_DEST_UNREACH:
case ICMPV6_TIME_EXCEED:
case ICMPV6_PARAMPROB:
icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
break;
case NDISC_ROUTER_SOLICITATION:
case NDISC_ROUTER_ADVERTISEMENT:
case NDISC_NEIGHBOUR_SOLICITATION:
case NDISC_NEIGHBOUR_ADVERTISEMENT:
case NDISC_REDIRECT:
ndisc_rcv(skb);
break;
case ICMPV6_MGM_QUERY:
igmp6_event_query(skb);
break;
case ICMPV6_MGM_REPORT:
igmp6_event_report(skb);
break;
case ICMPV6_MGM_REDUCTION:
case ICMPV6_NI_QUERY:
case ICMPV6_NI_REPLY:
case ICMPV6_MLD2_REPORT:
case ICMPV6_DHAAD_REQUEST:
case ICMPV6_DHAAD_REPLY:
case ICMPV6_MOBILE_PREFIX_SOL:
case ICMPV6_MOBILE_PREFIX_ADV:
break;
default:
LIMIT_NETDEBUG(
printk(KERN_DEBUG "icmpv6: msg of unknown type\n"));
/* informational */
if (type & ICMPV6_INFOMSG_MASK)
break;
/*
* error of unknown type.
* must pass to upper level
*/
icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
};
kfree_skb(skb);
return 0;
discard_it:
ICMP6_INC_STATS_BH(idev, ICMP6_MIB_INERRORS);
kfree_skb(skb);
return 0;
}
int __init icmpv6_init(struct net_proto_family *ops)
{
struct sock *sk;
int err, i, j;
for (i = 0; i < NR_CPUS; i++) {
if (!cpu_possible(i))
continue;
err = sock_create_kern(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6,
&per_cpu(__icmpv6_socket, i));
if (err < 0) {
printk(KERN_ERR
"Failed to initialize the ICMP6 control socket "
"(err %d).\n",
err);
goto fail;
}
sk = per_cpu(__icmpv6_socket, i)->sk;
sk->sk_allocation = GFP_ATOMIC;
/* Enough space for 2 64K ICMP packets, including
* sk_buff struct overhead.
*/
sk->sk_sndbuf =
(2 * ((64 * 1024) + sizeof(struct sk_buff)));
sk->sk_prot->unhash(sk);
}
if (inet6_add_protocol(&icmpv6_protocol, IPPROTO_ICMPV6) < 0) {
printk(KERN_ERR "Failed to register ICMP6 protocol\n");
err = -EAGAIN;
goto fail;
}
return 0;
fail:
for (j = 0; j < i; j++) {
if (!cpu_possible(j))
continue;
sock_release(per_cpu(__icmpv6_socket, j));
}
return err;
}
void icmpv6_cleanup(void)
{
int i;
for (i = 0; i < NR_CPUS; i++) {
if (!cpu_possible(i))
continue;
sock_release(per_cpu(__icmpv6_socket, i));
}
inet6_del_protocol(&icmpv6_protocol, IPPROTO_ICMPV6);
}
static struct icmp6_err {
int err;
int fatal;
} tab_unreach[] = {
{ /* NOROUTE */
.err = ENETUNREACH,
.fatal = 0,
},
{ /* ADM_PROHIBITED */
.err = EACCES,
.fatal = 1,
},
{ /* Was NOT_NEIGHBOUR, now reserved */
.err = EHOSTUNREACH,
.fatal = 0,
},
{ /* ADDR_UNREACH */
.err = EHOSTUNREACH,
.fatal = 0,
},
{ /* PORT_UNREACH */
.err = ECONNREFUSED,
.fatal = 1,
},
};
int icmpv6_err_convert(int type, int code, int *err)
{
int fatal = 0;
*err = EPROTO;
switch (type) {
case ICMPV6_DEST_UNREACH:
fatal = 1;
if (code <= ICMPV6_PORT_UNREACH) {
*err = tab_unreach[code].err;
fatal = tab_unreach[code].fatal;
}
break;
case ICMPV6_PKT_TOOBIG:
*err = EMSGSIZE;
break;
case ICMPV6_PARAMPROB:
*err = EPROTO;
fatal = 1;
break;
case ICMPV6_TIME_EXCEED:
*err = EHOSTUNREACH;
break;
};
return fatal;
}
#ifdef CONFIG_SYSCTL
ctl_table ipv6_icmp_table[] = {
{
.ctl_name = NET_IPV6_ICMP_RATELIMIT,
.procname = "ratelimit",
.data = &sysctl_icmpv6_time,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{ .ctl_name = 0 },
};
#endif

1225
net/ipv6/ip6_fib.c Normal file

File diff suppressed because it is too large Load Diff

706
net/ipv6/ip6_flowlabel.c Normal file
View File

@@ -0,0 +1,706 @@
/*
* ip6_flowlabel.c IPv6 flowlabel manager.
*
* 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.
*
* Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
*/
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/in6.h>
#include <linux/route.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <net/sock.h>
#include <net/ipv6.h>
#include <net/ndisc.h>
#include <net/protocol.h>
#include <net/ip6_route.h>
#include <net/addrconf.h>
#include <net/rawv6.h>
#include <net/icmp.h>
#include <net/transp_v6.h>
#include <asm/uaccess.h>
#define FL_MIN_LINGER 6 /* Minimal linger. It is set to 6sec specified
in old IPv6 RFC. Well, it was reasonable value.
*/
#define FL_MAX_LINGER 60 /* Maximal linger timeout */
/* FL hash table */
#define FL_MAX_PER_SOCK 32
#define FL_MAX_SIZE 4096
#define FL_HASH_MASK 255
#define FL_HASH(l) (ntohl(l)&FL_HASH_MASK)
static atomic_t fl_size = ATOMIC_INIT(0);
static struct ip6_flowlabel *fl_ht[FL_HASH_MASK+1];
static void ip6_fl_gc(unsigned long dummy);
static struct timer_list ip6_fl_gc_timer = TIMER_INITIALIZER(ip6_fl_gc, 0, 0);
/* FL hash table lock: it protects only of GC */
static DEFINE_RWLOCK(ip6_fl_lock);
/* Big socket sock */
static DEFINE_RWLOCK(ip6_sk_fl_lock);
static __inline__ struct ip6_flowlabel * __fl_lookup(u32 label)
{
struct ip6_flowlabel *fl;
for (fl=fl_ht[FL_HASH(label)]; fl; fl = fl->next) {
if (fl->label == label)
return fl;
}
return NULL;
}
static struct ip6_flowlabel * fl_lookup(u32 label)
{
struct ip6_flowlabel *fl;
read_lock_bh(&ip6_fl_lock);
fl = __fl_lookup(label);
if (fl)
atomic_inc(&fl->users);
read_unlock_bh(&ip6_fl_lock);
return fl;
}
static void fl_free(struct ip6_flowlabel *fl)
{
if (fl)
kfree(fl->opt);
kfree(fl);
}
static void fl_release(struct ip6_flowlabel *fl)
{
write_lock_bh(&ip6_fl_lock);
fl->lastuse = jiffies;
if (atomic_dec_and_test(&fl->users)) {
unsigned long ttd = fl->lastuse + fl->linger;
if (time_after(ttd, fl->expires))
fl->expires = ttd;
ttd = fl->expires;
if (fl->opt && fl->share == IPV6_FL_S_EXCL) {
struct ipv6_txoptions *opt = fl->opt;
fl->opt = NULL;
kfree(opt);
}
if (!timer_pending(&ip6_fl_gc_timer) ||
time_after(ip6_fl_gc_timer.expires, ttd))
mod_timer(&ip6_fl_gc_timer, ttd);
}
write_unlock_bh(&ip6_fl_lock);
}
static void ip6_fl_gc(unsigned long dummy)
{
int i;
unsigned long now = jiffies;
unsigned long sched = 0;
write_lock(&ip6_fl_lock);
for (i=0; i<=FL_HASH_MASK; i++) {
struct ip6_flowlabel *fl, **flp;
flp = &fl_ht[i];
while ((fl=*flp) != NULL) {
if (atomic_read(&fl->users) == 0) {
unsigned long ttd = fl->lastuse + fl->linger;
if (time_after(ttd, fl->expires))
fl->expires = ttd;
ttd = fl->expires;
if (time_after_eq(now, ttd)) {
*flp = fl->next;
fl_free(fl);
atomic_dec(&fl_size);
continue;
}
if (!sched || time_before(ttd, sched))
sched = ttd;
}
flp = &fl->next;
}
}
if (!sched && atomic_read(&fl_size))
sched = now + FL_MAX_LINGER;
if (sched) {
ip6_fl_gc_timer.expires = sched;
add_timer(&ip6_fl_gc_timer);
}
write_unlock(&ip6_fl_lock);
}
static int fl_intern(struct ip6_flowlabel *fl, __u32 label)
{
fl->label = label & IPV6_FLOWLABEL_MASK;
write_lock_bh(&ip6_fl_lock);
if (label == 0) {
for (;;) {
fl->label = htonl(net_random())&IPV6_FLOWLABEL_MASK;
if (fl->label) {
struct ip6_flowlabel *lfl;
lfl = __fl_lookup(fl->label);
if (lfl == NULL)
break;
}
}
}
fl->lastuse = jiffies;
fl->next = fl_ht[FL_HASH(fl->label)];
fl_ht[FL_HASH(fl->label)] = fl;
atomic_inc(&fl_size);
write_unlock_bh(&ip6_fl_lock);
return 0;
}
/* Socket flowlabel lists */
struct ip6_flowlabel * fl6_sock_lookup(struct sock *sk, u32 label)
{
struct ipv6_fl_socklist *sfl;
struct ipv6_pinfo *np = inet6_sk(sk);
label &= IPV6_FLOWLABEL_MASK;
for (sfl=np->ipv6_fl_list; sfl; sfl = sfl->next) {
struct ip6_flowlabel *fl = sfl->fl;
if (fl->label == label) {
fl->lastuse = jiffies;
atomic_inc(&fl->users);
return fl;
}
}
return NULL;
}
void fl6_free_socklist(struct sock *sk)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct ipv6_fl_socklist *sfl;
while ((sfl = np->ipv6_fl_list) != NULL) {
np->ipv6_fl_list = sfl->next;
fl_release(sfl->fl);
kfree(sfl);
}
}
/* Service routines */
/*
It is the only difficult place. flowlabel enforces equal headers
before and including routing header, however user may supply options
following rthdr.
*/
struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions * opt_space,
struct ip6_flowlabel * fl,
struct ipv6_txoptions * fopt)
{
struct ipv6_txoptions * fl_opt = fl->opt;
if (fopt == NULL || fopt->opt_flen == 0)
return fl_opt;
if (fl_opt != NULL) {
opt_space->hopopt = fl_opt->hopopt;
opt_space->dst0opt = fl_opt->dst0opt;
opt_space->srcrt = fl_opt->srcrt;
opt_space->opt_nflen = fl_opt->opt_nflen;
} else {
if (fopt->opt_nflen == 0)
return fopt;
opt_space->hopopt = NULL;
opt_space->dst0opt = NULL;
opt_space->srcrt = NULL;
opt_space->opt_nflen = 0;
}
opt_space->dst1opt = fopt->dst1opt;
opt_space->auth = fopt->auth;
opt_space->opt_flen = fopt->opt_flen;
return opt_space;
}
static unsigned long check_linger(unsigned long ttl)
{
if (ttl < FL_MIN_LINGER)
return FL_MIN_LINGER*HZ;
if (ttl > FL_MAX_LINGER && !capable(CAP_NET_ADMIN))
return 0;
return ttl*HZ;
}
static int fl6_renew(struct ip6_flowlabel *fl, unsigned long linger, unsigned long expires)
{
linger = check_linger(linger);
if (!linger)
return -EPERM;
expires = check_linger(expires);
if (!expires)
return -EPERM;
fl->lastuse = jiffies;
if (time_before(fl->linger, linger))
fl->linger = linger;
if (time_before(expires, fl->linger))
expires = fl->linger;
if (time_before(fl->expires, fl->lastuse + expires))
fl->expires = fl->lastuse + expires;
return 0;
}
static struct ip6_flowlabel *
fl_create(struct in6_flowlabel_req *freq, char __user *optval, int optlen, int *err_p)
{
struct ip6_flowlabel *fl;
int olen;
int addr_type;
int err;
err = -ENOMEM;
fl = kmalloc(sizeof(*fl), GFP_KERNEL);
if (fl == NULL)
goto done;
memset(fl, 0, sizeof(*fl));
olen = optlen - CMSG_ALIGN(sizeof(*freq));
if (olen > 0) {
struct msghdr msg;
struct flowi flowi;
int junk;
err = -ENOMEM;
fl->opt = kmalloc(sizeof(*fl->opt) + olen, GFP_KERNEL);
if (fl->opt == NULL)
goto done;
memset(fl->opt, 0, sizeof(*fl->opt));
fl->opt->tot_len = sizeof(*fl->opt) + olen;
err = -EFAULT;
if (copy_from_user(fl->opt+1, optval+CMSG_ALIGN(sizeof(*freq)), olen))
goto done;
msg.msg_controllen = olen;
msg.msg_control = (void*)(fl->opt+1);
flowi.oif = 0;
err = datagram_send_ctl(&msg, &flowi, fl->opt, &junk);
if (err)
goto done;
err = -EINVAL;
if (fl->opt->opt_flen)
goto done;
if (fl->opt->opt_nflen == 0) {
kfree(fl->opt);
fl->opt = NULL;
}
}
fl->expires = jiffies;
err = fl6_renew(fl, freq->flr_linger, freq->flr_expires);
if (err)
goto done;
fl->share = freq->flr_share;
addr_type = ipv6_addr_type(&freq->flr_dst);
if ((addr_type&IPV6_ADDR_MAPPED)
|| addr_type == IPV6_ADDR_ANY)
goto done;
ipv6_addr_copy(&fl->dst, &freq->flr_dst);
atomic_set(&fl->users, 1);
switch (fl->share) {
case IPV6_FL_S_EXCL:
case IPV6_FL_S_ANY:
break;
case IPV6_FL_S_PROCESS:
fl->owner = current->pid;
break;
case IPV6_FL_S_USER:
fl->owner = current->euid;
break;
default:
err = -EINVAL;
goto done;
}
return fl;
done:
fl_free(fl);
*err_p = err;
return NULL;
}
static int mem_check(struct sock *sk)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct ipv6_fl_socklist *sfl;
int room = FL_MAX_SIZE - atomic_read(&fl_size);
int count = 0;
if (room > FL_MAX_SIZE - FL_MAX_PER_SOCK)
return 0;
for (sfl = np->ipv6_fl_list; sfl; sfl = sfl->next)
count++;
if (room <= 0 ||
((count >= FL_MAX_PER_SOCK ||
(count > 0 && room < FL_MAX_SIZE/2) || room < FL_MAX_SIZE/4)
&& !capable(CAP_NET_ADMIN)))
return -ENOBUFS;
return 0;
}
static int ipv6_hdr_cmp(struct ipv6_opt_hdr *h1, struct ipv6_opt_hdr *h2)
{
if (h1 == h2)
return 0;
if (h1 == NULL || h2 == NULL)
return 1;
if (h1->hdrlen != h2->hdrlen)
return 1;
return memcmp(h1+1, h2+1, ((h1->hdrlen+1)<<3) - sizeof(*h1));
}
static int ipv6_opt_cmp(struct ipv6_txoptions *o1, struct ipv6_txoptions *o2)
{
if (o1 == o2)
return 0;
if (o1 == NULL || o2 == NULL)
return 1;
if (o1->opt_nflen != o2->opt_nflen)
return 1;
if (ipv6_hdr_cmp(o1->hopopt, o2->hopopt))
return 1;
if (ipv6_hdr_cmp(o1->dst0opt, o2->dst0opt))
return 1;
if (ipv6_hdr_cmp((struct ipv6_opt_hdr *)o1->srcrt, (struct ipv6_opt_hdr *)o2->srcrt))
return 1;
return 0;
}
int ipv6_flowlabel_opt(struct sock *sk, char __user *optval, int optlen)
{
int err;
struct ipv6_pinfo *np = inet6_sk(sk);
struct in6_flowlabel_req freq;
struct ipv6_fl_socklist *sfl1=NULL;
struct ipv6_fl_socklist *sfl, **sflp;
struct ip6_flowlabel *fl;
if (optlen < sizeof(freq))
return -EINVAL;
if (copy_from_user(&freq, optval, sizeof(freq)))
return -EFAULT;
switch (freq.flr_action) {
case IPV6_FL_A_PUT:
write_lock_bh(&ip6_sk_fl_lock);
for (sflp = &np->ipv6_fl_list; (sfl=*sflp)!=NULL; sflp = &sfl->next) {
if (sfl->fl->label == freq.flr_label) {
if (freq.flr_label == (np->flow_label&IPV6_FLOWLABEL_MASK))
np->flow_label &= ~IPV6_FLOWLABEL_MASK;
*sflp = sfl->next;
write_unlock_bh(&ip6_sk_fl_lock);
fl_release(sfl->fl);
kfree(sfl);
return 0;
}
}
write_unlock_bh(&ip6_sk_fl_lock);
return -ESRCH;
case IPV6_FL_A_RENEW:
read_lock_bh(&ip6_sk_fl_lock);
for (sfl = np->ipv6_fl_list; sfl; sfl = sfl->next) {
if (sfl->fl->label == freq.flr_label) {
err = fl6_renew(sfl->fl, freq.flr_linger, freq.flr_expires);
read_unlock_bh(&ip6_sk_fl_lock);
return err;
}
}
read_unlock_bh(&ip6_sk_fl_lock);
if (freq.flr_share == IPV6_FL_S_NONE && capable(CAP_NET_ADMIN)) {
fl = fl_lookup(freq.flr_label);
if (fl) {
err = fl6_renew(fl, freq.flr_linger, freq.flr_expires);
fl_release(fl);
return err;
}
}
return -ESRCH;
case IPV6_FL_A_GET:
if (freq.flr_label & ~IPV6_FLOWLABEL_MASK)
return -EINVAL;
fl = fl_create(&freq, optval, optlen, &err);
if (fl == NULL)
return err;
sfl1 = kmalloc(sizeof(*sfl1), GFP_KERNEL);
if (freq.flr_label) {
struct ip6_flowlabel *fl1 = NULL;
err = -EEXIST;
read_lock_bh(&ip6_sk_fl_lock);
for (sfl = np->ipv6_fl_list; sfl; sfl = sfl->next) {
if (sfl->fl->label == freq.flr_label) {
if (freq.flr_flags&IPV6_FL_F_EXCL) {
read_unlock_bh(&ip6_sk_fl_lock);
goto done;
}
fl1 = sfl->fl;
atomic_inc(&fl->users);
break;
}
}
read_unlock_bh(&ip6_sk_fl_lock);
if (fl1 == NULL)
fl1 = fl_lookup(freq.flr_label);
if (fl1) {
err = -EEXIST;
if (freq.flr_flags&IPV6_FL_F_EXCL)
goto release;
err = -EPERM;
if (fl1->share == IPV6_FL_S_EXCL ||
fl1->share != fl->share ||
fl1->owner != fl->owner)
goto release;
err = -EINVAL;
if (!ipv6_addr_equal(&fl1->dst, &fl->dst) ||
ipv6_opt_cmp(fl1->opt, fl->opt))
goto release;
err = -ENOMEM;
if (sfl1 == NULL)
goto release;
if (fl->linger > fl1->linger)
fl1->linger = fl->linger;
if ((long)(fl->expires - fl1->expires) > 0)
fl1->expires = fl->expires;
write_lock_bh(&ip6_sk_fl_lock);
sfl1->fl = fl1;
sfl1->next = np->ipv6_fl_list;
np->ipv6_fl_list = sfl1;
write_unlock_bh(&ip6_sk_fl_lock);
fl_free(fl);
return 0;
release:
fl_release(fl1);
goto done;
}
}
err = -ENOENT;
if (!(freq.flr_flags&IPV6_FL_F_CREATE))
goto done;
err = -ENOMEM;
if (sfl1 == NULL || (err = mem_check(sk)) != 0)
goto done;
err = fl_intern(fl, freq.flr_label);
if (err)
goto done;
/* Do not check for fault */
if (!freq.flr_label)
copy_to_user(&((struct in6_flowlabel_req __user *) optval)->flr_label,
&fl->label, sizeof(fl->label));
sfl1->fl = fl;
sfl1->next = np->ipv6_fl_list;
np->ipv6_fl_list = sfl1;
return 0;
default:
return -EINVAL;
}
done:
fl_free(fl);
kfree(sfl1);
return err;
}
#ifdef CONFIG_PROC_FS
struct ip6fl_iter_state {
int bucket;
};
#define ip6fl_seq_private(seq) ((struct ip6fl_iter_state *)(seq)->private)
static struct ip6_flowlabel *ip6fl_get_first(struct seq_file *seq)
{
struct ip6_flowlabel *fl = NULL;
struct ip6fl_iter_state *state = ip6fl_seq_private(seq);
for (state->bucket = 0; state->bucket <= FL_HASH_MASK; ++state->bucket) {
if (fl_ht[state->bucket]) {
fl = fl_ht[state->bucket];
break;
}
}
return fl;
}
static struct ip6_flowlabel *ip6fl_get_next(struct seq_file *seq, struct ip6_flowlabel *fl)
{
struct ip6fl_iter_state *state = ip6fl_seq_private(seq);
fl = fl->next;
while (!fl) {
if (++state->bucket <= FL_HASH_MASK)
fl = fl_ht[state->bucket];
}
return fl;
}
static struct ip6_flowlabel *ip6fl_get_idx(struct seq_file *seq, loff_t pos)
{
struct ip6_flowlabel *fl = ip6fl_get_first(seq);
if (fl)
while (pos && (fl = ip6fl_get_next(seq, fl)) != NULL)
--pos;
return pos ? NULL : fl;
}
static void *ip6fl_seq_start(struct seq_file *seq, loff_t *pos)
{
read_lock_bh(&ip6_fl_lock);
return *pos ? ip6fl_get_idx(seq, *pos - 1) : SEQ_START_TOKEN;
}
static void *ip6fl_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
struct ip6_flowlabel *fl;
if (v == SEQ_START_TOKEN)
fl = ip6fl_get_first(seq);
else
fl = ip6fl_get_next(seq, v);
++*pos;
return fl;
}
static void ip6fl_seq_stop(struct seq_file *seq, void *v)
{
read_unlock_bh(&ip6_fl_lock);
}
static void ip6fl_fl_seq_show(struct seq_file *seq, struct ip6_flowlabel *fl)
{
while(fl) {
seq_printf(seq,
"%05X %-1d %-6d %-6d %-6ld %-8ld "
"%02x%02x%02x%02x%02x%02x%02x%02x "
"%-4d\n",
(unsigned)ntohl(fl->label),
fl->share,
(unsigned)fl->owner,
atomic_read(&fl->users),
fl->linger/HZ,
(long)(fl->expires - jiffies)/HZ,
NIP6(fl->dst),
fl->opt ? fl->opt->opt_nflen : 0);
fl = fl->next;
}
}
static int ip6fl_seq_show(struct seq_file *seq, void *v)
{
if (v == SEQ_START_TOKEN)
seq_puts(seq, "Label S Owner Users Linger Expires "
"Dst Opt\n");
else
ip6fl_fl_seq_show(seq, v);
return 0;
}
static struct seq_operations ip6fl_seq_ops = {
.start = ip6fl_seq_start,
.next = ip6fl_seq_next,
.stop = ip6fl_seq_stop,
.show = ip6fl_seq_show,
};
static int ip6fl_seq_open(struct inode *inode, struct file *file)
{
struct seq_file *seq;
int rc = -ENOMEM;
struct ip6fl_iter_state *s = kmalloc(sizeof(*s), GFP_KERNEL);
if (!s)
goto out;
rc = seq_open(file, &ip6fl_seq_ops);
if (rc)
goto out_kfree;
seq = file->private_data;
seq->private = s;
memset(s, 0, sizeof(*s));
out:
return rc;
out_kfree:
kfree(s);
goto out;
}
static struct file_operations ip6fl_seq_fops = {
.owner = THIS_MODULE,
.open = ip6fl_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_private,
};
#endif
void ip6_flowlabel_init(void)
{
#ifdef CONFIG_PROC_FS
proc_net_fops_create("ip6_flowlabel", S_IRUGO, &ip6fl_seq_fops);
#endif
}
void ip6_flowlabel_cleanup(void)
{
del_timer(&ip6_fl_gc_timer);
#ifdef CONFIG_PROC_FS
proc_net_remove("ip6_flowlabel");
#endif
}

269
net/ipv6/ip6_input.c Normal file
View File

@@ -0,0 +1,269 @@
/*
* IPv6 input
* Linux INET6 implementation
*
* Authors:
* Pedro Roque <roque@di.fc.ul.pt>
* Ian P. Morris <I.P.Morris@soton.ac.uk>
*
* $Id: ip6_input.c,v 1.19 2000/12/13 18:31:50 davem Exp $
*
* Based in linux/net/ipv4/ip_input.c
*
* 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.
*/
/* Changes
*
* Mitsuru KANDA @USAGI and
* YOSHIFUJI Hideaki @USAGI: Remove ipv6_parse_exthdrs().
*/
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/sched.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/in6.h>
#include <linux/icmpv6.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv6.h>
#include <net/sock.h>
#include <net/snmp.h>
#include <net/ipv6.h>
#include <net/protocol.h>
#include <net/transp_v6.h>
#include <net/rawv6.h>
#include <net/ndisc.h>
#include <net/ip6_route.h>
#include <net/addrconf.h>
#include <net/xfrm.h>
static inline int ip6_rcv_finish( struct sk_buff *skb)
{
if (skb->dst == NULL)
ip6_route_input(skb);
return dst_input(skb);
}
int ipv6_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
struct ipv6hdr *hdr;
u32 pkt_len;
if (skb->pkt_type == PACKET_OTHERHOST)
goto drop;
IP6_INC_STATS_BH(IPSTATS_MIB_INRECEIVES);
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
IP6_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
goto out;
}
/*
* Store incoming device index. When the packet will
* be queued, we cannot refer to skb->dev anymore.
*
* BTW, when we send a packet for our own local address on a
* non-loopback interface (e.g. ethX), it is being delivered
* via the loopback interface (lo) here; skb->dev = &loopback_dev.
* It, however, should be considered as if it is being
* arrived via the sending interface (ethX), because of the
* nature of scoping architecture. --yoshfuji
*/
IP6CB(skb)->iif = skb->dst ? ((struct rt6_info *)skb->dst)->rt6i_idev->dev->ifindex : dev->ifindex;
if (skb->len < sizeof(struct ipv6hdr))
goto err;
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
goto drop;
}
hdr = skb->nh.ipv6h;
if (hdr->version != 6)
goto err;
pkt_len = ntohs(hdr->payload_len);
/* pkt_len may be zero if Jumbo payload option is present */
if (pkt_len || hdr->nexthdr != NEXTHDR_HOP) {
if (pkt_len + sizeof(struct ipv6hdr) > skb->len)
goto truncated;
if (pskb_trim_rcsum(skb, pkt_len + sizeof(struct ipv6hdr))) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
goto drop;
}
hdr = skb->nh.ipv6h;
}
if (hdr->nexthdr == NEXTHDR_HOP) {
skb->h.raw = (u8*)(hdr+1);
if (ipv6_parse_hopopts(skb, offsetof(struct ipv6hdr, nexthdr)) < 0) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
return 0;
}
hdr = skb->nh.ipv6h;
}
return NF_HOOK(PF_INET6,NF_IP6_PRE_ROUTING, skb, dev, NULL, ip6_rcv_finish);
truncated:
IP6_INC_STATS_BH(IPSTATS_MIB_INTRUNCATEDPKTS);
err:
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
kfree_skb(skb);
out:
return 0;
}
/*
* Deliver the packet to the host
*/
static inline int ip6_input_finish(struct sk_buff *skb)
{
struct inet6_protocol *ipprot;
struct sock *raw_sk;
unsigned int nhoff;
int nexthdr;
u8 hash;
skb->h.raw = skb->nh.raw + sizeof(struct ipv6hdr);
/*
* Parse extension headers
*/
nexthdr = skb->nh.ipv6h->nexthdr;
nhoff = offsetof(struct ipv6hdr, nexthdr);
/* Skip hop-by-hop options, they are already parsed. */
if (nexthdr == NEXTHDR_HOP) {
nhoff = sizeof(struct ipv6hdr);
nexthdr = skb->h.raw[0];
skb->h.raw += (skb->h.raw[1]+1)<<3;
}
rcu_read_lock();
resubmit:
if (!pskb_pull(skb, skb->h.raw - skb->data))
goto discard;
nexthdr = skb->nh.raw[nhoff];
raw_sk = sk_head(&raw_v6_htable[nexthdr & (MAX_INET_PROTOS - 1)]);
if (raw_sk)
ipv6_raw_deliver(skb, nexthdr);
hash = nexthdr & (MAX_INET_PROTOS - 1);
if ((ipprot = rcu_dereference(inet6_protos[hash])) != NULL) {
int ret;
if (ipprot->flags & INET6_PROTO_FINAL) {
struct ipv6hdr *hdr;
skb_postpull_rcsum(skb, skb->nh.raw,
skb->h.raw - skb->nh.raw);
hdr = skb->nh.ipv6h;
if (ipv6_addr_is_multicast(&hdr->daddr) &&
!ipv6_chk_mcast_addr(skb->dev, &hdr->daddr,
&hdr->saddr) &&
!ipv6_is_mld(skb, nexthdr))
goto discard;
}
if (!(ipprot->flags & INET6_PROTO_NOPOLICY) &&
!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb))
goto discard;
ret = ipprot->handler(&skb, &nhoff);
if (ret > 0)
goto resubmit;
else if (ret == 0)
IP6_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
} else {
if (!raw_sk) {
if (xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) {
IP6_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
icmpv6_param_prob(skb, ICMPV6_UNK_NEXTHDR, nhoff);
}
} else {
IP6_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
kfree_skb(skb);
}
}
rcu_read_unlock();
return 0;
discard:
IP6_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
rcu_read_unlock();
kfree_skb(skb);
return 0;
}
int ip6_input(struct sk_buff *skb)
{
return NF_HOOK(PF_INET6,NF_IP6_LOCAL_IN, skb, skb->dev, NULL, ip6_input_finish);
}
int ip6_mc_input(struct sk_buff *skb)
{
struct ipv6hdr *hdr;
int deliver;
IP6_INC_STATS_BH(IPSTATS_MIB_INMCASTPKTS);
hdr = skb->nh.ipv6h;
deliver = likely(!(skb->dev->flags & (IFF_PROMISC|IFF_ALLMULTI))) ||
ipv6_chk_mcast_addr(skb->dev, &hdr->daddr, NULL);
/*
* IPv6 multicast router mode isnt currently supported.
*/
#if 0
if (ipv6_config.multicast_route) {
int addr_type;
addr_type = ipv6_addr_type(&hdr->daddr);
if (!(addr_type & (IPV6_ADDR_LOOPBACK | IPV6_ADDR_LINKLOCAL))) {
struct sk_buff *skb2;
struct dst_entry *dst;
dst = skb->dst;
if (deliver) {
skb2 = skb_clone(skb, GFP_ATOMIC);
dst_output(skb2);
} else {
dst_output(skb);
return 0;
}
}
}
#endif
if (likely(deliver)) {
ip6_input(skb);
return 0;
}
/* discard */
kfree_skb(skb);
return 0;
}

1197
net/ipv6/ip6_output.c Normal file

File diff suppressed because it is too large Load Diff

1163
net/ipv6/ip6_tunnel.c Normal file

File diff suppressed because it is too large Load Diff

524
net/ipv6/ipcomp6.c Normal file
View File

@@ -0,0 +1,524 @@
/*
* IP Payload Compression Protocol (IPComp) for IPv6 - RFC3173
*
* Copyright (C)2003 USAGI/WIDE Project
*
* Author Mitsuru KANDA <mk@linux-ipv6.org>
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* [Memo]
*
* Outbound:
* The compression of IP datagram MUST be done before AH/ESP processing,
* fragmentation, and the addition of Hop-by-Hop/Routing header.
*
* Inbound:
* The decompression of IP datagram MUST be done after the reassembly,
* AH/ESP processing.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <net/ip.h>
#include <net/xfrm.h>
#include <net/ipcomp.h>
#include <asm/scatterlist.h>
#include <asm/semaphore.h>
#include <linux/crypto.h>
#include <linux/pfkeyv2.h>
#include <linux/random.h>
#include <linux/percpu.h>
#include <linux/smp.h>
#include <linux/list.h>
#include <linux/vmalloc.h>
#include <linux/rtnetlink.h>
#include <net/icmp.h>
#include <net/ipv6.h>
#include <linux/ipv6.h>
#include <linux/icmpv6.h>
struct ipcomp6_tfms {
struct list_head list;
struct crypto_tfm **tfms;
int users;
};
static DECLARE_MUTEX(ipcomp6_resource_sem);
static void **ipcomp6_scratches;
static int ipcomp6_scratch_users;
static LIST_HEAD(ipcomp6_tfms_list);
static int ipcomp6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_buff *skb)
{
int err = 0;
u8 nexthdr = 0;
int hdr_len = skb->h.raw - skb->nh.raw;
unsigned char *tmp_hdr = NULL;
struct ipv6hdr *iph;
int plen, dlen;
struct ipcomp_data *ipcd = x->data;
u8 *start, *scratch;
struct crypto_tfm *tfm;
int cpu;
if ((skb_is_nonlinear(skb) || skb_cloned(skb)) &&
skb_linearize(skb, GFP_ATOMIC) != 0) {
err = -ENOMEM;
goto out;
}
skb->ip_summed = CHECKSUM_NONE;
/* Remove ipcomp header and decompress original payload */
iph = skb->nh.ipv6h;
tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC);
if (!tmp_hdr)
goto out;
memcpy(tmp_hdr, iph, hdr_len);
nexthdr = *(u8 *)skb->data;
skb_pull(skb, sizeof(struct ipv6_comp_hdr));
skb->nh.raw += sizeof(struct ipv6_comp_hdr);
memcpy(skb->nh.raw, tmp_hdr, hdr_len);
iph = skb->nh.ipv6h;
iph->payload_len = htons(ntohs(iph->payload_len) - sizeof(struct ipv6_comp_hdr));
skb->h.raw = skb->data;
/* decompression */
plen = skb->len;
dlen = IPCOMP_SCRATCH_SIZE;
start = skb->data;
cpu = get_cpu();
scratch = *per_cpu_ptr(ipcomp6_scratches, cpu);
tfm = *per_cpu_ptr(ipcd->tfms, cpu);
err = crypto_comp_decompress(tfm, start, plen, scratch, &dlen);
if (err) {
err = -EINVAL;
goto out_put_cpu;
}
if (dlen < (plen + sizeof(struct ipv6_comp_hdr))) {
err = -EINVAL;
goto out_put_cpu;
}
err = pskb_expand_head(skb, 0, dlen - plen, GFP_ATOMIC);
if (err) {
goto out_put_cpu;
}
skb_put(skb, dlen - plen);
memcpy(skb->data, scratch, dlen);
iph = skb->nh.ipv6h;
iph->payload_len = htons(skb->len);
out_put_cpu:
put_cpu();
out:
if (tmp_hdr)
kfree(tmp_hdr);
if (err)
goto error_out;
return nexthdr;
error_out:
return err;
}
static int ipcomp6_output(struct xfrm_state *x, struct sk_buff *skb)
{
int err;
struct ipv6hdr *top_iph;
int hdr_len;
struct ipv6_comp_hdr *ipch;
struct ipcomp_data *ipcd = x->data;
int plen, dlen;
u8 *start, *scratch;
struct crypto_tfm *tfm;
int cpu;
hdr_len = skb->h.raw - skb->data;
/* check whether datagram len is larger than threshold */
if ((skb->len - hdr_len) < ipcd->threshold) {
goto out_ok;
}
if ((skb_is_nonlinear(skb) || skb_cloned(skb)) &&
skb_linearize(skb, GFP_ATOMIC) != 0) {
goto out_ok;
}
/* compression */
plen = skb->len - hdr_len;
dlen = IPCOMP_SCRATCH_SIZE;
start = skb->h.raw;
cpu = get_cpu();
scratch = *per_cpu_ptr(ipcomp6_scratches, cpu);
tfm = *per_cpu_ptr(ipcd->tfms, cpu);
err = crypto_comp_compress(tfm, start, plen, scratch, &dlen);
if (err || (dlen + sizeof(struct ipv6_comp_hdr)) >= plen) {
put_cpu();
goto out_ok;
}
memcpy(start + sizeof(struct ip_comp_hdr), scratch, dlen);
put_cpu();
pskb_trim(skb, hdr_len + dlen + sizeof(struct ip_comp_hdr));
/* insert ipcomp header and replace datagram */
top_iph = (struct ipv6hdr *)skb->data;
top_iph->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
ipch = (struct ipv6_comp_hdr *)start;
ipch->nexthdr = *skb->nh.raw;
ipch->flags = 0;
ipch->cpi = htons((u16 )ntohl(x->id.spi));
*skb->nh.raw = IPPROTO_COMP;
out_ok:
return 0;
}
static void ipcomp6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
int type, int code, int offset, __u32 info)
{
u32 spi;
struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
struct ipv6_comp_hdr *ipcomph = (struct ipv6_comp_hdr*)(skb->data+offset);
struct xfrm_state *x;
if (type != ICMPV6_DEST_UNREACH && type != ICMPV6_PKT_TOOBIG)
return;
spi = ntohl(ntohs(ipcomph->cpi));
x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, IPPROTO_COMP, AF_INET6);
if (!x)
return;
printk(KERN_DEBUG "pmtu discovery on SA IPCOMP/%08x/"
"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
spi, NIP6(iph->daddr));
xfrm_state_put(x);
}
static struct xfrm_state *ipcomp6_tunnel_create(struct xfrm_state *x)
{
struct xfrm_state *t = NULL;
t = xfrm_state_alloc();
if (!t)
goto out;
t->id.proto = IPPROTO_IPV6;
t->id.spi = xfrm6_tunnel_alloc_spi((xfrm_address_t *)&x->props.saddr);
memcpy(t->id.daddr.a6, x->id.daddr.a6, sizeof(struct in6_addr));
memcpy(&t->sel, &x->sel, sizeof(t->sel));
t->props.family = AF_INET6;
t->props.mode = 1;
memcpy(t->props.saddr.a6, x->props.saddr.a6, sizeof(struct in6_addr));
t->type = xfrm_get_type(IPPROTO_IPV6, t->props.family);
if (t->type == NULL)
goto error;
if (t->type->init_state(t, NULL))
goto error;
t->km.state = XFRM_STATE_VALID;
atomic_set(&t->tunnel_users, 1);
out:
return t;
error:
xfrm_state_put(t);
goto out;
}
static int ipcomp6_tunnel_attach(struct xfrm_state *x)
{
int err = 0;
struct xfrm_state *t = NULL;
u32 spi;
spi = xfrm6_tunnel_spi_lookup((xfrm_address_t *)&x->props.saddr);
if (spi)
t = xfrm_state_lookup((xfrm_address_t *)&x->id.daddr,
spi, IPPROTO_IPV6, AF_INET6);
if (!t) {
t = ipcomp6_tunnel_create(x);
if (!t) {
err = -EINVAL;
goto out;
}
xfrm_state_insert(t);
xfrm_state_hold(t);
}
x->tunnel = t;
atomic_inc(&t->tunnel_users);
out:
return err;
}
static void ipcomp6_free_scratches(void)
{
int i;
void **scratches;
if (--ipcomp6_scratch_users)
return;
scratches = ipcomp6_scratches;
if (!scratches)
return;
for_each_cpu(i) {
void *scratch = *per_cpu_ptr(scratches, i);
if (scratch)
vfree(scratch);
}
free_percpu(scratches);
}
static void **ipcomp6_alloc_scratches(void)
{
int i;
void **scratches;
if (ipcomp6_scratch_users++)
return ipcomp6_scratches;
scratches = alloc_percpu(void *);
if (!scratches)
return NULL;
ipcomp6_scratches = scratches;
for_each_cpu(i) {
void *scratch = vmalloc(IPCOMP_SCRATCH_SIZE);
if (!scratch)
return NULL;
*per_cpu_ptr(scratches, i) = scratch;
}
return scratches;
}
static void ipcomp6_free_tfms(struct crypto_tfm **tfms)
{
struct ipcomp6_tfms *pos;
int cpu;
list_for_each_entry(pos, &ipcomp6_tfms_list, list) {
if (pos->tfms == tfms)
break;
}
BUG_TRAP(pos);
if (--pos->users)
return;
list_del(&pos->list);
kfree(pos);
if (!tfms)
return;
for_each_cpu(cpu) {
struct crypto_tfm *tfm = *per_cpu_ptr(tfms, cpu);
if (tfm)
crypto_free_tfm(tfm);
}
free_percpu(tfms);
}
static struct crypto_tfm **ipcomp6_alloc_tfms(const char *alg_name)
{
struct ipcomp6_tfms *pos;
struct crypto_tfm **tfms;
int cpu;
/* This can be any valid CPU ID so we don't need locking. */
cpu = smp_processor_id();
list_for_each_entry(pos, &ipcomp6_tfms_list, list) {
struct crypto_tfm *tfm;
tfms = pos->tfms;
tfm = *per_cpu_ptr(tfms, cpu);
if (!strcmp(crypto_tfm_alg_name(tfm), alg_name)) {
pos->users++;
return tfms;
}
}
pos = kmalloc(sizeof(*pos), GFP_KERNEL);
if (!pos)
return NULL;
pos->users = 1;
INIT_LIST_HEAD(&pos->list);
list_add(&pos->list, &ipcomp6_tfms_list);
pos->tfms = tfms = alloc_percpu(struct crypto_tfm *);
if (!tfms)
goto error;
for_each_cpu(cpu) {
struct crypto_tfm *tfm = crypto_alloc_tfm(alg_name, 0);
if (!tfm)
goto error;
*per_cpu_ptr(tfms, cpu) = tfm;
}
return tfms;
error:
ipcomp6_free_tfms(tfms);
return NULL;
}
static void ipcomp6_free_data(struct ipcomp_data *ipcd)
{
if (ipcd->tfms)
ipcomp6_free_tfms(ipcd->tfms);
ipcomp6_free_scratches();
}
static void ipcomp6_destroy(struct xfrm_state *x)
{
struct ipcomp_data *ipcd = x->data;
if (!ipcd)
return;
xfrm_state_delete_tunnel(x);
down(&ipcomp6_resource_sem);
ipcomp6_free_data(ipcd);
up(&ipcomp6_resource_sem);
kfree(ipcd);
xfrm6_tunnel_free_spi((xfrm_address_t *)&x->props.saddr);
}
static int ipcomp6_init_state(struct xfrm_state *x, void *args)
{
int err;
struct ipcomp_data *ipcd;
struct xfrm_algo_desc *calg_desc;
err = -EINVAL;
if (!x->calg)
goto out;
if (x->encap)
goto out;
err = -ENOMEM;
ipcd = kmalloc(sizeof(*ipcd), GFP_KERNEL);
if (!ipcd)
goto out;
memset(ipcd, 0, sizeof(*ipcd));
x->props.header_len = 0;
if (x->props.mode)
x->props.header_len += sizeof(struct ipv6hdr);
down(&ipcomp6_resource_sem);
if (!ipcomp6_alloc_scratches())
goto error;
ipcd->tfms = ipcomp6_alloc_tfms(x->calg->alg_name);
if (!ipcd->tfms)
goto error;
up(&ipcomp6_resource_sem);
if (x->props.mode) {
err = ipcomp6_tunnel_attach(x);
if (err)
goto error_tunnel;
}
calg_desc = xfrm_calg_get_byname(x->calg->alg_name, 0);
BUG_ON(!calg_desc);
ipcd->threshold = calg_desc->uinfo.comp.threshold;
x->data = ipcd;
err = 0;
out:
return err;
error_tunnel:
down(&ipcomp6_resource_sem);
error:
ipcomp6_free_data(ipcd);
up(&ipcomp6_resource_sem);
kfree(ipcd);
goto out;
}
static struct xfrm_type ipcomp6_type =
{
.description = "IPCOMP6",
.owner = THIS_MODULE,
.proto = IPPROTO_COMP,
.init_state = ipcomp6_init_state,
.destructor = ipcomp6_destroy,
.input = ipcomp6_input,
.output = ipcomp6_output,
};
static struct inet6_protocol ipcomp6_protocol =
{
.handler = xfrm6_rcv,
.err_handler = ipcomp6_err,
.flags = INET6_PROTO_NOPOLICY,
};
static int __init ipcomp6_init(void)
{
if (xfrm_register_type(&ipcomp6_type, AF_INET6) < 0) {
printk(KERN_INFO "ipcomp6 init: can't add xfrm type\n");
return -EAGAIN;
}
if (inet6_add_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0) {
printk(KERN_INFO "ipcomp6 init: can't add protocol\n");
xfrm_unregister_type(&ipcomp6_type, AF_INET6);
return -EAGAIN;
}
return 0;
}
static void __exit ipcomp6_fini(void)
{
if (inet6_del_protocol(&ipcomp6_protocol, IPPROTO_COMP) < 0)
printk(KERN_INFO "ipv6 ipcomp close: can't remove protocol\n");
if (xfrm_unregister_type(&ipcomp6_type, AF_INET6) < 0)
printk(KERN_INFO "ipv6 ipcomp close: can't remove xfrm type\n");
}
module_init(ipcomp6_init);
module_exit(ipcomp6_fini);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("IP Payload Compression Protocol (IPComp) for IPv6 - RFC3173");
MODULE_AUTHOR("Mitsuru KANDA <mk@linux-ipv6.org>");

704
net/ipv6/ipv6_sockglue.c Normal file
View File

@@ -0,0 +1,704 @@
/*
* IPv6 BSD socket options interface
* Linux INET6 implementation
*
* Authors:
* Pedro Roque <roque@di.fc.ul.pt>
*
* Based on linux/net/ipv4/ip_sockglue.c
*
* $Id: ipv6_sockglue.c,v 1.41 2002/02/01 22:01:04 davem 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.
*
* FIXME: Make the setsockopt code POSIX compliant: That is
*
* o Return -EINVAL for setsockopt of short lengths
* o Truncate getsockopt returns
* o Return an optlen of the truncated length if need be
*
* Changes:
* David L Stevens <dlstevens@us.ibm.com>:
* - added multicast source filtering API for MLDv2
*/
#include <linux/module.h>
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/sched.h>
#include <linux/net.h>
#include <linux/in6.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/init.h>
#include <linux/sysctl.h>
#include <linux/netfilter.h>
#include <net/sock.h>
#include <net/snmp.h>
#include <net/ipv6.h>
#include <net/ndisc.h>
#include <net/protocol.h>
#include <net/transp_v6.h>
#include <net/ip6_route.h>
#include <net/addrconf.h>
#include <net/inet_common.h>
#include <net/tcp.h>
#include <net/udp.h>
#include <net/xfrm.h>
#include <asm/uaccess.h>
DEFINE_SNMP_STAT(struct ipstats_mib, ipv6_statistics);
static struct packet_type ipv6_packet_type = {
.type = __constant_htons(ETH_P_IPV6),
.func = ipv6_rcv,
};
struct ip6_ra_chain *ip6_ra_chain;
DEFINE_RWLOCK(ip6_ra_lock);
int ip6_ra_control(struct sock *sk, int sel, void (*destructor)(struct sock *))
{
struct ip6_ra_chain *ra, *new_ra, **rap;
/* RA packet may be delivered ONLY to IPPROTO_RAW socket */
if (sk->sk_type != SOCK_RAW || inet_sk(sk)->num != IPPROTO_RAW)
return -EINVAL;
new_ra = (sel>=0) ? kmalloc(sizeof(*new_ra), GFP_KERNEL) : NULL;
write_lock_bh(&ip6_ra_lock);
for (rap = &ip6_ra_chain; (ra=*rap) != NULL; rap = &ra->next) {
if (ra->sk == sk) {
if (sel>=0) {
write_unlock_bh(&ip6_ra_lock);
if (new_ra)
kfree(new_ra);
return -EADDRINUSE;
}
*rap = ra->next;
write_unlock_bh(&ip6_ra_lock);
if (ra->destructor)
ra->destructor(sk);
sock_put(sk);
kfree(ra);
return 0;
}
}
if (new_ra == NULL) {
write_unlock_bh(&ip6_ra_lock);
return -ENOBUFS;
}
new_ra->sk = sk;
new_ra->sel = sel;
new_ra->destructor = destructor;
new_ra->next = ra;
*rap = new_ra;
sock_hold(sk);
write_unlock_bh(&ip6_ra_lock);
return 0;
}
extern int ip6_mc_source(int add, int omode, struct sock *sk,
struct group_source_req *pgsr);
extern int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf);
extern int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf,
struct group_filter __user *optval, int __user *optlen);
int ipv6_setsockopt(struct sock *sk, int level, int optname,
char __user *optval, int optlen)
{
struct ipv6_pinfo *np = inet6_sk(sk);
int val, valbool;
int retv = -ENOPROTOOPT;
if (level == SOL_IP && sk->sk_type != SOCK_RAW)
return udp_prot.setsockopt(sk, level, optname, optval, optlen);
if(level!=SOL_IPV6)
goto out;
if (optval == NULL)
val=0;
else if (get_user(val, (int __user *) optval))
return -EFAULT;
valbool = (val!=0);
lock_sock(sk);
switch (optname) {
case IPV6_ADDRFORM:
if (val == PF_INET) {
struct ipv6_txoptions *opt;
struct sk_buff *pktopt;
if (sk->sk_protocol != IPPROTO_UDP &&
sk->sk_protocol != IPPROTO_TCP)
break;
if (sk->sk_state != TCP_ESTABLISHED) {
retv = -ENOTCONN;
break;
}
if (ipv6_only_sock(sk) ||
!(ipv6_addr_type(&np->daddr) & IPV6_ADDR_MAPPED)) {
retv = -EADDRNOTAVAIL;
break;
}
fl6_free_socklist(sk);
ipv6_sock_mc_close(sk);
if (sk->sk_protocol == IPPROTO_TCP) {
struct tcp_sock *tp = tcp_sk(sk);
local_bh_disable();
sock_prot_dec_use(sk->sk_prot);
sock_prot_inc_use(&tcp_prot);
local_bh_enable();
sk->sk_prot = &tcp_prot;
tp->af_specific = &ipv4_specific;
sk->sk_socket->ops = &inet_stream_ops;
sk->sk_family = PF_INET;
tcp_sync_mss(sk, tp->pmtu_cookie);
} else {
local_bh_disable();
sock_prot_dec_use(sk->sk_prot);
sock_prot_inc_use(&udp_prot);
local_bh_enable();
sk->sk_prot = &udp_prot;
sk->sk_socket->ops = &inet_dgram_ops;
sk->sk_family = PF_INET;
}
opt = xchg(&np->opt, NULL);
if (opt)
sock_kfree_s(sk, opt, opt->tot_len);
pktopt = xchg(&np->pktoptions, NULL);
if (pktopt)
kfree_skb(pktopt);
sk->sk_destruct = inet_sock_destruct;
#ifdef INET_REFCNT_DEBUG
atomic_dec(&inet6_sock_nr);
#endif
module_put(THIS_MODULE);
retv = 0;
break;
}
goto e_inval;
case IPV6_V6ONLY:
if (inet_sk(sk)->num)
goto e_inval;
np->ipv6only = valbool;
retv = 0;
break;
case IPV6_PKTINFO:
np->rxopt.bits.rxinfo = valbool;
retv = 0;
break;
case IPV6_HOPLIMIT:
np->rxopt.bits.rxhlim = valbool;
retv = 0;
break;
case IPV6_RTHDR:
if (val < 0 || val > 2)
goto e_inval;
np->rxopt.bits.srcrt = val;
retv = 0;
break;
case IPV6_HOPOPTS:
np->rxopt.bits.hopopts = valbool;
retv = 0;
break;
case IPV6_DSTOPTS:
np->rxopt.bits.dstopts = valbool;
retv = 0;
break;
case IPV6_FLOWINFO:
np->rxopt.bits.rxflow = valbool;
retv = 0;
break;
case IPV6_PKTOPTIONS:
{
struct ipv6_txoptions *opt = NULL;
struct msghdr msg;
struct flowi fl;
int junk;
fl.fl6_flowlabel = 0;
fl.oif = sk->sk_bound_dev_if;
if (optlen == 0)
goto update;
/* 1K is probably excessive
* 1K is surely not enough, 2K per standard header is 16K.
*/
retv = -EINVAL;
if (optlen > 64*1024)
break;
opt = sock_kmalloc(sk, sizeof(*opt) + optlen, GFP_KERNEL);
retv = -ENOBUFS;
if (opt == NULL)
break;
memset(opt, 0, sizeof(*opt));
opt->tot_len = sizeof(*opt) + optlen;
retv = -EFAULT;
if (copy_from_user(opt+1, optval, optlen))
goto done;
msg.msg_controllen = optlen;
msg.msg_control = (void*)(opt+1);
retv = datagram_send_ctl(&msg, &fl, opt, &junk);
if (retv)
goto done;
update:
retv = 0;
if (sk->sk_type == SOCK_STREAM) {
if (opt) {
struct tcp_sock *tp = tcp_sk(sk);
if (!((1 << sk->sk_state) &
(TCPF_LISTEN | TCPF_CLOSE))
&& inet_sk(sk)->daddr != LOOPBACK4_IPV6) {
tp->ext_header_len = opt->opt_flen + opt->opt_nflen;
tcp_sync_mss(sk, tp->pmtu_cookie);
}
}
opt = xchg(&np->opt, opt);
sk_dst_reset(sk);
} else {
write_lock(&sk->sk_dst_lock);
opt = xchg(&np->opt, opt);
write_unlock(&sk->sk_dst_lock);
sk_dst_reset(sk);
}
done:
if (opt)
sock_kfree_s(sk, opt, opt->tot_len);
break;
}
case IPV6_UNICAST_HOPS:
if (val > 255 || val < -1)
goto e_inval;
np->hop_limit = val;
retv = 0;
break;
case IPV6_MULTICAST_HOPS:
if (sk->sk_type == SOCK_STREAM)
goto e_inval;
if (val > 255 || val < -1)
goto e_inval;
np->mcast_hops = val;
retv = 0;
break;
case IPV6_MULTICAST_LOOP:
np->mc_loop = valbool;
retv = 0;
break;
case IPV6_MULTICAST_IF:
if (sk->sk_type == SOCK_STREAM)
goto e_inval;
if (sk->sk_bound_dev_if && sk->sk_bound_dev_if != val)
goto e_inval;
if (__dev_get_by_index(val) == NULL) {
retv = -ENODEV;
break;
}
np->mcast_oif = val;
retv = 0;
break;
case IPV6_ADD_MEMBERSHIP:
case IPV6_DROP_MEMBERSHIP:
{
struct ipv6_mreq mreq;
retv = -EFAULT;
if (copy_from_user(&mreq, optval, sizeof(struct ipv6_mreq)))
break;
if (optname == IPV6_ADD_MEMBERSHIP)
retv = ipv6_sock_mc_join(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_multiaddr);
else
retv = ipv6_sock_mc_drop(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_multiaddr);
break;
}
case IPV6_JOIN_ANYCAST:
case IPV6_LEAVE_ANYCAST:
{
struct ipv6_mreq mreq;
if (optlen != sizeof(struct ipv6_mreq))
goto e_inval;
retv = -EFAULT;
if (copy_from_user(&mreq, optval, sizeof(struct ipv6_mreq)))
break;
if (optname == IPV6_JOIN_ANYCAST)
retv = ipv6_sock_ac_join(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_acaddr);
else
retv = ipv6_sock_ac_drop(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_acaddr);
break;
}
case MCAST_JOIN_GROUP:
case MCAST_LEAVE_GROUP:
{
struct group_req greq;
struct sockaddr_in6 *psin6;
retv = -EFAULT;
if (copy_from_user(&greq, optval, sizeof(struct group_req)))
break;
if (greq.gr_group.ss_family != AF_INET6) {
retv = -EADDRNOTAVAIL;
break;
}
psin6 = (struct sockaddr_in6 *)&greq.gr_group;
if (optname == MCAST_JOIN_GROUP)
retv = ipv6_sock_mc_join(sk, greq.gr_interface,
&psin6->sin6_addr);
else
retv = ipv6_sock_mc_drop(sk, greq.gr_interface,
&psin6->sin6_addr);
break;
}
case MCAST_JOIN_SOURCE_GROUP:
case MCAST_LEAVE_SOURCE_GROUP:
case MCAST_BLOCK_SOURCE:
case MCAST_UNBLOCK_SOURCE:
{
struct group_source_req greqs;
int omode, add;
if (optlen != sizeof(struct group_source_req))
goto e_inval;
if (copy_from_user(&greqs, optval, sizeof(greqs))) {
retv = -EFAULT;
break;
}
if (greqs.gsr_group.ss_family != AF_INET6 ||
greqs.gsr_source.ss_family != AF_INET6) {
retv = -EADDRNOTAVAIL;
break;
}
if (optname == MCAST_BLOCK_SOURCE) {
omode = MCAST_EXCLUDE;
add = 1;
} else if (optname == MCAST_UNBLOCK_SOURCE) {
omode = MCAST_EXCLUDE;
add = 0;
} else if (optname == MCAST_JOIN_SOURCE_GROUP) {
struct sockaddr_in6 *psin6;
psin6 = (struct sockaddr_in6 *)&greqs.gsr_group;
retv = ipv6_sock_mc_join(sk, greqs.gsr_interface,
&psin6->sin6_addr);
if (retv)
break;
omode = MCAST_INCLUDE;
add = 1;
} else /*IP_DROP_SOURCE_MEMBERSHIP */ {
omode = MCAST_INCLUDE;
add = 0;
}
retv = ip6_mc_source(add, omode, sk, &greqs);
break;
}
case MCAST_MSFILTER:
{
extern int sysctl_optmem_max;
extern int sysctl_mld_max_msf;
struct group_filter *gsf;
if (optlen < GROUP_FILTER_SIZE(0))
goto e_inval;
if (optlen > sysctl_optmem_max) {
retv = -ENOBUFS;
break;
}
gsf = (struct group_filter *)kmalloc(optlen,GFP_KERNEL);
if (gsf == 0) {
retv = -ENOBUFS;
break;
}
retv = -EFAULT;
if (copy_from_user(gsf, optval, optlen)) {
kfree(gsf);
break;
}
/* numsrc >= (4G-140)/128 overflow in 32 bits */
if (gsf->gf_numsrc >= 0x1ffffffU ||
gsf->gf_numsrc > sysctl_mld_max_msf) {
kfree(gsf);
retv = -ENOBUFS;
break;
}
if (GROUP_FILTER_SIZE(gsf->gf_numsrc) > optlen) {
kfree(gsf);
retv = -EINVAL;
break;
}
retv = ip6_mc_msfilter(sk, gsf);
kfree(gsf);
break;
}
case IPV6_ROUTER_ALERT:
retv = ip6_ra_control(sk, val, NULL);
break;
case IPV6_MTU_DISCOVER:
if (val<0 || val>2)
goto e_inval;
np->pmtudisc = val;
retv = 0;
break;
case IPV6_MTU:
if (val && val < IPV6_MIN_MTU)
goto e_inval;
np->frag_size = val;
retv = 0;
break;
case IPV6_RECVERR:
np->recverr = valbool;
if (!val)
skb_queue_purge(&sk->sk_error_queue);
retv = 0;
break;
case IPV6_FLOWINFO_SEND:
np->sndflow = valbool;
retv = 0;
break;
case IPV6_FLOWLABEL_MGR:
retv = ipv6_flowlabel_opt(sk, optval, optlen);
break;
case IPV6_IPSEC_POLICY:
case IPV6_XFRM_POLICY:
retv = xfrm_user_policy(sk, optname, optval, optlen);
break;
#ifdef CONFIG_NETFILTER
default:
retv = nf_setsockopt(sk, PF_INET6, optname, optval,
optlen);
break;
#endif
}
release_sock(sk);
out:
return retv;
e_inval:
release_sock(sk);
return -EINVAL;
}
int ipv6_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
{
struct ipv6_pinfo *np = inet6_sk(sk);
int len;
int val;
if (level == SOL_IP && sk->sk_type != SOCK_RAW)
return udp_prot.getsockopt(sk, level, optname, optval, optlen);
if(level!=SOL_IPV6)
return -ENOPROTOOPT;
if (get_user(len, optlen))
return -EFAULT;
switch (optname) {
case IPV6_ADDRFORM:
if (sk->sk_protocol != IPPROTO_UDP &&
sk->sk_protocol != IPPROTO_TCP)
return -EINVAL;
if (sk->sk_state != TCP_ESTABLISHED)
return -ENOTCONN;
val = sk->sk_family;
break;
case MCAST_MSFILTER:
{
struct group_filter gsf;
int err;
if (len < GROUP_FILTER_SIZE(0))
return -EINVAL;
if (copy_from_user(&gsf, optval, GROUP_FILTER_SIZE(0)))
return -EFAULT;
lock_sock(sk);
err = ip6_mc_msfget(sk, &gsf,
(struct group_filter __user *)optval, optlen);
release_sock(sk);
return err;
}
case IPV6_PKTOPTIONS:
{
struct msghdr msg;
struct sk_buff *skb;
if (sk->sk_type != SOCK_STREAM)
return -ENOPROTOOPT;
msg.msg_control = optval;
msg.msg_controllen = len;
msg.msg_flags = 0;
lock_sock(sk);
skb = np->pktoptions;
if (skb)
atomic_inc(&skb->users);
release_sock(sk);
if (skb) {
int err = datagram_recv_ctl(sk, &msg, skb);
kfree_skb(skb);
if (err)
return err;
} else {
if (np->rxopt.bits.rxinfo) {
struct in6_pktinfo src_info;
src_info.ipi6_ifindex = np->mcast_oif;
ipv6_addr_copy(&src_info.ipi6_addr, &np->daddr);
put_cmsg(&msg, SOL_IPV6, IPV6_PKTINFO, sizeof(src_info), &src_info);
}
if (np->rxopt.bits.rxhlim) {
int hlim = np->mcast_hops;
put_cmsg(&msg, SOL_IPV6, IPV6_HOPLIMIT, sizeof(hlim), &hlim);
}
}
len -= msg.msg_controllen;
return put_user(len, optlen);
}
case IPV6_MTU:
{
struct dst_entry *dst;
val = 0;
lock_sock(sk);
dst = sk_dst_get(sk);
if (dst) {
val = dst_mtu(dst);
dst_release(dst);
}
release_sock(sk);
if (!val)
return -ENOTCONN;
break;
}
case IPV6_V6ONLY:
val = np->ipv6only;
break;
case IPV6_PKTINFO:
val = np->rxopt.bits.rxinfo;
break;
case IPV6_HOPLIMIT:
val = np->rxopt.bits.rxhlim;
break;
case IPV6_RTHDR:
val = np->rxopt.bits.srcrt;
break;
case IPV6_HOPOPTS:
val = np->rxopt.bits.hopopts;
break;
case IPV6_DSTOPTS:
val = np->rxopt.bits.dstopts;
break;
case IPV6_FLOWINFO:
val = np->rxopt.bits.rxflow;
break;
case IPV6_UNICAST_HOPS:
val = np->hop_limit;
break;
case IPV6_MULTICAST_HOPS:
val = np->mcast_hops;
break;
case IPV6_MULTICAST_LOOP:
val = np->mc_loop;
break;
case IPV6_MULTICAST_IF:
val = np->mcast_oif;
break;
case IPV6_MTU_DISCOVER:
val = np->pmtudisc;
break;
case IPV6_RECVERR:
val = np->recverr;
break;
case IPV6_FLOWINFO_SEND:
val = np->sndflow;
break;
default:
#ifdef CONFIG_NETFILTER
lock_sock(sk);
val = nf_getsockopt(sk, PF_INET6, optname, optval,
&len);
release_sock(sk);
if (val >= 0)
val = put_user(len, optlen);
return val;
#else
return -EINVAL;
#endif
}
len = min_t(unsigned int, sizeof(int), len);
if(put_user(len, optlen))
return -EFAULT;
if(copy_to_user(optval,&val,len))
return -EFAULT;
return 0;
}
void __init ipv6_packet_init(void)
{
dev_add_pack(&ipv6_packet_type);
}
void ipv6_packet_cleanup(void)
{
dev_remove_pack(&ipv6_packet_type);
}

41
net/ipv6/ipv6_syms.c Normal file
View File

@@ -0,0 +1,41 @@
#include <linux/config.h>
#include <linux/module.h>
#include <net/protocol.h>
#include <net/ipv6.h>
#include <net/addrconf.h>
#include <net/ip6_route.h>
#include <net/xfrm.h>
EXPORT_SYMBOL(ipv6_addr_type);
EXPORT_SYMBOL(icmpv6_send);
EXPORT_SYMBOL(icmpv6_statistics);
EXPORT_SYMBOL(icmpv6_err_convert);
EXPORT_SYMBOL(ndisc_mc_map);
EXPORT_SYMBOL(register_inet6addr_notifier);
EXPORT_SYMBOL(unregister_inet6addr_notifier);
EXPORT_SYMBOL(ip6_route_output);
#ifdef CONFIG_NETFILTER
EXPORT_SYMBOL(ip6_route_me_harder);
#endif
EXPORT_SYMBOL(addrconf_lock);
EXPORT_SYMBOL(ipv6_setsockopt);
EXPORT_SYMBOL(ipv6_getsockopt);
EXPORT_SYMBOL(inet6_register_protosw);
EXPORT_SYMBOL(inet6_unregister_protosw);
EXPORT_SYMBOL(inet6_add_protocol);
EXPORT_SYMBOL(inet6_del_protocol);
EXPORT_SYMBOL(ip6_xmit);
EXPORT_SYMBOL(inet6_release);
EXPORT_SYMBOL(inet6_bind);
EXPORT_SYMBOL(inet6_getname);
EXPORT_SYMBOL(inet6_ioctl);
EXPORT_SYMBOL(ipv6_get_saddr);
EXPORT_SYMBOL(ipv6_chk_addr);
EXPORT_SYMBOL(in6_dev_finish_destroy);
#ifdef CONFIG_XFRM
EXPORT_SYMBOL(xfrm6_rcv);
#endif
EXPORT_SYMBOL(rt6_lookup);
EXPORT_SYMBOL(fl6_sock_lookup);
EXPORT_SYMBOL(ipv6_push_nfrag_opts);

2499
net/ipv6/mcast.c Normal file

File diff suppressed because it is too large Load Diff

1690
net/ipv6/ndisc.c Normal file

File diff suppressed because it is too large Load Diff

242
net/ipv6/netfilter/Kconfig Normal file
View File

@@ -0,0 +1,242 @@
#
# IP netfilter configuration
#
menu "IPv6: Netfilter Configuration (EXPERIMENTAL)"
depends on INET && IPV6 && NETFILTER && EXPERIMENTAL
#tristate 'Connection tracking (required for masq/NAT)' CONFIG_IP6_NF_CONNTRACK
#if [ "$CONFIG_IP6_NF_CONNTRACK" != "n" ]; then
# dep_tristate ' FTP protocol support' CONFIG_IP6_NF_FTP $CONFIG_IP6_NF_CONNTRACK
#fi
config IP6_NF_QUEUE
tristate "Userspace queueing via NETLINK"
---help---
This option adds a queue handler to the kernel for IPv6
packets which lets us to receive the filtered packets
with QUEUE target using libiptc as we can do with
the IPv4 now.
(C) Fernando Anton 2001
IPv64 Project - Work based in IPv64 draft by Arturo Azcorra.
Universidad Carlos III de Madrid
Universidad Politecnica de Alcala de Henares
email: <fanton@it.uc3m.es>.
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_IPTABLES
tristate "IP6 tables support (required for filtering/masq/NAT)"
help
ip6tables is a general, extensible packet identification framework.
Currently only the packet filtering and packet mangling subsystem
for IPv6 use this, but connection tracking is going to follow.
Say 'Y' or 'M' here if you want to use either of those.
To compile it as a module, choose M here. If unsure, say N.
# The simple matches.
config IP6_NF_MATCH_LIMIT
tristate "limit match support"
depends on IP6_NF_IPTABLES
help
limit matching allows you to control the rate at which a rule can be
matched: mainly useful in combination with the LOG target ("LOG
target support", below) and to avoid some Denial of Service attacks.
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_MAC
tristate "MAC address match support"
depends on IP6_NF_IPTABLES
help
mac matching allows you to match packets based on the source
Ethernet address of the packet.
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_RT
tristate "Routing header match support"
depends on IP6_NF_IPTABLES
help
rt matching allows you to match packets based on the routing
header of the packet.
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_OPTS
tristate "Hop-by-hop and Dst opts header match support"
depends on IP6_NF_IPTABLES
help
This allows one to match packets based on the hop-by-hop
and destination options headers of a packet.
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_FRAG
tristate "Fragmentation header match support"
depends on IP6_NF_IPTABLES
help
frag matching allows you to match packets based on the fragmentation
header of the packet.
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_HL
tristate "HL match support"
depends on IP6_NF_IPTABLES
help
HL matching allows you to match packets based on the hop
limit of the packet.
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_MULTIPORT
tristate "Multiple port match support"
depends on IP6_NF_IPTABLES
help
Multiport matching allows you to match TCP or UDP packets based on
a series of source or destination ports: normally a rule can only
match a single range of ports.
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_OWNER
tristate "Owner match support"
depends on IP6_NF_IPTABLES
help
Packet owner matching allows you to match locally-generated packets
based on who created them: the user, group, process or session.
To compile it as a module, choose M here. If unsure, say N.
# dep_tristate ' MAC address match support' CONFIG_IP6_NF_MATCH_MAC $CONFIG_IP6_NF_IPTABLES
config IP6_NF_MATCH_MARK
tristate "netfilter MARK match support"
depends on IP6_NF_IPTABLES
help
Netfilter mark matching allows you to match packets based on the
`nfmark' value in the packet. This can be set by the MARK target
(see below).
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_IPV6HEADER
tristate "IPv6 Extension Headers Match"
depends on IP6_NF_IPTABLES
help
This module allows one to match packets based upon
the ipv6 extension headers.
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_AHESP
tristate "AH/ESP match support"
depends on IP6_NF_IPTABLES
help
This module allows one to match AH and ESP packets.
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_LENGTH
tristate "Packet Length match support"
depends on IP6_NF_IPTABLES
help
This option allows you to match the length of a packet against a
specific value or range of values.
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_EUI64
tristate "EUI64 address check"
depends on IP6_NF_IPTABLES
help
This module performs checking on the IPv6 source address
Compares the last 64 bits with the EUI64 (delivered
from the MAC address) address
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_MATCH_PHYSDEV
tristate "Physdev match support"
depends on IP6_NF_IPTABLES && BRIDGE_NETFILTER
help
Physdev packet matching matches against the physical bridge ports
the IP packet arrived on or will leave by.
To compile it as a module, choose M here. If unsure, say N.
# dep_tristate ' Multiple port match support' CONFIG_IP6_NF_MATCH_MULTIPORT $CONFIG_IP6_NF_IPTABLES
# dep_tristate ' TOS match support' CONFIG_IP6_NF_MATCH_TOS $CONFIG_IP6_NF_IPTABLES
# if [ "$CONFIG_IP6_NF_CONNTRACK" != "n" ]; then
# dep_tristate ' Connection state match support' CONFIG_IP6_NF_MATCH_STATE $CONFIG_IP6_NF_CONNTRACK $CONFIG_IP6_NF_IPTABLES
# fi
# if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
# dep_tristate ' Unclean match support (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_UNCLEAN $CONFIG_IP6_NF_IPTABLES
# dep_tristate ' Owner match support (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_OWNER $CONFIG_IP6_NF_IPTABLES
# fi
# The targets
config IP6_NF_FILTER
tristate "Packet filtering"
depends on IP6_NF_IPTABLES
help
Packet filtering defines a table `filter', which has a series of
rules for simple packet filtering at local input, forwarding and
local output. See the man page for iptables(8).
To compile it as a module, choose M here. If unsure, say N.
config IP6_NF_TARGET_LOG
tristate "LOG target support"
depends on IP6_NF_FILTER
help
This option adds a `LOG' target, which allows you to create rules in
any iptables table which records the packet header to the syslog.
To compile it as a module, choose M here. If unsure, say N.
# if [ "$CONFIG_IP6_NF_FILTER" != "n" ]; then
# dep_tristate ' REJECT target support' CONFIG_IP6_NF_TARGET_REJECT $CONFIG_IP6_NF_FILTER
# if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
# dep_tristate ' MIRROR target support (EXPERIMENTAL)' CONFIG_IP6_NF_TARGET_MIRROR $CONFIG_IP6_NF_FILTER
# fi
# fi
config IP6_NF_MANGLE
tristate "Packet mangling"
depends on IP6_NF_IPTABLES
help
This option adds a `mangle' table to iptables: see the man page for
iptables(8). This table is used for various packet alterations
which can effect how the packet is routed.
To compile it as a module, choose M here. If unsure, say N.
# dep_tristate ' TOS target support' CONFIG_IP6_NF_TARGET_TOS $CONFIG_IP_NF_MANGLE
config IP6_NF_TARGET_MARK
tristate "MARK target support"
depends on IP6_NF_MANGLE
help
This option adds a `MARK' target, which allows you to create rules
in the `mangle' table which alter the netfilter mark (nfmark) field
associated with the packet packet prior to routing. This can change
the routing method (see `Use netfilter MARK value as routing
key') and can also be used by other subsystems to change their
behavior.
To compile it as a module, choose M here. If unsure, say N.
#dep_tristate ' LOG target support' CONFIG_IP6_NF_TARGET_LOG $CONFIG_IP6_NF_IPTABLES
config IP6_NF_RAW
tristate 'raw table support (required for TRACE)'
depends on IP6_NF_IPTABLES
help
This option adds a `raw' table to ip6tables. This table is the very
first in the netfilter framework and hooks in at the PREROUTING
and OUTPUT chains.
If you want to compile it as a module, say M here and read
<file:Documentation/modules.txt>. If unsure, say `N'.
endmenu

View File

@@ -0,0 +1,26 @@
#
# Makefile for the netfilter modules on top of IPv6.
#
# Link order matters here.
obj-$(CONFIG_IP6_NF_IPTABLES) += ip6_tables.o
obj-$(CONFIG_IP6_NF_MATCH_LIMIT) += ip6t_limit.o
obj-$(CONFIG_IP6_NF_MATCH_MARK) += ip6t_mark.o
obj-$(CONFIG_IP6_NF_MATCH_LENGTH) += ip6t_length.o
obj-$(CONFIG_IP6_NF_MATCH_MAC) += ip6t_mac.o
obj-$(CONFIG_IP6_NF_MATCH_RT) += ip6t_rt.o
obj-$(CONFIG_IP6_NF_MATCH_OPTS) += ip6t_hbh.o ip6t_dst.o
obj-$(CONFIG_IP6_NF_MATCH_IPV6HEADER) += ip6t_ipv6header.o
obj-$(CONFIG_IP6_NF_MATCH_FRAG) += ip6t_frag.o
obj-$(CONFIG_IP6_NF_MATCH_AHESP) += ip6t_esp.o ip6t_ah.o
obj-$(CONFIG_IP6_NF_MATCH_EUI64) += ip6t_eui64.o
obj-$(CONFIG_IP6_NF_MATCH_MULTIPORT) += ip6t_multiport.o
obj-$(CONFIG_IP6_NF_MATCH_OWNER) += ip6t_owner.o
obj-$(CONFIG_IP6_NF_MATCH_PHYSDEV) += ip6t_physdev.o
obj-$(CONFIG_IP6_NF_FILTER) += ip6table_filter.o
obj-$(CONFIG_IP6_NF_MANGLE) += ip6table_mangle.o
obj-$(CONFIG_IP6_NF_TARGET_MARK) += ip6t_MARK.o
obj-$(CONFIG_IP6_NF_QUEUE) += ip6_queue.o
obj-$(CONFIG_IP6_NF_TARGET_LOG) += ip6t_LOG.o
obj-$(CONFIG_IP6_NF_RAW) += ip6table_raw.o
obj-$(CONFIG_IP6_NF_MATCH_HL) += ip6t_hl.o

View File

@@ -0,0 +1,741 @@
/*
* This is a module which is used for queueing IPv6 packets and
* communicating with userspace via netlink.
*
* (C) 2001 Fernando Anton, this code is GPL.
* IPv64 Project - Work based in IPv64 draft by Arturo Azcorra.
* Universidad Carlos III de Madrid - Leganes (Madrid) - Spain
* Universidad Politecnica de Alcala de Henares - Alcala de H. (Madrid) - Spain
* email: fanton@it.uc3m.es
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* 2001-11-06: First try. Working with ip_queue.c for IPv4 and trying
* to adapt it to IPv6
* HEAVILY based in ipqueue.c by James Morris. It's just
* a little modified version of it, so he's nearly the
* real coder of this.
* Few changes needed, mainly the hard_routing code and
* the netlink socket protocol (we're NETLINK_IP6_FW).
* 2002-06-25: Code cleanup. [JM: ported cleanup over from ip_queue.c]
* 2005-02-04: Added /proc counter for dropped packets; fixed so
* packets aren't delivered to user space if they're going
* to be dropped.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <linux/ipv6.h>
#include <linux/notifier.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netlink.h>
#include <linux/spinlock.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <net/sock.h>
#include <net/ipv6.h>
#include <net/ip6_route.h>
#include <linux/netfilter_ipv4/ip_queue.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#define IPQ_QMAX_DEFAULT 1024
#define IPQ_PROC_FS_NAME "ip6_queue"
#define NET_IPQ_QMAX 2088
#define NET_IPQ_QMAX_NAME "ip6_queue_maxlen"
struct ipq_rt_info {
struct in6_addr daddr;
struct in6_addr saddr;
};
struct ipq_queue_entry {
struct list_head list;
struct nf_info *info;
struct sk_buff *skb;
struct ipq_rt_info rt_info;
};
typedef int (*ipq_cmpfn)(struct ipq_queue_entry *, unsigned long);
static unsigned char copy_mode = IPQ_COPY_NONE;
static unsigned int queue_maxlen = IPQ_QMAX_DEFAULT;
static DEFINE_RWLOCK(queue_lock);
static int peer_pid;
static unsigned int copy_range;
static unsigned int queue_total;
static unsigned int queue_dropped = 0;
static unsigned int queue_user_dropped = 0;
static struct sock *ipqnl;
static LIST_HEAD(queue_list);
static DECLARE_MUTEX(ipqnl_sem);
static void
ipq_issue_verdict(struct ipq_queue_entry *entry, int verdict)
{
nf_reinject(entry->skb, entry->info, verdict);
kfree(entry);
}
static inline void
__ipq_enqueue_entry(struct ipq_queue_entry *entry)
{
list_add(&entry->list, &queue_list);
queue_total++;
}
/*
* Find and return a queued entry matched by cmpfn, or return the last
* entry if cmpfn is NULL.
*/
static inline struct ipq_queue_entry *
__ipq_find_entry(ipq_cmpfn cmpfn, unsigned long data)
{
struct list_head *p;
list_for_each_prev(p, &queue_list) {
struct ipq_queue_entry *entry = (struct ipq_queue_entry *)p;
if (!cmpfn || cmpfn(entry, data))
return entry;
}
return NULL;
}
static inline void
__ipq_dequeue_entry(struct ipq_queue_entry *entry)
{
list_del(&entry->list);
queue_total--;
}
static inline struct ipq_queue_entry *
__ipq_find_dequeue_entry(ipq_cmpfn cmpfn, unsigned long data)
{
struct ipq_queue_entry *entry;
entry = __ipq_find_entry(cmpfn, data);
if (entry == NULL)
return NULL;
__ipq_dequeue_entry(entry);
return entry;
}
static inline void
__ipq_flush(int verdict)
{
struct ipq_queue_entry *entry;
while ((entry = __ipq_find_dequeue_entry(NULL, 0)))
ipq_issue_verdict(entry, verdict);
}
static inline int
__ipq_set_mode(unsigned char mode, unsigned int range)
{
int status = 0;
switch(mode) {
case IPQ_COPY_NONE:
case IPQ_COPY_META:
copy_mode = mode;
copy_range = 0;
break;
case IPQ_COPY_PACKET:
copy_mode = mode;
copy_range = range;
if (copy_range > 0xFFFF)
copy_range = 0xFFFF;
break;
default:
status = -EINVAL;
}
return status;
}
static inline void
__ipq_reset(void)
{
peer_pid = 0;
net_disable_timestamp();
__ipq_set_mode(IPQ_COPY_NONE, 0);
__ipq_flush(NF_DROP);
}
static struct ipq_queue_entry *
ipq_find_dequeue_entry(ipq_cmpfn cmpfn, unsigned long data)
{
struct ipq_queue_entry *entry;
write_lock_bh(&queue_lock);
entry = __ipq_find_dequeue_entry(cmpfn, data);
write_unlock_bh(&queue_lock);
return entry;
}
static void
ipq_flush(int verdict)
{
write_lock_bh(&queue_lock);
__ipq_flush(verdict);
write_unlock_bh(&queue_lock);
}
static struct sk_buff *
ipq_build_packet_message(struct ipq_queue_entry *entry, int *errp)
{
unsigned char *old_tail;
size_t size = 0;
size_t data_len = 0;
struct sk_buff *skb;
struct ipq_packet_msg *pmsg;
struct nlmsghdr *nlh;
read_lock_bh(&queue_lock);
switch (copy_mode) {
case IPQ_COPY_META:
case IPQ_COPY_NONE:
size = NLMSG_SPACE(sizeof(*pmsg));
data_len = 0;
break;
case IPQ_COPY_PACKET:
if (copy_range == 0 || copy_range > entry->skb->len)
data_len = entry->skb->len;
else
data_len = copy_range;
size = NLMSG_SPACE(sizeof(*pmsg) + data_len);
break;
default:
*errp = -EINVAL;
read_unlock_bh(&queue_lock);
return NULL;
}
read_unlock_bh(&queue_lock);
skb = alloc_skb(size, GFP_ATOMIC);
if (!skb)
goto nlmsg_failure;
old_tail= skb->tail;
nlh = NLMSG_PUT(skb, 0, 0, IPQM_PACKET, size - sizeof(*nlh));
pmsg = NLMSG_DATA(nlh);
memset(pmsg, 0, sizeof(*pmsg));
pmsg->packet_id = (unsigned long )entry;
pmsg->data_len = data_len;
pmsg->timestamp_sec = entry->skb->stamp.tv_sec;
pmsg->timestamp_usec = entry->skb->stamp.tv_usec;
pmsg->mark = entry->skb->nfmark;
pmsg->hook = entry->info->hook;
pmsg->hw_protocol = entry->skb->protocol;
if (entry->info->indev)
strcpy(pmsg->indev_name, entry->info->indev->name);
else
pmsg->indev_name[0] = '\0';
if (entry->info->outdev)
strcpy(pmsg->outdev_name, entry->info->outdev->name);
else
pmsg->outdev_name[0] = '\0';
if (entry->info->indev && entry->skb->dev) {
pmsg->hw_type = entry->skb->dev->type;
if (entry->skb->dev->hard_header_parse)
pmsg->hw_addrlen =
entry->skb->dev->hard_header_parse(entry->skb,
pmsg->hw_addr);
}
if (data_len)
if (skb_copy_bits(entry->skb, 0, pmsg->payload, data_len))
BUG();
nlh->nlmsg_len = skb->tail - old_tail;
return skb;
nlmsg_failure:
if (skb)
kfree_skb(skb);
*errp = -EINVAL;
printk(KERN_ERR "ip6_queue: error creating packet message\n");
return NULL;
}
static int
ipq_enqueue_packet(struct sk_buff *skb, struct nf_info *info, void *data)
{
int status = -EINVAL;
struct sk_buff *nskb;
struct ipq_queue_entry *entry;
if (copy_mode == IPQ_COPY_NONE)
return -EAGAIN;
entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
if (entry == NULL) {
printk(KERN_ERR "ip6_queue: OOM in ipq_enqueue_packet()\n");
return -ENOMEM;
}
entry->info = info;
entry->skb = skb;
if (entry->info->hook == NF_IP_LOCAL_OUT) {
struct ipv6hdr *iph = skb->nh.ipv6h;
entry->rt_info.daddr = iph->daddr;
entry->rt_info.saddr = iph->saddr;
}
nskb = ipq_build_packet_message(entry, &status);
if (nskb == NULL)
goto err_out_free;
write_lock_bh(&queue_lock);
if (!peer_pid)
goto err_out_free_nskb;
if (queue_total >= queue_maxlen) {
queue_dropped++;
status = -ENOSPC;
if (net_ratelimit())
printk (KERN_WARNING "ip6_queue: fill at %d entries, "
"dropping packet(s). Dropped: %d\n", queue_total,
queue_dropped);
goto err_out_free_nskb;
}
/* netlink_unicast will either free the nskb or attach it to a socket */
status = netlink_unicast(ipqnl, nskb, peer_pid, MSG_DONTWAIT);
if (status < 0) {
queue_user_dropped++;
goto err_out_unlock;
}
__ipq_enqueue_entry(entry);
write_unlock_bh(&queue_lock);
return status;
err_out_free_nskb:
kfree_skb(nskb);
err_out_unlock:
write_unlock_bh(&queue_lock);
err_out_free:
kfree(entry);
return status;
}
static int
ipq_mangle_ipv6(ipq_verdict_msg_t *v, struct ipq_queue_entry *e)
{
int diff;
struct ipv6hdr *user_iph = (struct ipv6hdr *)v->payload;
if (v->data_len < sizeof(*user_iph))
return 0;
diff = v->data_len - e->skb->len;
if (diff < 0)
skb_trim(e->skb, v->data_len);
else if (diff > 0) {
if (v->data_len > 0xFFFF)
return -EINVAL;
if (diff > skb_tailroom(e->skb)) {
struct sk_buff *newskb;
newskb = skb_copy_expand(e->skb,
skb_headroom(e->skb),
diff,
GFP_ATOMIC);
if (newskb == NULL) {
printk(KERN_WARNING "ip6_queue: OOM "
"in mangle, dropping packet\n");
return -ENOMEM;
}
if (e->skb->sk)
skb_set_owner_w(newskb, e->skb->sk);
kfree_skb(e->skb);
e->skb = newskb;
}
skb_put(e->skb, diff);
}
if (!skb_ip_make_writable(&e->skb, v->data_len))
return -ENOMEM;
memcpy(e->skb->data, v->payload, v->data_len);
e->skb->nfcache |= NFC_ALTERED;
/*
* Extra routing may needed on local out, as the QUEUE target never
* returns control to the table.
* Not a nice way to cmp, but works
*/
if (e->info->hook == NF_IP_LOCAL_OUT) {
struct ipv6hdr *iph = e->skb->nh.ipv6h;
if (!ipv6_addr_equal(&iph->daddr, &e->rt_info.daddr) ||
!ipv6_addr_equal(&iph->saddr, &e->rt_info.saddr))
return ip6_route_me_harder(e->skb);
}
return 0;
}
static inline int
id_cmp(struct ipq_queue_entry *e, unsigned long id)
{
return (id == (unsigned long )e);
}
static int
ipq_set_verdict(struct ipq_verdict_msg *vmsg, unsigned int len)
{
struct ipq_queue_entry *entry;
if (vmsg->value > NF_MAX_VERDICT)
return -EINVAL;
entry = ipq_find_dequeue_entry(id_cmp, vmsg->id);
if (entry == NULL)
return -ENOENT;
else {
int verdict = vmsg->value;
if (vmsg->data_len && vmsg->data_len == len)
if (ipq_mangle_ipv6(vmsg, entry) < 0)
verdict = NF_DROP;
ipq_issue_verdict(entry, verdict);
return 0;
}
}
static int
ipq_set_mode(unsigned char mode, unsigned int range)
{
int status;
write_lock_bh(&queue_lock);
status = __ipq_set_mode(mode, range);
write_unlock_bh(&queue_lock);
return status;
}
static int
ipq_receive_peer(struct ipq_peer_msg *pmsg,
unsigned char type, unsigned int len)
{
int status = 0;
if (len < sizeof(*pmsg))
return -EINVAL;
switch (type) {
case IPQM_MODE:
status = ipq_set_mode(pmsg->msg.mode.value,
pmsg->msg.mode.range);
break;
case IPQM_VERDICT:
if (pmsg->msg.verdict.value > NF_MAX_VERDICT)
status = -EINVAL;
else
status = ipq_set_verdict(&pmsg->msg.verdict,
len - sizeof(*pmsg));
break;
default:
status = -EINVAL;
}
return status;
}
static int
dev_cmp(struct ipq_queue_entry *entry, unsigned long ifindex)
{
if (entry->info->indev)
if (entry->info->indev->ifindex == ifindex)
return 1;
if (entry->info->outdev)
if (entry->info->outdev->ifindex == ifindex)
return 1;
return 0;
}
static void
ipq_dev_drop(int ifindex)
{
struct ipq_queue_entry *entry;
while ((entry = ipq_find_dequeue_entry(dev_cmp, ifindex)) != NULL)
ipq_issue_verdict(entry, NF_DROP);
}
#define RCV_SKB_FAIL(err) do { netlink_ack(skb, nlh, (err)); return; } while (0)
static inline void
ipq_rcv_skb(struct sk_buff *skb)
{
int status, type, pid, flags, nlmsglen, skblen;
struct nlmsghdr *nlh;
skblen = skb->len;
if (skblen < sizeof(*nlh))
return;
nlh = (struct nlmsghdr *)skb->data;
nlmsglen = nlh->nlmsg_len;
if (nlmsglen < sizeof(*nlh) || skblen < nlmsglen)
return;
pid = nlh->nlmsg_pid;
flags = nlh->nlmsg_flags;
if(pid <= 0 || !(flags & NLM_F_REQUEST) || flags & NLM_F_MULTI)
RCV_SKB_FAIL(-EINVAL);
if (flags & MSG_TRUNC)
RCV_SKB_FAIL(-ECOMM);
type = nlh->nlmsg_type;
if (type < NLMSG_NOOP || type >= IPQM_MAX)
RCV_SKB_FAIL(-EINVAL);
if (type <= IPQM_BASE)
return;
if (security_netlink_recv(skb))
RCV_SKB_FAIL(-EPERM);
write_lock_bh(&queue_lock);
if (peer_pid) {
if (peer_pid != pid) {
write_unlock_bh(&queue_lock);
RCV_SKB_FAIL(-EBUSY);
}
} else {
net_enable_timestamp();
peer_pid = pid;
}
write_unlock_bh(&queue_lock);
status = ipq_receive_peer(NLMSG_DATA(nlh), type,
skblen - NLMSG_LENGTH(0));
if (status < 0)
RCV_SKB_FAIL(status);
if (flags & NLM_F_ACK)
netlink_ack(skb, nlh, 0);
return;
}
static void
ipq_rcv_sk(struct sock *sk, int len)
{
do {
struct sk_buff *skb;
if (down_trylock(&ipqnl_sem))
return;
while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
ipq_rcv_skb(skb);
kfree_skb(skb);
}
up(&ipqnl_sem);
} while (ipqnl && ipqnl->sk_receive_queue.qlen);
}
static int
ipq_rcv_dev_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct net_device *dev = ptr;
/* Drop any packets associated with the downed device */
if (event == NETDEV_DOWN)
ipq_dev_drop(dev->ifindex);
return NOTIFY_DONE;
}
static struct notifier_block ipq_dev_notifier = {
.notifier_call = ipq_rcv_dev_event,
};
static int
ipq_rcv_nl_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct netlink_notify *n = ptr;
if (event == NETLINK_URELEASE &&
n->protocol == NETLINK_IP6_FW && n->pid) {
write_lock_bh(&queue_lock);
if (n->pid == peer_pid)
__ipq_reset();
write_unlock_bh(&queue_lock);
}
return NOTIFY_DONE;
}
static struct notifier_block ipq_nl_notifier = {
.notifier_call = ipq_rcv_nl_event,
};
static struct ctl_table_header *ipq_sysctl_header;
static ctl_table ipq_table[] = {
{
.ctl_name = NET_IPQ_QMAX,
.procname = NET_IPQ_QMAX_NAME,
.data = &queue_maxlen,
.maxlen = sizeof(queue_maxlen),
.mode = 0644,
.proc_handler = proc_dointvec
},
{ .ctl_name = 0 }
};
static ctl_table ipq_dir_table[] = {
{
.ctl_name = NET_IPV6,
.procname = "ipv6",
.mode = 0555,
.child = ipq_table
},
{ .ctl_name = 0 }
};
static ctl_table ipq_root_table[] = {
{
.ctl_name = CTL_NET,
.procname = "net",
.mode = 0555,
.child = ipq_dir_table
},
{ .ctl_name = 0 }
};
static int
ipq_get_info(char *buffer, char **start, off_t offset, int length)
{
int len;
read_lock_bh(&queue_lock);
len = sprintf(buffer,
"Peer PID : %d\n"
"Copy mode : %hu\n"
"Copy range : %u\n"
"Queue length : %u\n"
"Queue max. length : %u\n"
"Queue dropped : %u\n"
"Netfilter dropped : %u\n",
peer_pid,
copy_mode,
copy_range,
queue_total,
queue_maxlen,
queue_dropped,
queue_user_dropped);
read_unlock_bh(&queue_lock);
*start = buffer + offset;
len -= offset;
if (len > length)
len = length;
else if (len < 0)
len = 0;
return len;
}
static int
init_or_cleanup(int init)
{
int status = -ENOMEM;
struct proc_dir_entry *proc;
if (!init)
goto cleanup;
netlink_register_notifier(&ipq_nl_notifier);
ipqnl = netlink_kernel_create(NETLINK_IP6_FW, ipq_rcv_sk);
if (ipqnl == NULL) {
printk(KERN_ERR "ip6_queue: failed to create netlink socket\n");
goto cleanup_netlink_notifier;
}
proc = proc_net_create(IPQ_PROC_FS_NAME, 0, ipq_get_info);
if (proc)
proc->owner = THIS_MODULE;
else {
printk(KERN_ERR "ip6_queue: failed to create proc entry\n");
goto cleanup_ipqnl;
}
register_netdevice_notifier(&ipq_dev_notifier);
ipq_sysctl_header = register_sysctl_table(ipq_root_table, 0);
status = nf_register_queue_handler(PF_INET6, ipq_enqueue_packet, NULL);
if (status < 0) {
printk(KERN_ERR "ip6_queue: failed to register queue handler\n");
goto cleanup_sysctl;
}
return status;
cleanup:
nf_unregister_queue_handler(PF_INET6);
synchronize_net();
ipq_flush(NF_DROP);
cleanup_sysctl:
unregister_sysctl_table(ipq_sysctl_header);
unregister_netdevice_notifier(&ipq_dev_notifier);
proc_net_remove(IPQ_PROC_FS_NAME);
cleanup_ipqnl:
sock_release(ipqnl->sk_socket);
down(&ipqnl_sem);
up(&ipqnl_sem);
cleanup_netlink_notifier:
netlink_unregister_notifier(&ipq_nl_notifier);
return status;
}
static int __init init(void)
{
return init_or_cleanup(1);
}
static void __exit fini(void)
{
init_or_cleanup(0);
}
MODULE_DESCRIPTION("IPv6 packet queue handler");
MODULE_LICENSE("GPL");
module_init(init);
module_exit(fini);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,509 @@
/*
* This is a module which is used for logging packets.
*/
/* (C) 2001 Jan Rekorajski <baggins@pld.org.pl>
* (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/spinlock.h>
#include <linux/icmpv6.h>
#include <net/udp.h>
#include <net/tcp.h>
#include <net/ipv6.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
MODULE_AUTHOR("Jan Rekorajski <baggins@pld.org.pl>");
MODULE_DESCRIPTION("IP6 tables LOG target module");
MODULE_LICENSE("GPL");
static unsigned int nflog = 1;
module_param(nflog, int, 0400);
MODULE_PARM_DESC(nflog, "register as internal netfilter logging module");
struct in_device;
#include <net/route.h>
#include <linux/netfilter_ipv6/ip6t_LOG.h>
#if 0
#define DEBUGP printk
#else
#define DEBUGP(format, args...)
#endif
/* Use lock to serialize, so printks don't overlap */
static DEFINE_SPINLOCK(log_lock);
/* One level of recursion won't kill us */
static void dump_packet(const struct ip6t_log_info *info,
const struct sk_buff *skb, unsigned int ip6hoff,
int recurse)
{
u_int8_t currenthdr;
int fragment;
struct ipv6hdr _ip6h, *ih;
unsigned int ptr;
unsigned int hdrlen = 0;
ih = skb_header_pointer(skb, ip6hoff, sizeof(_ip6h), &_ip6h);
if (ih == NULL) {
printk("TRUNCATED");
return;
}
/* Max length: 88 "SRC=0000.0000.0000.0000.0000.0000.0000.0000 DST=0000.0000.0000.0000.0000.0000.0000.0000" */
printk("SRC=%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x ", NIP6(ih->saddr));
printk("DST=%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x ", NIP6(ih->daddr));
/* Max length: 44 "LEN=65535 TC=255 HOPLIMIT=255 FLOWLBL=FFFFF " */
printk("LEN=%Zu TC=%u HOPLIMIT=%u FLOWLBL=%u ",
ntohs(ih->payload_len) + sizeof(struct ipv6hdr),
(ntohl(*(u_int32_t *)ih) & 0x0ff00000) >> 20,
ih->hop_limit,
(ntohl(*(u_int32_t *)ih) & 0x000fffff));
fragment = 0;
ptr = ip6hoff + sizeof(struct ipv6hdr);
currenthdr = ih->nexthdr;
while (currenthdr != NEXTHDR_NONE && ip6t_ext_hdr(currenthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
if (hp == NULL) {
printk("TRUNCATED");
return;
}
/* Max length: 48 "OPT (...) " */
if (info->logflags & IP6T_LOG_IPOPT)
printk("OPT ( ");
switch (currenthdr) {
case IPPROTO_FRAGMENT: {
struct frag_hdr _fhdr, *fh;
printk("FRAG:");
fh = skb_header_pointer(skb, ptr, sizeof(_fhdr),
&_fhdr);
if (fh == NULL) {
printk("TRUNCATED ");
return;
}
/* Max length: 6 "65535 " */
printk("%u ", ntohs(fh->frag_off) & 0xFFF8);
/* Max length: 11 "INCOMPLETE " */
if (fh->frag_off & htons(0x0001))
printk("INCOMPLETE ");
printk("ID:%08x ", ntohl(fh->identification));
if (ntohs(fh->frag_off) & 0xFFF8)
fragment = 1;
hdrlen = 8;
break;
}
case IPPROTO_DSTOPTS:
case IPPROTO_ROUTING:
case IPPROTO_HOPOPTS:
if (fragment) {
if (info->logflags & IP6T_LOG_IPOPT)
printk(")");
return;
}
hdrlen = ipv6_optlen(hp);
break;
/* Max Length */
case IPPROTO_AH:
if (info->logflags & IP6T_LOG_IPOPT) {
struct ip_auth_hdr _ahdr, *ah;
/* Max length: 3 "AH " */
printk("AH ");
if (fragment) {
printk(")");
return;
}
ah = skb_header_pointer(skb, ptr, sizeof(_ahdr),
&_ahdr);
if (ah == NULL) {
/*
* Max length: 26 "INCOMPLETE [65535
* bytes] )"
*/
printk("INCOMPLETE [%u bytes] )",
skb->len - ptr);
return;
}
/* Length: 15 "SPI=0xF1234567 */
printk("SPI=0x%x ", ntohl(ah->spi));
}
hdrlen = (hp->hdrlen+2)<<2;
break;
case IPPROTO_ESP:
if (info->logflags & IP6T_LOG_IPOPT) {
struct ip_esp_hdr _esph, *eh;
/* Max length: 4 "ESP " */
printk("ESP ");
if (fragment) {
printk(")");
return;
}
/*
* Max length: 26 "INCOMPLETE [65535 bytes] )"
*/
eh = skb_header_pointer(skb, ptr, sizeof(_esph),
&_esph);
if (eh == NULL) {
printk("INCOMPLETE [%u bytes] )",
skb->len - ptr);
return;
}
/* Length: 16 "SPI=0xF1234567 )" */
printk("SPI=0x%x )", ntohl(eh->spi) );
}
return;
default:
/* Max length: 20 "Unknown Ext Hdr 255" */
printk("Unknown Ext Hdr %u", currenthdr);
return;
}
if (info->logflags & IP6T_LOG_IPOPT)
printk(") ");
currenthdr = hp->nexthdr;
ptr += hdrlen;
}
switch (currenthdr) {
case IPPROTO_TCP: {
struct tcphdr _tcph, *th;
/* Max length: 10 "PROTO=TCP " */
printk("PROTO=TCP ");
if (fragment)
break;
/* Max length: 25 "INCOMPLETE [65535 bytes] " */
th = skb_header_pointer(skb, ptr, sizeof(_tcph), &_tcph);
if (th == NULL) {
printk("INCOMPLETE [%u bytes] ", skb->len - ptr);
return;
}
/* Max length: 20 "SPT=65535 DPT=65535 " */
printk("SPT=%u DPT=%u ",
ntohs(th->source), ntohs(th->dest));
/* Max length: 30 "SEQ=4294967295 ACK=4294967295 " */
if (info->logflags & IP6T_LOG_TCPSEQ)
printk("SEQ=%u ACK=%u ",
ntohl(th->seq), ntohl(th->ack_seq));
/* Max length: 13 "WINDOW=65535 " */
printk("WINDOW=%u ", ntohs(th->window));
/* Max length: 9 "RES=0x3C " */
printk("RES=0x%02x ", (u_int8_t)(ntohl(tcp_flag_word(th) & TCP_RESERVED_BITS) >> 22));
/* Max length: 32 "CWR ECE URG ACK PSH RST SYN FIN " */
if (th->cwr)
printk("CWR ");
if (th->ece)
printk("ECE ");
if (th->urg)
printk("URG ");
if (th->ack)
printk("ACK ");
if (th->psh)
printk("PSH ");
if (th->rst)
printk("RST ");
if (th->syn)
printk("SYN ");
if (th->fin)
printk("FIN ");
/* Max length: 11 "URGP=65535 " */
printk("URGP=%u ", ntohs(th->urg_ptr));
if ((info->logflags & IP6T_LOG_TCPOPT)
&& th->doff * 4 > sizeof(struct tcphdr)) {
u_int8_t _opt[60 - sizeof(struct tcphdr)], *op;
unsigned int i;
unsigned int optsize = th->doff * 4
- sizeof(struct tcphdr);
op = skb_header_pointer(skb,
ptr + sizeof(struct tcphdr),
optsize, _opt);
if (op == NULL) {
printk("OPT (TRUNCATED)");
return;
}
/* Max length: 127 "OPT (" 15*4*2chars ") " */
printk("OPT (");
for (i =0; i < optsize; i++)
printk("%02X", op[i]);
printk(") ");
}
break;
}
case IPPROTO_UDP: {
struct udphdr _udph, *uh;
/* Max length: 10 "PROTO=UDP " */
printk("PROTO=UDP ");
if (fragment)
break;
/* Max length: 25 "INCOMPLETE [65535 bytes] " */
uh = skb_header_pointer(skb, ptr, sizeof(_udph), &_udph);
if (uh == NULL) {
printk("INCOMPLETE [%u bytes] ", skb->len - ptr);
return;
}
/* Max length: 20 "SPT=65535 DPT=65535 " */
printk("SPT=%u DPT=%u LEN=%u ",
ntohs(uh->source), ntohs(uh->dest),
ntohs(uh->len));
break;
}
case IPPROTO_ICMPV6: {
struct icmp6hdr _icmp6h, *ic;
/* Max length: 13 "PROTO=ICMPv6 " */
printk("PROTO=ICMPv6 ");
if (fragment)
break;
/* Max length: 25 "INCOMPLETE [65535 bytes] " */
ic = skb_header_pointer(skb, ptr, sizeof(_icmp6h), &_icmp6h);
if (ic == NULL) {
printk("INCOMPLETE [%u bytes] ", skb->len - ptr);
return;
}
/* Max length: 18 "TYPE=255 CODE=255 " */
printk("TYPE=%u CODE=%u ", ic->icmp6_type, ic->icmp6_code);
switch (ic->icmp6_type) {
case ICMPV6_ECHO_REQUEST:
case ICMPV6_ECHO_REPLY:
/* Max length: 19 "ID=65535 SEQ=65535 " */
printk("ID=%u SEQ=%u ",
ntohs(ic->icmp6_identifier),
ntohs(ic->icmp6_sequence));
break;
case ICMPV6_MGM_QUERY:
case ICMPV6_MGM_REPORT:
case ICMPV6_MGM_REDUCTION:
break;
case ICMPV6_PARAMPROB:
/* Max length: 17 "POINTER=ffffffff " */
printk("POINTER=%08x ", ntohl(ic->icmp6_pointer));
/* Fall through */
case ICMPV6_DEST_UNREACH:
case ICMPV6_PKT_TOOBIG:
case ICMPV6_TIME_EXCEED:
/* Max length: 3+maxlen */
if (recurse) {
printk("[");
dump_packet(info, skb, ptr + sizeof(_icmp6h),
0);
printk("] ");
}
/* Max length: 10 "MTU=65535 " */
if (ic->icmp6_type == ICMPV6_PKT_TOOBIG)
printk("MTU=%u ", ntohl(ic->icmp6_mtu));
}
break;
}
/* Max length: 10 "PROTO=255 " */
default:
printk("PROTO=%u ", currenthdr);
}
/* Max length: 15 "UID=4294967295 " */
if ((info->logflags & IP6T_LOG_UID) && recurse && skb->sk) {
read_lock_bh(&skb->sk->sk_callback_lock);
if (skb->sk->sk_socket && skb->sk->sk_socket->file)
printk("UID=%u ", skb->sk->sk_socket->file->f_uid);
read_unlock_bh(&skb->sk->sk_callback_lock);
}
}
static void
ip6t_log_packet(unsigned int hooknum,
const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const struct ip6t_log_info *loginfo,
const char *level_string,
const char *prefix)
{
struct ipv6hdr *ipv6h = skb->nh.ipv6h;
spin_lock_bh(&log_lock);
printk(level_string);
printk("%sIN=%s OUT=%s ",
prefix == NULL ? loginfo->prefix : prefix,
in ? in->name : "",
out ? out->name : "");
if (in && !out) {
/* MAC logging for input chain only. */
printk("MAC=");
if (skb->dev && skb->dev->hard_header_len && skb->mac.raw != (void*)ipv6h) {
if (skb->dev->type != ARPHRD_SIT){
int i;
unsigned char *p = skb->mac.raw;
for (i = 0; i < skb->dev->hard_header_len; i++,p++)
printk("%02x%c", *p,
i==skb->dev->hard_header_len - 1
? ' ':':');
} else {
int i;
unsigned char *p = skb->mac.raw;
if ( p - (ETH_ALEN*2+2) > skb->head ){
p -= (ETH_ALEN+2);
for (i = 0; i < (ETH_ALEN); i++,p++)
printk("%02x%s", *p,
i == ETH_ALEN-1 ? "->" : ":");
p -= (ETH_ALEN*2);
for (i = 0; i < (ETH_ALEN); i++,p++)
printk("%02x%c", *p,
i == ETH_ALEN-1 ? ' ' : ':');
}
if ((skb->dev->addr_len == 4) &&
skb->dev->hard_header_len > 20){
printk("TUNNEL=");
p = skb->mac.raw + 12;
for (i = 0; i < 4; i++,p++)
printk("%3d%s", *p,
i == 3 ? "->" : ".");
for (i = 0; i < 4; i++,p++)
printk("%3d%c", *p,
i == 3 ? ' ' : '.');
}
}
} else
printk(" ");
}
dump_packet(loginfo, skb, (u8*)skb->nh.ipv6h - skb->data, 1);
printk("\n");
spin_unlock_bh(&log_lock);
}
static unsigned int
ip6t_log_target(struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
const void *targinfo,
void *userinfo)
{
const struct ip6t_log_info *loginfo = targinfo;
char level_string[4] = "< >";
level_string[1] = '0' + (loginfo->level % 8);
ip6t_log_packet(hooknum, *pskb, in, out, loginfo, level_string, NULL);
return IP6T_CONTINUE;
}
static void
ip6t_logfn(unsigned int hooknum,
const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const char *prefix)
{
struct ip6t_log_info loginfo = {
.level = 0,
.logflags = IP6T_LOG_MASK,
.prefix = ""
};
ip6t_log_packet(hooknum, skb, in, out, &loginfo, KERN_WARNING, prefix);
}
static int ip6t_log_checkentry(const char *tablename,
const struct ip6t_entry *e,
void *targinfo,
unsigned int targinfosize,
unsigned int hook_mask)
{
const struct ip6t_log_info *loginfo = targinfo;
if (targinfosize != IP6T_ALIGN(sizeof(struct ip6t_log_info))) {
DEBUGP("LOG: targinfosize %u != %u\n",
targinfosize, IP6T_ALIGN(sizeof(struct ip6t_log_info)));
return 0;
}
if (loginfo->level >= 8) {
DEBUGP("LOG: level %u >= 8\n", loginfo->level);
return 0;
}
if (loginfo->prefix[sizeof(loginfo->prefix)-1] != '\0') {
DEBUGP("LOG: prefix term %i\n",
loginfo->prefix[sizeof(loginfo->prefix)-1]);
return 0;
}
return 1;
}
static struct ip6t_target ip6t_log_reg = {
.name = "LOG",
.target = ip6t_log_target,
.checkentry = ip6t_log_checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
if (ip6t_register_target(&ip6t_log_reg))
return -EINVAL;
if (nflog)
nf_log_register(PF_INET6, &ip6t_logfn);
return 0;
}
static void __exit fini(void)
{
if (nflog)
nf_log_unregister(PF_INET6, &ip6t_logfn);
ip6t_unregister_target(&ip6t_log_reg);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,78 @@
/* This is a module which is used for setting the NFMARK field of an skb. */
/* (C) 1999-2001 Marc Boucher <marc@mbsi.ca>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <net/checksum.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter_ipv6/ip6t_MARK.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Netfilter Core Team <coreteam@netfilter.org>");
static unsigned int
target(struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
unsigned int hooknum,
const void *targinfo,
void *userinfo)
{
const struct ip6t_mark_target_info *markinfo = targinfo;
if((*pskb)->nfmark != markinfo->mark) {
(*pskb)->nfmark = markinfo->mark;
(*pskb)->nfcache |= NFC_ALTERED;
}
return IP6T_CONTINUE;
}
static int
checkentry(const char *tablename,
const struct ip6t_entry *e,
void *targinfo,
unsigned int targinfosize,
unsigned int hook_mask)
{
if (targinfosize != IP6T_ALIGN(sizeof(struct ip6t_mark_target_info))) {
printk(KERN_WARNING "MARK: targinfosize %u != %Zu\n",
targinfosize,
IP6T_ALIGN(sizeof(struct ip6t_mark_target_info)));
return 0;
}
if (strcmp(tablename, "mangle") != 0) {
printk(KERN_WARNING "MARK: can only be called from \"mangle\" table, not \"%s\"\n", tablename);
return 0;
}
return 1;
}
static struct ip6t_target ip6t_mark_reg
= { { NULL, NULL }, "MARK", target, checkentry, NULL, THIS_MODULE };
static int __init init(void)
{
printk(KERN_DEBUG "registering ipv6 mark target\n");
if (ip6t_register_target(&ip6t_mark_reg))
return -EINVAL;
return 0;
}
static void __exit fini(void)
{
ip6t_unregister_target(&ip6t_mark_reg);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,208 @@
/* Kernel module to match AH parameters. */
/* (C) 2001-2002 Andras Kis-Szabo <kisza@sch.bme.hu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ipv6.h>
#include <linux/types.h>
#include <net/checksum.h>
#include <net/ipv6.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter_ipv6/ip6t_ah.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("IPv6 AH match");
MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
#if 0
#define DEBUGP printk
#else
#define DEBUGP(format, args...)
#endif
/* Returns 1 if the spi is matched by the range, 0 otherwise */
static inline int
spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, int invert)
{
int r=0;
DEBUGP("ah spi_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
min,spi,max);
r = (spi >= min && spi <= max) ^ invert;
DEBUGP(" result %s\n",r? "PASS\n" : "FAILED\n");
return r;
}
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
struct ip_auth_hdr *ah = NULL, _ah;
const struct ip6t_ah *ahinfo = matchinfo;
unsigned int temp;
int len;
u8 nexthdr;
unsigned int ptr;
unsigned int hdrlen = 0;
/*DEBUGP("IPv6 AH entered\n");*/
/* if (opt->auth == 0) return 0;
* It does not filled on output */
/* type of the 1st exthdr */
nexthdr = skb->nh.ipv6h->nexthdr;
/* pointer to the 1st exthdr */
ptr = sizeof(struct ipv6hdr);
/* available length */
len = skb->len - ptr;
temp = 0;
while (ip6t_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
DEBUGP("ipv6_ah header iteration \n");
/* Is there enough space for the next ext header? */
if (len < sizeof(struct ipv6_opt_hdr))
return 0;
/* No more exthdr -> evaluate */
if (nexthdr == NEXTHDR_NONE)
break;
/* ESP -> evaluate */
if (nexthdr == NEXTHDR_ESP)
break;
hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
BUG_ON(hp == NULL);
/* Calculate the header length */
if (nexthdr == NEXTHDR_FRAGMENT)
hdrlen = 8;
else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hp->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hp);
/* AH -> evaluate */
if (nexthdr == NEXTHDR_AUTH) {
temp |= MASK_AH;
break;
}
/* set the flag */
switch (nexthdr) {
case NEXTHDR_HOP:
case NEXTHDR_ROUTING:
case NEXTHDR_FRAGMENT:
case NEXTHDR_AUTH:
case NEXTHDR_DEST:
break;
default:
DEBUGP("ipv6_ah match: unknown nextheader %u\n",nexthdr);
return 0;
}
nexthdr = hp->nexthdr;
len -= hdrlen;
ptr += hdrlen;
if (ptr > skb->len) {
DEBUGP("ipv6_ah: new pointer too large! \n");
break;
}
}
/* AH header not found */
if (temp != MASK_AH)
return 0;
if (len < sizeof(struct ip_auth_hdr)){
*hotdrop = 1;
return 0;
}
ah = skb_header_pointer(skb, ptr, sizeof(_ah), &_ah);
BUG_ON(ah == NULL);
DEBUGP("IPv6 AH LEN %u %u ", hdrlen, ah->hdrlen);
DEBUGP("RES %04X ", ah->reserved);
DEBUGP("SPI %u %08X\n", ntohl(ah->spi), ntohl(ah->spi));
DEBUGP("IPv6 AH spi %02X ",
(spi_match(ahinfo->spis[0], ahinfo->spis[1],
ntohl(ah->spi),
!!(ahinfo->invflags & IP6T_AH_INV_SPI))));
DEBUGP("len %02X %04X %02X ",
ahinfo->hdrlen, hdrlen,
(!ahinfo->hdrlen ||
(ahinfo->hdrlen == hdrlen) ^
!!(ahinfo->invflags & IP6T_AH_INV_LEN)));
DEBUGP("res %02X %04X %02X\n",
ahinfo->hdrres, ah->reserved,
!(ahinfo->hdrres && ah->reserved));
return (ah != NULL)
&&
(spi_match(ahinfo->spis[0], ahinfo->spis[1],
ntohl(ah->spi),
!!(ahinfo->invflags & IP6T_AH_INV_SPI)))
&&
(!ahinfo->hdrlen ||
(ahinfo->hdrlen == hdrlen) ^
!!(ahinfo->invflags & IP6T_AH_INV_LEN))
&&
!(ahinfo->hdrres && ah->reserved);
}
/* Called when user tries to insert an entry of this type. */
static int
checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask)
{
const struct ip6t_ah *ahinfo = matchinfo;
if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_ah))) {
DEBUGP("ip6t_ah: matchsize %u != %u\n",
matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_ah)));
return 0;
}
if (ahinfo->invflags & ~IP6T_AH_INV_MASK) {
DEBUGP("ip6t_ah: unknown flags %X\n", ahinfo->invflags);
return 0;
}
return 1;
}
static struct ip6t_match ah_match = {
.name = "ah",
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&ah_match);
}
static void __exit cleanup(void)
{
ip6t_unregister_match(&ah_match);
}
module_init(init);
module_exit(cleanup);

View File

@@ -0,0 +1,298 @@
/* Kernel module to match Hop-by-Hop and Destination parameters. */
/* (C) 2001-2002 Andras Kis-Szabo <kisza@sch.bme.hu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ipv6.h>
#include <linux/types.h>
#include <net/checksum.h>
#include <net/ipv6.h>
#include <asm/byteorder.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter_ipv6/ip6t_opts.h>
#define HOPBYHOP 0
MODULE_LICENSE("GPL");
#if HOPBYHOP
MODULE_DESCRIPTION("IPv6 HbH match");
#else
MODULE_DESCRIPTION("IPv6 DST match");
#endif
MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
#if 0
#define DEBUGP printk
#else
#define DEBUGP(format, args...)
#endif
/*
* (Type & 0xC0) >> 6
* 0 -> ignorable
* 1 -> must drop the packet
* 2 -> send ICMP PARM PROB regardless and drop packet
* 3 -> Send ICMP if not a multicast address and drop packet
* (Type & 0x20) >> 5
* 0 -> invariant
* 1 -> can change the routing
* (Type & 0x1F) Type
* 0 -> Pad1 (only 1 byte!)
* 1 -> PadN LENGTH info (total length = length + 2)
* C0 | 2 -> JUMBO 4 x x x x ( xxxx > 64k )
* 5 -> RTALERT 2 x x
*/
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
struct ipv6_opt_hdr _optsh, *oh;
const struct ip6t_opts *optinfo = matchinfo;
unsigned int temp;
unsigned int len;
u8 nexthdr;
unsigned int ptr;
unsigned int hdrlen = 0;
unsigned int ret = 0;
u8 _opttype, *tp = NULL;
u8 _optlen, *lp = NULL;
unsigned int optlen;
/* type of the 1st exthdr */
nexthdr = skb->nh.ipv6h->nexthdr;
/* pointer to the 1st exthdr */
ptr = sizeof(struct ipv6hdr);
/* available length */
len = skb->len - ptr;
temp = 0;
while (ip6t_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
DEBUGP("ipv6_opts header iteration \n");
/* Is there enough space for the next ext header? */
if (len < (int)sizeof(struct ipv6_opt_hdr))
return 0;
/* No more exthdr -> evaluate */
if (nexthdr == NEXTHDR_NONE) {
break;
}
/* ESP -> evaluate */
if (nexthdr == NEXTHDR_ESP) {
break;
}
hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
BUG_ON(hp == NULL);
/* Calculate the header length */
if (nexthdr == NEXTHDR_FRAGMENT) {
hdrlen = 8;
} else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hp->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hp);
/* OPTS -> evaluate */
#if HOPBYHOP
if (nexthdr == NEXTHDR_HOP) {
temp |= MASK_HOPOPTS;
#else
if (nexthdr == NEXTHDR_DEST) {
temp |= MASK_DSTOPTS;
#endif
break;
}
/* set the flag */
switch (nexthdr){
case NEXTHDR_HOP:
case NEXTHDR_ROUTING:
case NEXTHDR_FRAGMENT:
case NEXTHDR_AUTH:
case NEXTHDR_DEST:
break;
default:
DEBUGP("ipv6_opts match: unknown nextheader %u\n",nexthdr);
return 0;
break;
}
nexthdr = hp->nexthdr;
len -= hdrlen;
ptr += hdrlen;
if ( ptr > skb->len ) {
DEBUGP("ipv6_opts: new pointer is too large! \n");
break;
}
}
/* OPTIONS header not found */
#if HOPBYHOP
if ( temp != MASK_HOPOPTS ) return 0;
#else
if ( temp != MASK_DSTOPTS ) return 0;
#endif
if (len < (int)sizeof(struct ipv6_opt_hdr)){
*hotdrop = 1;
return 0;
}
if (len < hdrlen){
/* Packet smaller than it's length field */
return 0;
}
oh = skb_header_pointer(skb, ptr, sizeof(_optsh), &_optsh);
BUG_ON(oh == NULL);
DEBUGP("IPv6 OPTS LEN %u %u ", hdrlen, oh->hdrlen);
DEBUGP("len %02X %04X %02X ",
optinfo->hdrlen, hdrlen,
(!(optinfo->flags & IP6T_OPTS_LEN) ||
((optinfo->hdrlen == hdrlen) ^
!!(optinfo->invflags & IP6T_OPTS_INV_LEN))));
ret = (oh != NULL)
&&
(!(optinfo->flags & IP6T_OPTS_LEN) ||
((optinfo->hdrlen == hdrlen) ^
!!(optinfo->invflags & IP6T_OPTS_INV_LEN)));
ptr += 2;
hdrlen -= 2;
if ( !(optinfo->flags & IP6T_OPTS_OPTS) ){
return ret;
} else if (optinfo->flags & IP6T_OPTS_NSTRICT) {
DEBUGP("Not strict - not implemented");
} else {
DEBUGP("Strict ");
DEBUGP("#%d ",optinfo->optsnr);
for(temp=0; temp<optinfo->optsnr; temp++){
/* type field exists ? */
if (hdrlen < 1)
break;
tp = skb_header_pointer(skb, ptr, sizeof(_opttype),
&_opttype);
if (tp == NULL)
break;
/* Type check */
if (*tp != (optinfo->opts[temp] & 0xFF00)>>8){
DEBUGP("Tbad %02X %02X\n",
*tp,
(optinfo->opts[temp] & 0xFF00)>>8);
return 0;
} else {
DEBUGP("Tok ");
}
/* Length check */
if (*tp) {
u16 spec_len;
/* length field exists ? */
if (hdrlen < 2)
break;
lp = skb_header_pointer(skb, ptr + 1,
sizeof(_optlen),
&_optlen);
if (lp == NULL)
break;
spec_len = optinfo->opts[temp] & 0x00FF;
if (spec_len != 0x00FF && spec_len != *lp) {
DEBUGP("Lbad %02X %04X\n", *lp,
spec_len);
return 0;
}
DEBUGP("Lok ");
optlen = *lp + 2;
} else {
DEBUGP("Pad1\n");
optlen = 1;
}
/* Step to the next */
DEBUGP("len%04X \n", optlen);
if ((ptr > skb->len - optlen || hdrlen < optlen) &&
(temp < optinfo->optsnr - 1)) {
DEBUGP("new pointer is too large! \n");
break;
}
ptr += optlen;
hdrlen -= optlen;
}
if (temp == optinfo->optsnr)
return ret;
else return 0;
}
return 0;
}
/* Called when user tries to insert an entry of this type. */
static int
checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask)
{
const struct ip6t_opts *optsinfo = matchinfo;
if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_opts))) {
DEBUGP("ip6t_opts: matchsize %u != %u\n",
matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_opts)));
return 0;
}
if (optsinfo->invflags & ~IP6T_OPTS_INV_MASK) {
DEBUGP("ip6t_opts: unknown flags %X\n",
optsinfo->invflags);
return 0;
}
return 1;
}
static struct ip6t_match opts_match = {
#if HOPBYHOP
.name = "hbh",
#else
.name = "dst",
#endif
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&opts_match);
}
static void __exit cleanup(void)
{
ip6t_unregister_match(&opts_match);
}
module_init(init);
module_exit(cleanup);

View File

@@ -0,0 +1,181 @@
/* Kernel module to match ESP parameters. */
/* (C) 2001-2002 Andras Kis-Szabo <kisza@sch.bme.hu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ipv6.h>
#include <linux/types.h>
#include <net/checksum.h>
#include <net/ipv6.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter_ipv6/ip6t_esp.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("IPv6 ESP match");
MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
#if 0
#define DEBUGP printk
#else
#define DEBUGP(format, args...)
#endif
/* Returns 1 if the spi is matched by the range, 0 otherwise */
static inline int
spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, int invert)
{
int r=0;
DEBUGP("esp spi_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
min,spi,max);
r=(spi >= min && spi <= max) ^ invert;
DEBUGP(" result %s\n",r? "PASS\n" : "FAILED\n");
return r;
}
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
struct ip_esp_hdr _esp, *eh = NULL;
const struct ip6t_esp *espinfo = matchinfo;
unsigned int temp;
int len;
u8 nexthdr;
unsigned int ptr;
/* Make sure this isn't an evil packet */
/*DEBUGP("ipv6_esp entered \n");*/
/* type of the 1st exthdr */
nexthdr = skb->nh.ipv6h->nexthdr;
/* pointer to the 1st exthdr */
ptr = sizeof(struct ipv6hdr);
/* available length */
len = skb->len - ptr;
temp = 0;
while (ip6t_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
int hdrlen;
DEBUGP("ipv6_esp header iteration \n");
/* Is there enough space for the next ext header? */
if (len < sizeof(struct ipv6_opt_hdr))
return 0;
/* No more exthdr -> evaluate */
if (nexthdr == NEXTHDR_NONE)
break;
/* ESP -> evaluate */
if (nexthdr == NEXTHDR_ESP) {
temp |= MASK_ESP;
break;
}
hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
BUG_ON(hp == NULL);
/* Calculate the header length */
if (nexthdr == NEXTHDR_FRAGMENT)
hdrlen = 8;
else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hp->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hp);
/* set the flag */
switch (nexthdr) {
case NEXTHDR_HOP:
case NEXTHDR_ROUTING:
case NEXTHDR_FRAGMENT:
case NEXTHDR_AUTH:
case NEXTHDR_DEST:
break;
default:
DEBUGP("ipv6_esp match: unknown nextheader %u\n",nexthdr);
return 0;
}
nexthdr = hp->nexthdr;
len -= hdrlen;
ptr += hdrlen;
if (ptr > skb->len) {
DEBUGP("ipv6_esp: new pointer too large! \n");
break;
}
}
/* ESP header not found */
if (temp != MASK_ESP)
return 0;
if (len < sizeof(struct ip_esp_hdr)) {
*hotdrop = 1;
return 0;
}
eh = skb_header_pointer(skb, ptr, sizeof(_esp), &_esp);
BUG_ON(eh == NULL);
DEBUGP("IPv6 ESP SPI %u %08X\n", ntohl(eh->spi), ntohl(eh->spi));
return (eh != NULL)
&& spi_match(espinfo->spis[0], espinfo->spis[1],
ntohl(eh->spi),
!!(espinfo->invflags & IP6T_ESP_INV_SPI));
}
/* Called when user tries to insert an entry of this type. */
static int
checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask)
{
const struct ip6t_esp *espinfo = matchinfo;
if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_esp))) {
DEBUGP("ip6t_esp: matchsize %u != %u\n",
matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_esp)));
return 0;
}
if (espinfo->invflags & ~IP6T_ESP_INV_MASK) {
DEBUGP("ip6t_esp: unknown flags %X\n",
espinfo->invflags);
return 0;
}
return 1;
}
static struct ip6t_match esp_match = {
.name = "esp",
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&esp_match);
}
static void __exit cleanup(void)
{
ip6t_unregister_match(&esp_match);
}
module_init(init);
module_exit(cleanup);

View File

@@ -0,0 +1,101 @@
/* Kernel module to match EUI64 address parameters. */
/* (C) 2001-2002 Andras Kis-Szabo <kisza@sch.bme.hu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ipv6.h>
#include <linux/if_ether.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
MODULE_DESCRIPTION("IPv6 EUI64 address checking match");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
unsigned char eui64[8];
int i=0;
if ( !(skb->mac.raw >= skb->head
&& (skb->mac.raw + ETH_HLEN) <= skb->data)
&& offset != 0) {
*hotdrop = 1;
return 0;
}
memset(eui64, 0, sizeof(eui64));
if (eth_hdr(skb)->h_proto == ntohs(ETH_P_IPV6)) {
if (skb->nh.ipv6h->version == 0x6) {
memcpy(eui64, eth_hdr(skb)->h_source, 3);
memcpy(eui64 + 5, eth_hdr(skb)->h_source + 3, 3);
eui64[3]=0xff;
eui64[4]=0xfe;
eui64[0] |= 0x02;
i=0;
while ((skb->nh.ipv6h->saddr.s6_addr[8+i] ==
eui64[i]) && (i<8)) i++;
if ( i == 8 )
return 1;
}
}
return 0;
}
static int
ip6t_eui64_checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
if (hook_mask
& ~((1 << NF_IP6_PRE_ROUTING) | (1 << NF_IP6_LOCAL_IN) |
(1 << NF_IP6_FORWARD))) {
printk("ip6t_eui64: only valid for PRE_ROUTING, LOCAL_IN or FORWARD.\n");
return 0;
}
if (matchsize != IP6T_ALIGN(sizeof(int)))
return 0;
return 1;
}
static struct ip6t_match eui64_match = {
.name = "eui64",
.match = &match,
.checkentry = &ip6t_eui64_checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&eui64_match);
}
static void __exit fini(void)
{
ip6t_unregister_match(&eui64_match);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,229 @@
/* Kernel module to match FRAG parameters. */
/* (C) 2001-2002 Andras Kis-Szabo <kisza@sch.bme.hu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ipv6.h>
#include <linux/types.h>
#include <net/checksum.h>
#include <net/ipv6.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter_ipv6/ip6t_frag.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("IPv6 FRAG match");
MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
#if 0
#define DEBUGP printk
#else
#define DEBUGP(format, args...)
#endif
/* Returns 1 if the id is matched by the range, 0 otherwise */
static inline int
id_match(u_int32_t min, u_int32_t max, u_int32_t id, int invert)
{
int r=0;
DEBUGP("frag id_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
min,id,max);
r=(id >= min && id <= max) ^ invert;
DEBUGP(" result %s\n",r? "PASS" : "FAILED");
return r;
}
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
struct frag_hdr _frag, *fh = NULL;
const struct ip6t_frag *fraginfo = matchinfo;
unsigned int temp;
int len;
u8 nexthdr;
unsigned int ptr;
unsigned int hdrlen = 0;
/* type of the 1st exthdr */
nexthdr = skb->nh.ipv6h->nexthdr;
/* pointer to the 1st exthdr */
ptr = sizeof(struct ipv6hdr);
/* available length */
len = skb->len - ptr;
temp = 0;
while (ip6t_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
DEBUGP("ipv6_frag header iteration \n");
/* Is there enough space for the next ext header? */
if (len < (int)sizeof(struct ipv6_opt_hdr))
return 0;
/* No more exthdr -> evaluate */
if (nexthdr == NEXTHDR_NONE) {
break;
}
/* ESP -> evaluate */
if (nexthdr == NEXTHDR_ESP) {
break;
}
hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
BUG_ON(hp == NULL);
/* Calculate the header length */
if (nexthdr == NEXTHDR_FRAGMENT) {
hdrlen = 8;
} else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hp->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hp);
/* FRAG -> evaluate */
if (nexthdr == NEXTHDR_FRAGMENT) {
temp |= MASK_FRAGMENT;
break;
}
/* set the flag */
switch (nexthdr){
case NEXTHDR_HOP:
case NEXTHDR_ROUTING:
case NEXTHDR_FRAGMENT:
case NEXTHDR_AUTH:
case NEXTHDR_DEST:
break;
default:
DEBUGP("ipv6_frag match: unknown nextheader %u\n",nexthdr);
return 0;
break;
}
nexthdr = hp->nexthdr;
len -= hdrlen;
ptr += hdrlen;
if ( ptr > skb->len ) {
DEBUGP("ipv6_frag: new pointer too large! \n");
break;
}
}
/* FRAG header not found */
if ( temp != MASK_FRAGMENT ) return 0;
if (len < sizeof(struct frag_hdr)){
*hotdrop = 1;
return 0;
}
fh = skb_header_pointer(skb, ptr, sizeof(_frag), &_frag);
BUG_ON(fh == NULL);
DEBUGP("INFO %04X ", fh->frag_off);
DEBUGP("OFFSET %04X ", ntohs(fh->frag_off) & ~0x7);
DEBUGP("RES %02X %04X", fh->reserved, ntohs(fh->frag_off) & 0x6);
DEBUGP("MF %04X ", fh->frag_off & htons(IP6_MF));
DEBUGP("ID %u %08X\n", ntohl(fh->identification),
ntohl(fh->identification));
DEBUGP("IPv6 FRAG id %02X ",
(id_match(fraginfo->ids[0], fraginfo->ids[1],
ntohl(fh->identification),
!!(fraginfo->invflags & IP6T_FRAG_INV_IDS))));
DEBUGP("res %02X %02X%04X %02X ",
(fraginfo->flags & IP6T_FRAG_RES), fh->reserved,
ntohs(fh->frag_off) & 0x6,
!((fraginfo->flags & IP6T_FRAG_RES)
&& (fh->reserved || (ntohs(fh->frag_off) & 0x06))));
DEBUGP("first %02X %02X %02X ",
(fraginfo->flags & IP6T_FRAG_FST),
ntohs(fh->frag_off) & ~0x7,
!((fraginfo->flags & IP6T_FRAG_FST)
&& (ntohs(fh->frag_off) & ~0x7)));
DEBUGP("mf %02X %02X %02X ",
(fraginfo->flags & IP6T_FRAG_MF),
ntohs(fh->frag_off) & IP6_MF,
!((fraginfo->flags & IP6T_FRAG_MF)
&& !((ntohs(fh->frag_off) & IP6_MF))));
DEBUGP("last %02X %02X %02X\n",
(fraginfo->flags & IP6T_FRAG_NMF),
ntohs(fh->frag_off) & IP6_MF,
!((fraginfo->flags & IP6T_FRAG_NMF)
&& (ntohs(fh->frag_off) & IP6_MF)));
return (fh != NULL)
&&
(id_match(fraginfo->ids[0], fraginfo->ids[1],
ntohl(fh->identification),
!!(fraginfo->invflags & IP6T_FRAG_INV_IDS)))
&&
!((fraginfo->flags & IP6T_FRAG_RES)
&& (fh->reserved || (ntohs(fh->frag_off) & 0x6)))
&&
!((fraginfo->flags & IP6T_FRAG_FST)
&& (ntohs(fh->frag_off) & ~0x7))
&&
!((fraginfo->flags & IP6T_FRAG_MF)
&& !(ntohs(fh->frag_off) & IP6_MF))
&&
!((fraginfo->flags & IP6T_FRAG_NMF)
&& (ntohs(fh->frag_off) & IP6_MF));
}
/* Called when user tries to insert an entry of this type. */
static int
checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask)
{
const struct ip6t_frag *fraginfo = matchinfo;
if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_frag))) {
DEBUGP("ip6t_frag: matchsize %u != %u\n",
matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_frag)));
return 0;
}
if (fraginfo->invflags & ~IP6T_FRAG_INV_MASK) {
DEBUGP("ip6t_frag: unknown flags %X\n",
fraginfo->invflags);
return 0;
}
return 1;
}
static struct ip6t_match frag_match = {
.name = "frag",
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&frag_match);
}
static void __exit cleanup(void)
{
ip6t_unregister_match(&frag_match);
}
module_init(init);
module_exit(cleanup);

View File

@@ -0,0 +1,298 @@
/* Kernel module to match Hop-by-Hop and Destination parameters. */
/* (C) 2001-2002 Andras Kis-Szabo <kisza@sch.bme.hu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ipv6.h>
#include <linux/types.h>
#include <net/checksum.h>
#include <net/ipv6.h>
#include <asm/byteorder.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter_ipv6/ip6t_opts.h>
#define HOPBYHOP 1
MODULE_LICENSE("GPL");
#if HOPBYHOP
MODULE_DESCRIPTION("IPv6 HbH match");
#else
MODULE_DESCRIPTION("IPv6 DST match");
#endif
MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
#if 0
#define DEBUGP printk
#else
#define DEBUGP(format, args...)
#endif
/*
* (Type & 0xC0) >> 6
* 0 -> ignorable
* 1 -> must drop the packet
* 2 -> send ICMP PARM PROB regardless and drop packet
* 3 -> Send ICMP if not a multicast address and drop packet
* (Type & 0x20) >> 5
* 0 -> invariant
* 1 -> can change the routing
* (Type & 0x1F) Type
* 0 -> Pad1 (only 1 byte!)
* 1 -> PadN LENGTH info (total length = length + 2)
* C0 | 2 -> JUMBO 4 x x x x ( xxxx > 64k )
* 5 -> RTALERT 2 x x
*/
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
struct ipv6_opt_hdr _optsh, *oh;
const struct ip6t_opts *optinfo = matchinfo;
unsigned int temp;
unsigned int len;
u8 nexthdr;
unsigned int ptr;
unsigned int hdrlen = 0;
unsigned int ret = 0;
u8 _opttype, *tp = NULL;
u8 _optlen, *lp = NULL;
unsigned int optlen;
/* type of the 1st exthdr */
nexthdr = skb->nh.ipv6h->nexthdr;
/* pointer to the 1st exthdr */
ptr = sizeof(struct ipv6hdr);
/* available length */
len = skb->len - ptr;
temp = 0;
while (ip6t_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
DEBUGP("ipv6_opts header iteration \n");
/* Is there enough space for the next ext header? */
if (len < (int)sizeof(struct ipv6_opt_hdr))
return 0;
/* No more exthdr -> evaluate */
if (nexthdr == NEXTHDR_NONE) {
break;
}
/* ESP -> evaluate */
if (nexthdr == NEXTHDR_ESP) {
break;
}
hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
BUG_ON(hp == NULL);
/* Calculate the header length */
if (nexthdr == NEXTHDR_FRAGMENT) {
hdrlen = 8;
} else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hp->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hp);
/* OPTS -> evaluate */
#if HOPBYHOP
if (nexthdr == NEXTHDR_HOP) {
temp |= MASK_HOPOPTS;
#else
if (nexthdr == NEXTHDR_DEST) {
temp |= MASK_DSTOPTS;
#endif
break;
}
/* set the flag */
switch (nexthdr){
case NEXTHDR_HOP:
case NEXTHDR_ROUTING:
case NEXTHDR_FRAGMENT:
case NEXTHDR_AUTH:
case NEXTHDR_DEST:
break;
default:
DEBUGP("ipv6_opts match: unknown nextheader %u\n",nexthdr);
return 0;
break;
}
nexthdr = hp->nexthdr;
len -= hdrlen;
ptr += hdrlen;
if ( ptr > skb->len ) {
DEBUGP("ipv6_opts: new pointer is too large! \n");
break;
}
}
/* OPTIONS header not found */
#if HOPBYHOP
if ( temp != MASK_HOPOPTS ) return 0;
#else
if ( temp != MASK_DSTOPTS ) return 0;
#endif
if (len < (int)sizeof(struct ipv6_opt_hdr)){
*hotdrop = 1;
return 0;
}
if (len < hdrlen){
/* Packet smaller than it's length field */
return 0;
}
oh = skb_header_pointer(skb, ptr, sizeof(_optsh), &_optsh);
BUG_ON(oh == NULL);
DEBUGP("IPv6 OPTS LEN %u %u ", hdrlen, oh->hdrlen);
DEBUGP("len %02X %04X %02X ",
optinfo->hdrlen, hdrlen,
(!(optinfo->flags & IP6T_OPTS_LEN) ||
((optinfo->hdrlen == hdrlen) ^
!!(optinfo->invflags & IP6T_OPTS_INV_LEN))));
ret = (oh != NULL)
&&
(!(optinfo->flags & IP6T_OPTS_LEN) ||
((optinfo->hdrlen == hdrlen) ^
!!(optinfo->invflags & IP6T_OPTS_INV_LEN)));
ptr += 2;
hdrlen -= 2;
if ( !(optinfo->flags & IP6T_OPTS_OPTS) ){
return ret;
} else if (optinfo->flags & IP6T_OPTS_NSTRICT) {
DEBUGP("Not strict - not implemented");
} else {
DEBUGP("Strict ");
DEBUGP("#%d ",optinfo->optsnr);
for(temp=0; temp<optinfo->optsnr; temp++){
/* type field exists ? */
if (hdrlen < 1)
break;
tp = skb_header_pointer(skb, ptr, sizeof(_opttype),
&_opttype);
if (tp == NULL)
break;
/* Type check */
if (*tp != (optinfo->opts[temp] & 0xFF00)>>8){
DEBUGP("Tbad %02X %02X\n",
*tp,
(optinfo->opts[temp] & 0xFF00)>>8);
return 0;
} else {
DEBUGP("Tok ");
}
/* Length check */
if (*tp) {
u16 spec_len;
/* length field exists ? */
if (hdrlen < 2)
break;
lp = skb_header_pointer(skb, ptr + 1,
sizeof(_optlen),
&_optlen);
if (lp == NULL)
break;
spec_len = optinfo->opts[temp] & 0x00FF;
if (spec_len != 0x00FF && spec_len != *lp) {
DEBUGP("Lbad %02X %04X\n", *lp,
spec_len);
return 0;
}
DEBUGP("Lok ");
optlen = *lp + 2;
} else {
DEBUGP("Pad1\n");
optlen = 1;
}
/* Step to the next */
DEBUGP("len%04X \n", optlen);
if ((ptr > skb->len - optlen || hdrlen < optlen) &&
(temp < optinfo->optsnr - 1)) {
DEBUGP("new pointer is too large! \n");
break;
}
ptr += optlen;
hdrlen -= optlen;
}
if (temp == optinfo->optsnr)
return ret;
else return 0;
}
return 0;
}
/* Called when user tries to insert an entry of this type. */
static int
checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask)
{
const struct ip6t_opts *optsinfo = matchinfo;
if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_opts))) {
DEBUGP("ip6t_opts: matchsize %u != %u\n",
matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_opts)));
return 0;
}
if (optsinfo->invflags & ~IP6T_OPTS_INV_MASK) {
DEBUGP("ip6t_opts: unknown flags %X\n",
optsinfo->invflags);
return 0;
}
return 1;
}
static struct ip6t_match opts_match = {
#if HOPBYHOP
.name = "hbh",
#else
.name = "dst",
#endif
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&opts_match);
}
static void __exit cleanup(void)
{
ip6t_unregister_match(&opts_match);
}
module_init(init);
module_exit(cleanup);

View File

@@ -0,0 +1,80 @@
/* Hop Limit matching module */
/* (C) 2001-2002 Maciej Soltysiak <solt@dns.toxicfilms.tv>
* Based on HW's ttl module
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netfilter_ipv6/ip6t_hl.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
MODULE_AUTHOR("Maciej Soltysiak <solt@dns.toxicfilms.tv>");
MODULE_DESCRIPTION("IP tables Hop Limit matching module");
MODULE_LICENSE("GPL");
static int match(const struct sk_buff *skb, const struct net_device *in,
const struct net_device *out, const void *matchinfo,
int offset, unsigned int protoff,
int *hotdrop)
{
const struct ip6t_hl_info *info = matchinfo;
const struct ipv6hdr *ip6h = skb->nh.ipv6h;
switch (info->mode) {
case IP6T_HL_EQ:
return (ip6h->hop_limit == info->hop_limit);
break;
case IP6T_HL_NE:
return (!(ip6h->hop_limit == info->hop_limit));
break;
case IP6T_HL_LT:
return (ip6h->hop_limit < info->hop_limit);
break;
case IP6T_HL_GT:
return (ip6h->hop_limit > info->hop_limit);
break;
default:
printk(KERN_WARNING "ip6t_hl: unknown mode %d\n",
info->mode);
return 0;
}
return 0;
}
static int checkentry(const char *tablename, const struct ip6t_ip6 *ip,
void *matchinfo, unsigned int matchsize,
unsigned int hook_mask)
{
if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_hl_info)))
return 0;
return 1;
}
static struct ip6t_match hl_match = {
.name = "hl",
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&hl_match);
}
static void __exit fini(void)
{
ip6t_unregister_match(&hl_match);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,167 @@
/* ipv6header match - matches IPv6 packets based
on whether they contain certain headers */
/* Original idea: Brad Chapman
* Rewritten by: Andras Kis-Szabo <kisza@sch.bme.hu> */
/* (C) 2001-2002 Andras Kis-Szabo <kisza@sch.bme.hu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ipv6.h>
#include <linux/types.h>
#include <net/checksum.h>
#include <net/ipv6.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter_ipv6/ip6t_ipv6header.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("IPv6 headers match");
MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
static int
ipv6header_match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
const struct ip6t_ipv6header_info *info = matchinfo;
unsigned int temp;
int len;
u8 nexthdr;
unsigned int ptr;
/* Make sure this isn't an evil packet */
/* type of the 1st exthdr */
nexthdr = skb->nh.ipv6h->nexthdr;
/* pointer to the 1st exthdr */
ptr = sizeof(struct ipv6hdr);
/* available length */
len = skb->len - ptr;
temp = 0;
while (ip6t_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
int hdrlen;
/* Is there enough space for the next ext header? */
if (len < (int)sizeof(struct ipv6_opt_hdr))
return 0;
/* No more exthdr -> evaluate */
if (nexthdr == NEXTHDR_NONE) {
temp |= MASK_NONE;
break;
}
/* ESP -> evaluate */
if (nexthdr == NEXTHDR_ESP) {
temp |= MASK_ESP;
break;
}
hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
BUG_ON(hp == NULL);
/* Calculate the header length */
if (nexthdr == NEXTHDR_FRAGMENT) {
hdrlen = 8;
} else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hp->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hp);
/* set the flag */
switch (nexthdr){
case NEXTHDR_HOP:
temp |= MASK_HOPOPTS;
break;
case NEXTHDR_ROUTING:
temp |= MASK_ROUTING;
break;
case NEXTHDR_FRAGMENT:
temp |= MASK_FRAGMENT;
break;
case NEXTHDR_AUTH:
temp |= MASK_AH;
break;
case NEXTHDR_DEST:
temp |= MASK_DSTOPTS;
break;
default:
return 0;
break;
}
nexthdr = hp->nexthdr;
len -= hdrlen;
ptr += hdrlen;
if (ptr > skb->len)
break;
}
if ( (nexthdr != NEXTHDR_NONE ) && (nexthdr != NEXTHDR_ESP) )
temp |= MASK_PROTO;
if (info->modeflag)
return !((temp ^ info->matchflags ^ info->invflags)
& info->matchflags);
else {
if (info->invflags)
return temp != info->matchflags;
else
return temp == info->matchflags;
}
}
static int
ipv6header_checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
const struct ip6t_ipv6header_info *info = matchinfo;
/* Check for obvious errors */
/* This match is valid in all hooks! */
if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_ipv6header_info)))
return 0;
/* invflags is 0 or 0xff in hard mode */
if ((!info->modeflag) && info->invflags != 0x00
&& info->invflags != 0xFF)
return 0;
return 1;
}
static struct ip6t_match ip6t_ipv6header_match = {
.name = "ipv6header",
.match = &ipv6header_match,
.checkentry = &ipv6header_checkentry,
.destroy = NULL,
.me = THIS_MODULE,
};
static int __init ipv6header_init(void)
{
return ip6t_register_match(&ip6t_ipv6header_match);
}
static void __exit ipv6header_exit(void)
{
ip6t_unregister_match(&ip6t_ipv6header_match);
}
module_init(ipv6header_init);
module_exit(ipv6header_exit);

View File

@@ -0,0 +1,66 @@
/* Length Match - IPv6 Port */
/* (C) 1999-2001 James Morris <jmorros@intercode.com.au>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netfilter_ipv6/ip6t_length.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");
MODULE_DESCRIPTION("IPv6 packet length match");
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
const struct ip6t_length_info *info = matchinfo;
u_int16_t pktlen = ntohs(skb->nh.ipv6h->payload_len) + sizeof(struct ipv6hdr);
return (pktlen >= info->min && pktlen <= info->max) ^ info->invert;
}
static int
checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_length_info)))
return 0;
return 1;
}
static struct ip6t_match length_match = {
.name = "length",
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&length_match);
}
static void __exit fini(void)
{
ip6t_unregister_match(&length_match);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,147 @@
/* Kernel module to control the rate
*
* 2 September 1999: Changed from the target RATE to the match
* `limit', removed logging. Did I mention that
* Alexey is a fucking genius?
* Rusty Russell (rusty@rustcorp.com.au). */
/* (C) 1999 J<>r<EFBFBD>me de Vivie <devivie@info.enserb.u-bordeaux.fr>
* (C) 1999 Herv<72> Eychenne <eychenne@info.enserb.u-bordeaux.fr>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter_ipv6/ip6t_limit.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Herve Eychenne <rv@wallfire.org>");
MODULE_DESCRIPTION("rate limiting within ip6tables");
/* The algorithm used is the Simple Token Bucket Filter (TBF)
* see net/sched/sch_tbf.c in the linux source tree
*/
static DEFINE_SPINLOCK(limit_lock);
/* Rusty: This is my (non-mathematically-inclined) understanding of
this algorithm. The `average rate' in jiffies becomes your initial
amount of credit `credit' and the most credit you can ever have
`credit_cap'. The `peak rate' becomes the cost of passing the
test, `cost'.
`prev' tracks the last packet hit: you gain one credit per jiffy.
If you get credit balance more than this, the extra credit is
discarded. Every time the match passes, you lose `cost' credits;
if you don't have that many, the test fails.
See Alexey's formal explanation in net/sched/sch_tbf.c.
To avoid underflow, we multiply by 128 (ie. you get 128 credits per
jiffy). Hence a cost of 2^32-1, means one pass per 32768 seconds
at 1024HZ (or one every 9 hours). A cost of 1 means 12800 passes
per second at 100HZ. */
#define CREDITS_PER_JIFFY 128
static int
ip6t_limit_match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
struct ip6t_rateinfo *r = ((struct ip6t_rateinfo *)matchinfo)->master;
unsigned long now = jiffies;
spin_lock_bh(&limit_lock);
r->credit += (now - xchg(&r->prev, now)) * CREDITS_PER_JIFFY;
if (r->credit > r->credit_cap)
r->credit = r->credit_cap;
if (r->credit >= r->cost) {
/* We're not limited. */
r->credit -= r->cost;
spin_unlock_bh(&limit_lock);
return 1;
}
spin_unlock_bh(&limit_lock);
return 0;
}
/* Precision saver. */
static u_int32_t
user2credits(u_int32_t user)
{
/* If multiplying would overflow... */
if (user > 0xFFFFFFFF / (HZ*CREDITS_PER_JIFFY))
/* Divide first. */
return (user / IP6T_LIMIT_SCALE) * HZ * CREDITS_PER_JIFFY;
return (user * HZ * CREDITS_PER_JIFFY) / IP6T_LIMIT_SCALE;
}
static int
ip6t_limit_checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
struct ip6t_rateinfo *r = matchinfo;
if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_rateinfo)))
return 0;
/* Check for overflow. */
if (r->burst == 0
|| user2credits(r->avg * r->burst) < user2credits(r->avg)) {
printk("Call rusty: overflow in ip6t_limit: %u/%u\n",
r->avg, r->burst);
return 0;
}
/* User avg in seconds * IP6T_LIMIT_SCALE: convert to jiffies *
128. */
r->prev = jiffies;
r->credit = user2credits(r->avg * r->burst); /* Credits full. */
r->credit_cap = user2credits(r->avg * r->burst); /* Credits full. */
r->cost = user2credits(r->avg);
/* For SMP, we only want to use one set of counters. */
r->master = r;
return 1;
}
static struct ip6t_match ip6t_limit_reg = {
.name = "limit",
.match = ip6t_limit_match,
.checkentry = ip6t_limit_checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
if (ip6t_register_match(&ip6t_limit_reg))
return -EINVAL;
return 0;
}
static void __exit fini(void)
{
ip6t_unregister_match(&ip6t_limit_reg);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,80 @@
/* Kernel module to match MAC address parameters. */
/* (C) 1999-2001 Paul `Rusty' Russell
* (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/if_ether.h>
#include <linux/netfilter_ipv6/ip6t_mac.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MAC address matching module for IPv6");
MODULE_AUTHOR("Netfilter Core Teaam <coreteam@netfilter.org>");
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
const struct ip6t_mac_info *info = matchinfo;
/* Is mac pointer valid? */
return (skb->mac.raw >= skb->head
&& (skb->mac.raw + ETH_HLEN) <= skb->data
/* If so, compare... */
&& ((memcmp(eth_hdr(skb)->h_source, info->srcaddr, ETH_ALEN)
== 0) ^ info->invert));
}
static int
ip6t_mac_checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
if (hook_mask
& ~((1 << NF_IP6_PRE_ROUTING) | (1 << NF_IP6_LOCAL_IN)
| (1 << NF_IP6_FORWARD))) {
printk("ip6t_mac: only valid for PRE_ROUTING, LOCAL_IN or"
" FORWARD\n");
return 0;
}
if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_mac_info)))
return 0;
return 1;
}
static struct ip6t_match mac_match = {
.name = "mac",
.match = &match,
.checkentry = &ip6t_mac_checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&mac_match);
}
static void __exit fini(void)
{
ip6t_unregister_match(&mac_match);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,66 @@
/* Kernel module to match NFMARK values. */
/* (C) 1999-2001 Marc Boucher <marc@mbsi.ca>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netfilter_ipv6/ip6t_mark.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Netfilter Core Team <coreteam@netfilter.org>");
MODULE_DESCRIPTION("ip6tables mark match");
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
const struct ip6t_mark_info *info = matchinfo;
return ((skb->nfmark & info->mask) == info->mark) ^ info->invert;
}
static int
checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_mark_info)))
return 0;
return 1;
}
static struct ip6t_match mark_match = {
.name = "mark",
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&mark_match);
}
static void __exit fini(void)
{
ip6t_unregister_match(&mark_match);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,125 @@
/* Kernel module to match one of a list of TCP/UDP ports: ports are in
the same place so we can treat them as equal. */
/* (C) 1999-2001 Paul `Rusty' Russell
* (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/udp.h>
#include <linux/skbuff.h>
#include <linux/in.h>
#include <linux/netfilter_ipv6/ip6t_multiport.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Netfilter Core Team <coreteam@netfilter.org>");
MODULE_DESCRIPTION("ip6tables match for multiple ports");
#if 0
#define duprintf(format, args...) printk(format , ## args)
#else
#define duprintf(format, args...)
#endif
/* Returns 1 if the port is matched by the test, 0 otherwise. */
static inline int
ports_match(const u_int16_t *portlist, enum ip6t_multiport_flags flags,
u_int8_t count, u_int16_t src, u_int16_t dst)
{
unsigned int i;
for (i=0; i<count; i++) {
if (flags != IP6T_MULTIPORT_DESTINATION
&& portlist[i] == src)
return 1;
if (flags != IP6T_MULTIPORT_SOURCE
&& portlist[i] == dst)
return 1;
}
return 0;
}
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
u16 _ports[2], *pptr;
const struct ip6t_multiport *multiinfo = matchinfo;
/* Must not be a fragment. */
if (offset)
return 0;
/* Must be big enough to read ports (both UDP and TCP have
them at the start). */
pptr = skb_header_pointer(skb, protoff, sizeof(_ports), &_ports[0]);
if (pptr == NULL) {
/* We've been asked to examine this packet, and we
* can't. Hence, no choice but to drop.
*/
duprintf("ip6t_multiport:"
" Dropping evil offset=0 tinygram.\n");
*hotdrop = 1;
return 0;
}
return ports_match(multiinfo->ports,
multiinfo->flags, multiinfo->count,
ntohs(pptr[0]), ntohs(pptr[1]));
}
/* Called when user tries to insert an entry of this type. */
static int
checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
const struct ip6t_multiport *multiinfo = matchinfo;
if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_multiport)))
return 0;
/* Must specify proto == TCP/UDP, no unknown flags or bad count */
return (ip->proto == IPPROTO_TCP || ip->proto == IPPROTO_UDP)
&& !(ip->invflags & IP6T_INV_PROTO)
&& matchsize == IP6T_ALIGN(sizeof(struct ip6t_multiport))
&& (multiinfo->flags == IP6T_MULTIPORT_SOURCE
|| multiinfo->flags == IP6T_MULTIPORT_DESTINATION
|| multiinfo->flags == IP6T_MULTIPORT_EITHER)
&& multiinfo->count <= IP6T_MULTI_PORTS;
}
static struct ip6t_match multiport_match = {
.name = "multiport",
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&multiport_match);
}
static void __exit fini(void)
{
ip6t_unregister_match(&multiport_match);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,174 @@
/* Kernel module to match various things tied to sockets associated with
locally generated outgoing packets. */
/* (C) 2000-2001 Marc Boucher <marc@mbsi.ca>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/file.h>
#include <net/sock.h>
#include <linux/netfilter_ipv6/ip6t_owner.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
MODULE_DESCRIPTION("IP6 tables owner matching module");
MODULE_LICENSE("GPL");
static int
match_pid(const struct sk_buff *skb, pid_t pid)
{
struct task_struct *p;
struct files_struct *files;
int i;
read_lock(&tasklist_lock);
p = find_task_by_pid(pid);
if (!p)
goto out;
task_lock(p);
files = p->files;
if(files) {
spin_lock(&files->file_lock);
for (i=0; i < files->max_fds; i++) {
if (fcheck_files(files, i) == skb->sk->sk_socket->file) {
spin_unlock(&files->file_lock);
task_unlock(p);
read_unlock(&tasklist_lock);
return 1;
}
}
spin_unlock(&files->file_lock);
}
task_unlock(p);
out:
read_unlock(&tasklist_lock);
return 0;
}
static int
match_sid(const struct sk_buff *skb, pid_t sid)
{
struct task_struct *g, *p;
struct file *file = skb->sk->sk_socket->file;
int i, found=0;
read_lock(&tasklist_lock);
do_each_thread(g, p) {
struct files_struct *files;
if (p->signal->session != sid)
continue;
task_lock(p);
files = p->files;
if (files) {
spin_lock(&files->file_lock);
for (i=0; i < files->max_fds; i++) {
if (fcheck_files(files, i) == file) {
found = 1;
break;
}
}
spin_unlock(&files->file_lock);
}
task_unlock(p);
if (found)
goto out;
} while_each_thread(g, p);
out:
read_unlock(&tasklist_lock);
return found;
}
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
const struct ip6t_owner_info *info = matchinfo;
if (!skb->sk || !skb->sk->sk_socket || !skb->sk->sk_socket->file)
return 0;
if(info->match & IP6T_OWNER_UID) {
if((skb->sk->sk_socket->file->f_uid != info->uid) ^
!!(info->invert & IP6T_OWNER_UID))
return 0;
}
if(info->match & IP6T_OWNER_GID) {
if((skb->sk->sk_socket->file->f_gid != info->gid) ^
!!(info->invert & IP6T_OWNER_GID))
return 0;
}
if(info->match & IP6T_OWNER_PID) {
if (!match_pid(skb, info->pid) ^
!!(info->invert & IP6T_OWNER_PID))
return 0;
}
if(info->match & IP6T_OWNER_SID) {
if (!match_sid(skb, info->sid) ^
!!(info->invert & IP6T_OWNER_SID))
return 0;
}
return 1;
}
static int
checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
if (hook_mask
& ~((1 << NF_IP6_LOCAL_OUT) | (1 << NF_IP6_POST_ROUTING))) {
printk("ip6t_owner: only valid for LOCAL_OUT or POST_ROUTING.\n");
return 0;
}
if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_owner_info)))
return 0;
#ifdef CONFIG_SMP
/* files->file_lock can not be used in a BH */
if (((struct ip6t_owner_info *)matchinfo)->match
& (IP6T_OWNER_PID|IP6T_OWNER_SID)) {
printk("ip6t_owner: pid and sid matching is broken on SMP.\n");
return 0;
}
#endif
return 1;
}
static struct ip6t_match owner_match = {
.name = "owner",
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&owner_match);
}
static void __exit fini(void)
{
ip6t_unregister_match(&owner_match);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,135 @@
/* Kernel module to match the bridge port in and
* out device for IP packets coming into contact with a bridge. */
/* (C) 2001-2003 Bart De Schuymer <bdschuym@pandora.be>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netfilter_ipv6/ip6t_physdev.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter_bridge.h>
#define MATCH 1
#define NOMATCH 0
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Bart De Schuymer <bdschuym@pandora.be>");
MODULE_DESCRIPTION("iptables bridge physical device match module");
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
int i;
static const char nulldevname[IFNAMSIZ];
const struct ip6t_physdev_info *info = matchinfo;
unsigned int ret;
const char *indev, *outdev;
struct nf_bridge_info *nf_bridge;
/* Not a bridged IP packet or no info available yet:
* LOCAL_OUT/mangle and LOCAL_OUT/nat don't know if
* the destination device will be a bridge. */
if (!(nf_bridge = skb->nf_bridge)) {
/* Return MATCH if the invert flags of the used options are on */
if ((info->bitmask & IP6T_PHYSDEV_OP_BRIDGED) &&
!(info->invert & IP6T_PHYSDEV_OP_BRIDGED))
return NOMATCH;
if ((info->bitmask & IP6T_PHYSDEV_OP_ISIN) &&
!(info->invert & IP6T_PHYSDEV_OP_ISIN))
return NOMATCH;
if ((info->bitmask & IP6T_PHYSDEV_OP_ISOUT) &&
!(info->invert & IP6T_PHYSDEV_OP_ISOUT))
return NOMATCH;
if ((info->bitmask & IP6T_PHYSDEV_OP_IN) &&
!(info->invert & IP6T_PHYSDEV_OP_IN))
return NOMATCH;
if ((info->bitmask & IP6T_PHYSDEV_OP_OUT) &&
!(info->invert & IP6T_PHYSDEV_OP_OUT))
return NOMATCH;
return MATCH;
}
/* This only makes sense in the FORWARD and POSTROUTING chains */
if ((info->bitmask & IP6T_PHYSDEV_OP_BRIDGED) &&
(!!(nf_bridge->mask & BRNF_BRIDGED) ^
!(info->invert & IP6T_PHYSDEV_OP_BRIDGED)))
return NOMATCH;
if ((info->bitmask & IP6T_PHYSDEV_OP_ISIN &&
(!nf_bridge->physindev ^ !!(info->invert & IP6T_PHYSDEV_OP_ISIN))) ||
(info->bitmask & IP6T_PHYSDEV_OP_ISOUT &&
(!nf_bridge->physoutdev ^ !!(info->invert & IP6T_PHYSDEV_OP_ISOUT))))
return NOMATCH;
if (!(info->bitmask & IP6T_PHYSDEV_OP_IN))
goto match_outdev;
indev = nf_bridge->physindev ? nf_bridge->physindev->name : nulldevname;
for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned int); i++) {
ret |= (((const unsigned int *)indev)[i]
^ ((const unsigned int *)info->physindev)[i])
& ((const unsigned int *)info->in_mask)[i];
}
if ((ret == 0) ^ !(info->invert & IP6T_PHYSDEV_OP_IN))
return NOMATCH;
match_outdev:
if (!(info->bitmask & IP6T_PHYSDEV_OP_OUT))
return MATCH;
outdev = nf_bridge->physoutdev ?
nf_bridge->physoutdev->name : nulldevname;
for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned int); i++) {
ret |= (((const unsigned int *)outdev)[i]
^ ((const unsigned int *)info->physoutdev)[i])
& ((const unsigned int *)info->out_mask)[i];
}
return (ret != 0) ^ !(info->invert & IP6T_PHYSDEV_OP_OUT);
}
static int
checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchsize,
unsigned int hook_mask)
{
const struct ip6t_physdev_info *info = matchinfo;
if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_physdev_info)))
return 0;
if (!(info->bitmask & IP6T_PHYSDEV_OP_MASK) ||
info->bitmask & ~IP6T_PHYSDEV_OP_MASK)
return 0;
return 1;
}
static struct ip6t_match physdev_match = {
.name = "physdev",
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&physdev_match);
}
static void __exit fini(void)
{
ip6t_unregister_match(&physdev_match);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,301 @@
/* Kernel module to match ROUTING parameters. */
/* (C) 2001-2002 Andras Kis-Szabo <kisza@sch.bme.hu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/ipv6.h>
#include <linux/types.h>
#include <net/checksum.h>
#include <net/ipv6.h>
#include <asm/byteorder.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter_ipv6/ip6t_rt.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("IPv6 RT match");
MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
#if 0
#define DEBUGP printk
#else
#define DEBUGP(format, args...)
#endif
/* Returns 1 if the id is matched by the range, 0 otherwise */
static inline int
segsleft_match(u_int32_t min, u_int32_t max, u_int32_t id, int invert)
{
int r=0;
DEBUGP("rt segsleft_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
min,id,max);
r=(id >= min && id <= max) ^ invert;
DEBUGP(" result %s\n",r? "PASS" : "FAILED");
return r;
}
static int
match(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo,
int offset,
unsigned int protoff,
int *hotdrop)
{
struct ipv6_rt_hdr _route, *rh = NULL;
const struct ip6t_rt *rtinfo = matchinfo;
unsigned int temp;
unsigned int len;
u8 nexthdr;
unsigned int ptr;
unsigned int hdrlen = 0;
unsigned int ret = 0;
struct in6_addr *ap, _addr;
/* type of the 1st exthdr */
nexthdr = skb->nh.ipv6h->nexthdr;
/* pointer to the 1st exthdr */
ptr = sizeof(struct ipv6hdr);
/* available length */
len = skb->len - ptr;
temp = 0;
while (ip6t_ext_hdr(nexthdr)) {
struct ipv6_opt_hdr _hdr, *hp;
DEBUGP("ipv6_rt header iteration \n");
/* Is there enough space for the next ext header? */
if (len < (int)sizeof(struct ipv6_opt_hdr))
return 0;
/* No more exthdr -> evaluate */
if (nexthdr == NEXTHDR_NONE) {
break;
}
/* ESP -> evaluate */
if (nexthdr == NEXTHDR_ESP) {
break;
}
hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
BUG_ON(hp == NULL);
/* Calculate the header length */
if (nexthdr == NEXTHDR_FRAGMENT) {
hdrlen = 8;
} else if (nexthdr == NEXTHDR_AUTH)
hdrlen = (hp->hdrlen+2)<<2;
else
hdrlen = ipv6_optlen(hp);
/* ROUTING -> evaluate */
if (nexthdr == NEXTHDR_ROUTING) {
temp |= MASK_ROUTING;
break;
}
/* set the flag */
switch (nexthdr){
case NEXTHDR_HOP:
case NEXTHDR_ROUTING:
case NEXTHDR_FRAGMENT:
case NEXTHDR_AUTH:
case NEXTHDR_DEST:
break;
default:
DEBUGP("ipv6_rt match: unknown nextheader %u\n",nexthdr);
return 0;
break;
}
nexthdr = hp->nexthdr;
len -= hdrlen;
ptr += hdrlen;
if ( ptr > skb->len ) {
DEBUGP("ipv6_rt: new pointer is too large! \n");
break;
}
}
/* ROUTING header not found */
if ( temp != MASK_ROUTING ) return 0;
if (len < (int)sizeof(struct ipv6_rt_hdr)){
*hotdrop = 1;
return 0;
}
if (len < hdrlen){
/* Pcket smaller than its length field */
return 0;
}
rh = skb_header_pointer(skb, ptr, sizeof(_route), &_route);
BUG_ON(rh == NULL);
DEBUGP("IPv6 RT LEN %u %u ", hdrlen, rh->hdrlen);
DEBUGP("TYPE %04X ", rh->type);
DEBUGP("SGS_LEFT %u %02X\n", rh->segments_left, rh->segments_left);
DEBUGP("IPv6 RT segsleft %02X ",
(segsleft_match(rtinfo->segsleft[0], rtinfo->segsleft[1],
rh->segments_left,
!!(rtinfo->invflags & IP6T_RT_INV_SGS))));
DEBUGP("type %02X %02X %02X ",
rtinfo->rt_type, rh->type,
(!(rtinfo->flags & IP6T_RT_TYP) ||
((rtinfo->rt_type == rh->type) ^
!!(rtinfo->invflags & IP6T_RT_INV_TYP))));
DEBUGP("len %02X %04X %02X ",
rtinfo->hdrlen, hdrlen,
(!(rtinfo->flags & IP6T_RT_LEN) ||
((rtinfo->hdrlen == hdrlen) ^
!!(rtinfo->invflags & IP6T_RT_INV_LEN))));
DEBUGP("res %02X %02X %02X ",
(rtinfo->flags & IP6T_RT_RES), ((struct rt0_hdr *)rh)->bitmap,
!((rtinfo->flags & IP6T_RT_RES) && (((struct rt0_hdr *)rh)->bitmap)));
ret = (rh != NULL)
&&
(segsleft_match(rtinfo->segsleft[0], rtinfo->segsleft[1],
rh->segments_left,
!!(rtinfo->invflags & IP6T_RT_INV_SGS)))
&&
(!(rtinfo->flags & IP6T_RT_LEN) ||
((rtinfo->hdrlen == hdrlen) ^
!!(rtinfo->invflags & IP6T_RT_INV_LEN)))
&&
(!(rtinfo->flags & IP6T_RT_TYP) ||
((rtinfo->rt_type == rh->type) ^
!!(rtinfo->invflags & IP6T_RT_INV_TYP)));
if (ret && (rtinfo->flags & IP6T_RT_RES)) {
u_int32_t *bp, _bitmap;
bp = skb_header_pointer(skb,
ptr + offsetof(struct rt0_hdr, bitmap),
sizeof(_bitmap), &_bitmap);
ret = (*bp == 0);
}
DEBUGP("#%d ",rtinfo->addrnr);
if ( !(rtinfo->flags & IP6T_RT_FST) ){
return ret;
} else if (rtinfo->flags & IP6T_RT_FST_NSTRICT) {
DEBUGP("Not strict ");
if ( rtinfo->addrnr > (unsigned int)((hdrlen-8)/16) ){
DEBUGP("There isn't enough space\n");
return 0;
} else {
unsigned int i = 0;
DEBUGP("#%d ",rtinfo->addrnr);
for(temp=0; temp<(unsigned int)((hdrlen-8)/16); temp++){
ap = skb_header_pointer(skb,
ptr
+ sizeof(struct rt0_hdr)
+ temp * sizeof(_addr),
sizeof(_addr),
&_addr);
BUG_ON(ap == NULL);
if (ipv6_addr_equal(ap, &rtinfo->addrs[i])) {
DEBUGP("i=%d temp=%d;\n",i,temp);
i++;
}
if (i==rtinfo->addrnr) break;
}
DEBUGP("i=%d #%d\n", i, rtinfo->addrnr);
if (i == rtinfo->addrnr)
return ret;
else return 0;
}
} else {
DEBUGP("Strict ");
if ( rtinfo->addrnr > (unsigned int)((hdrlen-8)/16) ){
DEBUGP("There isn't enough space\n");
return 0;
} else {
DEBUGP("#%d ",rtinfo->addrnr);
for(temp=0; temp<rtinfo->addrnr; temp++){
ap = skb_header_pointer(skb,
ptr
+ sizeof(struct rt0_hdr)
+ temp * sizeof(_addr),
sizeof(_addr),
&_addr);
BUG_ON(ap == NULL);
if (!ipv6_addr_equal(ap, &rtinfo->addrs[temp]))
break;
}
DEBUGP("temp=%d #%d\n", temp, rtinfo->addrnr);
if ((temp == rtinfo->addrnr) && (temp == (unsigned int)((hdrlen-8)/16)))
return ret;
else return 0;
}
}
return 0;
}
/* Called when user tries to insert an entry of this type. */
static int
checkentry(const char *tablename,
const struct ip6t_ip6 *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask)
{
const struct ip6t_rt *rtinfo = matchinfo;
if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_rt))) {
DEBUGP("ip6t_rt: matchsize %u != %u\n",
matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_rt)));
return 0;
}
if (rtinfo->invflags & ~IP6T_RT_INV_MASK) {
DEBUGP("ip6t_rt: unknown flags %X\n",
rtinfo->invflags);
return 0;
}
if ( (rtinfo->flags & (IP6T_RT_RES|IP6T_RT_FST_MASK)) &&
(!(rtinfo->flags & IP6T_RT_TYP) ||
(rtinfo->rt_type != 0) ||
(rtinfo->invflags & IP6T_RT_INV_TYP)) ) {
DEBUGP("`--rt-type 0' required before `--rt-0-*'");
return 0;
}
return 1;
}
static struct ip6t_match rt_match = {
.name = "rt",
.match = &match,
.checkentry = &checkentry,
.me = THIS_MODULE,
};
static int __init init(void)
{
return ip6t_register_match(&rt_match);
}
static void __exit cleanup(void)
{
ip6t_unregister_match(&rt_match);
}
module_init(init);
module_exit(cleanup);

View File

@@ -0,0 +1,214 @@
/*
* This is the 1999 rewrite of IP Firewalling, aiming for kernel 2.3.x.
*
* Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling
* Copyright (C) 2000-2004 Netfilter Core Team <coreteam@netfilter.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Netfilter Core Team <coreteam@netfilter.org>");
MODULE_DESCRIPTION("ip6tables filter table");
#define FILTER_VALID_HOOKS ((1 << NF_IP6_LOCAL_IN) | (1 << NF_IP6_FORWARD) | (1 << NF_IP6_LOCAL_OUT))
/* Standard entry. */
struct ip6t_standard
{
struct ip6t_entry entry;
struct ip6t_standard_target target;
};
struct ip6t_error_target
{
struct ip6t_entry_target target;
char errorname[IP6T_FUNCTION_MAXNAMELEN];
};
struct ip6t_error
{
struct ip6t_entry entry;
struct ip6t_error_target target;
};
static struct
{
struct ip6t_replace repl;
struct ip6t_standard entries[3];
struct ip6t_error term;
} initial_table __initdata
= { { "filter", FILTER_VALID_HOOKS, 4,
sizeof(struct ip6t_standard) * 3 + sizeof(struct ip6t_error),
{ [NF_IP6_LOCAL_IN] = 0,
[NF_IP6_FORWARD] = sizeof(struct ip6t_standard),
[NF_IP6_LOCAL_OUT] = sizeof(struct ip6t_standard) * 2 },
{ [NF_IP6_LOCAL_IN] = 0,
[NF_IP6_FORWARD] = sizeof(struct ip6t_standard),
[NF_IP6_LOCAL_OUT] = sizeof(struct ip6t_standard) * 2 },
0, NULL, { } },
{
/* LOCAL_IN */
{ { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ip6t_entry),
sizeof(struct ip6t_standard),
0, { 0, 0 }, { } },
{ { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } },
/* FORWARD */
{ { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ip6t_entry),
sizeof(struct ip6t_standard),
0, { 0, 0 }, { } },
{ { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } },
/* LOCAL_OUT */
{ { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ip6t_entry),
sizeof(struct ip6t_standard),
0, { 0, 0 }, { } },
{ { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } }
},
/* ERROR */
{ { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ip6t_entry),
sizeof(struct ip6t_error),
0, { 0, 0 }, { } },
{ { { { IP6T_ALIGN(sizeof(struct ip6t_error_target)), IP6T_ERROR_TARGET } },
{ } },
"ERROR"
}
}
};
static struct ip6t_table packet_filter = {
.name = "filter",
.valid_hooks = FILTER_VALID_HOOKS,
.lock = RW_LOCK_UNLOCKED,
.me = THIS_MODULE,
};
/* The work comes in here from netfilter.c. */
static unsigned int
ip6t_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
return ip6t_do_table(pskb, hook, in, out, &packet_filter, NULL);
}
static unsigned int
ip6t_local_out_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
#if 0
/* root is playing with raw sockets. */
if ((*pskb)->len < sizeof(struct iphdr)
|| (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr)) {
if (net_ratelimit())
printk("ip6t_hook: happy cracking.\n");
return NF_ACCEPT;
}
#endif
return ip6t_do_table(pskb, hook, in, out, &packet_filter, NULL);
}
static struct nf_hook_ops ip6t_ops[] = {
{
.hook = ip6t_hook,
.owner = THIS_MODULE,
.pf = PF_INET6,
.hooknum = NF_IP6_LOCAL_IN,
.priority = NF_IP6_PRI_FILTER,
},
{
.hook = ip6t_hook,
.owner = THIS_MODULE,
.pf = PF_INET6,
.hooknum = NF_IP6_FORWARD,
.priority = NF_IP6_PRI_FILTER,
},
{
.hook = ip6t_local_out_hook,
.owner = THIS_MODULE,
.pf = PF_INET6,
.hooknum = NF_IP6_LOCAL_OUT,
.priority = NF_IP6_PRI_FILTER,
},
};
/* Default to forward because I got too much mail already. */
static int forward = NF_ACCEPT;
module_param(forward, bool, 0000);
static int __init init(void)
{
int ret;
if (forward < 0 || forward > NF_MAX_VERDICT) {
printk("iptables forward must be 0 or 1\n");
return -EINVAL;
}
/* Entry 1 is the FORWARD hook */
initial_table.entries[1].target.verdict = -forward - 1;
/* Register table */
ret = ip6t_register_table(&packet_filter, &initial_table.repl);
if (ret < 0)
return ret;
/* Register hooks */
ret = nf_register_hook(&ip6t_ops[0]);
if (ret < 0)
goto cleanup_table;
ret = nf_register_hook(&ip6t_ops[1]);
if (ret < 0)
goto cleanup_hook0;
ret = nf_register_hook(&ip6t_ops[2]);
if (ret < 0)
goto cleanup_hook1;
return ret;
cleanup_hook1:
nf_unregister_hook(&ip6t_ops[1]);
cleanup_hook0:
nf_unregister_hook(&ip6t_ops[0]);
cleanup_table:
ip6t_unregister_table(&packet_filter);
return ret;
}
static void __exit fini(void)
{
unsigned int i;
for (i = 0; i < sizeof(ip6t_ops)/sizeof(struct nf_hook_ops); i++)
nf_unregister_hook(&ip6t_ops[i]);
ip6t_unregister_table(&packet_filter);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,287 @@
/*
* IPv6 packet mangling table, a port of the IPv4 mangle table to IPv6
*
* Copyright (C) 2000-2001 by Harald Welte <laforge@gnumonks.org>
* Copyright (C) 2000-2004 Netfilter Core Team <coreteam@netfilter.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Extended to all five netfilter hooks by Brad Chapman & Harald Welte
*/
#include <linux/module.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Netfilter Core Team <coreteam@netfilter.org>");
MODULE_DESCRIPTION("ip6tables mangle table");
#define MANGLE_VALID_HOOKS ((1 << NF_IP6_PRE_ROUTING) | \
(1 << NF_IP6_LOCAL_IN) | \
(1 << NF_IP6_FORWARD) | \
(1 << NF_IP6_LOCAL_OUT) | \
(1 << NF_IP6_POST_ROUTING))
#if 0
#define DEBUGP(x, args...) printk(KERN_DEBUG x, ## args)
#else
#define DEBUGP(x, args...)
#endif
/* Standard entry. */
struct ip6t_standard
{
struct ip6t_entry entry;
struct ip6t_standard_target target;
};
struct ip6t_error_target
{
struct ip6t_entry_target target;
char errorname[IP6T_FUNCTION_MAXNAMELEN];
};
struct ip6t_error
{
struct ip6t_entry entry;
struct ip6t_error_target target;
};
static struct
{
struct ip6t_replace repl;
struct ip6t_standard entries[5];
struct ip6t_error term;
} initial_table __initdata
= { { "mangle", MANGLE_VALID_HOOKS, 6,
sizeof(struct ip6t_standard) * 5 + sizeof(struct ip6t_error),
{ [NF_IP6_PRE_ROUTING] = 0,
[NF_IP6_LOCAL_IN] = sizeof(struct ip6t_standard),
[NF_IP6_FORWARD] = sizeof(struct ip6t_standard) * 2,
[NF_IP6_LOCAL_OUT] = sizeof(struct ip6t_standard) * 3,
[NF_IP6_POST_ROUTING] = sizeof(struct ip6t_standard) * 4},
{ [NF_IP6_PRE_ROUTING] = 0,
[NF_IP6_LOCAL_IN] = sizeof(struct ip6t_standard),
[NF_IP6_FORWARD] = sizeof(struct ip6t_standard) * 2,
[NF_IP6_LOCAL_OUT] = sizeof(struct ip6t_standard) * 3,
[NF_IP6_POST_ROUTING] = sizeof(struct ip6t_standard) * 4},
0, NULL, { } },
{
/* PRE_ROUTING */
{ { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ip6t_entry),
sizeof(struct ip6t_standard),
0, { 0, 0 }, { } },
{ { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } },
/* LOCAL_IN */
{ { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ip6t_entry),
sizeof(struct ip6t_standard),
0, { 0, 0 }, { } },
{ { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } },
/* FORWARD */
{ { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ip6t_entry),
sizeof(struct ip6t_standard),
0, { 0, 0 }, { } },
{ { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } },
/* LOCAL_OUT */
{ { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ip6t_entry),
sizeof(struct ip6t_standard),
0, { 0, 0 }, { } },
{ { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } },
/* POST_ROUTING */
{ { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ip6t_entry),
sizeof(struct ip6t_standard),
0, { 0, 0 }, { } },
{ { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } }
},
/* ERROR */
{ { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ip6t_entry),
sizeof(struct ip6t_error),
0, { 0, 0 }, { } },
{ { { { IP6T_ALIGN(sizeof(struct ip6t_error_target)), IP6T_ERROR_TARGET } },
{ } },
"ERROR"
}
}
};
static struct ip6t_table packet_mangler = {
.name = "mangle",
.valid_hooks = MANGLE_VALID_HOOKS,
.lock = RW_LOCK_UNLOCKED,
.me = THIS_MODULE,
};
/* The work comes in here from netfilter.c. */
static unsigned int
ip6t_route_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
return ip6t_do_table(pskb, hook, in, out, &packet_mangler, NULL);
}
static unsigned int
ip6t_local_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
unsigned long nfmark;
unsigned int ret;
struct in6_addr saddr, daddr;
u_int8_t hop_limit;
u_int32_t flowlabel;
#if 0
/* root is playing with raw sockets. */
if ((*pskb)->len < sizeof(struct iphdr)
|| (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr)) {
if (net_ratelimit())
printk("ip6t_hook: happy cracking.\n");
return NF_ACCEPT;
}
#endif
/* save source/dest address, nfmark, hoplimit, flowlabel, priority, */
memcpy(&saddr, &(*pskb)->nh.ipv6h->saddr, sizeof(saddr));
memcpy(&daddr, &(*pskb)->nh.ipv6h->daddr, sizeof(daddr));
nfmark = (*pskb)->nfmark;
hop_limit = (*pskb)->nh.ipv6h->hop_limit;
/* flowlabel and prio (includes version, which shouldn't change either */
flowlabel = *((u_int32_t *) (*pskb)->nh.ipv6h);
ret = ip6t_do_table(pskb, hook, in, out, &packet_mangler, NULL);
if (ret != NF_DROP && ret != NF_STOLEN
&& (memcmp(&(*pskb)->nh.ipv6h->saddr, &saddr, sizeof(saddr))
|| memcmp(&(*pskb)->nh.ipv6h->daddr, &daddr, sizeof(daddr))
|| (*pskb)->nfmark != nfmark
|| (*pskb)->nh.ipv6h->hop_limit != hop_limit)) {
/* something which could affect routing has changed */
DEBUGP("ip6table_mangle: we'd need to re-route a packet\n");
}
return ret;
}
static struct nf_hook_ops ip6t_ops[] = {
{
.hook = ip6t_route_hook,
.owner = THIS_MODULE,
.pf = PF_INET6,
.hooknum = NF_IP6_PRE_ROUTING,
.priority = NF_IP6_PRI_MANGLE,
},
{
.hook = ip6t_local_hook,
.owner = THIS_MODULE,
.pf = PF_INET6,
.hooknum = NF_IP6_LOCAL_IN,
.priority = NF_IP6_PRI_MANGLE,
},
{
.hook = ip6t_route_hook,
.owner = THIS_MODULE,
.pf = PF_INET6,
.hooknum = NF_IP6_FORWARD,
.priority = NF_IP6_PRI_MANGLE,
},
{
.hook = ip6t_local_hook,
.owner = THIS_MODULE,
.pf = PF_INET6,
.hooknum = NF_IP6_LOCAL_OUT,
.priority = NF_IP6_PRI_MANGLE,
},
{
.hook = ip6t_route_hook,
.owner = THIS_MODULE,
.pf = PF_INET6,
.hooknum = NF_IP6_POST_ROUTING,
.priority = NF_IP6_PRI_MANGLE,
},
};
static int __init init(void)
{
int ret;
/* Register table */
ret = ip6t_register_table(&packet_mangler, &initial_table.repl);
if (ret < 0)
return ret;
/* Register hooks */
ret = nf_register_hook(&ip6t_ops[0]);
if (ret < 0)
goto cleanup_table;
ret = nf_register_hook(&ip6t_ops[1]);
if (ret < 0)
goto cleanup_hook0;
ret = nf_register_hook(&ip6t_ops[2]);
if (ret < 0)
goto cleanup_hook1;
ret = nf_register_hook(&ip6t_ops[3]);
if (ret < 0)
goto cleanup_hook2;
ret = nf_register_hook(&ip6t_ops[4]);
if (ret < 0)
goto cleanup_hook3;
return ret;
cleanup_hook3:
nf_unregister_hook(&ip6t_ops[3]);
cleanup_hook2:
nf_unregister_hook(&ip6t_ops[2]);
cleanup_hook1:
nf_unregister_hook(&ip6t_ops[1]);
cleanup_hook0:
nf_unregister_hook(&ip6t_ops[0]);
cleanup_table:
ip6t_unregister_table(&packet_mangler);
return ret;
}
static void __exit fini(void)
{
unsigned int i;
for (i = 0; i < sizeof(ip6t_ops)/sizeof(struct nf_hook_ops); i++)
nf_unregister_hook(&ip6t_ops[i]);
ip6t_unregister_table(&packet_mangler);
}
module_init(init);
module_exit(fini);

View File

@@ -0,0 +1,182 @@
/*
* IPv6 raw table, a port of the IPv4 raw table to IPv6
*
* Copyright (C) 2003 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
*/
#include <linux/module.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#define RAW_VALID_HOOKS ((1 << NF_IP6_PRE_ROUTING) | (1 << NF_IP6_LOCAL_OUT))
#if 0
#define DEBUGP(x, args...) printk(KERN_DEBUG x, ## args)
#else
#define DEBUGP(x, args...)
#endif
/* Standard entry. */
struct ip6t_standard
{
struct ip6t_entry entry;
struct ip6t_standard_target target;
};
struct ip6t_error_target
{
struct ip6t_entry_target target;
char errorname[IP6T_FUNCTION_MAXNAMELEN];
};
struct ip6t_error
{
struct ip6t_entry entry;
struct ip6t_error_target target;
};
static struct
{
struct ip6t_replace repl;
struct ip6t_standard entries[2];
struct ip6t_error term;
} initial_table __initdata = {
.repl = {
.name = "raw",
.valid_hooks = RAW_VALID_HOOKS,
.num_entries = 3,
.size = sizeof(struct ip6t_standard) * 2 + sizeof(struct ip6t_error),
.hook_entry = {
[NF_IP6_PRE_ROUTING] = 0,
[NF_IP6_LOCAL_OUT] = sizeof(struct ip6t_standard)
},
.underflow = {
[NF_IP6_PRE_ROUTING] = 0,
[NF_IP6_LOCAL_OUT] = sizeof(struct ip6t_standard)
},
},
.entries = {
/* PRE_ROUTING */
{
.entry = {
.target_offset = sizeof(struct ip6t_entry),
.next_offset = sizeof(struct ip6t_standard),
},
.target = {
.target = {
.u = {
.target_size = IP6T_ALIGN(sizeof(struct ip6t_standard_target)),
},
},
.verdict = -NF_ACCEPT - 1,
},
},
/* LOCAL_OUT */
{
.entry = {
.target_offset = sizeof(struct ip6t_entry),
.next_offset = sizeof(struct ip6t_standard),
},
.target = {
.target = {
.u = {
.target_size = IP6T_ALIGN(sizeof(struct ip6t_standard_target)),
},
},
.verdict = -NF_ACCEPT - 1,
},
},
},
/* ERROR */
.term = {
.entry = {
.target_offset = sizeof(struct ip6t_entry),
.next_offset = sizeof(struct ip6t_error),
},
.target = {
.target = {
.u = {
.user = {
.target_size = IP6T_ALIGN(sizeof(struct ip6t_error_target)),
.name = IP6T_ERROR_TARGET,
},
},
},
.errorname = "ERROR",
},
}
};
static struct ip6t_table packet_raw = {
.name = "raw",
.valid_hooks = RAW_VALID_HOOKS,
.lock = RW_LOCK_UNLOCKED,
.me = THIS_MODULE
};
/* The work comes in here from netfilter.c. */
static unsigned int
ip6t_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
return ip6t_do_table(pskb, hook, in, out, &packet_raw, NULL);
}
static struct nf_hook_ops ip6t_ops[] = {
{
.hook = ip6t_hook,
.pf = PF_INET6,
.hooknum = NF_IP6_PRE_ROUTING,
.priority = NF_IP6_PRI_FIRST
},
{
.hook = ip6t_hook,
.pf = PF_INET6,
.hooknum = NF_IP6_LOCAL_OUT,
.priority = NF_IP6_PRI_FIRST
},
};
static int __init init(void)
{
int ret;
/* Register table */
ret = ip6t_register_table(&packet_raw, &initial_table.repl);
if (ret < 0)
return ret;
/* Register hooks */
ret = nf_register_hook(&ip6t_ops[0]);
if (ret < 0)
goto cleanup_table;
ret = nf_register_hook(&ip6t_ops[1]);
if (ret < 0)
goto cleanup_hook0;
return ret;
cleanup_hook0:
nf_unregister_hook(&ip6t_ops[0]);
cleanup_table:
ip6t_unregister_table(&packet_raw);
return ret;
}
static void __exit fini(void)
{
unsigned int i;
for (i = 0; i < sizeof(ip6t_ops)/sizeof(struct nf_hook_ops); i++)
nf_unregister_hook(&ip6t_ops[i]);
ip6t_unregister_table(&packet_raw);
}
module_init(init);
module_exit(fini);
MODULE_LICENSE("GPL");

303
net/ipv6/proc.c Normal file
View File

@@ -0,0 +1,303 @@
/*
* INET An implementation of the TCP/IP protocol suite for the LINUX
* operating system. INET is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* This file implements the various access functions for the
* PROC file system. This is very similar to the IPv4 version,
* except it reports the sockets in the INET6 address family.
*
* Version: $Id: proc.c,v 1.17 2002/02/01 22:01:04 davem Exp $
*
* Authors: David S. Miller (davem@caip.rutgers.edu)
* YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
*
* 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/config.h>
#include <linux/sched.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <linux/ipv6.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stddef.h>
#include <net/sock.h>
#include <net/tcp.h>
#include <net/transp_v6.h>
#include <net/ipv6.h>
#ifdef CONFIG_PROC_FS
static struct proc_dir_entry *proc_net_devsnmp6;
static int fold_prot_inuse(struct proto *proto)
{
int res = 0;
int cpu;
for (cpu=0; cpu<NR_CPUS; cpu++)
res += proto->stats[cpu].inuse;
return res;
}
static int sockstat6_seq_show(struct seq_file *seq, void *v)
{
seq_printf(seq, "TCP6: inuse %d\n",
fold_prot_inuse(&tcpv6_prot));
seq_printf(seq, "UDP6: inuse %d\n",
fold_prot_inuse(&udpv6_prot));
seq_printf(seq, "RAW6: inuse %d\n",
fold_prot_inuse(&rawv6_prot));
seq_printf(seq, "FRAG6: inuse %d memory %d\n",
ip6_frag_nqueues, atomic_read(&ip6_frag_mem));
return 0;
}
static struct snmp_mib snmp6_ipstats_list[] = {
/* ipv6 mib according to RFC 2465 */
SNMP_MIB_ITEM("Ip6InReceives", IPSTATS_MIB_INRECEIVES),
SNMP_MIB_ITEM("Ip6InHdrErrors", IPSTATS_MIB_INHDRERRORS),
SNMP_MIB_ITEM("Ip6InTooBigErrors", IPSTATS_MIB_INTOOBIGERRORS),
SNMP_MIB_ITEM("Ip6InNoRoutes", IPSTATS_MIB_INNOROUTES),
SNMP_MIB_ITEM("Ip6InAddrErrors", IPSTATS_MIB_INADDRERRORS),
SNMP_MIB_ITEM("Ip6InUnknownProtos", IPSTATS_MIB_INUNKNOWNPROTOS),
SNMP_MIB_ITEM("Ip6InTruncatedPkts", IPSTATS_MIB_INTRUNCATEDPKTS),
SNMP_MIB_ITEM("Ip6InDiscards", IPSTATS_MIB_INDISCARDS),
SNMP_MIB_ITEM("Ip6InDelivers", IPSTATS_MIB_INDELIVERS),
SNMP_MIB_ITEM("Ip6OutForwDatagrams", IPSTATS_MIB_OUTFORWDATAGRAMS),
SNMP_MIB_ITEM("Ip6OutRequests", IPSTATS_MIB_OUTREQUESTS),
SNMP_MIB_ITEM("Ip6OutDiscards", IPSTATS_MIB_OUTDISCARDS),
SNMP_MIB_ITEM("Ip6OutNoRoutes", IPSTATS_MIB_OUTNOROUTES),
SNMP_MIB_ITEM("Ip6ReasmTimeout", IPSTATS_MIB_REASMTIMEOUT),
SNMP_MIB_ITEM("Ip6ReasmReqds", IPSTATS_MIB_REASMREQDS),
SNMP_MIB_ITEM("Ip6ReasmOKs", IPSTATS_MIB_REASMOKS),
SNMP_MIB_ITEM("Ip6ReasmFails", IPSTATS_MIB_REASMFAILS),
SNMP_MIB_ITEM("Ip6FragOKs", IPSTATS_MIB_FRAGOKS),
SNMP_MIB_ITEM("Ip6FragFails", IPSTATS_MIB_FRAGFAILS),
SNMP_MIB_ITEM("Ip6FragCreates", IPSTATS_MIB_FRAGCREATES),
SNMP_MIB_ITEM("Ip6InMcastPkts", IPSTATS_MIB_INMCASTPKTS),
SNMP_MIB_ITEM("Ip6OutMcastPkts", IPSTATS_MIB_OUTMCASTPKTS),
SNMP_MIB_SENTINEL
};
static struct snmp_mib snmp6_icmp6_list[] = {
/* icmpv6 mib according to RFC 2466
Exceptions: {In|Out}AdminProhibs are removed, because I see
no good reasons to account them separately
of another dest.unreachs.
OutErrs is zero identically.
OutEchos too.
OutRouterAdvertisements too.
OutGroupMembQueries too.
*/
SNMP_MIB_ITEM("Icmp6InMsgs", ICMP6_MIB_INMSGS),
SNMP_MIB_ITEM("Icmp6InErrors", ICMP6_MIB_INERRORS),
SNMP_MIB_ITEM("Icmp6InDestUnreachs", ICMP6_MIB_INDESTUNREACHS),
SNMP_MIB_ITEM("Icmp6InPktTooBigs", ICMP6_MIB_INPKTTOOBIGS),
SNMP_MIB_ITEM("Icmp6InTimeExcds", ICMP6_MIB_INTIMEEXCDS),
SNMP_MIB_ITEM("Icmp6InParmProblems", ICMP6_MIB_INPARMPROBLEMS),
SNMP_MIB_ITEM("Icmp6InEchos", ICMP6_MIB_INECHOS),
SNMP_MIB_ITEM("Icmp6InEchoReplies", ICMP6_MIB_INECHOREPLIES),
SNMP_MIB_ITEM("Icmp6InGroupMembQueries", ICMP6_MIB_INGROUPMEMBQUERIES),
SNMP_MIB_ITEM("Icmp6InGroupMembResponses", ICMP6_MIB_INGROUPMEMBRESPONSES),
SNMP_MIB_ITEM("Icmp6InGroupMembReductions", ICMP6_MIB_INGROUPMEMBREDUCTIONS),
SNMP_MIB_ITEM("Icmp6InRouterSolicits", ICMP6_MIB_INROUTERSOLICITS),
SNMP_MIB_ITEM("Icmp6InRouterAdvertisements", ICMP6_MIB_INROUTERADVERTISEMENTS),
SNMP_MIB_ITEM("Icmp6InNeighborSolicits", ICMP6_MIB_INNEIGHBORSOLICITS),
SNMP_MIB_ITEM("Icmp6InNeighborAdvertisements", ICMP6_MIB_INNEIGHBORADVERTISEMENTS),
SNMP_MIB_ITEM("Icmp6InRedirects", ICMP6_MIB_INREDIRECTS),
SNMP_MIB_ITEM("Icmp6OutMsgs", ICMP6_MIB_OUTMSGS),
SNMP_MIB_ITEM("Icmp6OutDestUnreachs", ICMP6_MIB_OUTDESTUNREACHS),
SNMP_MIB_ITEM("Icmp6OutPktTooBigs", ICMP6_MIB_OUTPKTTOOBIGS),
SNMP_MIB_ITEM("Icmp6OutTimeExcds", ICMP6_MIB_OUTTIMEEXCDS),
SNMP_MIB_ITEM("Icmp6OutParmProblems", ICMP6_MIB_OUTPARMPROBLEMS),
SNMP_MIB_ITEM("Icmp6OutEchoReplies", ICMP6_MIB_OUTECHOREPLIES),
SNMP_MIB_ITEM("Icmp6OutRouterSolicits", ICMP6_MIB_OUTROUTERSOLICITS),
SNMP_MIB_ITEM("Icmp6OutNeighborSolicits", ICMP6_MIB_OUTNEIGHBORSOLICITS),
SNMP_MIB_ITEM("Icmp6OutNeighborAdvertisements", ICMP6_MIB_OUTNEIGHBORADVERTISEMENTS),
SNMP_MIB_ITEM("Icmp6OutRedirects", ICMP6_MIB_OUTREDIRECTS),
SNMP_MIB_ITEM("Icmp6OutGroupMembResponses", ICMP6_MIB_OUTGROUPMEMBRESPONSES),
SNMP_MIB_ITEM("Icmp6OutGroupMembReductions", ICMP6_MIB_OUTGROUPMEMBREDUCTIONS),
SNMP_MIB_SENTINEL
};
static struct snmp_mib snmp6_udp6_list[] = {
SNMP_MIB_ITEM("Udp6InDatagrams", UDP_MIB_INDATAGRAMS),
SNMP_MIB_ITEM("Udp6NoPorts", UDP_MIB_NOPORTS),
SNMP_MIB_ITEM("Udp6InErrors", UDP_MIB_INERRORS),
SNMP_MIB_ITEM("Udp6OutDatagrams", UDP_MIB_OUTDATAGRAMS),
SNMP_MIB_SENTINEL
};
static unsigned long
fold_field(void *mib[], int offt)
{
unsigned long res = 0;
int i;
for (i = 0; i < NR_CPUS; i++) {
if (!cpu_possible(i))
continue;
res += *(((unsigned long *)per_cpu_ptr(mib[0], i)) + offt);
res += *(((unsigned long *)per_cpu_ptr(mib[1], i)) + offt);
}
return res;
}
static inline void
snmp6_seq_show_item(struct seq_file *seq, void **mib, struct snmp_mib *itemlist)
{
int i;
for (i=0; itemlist[i].name; i++)
seq_printf(seq, "%-32s\t%lu\n", itemlist[i].name,
fold_field(mib, itemlist[i].entry));
}
static int snmp6_seq_show(struct seq_file *seq, void *v)
{
struct inet6_dev *idev = (struct inet6_dev *)seq->private;
if (idev) {
seq_printf(seq, "%-32s\t%u\n", "ifIndex", idev->dev->ifindex);
snmp6_seq_show_item(seq, (void **)idev->stats.icmpv6, snmp6_icmp6_list);
} else {
snmp6_seq_show_item(seq, (void **)ipv6_statistics, snmp6_ipstats_list);
snmp6_seq_show_item(seq, (void **)icmpv6_statistics, snmp6_icmp6_list);
snmp6_seq_show_item(seq, (void **)udp_stats_in6, snmp6_udp6_list);
}
return 0;
}
static int sockstat6_seq_open(struct inode *inode, struct file *file)
{
return single_open(file, sockstat6_seq_show, NULL);
}
static struct file_operations sockstat6_seq_fops = {
.owner = THIS_MODULE,
.open = sockstat6_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int snmp6_seq_open(struct inode *inode, struct file *file)
{
return single_open(file, snmp6_seq_show, PDE(inode)->data);
}
static struct file_operations snmp6_seq_fops = {
.owner = THIS_MODULE,
.open = snmp6_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
int snmp6_register_dev(struct inet6_dev *idev)
{
struct proc_dir_entry *p;
if (!idev || !idev->dev)
return -EINVAL;
if (!proc_net_devsnmp6)
return -ENOENT;
p = create_proc_entry(idev->dev->name, S_IRUGO, proc_net_devsnmp6);
if (!p)
return -ENOMEM;
p->data = idev;
p->proc_fops = &snmp6_seq_fops;
idev->stats.proc_dir_entry = p;
return 0;
}
int snmp6_unregister_dev(struct inet6_dev *idev)
{
if (!proc_net_devsnmp6)
return -ENOENT;
if (!idev || !idev->stats.proc_dir_entry)
return -EINVAL;
remove_proc_entry(idev->stats.proc_dir_entry->name,
proc_net_devsnmp6);
return 0;
}
int __init ipv6_misc_proc_init(void)
{
int rc = 0;
if (!proc_net_fops_create("snmp6", S_IRUGO, &snmp6_seq_fops))
goto proc_snmp6_fail;
proc_net_devsnmp6 = proc_mkdir("dev_snmp6", proc_net);
if (!proc_net_devsnmp6)
goto proc_dev_snmp6_fail;
if (!proc_net_fops_create("sockstat6", S_IRUGO, &sockstat6_seq_fops))
goto proc_sockstat6_fail;
out:
return rc;
proc_sockstat6_fail:
proc_net_remove("dev_snmp6");
proc_dev_snmp6_fail:
proc_net_remove("snmp6");
proc_snmp6_fail:
rc = -ENOMEM;
goto out;
}
void ipv6_misc_proc_exit(void)
{
proc_net_remove("sockstat6");
proc_net_remove("dev_snmp6");
proc_net_remove("snmp6");
}
#else /* CONFIG_PROC_FS */
int snmp6_register_dev(struct inet6_dev *idev)
{
return 0;
}
int snmp6_unregister_dev(struct inet6_dev *idev)
{
return 0;
}
#endif /* CONFIG_PROC_FS */
int snmp6_alloc_dev(struct inet6_dev *idev)
{
int err = -ENOMEM;
if (!idev || !idev->dev)
return -EINVAL;
if (snmp6_mib_init((void **)idev->stats.icmpv6, sizeof(struct icmpv6_mib),
__alignof__(struct icmpv6_mib)) < 0)
goto err_icmp;
return 0;
err_icmp:
return err;
}
int snmp6_free_dev(struct inet6_dev *idev)
{
snmp6_mib_free((void **)idev->stats.icmpv6);
return 0;
}

86
net/ipv6/protocol.c Normal file
View File

@@ -0,0 +1,86 @@
/*
* INET An implementation of the TCP/IP protocol suite for the LINUX
* operating system. INET is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* PF_INET6 protocol dispatch tables.
*
* Version: $Id: protocol.c,v 1.10 2001/05/18 02:25:49 davem Exp $
*
* Authors: Pedro Roque <roque@di.fc.ul.pt>
*
* 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.
*/
/*
* Changes:
*
* Vince Laviano (vince@cs.stanford.edu) 16 May 2001
* - Removed unused variable 'inet6_protocol_base'
* - Modified inet6_del_protocol() to correctly maintain copy bit.
*/
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/sched.h>
#include <linux/net.h>
#include <linux/in6.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <net/sock.h>
#include <net/snmp.h>
#include <net/ipv6.h>
#include <net/protocol.h>
struct inet6_protocol *inet6_protos[MAX_INET_PROTOS];
static DEFINE_SPINLOCK(inet6_proto_lock);
int inet6_add_protocol(struct inet6_protocol *prot, unsigned char protocol)
{
int ret, hash = protocol & (MAX_INET_PROTOS - 1);
spin_lock_bh(&inet6_proto_lock);
if (inet6_protos[hash]) {
ret = -1;
} else {
inet6_protos[hash] = prot;
ret = 0;
}
spin_unlock_bh(&inet6_proto_lock);
return ret;
}
/*
* Remove a protocol from the hash tables.
*/
int inet6_del_protocol(struct inet6_protocol *prot, unsigned char protocol)
{
int ret, hash = protocol & (MAX_INET_PROTOS - 1);
spin_lock_bh(&inet6_proto_lock);
if (inet6_protos[hash] != prot) {
ret = -1;
} else {
inet6_protos[hash] = NULL;
ret = 0;
}
spin_unlock_bh(&inet6_proto_lock);
synchronize_net();
return ret;
}

1157
net/ipv6/raw.c Normal file

File diff suppressed because it is too large Load Diff

771
net/ipv6/reassembly.c Normal file
View File

@@ -0,0 +1,771 @@
/*
* IPv6 fragment reassembly
* Linux INET6 implementation
*
* Authors:
* Pedro Roque <roque@di.fc.ul.pt>
*
* $Id: reassembly.c,v 1.26 2001/03/07 22:00:57 davem Exp $
*
* Based on: net/ipv4/ip_fragment.c
*
* 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.
*/
/*
* Fixes:
* Andi Kleen Make it work with multiple hosts.
* More RFC compliance.
*
* Horst von Brand Add missing #include <linux/string.h>
* Alexey Kuznetsov SMP races, threading, cleanup.
* Patrick McHardy LRU queue of frag heads for evictor.
* Mitsuru KANDA @USAGI Register inet6_protocol{}.
* David Stevens and
* YOSHIFUJI,H. @USAGI Always remove fragment header to
* calculate ICV correctly.
*/
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/jiffies.h>
#include <linux/net.h>
#include <linux/list.h>
#include <linux/netdevice.h>
#include <linux/in6.h>
#include <linux/ipv6.h>
#include <linux/icmpv6.h>
#include <linux/random.h>
#include <linux/jhash.h>
#include <net/sock.h>
#include <net/snmp.h>
#include <net/ipv6.h>
#include <net/protocol.h>
#include <net/transp_v6.h>
#include <net/rawv6.h>
#include <net/ndisc.h>
#include <net/addrconf.h>
int sysctl_ip6frag_high_thresh = 256*1024;
int sysctl_ip6frag_low_thresh = 192*1024;
int sysctl_ip6frag_time = IPV6_FRAG_TIMEOUT;
struct ip6frag_skb_cb
{
struct inet6_skb_parm h;
int offset;
};
#define FRAG6_CB(skb) ((struct ip6frag_skb_cb*)((skb)->cb))
/*
* Equivalent of ipv4 struct ipq
*/
struct frag_queue
{
struct frag_queue *next;
struct list_head lru_list; /* lru list member */
__u32 id; /* fragment id */
struct in6_addr saddr;
struct in6_addr daddr;
spinlock_t lock;
atomic_t refcnt;
struct timer_list timer; /* expire timer */
struct sk_buff *fragments;
int len;
int meat;
int iif;
struct timeval stamp;
unsigned int csum;
__u8 last_in; /* has first/last segment arrived? */
#define COMPLETE 4
#define FIRST_IN 2
#define LAST_IN 1
__u16 nhoffset;
struct frag_queue **pprev;
};
/* Hash table. */
#define IP6Q_HASHSZ 64
static struct frag_queue *ip6_frag_hash[IP6Q_HASHSZ];
static DEFINE_RWLOCK(ip6_frag_lock);
static u32 ip6_frag_hash_rnd;
static LIST_HEAD(ip6_frag_lru_list);
int ip6_frag_nqueues = 0;
static __inline__ void __fq_unlink(struct frag_queue *fq)
{
if(fq->next)
fq->next->pprev = fq->pprev;
*fq->pprev = fq->next;
list_del(&fq->lru_list);
ip6_frag_nqueues--;
}
static __inline__ void fq_unlink(struct frag_queue *fq)
{
write_lock(&ip6_frag_lock);
__fq_unlink(fq);
write_unlock(&ip6_frag_lock);
}
static unsigned int ip6qhashfn(u32 id, struct in6_addr *saddr,
struct in6_addr *daddr)
{
u32 a, b, c;
a = saddr->s6_addr32[0];
b = saddr->s6_addr32[1];
c = saddr->s6_addr32[2];
a += JHASH_GOLDEN_RATIO;
b += JHASH_GOLDEN_RATIO;
c += ip6_frag_hash_rnd;
__jhash_mix(a, b, c);
a += saddr->s6_addr32[3];
b += daddr->s6_addr32[0];
c += daddr->s6_addr32[1];
__jhash_mix(a, b, c);
a += daddr->s6_addr32[2];
b += daddr->s6_addr32[3];
c += id;
__jhash_mix(a, b, c);
return c & (IP6Q_HASHSZ - 1);
}
static struct timer_list ip6_frag_secret_timer;
int sysctl_ip6frag_secret_interval = 10 * 60 * HZ;
static void ip6_frag_secret_rebuild(unsigned long dummy)
{
unsigned long now = jiffies;
int i;
write_lock(&ip6_frag_lock);
get_random_bytes(&ip6_frag_hash_rnd, sizeof(u32));
for (i = 0; i < IP6Q_HASHSZ; i++) {
struct frag_queue *q;
q = ip6_frag_hash[i];
while (q) {
struct frag_queue *next = q->next;
unsigned int hval = ip6qhashfn(q->id,
&q->saddr,
&q->daddr);
if (hval != i) {
/* Unlink. */
if (q->next)
q->next->pprev = q->pprev;
*q->pprev = q->next;
/* Relink to new hash chain. */
if ((q->next = ip6_frag_hash[hval]) != NULL)
q->next->pprev = &q->next;
ip6_frag_hash[hval] = q;
q->pprev = &ip6_frag_hash[hval];
}
q = next;
}
}
write_unlock(&ip6_frag_lock);
mod_timer(&ip6_frag_secret_timer, now + sysctl_ip6frag_secret_interval);
}
atomic_t ip6_frag_mem = ATOMIC_INIT(0);
/* Memory Tracking Functions. */
static inline void frag_kfree_skb(struct sk_buff *skb, int *work)
{
if (work)
*work -= skb->truesize;
atomic_sub(skb->truesize, &ip6_frag_mem);
kfree_skb(skb);
}
static inline void frag_free_queue(struct frag_queue *fq, int *work)
{
if (work)
*work -= sizeof(struct frag_queue);
atomic_sub(sizeof(struct frag_queue), &ip6_frag_mem);
kfree(fq);
}
static inline struct frag_queue *frag_alloc_queue(void)
{
struct frag_queue *fq = kmalloc(sizeof(struct frag_queue), GFP_ATOMIC);
if(!fq)
return NULL;
atomic_add(sizeof(struct frag_queue), &ip6_frag_mem);
return fq;
}
/* Destruction primitives. */
/* Complete destruction of fq. */
static void ip6_frag_destroy(struct frag_queue *fq, int *work)
{
struct sk_buff *fp;
BUG_TRAP(fq->last_in&COMPLETE);
BUG_TRAP(del_timer(&fq->timer) == 0);
/* Release all fragment data. */
fp = fq->fragments;
while (fp) {
struct sk_buff *xp = fp->next;
frag_kfree_skb(fp, work);
fp = xp;
}
frag_free_queue(fq, work);
}
static __inline__ void fq_put(struct frag_queue *fq, int *work)
{
if (atomic_dec_and_test(&fq->refcnt))
ip6_frag_destroy(fq, work);
}
/* Kill fq entry. It is not destroyed immediately,
* because caller (and someone more) holds reference count.
*/
static __inline__ void fq_kill(struct frag_queue *fq)
{
if (del_timer(&fq->timer))
atomic_dec(&fq->refcnt);
if (!(fq->last_in & COMPLETE)) {
fq_unlink(fq);
atomic_dec(&fq->refcnt);
fq->last_in |= COMPLETE;
}
}
static void ip6_evictor(void)
{
struct frag_queue *fq;
struct list_head *tmp;
int work;
work = atomic_read(&ip6_frag_mem) - sysctl_ip6frag_low_thresh;
if (work <= 0)
return;
while(work > 0) {
read_lock(&ip6_frag_lock);
if (list_empty(&ip6_frag_lru_list)) {
read_unlock(&ip6_frag_lock);
return;
}
tmp = ip6_frag_lru_list.next;
fq = list_entry(tmp, struct frag_queue, lru_list);
atomic_inc(&fq->refcnt);
read_unlock(&ip6_frag_lock);
spin_lock(&fq->lock);
if (!(fq->last_in&COMPLETE))
fq_kill(fq);
spin_unlock(&fq->lock);
fq_put(fq, &work);
IP6_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
}
}
static void ip6_frag_expire(unsigned long data)
{
struct frag_queue *fq = (struct frag_queue *) data;
spin_lock(&fq->lock);
if (fq->last_in & COMPLETE)
goto out;
fq_kill(fq);
IP6_INC_STATS_BH(IPSTATS_MIB_REASMTIMEOUT);
IP6_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
/* Send error only if the first segment arrived. */
if (fq->last_in&FIRST_IN && fq->fragments) {
struct net_device *dev = dev_get_by_index(fq->iif);
/*
But use as source device on which LAST ARRIVED
segment was received. And do not use fq->dev
pointer directly, device might already disappeared.
*/
if (dev) {
fq->fragments->dev = dev;
icmpv6_send(fq->fragments, ICMPV6_TIME_EXCEED, ICMPV6_EXC_FRAGTIME, 0,
dev);
dev_put(dev);
}
}
out:
spin_unlock(&fq->lock);
fq_put(fq, NULL);
}
/* Creation primitives. */
static struct frag_queue *ip6_frag_intern(unsigned int hash,
struct frag_queue *fq_in)
{
struct frag_queue *fq;
write_lock(&ip6_frag_lock);
#ifdef CONFIG_SMP
for (fq = ip6_frag_hash[hash]; fq; fq = fq->next) {
if (fq->id == fq_in->id &&
ipv6_addr_equal(&fq_in->saddr, &fq->saddr) &&
ipv6_addr_equal(&fq_in->daddr, &fq->daddr)) {
atomic_inc(&fq->refcnt);
write_unlock(&ip6_frag_lock);
fq_in->last_in |= COMPLETE;
fq_put(fq_in, NULL);
return fq;
}
}
#endif
fq = fq_in;
if (!mod_timer(&fq->timer, jiffies + sysctl_ip6frag_time))
atomic_inc(&fq->refcnt);
atomic_inc(&fq->refcnt);
if((fq->next = ip6_frag_hash[hash]) != NULL)
fq->next->pprev = &fq->next;
ip6_frag_hash[hash] = fq;
fq->pprev = &ip6_frag_hash[hash];
INIT_LIST_HEAD(&fq->lru_list);
list_add_tail(&fq->lru_list, &ip6_frag_lru_list);
ip6_frag_nqueues++;
write_unlock(&ip6_frag_lock);
return fq;
}
static struct frag_queue *
ip6_frag_create(unsigned int hash, u32 id, struct in6_addr *src, struct in6_addr *dst)
{
struct frag_queue *fq;
if ((fq = frag_alloc_queue()) == NULL)
goto oom;
memset(fq, 0, sizeof(struct frag_queue));
fq->id = id;
ipv6_addr_copy(&fq->saddr, src);
ipv6_addr_copy(&fq->daddr, dst);
init_timer(&fq->timer);
fq->timer.function = ip6_frag_expire;
fq->timer.data = (long) fq;
spin_lock_init(&fq->lock);
atomic_set(&fq->refcnt, 1);
return ip6_frag_intern(hash, fq);
oom:
IP6_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
return NULL;
}
static __inline__ struct frag_queue *
fq_find(u32 id, struct in6_addr *src, struct in6_addr *dst)
{
struct frag_queue *fq;
unsigned int hash = ip6qhashfn(id, src, dst);
read_lock(&ip6_frag_lock);
for(fq = ip6_frag_hash[hash]; fq; fq = fq->next) {
if (fq->id == id &&
ipv6_addr_equal(src, &fq->saddr) &&
ipv6_addr_equal(dst, &fq->daddr)) {
atomic_inc(&fq->refcnt);
read_unlock(&ip6_frag_lock);
return fq;
}
}
read_unlock(&ip6_frag_lock);
return ip6_frag_create(hash, id, src, dst);
}
static void ip6_frag_queue(struct frag_queue *fq, struct sk_buff *skb,
struct frag_hdr *fhdr, int nhoff)
{
struct sk_buff *prev, *next;
int offset, end;
if (fq->last_in & COMPLETE)
goto err;
offset = ntohs(fhdr->frag_off) & ~0x7;
end = offset + (ntohs(skb->nh.ipv6h->payload_len) -
((u8 *) (fhdr + 1) - (u8 *) (skb->nh.ipv6h + 1)));
if ((unsigned int)end > IPV6_MAXPLEN) {
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
icmpv6_param_prob(skb,ICMPV6_HDR_FIELD, (u8*)&fhdr->frag_off - skb->nh.raw);
return;
}
if (skb->ip_summed == CHECKSUM_HW)
skb->csum = csum_sub(skb->csum,
csum_partial(skb->nh.raw, (u8*)(fhdr+1)-skb->nh.raw, 0));
/* Is this the final fragment? */
if (!(fhdr->frag_off & htons(IP6_MF))) {
/* If we already have some bits beyond end
* or have different end, the segment is corrupted.
*/
if (end < fq->len ||
((fq->last_in & LAST_IN) && end != fq->len))
goto err;
fq->last_in |= LAST_IN;
fq->len = end;
} else {
/* Check if the fragment is rounded to 8 bytes.
* Required by the RFC.
*/
if (end & 0x7) {
/* RFC2460 says always send parameter problem in
* this case. -DaveM
*/
IP6_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
icmpv6_param_prob(skb, ICMPV6_HDR_FIELD,
offsetof(struct ipv6hdr, payload_len));
return;
}
if (end > fq->len) {
/* Some bits beyond end -> corruption. */
if (fq->last_in & LAST_IN)
goto err;
fq->len = end;
}
}
if (end == offset)
goto err;
/* Point into the IP datagram 'data' part. */
if (!pskb_pull(skb, (u8 *) (fhdr + 1) - skb->data))
goto err;
if (end-offset < skb->len) {
if (pskb_trim(skb, end - offset))
goto err;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
/* Find out which fragments are in front and at the back of us
* in the chain of fragments so far. We must know where to put
* this fragment, right?
*/
prev = NULL;
for(next = fq->fragments; next != NULL; next = next->next) {
if (FRAG6_CB(next)->offset >= offset)
break; /* bingo! */
prev = next;
}
/* We found where to put this one. Check for overlap with
* preceding fragment, and, if needed, align things so that
* any overlaps are eliminated.
*/
if (prev) {
int i = (FRAG6_CB(prev)->offset + prev->len) - offset;
if (i > 0) {
offset += i;
if (end <= offset)
goto err;
if (!pskb_pull(skb, i))
goto err;
if (skb->ip_summed != CHECKSUM_UNNECESSARY)
skb->ip_summed = CHECKSUM_NONE;
}
}
/* Look for overlap with succeeding segments.
* If we can merge fragments, do it.
*/
while (next && FRAG6_CB(next)->offset < end) {
int i = end - FRAG6_CB(next)->offset; /* overlap is 'i' bytes */
if (i < next->len) {
/* Eat head of the next overlapped fragment
* and leave the loop. The next ones cannot overlap.
*/
if (!pskb_pull(next, i))
goto err;
FRAG6_CB(next)->offset += i; /* next fragment */
fq->meat -= i;
if (next->ip_summed != CHECKSUM_UNNECESSARY)
next->ip_summed = CHECKSUM_NONE;
break;
} else {
struct sk_buff *free_it = next;
/* Old fragment is completely overridden with
* new one drop it.
*/
next = next->next;
if (prev)
prev->next = next;
else
fq->fragments = next;
fq->meat -= free_it->len;
frag_kfree_skb(free_it, NULL);
}
}
FRAG6_CB(skb)->offset = offset;
/* Insert this fragment in the chain of fragments. */
skb->next = next;
if (prev)
prev->next = skb;
else
fq->fragments = skb;
if (skb->dev)
fq->iif = skb->dev->ifindex;
skb->dev = NULL;
fq->stamp = skb->stamp;
fq->meat += skb->len;
atomic_add(skb->truesize, &ip6_frag_mem);
/* The first fragment.
* nhoffset is obtained from the first fragment, of course.
*/
if (offset == 0) {
fq->nhoffset = nhoff;
fq->last_in |= FIRST_IN;
}
write_lock(&ip6_frag_lock);
list_move_tail(&fq->lru_list, &ip6_frag_lru_list);
write_unlock(&ip6_frag_lock);
return;
err:
IP6_INC_STATS(IPSTATS_MIB_REASMFAILS);
kfree_skb(skb);
}
/*
* Check if this packet is complete.
* Returns NULL on failure by any reason, and pointer
* to current nexthdr field in reassembled frame.
*
* It is called with locked fq, and caller must check that
* queue is eligible for reassembly i.e. it is not COMPLETE,
* the last and the first frames arrived and all the bits are here.
*/
static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff **skb_in,
unsigned int *nhoffp,
struct net_device *dev)
{
struct sk_buff *fp, *head = fq->fragments;
int payload_len;
unsigned int nhoff;
fq_kill(fq);
BUG_TRAP(head != NULL);
BUG_TRAP(FRAG6_CB(head)->offset == 0);
/* Unfragmented part is taken from the first segment. */
payload_len = (head->data - head->nh.raw) - sizeof(struct ipv6hdr) + fq->len - sizeof(struct frag_hdr);
if (payload_len > IPV6_MAXPLEN)
goto out_oversize;
/* Head of list must not be cloned. */
if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
goto out_oom;
/* If the first fragment is fragmented itself, we split
* it to two chunks: the first with data and paged part
* and the second, holding only fragments. */
if (skb_shinfo(head)->frag_list) {
struct sk_buff *clone;
int i, plen = 0;
if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)
goto out_oom;
clone->next = head->next;
head->next = clone;
skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;
skb_shinfo(head)->frag_list = NULL;
for (i=0; i<skb_shinfo(head)->nr_frags; i++)
plen += skb_shinfo(head)->frags[i].size;
clone->len = clone->data_len = head->data_len - plen;
head->data_len -= clone->len;
head->len -= clone->len;
clone->csum = 0;
clone->ip_summed = head->ip_summed;
atomic_add(clone->truesize, &ip6_frag_mem);
}
/* We have to remove fragment header from datagram and to relocate
* header in order to calculate ICV correctly. */
nhoff = fq->nhoffset;
head->nh.raw[nhoff] = head->h.raw[0];
memmove(head->head + sizeof(struct frag_hdr), head->head,
(head->data - head->head) - sizeof(struct frag_hdr));
head->mac.raw += sizeof(struct frag_hdr);
head->nh.raw += sizeof(struct frag_hdr);
skb_shinfo(head)->frag_list = head->next;
head->h.raw = head->data;
skb_push(head, head->data - head->nh.raw);
atomic_sub(head->truesize, &ip6_frag_mem);
for (fp=head->next; fp; fp = fp->next) {
head->data_len += fp->len;
head->len += fp->len;
if (head->ip_summed != fp->ip_summed)
head->ip_summed = CHECKSUM_NONE;
else if (head->ip_summed == CHECKSUM_HW)
head->csum = csum_add(head->csum, fp->csum);
head->truesize += fp->truesize;
atomic_sub(fp->truesize, &ip6_frag_mem);
}
head->next = NULL;
head->dev = dev;
head->stamp = fq->stamp;
head->nh.ipv6h->payload_len = htons(payload_len);
*skb_in = head;
/* Yes, and fold redundant checksum back. 8) */
if (head->ip_summed == CHECKSUM_HW)
head->csum = csum_partial(head->nh.raw, head->h.raw-head->nh.raw, head->csum);
IP6_INC_STATS_BH(IPSTATS_MIB_REASMOKS);
fq->fragments = NULL;
*nhoffp = nhoff;
return 1;
out_oversize:
if (net_ratelimit())
printk(KERN_DEBUG "ip6_frag_reasm: payload len = %d\n", payload_len);
goto out_fail;
out_oom:
if (net_ratelimit())
printk(KERN_DEBUG "ip6_frag_reasm: no memory for reassembly\n");
out_fail:
IP6_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
return -1;
}
static int ipv6_frag_rcv(struct sk_buff **skbp, unsigned int *nhoffp)
{
struct sk_buff *skb = *skbp;
struct net_device *dev = skb->dev;
struct frag_hdr *fhdr;
struct frag_queue *fq;
struct ipv6hdr *hdr;
hdr = skb->nh.ipv6h;
IP6_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);
/* Jumbo payload inhibits frag. header */
if (hdr->payload_len==0) {
IP6_INC_STATS(IPSTATS_MIB_INHDRERRORS);
icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, skb->h.raw-skb->nh.raw);
return -1;
}
if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+sizeof(struct frag_hdr))) {
IP6_INC_STATS(IPSTATS_MIB_INHDRERRORS);
icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, skb->h.raw-skb->nh.raw);
return -1;
}
hdr = skb->nh.ipv6h;
fhdr = (struct frag_hdr *)skb->h.raw;
if (!(fhdr->frag_off & htons(0xFFF9))) {
/* It is not a fragmented frame */
skb->h.raw += sizeof(struct frag_hdr);
IP6_INC_STATS_BH(IPSTATS_MIB_REASMOKS);
*nhoffp = (u8*)fhdr - skb->nh.raw;
return 1;
}
if (atomic_read(&ip6_frag_mem) > sysctl_ip6frag_high_thresh)
ip6_evictor();
if ((fq = fq_find(fhdr->identification, &hdr->saddr, &hdr->daddr)) != NULL) {
int ret = -1;
spin_lock(&fq->lock);
ip6_frag_queue(fq, skb, fhdr, *nhoffp);
if (fq->last_in == (FIRST_IN|LAST_IN) &&
fq->meat == fq->len)
ret = ip6_frag_reasm(fq, skbp, nhoffp, dev);
spin_unlock(&fq->lock);
fq_put(fq, NULL);
return ret;
}
IP6_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
kfree_skb(skb);
return -1;
}
static struct inet6_protocol frag_protocol =
{
.handler = ipv6_frag_rcv,
.flags = INET6_PROTO_NOPOLICY,
};
void __init ipv6_frag_init(void)
{
if (inet6_add_protocol(&frag_protocol, IPPROTO_FRAGMENT) < 0)
printk(KERN_ERR "ipv6_frag_init: Could not register protocol\n");
ip6_frag_hash_rnd = (u32) ((num_physpages ^ (num_physpages>>7)) ^
(jiffies ^ (jiffies >> 6)));
init_timer(&ip6_frag_secret_timer);
ip6_frag_secret_timer.function = ip6_frag_secret_rebuild;
ip6_frag_secret_timer.expires = jiffies + sysctl_ip6frag_secret_interval;
add_timer(&ip6_frag_secret_timer);
}

2131
net/ipv6/route.c Normal file

File diff suppressed because it is too large Load Diff

833
net/ipv6/sit.c Normal file
View File

@@ -0,0 +1,833 @@
/*
* IPv6 over IPv4 tunnel device - Simple Internet Transition (SIT)
* Linux INET6 implementation
*
* Authors:
* Pedro Roque <roque@di.fc.ul.pt>
* Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
*
* $Id: sit.c,v 1.53 2001/09/25 05:09:53 davem 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.
*
* Changes:
* Roger Venning <r.venning@telstra.com>: 6to4 support
* Nate Thompson <nate@thebog.net>: 6to4 support
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/sched.h>
#include <linux/net.h>
#include <linux/in6.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/icmp.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <linux/netfilter_ipv4.h>
#include <net/sock.h>
#include <net/snmp.h>
#include <net/ipv6.h>
#include <net/protocol.h>
#include <net/transp_v6.h>
#include <net/ip6_fib.h>
#include <net/ip6_route.h>
#include <net/ndisc.h>
#include <net/addrconf.h>
#include <net/ip.h>
#include <net/udp.h>
#include <net/icmp.h>
#include <net/ipip.h>
#include <net/inet_ecn.h>
#include <net/xfrm.h>
#include <net/dsfield.h>
/*
This version of net/ipv6/sit.c is cloned of net/ipv4/ip_gre.c
For comments look at net/ipv4/ip_gre.c --ANK
*/
#define HASH_SIZE 16
#define HASH(addr) ((addr^(addr>>4))&0xF)
static int ipip6_fb_tunnel_init(struct net_device *dev);
static int ipip6_tunnel_init(struct net_device *dev);
static void ipip6_tunnel_setup(struct net_device *dev);
static struct net_device *ipip6_fb_tunnel_dev;
static struct ip_tunnel *tunnels_r_l[HASH_SIZE];
static struct ip_tunnel *tunnels_r[HASH_SIZE];
static struct ip_tunnel *tunnels_l[HASH_SIZE];
static struct ip_tunnel *tunnels_wc[1];
static struct ip_tunnel **tunnels[4] = { tunnels_wc, tunnels_l, tunnels_r, tunnels_r_l };
static DEFINE_RWLOCK(ipip6_lock);
static struct ip_tunnel * ipip6_tunnel_lookup(u32 remote, u32 local)
{
unsigned h0 = HASH(remote);
unsigned h1 = HASH(local);
struct ip_tunnel *t;
for (t = tunnels_r_l[h0^h1]; t; t = t->next) {
if (local == t->parms.iph.saddr &&
remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP))
return t;
}
for (t = tunnels_r[h0]; t; t = t->next) {
if (remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP))
return t;
}
for (t = tunnels_l[h1]; t; t = t->next) {
if (local == t->parms.iph.saddr && (t->dev->flags&IFF_UP))
return t;
}
if ((t = tunnels_wc[0]) != NULL && (t->dev->flags&IFF_UP))
return t;
return NULL;
}
static struct ip_tunnel ** ipip6_bucket(struct ip_tunnel *t)
{
u32 remote = t->parms.iph.daddr;
u32 local = t->parms.iph.saddr;
unsigned h = 0;
int prio = 0;
if (remote) {
prio |= 2;
h ^= HASH(remote);
}
if (local) {
prio |= 1;
h ^= HASH(local);
}
return &tunnels[prio][h];
}
static void ipip6_tunnel_unlink(struct ip_tunnel *t)
{
struct ip_tunnel **tp;
for (tp = ipip6_bucket(t); *tp; tp = &(*tp)->next) {
if (t == *tp) {
write_lock_bh(&ipip6_lock);
*tp = t->next;
write_unlock_bh(&ipip6_lock);
break;
}
}
}
static void ipip6_tunnel_link(struct ip_tunnel *t)
{
struct ip_tunnel **tp = ipip6_bucket(t);
t->next = *tp;
write_lock_bh(&ipip6_lock);
*tp = t;
write_unlock_bh(&ipip6_lock);
}
static struct ip_tunnel * ipip6_tunnel_locate(struct ip_tunnel_parm *parms, int create)
{
u32 remote = parms->iph.daddr;
u32 local = parms->iph.saddr;
struct ip_tunnel *t, **tp, *nt;
struct net_device *dev;
unsigned h = 0;
int prio = 0;
char name[IFNAMSIZ];
if (remote) {
prio |= 2;
h ^= HASH(remote);
}
if (local) {
prio |= 1;
h ^= HASH(local);
}
for (tp = &tunnels[prio][h]; (t = *tp) != NULL; tp = &t->next) {
if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr)
return t;
}
if (!create)
goto failed;
if (parms->name[0])
strlcpy(name, parms->name, IFNAMSIZ);
else {
int i;
for (i=1; i<100; i++) {
sprintf(name, "sit%d", i);
if (__dev_get_by_name(name) == NULL)
break;
}
if (i==100)
goto failed;
}
dev = alloc_netdev(sizeof(*t), name, ipip6_tunnel_setup);
if (dev == NULL)
return NULL;
nt = dev->priv;
dev->init = ipip6_tunnel_init;
nt->parms = *parms;
if (register_netdevice(dev) < 0) {
free_netdev(dev);
goto failed;
}
dev_hold(dev);
ipip6_tunnel_link(nt);
/* Do not decrement MOD_USE_COUNT here. */
return nt;
failed:
return NULL;
}
static void ipip6_tunnel_uninit(struct net_device *dev)
{
if (dev == ipip6_fb_tunnel_dev) {
write_lock_bh(&ipip6_lock);
tunnels_wc[0] = NULL;
write_unlock_bh(&ipip6_lock);
dev_put(dev);
} else {
ipip6_tunnel_unlink((struct ip_tunnel*)dev->priv);
dev_put(dev);
}
}
static void ipip6_err(struct sk_buff *skb, u32 info)
{
#ifndef I_WISH_WORLD_WERE_PERFECT
/* It is not :-( All the routers (except for Linux) return only
8 bytes of packet payload. It means, that precise relaying of
ICMP in the real Internet is absolutely infeasible.
*/
struct iphdr *iph = (struct iphdr*)skb->data;
int type = skb->h.icmph->type;
int code = skb->h.icmph->code;
struct ip_tunnel *t;
switch (type) {
default:
case ICMP_PARAMETERPROB:
return;
case ICMP_DEST_UNREACH:
switch (code) {
case ICMP_SR_FAILED:
case ICMP_PORT_UNREACH:
/* Impossible event. */
return;
case ICMP_FRAG_NEEDED:
/* Soft state for pmtu is maintained by IP core. */
return;
default:
/* All others are translated to HOST_UNREACH.
rfc2003 contains "deep thoughts" about NET_UNREACH,
I believe they are just ether pollution. --ANK
*/
break;
}
break;
case ICMP_TIME_EXCEEDED:
if (code != ICMP_EXC_TTL)
return;
break;
}
read_lock(&ipip6_lock);
t = ipip6_tunnel_lookup(iph->daddr, iph->saddr);
if (t == NULL || t->parms.iph.daddr == 0)
goto out;
if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
goto out;
if (jiffies - t->err_time < IPTUNNEL_ERR_TIMEO)
t->err_count++;
else
t->err_count = 1;
t->err_time = jiffies;
out:
read_unlock(&ipip6_lock);
return;
#else
struct iphdr *iph = (struct iphdr*)dp;
int hlen = iph->ihl<<2;
struct ipv6hdr *iph6;
int type = skb->h.icmph->type;
int code = skb->h.icmph->code;
int rel_type = 0;
int rel_code = 0;
int rel_info = 0;
struct sk_buff *skb2;
struct rt6_info *rt6i;
if (len < hlen + sizeof(struct ipv6hdr))
return;
iph6 = (struct ipv6hdr*)(dp + hlen);
switch (type) {
default:
return;
case ICMP_PARAMETERPROB:
if (skb->h.icmph->un.gateway < hlen)
return;
/* So... This guy found something strange INSIDE encapsulated
packet. Well, he is fool, but what can we do ?
*/
rel_type = ICMPV6_PARAMPROB;
rel_info = skb->h.icmph->un.gateway - hlen;
break;
case ICMP_DEST_UNREACH:
switch (code) {
case ICMP_SR_FAILED:
case ICMP_PORT_UNREACH:
/* Impossible event. */
return;
case ICMP_FRAG_NEEDED:
/* Too complicated case ... */
return;
default:
/* All others are translated to HOST_UNREACH.
rfc2003 contains "deep thoughts" about NET_UNREACH,
I believe, it is just ether pollution. --ANK
*/
rel_type = ICMPV6_DEST_UNREACH;
rel_code = ICMPV6_ADDR_UNREACH;
break;
}
break;
case ICMP_TIME_EXCEEDED:
if (code != ICMP_EXC_TTL)
return;
rel_type = ICMPV6_TIME_EXCEED;
rel_code = ICMPV6_EXC_HOPLIMIT;
break;
}
/* Prepare fake skb to feed it to icmpv6_send */
skb2 = skb_clone(skb, GFP_ATOMIC);
if (skb2 == NULL)
return;
dst_release(skb2->dst);
skb2->dst = NULL;
skb_pull(skb2, skb->data - (u8*)iph6);
skb2->nh.raw = skb2->data;
/* Try to guess incoming interface */
rt6i = rt6_lookup(&iph6->saddr, NULL, NULL, 0);
if (rt6i && rt6i->rt6i_dev) {
skb2->dev = rt6i->rt6i_dev;
rt6i = rt6_lookup(&iph6->daddr, &iph6->saddr, NULL, 0);
if (rt6i && rt6i->rt6i_dev && rt6i->rt6i_dev->type == ARPHRD_SIT) {
struct ip_tunnel * t = (struct ip_tunnel*)rt6i->rt6i_dev->priv;
if (rel_type == ICMPV6_TIME_EXCEED && t->parms.iph.ttl) {
rel_type = ICMPV6_DEST_UNREACH;
rel_code = ICMPV6_ADDR_UNREACH;
}
icmpv6_send(skb2, rel_type, rel_code, rel_info, skb2->dev);
}
}
kfree_skb(skb2);
return;
#endif
}
static inline void ipip6_ecn_decapsulate(struct iphdr *iph, struct sk_buff *skb)
{
if (INET_ECN_is_ce(iph->tos))
IP6_ECN_set_ce(skb->nh.ipv6h);
}
static int ipip6_rcv(struct sk_buff *skb)
{
struct iphdr *iph;
struct ip_tunnel *tunnel;
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
goto out;
iph = skb->nh.iph;
read_lock(&ipip6_lock);
if ((tunnel = ipip6_tunnel_lookup(iph->saddr, iph->daddr)) != NULL) {
secpath_reset(skb);
skb->mac.raw = skb->nh.raw;
skb->nh.raw = skb->data;
memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
skb->protocol = htons(ETH_P_IPV6);
skb->pkt_type = PACKET_HOST;
tunnel->stat.rx_packets++;
tunnel->stat.rx_bytes += skb->len;
skb->dev = tunnel->dev;
dst_release(skb->dst);
skb->dst = NULL;
nf_reset(skb);
ipip6_ecn_decapsulate(iph, skb);
netif_rx(skb);
read_unlock(&ipip6_lock);
return 0;
}
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0);
kfree_skb(skb);
read_unlock(&ipip6_lock);
out:
return 0;
}
/* Returns the embedded IPv4 address if the IPv6 address
comes from 6to4 (RFC 3056) addr space */
static inline u32 try_6to4(struct in6_addr *v6dst)
{
u32 dst = 0;
if (v6dst->s6_addr16[0] == htons(0x2002)) {
/* 6to4 v6 addr has 16 bits prefix, 32 v4addr, 16 SLA, ... */
memcpy(&dst, &v6dst->s6_addr16[1], 4);
}
return dst;
}
/*
* This function assumes it is being called from dev_queue_xmit()
* and that skb is filled properly by that function.
*/
static int ipip6_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct ip_tunnel *tunnel = (struct ip_tunnel*)dev->priv;
struct net_device_stats *stats = &tunnel->stat;
struct iphdr *tiph = &tunnel->parms.iph;
struct ipv6hdr *iph6 = skb->nh.ipv6h;
u8 tos = tunnel->parms.iph.tos;
struct rtable *rt; /* Route to the other host */
struct net_device *tdev; /* Device to other host */
struct iphdr *iph; /* Our new IP header */
int max_headroom; /* The extra header space needed */
u32 dst = tiph->daddr;
int mtu;
struct in6_addr *addr6;
int addr_type;
if (tunnel->recursion++) {
tunnel->stat.collisions++;
goto tx_error;
}
if (skb->protocol != htons(ETH_P_IPV6))
goto tx_error;
if (!dst)
dst = try_6to4(&iph6->daddr);
if (!dst) {
struct neighbour *neigh = NULL;
if (skb->dst)
neigh = skb->dst->neighbour;
if (neigh == NULL) {
if (net_ratelimit())
printk(KERN_DEBUG "sit: nexthop == NULL\n");
goto tx_error;
}
addr6 = (struct in6_addr*)&neigh->primary_key;
addr_type = ipv6_addr_type(addr6);
if (addr_type == IPV6_ADDR_ANY) {
addr6 = &skb->nh.ipv6h->daddr;
addr_type = ipv6_addr_type(addr6);
}
if ((addr_type & IPV6_ADDR_COMPATv4) == 0)
goto tx_error_icmp;
dst = addr6->s6_addr32[3];
}
{
struct flowi fl = { .nl_u = { .ip4_u =
{ .daddr = dst,
.saddr = tiph->saddr,
.tos = RT_TOS(tos) } },
.oif = tunnel->parms.link,
.proto = IPPROTO_IPV6 };
if (ip_route_output_key(&rt, &fl)) {
tunnel->stat.tx_carrier_errors++;
goto tx_error_icmp;
}
}
if (rt->rt_type != RTN_UNICAST) {
ip_rt_put(rt);
tunnel->stat.tx_carrier_errors++;
goto tx_error_icmp;
}
tdev = rt->u.dst.dev;
if (tdev == dev) {
ip_rt_put(rt);
tunnel->stat.collisions++;
goto tx_error;
}
if (tiph->frag_off)
mtu = dst_mtu(&rt->u.dst) - sizeof(struct iphdr);
else
mtu = skb->dst ? dst_mtu(skb->dst) : dev->mtu;
if (mtu < 68) {
tunnel->stat.collisions++;
ip_rt_put(rt);
goto tx_error;
}
if (mtu < IPV6_MIN_MTU)
mtu = IPV6_MIN_MTU;
if (tunnel->parms.iph.daddr && skb->dst)
skb->dst->ops->update_pmtu(skb->dst, mtu);
if (skb->len > mtu) {
icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev);
ip_rt_put(rt);
goto tx_error;
}
if (tunnel->err_count > 0) {
if (jiffies - tunnel->err_time < IPTUNNEL_ERR_TIMEO) {
tunnel->err_count--;
dst_link_failure(skb);
} else
tunnel->err_count = 0;
}
/*
* Okay, now see if we can stuff it in the buffer as-is.
*/
max_headroom = LL_RESERVED_SPACE(tdev)+sizeof(struct iphdr);
if (skb_headroom(skb) < max_headroom || skb_cloned(skb) || skb_shared(skb)) {
struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
if (!new_skb) {
ip_rt_put(rt);
stats->tx_dropped++;
dev_kfree_skb(skb);
tunnel->recursion--;
return 0;
}
if (skb->sk)
skb_set_owner_w(new_skb, skb->sk);
dev_kfree_skb(skb);
skb = new_skb;
iph6 = skb->nh.ipv6h;
}
skb->h.raw = skb->nh.raw;
skb->nh.raw = skb_push(skb, sizeof(struct iphdr));
memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
dst_release(skb->dst);
skb->dst = &rt->u.dst;
/*
* Push down and install the IPIP header.
*/
iph = skb->nh.iph;
iph->version = 4;
iph->ihl = sizeof(struct iphdr)>>2;
if (mtu > IPV6_MIN_MTU)
iph->frag_off = htons(IP_DF);
else
iph->frag_off = 0;
iph->protocol = IPPROTO_IPV6;
iph->tos = INET_ECN_encapsulate(tos, ipv6_get_dsfield(iph6));
iph->daddr = rt->rt_dst;
iph->saddr = rt->rt_src;
if ((iph->ttl = tiph->ttl) == 0)
iph->ttl = iph6->hop_limit;
nf_reset(skb);
IPTUNNEL_XMIT();
tunnel->recursion--;
return 0;
tx_error_icmp:
dst_link_failure(skb);
tx_error:
stats->tx_errors++;
dev_kfree_skb(skb);
tunnel->recursion--;
return 0;
}
static int
ipip6_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)
{
int err = 0;
struct ip_tunnel_parm p;
struct ip_tunnel *t;
switch (cmd) {
case SIOCGETTUNNEL:
t = NULL;
if (dev == ipip6_fb_tunnel_dev) {
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) {
err = -EFAULT;
break;
}
t = ipip6_tunnel_locate(&p, 0);
}
if (t == NULL)
t = (struct ip_tunnel*)dev->priv;
memcpy(&p, &t->parms, sizeof(p));
if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
err = -EFAULT;
break;
case SIOCADDTUNNEL:
case SIOCCHGTUNNEL:
err = -EPERM;
if (!capable(CAP_NET_ADMIN))
goto done;
err = -EFAULT;
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
goto done;
err = -EINVAL;
if (p.iph.version != 4 || p.iph.protocol != IPPROTO_IPV6 ||
p.iph.ihl != 5 || (p.iph.frag_off&htons(~IP_DF)))
goto done;
if (p.iph.ttl)
p.iph.frag_off |= htons(IP_DF);
t = ipip6_tunnel_locate(&p, cmd == SIOCADDTUNNEL);
if (dev != ipip6_fb_tunnel_dev && cmd == SIOCCHGTUNNEL) {
if (t != NULL) {
if (t->dev != dev) {
err = -EEXIST;
break;
}
} else {
if (((dev->flags&IFF_POINTOPOINT) && !p.iph.daddr) ||
(!(dev->flags&IFF_POINTOPOINT) && p.iph.daddr)) {
err = -EINVAL;
break;
}
t = (struct ip_tunnel*)dev->priv;
ipip6_tunnel_unlink(t);
t->parms.iph.saddr = p.iph.saddr;
t->parms.iph.daddr = p.iph.daddr;
memcpy(dev->dev_addr, &p.iph.saddr, 4);
memcpy(dev->broadcast, &p.iph.daddr, 4);
ipip6_tunnel_link(t);
netdev_state_change(dev);
}
}
if (t) {
err = 0;
if (cmd == SIOCCHGTUNNEL) {
t->parms.iph.ttl = p.iph.ttl;
t->parms.iph.tos = p.iph.tos;
}
if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof(p)))
err = -EFAULT;
} else
err = (cmd == SIOCADDTUNNEL ? -ENOBUFS : -ENOENT);
break;
case SIOCDELTUNNEL:
err = -EPERM;
if (!capable(CAP_NET_ADMIN))
goto done;
if (dev == ipip6_fb_tunnel_dev) {
err = -EFAULT;
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
goto done;
err = -ENOENT;
if ((t = ipip6_tunnel_locate(&p, 0)) == NULL)
goto done;
err = -EPERM;
if (t == ipip6_fb_tunnel_dev->priv)
goto done;
dev = t->dev;
}
err = unregister_netdevice(dev);
break;
default:
err = -EINVAL;
}
done:
return err;
}
static struct net_device_stats *ipip6_tunnel_get_stats(struct net_device *dev)
{
return &(((struct ip_tunnel*)dev->priv)->stat);
}
static int ipip6_tunnel_change_mtu(struct net_device *dev, int new_mtu)
{
if (new_mtu < IPV6_MIN_MTU || new_mtu > 0xFFF8 - sizeof(struct iphdr))
return -EINVAL;
dev->mtu = new_mtu;
return 0;
}
static void ipip6_tunnel_setup(struct net_device *dev)
{
SET_MODULE_OWNER(dev);
dev->uninit = ipip6_tunnel_uninit;
dev->destructor = free_netdev;
dev->hard_start_xmit = ipip6_tunnel_xmit;
dev->get_stats = ipip6_tunnel_get_stats;
dev->do_ioctl = ipip6_tunnel_ioctl;
dev->change_mtu = ipip6_tunnel_change_mtu;
dev->type = ARPHRD_SIT;
dev->hard_header_len = LL_MAX_HEADER + sizeof(struct iphdr);
dev->mtu = 1500 - sizeof(struct iphdr);
dev->flags = IFF_NOARP;
dev->iflink = 0;
dev->addr_len = 4;
}
static int ipip6_tunnel_init(struct net_device *dev)
{
struct net_device *tdev = NULL;
struct ip_tunnel *tunnel;
struct iphdr *iph;
tunnel = (struct ip_tunnel*)dev->priv;
iph = &tunnel->parms.iph;
tunnel->dev = dev;
strcpy(tunnel->parms.name, dev->name);
memcpy(dev->dev_addr, &tunnel->parms.iph.saddr, 4);
memcpy(dev->broadcast, &tunnel->parms.iph.daddr, 4);
if (iph->daddr) {
struct flowi fl = { .nl_u = { .ip4_u =
{ .daddr = iph->daddr,
.saddr = iph->saddr,
.tos = RT_TOS(iph->tos) } },
.oif = tunnel->parms.link,
.proto = IPPROTO_IPV6 };
struct rtable *rt;
if (!ip_route_output_key(&rt, &fl)) {
tdev = rt->u.dst.dev;
ip_rt_put(rt);
}
dev->flags |= IFF_POINTOPOINT;
}
if (!tdev && tunnel->parms.link)
tdev = __dev_get_by_index(tunnel->parms.link);
if (tdev) {
dev->hard_header_len = tdev->hard_header_len + sizeof(struct iphdr);
dev->mtu = tdev->mtu - sizeof(struct iphdr);
if (dev->mtu < IPV6_MIN_MTU)
dev->mtu = IPV6_MIN_MTU;
}
dev->iflink = tunnel->parms.link;
return 0;
}
int __init ipip6_fb_tunnel_init(struct net_device *dev)
{
struct ip_tunnel *tunnel = dev->priv;
struct iphdr *iph = &tunnel->parms.iph;
tunnel->dev = dev;
strcpy(tunnel->parms.name, dev->name);
iph->version = 4;
iph->protocol = IPPROTO_IPV6;
iph->ihl = 5;
iph->ttl = 64;
dev_hold(dev);
tunnels_wc[0] = tunnel;
return 0;
}
static struct net_protocol sit_protocol = {
.handler = ipip6_rcv,
.err_handler = ipip6_err,
};
void __exit sit_cleanup(void)
{
inet_del_protocol(&sit_protocol, IPPROTO_IPV6);
unregister_netdev(ipip6_fb_tunnel_dev);
}
int __init sit_init(void)
{
int err;
printk(KERN_INFO "IPv6 over IPv4 tunneling driver\n");
if (inet_add_protocol(&sit_protocol, IPPROTO_IPV6) < 0) {
printk(KERN_INFO "sit init: Can't add protocol\n");
return -EAGAIN;
}
ipip6_fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel), "sit0",
ipip6_tunnel_setup);
if (!ipip6_fb_tunnel_dev) {
err = -ENOMEM;
goto err1;
}
ipip6_fb_tunnel_dev->init = ipip6_fb_tunnel_init;
if ((err = register_netdev(ipip6_fb_tunnel_dev)))
goto err2;
out:
return err;
err2:
free_netdev(ipip6_fb_tunnel_dev);
err1:
inet_del_protocol(&sit_protocol, IPPROTO_IPV6);
goto out;
}

125
net/ipv6/sysctl_net_ipv6.c Normal file
View File

@@ -0,0 +1,125 @@
/*
* sysctl_net_ipv6.c: sysctl interface to net IPV6 subsystem.
*
* Changes:
* YOSHIFUJI Hideaki @USAGI: added icmp sysctl table.
*/
#include <linux/mm.h>
#include <linux/sysctl.h>
#include <linux/config.h>
#include <linux/in6.h>
#include <linux/ipv6.h>
#include <net/ndisc.h>
#include <net/ipv6.h>
#include <net/addrconf.h>
extern ctl_table ipv6_route_table[];
extern ctl_table ipv6_icmp_table[];
#ifdef CONFIG_SYSCTL
static ctl_table ipv6_table[] = {
{
.ctl_name = NET_IPV6_ROUTE,
.procname = "route",
.maxlen = 0,
.mode = 0555,
.child = ipv6_route_table
},
{
.ctl_name = NET_IPV6_ICMP,
.procname = "icmp",
.maxlen = 0,
.mode = 0555,
.child = ipv6_icmp_table
},
{
.ctl_name = NET_IPV6_BINDV6ONLY,
.procname = "bindv6only",
.data = &sysctl_ipv6_bindv6only,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{
.ctl_name = NET_IPV6_IP6FRAG_HIGH_THRESH,
.procname = "ip6frag_high_thresh",
.data = &sysctl_ip6frag_high_thresh,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{
.ctl_name = NET_IPV6_IP6FRAG_LOW_THRESH,
.procname = "ip6frag_low_thresh",
.data = &sysctl_ip6frag_low_thresh,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{
.ctl_name = NET_IPV6_IP6FRAG_TIME,
.procname = "ip6frag_time",
.data = &sysctl_ip6frag_time,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec_jiffies,
.strategy = &sysctl_jiffies,
},
{
.ctl_name = NET_IPV6_IP6FRAG_SECRET_INTERVAL,
.procname = "ip6frag_secret_interval",
.data = &sysctl_ip6frag_secret_interval,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec_jiffies,
.strategy = &sysctl_jiffies
},
{
.ctl_name = NET_IPV6_MLD_MAX_MSF,
.procname = "mld_max_msf",
.data = &sysctl_mld_max_msf,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{ .ctl_name = 0 }
};
static struct ctl_table_header *ipv6_sysctl_header;
static ctl_table ipv6_net_table[] = {
{
.ctl_name = NET_IPV6,
.procname = "ipv6",
.mode = 0555,
.child = ipv6_table
},
{ .ctl_name = 0 }
};
static ctl_table ipv6_root_table[] = {
{
.ctl_name = CTL_NET,
.procname = "net",
.mode = 0555,
.child = ipv6_net_table
},
{ .ctl_name = 0 }
};
void ipv6_sysctl_register(void)
{
ipv6_sysctl_header = register_sysctl_table(ipv6_root_table, 0);
}
void ipv6_sysctl_unregister(void)
{
unregister_sysctl_table(ipv6_sysctl_header);
}
#endif /* CONFIG_SYSCTL */

2265
net/ipv6/tcp_ipv6.c Normal file

File diff suppressed because it is too large Load Diff

1075
net/ipv6/udp.c Normal file

File diff suppressed because it is too large Load Diff

150
net/ipv6/xfrm6_input.c Normal file
View File

@@ -0,0 +1,150 @@
/*
* xfrm6_input.c: based on net/ipv4/xfrm4_input.c
*
* Authors:
* Mitsuru KANDA @USAGI
* Kazunori MIYAZAWA @USAGI
* Kunihiro Ishiguro <kunihiro@ipinfusion.com>
* YOSHIFUJI Hideaki @USAGI
* IPv6 support
*/
#include <linux/module.h>
#include <linux/string.h>
#include <net/dsfield.h>
#include <net/inet_ecn.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/xfrm.h>
static inline void ipip6_ecn_decapsulate(struct sk_buff *skb)
{
struct ipv6hdr *outer_iph = skb->nh.ipv6h;
struct ipv6hdr *inner_iph = skb->h.ipv6h;
if (INET_ECN_is_ce(ipv6_get_dsfield(outer_iph)))
IP6_ECN_set_ce(inner_iph);
}
int xfrm6_rcv_spi(struct sk_buff **pskb, unsigned int *nhoffp, u32 spi)
{
struct sk_buff *skb = *pskb;
int err;
u32 seq;
struct sec_decap_state xfrm_vec[XFRM_MAX_DEPTH];
struct xfrm_state *x;
int xfrm_nr = 0;
int decaps = 0;
int nexthdr;
unsigned int nhoff;
nhoff = *nhoffp;
nexthdr = skb->nh.raw[nhoff];
seq = 0;
if (!spi && (err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) != 0)
goto drop;
do {
struct ipv6hdr *iph = skb->nh.ipv6h;
if (xfrm_nr == XFRM_MAX_DEPTH)
goto drop;
x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, spi, nexthdr, AF_INET6);
if (x == NULL)
goto drop;
spin_lock(&x->lock);
if (unlikely(x->km.state != XFRM_STATE_VALID))
goto drop_unlock;
if (x->props.replay_window && xfrm_replay_check(x, seq))
goto drop_unlock;
if (xfrm_state_check_expire(x))
goto drop_unlock;
nexthdr = x->type->input(x, &(xfrm_vec[xfrm_nr].decap), skb);
if (nexthdr <= 0)
goto drop_unlock;
skb->nh.raw[nhoff] = nexthdr;
if (x->props.replay_window)
xfrm_replay_advance(x, seq);
x->curlft.bytes += skb->len;
x->curlft.packets++;
spin_unlock(&x->lock);
xfrm_vec[xfrm_nr++].xvec = x;
if (x->props.mode) { /* XXX */
if (nexthdr != IPPROTO_IPV6)
goto drop;
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
goto drop;
if (skb_cloned(skb) &&
pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
goto drop;
if (x->props.flags & XFRM_STATE_DECAP_DSCP)
ipv6_copy_dscp(skb->nh.ipv6h, skb->h.ipv6h);
if (!(x->props.flags & XFRM_STATE_NOECN))
ipip6_ecn_decapsulate(skb);
skb->mac.raw = memmove(skb->data - skb->mac_len,
skb->mac.raw, skb->mac_len);
skb->nh.raw = skb->data;
decaps = 1;
break;
}
if ((err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) < 0)
goto drop;
} while (!err);
/* Allocate new secpath or COW existing one. */
if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
struct sec_path *sp;
sp = secpath_dup(skb->sp);
if (!sp)
goto drop;
if (skb->sp)
secpath_put(skb->sp);
skb->sp = sp;
}
if (xfrm_nr + skb->sp->len > XFRM_MAX_DEPTH)
goto drop;
memcpy(skb->sp->x+skb->sp->len, xfrm_vec, xfrm_nr*sizeof(struct sec_decap_state));
skb->sp->len += xfrm_nr;
skb->ip_summed = CHECKSUM_NONE;
if (decaps) {
if (!(skb->dev->flags&IFF_LOOPBACK)) {
dst_release(skb->dst);
skb->dst = NULL;
}
netif_rx(skb);
return -1;
} else {
return 1;
}
drop_unlock:
spin_unlock(&x->lock);
xfrm_state_put(x);
drop:
while (--xfrm_nr >= 0)
xfrm_state_put(xfrm_vec[xfrm_nr].xvec);
kfree_skb(skb);
return -1;
}
EXPORT_SYMBOL(xfrm6_rcv_spi);
int xfrm6_rcv(struct sk_buff **pskb, unsigned int *nhoffp)
{
return xfrm6_rcv_spi(pskb, nhoffp, 0);
}

143
net/ipv6/xfrm6_output.c Normal file
View File

@@ -0,0 +1,143 @@
/*
* xfrm6_output.c - Common IPsec encapsulation code for IPv6.
* Copyright (C) 2002 USAGI/WIDE Project
* Copyright (c) 2004 Herbert Xu <herbert@gondor.apana.org.au>
*
* 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/skbuff.h>
#include <linux/spinlock.h>
#include <linux/icmpv6.h>
#include <net/dsfield.h>
#include <net/inet_ecn.h>
#include <net/ipv6.h>
#include <net/xfrm.h>
/* Add encapsulation header.
*
* In transport mode, the IP header and mutable extension headers will be moved
* forward to make space for the encapsulation header.
*
* In tunnel mode, the top IP header will be constructed per RFC 2401.
* The following fields in it shall be filled in by x->type->output:
* payload_len
*
* On exit, skb->h will be set to the start of the encapsulation header to be
* filled in by x->type->output and skb->nh will be set to the nextheader field
* of the extension header directly preceding the encapsulation header, or in
* its absence, that of the top IP header. The value of skb->data will always
* point to the top IP header.
*/
static void xfrm6_encap(struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst;
struct xfrm_state *x = dst->xfrm;
struct ipv6hdr *iph, *top_iph;
int dsfield;
skb_push(skb, x->props.header_len);
iph = skb->nh.ipv6h;
if (!x->props.mode) {
u8 *prevhdr;
int hdr_len;
hdr_len = ip6_find_1stfragopt(skb, &prevhdr);
skb->nh.raw = prevhdr - x->props.header_len;
skb->h.raw = skb->data + hdr_len;
memmove(skb->data, iph, hdr_len);
return;
}
skb->nh.raw = skb->data;
top_iph = skb->nh.ipv6h;
skb->nh.raw = &top_iph->nexthdr;
skb->h.ipv6h = top_iph + 1;
top_iph->version = 6;
top_iph->priority = iph->priority;
top_iph->flow_lbl[0] = iph->flow_lbl[0];
top_iph->flow_lbl[1] = iph->flow_lbl[1];
top_iph->flow_lbl[2] = iph->flow_lbl[2];
dsfield = ipv6_get_dsfield(top_iph);
dsfield = INET_ECN_encapsulate(dsfield, dsfield);
if (x->props.flags & XFRM_STATE_NOECN)
dsfield &= ~INET_ECN_MASK;
ipv6_change_dsfield(top_iph, 0, dsfield);
top_iph->nexthdr = IPPROTO_IPV6;
top_iph->hop_limit = dst_metric(dst->child, RTAX_HOPLIMIT);
ipv6_addr_copy(&top_iph->saddr, (struct in6_addr *)&x->props.saddr);
ipv6_addr_copy(&top_iph->daddr, (struct in6_addr *)&x->id.daddr);
}
static int xfrm6_tunnel_check_size(struct sk_buff *skb)
{
int mtu, ret = 0;
struct dst_entry *dst = skb->dst;
mtu = dst_mtu(dst);
if (mtu < IPV6_MIN_MTU)
mtu = IPV6_MIN_MTU;
if (skb->len > mtu) {
icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, skb->dev);
ret = -EMSGSIZE;
}
return ret;
}
int xfrm6_output(struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst;
struct xfrm_state *x = dst->xfrm;
int err;
if (skb->ip_summed == CHECKSUM_HW) {
err = skb_checksum_help(skb, 0);
if (err)
goto error_nolock;
}
if (x->props.mode) {
err = xfrm6_tunnel_check_size(skb);
if (err)
goto error_nolock;
}
spin_lock_bh(&x->lock);
err = xfrm_state_check(x, skb);
if (err)
goto error;
xfrm6_encap(skb);
err = x->type->output(x, skb);
if (err)
goto error;
x->curlft.bytes += skb->len;
x->curlft.packets++;
spin_unlock_bh(&x->lock);
skb->nh.raw = skb->data;
if (!(skb->dst = dst_pop(dst))) {
err = -EHOSTUNREACH;
goto error_nolock;
}
err = NET_XMIT_BYPASS;
out_exit:
return err;
error:
spin_unlock_bh(&x->lock);
error_nolock:
kfree_skb(skb);
goto out_exit;
}

295
net/ipv6/xfrm6_policy.c Normal file
View File

@@ -0,0 +1,295 @@
/*
* xfrm6_policy.c: based on xfrm4_policy.c
*
* Authors:
* Mitsuru KANDA @USAGI
* Kazunori MIYAZAWA @USAGI
* Kunihiro Ishiguro <kunihiro@ipinfusion.com>
* IPv6 support
* YOSHIFUJI Hideaki
* Split up af-specific portion
*
*/
#include <linux/config.h>
#include <net/xfrm.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/ip6_route.h>
static struct dst_ops xfrm6_dst_ops;
static struct xfrm_policy_afinfo xfrm6_policy_afinfo;
static struct xfrm_type_map xfrm6_type_map = { .lock = RW_LOCK_UNLOCKED };
static int xfrm6_dst_lookup(struct xfrm_dst **dst, struct flowi *fl)
{
int err = 0;
*dst = (struct xfrm_dst*)ip6_route_output(NULL, fl);
if (!*dst)
err = -ENETUNREACH;
return err;
}
static struct dst_entry *
__xfrm6_find_bundle(struct flowi *fl, struct xfrm_policy *policy)
{
struct dst_entry *dst;
/* Still not clear if we should set fl->fl6_{src,dst}... */
read_lock_bh(&policy->lock);
for (dst = policy->bundles; dst; dst = dst->next) {
struct xfrm_dst *xdst = (struct xfrm_dst*)dst;
struct in6_addr fl_dst_prefix, fl_src_prefix;
ipv6_addr_prefix(&fl_dst_prefix,
&fl->fl6_dst,
xdst->u.rt6.rt6i_dst.plen);
ipv6_addr_prefix(&fl_src_prefix,
&fl->fl6_src,
xdst->u.rt6.rt6i_src.plen);
if (ipv6_addr_equal(&xdst->u.rt6.rt6i_dst.addr, &fl_dst_prefix) &&
ipv6_addr_equal(&xdst->u.rt6.rt6i_src.addr, &fl_src_prefix) &&
xfrm_bundle_ok(xdst, fl, AF_INET6)) {
dst_clone(dst);
break;
}
}
read_unlock_bh(&policy->lock);
return dst;
}
/* Allocate chain of dst_entry's, attach known xfrm's, calculate
* all the metrics... Shortly, bundle a bundle.
*/
static int
__xfrm6_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, int nx,
struct flowi *fl, struct dst_entry **dst_p)
{
struct dst_entry *dst, *dst_prev;
struct rt6_info *rt0 = (struct rt6_info*)(*dst_p);
struct rt6_info *rt = rt0;
struct in6_addr *remote = &fl->fl6_dst;
struct in6_addr *local = &fl->fl6_src;
struct flowi fl_tunnel = {
.nl_u = {
.ip6_u = {
.saddr = *local,
.daddr = *remote
}
}
};
int i;
int err = 0;
int header_len = 0;
int trailer_len = 0;
dst = dst_prev = NULL;
dst_hold(&rt->u.dst);
for (i = 0; i < nx; i++) {
struct dst_entry *dst1 = dst_alloc(&xfrm6_dst_ops);
struct xfrm_dst *xdst;
int tunnel = 0;
if (unlikely(dst1 == NULL)) {
err = -ENOBUFS;
dst_release(&rt->u.dst);
goto error;
}
if (!dst)
dst = dst1;
else {
dst_prev->child = dst1;
dst1->flags |= DST_NOHASH;
dst_clone(dst1);
}
xdst = (struct xfrm_dst *)dst1;
xdst->route = &rt->u.dst;
dst1->next = dst_prev;
dst_prev = dst1;
if (xfrm[i]->props.mode) {
remote = (struct in6_addr*)&xfrm[i]->id.daddr;
local = (struct in6_addr*)&xfrm[i]->props.saddr;
tunnel = 1;
}
header_len += xfrm[i]->props.header_len;
trailer_len += xfrm[i]->props.trailer_len;
if (tunnel) {
ipv6_addr_copy(&fl_tunnel.fl6_dst, remote);
ipv6_addr_copy(&fl_tunnel.fl6_src, local);
err = xfrm_dst_lookup((struct xfrm_dst **) &rt,
&fl_tunnel, AF_INET6);
if (err)
goto error;
} else
dst_hold(&rt->u.dst);
}
dst_prev->child = &rt->u.dst;
dst->path = &rt->u.dst;
*dst_p = dst;
dst = dst_prev;
dst_prev = *dst_p;
i = 0;
for (; dst_prev != &rt->u.dst; dst_prev = dst_prev->child) {
struct xfrm_dst *x = (struct xfrm_dst*)dst_prev;
dst_prev->xfrm = xfrm[i++];
dst_prev->dev = rt->u.dst.dev;
if (rt->u.dst.dev)
dev_hold(rt->u.dst.dev);
dst_prev->obsolete = -1;
dst_prev->flags |= DST_HOST;
dst_prev->lastuse = jiffies;
dst_prev->header_len = header_len;
dst_prev->trailer_len = trailer_len;
memcpy(&dst_prev->metrics, &x->route->metrics, sizeof(dst_prev->metrics));
/* Copy neighbour for reachability confirmation */
dst_prev->neighbour = neigh_clone(rt->u.dst.neighbour);
dst_prev->input = rt->u.dst.input;
dst_prev->output = xfrm6_output;
/* Sheit... I remember I did this right. Apparently,
* it was magically lost, so this code needs audit */
x->u.rt6.rt6i_flags = rt0->rt6i_flags&(RTCF_BROADCAST|RTCF_MULTICAST|RTCF_LOCAL);
x->u.rt6.rt6i_metric = rt0->rt6i_metric;
x->u.rt6.rt6i_node = rt0->rt6i_node;
x->u.rt6.rt6i_gateway = rt0->rt6i_gateway;
memcpy(&x->u.rt6.rt6i_gateway, &rt0->rt6i_gateway, sizeof(x->u.rt6.rt6i_gateway));
x->u.rt6.rt6i_dst = rt0->rt6i_dst;
x->u.rt6.rt6i_src = rt0->rt6i_src;
header_len -= x->u.dst.xfrm->props.header_len;
trailer_len -= x->u.dst.xfrm->props.trailer_len;
}
xfrm_init_pmtu(dst);
return 0;
error:
if (dst)
dst_free(dst);
return err;
}
static inline void
_decode_session6(struct sk_buff *skb, struct flowi *fl)
{
u16 offset = sizeof(struct ipv6hdr);
struct ipv6hdr *hdr = skb->nh.ipv6h;
struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
u8 nexthdr = skb->nh.ipv6h->nexthdr;
memset(fl, 0, sizeof(struct flowi));
ipv6_addr_copy(&fl->fl6_dst, &hdr->daddr);
ipv6_addr_copy(&fl->fl6_src, &hdr->saddr);
while (pskb_may_pull(skb, skb->nh.raw + offset + 1 - skb->data)) {
switch (nexthdr) {
case NEXTHDR_ROUTING:
case NEXTHDR_HOP:
case NEXTHDR_DEST:
offset += ipv6_optlen(exthdr);
nexthdr = exthdr->nexthdr;
exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
break;
case IPPROTO_UDP:
case IPPROTO_TCP:
case IPPROTO_SCTP:
if (pskb_may_pull(skb, skb->nh.raw + offset + 4 - skb->data)) {
u16 *ports = (u16 *)exthdr;
fl->fl_ip_sport = ports[0];
fl->fl_ip_dport = ports[1];
}
fl->proto = nexthdr;
return;
case IPPROTO_ICMPV6:
if (pskb_may_pull(skb, skb->nh.raw + offset + 2 - skb->data)) {
u8 *icmp = (u8 *)exthdr;
fl->fl_icmp_type = icmp[0];
fl->fl_icmp_code = icmp[1];
}
fl->proto = nexthdr;
return;
/* XXX Why are there these headers? */
case IPPROTO_AH:
case IPPROTO_ESP:
case IPPROTO_COMP:
default:
fl->fl_ipsec_spi = 0;
fl->proto = nexthdr;
return;
};
}
}
static inline int xfrm6_garbage_collect(void)
{
read_lock(&xfrm6_policy_afinfo.lock);
xfrm6_policy_afinfo.garbage_collect();
read_unlock(&xfrm6_policy_afinfo.lock);
return (atomic_read(&xfrm6_dst_ops.entries) > xfrm6_dst_ops.gc_thresh*2);
}
static void xfrm6_update_pmtu(struct dst_entry *dst, u32 mtu)
{
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
struct dst_entry *path = xdst->route;
path->ops->update_pmtu(path, mtu);
}
static struct dst_ops xfrm6_dst_ops = {
.family = AF_INET6,
.protocol = __constant_htons(ETH_P_IPV6),
.gc = xfrm6_garbage_collect,
.update_pmtu = xfrm6_update_pmtu,
.gc_thresh = 1024,
.entry_size = sizeof(struct xfrm_dst),
};
static struct xfrm_policy_afinfo xfrm6_policy_afinfo = {
.family = AF_INET6,
.lock = RW_LOCK_UNLOCKED,
.type_map = &xfrm6_type_map,
.dst_ops = &xfrm6_dst_ops,
.dst_lookup = xfrm6_dst_lookup,
.find_bundle = __xfrm6_find_bundle,
.bundle_create = __xfrm6_bundle_create,
.decode_session = _decode_session6,
};
static void __init xfrm6_policy_init(void)
{
xfrm_policy_register_afinfo(&xfrm6_policy_afinfo);
}
static void xfrm6_policy_fini(void)
{
xfrm_policy_unregister_afinfo(&xfrm6_policy_afinfo);
}
void __init xfrm6_init(void)
{
xfrm6_policy_init();
xfrm6_state_init();
}
void xfrm6_fini(void)
{
//xfrm6_input_fini();
xfrm6_policy_fini();
xfrm6_state_fini();
}

136
net/ipv6/xfrm6_state.c Normal file
View File

@@ -0,0 +1,136 @@
/*
* xfrm6_state.c: based on xfrm4_state.c
*
* Authors:
* Mitsuru KANDA @USAGI
* Kazunori MIYAZAWA @USAGI
* Kunihiro Ishiguro <kunihiro@ipinfusion.com>
* IPv6 support
* YOSHIFUJI Hideaki @USAGI
* Split up af-specific portion
*
*/
#include <net/xfrm.h>
#include <linux/pfkeyv2.h>
#include <linux/ipsec.h>
#include <net/ipv6.h>
static struct xfrm_state_afinfo xfrm6_state_afinfo;
static void
__xfrm6_init_tempsel(struct xfrm_state *x, struct flowi *fl,
struct xfrm_tmpl *tmpl,
xfrm_address_t *daddr, xfrm_address_t *saddr)
{
/* Initialize temporary selector matching only
* to current session. */
ipv6_addr_copy((struct in6_addr *)&x->sel.daddr, &fl->fl6_dst);
ipv6_addr_copy((struct in6_addr *)&x->sel.saddr, &fl->fl6_src);
x->sel.dport = xfrm_flowi_dport(fl);
x->sel.dport_mask = ~0;
x->sel.sport = xfrm_flowi_sport(fl);
x->sel.sport_mask = ~0;
x->sel.prefixlen_d = 128;
x->sel.prefixlen_s = 128;
x->sel.proto = fl->proto;
x->sel.ifindex = fl->oif;
x->id = tmpl->id;
if (ipv6_addr_any((struct in6_addr*)&x->id.daddr))
memcpy(&x->id.daddr, daddr, sizeof(x->sel.daddr));
memcpy(&x->props.saddr, &tmpl->saddr, sizeof(x->props.saddr));
if (ipv6_addr_any((struct in6_addr*)&x->props.saddr))
memcpy(&x->props.saddr, saddr, sizeof(x->props.saddr));
x->props.mode = tmpl->mode;
x->props.reqid = tmpl->reqid;
x->props.family = AF_INET6;
}
static struct xfrm_state *
__xfrm6_state_lookup(xfrm_address_t *daddr, u32 spi, u8 proto)
{
unsigned h = __xfrm6_spi_hash(daddr, spi, proto);
struct xfrm_state *x;
list_for_each_entry(x, xfrm6_state_afinfo.state_byspi+h, byspi) {
if (x->props.family == AF_INET6 &&
spi == x->id.spi &&
ipv6_addr_equal((struct in6_addr *)daddr, (struct in6_addr *)x->id.daddr.a6) &&
proto == x->id.proto) {
xfrm_state_hold(x);
return x;
}
}
return NULL;
}
static struct xfrm_state *
__xfrm6_find_acq(u8 mode, u32 reqid, u8 proto,
xfrm_address_t *daddr, xfrm_address_t *saddr,
int create)
{
struct xfrm_state *x, *x0;
unsigned h = __xfrm6_dst_hash(daddr);
x0 = NULL;
list_for_each_entry(x, xfrm6_state_afinfo.state_bydst+h, bydst) {
if (x->props.family == AF_INET6 &&
ipv6_addr_equal((struct in6_addr *)daddr, (struct in6_addr *)x->id.daddr.a6) &&
mode == x->props.mode &&
proto == x->id.proto &&
ipv6_addr_equal((struct in6_addr *)saddr, (struct in6_addr *)x->props.saddr.a6) &&
reqid == x->props.reqid &&
x->km.state == XFRM_STATE_ACQ &&
!x->id.spi) {
x0 = x;
break;
}
}
if (!x0 && create && (x0 = xfrm_state_alloc()) != NULL) {
ipv6_addr_copy((struct in6_addr *)x0->sel.daddr.a6,
(struct in6_addr *)daddr);
ipv6_addr_copy((struct in6_addr *)x0->sel.saddr.a6,
(struct in6_addr *)saddr);
x0->sel.prefixlen_d = 128;
x0->sel.prefixlen_s = 128;
ipv6_addr_copy((struct in6_addr *)x0->props.saddr.a6,
(struct in6_addr *)saddr);
x0->km.state = XFRM_STATE_ACQ;
ipv6_addr_copy((struct in6_addr *)x0->id.daddr.a6,
(struct in6_addr *)daddr);
x0->id.proto = proto;
x0->props.family = AF_INET6;
x0->props.mode = mode;
x0->props.reqid = reqid;
x0->lft.hard_add_expires_seconds = XFRM_ACQ_EXPIRES;
xfrm_state_hold(x0);
x0->timer.expires = jiffies + XFRM_ACQ_EXPIRES*HZ;
add_timer(&x0->timer);
xfrm_state_hold(x0);
list_add_tail(&x0->bydst, xfrm6_state_afinfo.state_bydst+h);
wake_up(&km_waitq);
}
if (x0)
xfrm_state_hold(x0);
return x0;
}
static struct xfrm_state_afinfo xfrm6_state_afinfo = {
.family = AF_INET6,
.lock = RW_LOCK_UNLOCKED,
.init_tempsel = __xfrm6_init_tempsel,
.state_lookup = __xfrm6_state_lookup,
.find_acq = __xfrm6_find_acq,
};
void __init xfrm6_state_init(void)
{
xfrm_state_register_afinfo(&xfrm6_state_afinfo);
}
void xfrm6_state_fini(void)
{
xfrm_state_unregister_afinfo(&xfrm6_state_afinfo);
}

543
net/ipv6/xfrm6_tunnel.c Normal file
View File

@@ -0,0 +1,543 @@
/*
* Copyright (C)2003,2004 USAGI/WIDE Project
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Authors Mitsuru KANDA <mk@linux-ipv6.org>
* YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
*
* Based on net/ipv4/xfrm4_tunnel.c
*
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/xfrm.h>
#include <linux/list.h>
#include <net/ip.h>
#include <net/xfrm.h>
#include <net/ipv6.h>
#include <net/protocol.h>
#include <linux/ipv6.h>
#include <linux/icmpv6.h>
#ifdef CONFIG_IPV6_XFRM6_TUNNEL_DEBUG
# define X6TDEBUG 3
#else
# define X6TDEBUG 1
#endif
#define X6TPRINTK(fmt, args...) printk(fmt, ## args)
#define X6TNOPRINTK(fmt, args...) do { ; } while(0)
#if X6TDEBUG >= 1
# define X6TPRINTK1 X6TPRINTK
#else
# define X6TPRINTK1 X6TNOPRINTK
#endif
#if X6TDEBUG >= 3
# define X6TPRINTK3 X6TPRINTK
#else
# define X6TPRINTK3 X6TNOPRINTK
#endif
/*
* xfrm_tunnel_spi things are for allocating unique id ("spi")
* per xfrm_address_t.
*/
struct xfrm6_tunnel_spi {
struct hlist_node list_byaddr;
struct hlist_node list_byspi;
xfrm_address_t addr;
u32 spi;
atomic_t refcnt;
#ifdef XFRM6_TUNNEL_SPI_MAGIC
u32 magic;
#endif
};
#ifdef CONFIG_IPV6_XFRM6_TUNNEL_DEBUG
# define XFRM6_TUNNEL_SPI_MAGIC 0xdeadbeef
#endif
static DEFINE_RWLOCK(xfrm6_tunnel_spi_lock);
static u32 xfrm6_tunnel_spi;
#define XFRM6_TUNNEL_SPI_MIN 1
#define XFRM6_TUNNEL_SPI_MAX 0xffffffff
static kmem_cache_t *xfrm6_tunnel_spi_kmem;
#define XFRM6_TUNNEL_SPI_BYADDR_HSIZE 256
#define XFRM6_TUNNEL_SPI_BYSPI_HSIZE 256
static struct hlist_head xfrm6_tunnel_spi_byaddr[XFRM6_TUNNEL_SPI_BYADDR_HSIZE];
static struct hlist_head xfrm6_tunnel_spi_byspi[XFRM6_TUNNEL_SPI_BYSPI_HSIZE];
#ifdef XFRM6_TUNNEL_SPI_MAGIC
static int x6spi_check_magic(const struct xfrm6_tunnel_spi *x6spi,
const char *name)
{
if (unlikely(x6spi->magic != XFRM6_TUNNEL_SPI_MAGIC)) {
X6TPRINTK3(KERN_DEBUG "%s(): x6spi object "
"at %p has corrupted magic %08x "
"(should be %08x)\n",
name, x6spi, x6spi->magic, XFRM6_TUNNEL_SPI_MAGIC);
return -1;
}
return 0;
}
#else
static int inline x6spi_check_magic(const struct xfrm6_tunnel_spi *x6spi,
const char *name)
{
return 0;
}
#endif
#define X6SPI_CHECK_MAGIC(x6spi) x6spi_check_magic((x6spi), __FUNCTION__)
static unsigned inline xfrm6_tunnel_spi_hash_byaddr(xfrm_address_t *addr)
{
unsigned h;
X6TPRINTK3(KERN_DEBUG "%s(addr=%p)\n", __FUNCTION__, addr);
h = addr->a6[0] ^ addr->a6[1] ^ addr->a6[2] ^ addr->a6[3];
h ^= h >> 16;
h ^= h >> 8;
h &= XFRM6_TUNNEL_SPI_BYADDR_HSIZE - 1;
X6TPRINTK3(KERN_DEBUG "%s() = %u\n", __FUNCTION__, h);
return h;
}
static unsigned inline xfrm6_tunnel_spi_hash_byspi(u32 spi)
{
return spi % XFRM6_TUNNEL_SPI_BYSPI_HSIZE;
}
static int xfrm6_tunnel_spi_init(void)
{
int i;
X6TPRINTK3(KERN_DEBUG "%s()\n", __FUNCTION__);
xfrm6_tunnel_spi = 0;
xfrm6_tunnel_spi_kmem = kmem_cache_create("xfrm6_tunnel_spi",
sizeof(struct xfrm6_tunnel_spi),
0, SLAB_HWCACHE_ALIGN,
NULL, NULL);
if (!xfrm6_tunnel_spi_kmem) {
X6TPRINTK1(KERN_ERR
"%s(): failed to allocate xfrm6_tunnel_spi_kmem\n",
__FUNCTION__);
return -ENOMEM;
}
for (i = 0; i < XFRM6_TUNNEL_SPI_BYADDR_HSIZE; i++)
INIT_HLIST_HEAD(&xfrm6_tunnel_spi_byaddr[i]);
for (i = 0; i < XFRM6_TUNNEL_SPI_BYSPI_HSIZE; i++)
INIT_HLIST_HEAD(&xfrm6_tunnel_spi_byspi[i]);
return 0;
}
static void xfrm6_tunnel_spi_fini(void)
{
int i;
X6TPRINTK3(KERN_DEBUG "%s()\n", __FUNCTION__);
for (i = 0; i < XFRM6_TUNNEL_SPI_BYADDR_HSIZE; i++) {
if (!hlist_empty(&xfrm6_tunnel_spi_byaddr[i]))
goto err;
}
for (i = 0; i < XFRM6_TUNNEL_SPI_BYSPI_HSIZE; i++) {
if (!hlist_empty(&xfrm6_tunnel_spi_byspi[i]))
goto err;
}
kmem_cache_destroy(xfrm6_tunnel_spi_kmem);
xfrm6_tunnel_spi_kmem = NULL;
return;
err:
X6TPRINTK1(KERN_ERR "%s(): table is not empty\n", __FUNCTION__);
return;
}
static struct xfrm6_tunnel_spi *__xfrm6_tunnel_spi_lookup(xfrm_address_t *saddr)
{
struct xfrm6_tunnel_spi *x6spi;
struct hlist_node *pos;
X6TPRINTK3(KERN_DEBUG "%s(saddr=%p)\n", __FUNCTION__, saddr);
hlist_for_each_entry(x6spi, pos,
&xfrm6_tunnel_spi_byaddr[xfrm6_tunnel_spi_hash_byaddr(saddr)],
list_byaddr) {
if (memcmp(&x6spi->addr, saddr, sizeof(x6spi->addr)) == 0) {
X6SPI_CHECK_MAGIC(x6spi);
X6TPRINTK3(KERN_DEBUG "%s() = %p(%u)\n", __FUNCTION__, x6spi, x6spi->spi);
return x6spi;
}
}
X6TPRINTK3(KERN_DEBUG "%s() = NULL(0)\n", __FUNCTION__);
return NULL;
}
u32 xfrm6_tunnel_spi_lookup(xfrm_address_t *saddr)
{
struct xfrm6_tunnel_spi *x6spi;
u32 spi;
X6TPRINTK3(KERN_DEBUG "%s(saddr=%p)\n", __FUNCTION__, saddr);
read_lock_bh(&xfrm6_tunnel_spi_lock);
x6spi = __xfrm6_tunnel_spi_lookup(saddr);
spi = x6spi ? x6spi->spi : 0;
read_unlock_bh(&xfrm6_tunnel_spi_lock);
return spi;
}
EXPORT_SYMBOL(xfrm6_tunnel_spi_lookup);
static u32 __xfrm6_tunnel_alloc_spi(xfrm_address_t *saddr)
{
u32 spi;
struct xfrm6_tunnel_spi *x6spi;
struct hlist_node *pos;
unsigned index;
X6TPRINTK3(KERN_DEBUG "%s(saddr=%p)\n", __FUNCTION__, saddr);
if (xfrm6_tunnel_spi < XFRM6_TUNNEL_SPI_MIN ||
xfrm6_tunnel_spi >= XFRM6_TUNNEL_SPI_MAX)
xfrm6_tunnel_spi = XFRM6_TUNNEL_SPI_MIN;
else
xfrm6_tunnel_spi++;
for (spi = xfrm6_tunnel_spi; spi <= XFRM6_TUNNEL_SPI_MAX; spi++) {
index = xfrm6_tunnel_spi_hash_byspi(spi);
hlist_for_each_entry(x6spi, pos,
&xfrm6_tunnel_spi_byspi[index],
list_byspi) {
if (x6spi->spi == spi)
goto try_next_1;
}
xfrm6_tunnel_spi = spi;
goto alloc_spi;
try_next_1:;
}
for (spi = XFRM6_TUNNEL_SPI_MIN; spi < xfrm6_tunnel_spi; spi++) {
index = xfrm6_tunnel_spi_hash_byspi(spi);
hlist_for_each_entry(x6spi, pos,
&xfrm6_tunnel_spi_byspi[index],
list_byspi) {
if (x6spi->spi == spi)
goto try_next_2;
}
xfrm6_tunnel_spi = spi;
goto alloc_spi;
try_next_2:;
}
spi = 0;
goto out;
alloc_spi:
X6TPRINTK3(KERN_DEBUG "%s(): allocate new spi for "
"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
__FUNCTION__,
NIP6(*(struct in6_addr *)saddr));
x6spi = kmem_cache_alloc(xfrm6_tunnel_spi_kmem, SLAB_ATOMIC);
if (!x6spi) {
X6TPRINTK1(KERN_ERR "%s(): kmem_cache_alloc() failed\n",
__FUNCTION__);
goto out;
}
#ifdef XFRM6_TUNNEL_SPI_MAGIC
x6spi->magic = XFRM6_TUNNEL_SPI_MAGIC;
#endif
memcpy(&x6spi->addr, saddr, sizeof(x6spi->addr));
x6spi->spi = spi;
atomic_set(&x6spi->refcnt, 1);
hlist_add_head(&x6spi->list_byspi, &xfrm6_tunnel_spi_byspi[index]);
index = xfrm6_tunnel_spi_hash_byaddr(saddr);
hlist_add_head(&x6spi->list_byaddr, &xfrm6_tunnel_spi_byaddr[index]);
X6SPI_CHECK_MAGIC(x6spi);
out:
X6TPRINTK3(KERN_DEBUG "%s() = %u\n", __FUNCTION__, spi);
return spi;
}
u32 xfrm6_tunnel_alloc_spi(xfrm_address_t *saddr)
{
struct xfrm6_tunnel_spi *x6spi;
u32 spi;
X6TPRINTK3(KERN_DEBUG "%s(saddr=%p)\n", __FUNCTION__, saddr);
write_lock_bh(&xfrm6_tunnel_spi_lock);
x6spi = __xfrm6_tunnel_spi_lookup(saddr);
if (x6spi) {
atomic_inc(&x6spi->refcnt);
spi = x6spi->spi;
} else
spi = __xfrm6_tunnel_alloc_spi(saddr);
write_unlock_bh(&xfrm6_tunnel_spi_lock);
X6TPRINTK3(KERN_DEBUG "%s() = %u\n", __FUNCTION__, spi);
return spi;
}
EXPORT_SYMBOL(xfrm6_tunnel_alloc_spi);
void xfrm6_tunnel_free_spi(xfrm_address_t *saddr)
{
struct xfrm6_tunnel_spi *x6spi;
struct hlist_node *pos, *n;
X6TPRINTK3(KERN_DEBUG "%s(saddr=%p)\n", __FUNCTION__, saddr);
write_lock_bh(&xfrm6_tunnel_spi_lock);
hlist_for_each_entry_safe(x6spi, pos, n,
&xfrm6_tunnel_spi_byaddr[xfrm6_tunnel_spi_hash_byaddr(saddr)],
list_byaddr)
{
if (memcmp(&x6spi->addr, saddr, sizeof(x6spi->addr)) == 0) {
X6TPRINTK3(KERN_DEBUG "%s(): x6spi object "
"for %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x "
"found at %p\n",
__FUNCTION__,
NIP6(*(struct in6_addr *)saddr),
x6spi);
X6SPI_CHECK_MAGIC(x6spi);
if (atomic_dec_and_test(&x6spi->refcnt)) {
hlist_del(&x6spi->list_byaddr);
hlist_del(&x6spi->list_byspi);
kmem_cache_free(xfrm6_tunnel_spi_kmem, x6spi);
break;
}
}
}
write_unlock_bh(&xfrm6_tunnel_spi_lock);
}
EXPORT_SYMBOL(xfrm6_tunnel_free_spi);
static int xfrm6_tunnel_output(struct xfrm_state *x, struct sk_buff *skb)
{
struct ipv6hdr *top_iph;
top_iph = (struct ipv6hdr *)skb->data;
top_iph->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
return 0;
}
static int xfrm6_tunnel_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_buff *skb)
{
return 0;
}
static struct xfrm6_tunnel *xfrm6_tunnel_handler;
static DECLARE_MUTEX(xfrm6_tunnel_sem);
int xfrm6_tunnel_register(struct xfrm6_tunnel *handler)
{
int ret;
down(&xfrm6_tunnel_sem);
ret = 0;
if (xfrm6_tunnel_handler != NULL)
ret = -EINVAL;
if (!ret)
xfrm6_tunnel_handler = handler;
up(&xfrm6_tunnel_sem);
return ret;
}
EXPORT_SYMBOL(xfrm6_tunnel_register);
int xfrm6_tunnel_deregister(struct xfrm6_tunnel *handler)
{
int ret;
down(&xfrm6_tunnel_sem);
ret = 0;
if (xfrm6_tunnel_handler != handler)
ret = -EINVAL;
if (!ret)
xfrm6_tunnel_handler = NULL;
up(&xfrm6_tunnel_sem);
synchronize_net();
return ret;
}
EXPORT_SYMBOL(xfrm6_tunnel_deregister);
static int xfrm6_tunnel_rcv(struct sk_buff **pskb, unsigned int *nhoffp)
{
struct sk_buff *skb = *pskb;
struct xfrm6_tunnel *handler = xfrm6_tunnel_handler;
struct ipv6hdr *iph = skb->nh.ipv6h;
u32 spi;
/* device-like_ip6ip6_handler() */
if (handler && handler->handler(pskb, nhoffp) == 0)
return 0;
spi = xfrm6_tunnel_spi_lookup((xfrm_address_t *)&iph->saddr);
return xfrm6_rcv_spi(pskb, nhoffp, spi);
}
static void xfrm6_tunnel_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
int type, int code, int offset, __u32 info)
{
struct xfrm6_tunnel *handler = xfrm6_tunnel_handler;
/* call here first for device-like ip6ip6 err handling */
if (handler) {
handler->err_handler(skb, opt, type, code, offset, info);
return;
}
/* xfrm6_tunnel native err handling */
switch (type) {
case ICMPV6_DEST_UNREACH:
switch (code) {
case ICMPV6_NOROUTE:
case ICMPV6_ADM_PROHIBITED:
case ICMPV6_NOT_NEIGHBOUR:
case ICMPV6_ADDR_UNREACH:
case ICMPV6_PORT_UNREACH:
default:
X6TPRINTK3(KERN_DEBUG
"xfrm6_tunnel: Destination Unreach.\n");
break;
}
break;
case ICMPV6_PKT_TOOBIG:
X6TPRINTK3(KERN_DEBUG
"xfrm6_tunnel: Packet Too Big.\n");
break;
case ICMPV6_TIME_EXCEED:
switch (code) {
case ICMPV6_EXC_HOPLIMIT:
X6TPRINTK3(KERN_DEBUG
"xfrm6_tunnel: Too small Hoplimit.\n");
break;
case ICMPV6_EXC_FRAGTIME:
default:
break;
}
break;
case ICMPV6_PARAMPROB:
switch (code) {
case ICMPV6_HDR_FIELD: break;
case ICMPV6_UNK_NEXTHDR: break;
case ICMPV6_UNK_OPTION: break;
}
break;
default:
break;
}
return;
}
static int xfrm6_tunnel_init_state(struct xfrm_state *x, void *args)
{
if (!x->props.mode)
return -EINVAL;
if (x->encap)
return -EINVAL;
x->props.header_len = sizeof(struct ipv6hdr);
return 0;
}
static void xfrm6_tunnel_destroy(struct xfrm_state *x)
{
xfrm6_tunnel_free_spi((xfrm_address_t *)&x->props.saddr);
}
static struct xfrm_type xfrm6_tunnel_type = {
.description = "IP6IP6",
.owner = THIS_MODULE,
.proto = IPPROTO_IPV6,
.init_state = xfrm6_tunnel_init_state,
.destructor = xfrm6_tunnel_destroy,
.input = xfrm6_tunnel_input,
.output = xfrm6_tunnel_output,
};
static struct inet6_protocol xfrm6_tunnel_protocol = {
.handler = xfrm6_tunnel_rcv,
.err_handler = xfrm6_tunnel_err,
.flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
};
static int __init xfrm6_tunnel_init(void)
{
X6TPRINTK3(KERN_DEBUG "%s()\n", __FUNCTION__);
if (xfrm_register_type(&xfrm6_tunnel_type, AF_INET6) < 0) {
X6TPRINTK1(KERN_ERR
"xfrm6_tunnel init: can't add xfrm type\n");
return -EAGAIN;
}
if (inet6_add_protocol(&xfrm6_tunnel_protocol, IPPROTO_IPV6) < 0) {
X6TPRINTK1(KERN_ERR
"xfrm6_tunnel init(): can't add protocol\n");
xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6);
return -EAGAIN;
}
if (xfrm6_tunnel_spi_init() < 0) {
X6TPRINTK1(KERN_ERR
"xfrm6_tunnel init: failed to initialize spi\n");
inet6_del_protocol(&xfrm6_tunnel_protocol, IPPROTO_IPV6);
xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6);
return -EAGAIN;
}
return 0;
}
static void __exit xfrm6_tunnel_fini(void)
{
X6TPRINTK3(KERN_DEBUG "%s()\n", __FUNCTION__);
xfrm6_tunnel_spi_fini();
if (inet6_del_protocol(&xfrm6_tunnel_protocol, IPPROTO_IPV6) < 0)
X6TPRINTK1(KERN_ERR
"xfrm6_tunnel close: can't remove protocol\n");
if (xfrm_unregister_type(&xfrm6_tunnel_type, AF_INET6) < 0)
X6TPRINTK1(KERN_ERR
"xfrm6_tunnel close: can't remove xfrm type\n");
}
module_init(xfrm6_tunnel_init);
module_exit(xfrm6_tunnel_fini);
MODULE_LICENSE("GPL");