| /* |
| * IPv6 Firewall |
| * Linux INET6 implementation |
| * |
| * Authors: |
| * Pedro Roque <roque@di.fc.ul.pt> |
| * |
| * $Id: ip6_fw.c,v 1.16 2001-10-31 08:17:58 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/config.h> |
| #include <linux/errno.h> |
| #include <linux/types.h> |
| #include <linux/string.h> |
| #include <linux/socket.h> |
| #include <linux/sockios.h> |
| #include <linux/net.h> |
| #include <linux/route.h> |
| #include <linux/netdevice.h> |
| #include <linux/in6.h> |
| #include <linux/udp.h> |
| #include <linux/init.h> |
| |
| #include <net/ipv6.h> |
| #include <net/ip6_route.h> |
| #include <net/ip6_fw.h> |
| #include <net/netlink.h> |
| |
| static unsigned long ip6_fw_rule_cnt; |
| static struct ip6_fw_rule ip6_fw_rule_list = { |
| {0}, |
| NULL, NULL, |
| {0}, |
| IP6_FW_REJECT |
| }; |
| |
| static int ip6_fw_accept(struct dst_entry *dst, struct fl_acc_args *args); |
| |
| struct flow_rule_ops ip6_fw_ops = { |
| ip6_fw_accept |
| }; |
| |
| |
| static struct rt6_info ip6_fw_null_entry = { |
| {{NULL, 0, 0, NULL, |
| 0, 0, 0, 0, 0, 0, 0, 0, -ENETUNREACH, NULL, NULL, |
| ip6_pkt_discard, ip6_pkt_discard, NULL}}, |
| NULL, {{{0}}}, 256, RTF_REJECT|RTF_NONEXTHOP, ~0UL, |
| 0, &ip6_fw_rule_list, {{{{0}}}, 128}, {{{{0}}}, 128} |
| }; |
| |
| static struct fib6_node ip6_fw_fib = { |
| NULL, NULL, NULL, NULL, |
| &ip6_fw_null_entry, |
| 0, RTN_ROOT|RTN_TL_ROOT, 0 |
| }; |
| |
| rwlock_t ip6_fw_lock = RW_LOCK_UNLOCKED; |
| |
| |
| static void ip6_rule_add(struct ip6_fw_rule *rl) |
| { |
| struct ip6_fw_rule *next; |
| |
| write_lock_bh(&ip6_fw_lock); |
| ip6_fw_rule_cnt++; |
| next = &ip6_fw_rule_list; |
| rl->next = next; |
| rl->prev = next->prev; |
| rl->prev->next = rl; |
| next->prev = rl; |
| write_unlock_bh(&ip6_fw_lock); |
| } |
| |
| static void ip6_rule_del(struct ip6_fw_rule *rl) |
| { |
| struct ip6_fw_rule *next, *prev; |
| |
| write_lock_bh(&ip6_fw_lock); |
| ip6_fw_rule_cnt--; |
| next = rl->next; |
| prev = rl->prev; |
| next->prev = prev; |
| prev->next = next; |
| write_unlock_bh(&ip6_fw_lock); |
| } |
| |
| static __inline__ struct ip6_fw_rule * ip6_fwrule_alloc(void) |
| { |
| struct ip6_fw_rule *rl; |
| |
| rl = kmalloc(sizeof(struct ip6_fw_rule), GFP_ATOMIC); |
| if (rl) |
| { |
| memset(rl, 0, sizeof(struct ip6_fw_rule)); |
| rl->flowr.ops = &ip6_fw_ops; |
| } |
| return rl; |
| } |
| |
| static __inline__ void ip6_fwrule_free(struct ip6_fw_rule * rl) |
| { |
| kfree(rl); |
| } |
| |
| static __inline__ int port_match(int rl_port, int fl_port) |
| { |
| int res = 0; |
| if (rl_port == 0 || (rl_port == fl_port)) |
| res = 1; |
| return res; |
| } |
| |
| static int ip6_fw_accept_trans(struct ip6_fw_rule *rl, |
| struct fl_acc_args *args) |
| { |
| int res = FLOWR_NODECISION; |
| int proto = 0; |
| int sport = 0; |
| int dport = 0; |
| |
| switch (args->type) { |
| case FL_ARG_FORWARD: |
| { |
| struct sk_buff *skb = args->fl_u.skb; |
| struct ipv6hdr *hdr = skb->nh.ipv6h; |
| int len; |
| |
| len = skb->len - sizeof(struct ipv6hdr); |
| |
| proto = hdr->nexthdr; |
| |
| switch (proto) { |
| case IPPROTO_TCP: |
| { |
| struct tcphdr *th; |
| |
| if (len < sizeof(struct tcphdr)) { |
| res = FLOWR_ERROR; |
| goto out; |
| } |
| th = (struct tcphdr *)(hdr + 1); |
| sport = th->source; |
| dport = th->dest; |
| break; |
| } |
| case IPPROTO_UDP: |
| { |
| struct udphdr *uh; |
| |
| if (len < sizeof(struct udphdr)) { |
| res = FLOWR_ERROR; |
| goto out; |
| } |
| uh = (struct udphdr *)(hdr + 1); |
| sport = uh->source; |
| dport = uh->dest; |
| break; |
| } |
| default: |
| goto out; |
| }; |
| break; |
| } |
| |
| case FL_ARG_ORIGIN: |
| { |
| proto = args->fl_u.fl_o.flow->proto; |
| |
| if (proto == IPPROTO_ICMPV6) { |
| goto out; |
| } else { |
| sport = args->fl_u.fl_o.flow->uli_u.ports.sport; |
| dport = args->fl_u.fl_o.flow->uli_u.ports.dport; |
| } |
| break; |
| } |
| |
| if (proto == rl->info.proto && |
| port_match(args->fl_u.fl_o.flow->uli_u.ports.sport, sport) && |
| port_match(args->fl_u.fl_o.flow->uli_u.ports.dport, dport)) { |
| if (rl->policy & IP6_FW_REJECT) |
| res = FLOWR_SELECT; |
| else |
| res = FLOWR_CLEAR; |
| } |
| |
| default: |
| #if IP6_FW_DEBUG >= 1 |
| printk(KERN_DEBUG "ip6_fw_accept: unknown arg type\n"); |
| #endif |
| goto out; |
| }; |
| |
| out: |
| return res; |
| } |
| |
| static int ip6_fw_accept(struct dst_entry *dst, struct fl_acc_args *args) |
| { |
| struct rt6_info *rt; |
| struct ip6_fw_rule *rl; |
| int proto; |
| int res = FLOWR_NODECISION; |
| |
| rt = (struct rt6_info *) dst; |
| rl = (struct ip6_fw_rule *) rt->rt6i_flowr; |
| |
| proto = rl->info.proto; |
| |
| switch (proto) { |
| case 0: |
| if (rl->policy & IP6_FW_REJECT) |
| res = FLOWR_SELECT; |
| else |
| res = FLOWR_CLEAR; |
| break; |
| case IPPROTO_TCP: |
| case IPPROTO_UDP: |
| res = ip6_fw_accept_trans(rl, args); |
| break; |
| case IPPROTO_ICMPV6: |
| }; |
| |
| return res; |
| } |
| |
| static struct dst_entry * ip6_fw_dup(struct dst_entry *frule, |
| struct dst_entry *rt, |
| struct fl_acc_args *args) |
| { |
| struct ip6_fw_rule *rl; |
| struct rt6_info *nrt; |
| struct rt6_info *frt; |
| |
| frt = (struct rt6_info *) frule; |
| |
| rl = (struct ip6_fw_rule *) frt->rt6i_flowr; |
| |
| nrt = ip6_rt_copy((struct rt6_info *) rt); |
| |
| if (nrt) { |
| nrt->u.dst.input = frule->input; |
| nrt->u.dst.output = frule->output; |
| |
| nrt->rt6i_flowr = flow_clone(frt->rt6i_flowr); |
| |
| nrt->rt6i_flags |= RTF_CACHE; |
| nrt->rt6i_tstamp = jiffies; |
| } |
| |
| return (struct dst_entry *) nrt; |
| } |
| |
| int ip6_fw_reject(struct sk_buff *skb) |
| { |
| #if IP6_FW_DEBUG >= 1 |
| printk(KERN_DEBUG "packet rejected: \n"); |
| #endif |
| |
| icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADM_PROHIBITED, 0, |
| skb->dev); |
| /* |
| * send it via netlink, as (rule, skb) |
| */ |
| |
| kfree_skb(skb); |
| return 0; |
| } |
| |
| int ip6_fw_discard(struct sk_buff *skb) |
| { |
| printk(KERN_DEBUG "ip6_fw: BUG fw_reject called\n"); |
| kfree_skb(skb); |
| return 0; |
| } |
| |
| int ip6_fw_msg_add(struct ip6_fw_msg *msg) |
| { |
| struct in6_rtmsg rtmsg; |
| struct ip6_fw_rule *rl; |
| struct rt6_info *rt; |
| int err; |
| |
| ipv6_addr_copy(&rtmsg.rtmsg_dst, &msg->dst); |
| ipv6_addr_copy(&rtmsg.rtmsg_src, &msg->src); |
| rtmsg.rtmsg_dst_len = msg->dst_len; |
| rtmsg.rtmsg_src_len = msg->src_len; |
| rtmsg.rtmsg_metric = IP6_RT_PRIO_FW; |
| |
| rl = ip6_fwrule_alloc(); |
| |
| if (rl == NULL) |
| return -ENOMEM; |
| |
| rl->policy = msg->policy; |
| rl->info.proto = msg->proto; |
| rl->info.uli_u.data = msg->u.data; |
| |
| rtmsg.rtmsg_flags = RTF_NONEXTHOP|RTF_POLICY; |
| err = ip6_route_add(&rtmsg); |
| |
| if (err) { |
| ip6_fwrule_free(rl); |
| return err; |
| } |
| |
| /* The rest will not work for now. --ABK (989725) */ |
| |
| #ifndef notdef |
| ip6_fwrule_free(rl); |
| return -EPERM; |
| #else |
| rt->u.dst.error = -EPERM; |
| |
| if (msg->policy == IP6_FW_ACCEPT) { |
| /* |
| * Accept rules are never selected |
| * (i.e. packets use normal forwarding) |
| */ |
| rt->u.dst.input = ip6_fw_discard; |
| rt->u.dst.output = ip6_fw_discard; |
| } else { |
| rt->u.dst.input = ip6_fw_reject; |
| rt->u.dst.output = ip6_fw_reject; |
| } |
| |
| ip6_rule_add(rl); |
| |
| rt->rt6i_flowr = flow_clone((struct flow_rule *)rl); |
| |
| return 0; |
| #endif |
| } |
| |
| static int ip6_fw_msgrcv(int unit, struct sk_buff *skb) |
| { |
| int count = 0; |
| |
| while (skb->len) { |
| struct ip6_fw_msg *msg; |
| |
| if (skb->len < sizeof(struct ip6_fw_msg)) { |
| count = -EINVAL; |
| break; |
| } |
| |
| msg = (struct ip6_fw_msg *) skb->data; |
| skb_pull(skb, sizeof(struct ip6_fw_msg)); |
| count += sizeof(struct ip6_fw_msg); |
| |
| switch (msg->action) { |
| case IP6_FW_MSG_ADD: |
| ip6_fw_msg_add(msg); |
| break; |
| case IP6_FW_MSG_DEL: |
| break; |
| default: |
| return -EINVAL; |
| }; |
| } |
| |
| return count; |
| } |
| |
| static void ip6_fw_destroy(struct flow_rule *rl) |
| { |
| ip6_fwrule_free((struct ip6_fw_rule *)rl); |
| } |
| |
| #ifdef MODULE |
| #define ip6_fw_init module_init |
| #endif |
| |
| void __init ip6_fw_init(void) |
| { |
| netlink_attach(NETLINK_IP6_FW, ip6_fw_msgrcv); |
| } |
| |
| #ifdef MODULE |
| void cleanup_module(void) |
| { |
| netlink_detach(NETLINK_IP6_FW); |
| } |
| #endif |