diff options
author | YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> | 2006-11-14 19:07:45 -0800 |
---|---|---|
committer | David S. Miller <davem@sunset.davemloft.net> | 2006-12-02 21:22:39 -0800 |
commit | cfb6eeb4c860592edd123fdea908d23c6ad1c7dc (patch) | |
tree | 361c073622faa540ef6602ef1b0a6e8c0a17fc60 | |
parent | bf6bce71eae386dbc37f93af7e5ad173450d9945 (diff) |
[TCP]: MD5 Signature Option (RFC2385) support.
Based on implementation by Rick Payne.
Signed-off-by: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | CREDITS | 3 | ||||
-rw-r--r-- | include/linux/tcp.h | 35 | ||||
-rw-r--r-- | include/net/request_sock.h | 3 | ||||
-rw-r--r-- | include/net/tcp.h | 143 | ||||
-rw-r--r-- | include/net/timewait_sock.h | 3 | ||||
-rw-r--r-- | net/dccp/ipv4.c | 6 | ||||
-rw-r--r-- | net/dccp/ipv6.c | 6 | ||||
-rw-r--r-- | net/dccp/minisocks.c | 2 | ||||
-rw-r--r-- | net/ipv4/Kconfig | 16 | ||||
-rw-r--r-- | net/ipv4/tcp.c | 137 | ||||
-rw-r--r-- | net/ipv4/tcp_input.c | 8 | ||||
-rw-r--r-- | net/ipv4/tcp_ipv4.c | 673 | ||||
-rw-r--r-- | net/ipv4/tcp_minisocks.c | 64 | ||||
-rw-r--r-- | net/ipv4/tcp_output.c | 111 | ||||
-rw-r--r-- | net/ipv6/tcp_ipv6.c | 568 |
15 files changed, 1714 insertions, 64 deletions
@@ -2598,6 +2598,9 @@ S: Ucitelska 1576 S: Prague 8 S: 182 00 Czech Republic +N: Rick Payne +D: RFC2385 Support for TCP + N: Barak A. Pearlmutter E: bap@cs.unm.edu W: http://www.cs.unm.edu/~bap/ diff --git a/include/linux/tcp.h b/include/linux/tcp.h index 2d36f6db3706..0aecfc955591 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -19,6 +19,7 @@ #include <linux/types.h> #include <asm/byteorder.h> +#include <linux/socket.h> struct tcphdr { __be16 source; @@ -94,6 +95,7 @@ enum { #define TCP_INFO 11 /* Information about this connection. */ #define TCP_QUICKACK 12 /* Block/reenable quick acks */ #define TCP_CONGESTION 13 /* Congestion control algorithm */ +#define TCP_MD5SIG 14 /* TCP MD5 Signature (RFC2385) */ #define TCPI_OPT_TIMESTAMPS 1 #define TCPI_OPT_SACK 2 @@ -157,6 +159,17 @@ struct tcp_info __u32 tcpi_total_retrans; }; +/* for TCP_MD5SIG socket option */ +#define TCP_MD5SIG_MAXKEYLEN 80 + +struct tcp_md5sig { + struct __kernel_sockaddr_storage tcpm_addr; /* address associated */ + __u16 __tcpm_pad1; /* zero */ + __u16 tcpm_keylen; /* key length */ + __u32 __tcpm_pad2; /* zero */ + __u8 tcpm_key[TCP_MD5SIG_MAXKEYLEN]; /* key (binary) */ +}; + #ifdef __KERNEL__ #include <linux/skbuff.h> @@ -197,9 +210,13 @@ struct tcp_options_received { }; struct tcp_request_sock { - struct inet_request_sock req; - __u32 rcv_isn; - __u32 snt_isn; + struct inet_request_sock req; +#ifdef CONFIG_TCP_MD5SIG + /* Only used by TCP MD5 Signature so far. */ + struct tcp_request_sock_ops *af_specific; +#endif + __u32 rcv_isn; + __u32 snt_isn; }; static inline struct tcp_request_sock *tcp_rsk(const struct request_sock *req) @@ -363,6 +380,14 @@ struct tcp_sock { __u32 probe_seq_start; __u32 probe_seq_end; } mtu_probe; + +#ifdef CONFIG_TCP_MD5SIG +/* TCP AF-Specific parts; only used by MD5 Signature support so far */ + struct tcp_sock_af_ops *af_specific; + +/* TCP MD5 Signagure Option information */ + struct tcp_md5sig_info *md5sig_info; +#endif }; static inline struct tcp_sock *tcp_sk(const struct sock *sk) @@ -377,6 +402,10 @@ struct tcp_timewait_sock { __u32 tw_rcv_wnd; __u32 tw_ts_recent; long tw_ts_recent_stamp; +#ifdef CONFIG_TCP_MD5SIG + __u16 tw_md5_keylen; + __u8 tw_md5_key[TCP_MD5SIG_MAXKEYLEN]; +#endif }; static inline struct tcp_timewait_sock *tcp_twsk(const struct sock *sk) diff --git a/include/net/request_sock.h b/include/net/request_sock.h index b5b023e79e5f..e37baaf2080b 100644 --- a/include/net/request_sock.h +++ b/include/net/request_sock.h @@ -35,7 +35,8 @@ struct request_sock_ops { struct dst_entry *dst); void (*send_ack)(struct sk_buff *skb, struct request_sock *req); - void (*send_reset)(struct sk_buff *skb); + void (*send_reset)(struct sock *sk, + struct sk_buff *skb); void (*destructor)(struct request_sock *req); }; diff --git a/include/net/tcp.h b/include/net/tcp.h index e1a5d29d0a1f..363960872de0 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -28,6 +28,7 @@ #include <linux/percpu.h> #include <linux/skbuff.h> #include <linux/dmaengine.h> +#include <linux/crypto.h> #include <net/inet_connection_sock.h> #include <net/inet_timewait_sock.h> @@ -161,6 +162,7 @@ extern void tcp_time_wait(struct sock *sk, int state, int timeo); #define TCPOPT_SACK_PERM 4 /* SACK Permitted */ #define TCPOPT_SACK 5 /* SACK Block */ #define TCPOPT_TIMESTAMP 8 /* Better RTT estimations/PAWS */ +#define TCPOPT_MD5SIG 19 /* MD5 Signature (RFC2385) */ /* * TCP option lengths @@ -170,6 +172,7 @@ extern void tcp_time_wait(struct sock *sk, int state, int timeo); #define TCPOLEN_WINDOW 3 #define TCPOLEN_SACK_PERM 2 #define TCPOLEN_TIMESTAMP 10 +#define TCPOLEN_MD5SIG 18 /* But this is what stacks really send out. */ #define TCPOLEN_TSTAMP_ALIGNED 12 @@ -178,6 +181,7 @@ extern void tcp_time_wait(struct sock *sk, int state, int timeo); #define TCPOLEN_SACK_BASE 2 #define TCPOLEN_SACK_BASE_ALIGNED 4 #define TCPOLEN_SACK_PERBLOCK 8 +#define TCPOLEN_MD5SIG_ALIGNED 20 /* Flags in tp->nonagle */ #define TCP_NAGLE_OFF 1 /* Nagle's algo is disabled */ @@ -299,6 +303,8 @@ extern void tcp_cleanup_rbuf(struct sock *sk, int copied); extern int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp); +extern void tcp_twsk_destructor(struct sock *sk); + static inline void tcp_dec_quickack_mode(struct sock *sk, const unsigned int pkts) { @@ -1064,6 +1070,114 @@ static inline void clear_all_retrans_hints(struct tcp_sock *tp){ tp->fastpath_skb_hint = NULL; } +/* MD5 Signature */ +struct crypto_hash; + +/* - key database */ +struct tcp_md5sig_key { + u8 *key; + u8 keylen; +}; + +struct tcp4_md5sig_key { + u8 *key; + u16 keylen; + __be32 addr; +}; + +struct tcp6_md5sig_key { + u8 *key; + u16 keylen; +#if 0 + u32 scope_id; /* XXX */ +#endif + struct in6_addr addr; +}; + +/* - sock block */ +struct tcp_md5sig_info { + struct tcp4_md5sig_key *keys4; +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + struct tcp6_md5sig_key *keys6; + u32 entries6; + u32 alloced6; +#endif + u32 entries4; + u32 alloced4; +}; + +/* - pseudo header */ +struct tcp4_pseudohdr { + __be32 saddr; + __be32 daddr; + __u8 pad; + __u8 protocol; + __be16 len; +}; + +struct tcp6_pseudohdr { + struct in6_addr saddr; + struct in6_addr daddr; + __be32 len; + __be32 protocol; /* including padding */ +}; + +union tcp_md5sum_block { + struct tcp4_pseudohdr ip4; +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + struct tcp6_pseudohdr ip6; +#endif +}; + +/* - pool: digest algorithm, hash description and scratch buffer */ +struct tcp_md5sig_pool { + struct hash_desc md5_desc; + union tcp_md5sum_block md5_blk; +}; + +#define TCP_MD5SIG_MAXKEYS (~(u32)0) /* really?! */ + +/* - functions */ +extern int tcp_v4_calc_md5_hash(char *md5_hash, + struct tcp_md5sig_key *key, + struct sock *sk, + struct dst_entry *dst, + struct request_sock *req, + struct tcphdr *th, + int protocol, int tcplen); +extern struct tcp_md5sig_key *tcp_v4_md5_lookup(struct sock *sk, + struct sock *addr_sk); + +extern int tcp_v4_md5_do_add(struct sock *sk, + __be32 addr, + u8 *newkey, + u8 newkeylen); + +extern int tcp_v4_md5_do_del(struct sock *sk, + u32 addr); + +extern struct tcp_md5sig_pool **tcp_alloc_md5sig_pool(void); +extern void tcp_free_md5sig_pool(void); + +extern struct tcp_md5sig_pool *__tcp_get_md5sig_pool(int cpu); +extern void __tcp_put_md5sig_pool(void); + +static inline +struct tcp_md5sig_pool *tcp_get_md5sig_pool(void) +{ + int cpu = get_cpu(); + struct tcp_md5sig_pool *ret = __tcp_get_md5sig_pool(cpu); + if (!ret) + put_cpu(); + return ret; +} + +static inline void tcp_put_md5sig_pool(void) +{ + __tcp_put_md5sig_pool(); + put_cpu(); +} + /* /proc */ enum tcp_seq_states { TCP_SEQ_STATE_LISTENING, @@ -1103,6 +1217,35 @@ extern int tcp4_proc_init(void); extern void tcp4_proc_exit(void); #endif +/* TCP af-specific functions */ +struct tcp_sock_af_ops { +#ifdef CONFIG_TCP_MD5SIG + struct tcp_md5sig_key *(*md5_lookup) (struct sock *sk, + struct sock *addr_sk); + int (*calc_md5_hash) (char *location, + struct tcp_md5sig_key *md5, + struct sock *sk, + struct dst_entry *dst, + struct request_sock *req, + struct tcphdr *th, + int protocol, int len); + int (*md5_add) (struct sock *sk, + struct sock *addr_sk, + u8 *newkey, + u8 len); + int (*md5_parse) (struct sock *sk, + char __user *optval, + int optlen); +#endif +}; + +struct tcp_request_sock_ops { +#ifdef CONFIG_TCP_MD5SIG + struct tcp_md5sig_key *(*md5_lookup) (struct sock *sk, + struct request_sock *req); +#endif +}; + extern void tcp_v4_init(struct net_proto_family *ops); extern void tcp_init(void); diff --git a/include/net/timewait_sock.h b/include/net/timewait_sock.h index be293d795e38..d7a306ea560d 100644 --- a/include/net/timewait_sock.h +++ b/include/net/timewait_sock.h @@ -31,6 +31,9 @@ static inline int twsk_unique(struct sock *sk, struct sock *sktw, void *twp) static inline void twsk_destructor(struct sock *sk) { + BUG_ON(sk == NULL); + BUG_ON(sk->sk_prot == NULL); + BUG_ON(sk->sk_prot->twsk_prot == NULL); if (sk->sk_prot->twsk_prot->twsk_destructor != NULL) sk->sk_prot->twsk_prot->twsk_destructor(sk); } diff --git a/net/dccp/ipv4.c b/net/dccp/ipv4.c index 34d6d197c3b2..35985334daee 100644 --- a/net/dccp/ipv4.c +++ b/net/dccp/ipv4.c @@ -509,7 +509,7 @@ out: return err; } -static void dccp_v4_ctl_send_reset(struct sk_buff *rxskb) +static void dccp_v4_ctl_send_reset(struct sock *sk, struct sk_buff *rxskb) { int err; struct dccp_hdr *rxdh = dccp_hdr(rxskb), *dh; @@ -724,7 +724,7 @@ int dccp_v4_do_rcv(struct sock *sk, struct sk_buff *skb) return 0; reset: - dccp_v4_ctl_send_reset(skb); + dccp_v4_ctl_send_reset(sk, skb); discard: kfree_skb(skb); return 0; @@ -913,7 +913,7 @@ no_dccp_socket: if (dh->dccph_type != DCCP_PKT_RESET) { DCCP_SKB_CB(skb)->dccpd_reset_code = DCCP_RESET_CODE_NO_CONNECTION; - dccp_v4_ctl_send_reset(skb); + dccp_v4_ctl_send_reset(sk, skb); } discard_it: diff --git a/net/dccp/ipv6.c b/net/dccp/ipv6.c index fc326173c215..e0a0607862ef 100644 --- a/net/dccp/ipv6.c +++ b/net/dccp/ipv6.c @@ -310,7 +310,7 @@ static void dccp_v6_reqsk_destructor(struct request_sock *req) kfree_skb(inet6_rsk(req)->pktopts); } -static void dccp_v6_ctl_send_reset(struct sk_buff *rxskb) +static void dccp_v6_ctl_send_reset(struct sock *sk, struct sk_buff *rxskb) { struct dccp_hdr *rxdh = dccp_hdr(rxskb), *dh; const u32 dccp_hdr_reset_len = sizeof(struct dccp_hdr) + @@ -805,7 +805,7 @@ static int dccp_v6_do_rcv(struct sock *sk, struct sk_buff *skb) return 0; reset: - dccp_v6_ctl_send_reset(skb); + dccp_v6_ctl_send_reset(sk, skb); discard: if (opt_skb != NULL) __kfree_skb(opt_skb); @@ -902,7 +902,7 @@ no_dccp_socket: if (dh->dccph_type != DCCP_PKT_RESET) { DCCP_SKB_CB(skb)->dccpd_reset_code = DCCP_RESET_CODE_NO_CONNECTION; - dccp_v6_ctl_send_reset(skb); + dccp_v6_ctl_send_reset(sk, skb); } discard_it: diff --git a/net/dccp/minisocks.c b/net/dccp/minisocks.c index 0c49733f5be1..3975048d8094 100644 --- a/net/dccp/minisocks.c +++ b/net/dccp/minisocks.c @@ -246,7 +246,7 @@ listen_overflow: DCCP_SKB_CB(skb)->dccpd_reset_code = DCCP_RESET_CODE_TOO_BUSY; drop: if (dccp_hdr(skb)->dccph_type != DCCP_PKT_RESET) - req->rsk_ops->send_reset(skb); + req->rsk_ops->send_reset(sk, skb); inet_csk_reqsk_queue_drop(sk, req, prev); goto out; diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig index bc298bcc344e..39e0cb763588 100644 --- a/net/ipv4/Kconfig +++ b/net/ipv4/Kconfig @@ -618,5 +618,21 @@ config DEFAULT_TCP_CONG default "reno" if DEFAULT_RENO default "cubic" +config TCP_MD5SIG + bool "TCP: MD5 Signature Option support (RFC2385) (EXPERIMENTAL)" + depends on EXPERIMENTAL + select CRYPTO + select CRYPTO_MD5 + ---help--- + RFC2385 specifices a method of giving MD5 protection to TCP sessions. + Its main (only?) use is to protect BGP sessions between core routers + on the Internet. + + If unsure, say N. + +config TCP_MD5SIG_DEBUG + bool "TCP: MD5 Signature Option debugging" + depends on TCP_MD5SIG + source "net/ipv4/ipvs/Kconfig" diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index c05e8edaf544..dadef867a3bb 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -258,6 +258,7 @@ #include <linux/bootmem.h> #include <linux/cache.h> #include <linux/err.h> +#include <linux/crypto.h> #include <net/icmp.h> #include <net/tcp.h> @@ -1942,6 +1943,13 @@ static int do_tcp_setsockopt(struct sock *sk, int level, } break; +#ifdef CONFIG_TCP_MD5SIG + case TCP_MD5SIG: + /* Read the IP->Key mappings from userspace */ + err = tp->af_specific->md5_parse(sk, optval, optlen); + break; +#endif + default: err = -ENOPROTOOPT; break; @@ -2231,6 +2239,135 @@ out: } EXPORT_SYMBOL(tcp_tso_segment); +#ifdef CONFIG_TCP_MD5SIG +static unsigned long tcp_md5sig_users; +static struct tcp_md5sig_pool **tcp_md5sig_pool; +static DEFINE_SPINLOCK(tcp_md5sig_pool_lock); + +static void __tcp_free_md5sig_pool(struct tcp_md5sig_pool **pool) +{ + int cpu; + for_each_possible_cpu(cpu) { + struct tcp_md5sig_pool *p = *per_cpu_ptr(pool, cpu); + if (p) { + if (p->md5_desc.tfm) + crypto_free_hash(p->md5_desc.tfm); + kfree(p); + p = NULL; + } + } + free_percpu(pool); +} + +void tcp_free_md5sig_pool(void) +{ + struct tcp_md5sig_pool **pool = NULL; + + spin_lock(&tcp_md5sig_pool_lock); + if (--tcp_md5sig_users == 0) { + pool = tcp_md5sig_pool; + tcp_md5sig_pool = NULL; + } + spin_unlock(&tcp_md5sig_pool_lock); + if (pool) + __tcp_free_md5sig_pool(pool); +} + +EXPORT_SYMBOL(tcp_free_md5sig_pool); + +struct tcp_md5sig_pool **__tcp_alloc_md5sig_pool(void) +{ + int cpu; + struct tcp_md5sig_pool **pool; + + pool = alloc_percpu(struct tcp_md5sig_pool *); + if (!pool) + return NULL; + + for_each_possible_cpu(cpu) { + struct tcp_md5sig_pool *p; + struct crypto_hash *hash; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + goto out_free; + *per_cpu_ptr(pool, cpu) = p; + + hash = crypto_alloc_hash("md5", 0, CRYPTO_ALG_ASYNC); + if (!hash || IS_ERR(hash)) + goto out_free; + + p->md5_desc.tfm = hash; + } + return pool; +out_free: + __tcp_free_md5sig_pool(pool); + return NULL; +} + +struct tcp_md5sig_pool **tcp_alloc_md5sig_pool(void) +{ + struct tcp_md5sig_pool **pool; + int alloc = 0; + +retry: + spin_lock(&tcp_md5sig_pool_lock); + pool = tcp_md5sig_pool; + if (tcp_md5sig_users++ == 0) { + alloc = 1; + spin_unlock(&tcp_md5sig_pool_lock); + } else if (!pool) { + tcp_md5sig_users--; + spin_unlock(&tcp_md5sig_pool_lock); + cpu_relax(); + goto retry; + } else + spin_unlock(&tcp_md5sig_pool_lock); + + if (alloc) { + /* we cannot hold spinlock here because this may sleep. */ + struct tcp_md5sig_pool **p = __tcp_alloc_md5sig_pool(); + spin_lock(&tcp_md5sig_pool_lock); + if (!p) { + tcp_md5sig_users--; + spin_unlock(&tcp_md5sig_pool_lock); + return NULL; + } + pool = tcp_md5sig_pool; + if (pool) { + /* oops, it has already been assigned. */ + spin_unlock(&tcp_md5sig_pool_lock); + __tcp_free_md5sig_pool(p); + } else { + tcp_md5sig_pool = pool = p; + spin_unlock(&tcp_md5sig_pool_lock); + } + } + return pool; +} + +EXPORT_SYMBOL(tcp_alloc_md5sig_pool); + +struct tcp_md5sig_pool *__tcp_get_md5sig_pool(int cpu) +{ + struct tcp_md5sig_pool **p; + spin_lock(&tcp_md5sig_pool_lock); + p = tcp_md5sig_pool; + if (p) + tcp_md5sig_users++; + spin_unlock(&tcp_md5sig_pool_lock); + return (p ? *per_cpu_ptr(p, cpu) : NULL); +} + +EXPORT_SYMBOL(__tcp_get_md5sig_pool); + +void __tcp_put_md5sig_pool(void) { + __tcp_free_md5sig_pool(tcp_md5sig_pool); +} + +EXPORT_SYMBOL(__tcp_put_md5sig_pool); +#endif + extern void __skb_cb_too_small_for_tcp(int, int); extern struct tcp_congestion_ops tcp_reno; diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 4a8c96cdec7d..6ab3423674bb 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -2677,6 +2677,14 @@ void tcp_parse_options(struct sk_buff *skb, struct tcp_options_received *opt_rx, opt_rx->sack_ok) { TCP_SKB_CB(skb)->sacked = (ptr - 2) - (unsigned char *)th; } +#ifdef CONFIG_TCP_MD5SIG + case TCPOPT_MD5SIG: + /* + * The MD5 Hash has already been + * checked (see tcp_v{4,6}_do_rcv()). + */ + break; +#endif }; ptr+=opsize-2; length-=opsize; diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 0ad0904bf56c..8c8e8112f98d 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -78,6 +78,9 @@ #include <linux/proc_fs.h> #include <linux/seq_file.h> +#include <linux/crypto.h> +#include <linux/scatterlist.h> + int sysctl_tcp_tw_reuse __read_mostly; int sysctl_tcp_low_latency __read_mostly; @@ -89,6 +92,13 @@ static struct socket *tcp_socket; void tcp_v4_send_check(struct sock *sk, int len, struct sk_buff *skb); +#ifdef CONFIG_TCP_MD5SIG +static struct tcp_md5sig_key *tcp_v4_md5_do_lookup(struct sock *sk, __be32 addr); +static int tcp_v4_do_calc_md5_hash(char *md5_hash, struct tcp_md5sig_key *key, + __be32 saddr, __be32 daddr, struct tcphdr *th, + int protocol, int tcplen); +#endif + struct inet_hashinfo __cacheline_aligned tcp_hashinfo = { .lhash_lock = __RW_LOCK_UNLOCKED(tcp_hashinfo.lhash_lock), .lhash_users = ATOMIC_INIT(0), @@ -526,11 +536,19 @@ int tcp_v4_gso_send_check(struct sk_buff *skb) * Exception: precedence violation. We do not implement it in any case. */ -static void tcp_v4_send_reset(struct sk_buff *skb) +static void tcp_v4_send_reset(struct sock *sk, struct sk_buff *skb) { struct tcphdr *th = skb->h.th; - struct tcphdr rth; + struct { + struct tcphdr th; +#ifdef CONFIG_TCP_MD5SIG + u32 opt[(TCPOLEN_MD5SIG_ALIGNED >> 2)]; +#endif + } rep; struct ip_reply_arg arg; +#ifdef CONFIG_TCP_MD5SIG + struct tcp_md5sig_key *key; +#endif /* Never send a reset in response to a reset. */ if (th->rst) @@ -540,29 +558,50 @@ static void tcp_v4_send_reset(struct sk_buff *skb) return; /* Swap the send and the receive. */ - memset(&rth, 0, sizeof(struct tcphdr)); - rth.dest = th->source; - rth.source = th->dest; - rth.doff = sizeof(struct tcphdr) / 4; - rth.rst = 1; + memset(&rep, 0, sizeof(rep)); + rep.th.dest = th->source; + rep.th.source = th->dest; + rep.th.doff = sizeof(struct tcphdr) / 4; + rep.th.rst = 1; if (th->ack) { - rth.seq = th->ack_seq; + rep.th.seq = th->ack_seq; } else { - rth.ack = 1; - rth.ack_seq = htonl(ntohl(th->seq) + th->syn + th->fin + - skb->len - (th->doff << 2)); + rep.th.ack = 1; + rep.th.ack_seq = htonl(ntohl(th->seq) + th->syn + th->fin + + skb->len - (th->doff << 2)); } memset(&arg, 0, sizeof arg); - arg.iov[0].iov_base = (unsigned char *)&rth; - arg.iov[0].iov_len = sizeof rth; + arg.iov[0].iov_base = (unsigned char *)&rep; + arg.iov[0].iov_len = sizeof(rep.th); + +#ifdef CONFIG_TCP_MD5SIG + key = sk ? tcp_v4_md5_do_lookup(sk, skb->nh.iph->daddr) : NULL; + if (key) { + rep.opt[0] = htonl((TCPOPT_NOP << 24) | + (TCPOPT_NOP << 16) | + (TCPOPT_MD5SIG << 8) | + TCPOLEN_MD5SIG); + /* Update length and the length the header thinks exists */ + arg.iov[0].iov_len += TCPOLEN_MD5SIG_ALIGNED; + rep.th.doff = arg.iov[0].iov_len / 4; + + tcp_v4_do_calc_md5_hash((__u8 *)&rep.opt[1], + key, + skb->nh.iph->daddr, + skb->nh.iph->saddr, + &rep.th, IPPROTO_TCP, + arg.iov[0].iov_len); + } +#endif + arg.csum = csum_tcpudp_nofold(skb->nh.iph->daddr, skb->nh.iph->saddr, /*XXX*/ sizeof(struct tcphdr), IPPROTO_TCP, 0); arg.csumoffset = offsetof(struct tcphdr, check) / 2; - ip_send_reply(tcp_socket->sk, skb, &arg, sizeof rth); + ip_send_reply(tcp_socket->sk, skb, &arg, arg.iov[0].iov_len); TCP_INC_STATS_BH(TCP_MIB_OUTSEGS); TCP_INC_STATS_BH(TCP_MIB_OUTRSTS); @@ -572,15 +611,24 @@ static void tcp_v4_send_reset(struct sk_buff *skb) outside socket context is ugly, certainly. What can I do? */ -static void tcp_v4_send_ack(struct sk_buff *skb, u32 seq, u32 ack, +static void tcp_v4_send_ack(struct tcp_timewait_sock *twsk, + struct sk_buff *skb, u32 seq, u32 ack, u32 win, u32 ts) { struct tcphdr *th = skb->h.th; struct { struct tcphdr th; - u32 tsopt[TCPOLEN_TSTAMP_ALIGNED >> 2]; + u32 opt[(TCPOLEN_TSTAMP_ALIGNED >> 2) +#ifdef CONFIG_TCP_MD5SIG + + (TCPOLEN_MD5SIG_ALIGNED >> 2) +#endif + ]; } rep; struct ip_reply_arg arg; +#ifdef CONFIG_TCP_MD5SIG + struct tcp_md5sig_key *key; + struct tcp_md5sig_key tw_key; +#endif memset(&rep.th, 0, sizeof(struct tcphdr)); memset(&arg, 0, sizeof arg); @@ -588,12 +636,12 @@ static void tcp_v4_send_ack(struct sk_buff *skb, u32 seq, u32 ack, arg.iov[0].iov_base = (unsigned char *)&rep; arg.iov[0].iov_len = sizeof(rep.th); if (ts) { - rep.tsopt[0] = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) | - (TCPOPT_TIMESTAMP << 8) | - TCPOLEN_TIMESTAMP); - rep.tsopt[1] = htonl(tcp_time_stamp); - rep.tsopt[2] = htonl(ts); - arg.iov[0].iov_len = sizeof(rep); + rep.opt[0] = htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16) | + (TCPOPT_TIMESTAMP << 8) | + TCPOLEN_TIMESTAMP); + rep.opt[1] = htonl(tcp_time_stamp); + rep.opt[2] = htonl(ts); + arg.iov[0].iov_len = TCPOLEN_TSTAMP_ALIGNED; } /* Swap the send and the receive. */ @@ -605,6 +653,44 @@ static void tcp_v4_send_ack(struct sk_buff *skb, u32 seq, u32 ack, rep.th.ack = 1; rep.th.window = htons(win); +#ifdef CONFIG_TCP_MD5SIG + /* + * The SKB holds an imcoming packet, but may not have a valid ->sk + * pointer. This is especially the case when we're dealing with a + * TIME_WAIT ack, because the sk structure is long gone, and only + * the tcp_timewait_sock remains. So the md5 key is stashed in that + * structure, and we use it in preference. I believe that (twsk || + * skb->sk) holds true, but we program defensively. + */ + if (!twsk && skb->sk) { + key = tcp_v4_md5_do_lookup(skb->sk, skb->nh.iph->daddr); + } else if (twsk && twsk->tw_md5_keylen) { + tw_key.key = twsk->tw_md5_key; + tw_key.keylen = twsk->tw_md5_keylen; + key = &tw_key; + } else { + key = NULL; + } + + if (key) { + int offset = (ts) ? 3 : 0; + + rep.opt[offset++] = htonl((TCPOPT_NOP << 24) | + (TCPOPT_NOP << 16) | + (TCPOPT_MD5SIG << 8) | + TCPOLEN_MD5SIG); + arg.iov[0].iov_len += TCPOLEN_MD5SIG_ALIGNED; + rep.th.doff = arg.iov[0].iov_len/4; + + tcp_v4_do_calc_md5_hash((__u8 *)&rep.opt[offset], + key, + skb->nh.iph->daddr, + skb->nh.iph->saddr, + &rep.th, IPPROTO_TCP, + arg.iov[0].iov_len); + } +#endif + arg.csum = csum_tcpudp_nofold(skb->nh.iph->daddr, skb->nh.iph->saddr, /*XXX*/ arg.iov[0].iov_len, IPPROTO_TCP, 0); @@ -618,9 +704,9 @@ static void tcp_v4_send_ack(struct sk_buff *skb, u32 seq, u32 ack, static void tcp_v4_timewait_ack(struct sock *sk, struct sk_buff *skb) { struct inet_timewait_sock *tw = inet_twsk(sk); - const struct tcp_timewait_sock *tcptw = tcp_twsk(sk); + struct tcp_timewait_sock *tcptw = tcp_twsk(sk); - tcp_v4_send_ack(skb, tcptw->tw_snd_nxt, tcptw->tw_rcv_nxt, + tcp_v4_send_ack(tcptw, skb, tcptw->tw_snd_nxt, tcptw->tw_rcv_nxt, tcptw->tw_rcv_wnd >> tw->tw_rcv_wscale, tcptw->tw_ts_recent); inet_twsk_put(tw); @@ -628,7 +714,8 @@ static void tcp_v4_timewait_ack(struct sock *sk, struct sk_buff *skb) static void tcp_v4_reqsk_send_ack(struct sk_buff *skb, struct request_sock *req) { - tcp_v4_send_ack(skb, tcp_rsk(req)->snt_isn + 1, tcp_rsk(req)->rcv_isn + 1, req->rcv_wnd, + tcp_v4_send_ack(NULL, skb, tcp_rsk(req)->snt_isn + 1, + tcp_rsk(req)->rcv_isn + 1, req->rcv_wnd, req->ts_recent); } @@ -714,6 +801,461 @@ static struct ip_options *tcp_v4_save_options(struct sock *sk, return dopt; } +#ifdef CONFIG_TCP_MD5SIG +/* + * RFC2385 MD5 checksumming requires a mapping of + * IP address->MD5 Key. + * We need to maintain these in the sk structure. + */ + +/* Find the Key structure for an address. */ +static struct tcp_md5sig_key *tcp_v4_md5_do_lookup(struct sock *sk, __be32 addr) +{ + struct tcp_sock *tp = tcp_sk(sk); + int i; + + if (!tp->md5sig_info || !tp->md5sig_info->entries4) + return NULL; + for (i = 0; i < tp->md5sig_info->entries4; i++) { + if (tp->md5sig_info->keys4[i].addr == addr) + return (struct tcp_md5sig_key *)&tp->md5sig_info->keys4[i]; + } + return NULL; +} + +struct tcp_md5sig_key *tcp_v4_md5_lookup(struct sock *sk, + struct sock *addr_sk) +{ + return tcp_v4_md5_do_lookup(sk, inet_sk(addr_sk)->daddr); +} + +EXPORT_SYMBOL(tcp_v4_md5_lookup); + +struct tcp_md5sig_key *tcp_v4_reqsk_md5_lookup(struct sock *sk, + struct request_sock *req) +{ + return tcp_v4_md5_do_lookup(sk, inet_rsk(req)->rmt_addr); +} + +/* This can be called on a newly created socket, from other files */ +int tcp_v4_md5_do_add(struct sock *sk, __be32 addr, + u8 *newkey, u8 newkeylen) +{ + /* Add Key to the list */ + struct tcp4_md5sig_key *key; + struct tcp_sock *tp = tcp_sk(sk); + struct tcp4_md5sig_key *keys; + + key = (struct tcp4_md5sig_key *) tcp_v4_md5_do_lookup(sk, addr); + if (key) { + /* Pre-existing entry - just update that one. */ + kfree (key->key); + key->key = newkey; + key->keylen = newkeylen; + } else { + if (!tp->md5sig_info) { + tp->md5sig_info = kzalloc(sizeof(*tp->md5sig_info), GFP_ATOMIC); + if (!tp->md5sig_info) { + kfree(newkey); + return -ENOMEM; + } + } + if (tcp_alloc_md5sig_pool() == NULL) { + kfree(newkey); + return -ENOMEM; + } + if (tp->md5sig_info->alloced4 == tp->md5sig_info->entries4) { + keys = kmalloc((sizeof(struct tcp4_md5sig_key) * + (tp->md5sig_info->entries4 + 1)), GFP_ATOMIC); + if (!keys) { + kfree(newkey); + tcp_free_md5sig_pool(); + return -ENOMEM; + } + + if (tp->md5sig_info->entries4) + memcpy(keys, tp->md5sig_info->keys4, + (sizeof (struct tcp4_md5sig_key) * + tp->md5sig_info->entries4)); + + /* Free old key list, and reference new one */ + if (tp->md5sig_info->keys4) + kfree(tp->md5sig_info->keys4); + tp->md5sig_info->keys4 = keys; + tp->md5sig_info->alloced4++; + } + tp->md5sig_info->entries4++; + tp->md5sig_info->keys4[tp->md5sig_info->entries4 - 1].addr = addr; + tp->md5sig_info->keys4[tp->md5sig_info->entries4 - 1].key = newkey; + tp->md5sig_info->keys4[tp->md5sig_info->entries4 - 1].keylen = newkeylen; + } + return 0; +} + +EXPORT_SYMBOL(tcp_v4_md5_do_add); + +static int tcp_v4_md5_add_func(struct sock *sk, struct sock *addr_sk, + u8 *newkey, u8 newkeylen) +{ + return tcp_v4_md5_do_add(sk, inet_sk(addr_sk)->daddr, + newkey, newkeylen); +} + +int tcp_v4_md5_do_del(struct sock *sk, __be32 addr) +{ + struct tcp_sock *tp = tcp_sk(sk); + int i; + + for (i = 0; i < tp->md5sig_info->entries4; i++) { + if (tp->md5sig_info->keys4[i].addr == addr) { + /* Free the key */ + kfree(tp->md5sig_info->keys4[i].key); + tp->md5sig_info->entries4--; + + if (tp->md5sig_info->entries4 == 0) { + kfree(tp->md5sig_info->keys4); + tp->md5sig_info->keys4 = NULL; + } else { + /* Need to do some manipulation */ + if (tp->md5sig_info->entries4 != i) + memcpy(&tp->md5sig_info->keys4[i], + &tp->md5sig_info->keys4[i+1], + (tp->md5sig_info->entries4 - i) + * sizeof (struct tcp4_md5sig_key)); + } + tcp_free_md5sig_pool(); + return 0; + } + } + return -ENOENT; +} + +EXPORT_SYMBOL(tcp_v4_md5_do_del); + +static void tcp_v4_clear_md5_list (struct sock *sk) +{ + struct tcp_sock *tp = tcp_sk(sk); + + /* Free each key, then the set of key keys, + * the crypto element, and then decrement our + * hold on the last resort crypto. + */ + if (tp->md5sig_info->entries4) { + int i; + for (i = 0; i < tp->md5sig_info->entries4; i++) + kfree(tp->md5sig_info->keys4[i].key); + tp->md5sig_info->entries4 = 0; + tcp_free_md5sig_pool(); + } + if (tp->md5sig_info->keys4) { + kfree(tp->md5sig_info->keys4); + tp->md5sig_info->keys4 = NULL; + tp->md5sig_info->alloced4 = 0; + } +} + +static int tcp_v4_parse_md5_keys (struct sock *sk, char __user *optval, + int optlen) +{ + struct tcp_md5sig cmd; + struct sockaddr_in *sin = (struct sockaddr_in *)&cmd.tcpm_addr; + u8 *newkey; + + if (optlen < sizeof(cmd)) + return -EINVAL; + + if (copy_from_user (&cmd, optval, sizeof(cmd))) + return -EFAULT; + + if (sin->sin_family != AF_INET) + return -EINVAL; + + if (!cmd.tcpm_key || !cmd.tcpm_keylen) { + if (!tcp_sk(sk)->md5sig_info) + return -ENOENT; + return tcp_v4_md5_do_del(sk, sin->sin_addr.s_addr); + } + + if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN) + return -EINVAL; + + if (!tcp_sk(sk)->md5s |