Tried to patch ipt_REJECT for a RST to the destination as well

Joachim Wieland jwieland@kawo2.rwth-aachen.de
Sat, 20 Oct 2001 10:36:09 +0200


Hi,

I tried to modify ipt_REJECT.c, so that it is able to send an RST packet to
the destination as well.

I say "I tried" since it didn't work as it should, the packet is not sent
out

I'm shortening the file in the body here, for my complete change see

	http://www.mcknight.de/ipt_REJECT.c
		(for /usr/src/linux/net/ipv4/netfilter/)
	http://www.mcknight.de/netfilter_ipv4.h
		(for /usr/src/linux/include/linux/)
	http://www.mcknight.de/libipt_REJECT.c
		(for iptables-1.2.3/extensions/)

I guess people reading the digest won't be happy with a lot of attachments
:-)

----------------

The way of the program flow should be like this:

	reject() calls send_reset() and tells it what to reset by passing
	the constants TCP_RST_SRC and TCP_RST_DST.

	send_reset then calls send_reset_src and/or send_reset_dst.

	Both functions acquire an skbuff by calling prepare_reset_packet
	which sets all values that are equal for both reset directions.
	Thereafter both functions change the relevant values in the packet
	and then call finish_reset_packet, which calculates the checksums,
	determines the route and sends the packet out.

	Resetting the source works but this worked in the past as well  :-)

	Yet if I pass TCP_RST_DST or TCP_RST_DST | TCP_RST_SRC to
	send_reset, the destination does not get an RST-packet (I can't see
	it with tcpdump)


----------------

#define TCP_RST_SRC  1
#define TCP_RST_DST  2


/* This function allocates a new skbuff, clears it, cuts off the payload and
 * sets the necessary TCP and IP header options that are the same for an
 * RST packet to the source and to the destination
 */

static void prepare_reset_packet(struct sk_buff* oldskb,
                                 struct sk_buff** nskb,
                                 struct tcphdr** tcph)
{
	/* 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

	*tcph = (struct tcphdr *)((u_int32_t*)(*nskb)->nh.iph + (*nskb)->nh.iph->ihl);

	/* 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);

	/* Tell the other side to send no more data */
	(*tcph)->window = 0;
	(*tcph)->urg_ptr = 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;

}


/* This function performes the last operations on the packet, i.e. it
 * calculates the checksums, gets a route and sends the packet out.
 */
static void finish_reset_packet(struct sk_buff* oldskb,
                                struct sk_buff* nskb,
                                struct tcphdr* tcph,
                                int local)
{
	struct rtable *rt;
	struct rt_key rkey;

	printk("Finishing RST packet\n");
	/* 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 checksum */
	nskb->nh.iph->check = 0;
	nskb->nh.iph->check = ip_fast_csum((unsigned char *)nskb->nh.iph, 
					   nskb->nh.iph->ihl);

	printk("Getting route for RST packet\n");

-------------------

I didn't quite understand this part: When should I set the source address to
a value != 0 and when to 0 ? If it is a local packet I leave it because the
packet won't get routed accross an external interface and otherwise I set it
to 0, so that the IP can be set to the one of the external interface?

-------------------

	rkey.dst = nskb->nh.iph->daddr;
/*	rkey.src = nskb->nh.iph->saddr; */
	rkey.src = 0;
	rkey.tos = 0;
	rkey.oif = 0;

	/* Routing: if not headed for us, route won't like source */
	if (ip_route_output_key(&rt, &rkey) != 0) {
			printk("IP route output error\n");
			printk("daddr: %lu\n", (nskb->nh.iph->daddr));
			printk("saddr: %lu\n", (nskb->nh.iph->saddr));
			printk("local: %d\n", local);
		goto free_nskb;
	}

	dst_release(nskb->dst);
	nskb->dst = &rt->u.dst;

	/* "Never happens" */
	if (nskb->len > nskb->dst->pmtu) {
		goto free_nskb;
	}

	printk("Attaching packet\n");
	connection_attach(nskb, oldskb->nfct);

	printk("Sending packet\n");

-------------------
"Sending packet" is the last output I get but I can't see the packet on the
wire.

-------------------

	NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, nskb, NULL, nskb->dst->dev,
		ip_finish_output);

	printk("Packet sent\n");
	return;

 free_nskb:
	kfree_skb(nskb);
}

static void send_reset_src(struct sk_buff* oldskb,
                           struct tcphdr* otcph,
                           unsigned int otcplen,
                           int local)
{
	struct sk_buff* nskb;
	struct tcphdr* tcph;
	u_int16_t tmp;
	int needs_ack;

	prepare_reset_packet(oldskb, &nskb, &tcph);

	if (!nskb)
		return;

	/* Swap source and dest */
	nskb->nh.iph->daddr = xchg(&nskb->nh.iph->saddr, nskb->nh.iph->daddr);
	tmp = tcph->source;
	tcph->source = tcph->dest;
	tcph->dest = tmp;

	/* This packet goes back to the source. If it had an ACK flag, the
	 * reply packet has no ACK set.
	 * We also do not have to increase our SEQ number
	 */

	/* However, if it had no ACK set, let's ack it. Don't send a new SEQ
	 * number.
	 */

	if (tcph->ack) {
		needs_ack = 0;
		/* +1 ? */
		tcph->seq = otcph->ack_seq;
		tcph->ack_seq = 0;
	} else {
		needs_ack = 1;
		/* For the ACK number of the reply packet, take the SEQ
		 * number of the packet we are responding to, add the data
		 * size (length of TCP packet minus TCP header size) and
		 * increase it by one if it was a SYN or FIN packet.
		 */
		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;

	finish_reset_packet(oldskb, nskb, tcph, local);

}


static void send_reset_dst(struct sk_buff* oldskb,
                           struct tcphdr* otcph,
                           unsigned int otcplen,
                           int local)
{
	struct sk_buff* nskb;
	struct tcphdr* tcph;
	u_int16_t tmp;
	int needs_ack;

	prepare_reset_packet(oldskb, &nskb, &tcph);

	if (!nskb)
		return;

	/* This packet goes back to the source. If it had an ACK flag, the
	 * reply packet has no ACK set.
	 * We also do not have to increase our SEQ number
	 */

	/* However, if it had no ACK set, let's ack it. Don't send a new SEQ
	 * number.
	 */

	if (tcph->ack) {
		needs_ack = 0;
		/* +1 ? */
		tcph->seq = otcph->ack_seq;
		tcph->ack_seq = 0;
	} else {
		needs_ack = 1;
	/* For the ACK number of the reply packet, take the SEQ number
	 * of the packet we are responding to, add the data size (length
	 * of TCP packet minus TCP header size) and increase it by one if it
	 * was a SYN or FIN packet.
	 */
		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;

	finish_reset_packet(oldskb, nskb, tcph, local);

}


/* Send RST reply */
static void send_reset(struct sk_buff *oldskb, int local, int where)
{
	struct tcphdr *otcph;
	unsigned int otcplen;

	/* 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;

	if (where & TCP_RST_SRC)
		send_reset_src(oldskb, otcph, otcplen, local);

	if (where & TCP_RST_DST)
		send_reset_dst(oldskb, otcph, otcplen, local);
}


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_TCP_RESET:
		/* the default setting is to send the TCP RST to the source
		 * only */
	case IPT_TCP_RESET_SRC:
		send_reset(*pskb, hooknum == NF_IP_LOCAL_IN, TCP_RST_SRC);
		break;
	case IPT_TCP_RESET_DST:
		send_reset(*pskb, hooknum == NF_IP_LOCAL_IN, TCP_RST_DST);
		 break;
	case IPT_TCP_RESET_BOTH:
		send_reset(*pskb, hooknum == NF_IP_LOCAL_IN,
						TCP_RST_SRC | TCP_RST_DST);
		break;
	case IPT_ICMP_ECHOREPLY:
		/* Doesn't happen. */
		break;
	}

	return NF_DROP;
}


Thank you very much for any help,

Joachim

-- 
*****PGP key available - send e-mail request*****   -   ICQ: 37225940
Preserve Wildlife!  Throw a party today!