diff mbox

[next] netfilter: ipv6: add getsockopt to retrieve origdst

Message ID 1351595329-13081-1-git-send-email-fw@strlen.de
State Accepted
Headers show

Commit Message

Florian Westphal Oct. 30, 2012, 11:08 a.m. UTC
userspace can query the original ipv4 destination address of a REDIRECTed
connection via
getsockopt(m_sock, SOL_IP, SO_ORIGINAL_DST, &m_server_addr, &addrsize)

but for ipv6 no such option existed.

This adds getsockopt(..., IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, ...).

Without this, userspace needs to parse /proc or use ctnetlink, which
appears to be overkill.

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 Instead of adding new IP6T_SO_ORIGINAL_DST, we could also
 simply re-use existing SO_ORIGINAL_DST ipv4 define.  Any Preferences?

 include/uapi/linux/in6.h                       |    1 +
 include/uapi/linux/netfilter_ipv6/ip6_tables.h |    3 +
 net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c |   61 ++++++++++++++++++++++++
 3 files changed, 65 insertions(+), 0 deletions(-)

Comments

Nick Jones Oct. 31, 2012, 2:11 a.m. UTC | #1
On Tue, 2012-10-30 at 12:08 +0100, Florian Westphal wrote: 
> userspace can query the original ipv4 destination address of a REDIRECTed
> connection via
> getsockopt(m_sock, SOL_IP, SO_ORIGINAL_DST, &m_server_addr, &addrsize)
> 
> but for ipv6 no such option existed.
> 
> This adds getsockopt(..., IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, ...).
> 
> Without this, userspace needs to parse /proc or use ctnetlink, which
> appears to be overkill.
> 

Doesn't getsockname(2) provide this information?  It does for TPROXY'd
connections.

> Signed-off-by: Florian Westphal <fw@strlen.de>
> ---
>  Instead of adding new IP6T_SO_ORIGINAL_DST, we could also
>  simply re-use existing SO_ORIGINAL_DST ipv4 define.  Any Preferences?
> 
>  include/uapi/linux/in6.h                       |    1 +
>  include/uapi/linux/netfilter_ipv6/ip6_tables.h |    3 +
>  net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c |   61 ++++++++++++++++++++++++
>  3 files changed, 65 insertions(+), 0 deletions(-)
> 
> diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h
> index 1e31599..f79c372 100644
> --- a/include/uapi/linux/in6.h
> +++ b/include/uapi/linux/in6.h
> @@ -240,6 +240,7 @@ struct in6_flowlabel_req {
>   *
>   * IP6T_SO_GET_REVISION_MATCH	68
>   * IP6T_SO_GET_REVISION_TARGET	69
> + * IP6T_SO_ORIGINAL_DST		80
>   */
>  
>  /* RFC5014: Source address selection */
> diff --git a/include/uapi/linux/netfilter_ipv6/ip6_tables.h b/include/uapi/linux/netfilter_ipv6/ip6_tables.h
> index bf1ef65..649c680 100644
> --- a/include/uapi/linux/netfilter_ipv6/ip6_tables.h
> +++ b/include/uapi/linux/netfilter_ipv6/ip6_tables.h
> @@ -178,6 +178,9 @@ struct ip6t_error {
>  #define IP6T_SO_GET_REVISION_TARGET	(IP6T_BASE_CTL + 5)
>  #define IP6T_SO_GET_MAX			IP6T_SO_GET_REVISION_TARGET
>  
> +/* obtain original address if REDIRECT'd connection */
> +#define IP6T_SO_ORIGINAL_DST            80
> +
>  /* ICMP matching stuff */
>  struct ip6t_icmp {
>  	__u8 type;				/* type to match */
> diff --git a/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c b/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
> index 8860d23..02dcafd 100644
> --- a/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
> +++ b/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
> @@ -21,6 +21,7 @@
>  
>  #include <linux/netfilter_bridge.h>
>  #include <linux/netfilter_ipv6.h>
> +#include <linux/netfilter_ipv6/ip6_tables.h>
>  #include <net/netfilter/nf_conntrack.h>
>  #include <net/netfilter/nf_conntrack_helper.h>
>  #include <net/netfilter/nf_conntrack_l4proto.h>
> @@ -295,6 +296,50 @@ static struct nf_hook_ops ipv6_conntrack_ops[] __read_mostly = {
>  	},
>  };
>  
> +static int
> +ipv6_getorigdst(struct sock *sk, int optval, void __user *user, int *len)
> +{
> +	const struct inet_sock *inet = inet_sk(sk);
> +	const struct ipv6_pinfo *inet6 = inet6_sk(sk);
> +	const struct nf_conntrack_tuple_hash *h;
> +	struct sockaddr_in6 sin6;
> +	struct nf_conntrack_tuple tuple = { .src.l3num = NFPROTO_IPV6 };
> +	struct nf_conn *ct;
> +
> +	tuple.src.u3.in6 = inet6->rcv_saddr;
> +	tuple.src.u.tcp.port = inet->inet_sport;
> +	tuple.dst.u3.in6 = inet6->daddr;
> +	tuple.dst.u.tcp.port = inet->inet_dport;
> +	tuple.dst.protonum = sk->sk_protocol;
> +
> +	if (sk->sk_protocol != IPPROTO_TCP && sk->sk_protocol != IPPROTO_SCTP)
> +		return -ENOPROTOOPT;
> +
> +	if (*len < 0 || (unsigned int) *len < sizeof(sin6))
> +		return -EINVAL;
> +
> +	h = nf_conntrack_find_get(sock_net(sk), NF_CT_DEFAULT_ZONE, &tuple);
> +	if (!h) {
> +		pr_debug("IP6T_SO_ORIGINAL_DST: Can't find %pI6c/%u-%pI6c/%u.\n",
> +			 &tuple.src.u3.ip6, ntohs(tuple.src.u.tcp.port),
> +			 &tuple.dst.u3.ip6, ntohs(tuple.dst.u.tcp.port));
> +		return -ENOENT;
> +	}
> +
> +	ct = nf_ct_tuplehash_to_ctrack(h);
> +
> +	sin6.sin6_family = AF_INET6;
> +	sin6.sin6_port = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.tcp.port;
> +	sin6.sin6_flowinfo = inet6->flow_label & IPV6_FLOWINFO_MASK;
> +	memcpy(&sin6.sin6_addr,
> +		&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.in6,
> +					sizeof(sin6.sin6_addr));
> +	sin6.sin6_scope_id = sk->sk_bound_dev_if;
> +
> +	nf_ct_put(ct);
> +	return copy_to_user(user, &sin6, sizeof(sin6)) ? -EFAULT : 0;
> +}
> +
>  #if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
>  
>  #include <linux/netfilter/nfnetlink.h>
> @@ -359,6 +404,14 @@ MODULE_ALIAS("nf_conntrack-" __stringify(AF_INET6));
>  MODULE_LICENSE("GPL");
>  MODULE_AUTHOR("Yasuyuki KOZAKAI @USAGI <yasuyuki.kozakai@toshiba.co.jp>");
>  
> +static struct nf_sockopt_ops so_getorigdst6 = {
> +	.pf		= NFPROTO_IPV6,
> +	.get_optmin	= IP6T_SO_ORIGINAL_DST,
> +	.get_optmax	= IP6T_SO_ORIGINAL_DST + 1,
> +	.get		= ipv6_getorigdst,
> +	.owner		= THIS_MODULE,
> +};
> +
>  static int ipv6_net_init(struct net *net)
>  {
>  	int ret = 0;
> @@ -425,6 +478,12 @@ static int __init nf_conntrack_l3proto_ipv6_init(void)
>  	need_conntrack();
>  	nf_defrag_ipv6_enable();
>  
> +	ret = nf_register_sockopt(&so_getorigdst6);
> +	if (ret < 0) {
> +		pr_err("Unable to register netfilter socket option\n");
> +		return ret;
> +	}
> +
>  	ret = register_pernet_subsys(&ipv6_net_ops);
>  	if (ret < 0)
>  		goto cleanup_pernet;
> @@ -440,6 +499,7 @@ static int __init nf_conntrack_l3proto_ipv6_init(void)
>   cleanup_ipv6:
>  	unregister_pernet_subsys(&ipv6_net_ops);
>   cleanup_pernet:
> +	nf_unregister_sockopt(&so_getorigdst6);
>  	return ret;
>  }
>  
> @@ -448,6 +508,7 @@ static void __exit nf_conntrack_l3proto_ipv6_fini(void)
>  	synchronize_net();
>  	nf_unregister_hooks(ipv6_conntrack_ops, ARRAY_SIZE(ipv6_conntrack_ops));
>  	unregister_pernet_subsys(&ipv6_net_ops);
> +	nf_unregister_sockopt(&so_getorigdst6);
>  }
>  
>  module_init(nf_conntrack_l3proto_ipv6_init);
Florian Westphal Oct. 31, 2012, 8:48 a.m. UTC | #2
Nick Jones <nick.jones@network-box.com> wrote:
> > getsockopt(m_sock, SOL_IP, SO_ORIGINAL_DST, &m_server_addr, &addrsize)
> > 
> > but for ipv6 no such option existed.
> > 
> > This adds getsockopt(..., IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, ...).
> > 
> > Without this, userspace needs to parse /proc or use ctnetlink, which
> > appears to be overkill.
> 
> Doesn't getsockname(2) provide this information?

No.  It will show the local address (the address we REDIRECT'ed to).

> It does for TPROXY'd connections.

Yes, because with TPROXY the destination won't be rewritten.
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Amos Jeffries Oct. 31, 2012, 12:28 p.m. UTC | #3
On 31/10/2012 9:48 p.m., Florian Westphal wrote:
> Nick Jones <nick.jones@network-box.com> wrote:
>>> getsockopt(m_sock, SOL_IP, SO_ORIGINAL_DST, &m_server_addr, &addrsize)
>>>
>>> but for ipv6 no such option existed.
>>>
>>> This adds getsockopt(..., IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, ...).
>>>
>>> Without this, userspace needs to parse /proc or use ctnetlink, which
>>> appears to be overkill.
>> Doesn't getsockname(2) provide this information?
> No.  It will show the local address (the address we REDIRECT'ed to).


 From the user perspective I don't see why a new code macro is 
necessary. Surely it would be sufficient to support:

   getsockopt(..., IPPROTO_IPV6, SO_ORIGINAL_DST, ...).


AYJ
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Pablo Neira Ayuso Nov. 2, 2012, 11:36 a.m. UTC | #4
On Tue, Oct 30, 2012 at 12:08:49PM +0100, Florian Westphal wrote:
> userspace can query the original ipv4 destination address of a REDIRECTed
> connection via
> getsockopt(m_sock, SOL_IP, SO_ORIGINAL_DST, &m_server_addr, &addrsize)
> 
> but for ipv6 no such option existed.
> 
> This adds getsockopt(..., IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST, ...).
> 
> Without this, userspace needs to parse /proc or use ctnetlink, which
> appears to be overkill.

Applied, thanks Florian.

> ---
>  Instead of adding new IP6T_SO_ORIGINAL_DST, we could also
>  simply re-use existing SO_ORIGINAL_DST ipv4 define.  Any Preferences?

I've added this to the patch description, for the record:

"This uses option number 80 for IP6T_SO_ORIGINAL_DST, which is spare,
to use the same number we use in the IPv4 socket option
SO_ORIGINAL_DST".
--
To unsubscribe from this list: send the line "unsubscribe netfilter-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h
index 1e31599..f79c372 100644
--- a/include/uapi/linux/in6.h
+++ b/include/uapi/linux/in6.h
@@ -240,6 +240,7 @@  struct in6_flowlabel_req {
  *
  * IP6T_SO_GET_REVISION_MATCH	68
  * IP6T_SO_GET_REVISION_TARGET	69
+ * IP6T_SO_ORIGINAL_DST		80
  */
 
 /* RFC5014: Source address selection */
diff --git a/include/uapi/linux/netfilter_ipv6/ip6_tables.h b/include/uapi/linux/netfilter_ipv6/ip6_tables.h
index bf1ef65..649c680 100644
--- a/include/uapi/linux/netfilter_ipv6/ip6_tables.h
+++ b/include/uapi/linux/netfilter_ipv6/ip6_tables.h
@@ -178,6 +178,9 @@  struct ip6t_error {
 #define IP6T_SO_GET_REVISION_TARGET	(IP6T_BASE_CTL + 5)
 #define IP6T_SO_GET_MAX			IP6T_SO_GET_REVISION_TARGET
 
+/* obtain original address if REDIRECT'd connection */
+#define IP6T_SO_ORIGINAL_DST            80
+
 /* ICMP matching stuff */
 struct ip6t_icmp {
 	__u8 type;				/* type to match */
diff --git a/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c b/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
index 8860d23..02dcafd 100644
--- a/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
+++ b/net/ipv6/netfilter/nf_conntrack_l3proto_ipv6.c
@@ -21,6 +21,7 @@ 
 
 #include <linux/netfilter_bridge.h>
 #include <linux/netfilter_ipv6.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
 #include <net/netfilter/nf_conntrack.h>
 #include <net/netfilter/nf_conntrack_helper.h>
 #include <net/netfilter/nf_conntrack_l4proto.h>
@@ -295,6 +296,50 @@  static struct nf_hook_ops ipv6_conntrack_ops[] __read_mostly = {
 	},
 };
 
+static int
+ipv6_getorigdst(struct sock *sk, int optval, void __user *user, int *len)
+{
+	const struct inet_sock *inet = inet_sk(sk);
+	const struct ipv6_pinfo *inet6 = inet6_sk(sk);
+	const struct nf_conntrack_tuple_hash *h;
+	struct sockaddr_in6 sin6;
+	struct nf_conntrack_tuple tuple = { .src.l3num = NFPROTO_IPV6 };
+	struct nf_conn *ct;
+
+	tuple.src.u3.in6 = inet6->rcv_saddr;
+	tuple.src.u.tcp.port = inet->inet_sport;
+	tuple.dst.u3.in6 = inet6->daddr;
+	tuple.dst.u.tcp.port = inet->inet_dport;
+	tuple.dst.protonum = sk->sk_protocol;
+
+	if (sk->sk_protocol != IPPROTO_TCP && sk->sk_protocol != IPPROTO_SCTP)
+		return -ENOPROTOOPT;
+
+	if (*len < 0 || (unsigned int) *len < sizeof(sin6))
+		return -EINVAL;
+
+	h = nf_conntrack_find_get(sock_net(sk), NF_CT_DEFAULT_ZONE, &tuple);
+	if (!h) {
+		pr_debug("IP6T_SO_ORIGINAL_DST: Can't find %pI6c/%u-%pI6c/%u.\n",
+			 &tuple.src.u3.ip6, ntohs(tuple.src.u.tcp.port),
+			 &tuple.dst.u3.ip6, ntohs(tuple.dst.u.tcp.port));
+		return -ENOENT;
+	}
+
+	ct = nf_ct_tuplehash_to_ctrack(h);
+
+	sin6.sin6_family = AF_INET6;
+	sin6.sin6_port = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.tcp.port;
+	sin6.sin6_flowinfo = inet6->flow_label & IPV6_FLOWINFO_MASK;
+	memcpy(&sin6.sin6_addr,
+		&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3.in6,
+					sizeof(sin6.sin6_addr));
+	sin6.sin6_scope_id = sk->sk_bound_dev_if;
+
+	nf_ct_put(ct);
+	return copy_to_user(user, &sin6, sizeof(sin6)) ? -EFAULT : 0;
+}
+
 #if defined(CONFIG_NF_CT_NETLINK) || defined(CONFIG_NF_CT_NETLINK_MODULE)
 
 #include <linux/netfilter/nfnetlink.h>
@@ -359,6 +404,14 @@  MODULE_ALIAS("nf_conntrack-" __stringify(AF_INET6));
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Yasuyuki KOZAKAI @USAGI <yasuyuki.kozakai@toshiba.co.jp>");
 
+static struct nf_sockopt_ops so_getorigdst6 = {
+	.pf		= NFPROTO_IPV6,
+	.get_optmin	= IP6T_SO_ORIGINAL_DST,
+	.get_optmax	= IP6T_SO_ORIGINAL_DST + 1,
+	.get		= ipv6_getorigdst,
+	.owner		= THIS_MODULE,
+};
+
 static int ipv6_net_init(struct net *net)
 {
 	int ret = 0;
@@ -425,6 +478,12 @@  static int __init nf_conntrack_l3proto_ipv6_init(void)
 	need_conntrack();
 	nf_defrag_ipv6_enable();
 
+	ret = nf_register_sockopt(&so_getorigdst6);
+	if (ret < 0) {
+		pr_err("Unable to register netfilter socket option\n");
+		return ret;
+	}
+
 	ret = register_pernet_subsys(&ipv6_net_ops);
 	if (ret < 0)
 		goto cleanup_pernet;
@@ -440,6 +499,7 @@  static int __init nf_conntrack_l3proto_ipv6_init(void)
  cleanup_ipv6:
 	unregister_pernet_subsys(&ipv6_net_ops);
  cleanup_pernet:
+	nf_unregister_sockopt(&so_getorigdst6);
 	return ret;
 }
 
@@ -448,6 +508,7 @@  static void __exit nf_conntrack_l3proto_ipv6_fini(void)
 	synchronize_net();
 	nf_unregister_hooks(ipv6_conntrack_ops, ARRAY_SIZE(ipv6_conntrack_ops));
 	unregister_pernet_subsys(&ipv6_net_ops);
+	nf_unregister_sockopt(&so_getorigdst6);
 }
 
 module_init(nf_conntrack_l3proto_ipv6_init);