summaryrefslogtreecommitdiff
path: root/release/src/linux/linux/net/ipv4/netfilter/ipt_REJECT.c
diff options
context:
space:
mode:
authorAndreas Baumann <mail@andreasbaumann.cc>2015-01-03 12:04:58 +0100
committerAndreas Baumann <mail@andreasbaumann.cc>2015-01-03 12:04:58 +0100
commit008d0be72b2f160382c6e880765e96b64a050c65 (patch)
tree36f48a98a3815a408e2ce1693dd182af90f80305 /release/src/linux/linux/net/ipv4/netfilter/ipt_REJECT.c
parent611becfb8726c60cb060368541ad98191d4532f5 (diff)
downloadtomato-008d0be72b2f160382c6e880765e96b64a050c65.tar.gz
tomato-008d0be72b2f160382c6e880765e96b64a050c65.tar.bz2
imported original firmware WRT54GL_v4.30.11_11_US
Diffstat (limited to 'release/src/linux/linux/net/ipv4/netfilter/ipt_REJECT.c')
-rw-r--r--release/src/linux/linux/net/ipv4/netfilter/ipt_REJECT.c388
1 files changed, 388 insertions, 0 deletions
diff --git a/release/src/linux/linux/net/ipv4/netfilter/ipt_REJECT.c b/release/src/linux/linux/net/ipv4/netfilter/ipt_REJECT.c
new file mode 100644
index 00000000..fd64ebab
--- /dev/null
+++ b/release/src/linux/linux/net/ipv4/netfilter/ipt_REJECT.c
@@ -0,0 +1,388 @@
+/*
+ * This is a module which is used for rejecting packets.
+ * Added support for customized reject packets (Jozsef Kadlecsik).
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <linux/icmp.h>
+#include <net/icmp.h>
+#include <net/ip.h>
+#include <net/tcp.h>
+struct in_device;
+#include <net/route.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_REJECT.h>
+
+#define DEBUGP(format, args...)
+
+/* If the original packet is part of a connection, but the connection
+ is not confirmed, our manufactured reply will not be associated
+ with it, so we need to do this manually. */
+static void connection_attach(struct sk_buff *new_skb, struct nf_ct_info *nfct)
+{
+ void (*attach)(struct sk_buff *, struct nf_ct_info *);
+
+ /* Avoid module unload race with ip_ct_attach being NULLed out */
+ if (nfct && (attach = ip_ct_attach) != NULL)
+ attach(new_skb, nfct);
+}
+
+/* Send RST reply */
+static void send_reset(struct sk_buff *oldskb, int local)
+{
+ struct sk_buff *nskb;
+ struct tcphdr *otcph, *tcph;
+ struct rtable *rt;
+ unsigned int otcplen;
+ u_int16_t tmp_port;
+ u_int32_t tmp_addr;
+ int needs_ack;
+
+ /* IP header checks: fragment, too short. */
+ if (oldskb->nh.iph->frag_off & htons(IP_OFFSET)
+ || oldskb->len < (oldskb->nh.iph->ihl<<2) + sizeof(struct tcphdr))
+ return;
+
+ otcph = (struct tcphdr *)((u_int32_t*)oldskb->nh.iph + oldskb->nh.iph->ihl);
+ otcplen = oldskb->len - oldskb->nh.iph->ihl*4;
+
+ /* No RST for RST. */
+ if (otcph->rst)
+ return;
+
+ /* Check checksum. */
+ if (tcp_v4_check(otcph, otcplen, oldskb->nh.iph->saddr,
+ oldskb->nh.iph->daddr,
+ csum_partial((char *)otcph, otcplen, 0)) != 0)
+ return;
+
+ /* Copy skb (even if skb is about to be dropped, we can't just
+ clone it because there may be other things, such as tcpdump,
+ interested in it) */
+ nskb = skb_copy(oldskb, GFP_ATOMIC);
+ if (!nskb)
+ return;
+
+ /* This packet will not be the same as the other: clear nf fields */
+ nf_conntrack_put(nskb->nfct);
+ nskb->nfct = NULL;
+ nskb->nfcache = 0;
+#ifdef CONFIG_NETFILTER_DEBUG
+ nskb->nf_debug = 0;
+#endif
+ nskb->nfmark = 0;
+
+ tcph = (struct tcphdr *)((u_int32_t*)nskb->nh.iph + nskb->nh.iph->ihl);
+
+ /* Swap source and dest */
+ tmp_addr = nskb->nh.iph->saddr;
+ nskb->nh.iph->saddr = nskb->nh.iph->daddr;
+ nskb->nh.iph->daddr = tmp_addr;
+ tmp_port = tcph->source;
+ tcph->source = tcph->dest;
+ tcph->dest = tmp_port;
+
+ /* Truncate to length (no data) */
+ tcph->doff = sizeof(struct tcphdr)/4;
+ skb_trim(nskb, nskb->nh.iph->ihl*4 + sizeof(struct tcphdr));
+ nskb->nh.iph->tot_len = htons(nskb->len);
+
+ if (tcph->ack) {
+ needs_ack = 0;
+ tcph->seq = otcph->ack_seq;
+ tcph->ack_seq = 0;
+ } else {
+ needs_ack = 1;
+ tcph->ack_seq = htonl(ntohl(otcph->seq) + otcph->syn + otcph->fin
+ + otcplen - (otcph->doff<<2));
+ tcph->seq = 0;
+ }
+
+ /* Reset flags */
+ ((u_int8_t *)tcph)[13] = 0;
+ tcph->rst = 1;
+ tcph->ack = needs_ack;
+
+ tcph->window = 0;
+ tcph->urg_ptr = 0;
+
+ /* Adjust TCP checksum */
+ tcph->check = 0;
+ tcph->check = tcp_v4_check(tcph, sizeof(struct tcphdr),
+ nskb->nh.iph->saddr,
+ nskb->nh.iph->daddr,
+ csum_partial((char *)tcph,
+ sizeof(struct tcphdr), 0));
+
+ /* Adjust IP TTL, DF */
+ nskb->nh.iph->ttl = MAXTTL;
+ /* Set DF, id = 0 */
+ nskb->nh.iph->frag_off = htons(IP_DF);
+ nskb->nh.iph->id = 0;
+
+ /* Adjust IP checksum */
+ nskb->nh.iph->check = 0;
+ nskb->nh.iph->check = ip_fast_csum((unsigned char *)nskb->nh.iph,
+ nskb->nh.iph->ihl);
+
+ /* Routing: if not headed for us, route won't like source */
+ if (ip_route_output(&rt, nskb->nh.iph->daddr,
+ local ? nskb->nh.iph->saddr : 0,
+ RT_TOS(nskb->nh.iph->tos) | RTO_CONN,
+ 0) != 0)
+ goto free_nskb;
+
+ dst_release(nskb->dst);
+ nskb->dst = &rt->u.dst;
+
+ /* "Never happens" */
+ if (nskb->len > nskb->dst->pmtu)
+ goto free_nskb;
+
+ connection_attach(nskb, oldskb->nfct);
+
+ NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, nskb, NULL, nskb->dst->dev,
+ ip_finish_output);
+ return;
+
+ free_nskb:
+ kfree_skb(nskb);
+}
+
+static void send_unreach(struct sk_buff *skb_in, int code)
+{
+ struct iphdr *iph;
+ struct udphdr *udph;
+ struct icmphdr *icmph;
+ struct sk_buff *nskb;
+ u32 saddr;
+ u8 tos;
+ int hh_len, length;
+ struct rtable *rt = (struct rtable*)skb_in->dst;
+ unsigned char *data;
+
+ if (!rt)
+ return;
+
+ if (!xrlim_allow(&rt->u.dst, 1*HZ))
+ return;
+
+ iph = skb_in->nh.iph;
+
+ /* No replies to physical multicast/broadcast */
+ if (skb_in->pkt_type!=PACKET_HOST)
+ return;
+
+ /* Now check at the protocol level */
+ if (rt->rt_flags&(RTCF_BROADCAST|RTCF_MULTICAST))
+ return;
+
+ /* Only reply to fragment 0. */
+ if (iph->frag_off&htons(IP_OFFSET))
+ return;
+
+ /* if UDP checksum is set, verify it's correct */
+ if (iph->protocol == IPPROTO_UDP
+ && skb_in->tail-(u8*)iph >= sizeof(struct udphdr)) {
+ int datalen = skb_in->len - (iph->ihl<<2);
+ udph = (struct udphdr *)((char *)iph + (iph->ihl<<2));
+ if (udph->check
+ && csum_tcpudp_magic(iph->saddr, iph->daddr,
+ datalen, IPPROTO_UDP,
+ csum_partial((char *)udph, datalen,
+ 0)) != 0)
+ return;
+ }
+
+ /* If we send an ICMP error to an ICMP error a mess would result.. */
+ if (iph->protocol == IPPROTO_ICMP
+ && skb_in->tail-(u8*)iph >= sizeof(struct icmphdr)) {
+ icmph = (struct icmphdr *)((char *)iph + (iph->ihl<<2));
+ /* Between echo-reply (0) and timestamp (13),
+ everything except echo-request (8) is an error.
+ Also, anything greater than NR_ICMP_TYPES is
+ unknown, and hence should be treated as an error... */
+ if ((icmph->type < ICMP_TIMESTAMP
+ && icmph->type != ICMP_ECHOREPLY
+ && icmph->type != ICMP_ECHO)
+ || icmph->type > NR_ICMP_TYPES)
+ return;
+ }
+
+ saddr = iph->daddr;
+ if (!(rt->rt_flags & RTCF_LOCAL))
+ saddr = 0;
+
+ tos = (iph->tos & IPTOS_TOS_MASK) | IPTOS_PREC_INTERNETCONTROL;
+
+ if (ip_route_output(&rt, iph->saddr, saddr, RT_TOS(tos), 0))
+ return;
+
+ /* RFC says return as much as we can without exceeding 576 bytes. */
+ length = skb_in->len + sizeof(struct iphdr) + sizeof(struct icmphdr);
+
+ if (length > rt->u.dst.pmtu)
+ length = rt->u.dst.pmtu;
+ if (length > 576)
+ length = 576;
+
+ hh_len = (rt->u.dst.dev->hard_header_len + 15)&~15;
+
+ nskb = alloc_skb(hh_len+15+length, GFP_ATOMIC);
+ if (!nskb) {
+ ip_rt_put(rt);
+ return;
+ }
+
+ nskb->priority = 0;
+ nskb->dst = &rt->u.dst;
+ skb_reserve(nskb, hh_len);
+
+ /* Set up IP header */
+ iph = nskb->nh.iph
+ = (struct iphdr *)skb_put(nskb, sizeof(struct iphdr));
+ iph->version=4;
+ iph->ihl=5;
+ iph->tos=tos;
+ iph->tot_len = htons(length);
+
+ /* PMTU discovery never applies to ICMP packets. */
+ iph->frag_off = 0;
+
+ iph->ttl = MAXTTL;
+ ip_select_ident(iph, &rt->u.dst, NULL);
+ iph->protocol=IPPROTO_ICMP;
+ iph->saddr=rt->rt_src;
+ iph->daddr=rt->rt_dst;
+ iph->check=0;
+ iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
+
+ /* Set up ICMP header. */
+ icmph = nskb->h.icmph
+ = (struct icmphdr *)skb_put(nskb, sizeof(struct icmphdr));
+ icmph->type = ICMP_DEST_UNREACH;
+ icmph->code = code;
+ icmph->un.gateway = 0;
+ icmph->checksum = 0;
+
+ /* Copy as much of original packet as will fit */
+ data = skb_put(nskb,
+ length - sizeof(struct iphdr) - sizeof(struct icmphdr));
+ memcpy(data, skb_in->nh.iph,
+ length - sizeof(struct iphdr) - sizeof(struct icmphdr));
+ icmph->checksum = ip_compute_csum((unsigned char *)icmph,
+ length - sizeof(struct iphdr));
+
+ connection_attach(nskb, skb_in->nfct);
+
+ NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, nskb, NULL, nskb->dst->dev,
+ ip_finish_output);
+}
+
+static unsigned int reject(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ const struct ipt_reject_info *reject = targinfo;
+
+ /* Our naive response construction doesn't deal with IP
+ options, and probably shouldn't try. */
+ if ((*pskb)->nh.iph->ihl<<2 != sizeof(struct iphdr))
+ return NF_DROP;
+
+ /* WARNING: This code causes reentry within iptables.
+ This means that the iptables jump stack is now crap. We
+ must return an absolute verdict. --RR */
+ switch (reject->with) {
+ case IPT_ICMP_NET_UNREACHABLE:
+ send_unreach(*pskb, ICMP_NET_UNREACH);
+ break;
+ case IPT_ICMP_HOST_UNREACHABLE:
+ send_unreach(*pskb, ICMP_HOST_UNREACH);
+ break;
+ case IPT_ICMP_PROT_UNREACHABLE:
+ send_unreach(*pskb, ICMP_PROT_UNREACH);
+ break;
+ case IPT_ICMP_PORT_UNREACHABLE:
+ send_unreach(*pskb, ICMP_PORT_UNREACH);
+ break;
+ case IPT_ICMP_NET_PROHIBITED:
+ send_unreach(*pskb, ICMP_NET_ANO);
+ break;
+ case IPT_ICMP_HOST_PROHIBITED:
+ send_unreach(*pskb, ICMP_HOST_ANO);
+ break;
+ case IPT_TCP_RESET:
+ send_reset(*pskb, hooknum == NF_IP_LOCAL_IN);
+ case IPT_ICMP_ECHOREPLY:
+ /* Doesn't happen. */
+ break;
+ }
+
+ return NF_DROP;
+}
+
+static int check(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const struct ipt_reject_info *rejinfo = targinfo;
+
+ if (targinfosize != IPT_ALIGN(sizeof(struct ipt_reject_info))) {
+ DEBUGP("REJECT: targinfosize %u != 0\n", targinfosize);
+ return 0;
+ }
+
+ /* Only allow these for packet filtering. */
+ if (strcmp(tablename, "filter") != 0) {
+ DEBUGP("REJECT: bad table `%s'.\n", tablename);
+ return 0;
+ }
+ if ((hook_mask & ~((1 << NF_IP_LOCAL_IN)
+ | (1 << NF_IP_FORWARD)
+ | (1 << NF_IP_LOCAL_OUT))) != 0) {
+ DEBUGP("REJECT: bad hook mask %X\n", hook_mask);
+ return 0;
+ }
+
+ if (rejinfo->with == IPT_ICMP_ECHOREPLY) {
+ printk("REJECT: ECHOREPLY no longer supported.\n");
+ return 0;
+ } else if (rejinfo->with == IPT_TCP_RESET) {
+ /* Must specify that it's a TCP packet */
+ if (e->ip.proto != IPPROTO_TCP
+ || (e->ip.invflags & IPT_INV_PROTO)) {
+ DEBUGP("REJECT: TCP_RESET illegal for non-tcp\n");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static struct ipt_target ipt_reject_reg
+= { { NULL, NULL }, "REJECT", reject, check, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ipt_register_target(&ipt_reject_reg))
+ return -EINVAL;
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_reject_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");