diff options
Diffstat (limited to 'release/src/linux/linux/net/ipv4/netfilter/ip_conntrack_proto_tcp.c')
-rw-r--r-- | release/src/linux/linux/net/ipv4/netfilter/ip_conntrack_proto_tcp.c | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/release/src/linux/linux/net/ipv4/netfilter/ip_conntrack_proto_tcp.c b/release/src/linux/linux/net/ipv4/netfilter/ip_conntrack_proto_tcp.c new file mode 100644 index 00000000..02f20742 --- /dev/null +++ b/release/src/linux/linux/net/ipv4/netfilter/ip_conntrack_proto_tcp.c @@ -0,0 +1,239 @@ +#define __NO_VERSION__ +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/timer.h> +#include <linux/netfilter.h> +#include <linux/module.h> +#include <linux/in.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/string.h> + +#include <net/tcp.h> + +#include <linux/netfilter_ipv4/ip_conntrack.h> +#include <linux/netfilter_ipv4/ip_conntrack_protocol.h> +#include <linux/netfilter_ipv4/lockhelp.h> + +#define DEBUGP(format, args...) + +/* Protects conntrack->proto.tcp */ +static DECLARE_RWLOCK(tcp_lock); + + +/* Actually, I believe that neither ipmasq (where this code is stolen + from) nor ipfilter do it exactly right. A new conntrack machine taking + into account packet loss (which creates uncertainty as to exactly + the conntrack of the connection) is required. RSN. --RR */ + +static const char *tcp_conntrack_names[] = { + "NONE", + "ESTABLISHED", + "SYN_SENT", + "SYN_RECV", + "FIN_WAIT", + "TIME_WAIT", + "CLOSE", + "CLOSE_WAIT", + "LAST_ACK", + "LISTEN" +}; + +#define sNO TCP_CONNTRACK_NONE +#define sES TCP_CONNTRACK_ESTABLISHED +#define sSS TCP_CONNTRACK_SYN_SENT +#define sSR TCP_CONNTRACK_SYN_RECV +#define sFW TCP_CONNTRACK_FIN_WAIT +#define sTW TCP_CONNTRACK_TIME_WAIT +#define sCL TCP_CONNTRACK_CLOSE +#define sCW TCP_CONNTRACK_CLOSE_WAIT +#define sLA TCP_CONNTRACK_LAST_ACK +#define sLI TCP_CONNTRACK_LISTEN +#define sIV TCP_CONNTRACK_MAX + +static enum tcp_conntrack tcp_conntracks[2][5][TCP_CONNTRACK_MAX] = { + { +/* ORIGINAL */ +/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI */ +/*syn*/ {sSS, sES, sSS, sSR, sSS, sSS, sSS, sSS, sSS, sLI }, +/*fin*/ {sTW, sFW, sSS, sTW, sFW, sTW, sCL, sTW, sLA, sLI }, +/*ack*/ {sES, sES, sSS, sES, sFW, sTW, sCL, sCW, sLA, sES }, +/*rst*/ {sCL, sCL, sSS, sCL, sCL, sTW, sCL, sCL, sCL, sCL }, +/*none*/{sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV } + }, + { +/* REPLY */ +/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI */ +/*syn*/ {sSR, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSR }, +/*fin*/ {sCL, sCW, sSS, sTW, sTW, sTW, sCL, sCW, sLA, sLI }, +/*ack*/ {sCL, sES, sSS, sSR, sFW, sTW, sCL, sCW, sCL, sLI }, +/*rst*/ {sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sLA, sLI }, +/*none*/{sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV } + } +}; + +static int tcp_pkt_to_tuple(const void *datah, size_t datalen, + struct ip_conntrack_tuple *tuple) +{ + const struct tcphdr *hdr = datah; + + tuple->src.u.tcp.port = hdr->source; + tuple->dst.u.tcp.port = hdr->dest; + + return 1; +} + +static int tcp_invert_tuple(struct ip_conntrack_tuple *tuple, + const struct ip_conntrack_tuple *orig) +{ + tuple->src.u.tcp.port = orig->dst.u.tcp.port; + tuple->dst.u.tcp.port = orig->src.u.tcp.port; + return 1; +} + +/* Print out the per-protocol part of the tuple. */ +static unsigned int tcp_print_tuple(char *buffer, + const struct ip_conntrack_tuple *tuple) +{ + return sprintf(buffer, "sport=%hu dport=%hu ", + ntohs(tuple->src.u.tcp.port), + ntohs(tuple->dst.u.tcp.port)); +} + +/* Print out the private part of the conntrack. */ +static unsigned int tcp_print_conntrack(char *buffer, + const struct ip_conntrack *conntrack) +{ + enum tcp_conntrack state; + + READ_LOCK(&tcp_lock); + state = conntrack->proto.tcp.state; + READ_UNLOCK(&tcp_lock); + + return sprintf(buffer, "%s ", tcp_conntrack_names[state]); +} + +static unsigned int get_conntrack_index(const struct tcphdr *tcph) +{ + if (tcph->rst) return 3; + else if (tcph->syn) return 0; + else if (tcph->fin) return 1; + else if (tcph->ack) return 2; + else return 4; +} + +/* Returns verdict for packet, or -1 for invalid. */ +static int tcp_packet(struct ip_conntrack *conntrack, + struct iphdr *iph, size_t len, + enum ip_conntrack_info ctinfo) +{ + enum tcp_conntrack newconntrack, oldtcpstate; + struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph + iph->ihl); + + /* We're guaranteed to have the base header, but maybe not the + options. */ + if (len < (iph->ihl + tcph->doff) * 4) { + DEBUGP("ip_conntrack_tcp: Truncated packet.\n"); + return -1; + } + + WRITE_LOCK(&tcp_lock); + oldtcpstate = conntrack->proto.tcp.state; + newconntrack + = tcp_conntracks + [CTINFO2DIR(ctinfo)] + [get_conntrack_index(tcph)][oldtcpstate]; + + /* Invalid */ + if (newconntrack == TCP_CONNTRACK_MAX) { + DEBUGP("ip_conntrack_tcp: Invalid dir=%i index=%u conntrack=%u\n", + CTINFO2DIR(ctinfo), get_conntrack_index(tcph), + conntrack->proto.tcp.state); + WRITE_UNLOCK(&tcp_lock); + return -1; + } + + conntrack->proto.tcp.state = newconntrack; + + /* Poor man's window tracking: record SYN/ACK for handshake check */ + if (oldtcpstate == TCP_CONNTRACK_SYN_SENT + && CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY + && tcph->syn && tcph->ack) + conntrack->proto.tcp.handshake_ack + = htonl(ntohl(tcph->seq) + 1); + + if (oldtcpstate == TCP_CONNTRACK_SYN_SENT + && CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY + && tcph->syn && !tcph->ack){ + WRITE_UNLOCK(&tcp_lock); + return -1; + } + + /* If only reply is a RST, we can consider ourselves not to + have an established connection: this is a fairly common + problem case, so we can delete the conntrack + immediately. --RR */ + if (!(conntrack->status & IPS_SEEN_REPLY) && tcph->rst) { + WRITE_UNLOCK(&tcp_lock); + if (del_timer(&conntrack->timeout)) + conntrack->timeout.function((unsigned long)conntrack); + } else { + /* Set ASSURED if we see see valid ack in ESTABLISHED after SYN_RECV */ + if (oldtcpstate == TCP_CONNTRACK_SYN_RECV + && CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL + && tcph->ack && !tcph->syn + && tcph->ack_seq == conntrack->proto.tcp.handshake_ack) + set_bit(IPS_ASSURED_BIT, &conntrack->status); + + WRITE_UNLOCK(&tcp_lock); + ip_ct_refresh(conntrack, + sysctl_ip_conntrack_tcp_timeouts[newconntrack]); + } + + return NF_ACCEPT; +} + +/* Called when a new connection for this protocol found. */ +static int tcp_new(struct ip_conntrack *conntrack, + struct iphdr *iph, size_t len) +{ + enum tcp_conntrack newconntrack; + struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph + iph->ihl); + + /* Don't need lock here: this conntrack not in circulation yet */ + newconntrack + = tcp_conntracks[0][get_conntrack_index(tcph)] + [TCP_CONNTRACK_NONE]; + + /* Invalid: delete conntrack */ + if (newconntrack == TCP_CONNTRACK_MAX) { + DEBUGP("ip_conntrack_tcp: invalid new deleting.\n"); + return 0; + } + + if (tcph->syn && tcph->ack) + { + DEBUGP("ip_conntrack_tcp: invalid new deleting.\n"); + return 0; + } + + conntrack->proto.tcp.state = newconntrack; + return 1; +} + +static int tcp_exp_matches_pkt(struct ip_conntrack_expect *exp, + struct sk_buff **pskb) +{ + struct iphdr *iph = (*pskb)->nh.iph; + struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph + iph->ihl); + unsigned int datalen; + + datalen = (*pskb)->len - iph->ihl*4 - tcph->doff*4; + + return between(exp->seq, ntohl(tcph->seq), ntohl(tcph->seq) + datalen); +} + +struct ip_conntrack_protocol ip_conntrack_protocol_tcp += { { NULL, NULL }, IPPROTO_TCP, "tcp", + tcp_pkt_to_tuple, tcp_invert_tuple, tcp_print_tuple, tcp_print_conntrack, + tcp_packet, tcp_new, NULL, tcp_exp_matches_pkt, NULL }; |