| /* |
| * MPTCP implementation - MPTCP-control |
| * |
| * Initial Design & Implementation: |
| * Sébastien Barré <sebastien.barre@uclouvain.be> |
| * |
| * Current Maintainer & Author: |
| * Christoph Paasch <christoph.paasch@uclouvain.be> |
| * |
| * Additional authors: |
| * Jaakko Korkeaniemi <jaakko.korkeaniemi@aalto.fi> |
| * Gregory Detal <gregory.detal@uclouvain.be> |
| * Fabien Duchêne <fabien.duchene@uclouvain.be> |
| * Andreas Seelinger <Andreas.Seelinger@rwth-aachen.de> |
| * Lavkesh Lahngir <lavkesh51@gmail.com> |
| * Andreas Ripke <ripke@neclab.eu> |
| * Vlad Dogaru <vlad.dogaru@intel.com> |
| * Octavian Purdila <octavian.purdila@intel.com> |
| * John Ronan <jronan@tssg.org> |
| * Catalin Nicutar <catalin.nicutar@gmail.com> |
| * Brandon Heller <brandonh@stanford.edu> |
| * |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version |
| * 2 of the License, or (at your option) any later version. |
| */ |
| |
| #include <net/inet_common.h> |
| #include <net/inet6_hashtables.h> |
| #include <net/ipv6.h> |
| #include <net/ip6_checksum.h> |
| #include <net/mptcp.h> |
| #include <net/mptcp_v4.h> |
| #if IS_ENABLED(CONFIG_IPV6) |
| #include <net/ip6_route.h> |
| #include <net/mptcp_v6.h> |
| #endif |
| #include <net/sock.h> |
| #include <net/tcp.h> |
| #include <net/tcp_states.h> |
| #include <net/transp_v6.h> |
| #include <net/xfrm.h> |
| |
| #include <linux/cryptohash.h> |
| #include <linux/kconfig.h> |
| #include <linux/module.h> |
| #include <linux/netpoll.h> |
| #include <linux/proc_fs.h> |
| #include <linux/list.h> |
| #include <linux/jhash.h> |
| #include <linux/tcp.h> |
| #include <linux/net.h> |
| #include <linux/in.h> |
| #include <linux/random.h> |
| #include <linux/inetdevice.h> |
| #include <linux/workqueue.h> |
| #include <linux/atomic.h> |
| #include <linux/sysctl.h> |
| |
| static struct kmem_cache *mptcp_sock_cache __read_mostly; |
| static struct kmem_cache *mptcp_cb_cache __read_mostly; |
| static struct kmem_cache *mptcp_tw_cache __read_mostly; |
| |
| int sysctl_mptcp_enabled __read_mostly; |
| int sysctl_mptcp_version __read_mostly = 0; |
| static int min_mptcp_version; |
| static int max_mptcp_version = 1; |
| int sysctl_mptcp_checksum __read_mostly; |
| int sysctl_mptcp_debug __read_mostly; |
| EXPORT_SYMBOL(sysctl_mptcp_debug); |
| int sysctl_mptcp_syn_retries __read_mostly = 3; |
| |
| bool mptcp_init_failed __read_mostly; |
| |
| struct static_key mptcp_static_key = STATIC_KEY_INIT_FALSE; |
| EXPORT_SYMBOL(mptcp_static_key); |
| |
| static int proc_mptcp_path_manager(struct ctl_table *ctl, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| char val[MPTCP_PM_NAME_MAX]; |
| struct ctl_table tbl = { |
| .data = val, |
| .maxlen = MPTCP_PM_NAME_MAX, |
| }; |
| int ret; |
| |
| mptcp_get_default_path_manager(val); |
| |
| ret = proc_dostring(&tbl, write, buffer, lenp, ppos); |
| if (write && ret == 0) |
| ret = mptcp_set_default_path_manager(val); |
| return ret; |
| } |
| |
| static int proc_mptcp_scheduler(struct ctl_table *ctl, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| char val[MPTCP_SCHED_NAME_MAX]; |
| struct ctl_table tbl = { |
| .data = val, |
| .maxlen = MPTCP_SCHED_NAME_MAX, |
| }; |
| int ret; |
| |
| mptcp_get_default_scheduler(val); |
| |
| ret = proc_dostring(&tbl, write, buffer, lenp, ppos); |
| if (write && ret == 0) |
| ret = mptcp_set_default_scheduler(val); |
| return ret; |
| } |
| |
| static struct ctl_table mptcp_table[] = { |
| { |
| .procname = "mptcp_enabled", |
| .data = &sysctl_mptcp_enabled, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = &proc_dointvec |
| }, |
| { |
| .procname = "mptcp_version", |
| .data = &sysctl_mptcp_version, |
| .mode = 0644, |
| .maxlen = sizeof(int), |
| .proc_handler = &proc_dointvec_minmax, |
| .extra1 = &min_mptcp_version, |
| .extra2 = &max_mptcp_version, |
| }, |
| { |
| .procname = "mptcp_checksum", |
| .data = &sysctl_mptcp_checksum, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = &proc_dointvec |
| }, |
| { |
| .procname = "mptcp_debug", |
| .data = &sysctl_mptcp_debug, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = &proc_dointvec |
| }, |
| { |
| .procname = "mptcp_syn_retries", |
| .data = &sysctl_mptcp_syn_retries, |
| .maxlen = sizeof(int), |
| .mode = 0644, |
| .proc_handler = &proc_dointvec |
| }, |
| { |
| .procname = "mptcp_path_manager", |
| .mode = 0644, |
| .maxlen = MPTCP_PM_NAME_MAX, |
| .proc_handler = proc_mptcp_path_manager, |
| }, |
| { |
| .procname = "mptcp_scheduler", |
| .mode = 0644, |
| .maxlen = MPTCP_SCHED_NAME_MAX, |
| .proc_handler = proc_mptcp_scheduler, |
| }, |
| { } |
| }; |
| |
| static inline u32 mptcp_hash_tk(u32 token) |
| { |
| return token % MPTCP_HASH_SIZE; |
| } |
| |
| struct hlist_nulls_head tk_hashtable[MPTCP_HASH_SIZE]; |
| EXPORT_SYMBOL(tk_hashtable); |
| |
| /* The following hash table is used to avoid collision of token */ |
| static struct hlist_nulls_head mptcp_reqsk_tk_htb[MPTCP_HASH_SIZE]; |
| |
| /* Lock, protecting the two hash-tables that hold the token. Namely, |
| * mptcp_reqsk_tk_htb and tk_hashtable |
| */ |
| static spinlock_t mptcp_tk_hashlock; |
| |
| static bool mptcp_reqsk_find_tk(const u32 token) |
| { |
| const u32 hash = mptcp_hash_tk(token); |
| const struct mptcp_request_sock *mtreqsk; |
| const struct hlist_nulls_node *node; |
| |
| begin: |
| hlist_nulls_for_each_entry_rcu(mtreqsk, node, |
| &mptcp_reqsk_tk_htb[hash], hash_entry) { |
| if (token == mtreqsk->mptcp_loc_token) |
| return true; |
| } |
| /* A request-socket is destroyed by RCU. So, it might have been recycled |
| * and put into another hash-table list. So, after the lookup we may |
| * end up in a different list. So, we may need to restart. |
| * |
| * See also the comment in __inet_lookup_established. |
| */ |
| if (get_nulls_value(node) != hash) |
| goto begin; |
| return false; |
| } |
| |
| static void mptcp_reqsk_insert_tk(struct request_sock *reqsk, const u32 token) |
| { |
| u32 hash = mptcp_hash_tk(token); |
| |
| hlist_nulls_add_head_rcu(&mptcp_rsk(reqsk)->hash_entry, |
| &mptcp_reqsk_tk_htb[hash]); |
| } |
| |
| static void mptcp_reqsk_remove_tk(const struct request_sock *reqsk) |
| { |
| rcu_read_lock(); |
| spin_lock(&mptcp_tk_hashlock); |
| hlist_nulls_del_init_rcu(&mptcp_rsk(reqsk)->hash_entry); |
| spin_unlock(&mptcp_tk_hashlock); |
| rcu_read_unlock(); |
| } |
| |
| void mptcp_reqsk_destructor(struct request_sock *req) |
| { |
| if (!mptcp_rsk(req)->is_sub) { |
| if (in_softirq()) { |
| mptcp_reqsk_remove_tk(req); |
| } else { |
| rcu_read_lock_bh(); |
| spin_lock(&mptcp_tk_hashlock); |
| hlist_nulls_del_init_rcu(&mptcp_rsk(req)->hash_entry); |
| spin_unlock(&mptcp_tk_hashlock); |
| rcu_read_unlock_bh(); |
| } |
| } |
| } |
| |
| static void __mptcp_hash_insert(struct tcp_sock *meta_tp, const u32 token) |
| { |
| u32 hash = mptcp_hash_tk(token); |
| hlist_nulls_add_head_rcu(&meta_tp->tk_table, &tk_hashtable[hash]); |
| meta_tp->inside_tk_table = 1; |
| } |
| |
| static bool mptcp_find_token(u32 token) |
| { |
| const u32 hash = mptcp_hash_tk(token); |
| const struct tcp_sock *meta_tp; |
| const struct hlist_nulls_node *node; |
| |
| begin: |
| hlist_nulls_for_each_entry_rcu(meta_tp, node, &tk_hashtable[hash], tk_table) { |
| if (token == meta_tp->mptcp_loc_token) |
| return true; |
| } |
| /* A TCP-socket is destroyed by RCU. So, it might have been recycled |
| * and put into another hash-table list. So, after the lookup we may |
| * end up in a different list. So, we may need to restart. |
| * |
| * See also the comment in __inet_lookup_established. |
| */ |
| if (get_nulls_value(node) != hash) |
| goto begin; |
| return false; |
| } |
| |
| static void mptcp_set_key_reqsk(struct request_sock *req, |
| const struct sk_buff *skb, |
| u32 seed) |
| { |
| const struct inet_request_sock *ireq = inet_rsk(req); |
| struct mptcp_request_sock *mtreq = mptcp_rsk(req); |
| |
| if (skb->protocol == htons(ETH_P_IP)) { |
| mtreq->mptcp_loc_key = mptcp_v4_get_key(ip_hdr(skb)->saddr, |
| ip_hdr(skb)->daddr, |
| htons(ireq->ir_num), |
| ireq->ir_rmt_port, |
| seed); |
| #if IS_ENABLED(CONFIG_IPV6) |
| } else { |
| mtreq->mptcp_loc_key = mptcp_v6_get_key(ipv6_hdr(skb)->saddr.s6_addr32, |
| ipv6_hdr(skb)->daddr.s6_addr32, |
| htons(ireq->ir_num), |
| ireq->ir_rmt_port, |
| seed); |
| #endif |
| } |
| |
| mptcp_key_sha1(mtreq->mptcp_loc_key, &mtreq->mptcp_loc_token, NULL); |
| } |
| |
| /* New MPTCP-connection request, prepare a new token for the meta-socket that |
| * will be created in mptcp_check_req_master(), and store the received token. |
| */ |
| static void mptcp_reqsk_new_mptcp(struct request_sock *req, |
| const struct sock *sk, |
| const struct mptcp_options_received *mopt, |
| const struct sk_buff *skb) |
| { |
| struct mptcp_request_sock *mtreq = mptcp_rsk(req); |
| const struct tcp_sock *tp = tcp_sk(sk); |
| |
| inet_rsk(req)->saw_mpc = 1; |
| /* MPTCP version agreement */ |
| if (mopt->mptcp_ver >= tp->mptcp_ver) |
| mtreq->mptcp_ver = tp->mptcp_ver; |
| else |
| mtreq->mptcp_ver = mopt->mptcp_ver; |
| |
| rcu_read_lock(); |
| spin_lock(&mptcp_tk_hashlock); |
| do { |
| mptcp_set_key_reqsk(req, skb, mptcp_seed++); |
| } while (mptcp_reqsk_find_tk(mtreq->mptcp_loc_token) || |
| mptcp_find_token(mtreq->mptcp_loc_token)); |
| mptcp_reqsk_insert_tk(req, mtreq->mptcp_loc_token); |
| spin_unlock(&mptcp_tk_hashlock); |
| rcu_read_unlock(); |
| mtreq->mptcp_rem_key = mopt->mptcp_sender_key; |
| } |
| |
| static int mptcp_reqsk_new_cookie(struct request_sock *req, |
| const struct mptcp_options_received *mopt, |
| const struct sk_buff *skb) |
| { |
| struct mptcp_request_sock *mtreq = mptcp_rsk(req); |
| |
| rcu_read_lock(); |
| spin_lock(&mptcp_tk_hashlock); |
| |
| mptcp_set_key_reqsk(req, skb, tcp_rsk(req)->snt_isn); |
| |
| if (mptcp_reqsk_find_tk(mtreq->mptcp_loc_token) || |
| mptcp_find_token(mtreq->mptcp_loc_token)) { |
| spin_unlock(&mptcp_tk_hashlock); |
| rcu_read_unlock(); |
| return false; |
| } |
| |
| inet_rsk(req)->saw_mpc = 1; |
| |
| spin_unlock(&mptcp_tk_hashlock); |
| rcu_read_unlock(); |
| |
| mtreq->mptcp_rem_key = mopt->mptcp_sender_key; |
| |
| return true; |
| } |
| |
| static void mptcp_set_key_sk(const struct sock *sk) |
| { |
| struct tcp_sock *tp = tcp_sk(sk); |
| const struct inet_sock *isk = inet_sk(sk); |
| |
| if (sk->sk_family == AF_INET) |
| tp->mptcp_loc_key = mptcp_v4_get_key(isk->inet_saddr, |
| isk->inet_daddr, |
| isk->inet_sport, |
| isk->inet_dport, |
| mptcp_seed++); |
| #if IS_ENABLED(CONFIG_IPV6) |
| else |
| tp->mptcp_loc_key = mptcp_v6_get_key(inet6_sk(sk)->saddr.s6_addr32, |
| sk->sk_v6_daddr.s6_addr32, |
| isk->inet_sport, |
| isk->inet_dport, |
| mptcp_seed++); |
| #endif |
| |
| mptcp_key_sha1(tp->mptcp_loc_key, |
| &tp->mptcp_loc_token, NULL); |
| } |
| |
| #ifdef HAVE_JUMP_LABEL |
| /* We are not allowed to call static_key_slow_dec() from irq context |
| * If mptcp_enable/disable_static_key() is called from irq context, |
| * defer the static_key_slow_dec() calls. |
| */ |
| static atomic_t mptcp_enable_deferred; |
| #endif |
| |
| void mptcp_enable_static_key(void) |
| { |
| #ifdef HAVE_JUMP_LABEL |
| int deferred; |
| |
| if (in_interrupt()) { |
| atomic_inc(&mptcp_enable_deferred); |
| return; |
| } |
| |
| deferred = atomic_xchg(&mptcp_enable_deferred, 0); |
| |
| if (deferred > 0) { |
| while (deferred--) |
| static_key_slow_inc(&mptcp_static_key); |
| } else if (deferred < 0) { |
| /* Do exactly one dec less than necessary */ |
| while (++deferred) |
| static_key_slow_dec(&mptcp_static_key); |
| return; |
| } |
| #endif |
| static_key_slow_inc(&mptcp_static_key); |
| WARN_ON(atomic_read(&mptcp_static_key.enabled) == 0); |
| } |
| |
| void mptcp_disable_static_key(void) |
| { |
| #ifdef HAVE_JUMP_LABEL |
| int deferred; |
| |
| if (in_interrupt()) { |
| atomic_dec(&mptcp_enable_deferred); |
| return; |
| } |
| |
| deferred = atomic_xchg(&mptcp_enable_deferred, 0); |
| |
| if (deferred > 0) { |
| /* Do exactly one inc less than necessary */ |
| while (--deferred) |
| static_key_slow_inc(&mptcp_static_key); |
| return; |
| } else if (deferred < 0) { |
| while (deferred++) |
| static_key_slow_dec(&mptcp_static_key); |
| } |
| #endif |
| static_key_slow_dec(&mptcp_static_key); |
| } |
| |
| void mptcp_enable_sock(struct sock *sk) |
| { |
| if (!sock_flag(sk, SOCK_MPTCP)) { |
| sock_set_flag(sk, SOCK_MPTCP); |
| tcp_sk(sk)->mptcp_ver = sysctl_mptcp_version; |
| |
| /* Necessary here, because MPTCP can be enabled/disabled through |
| * a setsockopt. |
| */ |
| if (sk->sk_family == AF_INET) |
| inet_csk(sk)->icsk_af_ops = &mptcp_v4_specific; |
| #if IS_ENABLED(CONFIG_IPV6) |
| else if (mptcp_v6_is_v4_mapped(sk)) |
| inet_csk(sk)->icsk_af_ops = &mptcp_v6_mapped; |
| else |
| inet_csk(sk)->icsk_af_ops = &mptcp_v6_specific; |
| #endif |
| |
| mptcp_enable_static_key(); |
| } |
| } |
| |
| void mptcp_disable_sock(struct sock *sk) |
| { |
| if (sock_flag(sk, SOCK_MPTCP)) { |
| sock_reset_flag(sk, SOCK_MPTCP); |
| |
| /* Necessary here, because MPTCP can be enabled/disabled through |
| * a setsockopt. |
| */ |
| if (sk->sk_family == AF_INET) |
| inet_csk(sk)->icsk_af_ops = &ipv4_specific; |
| #if IS_ENABLED(CONFIG_IPV6) |
| else if (mptcp_v6_is_v4_mapped(sk)) |
| inet_csk(sk)->icsk_af_ops = &ipv6_mapped; |
| else |
| inet_csk(sk)->icsk_af_ops = &ipv6_specific; |
| #endif |
| |
| mptcp_disable_static_key(); |
| } |
| } |
| |
| void mptcp_connect_init(struct sock *sk) |
| { |
| struct tcp_sock *tp = tcp_sk(sk); |
| |
| rcu_read_lock_bh(); |
| spin_lock(&mptcp_tk_hashlock); |
| do { |
| mptcp_set_key_sk(sk); |
| } while (mptcp_reqsk_find_tk(tp->mptcp_loc_token) || |
| mptcp_find_token(tp->mptcp_loc_token)); |
| |
| __mptcp_hash_insert(tp, tp->mptcp_loc_token); |
| spin_unlock(&mptcp_tk_hashlock); |
| rcu_read_unlock_bh(); |
| |
| MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_MPCAPABLEACTIVE); |
| } |
| |
| /** |
| * This function increments the refcount of the mpcb struct. |
| * It is the responsibility of the caller to decrement when releasing |
| * the structure. |
| */ |
| struct sock *mptcp_hash_find(const struct net *net, const u32 token) |
| { |
| const u32 hash = mptcp_hash_tk(token); |
| const struct tcp_sock *meta_tp; |
| struct sock *meta_sk = NULL; |
| const struct hlist_nulls_node *node; |
| |
| rcu_read_lock(); |
| begin: |
| hlist_nulls_for_each_entry_rcu(meta_tp, node, &tk_hashtable[hash], |
| tk_table) { |
| meta_sk = (struct sock *)meta_tp; |
| if (token == meta_tp->mptcp_loc_token && |
| net_eq(net, sock_net(meta_sk))) { |
| if (unlikely(!atomic_inc_not_zero(&meta_sk->sk_refcnt))) |
| goto out; |
| if (unlikely(token != meta_tp->mptcp_loc_token || |
| !net_eq(net, sock_net(meta_sk)))) { |
| sock_gen_put(meta_sk); |
| goto begin; |
| } |
| goto found; |
| } |
| } |
| /* A TCP-socket is destroyed by RCU. So, it might have been recycled |
| * and put into another hash-table list. So, after the lookup we may |
| * end up in a different list. So, we may need to restart. |
| * |
| * See also the comment in __inet_lookup_established. |
| */ |
| if (get_nulls_value(node) != hash) |
| goto begin; |
| out: |
| meta_sk = NULL; |
| found: |
| rcu_read_unlock(); |
| return meta_sk; |
| } |
| |
| void mptcp_hash_remove_bh(struct tcp_sock *meta_tp) |
| { |
| /* remove from the token hashtable */ |
| rcu_read_lock_bh(); |
| spin_lock(&mptcp_tk_hashlock); |
| hlist_nulls_del_init_rcu(&meta_tp->tk_table); |
| meta_tp->inside_tk_table = 0; |
| spin_unlock(&mptcp_tk_hashlock); |
| rcu_read_unlock_bh(); |
| } |
| |
| void mptcp_hash_remove(struct tcp_sock *meta_tp) |
| { |
| rcu_read_lock(); |
| spin_lock(&mptcp_tk_hashlock); |
| hlist_nulls_del_init_rcu(&meta_tp->tk_table); |
| meta_tp->inside_tk_table = 0; |
| spin_unlock(&mptcp_tk_hashlock); |
| rcu_read_unlock(); |
| } |
| |
| struct sock *mptcp_select_ack_sock(const struct sock *meta_sk) |
| { |
| const struct tcp_sock *meta_tp = tcp_sk(meta_sk); |
| struct sock *sk, *rttsk = NULL, *lastsk = NULL; |
| u32 min_time = 0, last_active = 0; |
| |
| mptcp_for_each_sk(meta_tp->mpcb, sk) { |
| struct tcp_sock *tp = tcp_sk(sk); |
| u32 elapsed; |
| |
| if (!mptcp_sk_can_send_ack(sk) || tp->pf) |
| continue; |
| |
| elapsed = keepalive_time_elapsed(tp); |
| |
| /* We take the one with the lowest RTT within a reasonable |
| * (meta-RTO)-timeframe |
| */ |
| if (elapsed < inet_csk(meta_sk)->icsk_rto) { |
| if (!min_time || tp->srtt_us < min_time) { |
| min_time = tp->srtt_us; |
| rttsk = sk; |
| } |
| continue; |
| } |
| |
| /* Otherwise, we just take the most recent active */ |
| if (!rttsk && (!last_active || elapsed < last_active)) { |
| last_active = elapsed; |
| lastsk = sk; |
| } |
| } |
| |
| if (rttsk) |
| return rttsk; |
| |
| return lastsk; |
| } |
| EXPORT_SYMBOL(mptcp_select_ack_sock); |
| |
| static void mptcp_sock_def_error_report(struct sock *sk) |
| { |
| const struct mptcp_cb *mpcb = tcp_sk(sk)->mpcb; |
| |
| if (!sock_flag(sk, SOCK_DEAD)) |
| mptcp_sub_close(sk, 0); |
| |
| if (mpcb->infinite_mapping_rcv || mpcb->infinite_mapping_snd || |
| mpcb->send_infinite_mapping) { |
| struct sock *meta_sk = mptcp_meta_sk(sk); |
| |
| meta_sk->sk_err = sk->sk_err; |
| meta_sk->sk_err_soft = sk->sk_err_soft; |
| |
| if (!sock_flag(meta_sk, SOCK_DEAD)) |
| meta_sk->sk_error_report(meta_sk); |
| |
| WARN(meta_sk->sk_state == TCP_CLOSE, |
| "Meta already closed i_rcv %u i_snd %u send_i %u flags %#lx\n", |
| mpcb->infinite_mapping_rcv, mpcb->infinite_mapping_snd, |
| mpcb->send_infinite_mapping, meta_sk->sk_flags); |
| |
| if (meta_sk->sk_state != TCP_CLOSE) |
| tcp_done(meta_sk); |
| } |
| |
| sk->sk_err = 0; |
| return; |
| } |
| |
| static void mptcp_mpcb_put(struct mptcp_cb *mpcb) |
| { |
| if (atomic_dec_and_test(&mpcb->mpcb_refcnt)) { |
| mptcp_cleanup_path_manager(mpcb); |
| mptcp_cleanup_scheduler(mpcb); |
| kmem_cache_free(mptcp_cb_cache, mpcb); |
| } |
| } |
| |
| void mptcp_sock_destruct(struct sock *sk) |
| { |
| struct tcp_sock *tp = tcp_sk(sk); |
| |
| if (!is_meta_sk(sk) && !tp->was_meta_sk) { |
| BUG_ON(!hlist_unhashed(&tp->mptcp->cb_list)); |
| |
| kmem_cache_free(mptcp_sock_cache, tp->mptcp); |
| tp->mptcp = NULL; |
| |
| /* Taken when mpcb pointer was set */ |
| sock_put(mptcp_meta_sk(sk)); |
| mptcp_mpcb_put(tp->mpcb); |
| } else { |
| struct mptcp_cb *mpcb = tp->mpcb; |
| struct mptcp_tw *mptw; |
| |
| /* The mpcb is disappearing - we can make the final |
| * update to the rcv_nxt of the time-wait-sock and remove |
| * its reference to the mpcb. |
| */ |
| spin_lock_bh(&mpcb->tw_lock); |
| list_for_each_entry_rcu(mptw, &mpcb->tw_list, list) { |
| list_del_rcu(&mptw->list); |
| mptw->in_list = 0; |
| mptcp_mpcb_put(mpcb); |
| rcu_assign_pointer(mptw->mpcb, NULL); |
| } |
| spin_unlock_bh(&mpcb->tw_lock); |
| |
| mptcp_mpcb_put(mpcb); |
| |
| mptcp_debug("%s destroying meta-sk\n", __func__); |
| } |
| |
| WARN_ON(!static_key_false(&mptcp_static_key)); |
| |
| /* Must be called here, because this will decrement the jump-label. */ |
| inet_sock_destruct(sk); |
| } |
| |
| void mptcp_destroy_sock(struct sock *sk) |
| { |
| if (is_meta_sk(sk)) { |
| struct sock *sk_it, *tmpsk; |
| |
| __skb_queue_purge(&tcp_sk(sk)->mpcb->reinject_queue); |
| |
| /* We have to close all remaining subflows. Normally, they |
| * should all be about to get closed. But, if the kernel is |
| * forcing a closure (e.g., tcp_write_err), the subflows might |
| * not have been closed properly (as we are waiting for the |
| * DATA_ACK of the DATA_FIN). |
| */ |
| mptcp_for_each_sk_safe(tcp_sk(sk)->mpcb, sk_it, tmpsk) { |
| /* Already did call tcp_close - waiting for graceful |
| * closure, or if we are retransmitting fast-close on |
| * the subflow. The reset (or timeout) will kill the |
| * subflow.. |
| */ |
| if (tcp_sk(sk_it)->closing || |
| tcp_sk(sk_it)->send_mp_fclose) |
| continue; |
| |
| /* Allow the delayed work first to prevent time-wait state */ |
| if (delayed_work_pending(&tcp_sk(sk_it)->mptcp->work)) |
| continue; |
| |
| mptcp_sub_close(sk_it, 0); |
| } |
| } else { |
| mptcp_del_sock(sk); |
| } |
| } |
| |
| static void mptcp_set_state(struct sock *sk) |
| { |
| struct sock *meta_sk = mptcp_meta_sk(sk); |
| |
| /* Meta is not yet established - wake up the application */ |
| if ((1 << meta_sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV) && |
| sk->sk_state == TCP_ESTABLISHED) { |
| tcp_set_state(meta_sk, TCP_ESTABLISHED); |
| |
| if (!sock_flag(meta_sk, SOCK_DEAD)) { |
| meta_sk->sk_state_change(meta_sk); |
| sk_wake_async(meta_sk, SOCK_WAKE_IO, POLL_OUT); |
| } |
| |
| tcp_sk(meta_sk)->lsndtime = tcp_time_stamp; |
| } |
| |
| if (sk->sk_state == TCP_ESTABLISHED) { |
| tcp_sk(sk)->mptcp->establish_increased = 1; |
| tcp_sk(sk)->mpcb->cnt_established++; |
| } |
| } |
| |
| static void mptcp_assign_congestion_control(struct sock *sk) |
| { |
| struct inet_connection_sock *icsk = inet_csk(sk); |
| struct inet_connection_sock *meta_icsk = inet_csk(mptcp_meta_sk(sk)); |
| const struct tcp_congestion_ops *ca = meta_icsk->icsk_ca_ops; |
| |
| /* Congestion control is the same as meta. Thus, it has been |
| * try_module_get'd by tcp_assign_congestion_control. |
| */ |
| if (icsk->icsk_ca_ops == ca) |
| return; |
| |
| /* Use the same congestion control as set on the meta-sk */ |
| if (!try_module_get(ca->owner)) { |
| /* This should never happen. The congestion control is linked |
| * to the meta-socket (through tcp_assign_congestion_control) |
| * who "holds" the refcnt on the module. |
| */ |
| WARN(1, "Could not get the congestion control!"); |
| return; |
| } |
| icsk->icsk_ca_ops = ca; |
| |
| /* Clear out private data before diag gets it and |
| * the ca has not been initialized. |
| */ |
| if (ca->get_info) |
| memset(icsk->icsk_ca_priv, 0, sizeof(icsk->icsk_ca_priv)); |
| |
| return; |
| } |
| |
| u32 mptcp_secret[MD5_MESSAGE_BYTES / 4] ____cacheline_aligned; |
| u32 mptcp_seed = 0; |
| |
| void mptcp_key_sha1(u64 key, u32 *token, u64 *idsn) |
| { |
| u32 workspace[SHA_WORKSPACE_WORDS]; |
| u32 mptcp_hashed_key[SHA_DIGEST_WORDS]; |
| u8 input[64]; |
| int i; |
| |
| memset(workspace, 0, sizeof(workspace)); |
| |
| /* Initialize input with appropriate padding */ |
| memset(&input[9], 0, sizeof(input) - 10); /* -10, because the last byte |
| * is explicitly set too |
| */ |
| memcpy(input, &key, sizeof(key)); /* Copy key to the msg beginning */ |
| input[8] = 0x80; /* Padding: First bit after message = 1 */ |
| input[63] = 0x40; /* Padding: Length of the message = 64 bits */ |
| |
| sha_init(mptcp_hashed_key); |
| sha_transform(mptcp_hashed_key, input, workspace); |
| |
| for (i = 0; i < 5; i++) |
| mptcp_hashed_key[i] = cpu_to_be32(mptcp_hashed_key[i]); |
| |
| if (token) |
| *token = mptcp_hashed_key[0]; |
| if (idsn) |
| *idsn = *((u64 *)&mptcp_hashed_key[3]); |
| } |
| |
| void mptcp_hmac_sha1(const u8 *key_1, const u8 *key_2, u32 *hash_out, |
| int arg_num, ...) |
| { |
| u32 workspace[SHA_WORKSPACE_WORDS]; |
| u8 input[128]; /* 2 512-bit blocks */ |
| int i; |
| int index; |
| int length; |
| u8 *msg; |
| va_list list; |
| |
| memset(workspace, 0, sizeof(workspace)); |
| |
| /* Generate key xored with ipad */ |
| memset(input, 0x36, 64); |
| for (i = 0; i < 8; i++) |
| input[i] ^= key_1[i]; |
| for (i = 0; i < 8; i++) |
| input[i + 8] ^= key_2[i]; |
| |
| va_start(list, arg_num); |
| index = 64; |
| for (i = 0; i < arg_num; i++) { |
| length = va_arg(list, int); |
| msg = va_arg(list, u8 *); |
| BUG_ON(index + length > 125); /* Message is too long */ |
| memcpy(&input[index], msg, length); |
| index += length; |
| } |
| va_end(list); |
| |
| input[index] = 0x80; /* Padding: First bit after message = 1 */ |
| memset(&input[index + 1], 0, (126 - index)); |
| |
| /* Padding: Length of the message = 512 + message length (bits) */ |
| input[126] = 0x02; |
| input[127] = ((index - 64) * 8); /* Message length (bits) */ |
| |
| sha_init(hash_out); |
| sha_transform(hash_out, input, workspace); |
| memset(workspace, 0, sizeof(workspace)); |
| |
| sha_transform(hash_out, &input[64], workspace); |
| memset(workspace, 0, sizeof(workspace)); |
| |
| for (i = 0; i < 5; i++) |
| hash_out[i] = cpu_to_be32(hash_out[i]); |
| |
| /* Prepare second part of hmac */ |
| memset(input, 0x5C, 64); |
| for (i = 0; i < 8; i++) |
| input[i] ^= key_1[i]; |
| for (i = 0; i < 8; i++) |
| input[i + 8] ^= key_2[i]; |
| |
| memcpy(&input[64], hash_out, 20); |
| input[84] = 0x80; |
| memset(&input[85], 0, 41); |
| |
| /* Padding: Length of the message = 512 + 160 bits */ |
| input[126] = 0x02; |
| input[127] = 0xA0; |
| |
| sha_init(hash_out); |
| sha_transform(hash_out, input, workspace); |
| memset(workspace, 0, sizeof(workspace)); |
| |
| sha_transform(hash_out, &input[64], workspace); |
| |
| for (i = 0; i < 5; i++) |
| hash_out[i] = cpu_to_be32(hash_out[i]); |
| } |
| EXPORT_SYMBOL(mptcp_hmac_sha1); |
| |
| static void mptcp_mpcb_inherit_sockopts(struct sock *meta_sk, struct sock *master_sk) |
| { |
| /* Socket-options handled by sk_clone_lock while creating the meta-sk. |
| * ====== |
| * SO_SNDBUF, SO_SNDBUFFORCE, SO_RCVBUF, SO_RCVBUFFORCE, SO_RCVLOWAT, |
| * SO_RCVTIMEO, SO_SNDTIMEO, SO_ATTACH_FILTER, SO_DETACH_FILTER, |
| * TCP_NODELAY, TCP_CORK |
| * |
| * Socket-options handled in this function here |
| * ====== |
| * TCP_DEFER_ACCEPT |
| * SO_KEEPALIVE |
| * |
| * Socket-options on the todo-list |
| * ====== |
| * SO_BINDTODEVICE - should probably prevent creation of new subsocks |
| * across other devices. - what about the api-draft? |
| * SO_DEBUG |
| * SO_REUSEADDR - probably we don't care about this |
| * SO_DONTROUTE, SO_BROADCAST |
| * SO_OOBINLINE |
| * SO_LINGER |
| * SO_TIMESTAMP* - I don't think this is of concern for a SOCK_STREAM |
| * SO_PASSSEC - I don't think this is of concern for a SOCK_STREAM |
| * SO_RXQ_OVFL |
| * TCP_COOKIE_TRANSACTIONS |
| * TCP_MAXSEG |
| * TCP_THIN_* - Handled by sk_clone_lock, but we need to support this |
| * in mptcp_meta_retransmit_timer. AND we need to check |
| * what is about the subsockets. |
| * TCP_LINGER2 |
| * TCP_WINDOW_CLAMP |
| * TCP_USER_TIMEOUT |
| * TCP_MD5SIG |
| * |
| * Socket-options of no concern for the meta-socket (but for the subsocket) |
| * ====== |
| * SO_PRIORITY |
| * SO_MARK |
| * TCP_CONGESTION |
| * TCP_SYNCNT |
| * TCP_QUICKACK |
| */ |
| |
| /* DEFER_ACCEPT should not be set on the meta, as we want to accept new subflows directly */ |
| inet_csk(meta_sk)->icsk_accept_queue.rskq_defer_accept = 0; |
| |
| /* Keepalives are handled entirely at the MPTCP-layer */ |
| if (sock_flag(meta_sk, SOCK_KEEPOPEN)) { |
| inet_csk_reset_keepalive_timer(meta_sk, |
| keepalive_time_when(tcp_sk(meta_sk))); |
| sock_reset_flag(master_sk, SOCK_KEEPOPEN); |
| inet_csk_delete_keepalive_timer(master_sk); |
| } |
| |
| /* Do not propagate subflow-errors up to the MPTCP-layer */ |
| inet_sk(master_sk)->recverr = 0; |
| } |
| |
| static void mptcp_sub_inherit_sockopts(const struct sock *meta_sk, struct sock *sub_sk) |
| { |
| /* IP_TOS also goes to the subflow. */ |
| if (inet_sk(sub_sk)->tos != inet_sk(meta_sk)->tos) { |
| inet_sk(sub_sk)->tos = inet_sk(meta_sk)->tos; |
| sub_sk->sk_priority = meta_sk->sk_priority; |
| sk_dst_reset(sub_sk); |
| } |
| |
| /* Inherit SO_REUSEADDR */ |
| sub_sk->sk_reuse = meta_sk->sk_reuse; |
| |
| /* Inherit SO_MARK: can be used for routing or filtering */ |
| sub_sk->sk_mark = meta_sk->sk_mark; |
| |
| /* Inherit snd/rcv-buffer locks */ |
| sub_sk->sk_userlocks = meta_sk->sk_userlocks & ~SOCK_BINDPORT_LOCK; |
| |
| /* Nagle/Cork is forced off on the subflows. It is handled at the meta-layer */ |
| tcp_sk(sub_sk)->nonagle = TCP_NAGLE_OFF|TCP_NAGLE_PUSH; |
| |
| /* Keepalives are handled entirely at the MPTCP-layer */ |
| if (sock_flag(sub_sk, SOCK_KEEPOPEN)) { |
| sock_reset_flag(sub_sk, SOCK_KEEPOPEN); |
| inet_csk_delete_keepalive_timer(sub_sk); |
| } |
| |
| /* Do not propagate subflow-errors up to the MPTCP-layer */ |
| inet_sk(sub_sk)->recverr = 0; |
| } |
| |
| int mptcp_backlog_rcv(struct sock *meta_sk, struct sk_buff *skb) |
| { |
| /* skb-sk may be NULL if we receive a packet immediatly after the |
| * SYN/ACK + MP_CAPABLE. |
| */ |
| struct sock *sk = skb->sk ? skb->sk : meta_sk; |
| int ret = 0; |
| |
| skb->sk = NULL; |
| |
| if (unlikely(!atomic_inc_not_zero(&sk->sk_refcnt))) { |
| kfree_skb(skb); |
| return 0; |
| } |
| |
| if (sk->sk_family == AF_INET) |
| ret = tcp_v4_do_rcv(sk, skb); |
| #if IS_ENABLED(CONFIG_IPV6) |
| else |
| ret = tcp_v6_do_rcv(sk, skb); |
| #endif |
| |
| sock_put(sk); |
| return ret; |
| } |
| |
| struct lock_class_key meta_key; |
| char *meta_key_name = "sk_lock-AF_INET-MPTCP"; |
| struct lock_class_key meta_slock_key; |
| char *meta_slock_key_name = "slock-AF_INET-MPTCP"; |
| |
| static const struct tcp_sock_ops mptcp_meta_specific = { |
| .__select_window = __mptcp_select_window, |
| .select_window = mptcp_select_window, |
| .select_initial_window = mptcp_select_initial_window, |
| .select_size = mptcp_select_size, |
| .init_buffer_space = mptcp_init_buffer_space, |
| .set_rto = mptcp_tcp_set_rto, |
| .should_expand_sndbuf = mptcp_should_expand_sndbuf, |
| .send_fin = mptcp_send_fin, |
| .write_xmit = mptcp_write_xmit, |
| .send_active_reset = mptcp_send_active_reset, |
| .write_wakeup = mptcp_write_wakeup, |
| .retransmit_timer = mptcp_meta_retransmit_timer, |
| .time_wait = mptcp_time_wait, |
| .cleanup_rbuf = mptcp_cleanup_rbuf, |
| }; |
| |
| static const struct tcp_sock_ops mptcp_sub_specific = { |
| .__select_window = __mptcp_select_window, |
| .select_window = mptcp_select_window, |
| .select_initial_window = mptcp_select_initial_window, |
| .select_size = mptcp_select_size, |
| .init_buffer_space = mptcp_init_buffer_space, |
| .set_rto = mptcp_tcp_set_rto, |
| .should_expand_sndbuf = mptcp_should_expand_sndbuf, |
| .send_fin = tcp_send_fin, |
| .write_xmit = tcp_write_xmit, |
| .send_active_reset = tcp_send_active_reset, |
| .write_wakeup = tcp_write_wakeup, |
| .retransmit_timer = mptcp_sub_retransmit_timer, |
| .time_wait = tcp_time_wait, |
| .cleanup_rbuf = tcp_cleanup_rbuf, |
| }; |
| |
| static int mptcp_alloc_mpcb(struct sock *meta_sk, __u64 remote_key, |
| __u8 mptcp_ver, u32 window) |
| { |
| struct mptcp_cb *mpcb; |
| struct sock *master_sk; |
| struct inet_connection_sock *meta_icsk = inet_csk(meta_sk); |
| struct tcp_sock *master_tp, *meta_tp = tcp_sk(meta_sk); |
| u64 idsn; |
| |
| dst_release(meta_sk->sk_rx_dst); |
| meta_sk->sk_rx_dst = NULL; |
| /* This flag is set to announce sock_lock_init to |
| * reclassify the lock-class of the master socket. |
| */ |
| meta_tp->is_master_sk = 1; |
| master_sk = sk_clone_lock(meta_sk, GFP_ATOMIC | __GFP_ZERO); |
| meta_tp->is_master_sk = 0; |
| if (!master_sk) |
| return -ENOBUFS; |
| |
| master_tp = tcp_sk(master_sk); |
| |
| mpcb = kmem_cache_zalloc(mptcp_cb_cache, GFP_ATOMIC); |
| if (!mpcb) { |
| /* sk_free (and __sk_free) requirese wmem_alloc to be 1. |
| * All the rest is set to 0 thanks to __GFP_ZERO above. |
| */ |
| atomic_set(&master_sk->sk_wmem_alloc, 1); |
| sk_free(master_sk); |
| return -ENOBUFS; |
| } |
| |
| #if IS_ENABLED(CONFIG_IPV6) |
| if (meta_icsk->icsk_af_ops == &mptcp_v6_mapped) { |
| struct ipv6_pinfo *newnp, *np = inet6_sk(meta_sk); |
| |
| inet_sk(master_sk)->pinet6 = &((struct tcp6_sock *)master_sk)->inet6; |
| |
| newnp = inet6_sk(master_sk); |
| memcpy(newnp, np, sizeof(struct ipv6_pinfo)); |
| |
| newnp->ipv6_mc_list = NULL; |
| newnp->ipv6_ac_list = NULL; |
| newnp->ipv6_fl_list = NULL; |
| newnp->opt = NULL; |
| newnp->pktoptions = NULL; |
| (void)xchg(&newnp->rxpmtu, NULL); |
| } else if (meta_sk->sk_family == AF_INET6) { |
| struct ipv6_pinfo *newnp, *np = inet6_sk(meta_sk); |
| |
| inet_sk(master_sk)->pinet6 = &((struct tcp6_sock *)master_sk)->inet6; |
| |
| newnp = inet6_sk(master_sk); |
| memcpy(newnp, np, sizeof(struct ipv6_pinfo)); |
| |
| newnp->hop_limit = -1; |
| newnp->mcast_hops = IPV6_DEFAULT_MCASTHOPS; |
| newnp->mc_loop = 1; |
| newnp->pmtudisc = IPV6_PMTUDISC_WANT; |
| master_sk->sk_ipv6only = sock_net(master_sk)->ipv6.sysctl.bindv6only; |
| } |
| #endif |
| |
| meta_tp->mptcp = NULL; |
| |
| /* Store the mptcp version agreed on initial handshake */ |
| mpcb->mptcp_ver = mptcp_ver; |
| |
| /* Store the keys and generate the peer's token */ |
| mpcb->mptcp_loc_key = meta_tp->mptcp_loc_key; |
| mpcb->mptcp_loc_token = meta_tp->mptcp_loc_token; |
| |
| /* Generate Initial data-sequence-numbers */ |
| mptcp_key_sha1(mpcb->mptcp_loc_key, NULL, &idsn); |
| idsn = ntohll(idsn) + 1; |
| mpcb->snd_high_order[0] = idsn >> 32; |
| mpcb->snd_high_order[1] = mpcb->snd_high_order[0] - 1; |
| |
| meta_tp->write_seq = (u32)idsn; |
| meta_tp->snd_sml = meta_tp->write_seq; |
| meta_tp->snd_una = meta_tp->write_seq; |
| meta_tp->snd_nxt = meta_tp->write_seq; |
| meta_tp->pushed_seq = meta_tp->write_seq; |
| meta_tp->snd_up = meta_tp->write_seq; |
| |
| mpcb->mptcp_rem_key = remote_key; |
| mptcp_key_sha1(mpcb->mptcp_rem_key, &mpcb->mptcp_rem_token, &idsn); |
| idsn = ntohll(idsn) + 1; |
| mpcb->rcv_high_order[0] = idsn >> 32; |
| mpcb->rcv_high_order[1] = mpcb->rcv_high_order[0] + 1; |
| meta_tp->copied_seq = (u32) idsn; |
| meta_tp->rcv_nxt = (u32) idsn; |
| meta_tp->rcv_wup = (u32) idsn; |
| |
| meta_tp->snd_wl1 = meta_tp->rcv_nxt - 1; |
| meta_tp->snd_wnd = window; |
| meta_tp->retrans_stamp = 0; /* Set in tcp_connect() */ |
| |
| meta_tp->packets_out = 0; |
| meta_icsk->icsk_probes_out = 0; |
| |
| /* Set mptcp-pointers */ |
| master_tp->mpcb = mpcb; |
| master_tp->meta_sk = meta_sk; |
| meta_tp->mpcb = mpcb; |
| meta_tp->meta_sk = meta_sk; |
| mpcb->meta_sk = meta_sk; |
| mpcb->master_sk = master_sk; |
| |
| meta_tp->was_meta_sk = 0; |
| |
| /* Initialize the queues */ |
| skb_queue_head_init(&mpcb->reinject_queue); |
| master_tp->out_of_order_queue = RB_ROOT; |
| tcp_prequeue_init(master_tp); |
| INIT_LIST_HEAD(&master_tp->tsq_node); |
| |
| master_tp->tsq_flags = 0; |
| |
| mutex_init(&mpcb->mpcb_mutex); |
| |
| /* Init the accept_queue structure, we support a queue of 32 pending |
| * connections, it does not need to be huge, since we only store here |
| * pending subflow creations. |
| */ |
| reqsk_queue_alloc(&meta_icsk->icsk_accept_queue); |
| meta_sk->sk_max_ack_backlog = 32; |
| meta_sk->sk_ack_backlog = 0; |
| |
| if (!sock_flag(meta_sk, SOCK_MPTCP)) { |
| mptcp_enable_static_key(); |
| sock_set_flag(meta_sk, SOCK_MPTCP); |
| } |
| |
| /* Redefine function-pointers as the meta-sk is now fully ready */ |
| meta_tp->mpc = 1; |
| meta_tp->ops = &mptcp_meta_specific; |
| |
| meta_sk->sk_backlog_rcv = mptcp_backlog_rcv; |
| meta_sk->sk_destruct = mptcp_sock_destruct; |
| |
| /* Meta-level retransmit timer */ |
| meta_icsk->icsk_rto *= 2; /* Double of initial - rto */ |
| |
| tcp_init_xmit_timers(master_sk); |
| /* Has been set for sending out the SYN */ |
| inet_csk_clear_xmit_timer(meta_sk, ICSK_TIME_RETRANS); |
| |
| if (!meta_tp->inside_tk_table) { |
| /* Adding the meta_tp in the token hashtable - coming from server-side */ |
| rcu_read_lock(); |
| spin_lock(&mptcp_tk_hashlock); |
| |
| /* With lockless listeners, we might process two ACKs at the |
| * same time. With TCP, inet_csk_complete_hashdance takes care |
| * of this. But, for MPTCP this would be too late if we add |
| * this MPTCP-socket in the token table (new subflows might |
| * come in and match on this socket here. |
| * So, we need to check if someone else already added the token |
| * and revert in that case. The other guy won the race... |
| */ |
| if (mptcp_find_token(mpcb->mptcp_loc_token)) { |
| spin_unlock(&mptcp_tk_hashlock); |
| rcu_read_unlock(); |
| |
| inet_put_port(master_sk); |
| kmem_cache_free(mptcp_cb_cache, mpcb); |
| sk_free(master_sk); |
| |
| return -ENOBUFS; |
| } |
| __mptcp_hash_insert(meta_tp, mpcb->mptcp_loc_token); |
| |
| spin_unlock(&mptcp_tk_hashlock); |
| rcu_read_unlock(); |
| } |
| master_tp->inside_tk_table = 0; |
| |
| /* Init time-wait stuff */ |
| INIT_LIST_HEAD(&mpcb->tw_list); |
| spin_lock_init(&mpcb->tw_lock); |
| |
| INIT_HLIST_HEAD(&mpcb->callback_list); |
| |
| mptcp_mpcb_inherit_sockopts(meta_sk, master_sk); |
| |
| mpcb->orig_sk_rcvbuf = meta_sk->sk_rcvbuf; |
| mpcb->orig_sk_sndbuf = meta_sk->sk_sndbuf; |
| mpcb->orig_window_clamp = meta_tp->window_clamp; |
| |
| /* The meta is directly linked - set refcnt to 1 */ |
| atomic_set(&mpcb->mpcb_refcnt, 1); |
| |
| mptcp_init_path_manager(mpcb); |
| mptcp_init_scheduler(mpcb); |
| |
| if (!try_module_get(inet_csk(master_sk)->icsk_ca_ops->owner)) |
| tcp_assign_congestion_control(master_sk); |
| |
| |
| mptcp_debug("%s: created mpcb with token %#x\n", |
| __func__, mpcb->mptcp_loc_token); |
| |
| return 0; |
| } |
| |
| void mptcp_fallback_meta_sk(struct sock *meta_sk) |
| { |
| kmem_cache_free(mptcp_cb_cache, tcp_sk(meta_sk)->mpcb); |
| } |
| |
| int mptcp_add_sock(struct sock *meta_sk, struct sock *sk, u8 loc_id, u8 rem_id, |
| gfp_t flags) |
| { |
| struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; |
| struct tcp_sock *tp = tcp_sk(sk); |
| |
| tp->mptcp = kmem_cache_zalloc(mptcp_sock_cache, flags); |
| if (!tp->mptcp) |
| return -ENOMEM; |
| |
| tp->mptcp->path_index = mptcp_set_new_pathindex(mpcb); |
| /* No more space for more subflows? */ |
| if (!tp->mptcp->path_index) { |
| kmem_cache_free(mptcp_sock_cache, tp->mptcp); |
| return -EPERM; |
| } |
| |
| INIT_HLIST_NODE(&tp->mptcp->cb_list); |
| |
| tp->mptcp->tp = tp; |
| tp->mpcb = mpcb; |
| tp->meta_sk = meta_sk; |
| |
| if (!sock_flag(sk, SOCK_MPTCP)) { |
| mptcp_enable_static_key(); |
| sock_set_flag(sk, SOCK_MPTCP); |
| } |
| |
| tp->mpc = 1; |
| tp->ops = &mptcp_sub_specific; |
| |
| tp->mptcp->loc_id = loc_id; |
| tp->mptcp->rem_id = rem_id; |
| if (mpcb->sched_ops->init) |
| mpcb->sched_ops->init(sk); |
| |
| /* The corresponding sock_put is in mptcp_sock_destruct(). It cannot be |
| * included in mptcp_del_sock(), because the mpcb must remain alive |
| * until the last subsocket is completely destroyed. |
| */ |
| sock_hold(meta_sk); |
| atomic_inc(&mpcb->mpcb_refcnt); |
| |
| tp->mptcp->next = mpcb->connection_list; |
| mpcb->connection_list = tp; |
| tp->mptcp->attached = 1; |
| |
| mpcb->cnt_subflows++; |
| atomic_add(atomic_read(&((struct sock *)tp)->sk_rmem_alloc), |
| &meta_sk->sk_rmem_alloc); |
| |
| mptcp_sub_inherit_sockopts(meta_sk, sk); |
| INIT_DELAYED_WORK(&tp->mptcp->work, mptcp_sub_close_wq); |
| |
| /* Properly inherit CC from the meta-socket */ |
| mptcp_assign_congestion_control(sk); |
| |
| /* As we successfully allocated the mptcp_tcp_sock, we have to |
| * change the function-pointers here (for sk_destruct to work correctly) |
| */ |
| sk->sk_error_report = mptcp_sock_def_error_report; |
| sk->sk_data_ready = mptcp_data_ready; |
| sk->sk_write_space = mptcp_write_space; |
| sk->sk_state_change = mptcp_set_state; |
| sk->sk_destruct = mptcp_sock_destruct; |
| |
| if (sk->sk_family == AF_INET) |
| mptcp_debug("%s: token %#x pi %d, src_addr:%pI4:%d dst_addr:%pI4:%d, cnt_subflows now %d\n", |
| __func__ , mpcb->mptcp_loc_token, |
| tp->mptcp->path_index, |
| &((struct inet_sock *)tp)->inet_saddr, |
| ntohs(((struct inet_sock *)tp)->inet_sport), |
| &((struct inet_sock *)tp)->inet_daddr, |
| ntohs(((struct inet_sock *)tp)->inet_dport), |
| mpcb->cnt_subflows); |
| #if IS_ENABLED(CONFIG_IPV6) |
| else |
| mptcp_debug("%s: token %#x pi %d, src_addr:%pI6:%d dst_addr:%pI6:%d, cnt_subflows now %d\n", |
| __func__ , mpcb->mptcp_loc_token, |
| tp->mptcp->path_index, &inet6_sk(sk)->saddr, |
| ntohs(((struct inet_sock *)tp)->inet_sport), |
| &sk->sk_v6_daddr, |
| ntohs(((struct inet_sock *)tp)->inet_dport), |
| mpcb->cnt_subflows); |
| #endif |
| |
| return 0; |
| } |
| |
| void mptcp_del_sock(struct sock *sk) |
| { |
| struct tcp_sock *tp = tcp_sk(sk), *tp_prev; |
| struct mptcp_cb *mpcb; |
| |
| if (!tp->mptcp || !tp->mptcp->attached) |
| return; |
| |
| mpcb = tp->mpcb; |
| tp_prev = mpcb->connection_list; |
| |
| if (mpcb->sched_ops->release) |
| mpcb->sched_ops->release(sk); |
| |
| mptcp_debug("%s: Removing subsock tok %#x pi:%d state %d is_meta? %d\n", |
| __func__, mpcb->mptcp_loc_token, tp->mptcp->path_index, |
| sk->sk_state, is_meta_sk(sk)); |
| |
| if (tp_prev == tp) { |
| mpcb->connection_list = tp->mptcp->next; |
| } else { |
| for (; tp_prev && tp_prev->mptcp->next; tp_prev = tp_prev->mptcp->next) { |
| if (tp_prev->mptcp->next == tp) { |
| tp_prev->mptcp->next = tp->mptcp->next; |
| break; |
| } |
| } |
| } |
| mpcb->cnt_subflows--; |
| if (tp->mptcp->establish_increased) |
| mpcb->cnt_established--; |
| |
| tp->mptcp->next = NULL; |
| tp->mptcp->attached = 0; |
| mpcb->path_index_bits &= ~(1 << tp->mptcp->path_index); |
| |
| if (!skb_queue_empty(&sk->sk_write_queue)) |
| mptcp_reinject_data(sk, 0); |
| |
| if (is_master_tp(tp)) |
| mpcb->master_sk = NULL; |
| else if (tp->mptcp->pre_established) |
| sk_stop_timer(sk, &tp->mptcp->mptcp_ack_timer); |
| |
| rcu_assign_pointer(inet_sk(sk)->inet_opt, NULL); |
| } |
| |
| /* Updates the MPTCP-session based on path-manager information (e.g., addresses, |
| * low-prio flows,...). |
| */ |
| void mptcp_update_metasocket(struct sock *sk, const struct sock *meta_sk) |
| { |
| if (tcp_sk(sk)->mpcb->pm_ops->new_session) |
| tcp_sk(sk)->mpcb->pm_ops->new_session(meta_sk); |
| } |
| |
| /* Clean up the receive buffer for full frames taken by the user, |
| * then send an ACK if necessary. COPIED is the number of bytes |
| * tcp_recvmsg has given to the user so far, it speeds up the |
| * calculation of whether or not we must ACK for the sake of |
| * a window update. |
| */ |
| void mptcp_cleanup_rbuf(struct sock *meta_sk, int copied) |
| { |
| struct tcp_sock *meta_tp = tcp_sk(meta_sk); |
| struct sock *sk; |
| __u32 rcv_window_now = 0; |
| |
| if (copied > 0 && !(meta_sk->sk_shutdown & RCV_SHUTDOWN)) { |
| rcv_window_now = tcp_receive_window(meta_tp); |
| |
| if (2 * rcv_window_now > meta_tp->window_clamp) |
| rcv_window_now = 0; |
| } |
| |
| mptcp_for_each_sk(meta_tp->mpcb, sk) { |
| struct tcp_sock *tp = tcp_sk(sk); |
| const struct inet_connection_sock *icsk = inet_csk(sk); |
| |
| if (!mptcp_sk_can_send_ack(sk)) |
| continue; |
| |
| if (!inet_csk_ack_scheduled(sk)) |
| goto second_part; |
| /* Delayed ACKs frequently hit locked sockets during bulk |
| * receive. |
| */ |
| if (icsk->icsk_ack.blocked || |
| /* Once-per-two-segments ACK was not sent by tcp_input.c */ |
| tp->rcv_nxt - tp->rcv_wup > icsk->icsk_ack.rcv_mss || |
| /* If this read emptied read buffer, we send ACK, if |
| * connection is not bidirectional, user drained |
| * receive buffer and there was a small segment |
| * in queue. |
| */ |
| (copied > 0 && |
| ((icsk->icsk_ack.pending & ICSK_ACK_PUSHED2) || |
| ((icsk->icsk_ack.pending & ICSK_ACK_PUSHED) && |
| !icsk->icsk_ack.pingpong)) && |
| !atomic_read(&meta_sk->sk_rmem_alloc))) { |
| tcp_send_ack(sk); |
| continue; |
| } |
| |
| second_part: |
| /* This here is the second part of tcp_cleanup_rbuf */ |
| if (rcv_window_now) { |
| __u32 new_window = tp->ops->__select_window(sk); |
| |
| /* Send ACK now, if this read freed lots of space |
| * in our buffer. Certainly, new_window is new window. |
| * We can advertise it now, if it is not less than |
| * current one. |
| * "Lots" means "at least twice" here. |
| */ |
| if (new_window && new_window >= 2 * rcv_window_now) |
| tcp_send_ack(sk); |
| } |
| } |
| } |
| |
| static int mptcp_sub_send_fin(struct sock *sk) |
| { |
| struct tcp_sock *tp = tcp_sk(sk); |
| struct sk_buff *skb = tcp_write_queue_tail(sk); |
| int mss_now; |
| |
| /* Optimization, tack on the FIN if we have a queue of |
| * unsent frames. But be careful about outgoing SACKS |
| * and IP options. |
| */ |
| mss_now = tcp_current_mss(sk); |
| |
| if (tcp_send_head(sk) != NULL) { |
| TCP_SKB_CB(skb)->tcp_flags |= TCPHDR_FIN; |
| TCP_SKB_CB(skb)->end_seq++; |
| tp->write_seq++; |
| } else { |
| skb = alloc_skb_fclone(MAX_TCP_HEADER, GFP_ATOMIC); |
| if (!skb) |
| return 1; |
| |
| /* Reserve space for headers and prepare control bits. */ |
| skb_reserve(skb, MAX_TCP_HEADER); |
| /* FIN eats a sequence byte, write_seq advanced by tcp_queue_skb(). */ |
| tcp_init_nondata_skb(skb, tp->write_seq, |
| TCPHDR_ACK | TCPHDR_FIN); |
| tcp_queue_skb(sk, skb); |
| } |
| __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_OFF); |
| |
| return 0; |
| } |
| |
| /* Protect bind_hash on mptcp */ |
| static bool mptcp_tcp_close_state(struct sock *sk) |
| { |
| bool state; |
| lock_sock(sk); |
| state = tcp_close_state(sk); |
| release_sock(sk); |
| return state; |
| } |
| |
| void mptcp_sub_close_wq(struct work_struct *work) |
| { |
| struct tcp_sock *tp = container_of(work, struct mptcp_tcp_sock, work.work)->tp; |
| struct sock *sk = (struct sock *)tp; |
| struct sock *meta_sk = mptcp_meta_sk(sk); |
| |
| mutex_lock(&tp->mpcb->mpcb_mutex); |
| lock_sock_nested(meta_sk, SINGLE_DEPTH_NESTING); |
| |
| if (sock_flag(sk, SOCK_DEAD)) |
| goto exit; |
| |
| /* We come from tcp_disconnect. We are sure that meta_sk is set */ |
| if (!mptcp(tp)) { |
| tp->closing = 1; |
| tcp_close(sk, 0); |
| goto exit; |
| } |
| |
| if (meta_sk->sk_shutdown == SHUTDOWN_MASK || sk->sk_state == TCP_CLOSE) { |
| tp->closing = 1; |
| tcp_close(sk, 0); |
| } else if (mptcp_tcp_close_state(sk)) { |
| sk->sk_shutdown |= SEND_SHUTDOWN; |
| tcp_send_fin(sk); |
| } |
| |
| exit: |
| release_sock(meta_sk); |
| mutex_unlock(&tp->mpcb->mpcb_mutex); |
| sock_put(sk); |
| } |
| |
| void mptcp_sub_close(struct sock *sk, unsigned long delay) |
| { |
| struct tcp_sock *tp = tcp_sk(sk); |
| struct delayed_work *work = &tcp_sk(sk)->mptcp->work; |
| |
| /* We are already closing - e.g., call from sock_def_error_report upon |
| * tcp_disconnect in tcp_close. |
| */ |
| if (tp->closing) |
| return; |
| |
| /* Work already scheduled ? */ |
| if (work_pending(&work->work)) { |
| /* Work present - who will be first ? */ |
| if (jiffies + delay > work->timer.expires) |
| return; |
| |
| /* Try canceling - if it fails, work will be executed soon */ |
| if (!cancel_delayed_work(work)) |
| return; |
| sock_put(sk); |
| } |
| |
| if (!delay) { |
| unsigned char old_state = sk->sk_state; |
| |
| /* If we are in user-context we can directly do the closing |
| * procedure. No need to schedule a work-queue. |
| */ |
| if (!in_softirq()) { |
| if (sock_flag(sk, SOCK_DEAD)) |
| return; |
| |
| if (!mptcp(tp)) { |
| tp->closing = 1; |
| tcp_close(sk, 0); |
| return; |
| } |
| |
| if (mptcp_meta_sk(sk)->sk_shutdown == SHUTDOWN_MASK || |
| sk->sk_state == TCP_CLOSE) { |
| tp->closing = 1; |
| tcp_close(sk, 0); |
| } else if (tcp_close_state(sk)) { |
| sk->sk_shutdown |= SEND_SHUTDOWN; |
| tcp_send_fin(sk); |
| } |
| |
| return; |
| } |
| |
| /* We directly send the FIN. Because it may take so a long time, |
| * untile the work-queue will get scheduled... |
| * |
| * If mptcp_sub_send_fin returns 1, it failed and thus we reset |
| * the old state so that tcp_close will finally send the fin |
| * in user-context. |
| */ |
| if (!sk->sk_err && old_state != TCP_CLOSE && |
| tcp_close_state(sk) && mptcp_sub_send_fin(sk)) { |
| if (old_state == TCP_ESTABLISHED) |
| TCP_INC_STATS(sock_net(sk), TCP_MIB_CURRESTAB); |
| sk->sk_state = old_state; |
| } |
| } |
| |
| sock_hold(sk); |
| queue_delayed_work(mptcp_wq, work, delay); |
| } |
| |
| void mptcp_sub_force_close(struct sock *sk) |
| { |
| /* The below tcp_done may have freed the socket, if he is already dead. |
| * Thus, we are not allowed to access it afterwards. That's why |
| * we have to store the dead-state in this local variable. |
| */ |
| int sock_is_dead = sock_flag(sk, SOCK_DEAD); |
| |
| tcp_sk(sk)->mp_killed = 1; |
| |
| if (sk->sk_state != TCP_CLOSE) |
| tcp_done(sk); |
| |
| if (!sock_is_dead) |
| mptcp_sub_close(sk, 0); |
| } |
| EXPORT_SYMBOL(mptcp_sub_force_close); |
| |
| /* Update the mpcb send window, based on the contributions |
| * of each subflow |
| */ |
| void mptcp_update_sndbuf(const struct tcp_sock *tp) |
| { |
| struct sock *meta_sk = tp->meta_sk, *sk; |
| int new_sndbuf = 0, old_sndbuf = meta_sk->sk_sndbuf; |
| |
| mptcp_for_each_sk(tp->mpcb, sk) { |
| if (!mptcp_sk_can_send(sk)) |
| continue; |
| |
| new_sndbuf += sk->sk_sndbuf; |
| |
| if (new_sndbuf > sysctl_tcp_wmem[2] || new_sndbuf < 0) { |
| new_sndbuf = sysctl_tcp_wmem[2]; |
| break; |
| } |
| } |
| meta_sk->sk_sndbuf = max(min(new_sndbuf, sysctl_tcp_wmem[2]), meta_sk->sk_sndbuf); |
| |
| /* The subflow's call to sk_write_space in tcp_new_space ends up in |
| * mptcp_write_space. |
| * It has nothing to do with waking up the application. |
| * So, we do it here. |
| */ |
| if (old_sndbuf != meta_sk->sk_sndbuf) |
| meta_sk->sk_write_space(meta_sk); |
| } |
| |
| /* Similar to: tcp_close */ |
| void mptcp_close(struct sock *meta_sk, long timeout) |
| { |
| struct tcp_sock *meta_tp = tcp_sk(meta_sk); |
| struct sock *sk_it, *tmpsk; |
| struct mptcp_cb *mpcb = meta_tp->mpcb; |
| struct sk_buff *skb; |
| int data_was_unread = 0; |
| int state; |
| |
| mptcp_debug("%s: Close of meta_sk with tok %#x\n", |
| __func__, mpcb->mptcp_loc_token); |
| |
| mutex_lock(&mpcb->mpcb_mutex); |
| lock_sock(meta_sk); |
| |
| if (meta_tp->inside_tk_table) |
| /* Detach the mpcb from the token hashtable */ |
| mptcp_hash_remove_bh(meta_tp); |
| |
| meta_sk->sk_shutdown = SHUTDOWN_MASK; |
| /* We need to flush the recv. buffs. We do this only on the |
| * descriptor close, not protocol-sourced closes, because the |
| * reader process may not have drained the data yet! |
| */ |
| while ((skb = __skb_dequeue(&meta_sk->sk_receive_queue)) != NULL) { |
| u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq; |
| |
| if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN) |
| len--; |
| data_was_unread += len; |
| __kfree_skb(skb); |
| } |
| |
| sk_mem_reclaim(meta_sk); |
| |
| /* If socket has been already reset (e.g. in tcp_reset()) - kill it. */ |
| if (meta_sk->sk_state == TCP_CLOSE) { |
| mptcp_for_each_sk_safe(mpcb, sk_it, tmpsk) { |
| if (tcp_sk(sk_it)->send_mp_fclose) |
| continue; |
| mptcp_sub_close(sk_it, 0); |
| } |
| goto adjudge_to_death; |
| } |
| |
| if (data_was_unread) { |
| /* Unread data was tossed, zap the connection. */ |
| NET_INC_STATS_USER(sock_net(meta_sk), LINUX_MIB_TCPABORTONCLOSE); |
| tcp_set_state(meta_sk, TCP_CLOSE); |
| tcp_sk(meta_sk)->ops->send_active_reset(meta_sk, |
| meta_sk->sk_allocation); |
| } else if (sock_flag(meta_sk, SOCK_LINGER) && !meta_sk->sk_lingertime) { |
| /* Check zero linger _after_ checking for unread data. */ |
| meta_sk->sk_prot->disconnect(meta_sk, 0); |
| NET_INC_STATS_USER(sock_net(meta_sk), LINUX_MIB_TCPABORTONDATA); |
| } else if (tcp_close_state(meta_sk)) { |
| mptcp_send_fin(meta_sk); |
| } else if (meta_tp->snd_una == meta_tp->write_seq) { |
| /* The DATA_FIN has been sent and acknowledged |
| * (e.g., by sk_shutdown). Close all the other subflows |
| */ |
| mptcp_for_each_sk_safe(mpcb, sk_it, tmpsk) { |
| unsigned long delay = 0; |
| /* If we are the passive closer, don't trigger |
| * subflow-fin until the subflow has been finned |
| * by the peer. - thus we add a delay |
| */ |
| if (mpcb->passive_close && |
| sk_it->sk_state == TCP_ESTABLISHED) |
| delay = inet_csk(sk_it)->icsk_rto << 3; |
| |
| mptcp_sub_close(sk_it, delay); |
| } |
| } |
| |
| sk_stream_wait_close(meta_sk, timeout); |
| |
| adjudge_to_death: |
| state = meta_sk->sk_state; |
| sock_hold(meta_sk); |
| sock_orphan(meta_sk); |
| |
| /* socket will be freed after mptcp_close - we have to prevent |
| * access from the subflows. |
| */ |
| mptcp_for_each_sk(mpcb, sk_it) { |
| /* Similar to sock_orphan, but we don't set it DEAD, because |
| * the callbacks are still set and must be called. |
| */ |
| write_lock_bh(&sk_it->sk_callback_lock); |
| sk_set_socket(sk_it, NULL); |
| sk_it->sk_wq = NULL; |
| write_unlock_bh(&sk_it->sk_callback_lock); |
| } |
| |
| /* It is the last release_sock in its life. It will remove backlog. */ |
| release_sock(meta_sk); |
| |
| /* Now socket is owned by kernel and we acquire BH lock |
| * to finish close. No need to check for user refs. |
| */ |
| local_bh_disable(); |
| bh_lock_sock(meta_sk); |
| WARN_ON(sock_owned_by_user(meta_sk)); |
| |
| percpu_counter_inc(meta_sk->sk_prot->orphan_count); |
| |
| /* Have we already been destroyed by a softirq or backlog? */ |
| if (state != TCP_CLOSE && meta_sk->sk_state == TCP_CLOSE) |
| goto out; |
| |
| /* This is a (useful) BSD violating of the RFC. There is a |
| * problem with TCP as specified in that the other end could |
| * keep a socket open forever with no application left this end. |
| * We use a 3 minute timeout (about the same as BSD) then kill |
| * our end. If they send after that then tough - BUT: long enough |
| * that we won't make the old 4*rto = almost no time - whoops |
| * reset mistake. |
| * |
| * Nope, it was not mistake. It is really desired behaviour |
| * f.e. on http servers, when such sockets are useless, but |
| * consume significant resources. Let's do it with special |
| * linger2 option. --ANK |
| */ |
| |
| if (meta_sk->sk_state == TCP_FIN_WAIT2) { |
| if (meta_tp->linger2 < 0) { |
| tcp_set_state(meta_sk, TCP_CLOSE); |
| meta_tp->ops->send_active_reset(meta_sk, GFP_ATOMIC); |
| NET_INC_STATS_BH(sock_net(meta_sk), |
| LINUX_MIB_TCPABORTONLINGER); |
| } else { |
| const int tmo = tcp_fin_time(meta_sk); |
| |
| if (tmo > TCP_TIMEWAIT_LEN) { |
| inet_csk_reset_keepalive_timer(meta_sk, |
| tmo - TCP_TIMEWAIT_LEN); |
| } else { |
| meta_tp->ops->time_wait(meta_sk, TCP_FIN_WAIT2, |
| tmo); |
| goto out; |
| } |
| } |
| } |
| if (meta_sk->sk_state != TCP_CLOSE) { |
| sk_mem_reclaim(meta_sk); |
| if (tcp_check_oom(meta_sk, 0)) { |
| if (net_ratelimit()) |
| pr_info("MPTCP: out of memory: force closing socket\n"); |
| tcp_set_state(meta_sk, TCP_CLOSE); |
| meta_tp->ops->send_active_reset(meta_sk, GFP_ATOMIC); |
| NET_INC_STATS_BH(sock_net(meta_sk), |
| LINUX_MIB_TCPABORTONMEMORY); |
| } |
| } |
| |
| |
| if (meta_sk->sk_state == TCP_CLOSE) |
| inet_csk_destroy_sock(meta_sk); |
| /* Otherwise, socket is reprieved until protocol close. */ |
| |
| out: |
| bh_unlock_sock(meta_sk); |
| local_bh_enable(); |
| mutex_unlock(&mpcb->mpcb_mutex); |
| sock_put(meta_sk); /* Taken by sock_hold */ |
| } |
| |
| void mptcp_disconnect(struct sock *sk) |
| { |
| struct sock *subsk, *tmpsk; |
| struct tcp_sock *tp = tcp_sk(sk); |
| |
| __skb_queue_purge(&tp->mpcb->reinject_queue); |
| |
| if (tp->inside_tk_table) |
| mptcp_hash_remove_bh(tp); |
| |
| local_bh_disable(); |
| mptcp_for_each_sk_safe(tp->mpcb, subsk, tmpsk) { |
| /* The socket will get removed from the subsocket-list |
| * and made non-mptcp by setting mpc to 0. |
| * |
| * This is necessary, because tcp_disconnect assumes |
| * that the connection is completly dead afterwards. |
| * Thus we need to do a mptcp_del_sock. Due to this call |
| * we have to make it non-mptcp. |
| * |
| * We have to lock the socket, because we set mpc to 0. |
| * An incoming packet would take the subsocket's lock |
| * and go on into the receive-path. |
| * This would be a race. |
| */ |
| |
| bh_lock_sock(subsk); |
| mptcp_del_sock(subsk); |
| tcp_sk(subsk)->mpc = 0; |
| tcp_sk(subsk)->ops = &tcp_specific; |
| mptcp_sub_force_close(subsk); |
| bh_unlock_sock(subsk); |
| } |
| local_bh_enable(); |
| |
| tp->was_meta_sk = 1; |
| tp->mpc = 0; |
| tp->ops = &tcp_specific; |
| } |
| |
| |
| /* Returns 1 if we should enable MPTCP for that socket. */ |
| int mptcp_doit(struct sock *sk) |
| { |
| /* Don't do mptcp over loopback */ |
| if (sk->sk_family == AF_INET && |
| (ipv4_is_loopback(inet_sk(sk)->inet_daddr) || |
| ipv4_is_loopback(inet_sk(sk)->inet_saddr))) |
| return 0; |
| #if IS_ENABLED(CONFIG_IPV6) |
| if (sk->sk_family == AF_INET6 && |
| (ipv6_addr_loopback(&sk->sk_v6_daddr) || |
| ipv6_addr_loopback(&inet6_sk(sk)->saddr))) |
| return 0; |
| #endif |
| if (mptcp_v6_is_v4_mapped(sk) && |
| ipv4_is_loopback(inet_sk(sk)->inet_saddr)) |
| return 0; |
| |
| #ifdef CONFIG_TCP_MD5SIG |
| /* If TCP_MD5SIG is enabled, do not do MPTCP - there is no Option-Space */ |
| if (tcp_sk(sk)->af_specific->md5_lookup(sk, sk)) |
| return 0; |
| #endif |
| |
| return 1; |
| } |
| |
| int mptcp_create_master_sk(struct sock *meta_sk, __u64 remote_key, |
| __u8 mptcp_ver, u32 window) |
| { |
| struct tcp_sock *master_tp; |
| struct sock *master_sk; |
| |
| if (mptcp_alloc_mpcb(meta_sk, remote_key, mptcp_ver, window)) |
| goto err_alloc_mpcb; |
| |
| master_sk = tcp_sk(meta_sk)->mpcb->master_sk; |
| master_tp = tcp_sk(master_sk); |
| |
| if (mptcp_add_sock(meta_sk, master_sk, 0, 0, GFP_ATOMIC)) |
| goto err_add_sock; |
| |
| if (__inet_inherit_port(meta_sk, master_sk) < 0) |
| goto err_add_sock; |
| |
| meta_sk->sk_prot->unhash(meta_sk); |
| inet_ehash_nolisten(master_sk, NULL); |
| |
| master_tp->mptcp->init_rcv_wnd = master_tp->rcv_wnd; |
| |
| return 0; |
| |
| err_add_sock: |
| mptcp_fallback_meta_sk(meta_sk); |
| |
| inet_csk_prepare_forced_close(master_sk); |
| tcp_done(master_sk); |
| inet_csk_prepare_forced_close(meta_sk); |
| tcp_done(meta_sk); |
| |
| err_alloc_mpcb: |
| return -ENOBUFS; |
| } |
| |
| static int __mptcp_check_req_master(struct sock *child, |
| struct request_sock *req) |
| { |
| struct tcp_sock *child_tp = tcp_sk(child); |
| struct sock *meta_sk = child; |
| struct mptcp_cb *mpcb; |
| struct mptcp_request_sock *mtreq; |
| |
| /* Never contained an MP_CAPABLE */ |
| if (!inet_rsk(req)->mptcp_rqsk) |
| return 1; |
| |
| if (!inet_rsk(req)->saw_mpc) { |
| /* Fallback to regular TCP, because we saw one SYN without |
| * MP_CAPABLE. In tcp_check_req we continue the regular path. |
| * But, the socket has been added to the reqsk_tk_htb, so we |
| * must still remove it. |
| */ |
| MPTCP_INC_STATS_BH(sock_net(meta_sk), MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK); |
| mptcp_reqsk_remove_tk(req); |
| return 1; |
| } |
| |
| MPTCP_INC_STATS_BH(sock_net(meta_sk), MPTCP_MIB_MPCAPABLEPASSIVEACK); |
| |
| /* Just set this values to pass them to mptcp_alloc_mpcb */ |
| mtreq = mptcp_rsk(req); |
| child_tp->mptcp_loc_key = mtreq->mptcp_loc_key; |
| child_tp->mptcp_loc_token = mtreq->mptcp_loc_token; |
| |
| if (mptcp_create_master_sk(meta_sk, mtreq->mptcp_rem_key, |
| mtreq->mptcp_ver, child_tp->snd_wnd)) |
| return -ENOBUFS; |
| |
| child = tcp_sk(child)->mpcb->master_sk; |
| child_tp = tcp_sk(child); |
| mpcb = child_tp->mpcb; |
| |
| child_tp->mptcp->snt_isn = tcp_rsk(req)->snt_isn; |
| child_tp->mptcp->rcv_isn = tcp_rsk(req)->rcv_isn; |
| |
| mpcb->dss_csum = mtreq->dss_csum; |
| mpcb->server_side = 1; |
| |
| /* Will be moved to ESTABLISHED by tcp_rcv_state_process() */ |
| mptcp_update_metasocket(child, meta_sk); |
| |
| /* Needs to be done here additionally, because when accepting a |
| * new connection we pass by __reqsk_free and not reqsk_free. |
| */ |
| mptcp_reqsk_remove_tk(req); |
| |
| /* Hold when creating the meta-sk in tcp_vX_syn_recv_sock. */ |
| sock_put(meta_sk); |
| |
| return 0; |
| } |
| |
| int mptcp_check_req_fastopen(struct sock *child, struct request_sock *req) |
| { |
| struct sock *meta_sk = child, *master_sk; |
| struct sk_buff *skb; |
| u32 new_mapping; |
| int ret; |
| |
| ret = __mptcp_check_req_master(child, req); |
| if (ret) |
| return ret; |
| |
| master_sk = tcp_sk(meta_sk)->mpcb->master_sk; |
| |
| /* We need to rewind copied_seq as it is set to IDSN + 1 and as we have |
| * pre-MPTCP data in the receive queue. |
| */ |
| tcp_sk(meta_sk)->copied_seq -= tcp_sk(master_sk)->rcv_nxt - |
| tcp_rsk(req)->rcv_isn - 1; |
| |
| /* Map subflow sequence number to data sequence numbers. We need to map |
| * these data to [IDSN - len - 1, IDSN[. |
| */ |
| new_mapping = tcp_sk(meta_sk)->copied_seq - tcp_rsk(req)->rcv_isn - 1; |
| |
| /* There should be only one skb: the SYN + data. */ |
| skb_queue_walk(&meta_sk->sk_receive_queue, skb) { |
| TCP_SKB_CB(skb)->seq += new_mapping; |
| TCP_SKB_CB(skb)->end_seq += new_mapping; |
| } |
| |
| /* With fastopen we change the semantics of the relative subflow |
| * sequence numbers to deal with middleboxes that could add/remove |
| * multiple bytes in the SYN. We chose to start counting at rcv_nxt - 1 |
| * instead of the regular TCP ISN. |
| */ |
| tcp_sk(master_sk)->mptcp->rcv_isn = tcp_sk(master_sk)->rcv_nxt - 1; |
| |
| /* We need to update copied_seq of the master_sk to account for the |
| * already moved data to the meta receive queue. |
| */ |
| tcp_sk(master_sk)->copied_seq = tcp_sk(master_sk)->rcv_nxt; |
| |
| /* Handled by the master_sk */ |
| tcp_sk(meta_sk)->fastopen_rsk = NULL; |
| |
| return 0; |
| } |
| |
| int mptcp_check_req_master(struct sock *sk, struct sock *child, |
| struct request_sock *req, const struct sk_buff *skb, |
| int drop) |
| { |
| struct sock *meta_sk = child; |
| int ret; |
| |
| ret = __mptcp_check_req_master(child, req); |
| if (ret) |
| return ret; |
| child = tcp_sk(child)->mpcb->master_sk; |
| |
| sock_rps_save_rxhash(child, skb); |
| |
| /* drop indicates that we come from tcp_check_req and thus need to |
| * handle the request-socket fully. |
| */ |
| if (drop) { |
| tcp_synack_rtt_meas(child, req); |
| inet_csk_complete_hashdance(sk, meta_sk, req, true); |
| } else { |
| /* Thus, we come from syn-cookies */ |
| atomic_set(&req->rsk_refcnt, 1); |
| inet_csk_reqsk_queue_add(sk, req, meta_sk); |
| } |
| |
| return 0; |
| } |
| |
| struct sock *mptcp_check_req_child(struct sock *meta_sk, |
| struct sock *child, |
| struct request_sock *req, |
| struct sk_buff *skb, |
| const struct mptcp_options_received *mopt) |
| { |
| struct tcp_sock *child_tp = tcp_sk(child); |
| struct mptcp_request_sock *mtreq = mptcp_rsk(req); |
| struct mptcp_cb *mpcb = tcp_sk(meta_sk)->mpcb; |
| u8 hash_mac_check[20]; |
| |
| child_tp->inside_tk_table = 0; |
| |
| if (!mopt->join_ack) { |
| MPTCP_INC_STATS_BH(sock_net(meta_sk), MPTCP_MIB_JOINACKFAIL); |
| goto teardown; |
| } |
| |
| mptcp_hmac_sha1((u8 *)&mpcb->mptcp_rem_key, |
| (u8 *)&mpcb->mptcp_loc_key, |
| (u32 *)hash_mac_check, 2, |
| 4, (u8 *)&mtreq->mptcp_rem_nonce, |
| 4, (u8 *)&mtreq->mptcp_loc_nonce); |
| |
| if (memcmp(hash_mac_check, (char *)&mopt->mptcp_recv_mac, 20)) { |
| MPTCP_INC_STATS_BH(sock_net(meta_sk), MPTCP_MIB_JOINACKMAC); |
| goto teardown; |
| } |
| |
| /* Point it to the same struct socket and wq as the meta_sk */ |
| sk_set_socket(child, meta_sk->sk_socket); |
| child->sk_wq = meta_sk->sk_wq; |
| |
| if (mptcp_add_sock(meta_sk, child, mtreq->loc_id, mtreq->rem_id, GFP_ATOMIC)) { |
| /* Has been inherited, but now child_tp->mptcp is NULL */ |
| child_tp->mpc = 0; |
| child_tp->ops = &tcp_specific; |
| |
| /* TODO when we support acking the third ack for new subflows, |
| * we should silently discard this third ack, by returning NULL. |
| * |
| * Maybe, at the retransmission we will have enough memory to |
| * fully add the socket to the meta-sk. |
| */ |
| goto teardown; |
| } |
| |
| /* The child is a clone of the meta socket, we must now reset |
| * some of the fields |
| */ |
| child_tp->mptcp->rcv_low_prio = mtreq->rcv_low_prio; |
| |
| /* We should allow proper increase of the snd/rcv-buffers. Thus, we |
| * use the original values instead of the bloated up ones from the |
| * clone. |
| */ |
| child->sk_sndbuf = mpcb->orig_sk_sndbuf; |
| child->sk_rcvbuf = mpcb->orig_sk_rcvbuf; |
| |
| child_tp->mptcp->slave_sk = 1; |
| child_tp->mptcp->snt_isn = tcp_rsk(req)->snt_isn; |
| child_tp->mptcp->rcv_isn = tcp_rsk(req)->rcv_isn; |
| child_tp->mptcp->init_rcv_wnd = req->rsk_rcv_wnd; |
| |
| child_tp->tsq_flags = 0; |
| child_tp->out_of_order_queue = RB_ROOT; |
| |
| sock_rps_save_rxhash(child, skb); |
| tcp_synack_rtt_meas(child, req); |
| |
| /* Subflows do not use the accept queue, as they |
| * are attached immediately to the mpcb. |
| */ |
| inet_csk_reqsk_queue_drop(meta_sk, req); |
| |
| /* The refcnt is initialized to 2, because regular TCP will put him |
| * in the socket's listener queue. However, we do not have a listener-queue. |
| * So, we need to make sure that this request-sock indeed gets destroyed. |
| */ |
| reqsk_put(req); |
| |
| MPTCP_INC_STATS_BH(sock_net(meta_sk), MPTCP_MIB_JOINACKRX); |
| return child; |
| |
| teardown: |
| req->rsk_ops->send_reset(meta_sk, skb); |
| |
| /* Drop this request - sock creation failed. */ |
| inet_csk_reqsk_queue_drop(meta_sk, req); |
| inet_csk_prepare_forced_close(child); |
| tcp_done(child); |
| return meta_sk; |
| } |
| |
| int mptcp_init_tw_sock(struct sock *sk, struct tcp_timewait_sock *tw) |
| { |
| struct mptcp_tw *mptw; |
| struct tcp_sock *tp = tcp_sk(sk); |
| struct mptcp_cb *mpcb = tp->mpcb; |
| |
| /* A subsocket in tw can only receive data. So, if we are in |
| * infinite-receive, then we should not reply with a data-ack or act |
| * upon general MPTCP-signaling. We prevent this by simply not creating |
| * the mptcp_tw_sock. |
| */ |
| if (mpcb->infinite_mapping_rcv) { |
| tw->mptcp_tw = NULL; |
| return 0; |
| } |
| |
| /* Alloc MPTCP-tw-sock */ |
| mptw = kmem_cache_alloc(mptcp_tw_cache, GFP_ATOMIC); |
| if (!mptw) { |
| tw->mptcp_tw = NULL; |
| return -ENOBUFS; |
| } |
| |
| atomic_inc(&mpcb->mpcb_refcnt); |
| |
| tw->mptcp_tw = mptw; |
| mptw->loc_key = mpcb->mptcp_loc_key; |
| mptw->meta_tw = mpcb->in_time_wait; |
| if (mptw->meta_tw) { |
| mptw->rcv_nxt = mptcp_get_rcv_nxt_64(mptcp_meta_tp(tp)); |
| if (mpcb->mptw_state != TCP_TIME_WAIT) |
| mptw->rcv_nxt++; |
| } |
| rcu_assign_pointer(mptw->mpcb, mpcb); |
| |
| spin_lock(&mpcb->tw_lock); |
| list_add_rcu(&mptw->list, &tp->mpcb->tw_list); |
| mptw->in_list = 1; |
| spin_unlock(&mpcb->tw_lock); |
| |
| return 0; |
| } |
| |
| void mptcp_twsk_destructor(struct tcp_timewait_sock *tw) |
| { |
| struct mptcp_cb *mpcb; |
| |
| rcu_read_lock(); |
| mpcb = rcu_dereference(tw->mptcp_tw->mpcb); |
| |
| /* If we are still holding a ref to the mpcb, we have to remove ourself |
| * from the list and drop the ref properly. |
| */ |
| if (mpcb && atomic_inc_not_zero(&mpcb->mpcb_refcnt)) { |
| spin_lock(&mpcb->tw_lock); |
| if (tw->mptcp_tw->in_list) { |
| list_del_rcu(&tw->mptcp_tw->list); |
| tw->mptcp_tw->in_list = 0; |
| } |
| spin_unlock(&mpcb->tw_lock); |
| |
| /* Twice, because we increased it above */ |
| mptcp_mpcb_put(mpcb); |
| mptcp_mpcb_put(mpcb); |
| } |
| |
| rcu_read_unlock(); |
| |
| kmem_cache_free(mptcp_tw_cache, tw->mptcp_tw); |
| } |
| |
| /* Updates the rcv_nxt of the time-wait-socks and allows them to ack a |
| * data-fin. |
| */ |
| void mptcp_time_wait(struct sock *meta_sk, int state, int timeo) |
| { |
| struct tcp_sock *meta_tp = tcp_sk(meta_sk); |
| struct mptcp_tw *mptw; |
| |
| /* Used for sockets that go into tw after the meta |
| * (see mptcp_init_tw_sock()) |
| */ |
| meta_tp->mpcb->in_time_wait = 1; |
| meta_tp->mpcb->mptw_state = state; |
| |
| /* Update the time-wait-sock's information */ |
| rcu_read_lock_bh(); |
| list_for_each_entry_rcu(mptw, &meta_tp->mpcb->tw_list, list) { |
| mptw->meta_tw = 1; |
| mptw->rcv_nxt = mptcp_get_rcv_nxt_64(meta_tp); |
| |
| /* We want to ack a DATA_FIN, but are yet in FIN_WAIT_2 - |
| * pretend as if the DATA_FIN has already reached us, that way |
| * the checks in tcp_timewait_state_process will be good as the |
| * DATA_FIN comes in. |
| */ |
| if (state != TCP_TIME_WAIT) |
| mptw->rcv_nxt++; |
| } |
| rcu_read_unlock_bh(); |
| |
| if (meta_sk->sk_state != TCP_CLOSE) |
| tcp_done(meta_sk); |
| } |
| |
| void mptcp_tsq_flags(struct sock *sk) |
| { |
| struct tcp_sock *tp = tcp_sk(sk); |
| struct sock *meta_sk = mptcp_meta_sk(sk); |
| |
| /* It will be handled as a regular deferred-call */ |
| if (is_meta_sk(sk)) |
| return; |
| |
| if (hlist_unhashed(&tp->mptcp->cb_list)) { |
| hlist_add_head(&tp->mptcp->cb_list, &tp->mpcb->callback_list); |
| /* We need to hold it here, as the sock_hold is not assured |
| * by the release_sock as it is done in regular TCP. |
| * |
| * The subsocket may get inet_csk_destroy'd while it is inside |
| * the callback_list. |
| */ |
| sock_hold(sk); |
| } |
| |
| if (!test_and_set_bit(MPTCP_SUB_DEFERRED, &tcp_sk(meta_sk)->tsq_flags)) |
| sock_hold(meta_sk); |
| } |
| |
| void mptcp_tsq_sub_deferred(struct sock *meta_sk) |
| { |
| struct tcp_sock *meta_tp = tcp_sk(meta_sk); |
| struct mptcp_tcp_sock *mptcp; |
| struct hlist_node *tmp; |
| |
| BUG_ON(!is_meta_sk(meta_sk) && !meta_tp->was_meta_sk); |
| |
| __sock_put(meta_sk); |
| hlist_for_each_entry_safe(mptcp, tmp, &meta_tp->mpcb->callback_list, cb_list) { |
| struct tcp_sock *tp = mptcp->tp; |
| struct sock *sk = (struct sock *)tp; |
| |
| hlist_del_init(&mptcp->cb_list); |
| sk->sk_prot->release_cb(sk); |
| /* Final sock_put (cfr. mptcp_tsq_flags */ |
| sock_put(sk); |
| } |
| } |
| |
| void mptcp_join_reqsk_init(const struct mptcp_cb *mpcb, |
| const struct request_sock *req, |
| struct sk_buff *skb) |
| { |
| struct mptcp_request_sock *mtreq = mptcp_rsk(req); |
| struct mptcp_options_received mopt; |
| u8 mptcp_hash_mac[20]; |
| |
| mptcp_init_mp_opt(&mopt); |
| tcp_parse_mptcp_options(skb, &mopt); |
| |
| mtreq->is_sub = 1; |
| inet_rsk(req)->mptcp_rqsk = 1; |
| |
| mtreq->mptcp_rem_nonce = mopt.mptcp_recv_nonce; |
| |
| mptcp_hmac_sha1((u8 *)&mpcb->mptcp_loc_key, |
| (u8 *)&mpcb->mptcp_rem_key, |
| (u32 *)mptcp_hash_mac, 2, |
| 4, (u8 *)&mtreq->mptcp_loc_nonce, |
| 4, (u8 *)&mtreq->mptcp_rem_nonce); |
| mtreq->mptcp_hash_tmac = *(u64 *)mptcp_hash_mac; |
| |
| mtreq->rem_id = mopt.rem_id; |
| mtreq->rcv_low_prio = mopt.low_prio; |
| inet_rsk(req)->saw_mpc = 1; |
| |
| MPTCP_INC_STATS_BH(sock_net(mpcb->meta_sk), MPTCP_MIB_JOINSYNRX); |
| } |
| |
| void mptcp_reqsk_init(struct request_sock *req, const struct sock *sk, |
| const struct sk_buff *skb, bool want_cookie) |
| { |
| struct mptcp_options_received mopt; |
| struct mptcp_request_sock *mtreq = mptcp_rsk(req); |
| |
| mptcp_init_mp_opt(&mopt); |
| tcp_parse_mptcp_options(skb, &mopt); |
| |
| mtreq->dss_csum = mopt.dss_csum; |
| |
| if (want_cookie) { |
| if (!mptcp_reqsk_new_cookie(req, &mopt, skb)) |
| /* No key available - back to regular TCP */ |
| inet_rsk(req)->mptcp_rqsk = 0; |
| return; |
| } |
| |
| mptcp_reqsk_new_mptcp(req, sk, &mopt, skb); |
| } |
| |
| void mptcp_cookies_reqsk_init(struct request_sock *req, |
| struct mptcp_options_received *mopt, |
| struct sk_buff *skb) |
| { |
| struct mptcp_request_sock *mtreq = mptcp_rsk(req); |
| |
| /* Absolutely need to always initialize this. */ |
| mtreq->hash_entry.pprev = NULL; |
| |
| mtreq->mptcp_rem_key = mopt->mptcp_sender_key; |
| mtreq->mptcp_loc_key = mopt->mptcp_receiver_key; |
| |
| /* Generate the token */ |
| mptcp_key_sha1(mtreq->mptcp_loc_key, &mtreq->mptcp_loc_token, NULL); |
| |
| rcu_read_lock(); |
| spin_lock(&mptcp_tk_hashlock); |
| |
| /* Check, if the key is still free */ |
| if (mptcp_reqsk_find_tk(mtreq->mptcp_loc_token) || |
| mptcp_find_token(mtreq->mptcp_loc_token)) |
| goto out; |
| |
| inet_rsk(req)->saw_mpc = 1; |
| mtreq->is_sub = 0; |
| inet_rsk(req)->mptcp_rqsk = 1; |
| mtreq->dss_csum = mopt->dss_csum; |
| |
| out: |
| spin_unlock(&mptcp_tk_hashlock); |
| rcu_read_unlock(); |
| } |
| |
| int mptcp_conn_request(struct sock *sk, struct sk_buff *skb) |
| { |
| struct mptcp_options_received mopt; |
| |
| mptcp_init_mp_opt(&mopt); |
| tcp_parse_mptcp_options(skb, &mopt); |
| |
| if (mopt.is_mp_join) |
| return mptcp_do_join_short(skb, &mopt, sock_net(sk)); |
| if (mopt.drop_me) |
| goto drop; |
| |
| if (!sock_flag(sk, SOCK_MPTCP)) |
| mopt.saw_mpc = 0; |
| |
| if (skb->protocol == htons(ETH_P_IP)) { |
| if (mopt.saw_mpc) { |
| if (skb_rtable(skb)->rt_flags & |
| (RTCF_BROADCAST | RTCF_MULTICAST)) |
| goto drop; |
| |
| MPTCP_INC_STATS_BH(sock_net(sk), MPTCP_MIB_MPCAPABLEPASSIVE); |
| return tcp_conn_request(&mptcp_request_sock_ops, |
| &mptcp_request_sock_ipv4_ops, |
| sk, skb); |
| } |
| |
| return tcp_v4_conn_request(sk, skb); |
| #if IS_ENABLED(CONFIG_IPV6) |
| } else { |
| if (mopt.saw_mpc) { |
| if (!ipv6_unicast_destination(skb)) |
| goto drop; |
| |
| MPTCP_INC_STATS_BH(sock_net(sk), MPTCP_MIB_MPCAPABLEPASSIVE); |
| return tcp_conn_request(&mptcp6_request_sock_ops, |
| &mptcp_request_sock_ipv6_ops, |
| sk, skb); |
| } |
| |
| return tcp_v6_conn_request(sk, skb); |
| #endif |
| } |
| drop: |
| NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS); |
| return 0; |
| } |
| |
| static const struct snmp_mib mptcp_snmp_list[] = { |
| SNMP_MIB_ITEM("MPCapableSYNRX", MPTCP_MIB_MPCAPABLEPASSIVE), |
| SNMP_MIB_ITEM("MPCapableSYNTX", MPTCP_MIB_MPCAPABLEACTIVE), |
| SNMP_MIB_ITEM("MPCapableSYNACKRX", MPTCP_MIB_MPCAPABLEACTIVEACK), |
| SNMP_MIB_ITEM("MPCapableACKRX", MPTCP_MIB_MPCAPABLEPASSIVEACK), |
| SNMP_MIB_ITEM("MPCapableFallbackACK", MPTCP_MIB_MPCAPABLEPASSIVEFALLBACK), |
| SNMP_MIB_ITEM("MPCapableFallbackSYNACK", MPTCP_MIB_MPCAPABLEACTIVEFALLBACK), |
| SNMP_MIB_ITEM("MPCapableRetransFallback", MPTCP_MIB_MPCAPABLERETRANSFALLBACK), |
| SNMP_MIB_ITEM("MPTCPCsumEnabled", MPTCP_MIB_CSUMENABLED), |
| SNMP_MIB_ITEM("MPTCPRetrans", MPTCP_MIB_RETRANSSEGS), |
| SNMP_MIB_ITEM("MPFailRX", MPTCP_MIB_MPFAILRX), |
| SNMP_MIB_ITEM("MPCsumFail", MPTCP_MIB_CSUMFAIL), |
| SNMP_MIB_ITEM("MPFastcloseRX", MPTCP_MIB_FASTCLOSERX), |
| SNMP_MIB_ITEM("MPFastcloseTX", MPTCP_MIB_FASTCLOSETX), |
| SNMP_MIB_ITEM("MPFallbackAckSub", MPTCP_MIB_FBACKSUB), |
| SNMP_MIB_ITEM("MPFallbackAckInit", MPTCP_MIB_FBACKINIT), |
| SNMP_MIB_ITEM("MPFallbackDataSub", MPTCP_MIB_FBDATASUB), |
| SNMP_MIB_ITEM("MPFallbackDataInit", MPTCP_MIB_FBDATAINIT), |
| SNMP_MIB_ITEM("MPRemoveAddrSubDelete", MPTCP_MIB_REMADDRSUB), |
| SNMP_MIB_ITEM("MPJoinNoTokenFound", MPTCP_MIB_JOINNOTOKEN), |
| SNMP_MIB_ITEM("MPJoinAlreadyFallenback", MPTCP_MIB_JOINFALLBACK), |
| SNMP_MIB_ITEM("MPJoinSynTx", MPTCP_MIB_JOINSYNTX), |
| SNMP_MIB_ITEM("MPJoinSynRx", MPTCP_MIB_JOINSYNRX), |
| SNMP_MIB_ITEM("MPJoinSynAckRx", MPTCP_MIB_JOINSYNACKRX), |
| SNMP_MIB_ITEM("MPJoinSynAckHMacFailure", MPTCP_MIB_JOINSYNACKMAC), |
| SNMP_MIB_ITEM("MPJoinAckRx", MPTCP_MIB_JOINACKRX), |
| SNMP_MIB_ITEM("MPJoinAckHMacFailure", MPTCP_MIB_JOINACKMAC), |
| SNMP_MIB_ITEM("MPJoinAckMissing", MPTCP_MIB_JOINACKFAIL), |
| SNMP_MIB_ITEM("MPJoinAckRTO", MPTCP_MIB_JOINACKRTO), |
| SNMP_MIB_ITEM("MPJoinAckRexmit", MPTCP_MIB_JOINACKRXMIT), |
| SNMP_MIB_ITEM("NoDSSInWindow", MPTCP_MIB_NODSSWINDOW), |
| SNMP_MIB_ITEM("DSSNotMatching", MPTCP_MIB_DSSNOMATCH), |
| SNMP_MIB_ITEM("InfiniteMapRx", MPTCP_MIB_INFINITEMAPRX), |
| SNMP_MIB_ITEM("DSSNoMatchTCP", MPTCP_MIB_DSSTCPMISMATCH), |
| SNMP_MIB_ITEM("DSSTrimHead", MPTCP_MIB_DSSTRIMHEAD), |
| SNMP_MIB_ITEM("DSSSplitTail", MPTCP_MIB_DSSSPLITTAIL), |
| SNMP_MIB_ITEM("DSSPurgeOldSubSegs", MPTCP_MIB_PURGEOLD), |
| SNMP_MIB_ITEM("AddAddrRx", MPTCP_MIB_ADDADDRRX), |
| SNMP_MIB_ITEM("AddAddrTx", MPTCP_MIB_ADDADDRTX), |
| SNMP_MIB_ITEM("RemAddrRx", MPTCP_MIB_REMADDRRX), |
| SNMP_MIB_ITEM("RemAddrTx", MPTCP_MIB_REMADDRTX), |
| SNMP_MIB_SENTINEL |
| }; |
| |
| struct workqueue_struct *mptcp_wq; |
| EXPORT_SYMBOL(mptcp_wq); |
| |
| /* Output /proc/net/mptcp */ |
| static int mptcp_pm_seq_show(struct seq_file *seq, void *v) |
| { |
| struct tcp_sock *meta_tp; |
| const struct net *net = seq->private; |
| int i, n = 0; |
| |
| seq_printf(seq, " sl loc_tok rem_tok v6 local_address remote_address st ns tx_queue rx_queue inode"); |
| seq_putc(seq, '\n'); |
| |
| for (i = 0; i < MPTCP_HASH_SIZE; i++) { |
| struct hlist_nulls_node *node; |
| rcu_read_lock_bh(); |
| hlist_nulls_for_each_entry_rcu(meta_tp, node, |
| &tk_hashtable[i], tk_table) { |
| struct mptcp_cb *mpcb = meta_tp->mpcb; |
| struct sock *meta_sk = (struct sock *)meta_tp; |
| struct inet_sock *isk = inet_sk(meta_sk); |
| |
| if (!mptcp(meta_tp) || !net_eq(net, sock_net(meta_sk))) |
| continue; |
| |
| if (capable(CAP_NET_ADMIN)) { |
| seq_printf(seq, "%4d: %04X %04X ", n++, |
| mpcb->mptcp_loc_token, |
| mpcb->mptcp_rem_token); |
| } else { |
| seq_printf(seq, "%4d: %04X %04X ", n++, -1, -1); |
| } |
| if (meta_sk->sk_family == AF_INET || |
| mptcp_v6_is_v4_mapped(meta_sk)) { |
| seq_printf(seq, " 0 %08X:%04X %08X:%04X ", |
| isk->inet_rcv_saddr, |
| ntohs(isk->inet_sport), |
| isk->inet_daddr, |
| ntohs(isk->inet_dport)); |
| #if IS_ENABLED(CONFIG_IPV6) |
| } else if (meta_sk->sk_family == AF_INET6) { |
| struct in6_addr *src = &meta_sk->sk_v6_rcv_saddr; |
| struct in6_addr *dst = &meta_sk->sk_v6_daddr; |
| seq_printf(seq, " 1 %08X%08X%08X%08X:%04X %08X%08X%08X%08X:%04X", |
| src->s6_addr32[0], src->s6_addr32[1], |
| src->s6_addr32[2], src->s6_addr32[3], |
| ntohs(isk->inet_sport), |
| dst->s6_addr32[0], dst->s6_addr32[1], |
| dst->s6_addr32[2], dst->s6_addr32[3], |
| ntohs(isk->inet_dport)); |
| #endif |
| } |
| seq_printf(seq, " %02X %02X %08X:%08X %lu", |
| meta_sk->sk_state, mpcb->cnt_subflows, |
| meta_tp->write_seq - meta_tp->snd_una, |
| max_t(int, meta_tp->rcv_nxt - |
| meta_tp->copied_seq, 0), |
| sock_i_ino(meta_sk)); |
| seq_putc(seq, '\n'); |
| } |
| |
| rcu_read_unlock_bh(); |
| } |
| |
| return 0; |
| } |
| |
| static int mptcp_pm_seq_open(struct inode *inode, struct file *file) |
| { |
| return single_open_net(inode, file, mptcp_pm_seq_show); |
| } |
| |
| static const struct file_operations mptcp_pm_seq_fops = { |
| .owner = THIS_MODULE, |
| .open = mptcp_pm_seq_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release_net, |
| }; |
| |
| static int mptcp_snmp_seq_show(struct seq_file *seq, void *v) |
| { |
| struct net *net = seq->private; |
| int i; |
| |
| for (i = 0; mptcp_snmp_list[i].name != NULL; i++) |
| seq_printf(seq, "%-32s\t%ld\n", mptcp_snmp_list[i].name, |
| snmp_fold_field(net->mptcp.mptcp_statistics, |
| mptcp_snmp_list[i].entry)); |
| |
| return 0; |
| } |
| |
| static int mptcp_snmp_seq_open(struct inode *inode, struct file *file) |
| { |
| return single_open_net(inode, file, mptcp_snmp_seq_show); |
| } |
| |
| static const struct file_operations mptcp_snmp_seq_fops = { |
| .owner = THIS_MODULE, |
| .open = mptcp_snmp_seq_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .release = single_release_net, |
| }; |
| |
| static int mptcp_pm_init_net(struct net *net) |
| { |
| net->mptcp.mptcp_statistics = alloc_percpu(struct mptcp_mib); |
| if (!net->mptcp.mptcp_statistics) |
| goto out_mptcp_mibs; |
| |
| #ifdef CONFIG_PROC_FS |
| net->mptcp.proc_net_mptcp = proc_net_mkdir(net, "mptcp_net", net->proc_net); |
| if (!net->mptcp.proc_net_mptcp) |
| goto out_proc_net_mptcp; |
| if (!proc_create("mptcp", S_IRUGO, net->mptcp.proc_net_mptcp, |
| &mptcp_pm_seq_fops)) |
| goto out_mptcp_net_mptcp; |
| if (!proc_create("snmp", S_IRUGO, net->mptcp.proc_net_mptcp, |
| &mptcp_snmp_seq_fops)) |
| goto out_mptcp_net_snmp; |
| #endif |
| |
| return 0; |
| |
| #ifdef CONFIG_PROC_FS |
| out_mptcp_net_snmp: |
| remove_proc_entry("mptcp", net->mptcp.proc_net_mptcp); |
| out_mptcp_net_mptcp: |
| remove_proc_subtree("mptcp_net", net->proc_net); |
| net->mptcp.proc_net_mptcp = NULL; |
| out_proc_net_mptcp: |
| free_percpu(net->mptcp.mptcp_statistics); |
| #endif |
| out_mptcp_mibs: |
| return -ENOMEM; |
| } |
| |
| static void mptcp_pm_exit_net(struct net *net) |
| { |
| remove_proc_entry("snmp", net->mptcp.proc_net_mptcp); |
| remove_proc_entry("mptcp", net->mptcp.proc_net_mptcp); |
| remove_proc_subtree("mptcp_net", net->proc_net); |
| free_percpu(net->mptcp.mptcp_statistics); |
| } |
| |
| static struct pernet_operations mptcp_pm_proc_ops = { |
| .init = mptcp_pm_init_net, |
| .exit = mptcp_pm_exit_net, |
| }; |
| |
| /* General initialization of mptcp */ |
| void __init mptcp_init(void) |
| { |
| int i; |
| struct ctl_table_header *mptcp_sysctl; |
| |
| mptcp_sock_cache = kmem_cache_create("mptcp_sock", |
| sizeof(struct mptcp_tcp_sock), |
| 0, SLAB_HWCACHE_ALIGN, |
| NULL); |
| if (!mptcp_sock_cache) |
| goto mptcp_sock_cache_failed; |
| |
| mptcp_cb_cache = kmem_cache_create("mptcp_cb", sizeof(struct mptcp_cb), |
| 0, SLAB_DESTROY_BY_RCU|SLAB_HWCACHE_ALIGN, |
| NULL); |
| if (!mptcp_cb_cache) |
| goto mptcp_cb_cache_failed; |
| |
| mptcp_tw_cache = kmem_cache_create("mptcp_tw", sizeof(struct mptcp_tw), |
| 0, SLAB_DESTROY_BY_RCU|SLAB_HWCACHE_ALIGN, |
| NULL); |
| if (!mptcp_tw_cache) |
| goto mptcp_tw_cache_failed; |
| |
| get_random_bytes(mptcp_secret, sizeof(mptcp_secret)); |
| |
| mptcp_wq = alloc_workqueue("mptcp_wq", WQ_UNBOUND | WQ_MEM_RECLAIM, 8); |
| if (!mptcp_wq) |
| goto alloc_workqueue_failed; |
| |
| for (i = 0; i < MPTCP_HASH_SIZE; i++) { |
| INIT_HLIST_NULLS_HEAD(&tk_hashtable[i], i); |
| INIT_HLIST_NULLS_HEAD(&mptcp_reqsk_tk_htb[i], i); |
| } |
| |
| spin_lock_init(&mptcp_tk_hashlock); |
| |
| if (register_pernet_subsys(&mptcp_pm_proc_ops)) |
| goto pernet_failed; |
| |
| #if IS_ENABLED(CONFIG_IPV6) |
| if (mptcp_pm_v6_init()) |
| goto mptcp_pm_v6_failed; |
| #endif |
| if (mptcp_pm_v4_init()) |
| goto mptcp_pm_v4_failed; |
| |
| mptcp_sysctl = register_net_sysctl(&init_net, "net/mptcp", mptcp_table); |
| if (!mptcp_sysctl) |
| goto register_sysctl_failed; |
| |
| if (mptcp_register_path_manager(&mptcp_pm_default)) |
| goto register_pm_failed; |
| |
| if (mptcp_register_scheduler(&mptcp_sched_default)) |
| goto register_sched_failed; |
| |
| pr_info("MPTCP: Stable release v0.89.0-rc"); |
| |
| mptcp_init_failed = false; |
| |
| return; |
| |
| register_sched_failed: |
| mptcp_unregister_path_manager(&mptcp_pm_default); |
| register_pm_failed: |
| unregister_net_sysctl_table(mptcp_sysctl); |
| register_sysctl_failed: |
| mptcp_pm_v4_undo(); |
| mptcp_pm_v4_failed: |
| #if IS_ENABLED(CONFIG_IPV6) |
| mptcp_pm_v6_undo(); |
| mptcp_pm_v6_failed: |
| #endif |
| unregister_pernet_subsys(&mptcp_pm_proc_ops); |
| pernet_failed: |
| destroy_workqueue(mptcp_wq); |
| alloc_workqueue_failed: |
| kmem_cache_destroy(mptcp_tw_cache); |
| mptcp_tw_cache_failed: |
| kmem_cache_destroy(mptcp_cb_cache); |
| mptcp_cb_cache_failed: |
| kmem_cache_destroy(mptcp_sock_cache); |
| mptcp_sock_cache_failed: |
| mptcp_init_failed = true; |
| } |