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!