diff options
Diffstat (limited to 'net/rds/tcp.c')
| -rw-r--r-- | net/rds/tcp.c | 154 | 
1 files changed, 131 insertions, 23 deletions
| diff --git a/net/rds/tcp.c b/net/rds/tcp.c index 351a28474667..2c7b7c352d3e 100644 --- a/net/rds/tcp.c +++ b/net/rds/tcp.c @@ -1,5 +1,5 @@  /* - * Copyright (c) 2006 Oracle.  All rights reserved. + * Copyright (c) 2006, 2018 Oracle and/or its affiliates. All rights reserved.   *   * This software is available to you under a choice of one of two   * licenses.  You may choose to be licensed under the terms of the GNU @@ -37,6 +37,8 @@  #include <net/tcp.h>  #include <net/net_namespace.h>  #include <net/netns/generic.h> +#include <net/tcp.h> +#include <net/addrconf.h>  #include "rds.h"  #include "tcp.h" @@ -44,7 +46,14 @@  /* only for info exporting */  static DEFINE_SPINLOCK(rds_tcp_tc_list_lock);  static LIST_HEAD(rds_tcp_tc_list); + +/* rds_tcp_tc_count counts only IPv4 connections. + * rds6_tcp_tc_count counts both IPv4 and IPv6 connections. + */  static unsigned int rds_tcp_tc_count; +#if IS_ENABLED(CONFIG_IPV6) +static unsigned int rds6_tcp_tc_count; +#endif  /* Track rds_tcp_connection structs so they can be cleaned up */  static DEFINE_SPINLOCK(rds_tcp_conn_lock); @@ -111,7 +120,11 @@ void rds_tcp_restore_callbacks(struct socket *sock,  	/* done under the callback_lock to serialize with write_space */  	spin_lock(&rds_tcp_tc_list_lock);  	list_del_init(&tc->t_list_item); -	rds_tcp_tc_count--; +#if IS_ENABLED(CONFIG_IPV6) +	rds6_tcp_tc_count--; +#endif +	if (!tc->t_cpath->cp_conn->c_isv6) +		rds_tcp_tc_count--;  	spin_unlock(&rds_tcp_tc_list_lock);  	tc->t_sock = NULL; @@ -198,7 +211,11 @@ void rds_tcp_set_callbacks(struct socket *sock, struct rds_conn_path *cp)  	/* done under the callback_lock to serialize with write_space */  	spin_lock(&rds_tcp_tc_list_lock);  	list_add_tail(&tc->t_list_item, &rds_tcp_tc_list); -	rds_tcp_tc_count++; +#if IS_ENABLED(CONFIG_IPV6) +	rds6_tcp_tc_count++; +#endif +	if (!tc->t_cpath->cp_conn->c_isv6) +		rds_tcp_tc_count++;  	spin_unlock(&rds_tcp_tc_list_lock);  	/* accepted sockets need our listen data ready undone */ @@ -219,6 +236,9 @@ void rds_tcp_set_callbacks(struct socket *sock, struct rds_conn_path *cp)  	write_unlock_bh(&sock->sk->sk_callback_lock);  } +/* Handle RDS_INFO_TCP_SOCKETS socket option.  It only returns IPv4 + * connections for backward compatibility. + */  static void rds_tcp_tc_info(struct socket *rds_sock, unsigned int len,  			    struct rds_info_iterator *iter,  			    struct rds_info_lengths *lens) @@ -226,8 +246,6 @@ static void rds_tcp_tc_info(struct socket *rds_sock, unsigned int len,  	struct rds_info_tcp_socket tsinfo;  	struct rds_tcp_connection *tc;  	unsigned long flags; -	struct sockaddr_in sin; -	struct socket *sock;  	spin_lock_irqsave(&rds_tcp_tc_list_lock, flags); @@ -235,16 +253,15 @@ static void rds_tcp_tc_info(struct socket *rds_sock, unsigned int len,  		goto out;  	list_for_each_entry(tc, &rds_tcp_tc_list, t_list_item) { +		struct inet_sock *inet = inet_sk(tc->t_sock->sk); -		sock = tc->t_sock; -		if (sock) { -			sock->ops->getname(sock, (struct sockaddr *)&sin, 0); -			tsinfo.local_addr = sin.sin_addr.s_addr; -			tsinfo.local_port = sin.sin_port; -			sock->ops->getname(sock, (struct sockaddr *)&sin, 1); -			tsinfo.peer_addr = sin.sin_addr.s_addr; -			tsinfo.peer_port = sin.sin_port; -		} +		if (tc->t_cpath->cp_conn->c_isv6) +			continue; + +		tsinfo.local_addr = inet->inet_saddr; +		tsinfo.local_port = inet->inet_sport; +		tsinfo.peer_addr = inet->inet_daddr; +		tsinfo.peer_port = inet->inet_dport;  		tsinfo.hdr_rem = tc->t_tinc_hdr_rem;  		tsinfo.data_rem = tc->t_tinc_data_rem; @@ -262,10 +279,82 @@ out:  	spin_unlock_irqrestore(&rds_tcp_tc_list_lock, flags);  } -static int rds_tcp_laddr_check(struct net *net, __be32 addr) +#if IS_ENABLED(CONFIG_IPV6) +/* Handle RDS6_INFO_TCP_SOCKETS socket option. It returns both IPv4 and + * IPv6 connections. IPv4 connection address is returned in an IPv4 mapped + * address. + */ +static void rds6_tcp_tc_info(struct socket *sock, unsigned int len, +			     struct rds_info_iterator *iter, +			     struct rds_info_lengths *lens)  { -	if (inet_addr_type(net, addr) == RTN_LOCAL) +	struct rds6_info_tcp_socket tsinfo6; +	struct rds_tcp_connection *tc; +	unsigned long flags; + +	spin_lock_irqsave(&rds_tcp_tc_list_lock, flags); + +	if (len / sizeof(tsinfo6) < rds6_tcp_tc_count) +		goto out; + +	list_for_each_entry(tc, &rds_tcp_tc_list, t_list_item) { +		struct sock *sk = tc->t_sock->sk; +		struct inet_sock *inet = inet_sk(sk); + +		tsinfo6.local_addr = sk->sk_v6_rcv_saddr; +		tsinfo6.local_port = inet->inet_sport; +		tsinfo6.peer_addr = sk->sk_v6_daddr; +		tsinfo6.peer_port = inet->inet_dport; + +		tsinfo6.hdr_rem = tc->t_tinc_hdr_rem; +		tsinfo6.data_rem = tc->t_tinc_data_rem; +		tsinfo6.last_sent_nxt = tc->t_last_sent_nxt; +		tsinfo6.last_expected_una = tc->t_last_expected_una; +		tsinfo6.last_seen_una = tc->t_last_seen_una; + +		rds_info_copy(iter, &tsinfo6, sizeof(tsinfo6)); +	} + +out: +	lens->nr = rds6_tcp_tc_count; +	lens->each = sizeof(tsinfo6); + +	spin_unlock_irqrestore(&rds_tcp_tc_list_lock, flags); +} +#endif + +static int rds_tcp_laddr_check(struct net *net, const struct in6_addr *addr, +			       __u32 scope_id) +{ +	struct net_device *dev = NULL; +#if IS_ENABLED(CONFIG_IPV6) +	int ret; +#endif + +	if (ipv6_addr_v4mapped(addr)) { +		if (inet_addr_type(net, addr->s6_addr32[3]) == RTN_LOCAL) +			return 0; +		return -EADDRNOTAVAIL; +	} + +	/* If the scope_id is specified, check only those addresses +	 * hosted on the specified interface. +	 */ +	if (scope_id != 0) { +		rcu_read_lock(); +		dev = dev_get_by_index_rcu(net, scope_id); +		/* scope_id is not valid... */ +		if (!dev) { +			rcu_read_unlock(); +			return -EADDRNOTAVAIL; +		} +		rcu_read_unlock(); +	} +#if IS_ENABLED(CONFIG_IPV6) +	ret = ipv6_chk_addr(net, addr, dev, 0); +	if (ret)  		return 0; +#endif  	return -EADDRNOTAVAIL;  } @@ -468,13 +557,27 @@ static __net_init int rds_tcp_init_net(struct net *net)  		err = -ENOMEM;  		goto fail;  	} -	rtn->rds_tcp_listen_sock = rds_tcp_listen_init(net); + +#if IS_ENABLED(CONFIG_IPV6) +	rtn->rds_tcp_listen_sock = rds_tcp_listen_init(net, true); +#else +	rtn->rds_tcp_listen_sock = rds_tcp_listen_init(net, false); +#endif  	if (!rtn->rds_tcp_listen_sock) { -		pr_warn("could not set up listen sock\n"); -		unregister_net_sysctl_table(rtn->rds_tcp_sysctl); -		rtn->rds_tcp_sysctl = NULL; -		err = -EAFNOSUPPORT; -		goto fail; +		pr_warn("could not set up IPv6 listen sock\n"); + +#if IS_ENABLED(CONFIG_IPV6) +		/* Try IPv4 as some systems disable IPv6 */ +		rtn->rds_tcp_listen_sock = rds_tcp_listen_init(net, false); +		if (!rtn->rds_tcp_listen_sock) { +#endif +			unregister_net_sysctl_table(rtn->rds_tcp_sysctl); +			rtn->rds_tcp_sysctl = NULL; +			err = -EAFNOSUPPORT; +			goto fail; +#if IS_ENABLED(CONFIG_IPV6) +		} +#endif  	}  	INIT_WORK(&rtn->rds_tcp_accept_w, rds_tcp_accept_worker);  	return 0; @@ -588,6 +691,9 @@ static void rds_tcp_exit(void)  	rds_tcp_set_unloading();  	synchronize_rcu();  	rds_info_deregister_func(RDS_INFO_TCP_SOCKETS, rds_tcp_tc_info); +#if IS_ENABLED(CONFIG_IPV6) +	rds_info_deregister_func(RDS6_INFO_TCP_SOCKETS, rds6_tcp_tc_info); +#endif  	unregister_pernet_device(&rds_tcp_net_ops);  	rds_tcp_destroy_conns();  	rds_trans_unregister(&rds_tcp_transport); @@ -619,6 +725,9 @@ static int rds_tcp_init(void)  	rds_trans_register(&rds_tcp_transport);  	rds_info_register_func(RDS_INFO_TCP_SOCKETS, rds_tcp_tc_info); +#if IS_ENABLED(CONFIG_IPV6) +	rds_info_register_func(RDS6_INFO_TCP_SOCKETS, rds6_tcp_tc_info); +#endif  	goto out;  out_recv: @@ -633,4 +742,3 @@ module_init(rds_tcp_init);  MODULE_AUTHOR("Oracle Corporation <[email protected]>");  MODULE_DESCRIPTION("RDS: TCP transport");  MODULE_LICENSE("Dual BSD/GPL"); - |