[PATCH net-next] ipv6: raw: use IPv4 raw_sendmsg on v4-mapped IPv6 destinations

2018-01-24 Thread Ivan Delalande
Make IPv6 SOCK_RAW sockets operate like IPv6 UDP and TCP sockets with
respect to IPv4 mapped addresses by calling IPv4 raw_sendmsg from
rawv6_sendmsg to send those messages out.

Signed-off-by: Travis Brown <trav...@arista.com>
Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/net/raw.h |  1 +
 net/ipv4/raw.c|  5 +++--
 net/ipv6/raw.c| 14 ++
 3 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/include/net/raw.h b/include/net/raw.h
index 99d26d0c4a19..b4dbf730da54 100644
--- a/include/net/raw.h
+++ b/include/net/raw.h
@@ -33,6 +33,7 @@ void raw_icmp_error(struct sk_buff *, int, u32);
 int raw_local_deliver(struct sk_buff *, int);
 
 int raw_rcv(struct sock *, struct sk_buff *);
+int rawv4_sendmsg(struct sock *sk, struct msghdr *msg, size_t len);
 
 #define RAW_HTABLE_SIZEMAX_INET_PROTOS
 
diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c
index 136544b36a46..09f719af8642 100644
--- a/net/ipv4/raw.c
+++ b/net/ipv4/raw.c
@@ -499,7 +499,7 @@ static int raw_getfrag(void *from, char *to, int offset, 
int len, int odd,
return ip_generic_getfrag(rfv->msg, to, offset, len, odd, skb);
 }
 
-static int raw_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
+int rawv4_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
 {
struct inet_sock *inet = inet_sk(sk);
struct net *net = sock_net(sk);
@@ -692,6 +692,7 @@ static int raw_sendmsg(struct sock *sk, struct msghdr *msg, 
size_t len)
err = 0;
goto done;
 }
+EXPORT_SYMBOL_GPL(rawv4_sendmsg);
 
 static void raw_close(struct sock *sk, long timeout)
 {
@@ -969,7 +970,7 @@ struct proto raw_prot = {
.init  = raw_init,
.setsockopt= raw_setsockopt,
.getsockopt= raw_getsockopt,
-   .sendmsg   = raw_sendmsg,
+   .sendmsg   = rawv4_sendmsg,
.recvmsg   = raw_recvmsg,
.bind  = raw_bind,
.backlog_rcv   = raw_rcv_skb,
diff --git a/net/ipv6/raw.c b/net/ipv6/raw.c
index ddda7eb3c623..f8513e2f1481 100644
--- a/net/ipv6/raw.c
+++ b/net/ipv6/raw.c
@@ -844,6 +844,20 @@ static int rawv6_sendmsg(struct sock *sk, struct msghdr 
*msg, size_t len)
fl6.flowlabel = np->flow_label;
}
 
+   if (daddr && ipv6_addr_v4mapped(daddr)) {
+   struct sockaddr_in sin;
+
+   sin.sin_family = AF_INET;
+   sin.sin_port = sin6 ? sin6->sin6_port : inet->inet_dport;
+   sin.sin_addr.s_addr = daddr->s6_addr32[3];
+   msg->msg_name = 
+   msg->msg_namelen = sizeof(sin);
+
+   if (__ipv6_only_sock(sk))
+   return -ENETUNREACH;
+   return rawv4_sendmsg(sk, msg, len);
+   }
+
if (fl6.flowi6_oif == 0)
fl6.flowi6_oif = sk->sk_bound_dev_if;
 
-- 
2.16.1


[PATCH iproute2 2/2] ss: print MD5 signature keys configured on TCP sockets

2017-10-06 Thread Ivan Delalande
These keys are reported by kernel 4.14 and later under the
INET_DIAG_MD5SIG attribute, when INET_DIAG_INFO is requested (ss -i)
and we have CAP_NET_ADMIN. The additional output looks like:

md5keys:fe80::/64=signing_key,10.1.2.0/24=foobar,::1/128=Test

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 misc/ss.c | 21 +
 1 file changed, 21 insertions(+)

diff --git a/misc/ss.c b/misc/ss.c
index dd8dfaa4..09bff8a7 100644
--- a/misc/ss.c
+++ b/misc/ss.c
@@ -2153,6 +2153,16 @@ static void print_skmeminfo(struct rtattr *tb[], int 
attrtype)
printf(")");
 }
 
+static void print_md5sig(struct tcp_diag_md5sig *sig)
+{
+   printf("%s/%d=",
+  format_host(sig->tcpm_family,
+  sig->tcpm_family == AF_INET6 ? 16 : 4,
+  >tcpm_addr),
+  sig->tcpm_prefixlen);
+   print_escape_buf(sig->tcpm_key, sig->tcpm_keylen, " ,");
+}
+
 #define TCPI_HAS_OPT(info, opt) !!(info->tcpi_options & (opt))
 
 static void tcp_show_info(const struct nlmsghdr *nlh, struct inet_diag_msg *r,
@@ -2289,6 +2299,17 @@ static void tcp_show_info(const struct nlmsghdr *nlh, 
struct inet_diag_msg *r,
free(s.dctcp);
free(s.bbr_info);
}
+   if (tb[INET_DIAG_MD5SIG]) {
+   struct tcp_diag_md5sig *sig = RTA_DATA(tb[INET_DIAG_MD5SIG]);
+   int len = RTA_PAYLOAD(tb[INET_DIAG_MD5SIG]);
+
+   printf(" md5keys:");
+   print_md5sig(sig++);
+   for (len -= sizeof(*sig); len > 0; len -= sizeof(*sig)) {
+   printf(",");
+   print_md5sig(sig++);
+   }
+   }
 }
 
 static const char *format_host_sa(struct sockaddr_storage *sa)
-- 
2.14.2



[PATCH iproute2 1/2] utils: add print_escape_buf to format and print arbitrary bytes

2017-10-06 Thread Ivan Delalande
Keep it as simple as possible for now: just escape anything that is not
isprint-able, is among the "escape" parameter or '\' as an octal escape
sequence. This should be pretty easy to extend if any other user needs
something more complex in the future.

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/utils.h |  2 ++
 lib/utils.c | 15 +++
 2 files changed, 17 insertions(+)

diff --git a/include/utils.h b/include/utils.h
index 76addb32..3d91c50d 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -195,6 +195,8 @@ static inline void __jiffies_to_tv(struct timeval *tv, 
unsigned long jiffies)
tv->tv_usec = tvusec - 100 * tv->tv_sec;
 }
 
+void print_escape_buf(const __u8 *buf, size_t len, const char *escape);
+
 int print_timestamp(FILE *fp);
 void print_nlmsg_timestamp(FILE *fp, const struct nlmsghdr *n);
 
diff --git a/lib/utils.c b/lib/utils.c
index 0cf99619..a494190e 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -31,6 +31,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include "rt_names.h"
 #include "utils.h"
@@ -1047,6 +1048,20 @@ int addr64_n2a(__u64 addr, char *buff, size_t len)
return written;
 }
 
+/* Print buffer and escape bytes that are !isprint or among 'escape' */
+void print_escape_buf(const __u8 *buf, size_t len, const char *escape)
+{
+   size_t i;
+
+   for (i = 0; i < len; ++i) {
+   if (isprint(buf[i]) && buf[i] != '\\' &&
+   !strchr(escape, buf[i]))
+   printf("%c", buf[i]);
+   else
+   printf("\\%03o", buf[i]);
+   }
+}
+
 int print_timestamp(FILE *fp)
 {
struct timeval tv;
-- 
2.14.2



Re: [PATCH net-next v5 2/2] tcp_diag: report TCP MD5 signing keys and addresses

2017-08-31 Thread Ivan Delalande
On Fri, Sep 01, 2017 at 01:26:33AM +0200, Sabrina Dubroca wrote:
> 2017-08-31, 09:59:39 -0700, Ivan Delalande wrote:
> > diff --git a/net/ipv4/tcp_diag.c b/net/ipv4/tcp_diag.c
> > index a748c74aa8b7..abbf0edcf6c2 100644
> > --- a/net/ipv4/tcp_diag.c
> > +++ b/net/ipv4/tcp_diag.c
> [...]
> > +static int tcp_diag_get_aux(struct sock *sk, bool net_admin,
> > +   struct sk_buff *skb)
> > +{
> > +#ifdef CONFIG_TCP_MD5SIG
> > +   if (net_admin) {
> 
> In tcp_diag_get_aux_size() you put a check for sk_fullsock. I don't
> see anything preventing you from reaching this with a !fullsock?

Currently handler->idiag_get_aux is only called from inet_sk_diag_fill
which has a `BUG_ON(!sk_fullsock(sk));`, but I could add another
explicit check in that function if you think it's more consistent.

Actually, I wasn't sure when adding this idiag_get_aux in v2 if it
should be called from inet_twsk_diag_fill, inet_req_diag_fill and
inet_csk_diag_fill, or just the last one. I chose that simpler approach
for now to avoid duplicating these state checks in the idiag_get_aux
defined by protocols and because we didn't need for INET_DIAG_MD5SIG,
but it shouldn't be too hard to change. Do you think this could be
useful for other protocols or attributes?

Thank you,
-- 
Ivan Delalande
Arista Networks


[PATCH net-next v5 1/2] inet_diag: allow protocols to provide additional data

2017-08-31 Thread Ivan Delalande
Extend inet_diag_handler to allow individual protocols to report
additional data on INET_DIAG_INFO through idiag_get_aux. The size
can be dynamic and is computed by idiag_get_aux_size.

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/linux/inet_diag.h |  7 +++
 net/ipv4/inet_diag.c  | 22 ++
 2 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/include/linux/inet_diag.h b/include/linux/inet_diag.h
index 65da430e260f..ee251c585854 100644
--- a/include/linux/inet_diag.h
+++ b/include/linux/inet_diag.h
@@ -25,6 +25,13 @@ struct inet_diag_handler {
  struct inet_diag_msg *r,
  void *info);
 
+   int (*idiag_get_aux)(struct sock *sk,
+bool net_admin,
+struct sk_buff *skb);
+
+   size_t  (*idiag_get_aux_size)(struct sock *sk,
+ bool net_admin);
+
int (*destroy)(struct sk_buff *in_skb,
   const struct inet_diag_req_v2 *req);
 
diff --git a/net/ipv4/inet_diag.c b/net/ipv4/inet_diag.c
index 67325d5832d7..c9c35b61a027 100644
--- a/net/ipv4/inet_diag.c
+++ b/net/ipv4/inet_diag.c
@@ -93,8 +93,17 @@ void inet_diag_msg_common_fill(struct inet_diag_msg *r, 
struct sock *sk)
 }
 EXPORT_SYMBOL_GPL(inet_diag_msg_common_fill);
 
-static size_t inet_sk_attr_size(void)
+static size_t inet_sk_attr_size(struct sock *sk,
+   const struct inet_diag_req_v2 *req,
+   bool net_admin)
 {
+   const struct inet_diag_handler *handler;
+   size_t aux = 0;
+
+   handler = inet_diag_table[req->sdiag_protocol];
+   if (handler && handler->idiag_get_aux_size)
+   aux = handler->idiag_get_aux_size(sk, net_admin);
+
returnnla_total_size(sizeof(struct tcp_info))
+ nla_total_size(1) /* INET_DIAG_SHUTDOWN */
+ nla_total_size(1) /* INET_DIAG_TOS */
@@ -105,6 +114,7 @@ static size_t inet_sk_attr_size(void)
+ nla_total_size(SK_MEMINFO_VARS * sizeof(u32))
+ nla_total_size(TCP_CA_NAME_MAX)
+ nla_total_size(sizeof(struct tcpvegas_info))
+   + aux
+ 64;
 }
 
@@ -260,6 +270,10 @@ int inet_sk_diag_fill(struct sock *sk, struct 
inet_connection_sock *icsk,
 
handler->idiag_get_info(sk, r, info);
 
+   if (ext & (1 << (INET_DIAG_INFO - 1)) && handler->idiag_get_aux)
+   if (handler->idiag_get_aux(sk, net_admin, skb) < 0)
+   goto errout;
+
if (sk->sk_state < TCP_TIME_WAIT) {
union tcp_cc_info info;
size_t sz = 0;
@@ -449,6 +463,7 @@ int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
const struct nlmsghdr *nlh,
const struct inet_diag_req_v2 *req)
 {
+   bool net_admin = netlink_net_capable(in_skb, CAP_NET_ADMIN);
struct net *net = sock_net(in_skb->sk);
struct sk_buff *rep;
struct sock *sk;
@@ -458,7 +473,7 @@ int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
if (IS_ERR(sk))
return PTR_ERR(sk);
 
-   rep = nlmsg_new(inet_sk_attr_size(), GFP_KERNEL);
+   rep = nlmsg_new(inet_sk_attr_size(sk, req, net_admin), GFP_KERNEL);
if (!rep) {
err = -ENOMEM;
goto out;
@@ -467,8 +482,7 @@ int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
err = sk_diag_fill(sk, rep, req,
   sk_user_ns(NETLINK_CB(in_skb).sk),
   NETLINK_CB(in_skb).portid,
-  nlh->nlmsg_seq, 0, nlh,
-  netlink_net_capable(in_skb, CAP_NET_ADMIN));
+  nlh->nlmsg_seq, 0, nlh, net_admin);
if (err < 0) {
WARN_ON(err == -EMSGSIZE);
nlmsg_free(rep);
-- 
2.14.1



[PATCH net-next v5 0/2] report TCP MD5 signing keys and addresses

2017-08-31 Thread Ivan Delalande
Allow userspace to retrieve MD5 signature keys and addresses configured
on TCP sockets through inet_diag.

Thanks to Eric Dumazet and Stephen Hemminger for their useful
explanations and feedback.

v5: - memset the whole netlink payload after it has been nla_reserve-d
  in tcp_diag_put_md5sig (a third memset had to be added for
  tcpm_key so we might as well have just one for entire region).
- move the nla_total_size call from inet_sk_attr_size to the
  idiag_get_aux_size defined by protocols as they could add multiple
  netlink attributes,
- add check for net_admin in tcp_diag_get_aux_size.

v4: - add new struct tcp_diag_md5sig to report the data instead of
  tcp_md5sig to avoid wasting 112 bytes on every tcpm_addr,
- memset tcpm_addr on IPv4 addresses to avoid leaks,
- style fix in inet_diag_dump_one_icsk.

v3: - rename inet_diag_*md5sig in tcp_diag.c to tcp_diag_* for
  consistency,
- don't lock the socket in tcp_diag_put_md5sig,
- add checks on md5sig_count in tcp_diag_put_md5sig to not create
  the netlink attribute if the list is empty, and to avoid overflows
  or memory leaks if the list has changed in the meantime.

v2: - move changes to tcp_diag.c and extend inet_diag_handler to allow
  protocols to provide additional data on INET_DIAG_INFO,
- lock socket before calling tcp_diag_put_md5sig.


I also have a patch for iproute2/ss to test this change, making it print
this new attribute. I'm planning to polish and send it if this series
gets applied.


Ivan Delalande (2):
  inet_diag: allow protocols to provide additional data
  tcp_diag: report TCP MD5 signing keys and addresses

 include/linux/inet_diag.h  |   7 +++
 include/uapi/linux/inet_diag.h |   1 +
 include/uapi/linux/tcp.h   |   9 
 net/ipv4/inet_diag.c   |  22 +++--
 net/ipv4/tcp_diag.c| 109 ++---
 5 files changed, 138 insertions(+), 10 deletions(-)

-- 
2.14.1



[PATCH net-next v5 2/2] tcp_diag: report TCP MD5 signing keys and addresses

2017-08-31 Thread Ivan Delalande
Report TCP MD5 (RFC2385) signing keys, addresses and address prefixes to
processes with CAP_NET_ADMIN requesting INET_DIAG_INFO. Currently it is
not possible to retrieve these from the kernel once they have been
configured on sockets.

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/uapi/linux/inet_diag.h |   1 +
 include/uapi/linux/tcp.h   |   9 
 net/ipv4/tcp_diag.c| 109 ++---
 3 files changed, 113 insertions(+), 6 deletions(-)

diff --git a/include/uapi/linux/inet_diag.h b/include/uapi/linux/inet_diag.h
index 678496897a68..f52ff62bfabe 100644
--- a/include/uapi/linux/inet_diag.h
+++ b/include/uapi/linux/inet_diag.h
@@ -143,6 +143,7 @@ enum {
INET_DIAG_MARK,
INET_DIAG_BBRINFO,
INET_DIAG_CLASS_ID,
+   INET_DIAG_MD5SIG,
__INET_DIAG_MAX,
 };
 
diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
index 030e594bab45..15c25eccab2b 100644
--- a/include/uapi/linux/tcp.h
+++ b/include/uapi/linux/tcp.h
@@ -256,4 +256,13 @@ struct tcp_md5sig {
__u8tcpm_key[TCP_MD5SIG_MAXKEYLEN]; /* key (binary) */
 };
 
+/* INET_DIAG_MD5SIG */
+struct tcp_diag_md5sig {
+   __u8tcpm_family;
+   __u8tcpm_prefixlen;
+   __u16   tcpm_keylen;
+   __be32  tcpm_addr[4];
+   __u8tcpm_key[TCP_MD5SIG_MAXKEYLEN];
+};
+
 #endif /* _UAPI_LINUX_TCP_H */
diff --git a/net/ipv4/tcp_diag.c b/net/ipv4/tcp_diag.c
index a748c74aa8b7..abbf0edcf6c2 100644
--- a/net/ipv4/tcp_diag.c
+++ b/net/ipv4/tcp_diag.c
@@ -16,6 +16,7 @@
 
 #include 
 
+#include 
 #include 
 
 static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
@@ -36,6 +37,100 @@ static void tcp_diag_get_info(struct sock *sk, struct 
inet_diag_msg *r,
tcp_get_info(sk, info);
 }
 
+#ifdef CONFIG_TCP_MD5SIG
+static void tcp_diag_md5sig_fill(struct tcp_diag_md5sig *info,
+const struct tcp_md5sig_key *key)
+{
+   info->tcpm_family = key->family;
+   info->tcpm_prefixlen = key->prefixlen;
+   info->tcpm_keylen = key->keylen;
+   memcpy(info->tcpm_key, key->key, key->keylen);
+
+   if (key->family == AF_INET)
+   info->tcpm_addr[0] = key->addr.a4.s_addr;
+   #if IS_ENABLED(CONFIG_IPV6)
+   else if (key->family == AF_INET6)
+   memcpy(>tcpm_addr, >addr.a6,
+  sizeof(info->tcpm_addr));
+   #endif
+}
+
+static int tcp_diag_put_md5sig(struct sk_buff *skb,
+  const struct tcp_md5sig_info *md5sig)
+{
+   const struct tcp_md5sig_key *key;
+   struct tcp_diag_md5sig *info;
+   struct nlattr *attr;
+   int md5sig_count = 0;
+
+   hlist_for_each_entry_rcu(key, >head, node)
+   md5sig_count++;
+   if (md5sig_count == 0)
+   return 0;
+
+   attr = nla_reserve(skb, INET_DIAG_MD5SIG,
+  md5sig_count * sizeof(struct tcp_diag_md5sig));
+   if (!attr)
+   return -EMSGSIZE;
+
+   info = nla_data(attr);
+   memset(info, 0, md5sig_count * sizeof(struct tcp_diag_md5sig));
+   hlist_for_each_entry_rcu(key, >head, node) {
+   tcp_diag_md5sig_fill(info++, key);
+   if (--md5sig_count == 0)
+   break;
+   }
+
+   return 0;
+}
+#endif
+
+static int tcp_diag_get_aux(struct sock *sk, bool net_admin,
+   struct sk_buff *skb)
+{
+#ifdef CONFIG_TCP_MD5SIG
+   if (net_admin) {
+   struct tcp_md5sig_info *md5sig;
+   int err = 0;
+
+   rcu_read_lock();
+   md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info);
+   if (md5sig)
+   err = tcp_diag_put_md5sig(skb, md5sig);
+   rcu_read_unlock();
+   if (err < 0)
+   return err;
+   }
+#endif
+
+   return 0;
+}
+
+static size_t tcp_diag_get_aux_size(struct sock *sk, bool net_admin)
+{
+   size_t size = 0;
+
+#ifdef CONFIG_TCP_MD5SIG
+   if (net_admin && sk_fullsock(sk)) {
+   const struct tcp_md5sig_info *md5sig;
+   const struct tcp_md5sig_key *key;
+   size_t md5sig_count = 0;
+
+   rcu_read_lock();
+   md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info);
+   if (md5sig) {
+   hlist_for_each_entry_rcu(key, >head, node)
+   md5sig_count++;
+   }
+   rcu_read_unlock();
+   size += nla_total_size(md5sig_count *
+  sizeof(struct tcp_diag_md5sig));
+   }
+#endif
+
+   return size;
+}
+
 static void tcp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
  const struct inet_diag_req_v2 *r, struct nlattr *bc)
 {
@@ -68,13 +163,15 @@ st

[PATCH net-next v4 2/2] tcp_diag: report TCP MD5 signing keys and addresses

2017-08-30 Thread Ivan Delalande
Report TCP MD5 (RFC2385) signing keys, addresses and address prefixes to
processes with CAP_NET_ADMIN requesting INET_DIAG_INFO. Currently it is
not possible to retrieve these from the kernel once they have been
configured on sockets.

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/uapi/linux/inet_diag.h |   1 +
 include/uapi/linux/tcp.h   |   9 
 net/ipv4/tcp_diag.c| 110 ++---
 3 files changed, 114 insertions(+), 6 deletions(-)

diff --git a/include/uapi/linux/inet_diag.h b/include/uapi/linux/inet_diag.h
index 678496897a68..f52ff62bfabe 100644
--- a/include/uapi/linux/inet_diag.h
+++ b/include/uapi/linux/inet_diag.h
@@ -143,6 +143,7 @@ enum {
INET_DIAG_MARK,
INET_DIAG_BBRINFO,
INET_DIAG_CLASS_ID,
+   INET_DIAG_MD5SIG,
__INET_DIAG_MAX,
 };
 
diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
index 030e594bab45..15c25eccab2b 100644
--- a/include/uapi/linux/tcp.h
+++ b/include/uapi/linux/tcp.h
@@ -256,4 +256,13 @@ struct tcp_md5sig {
__u8tcpm_key[TCP_MD5SIG_MAXKEYLEN]; /* key (binary) */
 };
 
+/* INET_DIAG_MD5SIG */
+struct tcp_diag_md5sig {
+   __u8tcpm_family;
+   __u8tcpm_prefixlen;
+   __u16   tcpm_keylen;
+   __be32  tcpm_addr[4];
+   __u8tcpm_key[TCP_MD5SIG_MAXKEYLEN];
+};
+
 #endif /* _UAPI_LINUX_TCP_H */
diff --git a/net/ipv4/tcp_diag.c b/net/ipv4/tcp_diag.c
index a748c74aa8b7..65d0c34a76ee 100644
--- a/net/ipv4/tcp_diag.c
+++ b/net/ipv4/tcp_diag.c
@@ -16,6 +16,7 @@
 
 #include 
 
+#include 
 #include 
 
 static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
@@ -36,6 +37,101 @@ static void tcp_diag_get_info(struct sock *sk, struct 
inet_diag_msg *r,
tcp_get_info(sk, info);
 }
 
+#ifdef CONFIG_TCP_MD5SIG
+static void tcp_diag_md5sig_fill(struct tcp_diag_md5sig *info,
+const struct tcp_md5sig_key *key)
+{
+   info->tcpm_family = key->family;
+   info->tcpm_prefixlen = key->prefixlen;
+   info->tcpm_keylen = key->keylen;
+   memcpy(info->tcpm_key, key->key, key->keylen);
+
+   if (key->family == AF_INET) {
+   memset(info->tcpm_addr, 0, sizeof(info->tcpm_addr));
+   info->tcpm_addr[0] = key->addr.a4.s_addr;
+   }
+   #if IS_ENABLED(CONFIG_IPV6)
+   else if (key->family == AF_INET6) {
+   memcpy(>tcpm_addr, >addr.a6,
+  sizeof(info->tcpm_addr));
+   }
+   #endif
+}
+
+static int tcp_diag_put_md5sig(struct sk_buff *skb,
+  const struct tcp_md5sig_info *md5sig)
+{
+   const struct tcp_md5sig_key *key;
+   struct nlattr *attr;
+   struct tcp_diag_md5sig *info;
+   int md5sig_count = 0;
+
+   hlist_for_each_entry_rcu(key, >head, node)
+   md5sig_count++;
+   if (md5sig_count == 0)
+   return 0;
+
+   attr = nla_reserve(skb, INET_DIAG_MD5SIG,
+  md5sig_count * sizeof(struct tcp_diag_md5sig));
+   if (!attr)
+   return -EMSGSIZE;
+
+   info = nla_data(attr);
+   hlist_for_each_entry_rcu(key, >head, node) {
+   tcp_diag_md5sig_fill(info++, key);
+   if (--md5sig_count == 0)
+   break;
+   }
+   if (md5sig_count > 0)
+   memset(info, 0, md5sig_count * sizeof(struct tcp_diag_md5sig));
+
+   return 0;
+}
+#endif
+
+static int tcp_diag_get_aux(struct sock *sk, bool net_admin,
+   struct sk_buff *skb)
+{
+#ifdef CONFIG_TCP_MD5SIG
+   if (net_admin) {
+   struct tcp_md5sig_info *md5sig;
+   int err = 0;
+
+   rcu_read_lock();
+   md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info);
+   if (md5sig)
+   err = tcp_diag_put_md5sig(skb, md5sig);
+   rcu_read_unlock();
+   if (err < 0)
+   return err;
+   }
+#endif
+
+   return 0;
+}
+
+static size_t tcp_diag_get_aux_size(struct sock *sk, bool net_admin)
+{
+   size_t size = 0;
+
+#ifdef CONFIG_TCP_MD5SIG
+   if (sk_fullsock(sk)) {
+   const struct tcp_md5sig_info *md5sig;
+   const struct tcp_md5sig_key *key;
+
+   rcu_read_lock();
+   md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info);
+   if (md5sig) {
+   hlist_for_each_entry_rcu(key, >head, node)
+   size += sizeof(struct tcp_diag_md5sig);
+   }
+   rcu_read_unlock();
+   }
+#endif
+
+   return size;
+}
+
 static void tcp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
  const struct inet_diag_req_v2 *r, struct nlattr *bc)
 {
@@ -68,13 +164,15 @@ static int tcp_diag

[PATCH net-next v4 1/2] inet_diag: allow protocols to provide additional data

2017-08-30 Thread Ivan Delalande
Extend inet_diag_handler to allow individual protocols to report
additional data on INET_DIAG_INFO through idiag_get_aux. The size
can be dynamic and is computed by idiag_get_aux_size.

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/linux/inet_diag.h |  7 +++
 net/ipv4/inet_diag.c  | 22 ++
 2 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/include/linux/inet_diag.h b/include/linux/inet_diag.h
index 65da430e260f..ee251c585854 100644
--- a/include/linux/inet_diag.h
+++ b/include/linux/inet_diag.h
@@ -25,6 +25,13 @@ struct inet_diag_handler {
  struct inet_diag_msg *r,
  void *info);
 
+   int (*idiag_get_aux)(struct sock *sk,
+bool net_admin,
+struct sk_buff *skb);
+
+   size_t  (*idiag_get_aux_size)(struct sock *sk,
+ bool net_admin);
+
int (*destroy)(struct sk_buff *in_skb,
   const struct inet_diag_req_v2 *req);
 
diff --git a/net/ipv4/inet_diag.c b/net/ipv4/inet_diag.c
index 67325d5832d7..cb7012d1720f 100644
--- a/net/ipv4/inet_diag.c
+++ b/net/ipv4/inet_diag.c
@@ -93,8 +93,17 @@ void inet_diag_msg_common_fill(struct inet_diag_msg *r, 
struct sock *sk)
 }
 EXPORT_SYMBOL_GPL(inet_diag_msg_common_fill);
 
-static size_t inet_sk_attr_size(void)
+static size_t inet_sk_attr_size(struct sock *sk,
+   const struct inet_diag_req_v2 *req,
+   bool net_admin)
 {
+   const struct inet_diag_handler *handler;
+   size_t aux = 0;
+
+   handler = inet_diag_table[req->sdiag_protocol];
+   if (handler && handler->idiag_get_aux_size)
+   aux = handler->idiag_get_aux_size(sk, net_admin);
+
returnnla_total_size(sizeof(struct tcp_info))
+ nla_total_size(1) /* INET_DIAG_SHUTDOWN */
+ nla_total_size(1) /* INET_DIAG_TOS */
@@ -105,6 +114,7 @@ static size_t inet_sk_attr_size(void)
+ nla_total_size(SK_MEMINFO_VARS * sizeof(u32))
+ nla_total_size(TCP_CA_NAME_MAX)
+ nla_total_size(sizeof(struct tcpvegas_info))
+   + nla_total_size(aux)
+ 64;
 }
 
@@ -260,6 +270,10 @@ int inet_sk_diag_fill(struct sock *sk, struct 
inet_connection_sock *icsk,
 
handler->idiag_get_info(sk, r, info);
 
+   if (ext & (1 << (INET_DIAG_INFO - 1)) && handler->idiag_get_aux)
+   if (handler->idiag_get_aux(sk, net_admin, skb) < 0)
+   goto errout;
+
if (sk->sk_state < TCP_TIME_WAIT) {
union tcp_cc_info info;
size_t sz = 0;
@@ -449,6 +463,7 @@ int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
const struct nlmsghdr *nlh,
const struct inet_diag_req_v2 *req)
 {
+   bool net_admin = netlink_net_capable(in_skb, CAP_NET_ADMIN);
struct net *net = sock_net(in_skb->sk);
struct sk_buff *rep;
struct sock *sk;
@@ -458,7 +473,7 @@ int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
if (IS_ERR(sk))
return PTR_ERR(sk);
 
-   rep = nlmsg_new(inet_sk_attr_size(), GFP_KERNEL);
+   rep = nlmsg_new(inet_sk_attr_size(sk, req, net_admin), GFP_KERNEL);
if (!rep) {
err = -ENOMEM;
goto out;
@@ -467,8 +482,7 @@ int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
err = sk_diag_fill(sk, rep, req,
   sk_user_ns(NETLINK_CB(in_skb).sk),
   NETLINK_CB(in_skb).portid,
-  nlh->nlmsg_seq, 0, nlh,
-  netlink_net_capable(in_skb, CAP_NET_ADMIN));
+  nlh->nlmsg_seq, 0, nlh, net_admin);
if (err < 0) {
WARN_ON(err == -EMSGSIZE);
nlmsg_free(rep);
-- 
2.14.1



[PATCH net-next v4 0/2] report TCP MD5 signing keys and addresses

2017-08-30 Thread Ivan Delalande
Allow userspace to retrieve MD5 signature keys and addresses configured
on TCP sockets through inet_diag.

Thanks to Eric Dumazet and Stephen Hemminger for their useful
explanations and feedback.

v4: - add new struct tcp_diag_md5sig to report the data instead of
  tcp_md5sig to avoid wasting 112 bytes on every tcpm_addr,
- memset tcpm_addr on IPv4 addresses to avoid leaks,
- style fix in inet_diag_dump_one_icsk.

v3: - rename inet_diag_*md5sig in tcp_diag.c to tcp_diag_* for
  consistency,
- don't lock the socket in tcp_diag_put_md5sig,
- add checks on md5sig_count in tcp_diag_put_md5sig to not create
  the netlink attribute if the list is empty, and to avoid overflows
  or memory leaks if the list has changed in the meantime.

v2: - move changes to tcp_diag.c and extend inet_diag_handler to allow
  protocols to provide additional data on INET_DIAG_INFO,
- lock socket before calling tcp_diag_put_md5sig.


I also have a patch for iproute2/ss to test this change, making it print
this new attribute. I'm planning to polish and send it if this series
gets applied.


Ivan Delalande (2):
  inet_diag: allow protocols to provide additional data
  tcp_diag: report TCP MD5 signing keys and addresses

 include/linux/inet_diag.h  |   7 +++
 include/uapi/linux/inet_diag.h |   1 +
 include/uapi/linux/tcp.h   |   9 
 net/ipv4/inet_diag.c   |  22 +++--
 net/ipv4/tcp_diag.c| 110 ++---
 5 files changed, 139 insertions(+), 10 deletions(-)

-- 
2.14.1



[PATCH net-next v3 0/2] report TCP MD5 signing keys and addresses

2017-08-29 Thread Ivan Delalande
Allow userspace to retrieve MD5 signature keys and addresses configured
on TCP sockets through inet_diag.

Thank you Eric Dumazet for the useful explanations and feedback.

v3: - rename inet_diag_*md5sig in tcp_diag.c to tcp_diag_* for
  consistency,
- don't lock the socket tcp_diag_put_md5sig,
- add checks on md5sig_count in tcp_diag_put_md5sig to not create
  the netlink attribute if the list is empty, and to avoid overflows
  or memory leaks if the list has changed in the meantime.

v2: - move changes to tcp_diag.c and extend inet_diag_handler to allow
  protocols to provide additional data on INET_DIAG_INFO,
- lock socket before calling tcp_diag_put_md5sig.


I also have a patch for iproute2/ss to test this change, making it print
this new attribute. I'm planning to polish and send it if this series
gets applied.


Ivan Delalande (2):
  inet_diag: allow protocols to provide additional data
  tcp_diag: report TCP MD5 signing keys and addresses

 include/linux/inet_diag.h  |   7 +++
 include/uapi/linux/inet_diag.h |   1 +
 net/ipv4/inet_diag.c   |  22 ++--
 net/ipv4/tcp_diag.c| 115 ++---
 4 files changed, 135 insertions(+), 10 deletions(-)

-- 
2.14.1



[PATCH net-next v3 2/2] tcp_diag: report TCP MD5 signing keys and addresses

2017-08-29 Thread Ivan Delalande
Report TCP MD5 (RFC2385) signing keys, addresses and address prefixes to
processes with CAP_NET_ADMIN requesting INET_DIAG_INFO. Currently it is
not possible to retrieve these from the kernel once they have been
configured on sockets.

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/uapi/linux/inet_diag.h |   1 +
 net/ipv4/tcp_diag.c| 115 ++---
 2 files changed, 110 insertions(+), 6 deletions(-)

diff --git a/include/uapi/linux/inet_diag.h b/include/uapi/linux/inet_diag.h
index 678496897a68..f52ff62bfabe 100644
--- a/include/uapi/linux/inet_diag.h
+++ b/include/uapi/linux/inet_diag.h
@@ -143,6 +143,7 @@ enum {
INET_DIAG_MARK,
INET_DIAG_BBRINFO,
INET_DIAG_CLASS_ID,
+   INET_DIAG_MD5SIG,
__INET_DIAG_MAX,
 };
 
diff --git a/net/ipv4/tcp_diag.c b/net/ipv4/tcp_diag.c
index a748c74aa8b7..f972f9f7eae4 100644
--- a/net/ipv4/tcp_diag.c
+++ b/net/ipv4/tcp_diag.c
@@ -16,6 +16,7 @@
 
 #include 
 
+#include 
 #include 
 
 static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
@@ -36,6 +37,106 @@ static void tcp_diag_get_info(struct sock *sk, struct 
inet_diag_msg *r,
tcp_get_info(sk, info);
 }
 
+#ifdef CONFIG_TCP_MD5SIG
+static void inet_diag_md5sig_fill(struct tcp_md5sig *info,
+ const struct tcp_md5sig_key *key)
+{
+   #if IS_ENABLED(CONFIG_IPV6)
+   if (key->family == AF_INET6) {
+   struct sockaddr_in6 *sin6 =
+   (struct sockaddr_in6 *)>tcpm_addr;
+
+   memcpy(>sin6_addr, >addr.a6,
+  sizeof(struct in6_addr));
+   } else
+   #endif
+   {
+   struct sockaddr_in *sin =
+   (struct sockaddr_in *)>tcpm_addr;
+
+   memcpy(>sin_addr, >addr.a4, sizeof(struct in_addr));
+   }
+
+   info->tcpm_addr.ss_family = key->family;
+   info->tcpm_prefixlen = key->prefixlen;
+   info->tcpm_keylen = key->keylen;
+   memcpy(info->tcpm_key, key->key, key->keylen);
+}
+
+static int inet_diag_put_md5sig(struct sk_buff *skb,
+   const struct tcp_md5sig_info *md5sig)
+{
+   const struct tcp_md5sig_key *key;
+   struct nlattr *attr;
+   struct tcp_md5sig *info;
+   int md5sig_count = 0;
+
+   hlist_for_each_entry_rcu(key, >head, node)
+   md5sig_count++;
+   if (md5sig_count == 0)
+   return 0;
+
+   attr = nla_reserve(skb, INET_DIAG_MD5SIG,
+  md5sig_count * sizeof(struct tcp_md5sig));
+   if (!attr)
+   return -EMSGSIZE;
+
+   info = nla_data(attr);
+   hlist_for_each_entry_rcu(key, >head, node) {
+   inet_diag_md5sig_fill(info++, key);
+   if (--md5sig_count == 0)
+   break;
+   }
+   if (md5sig_count > 0)
+   memset(info, 0, md5sig_count * sizeof(struct tcp_md5sig));
+
+   return 0;
+}
+#endif
+
+static int tcp_diag_get_aux(struct sock *sk, bool net_admin,
+   struct sk_buff *skb)
+{
+#ifdef CONFIG_TCP_MD5SIG
+   if (net_admin) {
+   struct tcp_md5sig_info *md5sig;
+   int err = 0;
+
+   rcu_read_lock();
+   md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info);
+   if (md5sig)
+   err = inet_diag_put_md5sig(skb, md5sig);
+   rcu_read_unlock();
+   if (err < 0)
+   return err;
+   }
+#endif
+
+   return 0;
+}
+
+static size_t tcp_diag_get_aux_size(struct sock *sk, bool net_admin)
+{
+   size_t size = 0;
+
+#ifdef CONFIG_TCP_MD5SIG
+   if (sk_fullsock(sk)) {
+   const struct tcp_md5sig_info *md5sig;
+   const struct tcp_md5sig_key *key;
+
+   rcu_read_lock();
+   md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info);
+   if (md5sig) {
+   hlist_for_each_entry_rcu(key, >head, node)
+   size += sizeof(struct tcp_md5sig);
+   }
+   rcu_read_unlock();
+   }
+#endif
+
+   return size;
+}
+
 static void tcp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
  const struct inet_diag_req_v2 *r, struct nlattr *bc)
 {
@@ -68,13 +169,15 @@ static int tcp_diag_destroy(struct sk_buff *in_skb,
 #endif
 
 static const struct inet_diag_handler tcp_diag_handler = {
-   .dump= tcp_diag_dump,
-   .dump_one= tcp_diag_dump_one,
-   .idiag_get_info  = tcp_diag_get_info,
-   .idiag_type  = IPPROTO_TCP,
-   .idiag_info_size = sizeof(struct tcp_info),
+   .dump   = tcp_diag_dump,
+   .dump_one   = tcp_diag_dump_one,
+   .idiag_get_info = tcp_diag_get_info,
+   

[PATCH net-next v3 1/2] inet_diag: allow protocols to provide additional data

2017-08-29 Thread Ivan Delalande
Extend inet_diag_handler to allow individual protocols to report
additional data on INET_DIAG_INFO through idiag_get_aux. The size
can be dynamic and is computed by idiag_get_aux_size.

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/linux/inet_diag.h |  7 +++
 net/ipv4/inet_diag.c  | 22 ++
 2 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/include/linux/inet_diag.h b/include/linux/inet_diag.h
index 65da430e260f..ee251c585854 100644
--- a/include/linux/inet_diag.h
+++ b/include/linux/inet_diag.h
@@ -25,6 +25,13 @@ struct inet_diag_handler {
  struct inet_diag_msg *r,
  void *info);
 
+   int (*idiag_get_aux)(struct sock *sk,
+bool net_admin,
+struct sk_buff *skb);
+
+   size_t  (*idiag_get_aux_size)(struct sock *sk,
+ bool net_admin);
+
int (*destroy)(struct sk_buff *in_skb,
   const struct inet_diag_req_v2 *req);
 
diff --git a/net/ipv4/inet_diag.c b/net/ipv4/inet_diag.c
index 67325d5832d7..8a88ef373395 100644
--- a/net/ipv4/inet_diag.c
+++ b/net/ipv4/inet_diag.c
@@ -93,8 +93,17 @@ void inet_diag_msg_common_fill(struct inet_diag_msg *r, 
struct sock *sk)
 }
 EXPORT_SYMBOL_GPL(inet_diag_msg_common_fill);
 
-static size_t inet_sk_attr_size(void)
+static size_t inet_sk_attr_size(struct sock *sk,
+   const struct inet_diag_req_v2 *req,
+   bool net_admin)
 {
+   const struct inet_diag_handler *handler;
+   size_t aux = 0;
+
+   handler = inet_diag_table[req->sdiag_protocol];
+   if (handler && handler->idiag_get_aux_size)
+   aux = handler->idiag_get_aux_size(sk, net_admin);
+
returnnla_total_size(sizeof(struct tcp_info))
+ nla_total_size(1) /* INET_DIAG_SHUTDOWN */
+ nla_total_size(1) /* INET_DIAG_TOS */
@@ -105,6 +114,7 @@ static size_t inet_sk_attr_size(void)
+ nla_total_size(SK_MEMINFO_VARS * sizeof(u32))
+ nla_total_size(TCP_CA_NAME_MAX)
+ nla_total_size(sizeof(struct tcpvegas_info))
+   + nla_total_size(aux)
+ 64;
 }
 
@@ -260,6 +270,10 @@ int inet_sk_diag_fill(struct sock *sk, struct 
inet_connection_sock *icsk,
 
handler->idiag_get_info(sk, r, info);
 
+   if (ext & (1 << (INET_DIAG_INFO - 1)) && handler->idiag_get_aux)
+   if (handler->idiag_get_aux(sk, net_admin, skb) < 0)
+   goto errout;
+
if (sk->sk_state < TCP_TIME_WAIT) {
union tcp_cc_info info;
size_t sz = 0;
@@ -452,13 +466,14 @@ int inet_diag_dump_one_icsk(struct inet_hashinfo 
*hashinfo,
struct net *net = sock_net(in_skb->sk);
struct sk_buff *rep;
struct sock *sk;
+   bool net_admin = netlink_net_capable(in_skb, CAP_NET_ADMIN);
int err;
 
sk = inet_diag_find_one_icsk(net, hashinfo, req);
if (IS_ERR(sk))
return PTR_ERR(sk);
 
-   rep = nlmsg_new(inet_sk_attr_size(), GFP_KERNEL);
+   rep = nlmsg_new(inet_sk_attr_size(sk, req, net_admin), GFP_KERNEL);
if (!rep) {
err = -ENOMEM;
goto out;
@@ -467,8 +482,7 @@ int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
err = sk_diag_fill(sk, rep, req,
   sk_user_ns(NETLINK_CB(in_skb).sk),
   NETLINK_CB(in_skb).portid,
-  nlh->nlmsg_seq, 0, nlh,
-  netlink_net_capable(in_skb, CAP_NET_ADMIN));
+  nlh->nlmsg_seq, 0, nlh, net_admin);
if (err < 0) {
WARN_ON(err == -EMSGSIZE);
nlmsg_free(rep);
-- 
2.14.1



Re: [PATCH net-next v2 2/2] tcp_diag: report TCP MD5 signing keys and addresses

2017-08-25 Thread Ivan Delalande
On Fri, Aug 25, 2017 at 08:41:25PM -0700, Eric Dumazet wrote:
> On Fri, 2017-08-25 at 18:53 -0700, Ivan Delalande wrote:
> > Report TCP MD5 (RFC2385) signing keys, addresses and address prefixes to
> > processes with CAP_NET_ADMIN requesting INET_DIAG_INFO. Currently it is
> > not possible to retrieve these from the kernel once they have been
> > configured on sockets.
> 
> ...
> 
> > +static int inet_diag_put_md5sig(struct sk_buff *skb,
> > +   const struct tcp_md5sig_info *md5sig)
> > +{
> > +   const struct tcp_md5sig_key *key;
> > +   struct nlattr *attr;
> > +   struct tcp_md5sig *info;
> > +   int md5sig_count = 0;
> > +
> > +   hlist_for_each_entry_rcu(key, >head, node)
> > +   md5sig_count++;
> > +
> > +   attr = nla_reserve(skb, INET_DIAG_MD5SIG,
> > +  md5sig_count * sizeof(struct tcp_md5sig));
> > +   if (!attr)
> > +   return -EMSGSIZE;
> > +
> > +   info = nla_data(attr);
> > +   hlist_for_each_entry_rcu(key, >head, node) {
> > +   inet_diag_md5sig_fill(info, key);
> > +   info++;
> > +   }
> > +
> > +   return 0;
> > +}
> > +#endif
> 
> Unless I missed something, I am sure I gave a feedback on this function
> already :/

Sorry, I probably should have detailed my changes. I tried to address
this by locking the whole socket in the caller, tcp_diag_get_aux, just
outside of the rcu_read_lock. Would this work here, or do you see a
better way?

Thanks for your feedback,
-- 
Ivan Delalande
Arista Networks


[PATCH net-next v2 1/2] inet_diag: allow protocols to provide additional data

2017-08-25 Thread Ivan Delalande
Extend inet_diag_handler to allow individual protocols to report
additional data on INET_DIAG_INFO through idiag_get_aux. The size
can be dynamic and is computed by idiag_get_aux_size.

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/linux/inet_diag.h |  7 +++
 net/ipv4/inet_diag.c  | 22 ++
 2 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/include/linux/inet_diag.h b/include/linux/inet_diag.h
index 65da430e260f..ee251c585854 100644
--- a/include/linux/inet_diag.h
+++ b/include/linux/inet_diag.h
@@ -25,6 +25,13 @@ struct inet_diag_handler {
  struct inet_diag_msg *r,
  void *info);
 
+   int (*idiag_get_aux)(struct sock *sk,
+bool net_admin,
+struct sk_buff *skb);
+
+   size_t  (*idiag_get_aux_size)(struct sock *sk,
+ bool net_admin);
+
int (*destroy)(struct sk_buff *in_skb,
   const struct inet_diag_req_v2 *req);
 
diff --git a/net/ipv4/inet_diag.c b/net/ipv4/inet_diag.c
index 67325d5832d7..8a88ef373395 100644
--- a/net/ipv4/inet_diag.c
+++ b/net/ipv4/inet_diag.c
@@ -93,8 +93,17 @@ void inet_diag_msg_common_fill(struct inet_diag_msg *r, 
struct sock *sk)
 }
 EXPORT_SYMBOL_GPL(inet_diag_msg_common_fill);
 
-static size_t inet_sk_attr_size(void)
+static size_t inet_sk_attr_size(struct sock *sk,
+   const struct inet_diag_req_v2 *req,
+   bool net_admin)
 {
+   const struct inet_diag_handler *handler;
+   size_t aux = 0;
+
+   handler = inet_diag_table[req->sdiag_protocol];
+   if (handler && handler->idiag_get_aux_size)
+   aux = handler->idiag_get_aux_size(sk, net_admin);
+
returnnla_total_size(sizeof(struct tcp_info))
+ nla_total_size(1) /* INET_DIAG_SHUTDOWN */
+ nla_total_size(1) /* INET_DIAG_TOS */
@@ -105,6 +114,7 @@ static size_t inet_sk_attr_size(void)
+ nla_total_size(SK_MEMINFO_VARS * sizeof(u32))
+ nla_total_size(TCP_CA_NAME_MAX)
+ nla_total_size(sizeof(struct tcpvegas_info))
+   + nla_total_size(aux)
+ 64;
 }
 
@@ -260,6 +270,10 @@ int inet_sk_diag_fill(struct sock *sk, struct 
inet_connection_sock *icsk,
 
handler->idiag_get_info(sk, r, info);
 
+   if (ext & (1 << (INET_DIAG_INFO - 1)) && handler->idiag_get_aux)
+   if (handler->idiag_get_aux(sk, net_admin, skb) < 0)
+   goto errout;
+
if (sk->sk_state < TCP_TIME_WAIT) {
union tcp_cc_info info;
size_t sz = 0;
@@ -452,13 +466,14 @@ int inet_diag_dump_one_icsk(struct inet_hashinfo 
*hashinfo,
struct net *net = sock_net(in_skb->sk);
struct sk_buff *rep;
struct sock *sk;
+   bool net_admin = netlink_net_capable(in_skb, CAP_NET_ADMIN);
int err;
 
sk = inet_diag_find_one_icsk(net, hashinfo, req);
if (IS_ERR(sk))
return PTR_ERR(sk);
 
-   rep = nlmsg_new(inet_sk_attr_size(), GFP_KERNEL);
+   rep = nlmsg_new(inet_sk_attr_size(sk, req, net_admin), GFP_KERNEL);
if (!rep) {
err = -ENOMEM;
goto out;
@@ -467,8 +482,7 @@ int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
err = sk_diag_fill(sk, rep, req,
   sk_user_ns(NETLINK_CB(in_skb).sk),
   NETLINK_CB(in_skb).portid,
-  nlh->nlmsg_seq, 0, nlh,
-  netlink_net_capable(in_skb, CAP_NET_ADMIN));
+  nlh->nlmsg_seq, 0, nlh, net_admin);
if (err < 0) {
WARN_ON(err == -EMSGSIZE);
nlmsg_free(rep);
-- 
2.14.1



[PATCH net-next v2 2/2] tcp_diag: report TCP MD5 signing keys and addresses

2017-08-25 Thread Ivan Delalande
Report TCP MD5 (RFC2385) signing keys, addresses and address prefixes to
processes with CAP_NET_ADMIN requesting INET_DIAG_INFO. Currently it is
not possible to retrieve these from the kernel once they have been
configured on sockets.

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/uapi/linux/inet_diag.h |   1 +
 net/ipv4/tcp_diag.c| 112 ++---
 2 files changed, 107 insertions(+), 6 deletions(-)

diff --git a/include/uapi/linux/inet_diag.h b/include/uapi/linux/inet_diag.h
index 678496897a68..f52ff62bfabe 100644
--- a/include/uapi/linux/inet_diag.h
+++ b/include/uapi/linux/inet_diag.h
@@ -143,6 +143,7 @@ enum {
INET_DIAG_MARK,
INET_DIAG_BBRINFO,
INET_DIAG_CLASS_ID,
+   INET_DIAG_MD5SIG,
__INET_DIAG_MAX,
 };
 
diff --git a/net/ipv4/tcp_diag.c b/net/ipv4/tcp_diag.c
index a748c74aa8b7..99c54b765921 100644
--- a/net/ipv4/tcp_diag.c
+++ b/net/ipv4/tcp_diag.c
@@ -16,6 +16,7 @@
 
 #include 
 
+#include 
 #include 
 
 static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r,
@@ -36,6 +37,103 @@ static void tcp_diag_get_info(struct sock *sk, struct 
inet_diag_msg *r,
tcp_get_info(sk, info);
 }
 
+#ifdef CONFIG_TCP_MD5SIG
+static void inet_diag_md5sig_fill(struct tcp_md5sig *info,
+ const struct tcp_md5sig_key *key)
+{
+   #if IS_ENABLED(CONFIG_IPV6)
+   if (key->family == AF_INET6) {
+   struct sockaddr_in6 *sin6 =
+   (struct sockaddr_in6 *)>tcpm_addr;
+
+   memcpy(>sin6_addr, >addr.a6,
+  sizeof(struct in6_addr));
+   } else
+   #endif
+   {
+   struct sockaddr_in *sin =
+   (struct sockaddr_in *)>tcpm_addr;
+
+   memcpy(>sin_addr, >addr.a4, sizeof(struct in_addr));
+   }
+
+   info->tcpm_addr.ss_family = key->family;
+   info->tcpm_prefixlen = key->prefixlen;
+   info->tcpm_keylen = key->keylen;
+   memcpy(info->tcpm_key, key->key, key->keylen);
+}
+
+static int inet_diag_put_md5sig(struct sk_buff *skb,
+   const struct tcp_md5sig_info *md5sig)
+{
+   const struct tcp_md5sig_key *key;
+   struct nlattr *attr;
+   struct tcp_md5sig *info;
+   int md5sig_count = 0;
+
+   hlist_for_each_entry_rcu(key, >head, node)
+   md5sig_count++;
+
+   attr = nla_reserve(skb, INET_DIAG_MD5SIG,
+  md5sig_count * sizeof(struct tcp_md5sig));
+   if (!attr)
+   return -EMSGSIZE;
+
+   info = nla_data(attr);
+   hlist_for_each_entry_rcu(key, >head, node) {
+   inet_diag_md5sig_fill(info, key);
+   info++;
+   }
+
+   return 0;
+}
+#endif
+
+static int tcp_diag_get_aux(struct sock *sk, bool net_admin,
+   struct sk_buff *skb)
+{
+#ifdef CONFIG_TCP_MD5SIG
+   if (net_admin) {
+   struct tcp_md5sig_info *md5sig;
+   int err = 0;
+
+   lock_sock(sk);
+   rcu_read_lock();
+   md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info);
+   if (md5sig)
+   err = inet_diag_put_md5sig(skb, md5sig);
+   rcu_read_unlock();
+   release_sock(sk);
+   if (err < 0)
+   return err;
+   }
+#endif
+
+   return 0;
+}
+
+static size_t tcp_diag_get_aux_size(struct sock *sk, bool net_admin)
+{
+   size_t size = 0;
+
+#ifdef CONFIG_TCP_MD5SIG
+   if (sk_fullsock(sk)) {
+   const struct tcp_md5sig_info *md5sig;
+   const struct tcp_md5sig_key *key;
+
+   rcu_read_lock();
+   md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info);
+   if (md5sig) {
+   hlist_for_each_entry_rcu(key, >head, node)
+   size += sizeof(struct tcp_md5sig);
+   }
+   rcu_read_unlock();
+   }
+#endif
+
+   return size;
+}
+
 static void tcp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
  const struct inet_diag_req_v2 *r, struct nlattr *bc)
 {
@@ -68,13 +166,15 @@ static int tcp_diag_destroy(struct sk_buff *in_skb,
 #endif
 
 static const struct inet_diag_handler tcp_diag_handler = {
-   .dump= tcp_diag_dump,
-   .dump_one= tcp_diag_dump_one,
-   .idiag_get_info  = tcp_diag_get_info,
-   .idiag_type  = IPPROTO_TCP,
-   .idiag_info_size = sizeof(struct tcp_info),
+   .dump   = tcp_diag_dump,
+   .dump_one   = tcp_diag_dump_one,
+   .idiag_get_info = tcp_diag_get_info,
+   .idiag_get_aux  = tcp_diag_get_aux,
+   .idiag_get_aux_size = tcp_diag_get_aux_size,
+   .idiag_type = IPPROTO

[PATCH net-next] inet_diag: report TCP MD5 signing keys and addresses

2017-08-23 Thread Ivan Delalande
Report TCP MD5 (RFC2385) signing keys, addresses and address prefixes to
processes with CAP_NET_ADMIN requesting INET_DIAG_INFO. Currently it is
not possible to retrieve these from the kernel once they have been
configured on sockets.

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/uapi/linux/inet_diag.h |   1 +
 net/ipv4/inet_diag.c   | 108 +++--
 2 files changed, 105 insertions(+), 4 deletions(-)

diff --git a/include/uapi/linux/inet_diag.h b/include/uapi/linux/inet_diag.h
index 678496897a68..f52ff62bfabe 100644
--- a/include/uapi/linux/inet_diag.h
+++ b/include/uapi/linux/inet_diag.h
@@ -143,6 +143,7 @@ enum {
INET_DIAG_MARK,
INET_DIAG_BBRINFO,
INET_DIAG_CLASS_ID,
+   INET_DIAG_MD5SIG,
__INET_DIAG_MAX,
 };
 
diff --git a/net/ipv4/inet_diag.c b/net/ipv4/inet_diag.c
index 67325d5832d7..81bacf1d8da6 100644
--- a/net/ipv4/inet_diag.c
+++ b/net/ipv4/inet_diag.c
@@ -93,8 +93,27 @@ void inet_diag_msg_common_fill(struct inet_diag_msg *r, 
struct sock *sk)
 }
 EXPORT_SYMBOL_GPL(inet_diag_msg_common_fill);
 
-static size_t inet_sk_attr_size(void)
+static size_t inet_sk_attr_size(struct sock *sp)
 {
+#ifdef CONFIG_TCP_MD5SIG
+   const struct tcp_md5sig_info *md5sig;
+   const struct tcp_md5sig_key *key;
+   int md5sig_count = 0;
+
+   if (sp->sk_state == TCP_TIME_WAIT) {
+   if (tcp_twsk(sp)->tw_md5_key)
+   md5sig_count = 1;
+   } else {
+   rcu_read_lock();
+   md5sig = rcu_dereference(tcp_sk(sp)->md5sig_info);
+   if (md5sig) {
+   hlist_for_each_entry_rcu(key, >head, node)
+   md5sig_count++;
+   }
+   rcu_read_unlock();
+   }
+#endif
+
returnnla_total_size(sizeof(struct tcp_info))
+ nla_total_size(1) /* INET_DIAG_SHUTDOWN */
+ nla_total_size(1) /* INET_DIAG_TOS */
@@ -105,6 +124,9 @@ static size_t inet_sk_attr_size(void)
+ nla_total_size(SK_MEMINFO_VARS * sizeof(u32))
+ nla_total_size(TCP_CA_NAME_MAX)
+ nla_total_size(sizeof(struct tcpvegas_info))
+#ifdef CONFIG_TCP_MD5SIG
+   + nla_total_size(md5sig_count * sizeof(struct tcp_md5sig))
+#endif
+ 64;
 }
 
@@ -150,6 +172,58 @@ int inet_diag_msg_attrs_fill(struct sock *sk, struct 
sk_buff *skb,
 }
 EXPORT_SYMBOL_GPL(inet_diag_msg_attrs_fill);
 
+#ifdef CONFIG_TCP_MD5SIG
+static void inet_diag_md5sig_fill(struct tcp_md5sig *info,
+ const struct tcp_md5sig_key *key)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+   if (key->family == AF_INET6) {
+   struct sockaddr_in6 *sin6 =
+   (struct sockaddr_in6 *)>tcpm_addr;
+
+   memcpy(>sin6_addr, >addr.a6,
+  sizeof(struct in6_addr));
+   } else
+#endif
+   {
+   struct sockaddr_in *sin =
+   (struct sockaddr_in *)>tcpm_addr;
+
+   memcpy(>sin_addr, >addr.a4, sizeof(struct in_addr));
+   }
+
+   info->tcpm_addr.ss_family = key->family;
+   info->tcpm_prefixlen = key->prefixlen;
+   info->tcpm_keylen = key->keylen;
+   memcpy(info->tcpm_key, key->key, key->keylen);
+}
+
+static int inet_diag_put_md5sig(struct sk_buff *skb,
+   const struct tcp_md5sig_info *md5sig)
+{
+   const struct tcp_md5sig_key *key;
+   struct nlattr *attr;
+   struct tcp_md5sig *info = NULL;
+   int md5sig_count = 0;
+
+   hlist_for_each_entry_rcu(key, >head, node)
+   md5sig_count++;
+
+   attr = nla_reserve(skb, INET_DIAG_MD5SIG,
+  md5sig_count * sizeof(struct tcp_md5sig));
+   if (!attr)
+   return -EMSGSIZE;
+
+   info = nla_data(attr);
+   hlist_for_each_entry_rcu(key, >head, node) {
+   inet_diag_md5sig_fill(info, key);
+   info++;
+   }
+
+   return 0;
+}
+#endif
+
 int inet_sk_diag_fill(struct sock *sk, struct inet_connection_sock *icsk,
  struct sk_buff *skb, const struct inet_diag_req_v2 *req,
  struct user_namespace *user_ns,
@@ -260,6 +334,21 @@ int inet_sk_diag_fill(struct sock *sk, struct 
inet_connection_sock *icsk,
 
handler->idiag_get_info(sk, r, info);
 
+#ifdef CONFIG_TCP_MD5SIG
+   if ((ext & (1 << (INET_DIAG_INFO - 1))) && net_admin) {
+   struct tcp_md5sig_info *md5sig;
+   int err = 0;
+
+   rcu_read_lock();
+   md5sig = rcu_dereference(tcp_sk(sk)->md5sig_info);
+   if (md5sig)
+   err = inet_diag_put_md5sig(skb, md5sig);
+   rcu_read_unlock();
+   if (err < 0)
+   goto errout;
+  

[PATCH 1/2] tcp: add mode parameter to tcp_proc_register

2017-06-22 Thread Ivan Delalande
This will be used to create a proc file that regular users cannot read.

Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/net/tcp.h   | 3 ++-
 net/ipv4/tcp_ipv4.c | 7 ---
 net/ipv6/tcp_ipv6.c | 2 +-
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 575f95cb8275..5d78f9af309e 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1753,7 +1753,8 @@ struct tcp_iter_state {
loff_t  last_pos;
 };
 
-int tcp_proc_register(struct net *net, struct tcp_seq_afinfo *afinfo);
+int tcp_proc_register(struct net *net, struct tcp_seq_afinfo *afinfo,
+ umode_t mode);
 void tcp_proc_unregister(struct net *net, struct tcp_seq_afinfo *afinfo);
 
 extern struct request_sock_ops tcp_request_sock_ops;
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 81d6c16aecdc..0ae3d7cd59a3 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -2221,7 +2221,8 @@ int tcp_seq_open(struct inode *inode, struct file *file)
 }
 EXPORT_SYMBOL(tcp_seq_open);
 
-int tcp_proc_register(struct net *net, struct tcp_seq_afinfo *afinfo)
+int tcp_proc_register(struct net *net, struct tcp_seq_afinfo *afinfo,
+ umode_t mode)
 {
int rc = 0;
struct proc_dir_entry *p;
@@ -2230,7 +2231,7 @@ int tcp_proc_register(struct net *net, struct 
tcp_seq_afinfo *afinfo)
afinfo->seq_ops.next= tcp_seq_next;
afinfo->seq_ops.stop= tcp_seq_stop;
 
-   p = proc_create_data(afinfo->name, S_IRUGO, net->proc_net,
+   p = proc_create_data(afinfo->name, mode, net->proc_net,
 afinfo->seq_fops, afinfo);
if (!p)
rc = -ENOMEM;
@@ -2396,7 +2397,7 @@ static struct tcp_seq_afinfo tcp4_seq_afinfo = {
 
 static int __net_init tcp4_proc_init_net(struct net *net)
 {
-   return tcp_proc_register(net, _seq_afinfo);
+   return tcp_proc_register(net, _seq_afinfo, S_IRUGO);
 }
 
 static void __net_exit tcp4_proc_exit_net(struct net *net)
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index ae36442786ec..d97d6627666f 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -1888,7 +1888,7 @@ static struct tcp_seq_afinfo tcp6_seq_afinfo = {
 
 int __net_init tcp6_proc_init(struct net *net)
 {
-   return tcp_proc_register(net, _seq_afinfo);
+   return tcp_proc_register(net, _seq_afinfo, S_IRUGO);
 }
 
 void tcp6_proc_exit(struct net *net)
-- 
2.13.1



[PATCH 2/2] tcp: md5: export all configured signature keys in /proc/net

2017-06-22 Thread Ivan Delalande
Add files "tcpmd5" and "tcp6md5" in /proc/net containing all the TCP
MD5 keys configured for sockets using this signature option (RFC2385).
These files contain a line for each key configured on each socket, with
the index number of the socket (as found in /proc/net/tcp{,6}), its
inode number, the address, prefix length and the key itself.

Note that IPv4-mapped IPv6 addresses will be printed as a regular IPv4
address in the tcp6md5 file.

Signed-off-by: Ani Sinha <a...@arista.com>
Signed-off-by: Ken Kofman <kkof...@arista.com>
Signed-off-by: Ivan Delalande <col...@arista.com>
---
 Documentation/filesystems/proc.txt |  2 ++
 include/net/tcp.h  |  1 +
 net/ipv4/tcp.c | 55 ++
 net/ipv4/tcp_ipv4.c| 29 +++-
 net/ipv6/tcp_ipv6.c| 29 +++-
 5 files changed, 114 insertions(+), 2 deletions(-)

diff --git a/Documentation/filesystems/proc.txt 
b/Documentation/filesystems/proc.txt
index 4cddbce85ac9..d52a03b2e534 100644
--- a/Documentation/filesystems/proc.txt
+++ b/Documentation/filesystems/proc.txt
@@ -1105,6 +1105,7 @@ Table 1-8: IPv6 info in /proc/net
  File   Content   
  udp6   UDP sockets (IPv6)
  tcp6   TCP sockets (IPv6)
+ tcp6md5MD5 signature keys configured on IPv6 TCP sockets
  raw6   Raw device statistics (IPv6)  
  igmp6  IP multicast addresses, which this host joined (IPv6) 
  if_inet6   List of IPv6 interface addresses  
@@ -1136,6 +1137,7 @@ Table 1-9: Network info in /proc/net
  snmp  SNMP data   
  sockstat  Socket statistics   
  tcp   TCP  sockets
+ tcpmd5MD5 signature keys configured on IPv4 TCP sockets
  udp   UDP sockets 
  unix  UNIX domain sockets 
  wireless  Wireless interface data (Wavelan etc)   
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 5d78f9af309e..95c9dc47e0c5 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1450,6 +1450,7 @@ struct tcp_md5sig_key *tcp_v4_md5_lookup(const struct 
sock *sk,
 struct tcp_md5sig_key *tcp_md5_do_lookup(const struct sock *sk,
 const union tcp_md5_addr *addr,
 int family);
+int tcp_md5_seq_show(struct seq_file *seq, void *v);
 #define tcp_twsk_md5_key(twsk) ((twsk)->tw_md5_key)
 #else
 static inline struct tcp_md5sig_key *tcp_md5_do_lookup(const struct sock *sk,
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 2a68221d2e55..47bcaeed3605 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -3279,6 +3279,61 @@ int tcp_md5_hash_key(struct tcp_md5sig_pool *hp, const 
struct tcp_md5sig_key *ke
 }
 EXPORT_SYMBOL(tcp_md5_hash_key);
 
+static void do_md5_seq_print_key(struct seq_file *seq,
+const struct tcp_iter_state *st,
+const struct tcp_md5sig_key *key,
+unsigned long ino)
+{
+   if (key->keylen == 0)
+   return;
+
+   seq_printf(seq, "%4d: %6lu ", st->num, ino);
+   if (key->family == AF_INET)
+   seq_printf(seq, "%39pI4/%-3u ", >addr.a4, key->prefixlen);
+   else
+   seq_printf(seq, "%39pI6c/%-3u ", >addr.a6, key->prefixlen);
+   seq_printf(seq, "%*pE\n", key->keylen, key->key);
+}
+
+int tcp_md5_seq_show(struct seq_file *seq, void *v)
+{
+   struct sock *sp = v;
+   const struct tcp_sock *tp = tcp_sk(sp);
+   const struct tcp_iter_state *st = seq->private;
+   const struct tcp_md5sig_info *md5sig;
+   const struct tcp_md5sig_key *key;
+   unsigned long ino;
+
+   if (v == SEQ_START_TOKEN) {
+   seq_puts(seq, "  sl  inode  addr
key\n");
+   goto out;
+   }
+
+   if (sp->sk_state == TCP_TIME_WAIT) {
+   const struct tcp_timewait_sock *tcptw = tcp_twsk(sp);
+
+   key = tcptw->tw_md5_key;
+   if (key)
+   do_md5_seq_print_key(seq, st, key, 0);
+   goto out;
+   }
+
+   ino = sock_i_ino(sp);
+   rcu_read_lock();
+   md5sig = rcu_dereference(tp->md5sig_info);
+   if (!md5sig)
+   goto out_unlock;
+
+   hlist_for_each_entry_rcu(key, >head, node) {
+   do_md5_seq_print_key(seq, st, key, ino);
+   }
+
+out_unlock:
+   rcu_read_unlock();
+out

[PATCH v3 1/2] tcp: md5: add an address prefix for key lookup

2017-06-15 Thread Ivan Delalande
This allows the keys used for TCP MD5 signature to be used for whole
range of addresses, specified with a prefix length, instead of only one
address as it currently is.

Signed-off-by: Bob Gilligan <gilli...@arista.com>
Signed-off-by: Eric Mowat <mo...@arista.com>
Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/net/tcp.h   |  6 +++--
 net/ipv4/tcp_ipv4.c | 68 ++---
 net/ipv6/tcp_ipv6.c | 12 ++
 3 files changed, 70 insertions(+), 16 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 38a7427ae902..2b68023ab095 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1395,6 +1395,7 @@ struct tcp_md5sig_key {
u8  keylen;
u8  family; /* AF_INET or AF_INET6 */
union tcp_md5_addr  addr;
+   u8  prefixlen;
u8  key[TCP_MD5SIG_MAXKEYLEN];
struct rcu_head rcu;
 };
@@ -1438,9 +1439,10 @@ struct tcp_md5sig_pool {
 int tcp_v4_md5_hash_skb(char *md5_hash, const struct tcp_md5sig_key *key,
const struct sock *sk, const struct sk_buff *skb);
 int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr,
-  int family, const u8 *newkey, u8 newkeylen, gfp_t gfp);
+  int family, u8 prefixlen, const u8 *newkey, u8 newkeylen,
+  gfp_t gfp);
 int tcp_md5_do_del(struct sock *sk, const union tcp_md5_addr *addr,
-  int family);
+  int family, u8 prefixlen);
 struct tcp_md5sig_key *tcp_v4_md5_lookup(const struct sock *sk,
 const struct sock *addr_sk);
 
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 5ab2aac5ca19..51ca3bd5a8a3 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -80,6 +80,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include 
 #include 
@@ -906,6 +907,9 @@ struct tcp_md5sig_key *tcp_md5_do_lookup(const struct sock 
*sk,
struct tcp_md5sig_key *key;
unsigned int size = sizeof(struct in_addr);
const struct tcp_md5sig_info *md5sig;
+   __be32 mask;
+   struct tcp_md5sig_key *best_match = NULL;
+   bool match;
 
/* caller either holds rcu_read_lock() or socket lock */
md5sig = rcu_dereference_check(tp->md5sig_info,
@@ -919,12 +923,55 @@ struct tcp_md5sig_key *tcp_md5_do_lookup(const struct 
sock *sk,
hlist_for_each_entry_rcu(key, >head, node) {
if (key->family != family)
continue;
-   if (!memcmp(>addr, addr, size))
+
+   if (family == AF_INET) {
+   mask = inet_make_mask(key->prefixlen);
+   match = (key->addr.a4.s_addr & mask) ==
+   (addr->a4.s_addr & mask);
+#if IS_ENABLED(CONFIG_IPV6)
+   } else if (family == AF_INET6) {
+   match = ipv6_prefix_equal(>addr.a6, >a6,
+ key->prefixlen);
+#endif
+   } else {
+   match = false;
+   }
+
+   if (match && (!best_match ||
+ key->prefixlen > best_match->prefixlen))
+   best_match = key;
+   }
+   return best_match;
+}
+EXPORT_SYMBOL(tcp_md5_do_lookup);
+
+struct tcp_md5sig_key *tcp_md5_do_lookup_exact(const struct sock *sk,
+  const union tcp_md5_addr *addr,
+  int family, u8 prefixlen)
+{
+   const struct tcp_sock *tp = tcp_sk(sk);
+   struct tcp_md5sig_key *key;
+   unsigned int size = sizeof(struct in_addr);
+   const struct tcp_md5sig_info *md5sig;
+
+   /* caller either holds rcu_read_lock() or socket lock */
+   md5sig = rcu_dereference_check(tp->md5sig_info,
+  lockdep_sock_is_held(sk));
+   if (!md5sig)
+   return NULL;
+#if IS_ENABLED(CONFIG_IPV6)
+   if (family == AF_INET6)
+   size = sizeof(struct in6_addr);
+#endif
+   hlist_for_each_entry_rcu(key, >head, node) {
+   if (key->family != family)
+   continue;
+   if (!memcmp(>addr, addr, size) &&
+   key->prefixlen == prefixlen)
return key;
}
return NULL;
 }
-EXPORT_SYMBOL(tcp_md5_do_lookup);
 
 struct tcp_md5sig_key *tcp_v4_md5_lookup(const struct sock *sk,
 const struct sock *addr_sk)
@@ -938,14 +985,15 @@ EXPORT_SYMBOL(tcp_v4_md5_lookup);
 
 /* This can be called on a newly created socket, from other files */
 int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr,
-  int family, const u8 *ne

[PATCH v3 2/2] tcp: md5: add TCP_MD5SIG_EXT socket option to set a key address prefix

2017-06-15 Thread Ivan Delalande
Replace first padding in the tcp_md5sig structure with a new flag field
and address prefix length so it can be specified when configuring a new
key for TCP MD5 signature. The tcpm_flags field will only be used if the
socket option is TCP_MD5SIG_EXT to avoid breaking existing programs, and
tcpm_prefixlen only when the TCP_MD5SIG_FLAG_PREFIX flag is set.

Signed-off-by: Bob Gilligan <gilli...@arista.com>
Signed-off-by: Eric Mowat <mo...@arista.com>
Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/net/tcp.h|  1 +
 include/uapi/linux/tcp.h |  9 +++--
 net/ipv4/tcp.c   |  3 ++-
 net/ipv4/tcp_ipv4.c  | 16 
 net/ipv6/tcp_ipv6.c  | 25 ++---
 5 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 2b68023ab095..575f95cb8275 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1802,6 +1802,7 @@ struct tcp_sock_af_ops {
 const struct sock *sk,
 const struct sk_buff *skb);
int (*md5_parse)(struct sock *sk,
+int optname,
 char __user *optval,
 int optlen);
 #endif
diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
index 38a2b07afdff..9870b7f08f4f 100644
--- a/include/uapi/linux/tcp.h
+++ b/include/uapi/linux/tcp.h
@@ -117,6 +117,7 @@ enum {
 #define TCP_SAVED_SYN  28  /* Get SYN headers recorded for 
connection */
 #define TCP_REPAIR_WINDOW  29  /* Get/set window parameters */
 #define TCP_FASTOPEN_CONNECT   30  /* Attempt FastOpen with connect */
+#define TCP_MD5SIG_EXT 31  /* TCP MD5 Signature with extensions */
 
 struct tcp_repair_opt {
__u32   opt_code;
@@ -234,11 +235,15 @@ enum {
 /* for TCP_MD5SIG socket option */
 #define TCP_MD5SIG_MAXKEYLEN   80
 
+/* tcp_md5sig extension flags for TCP_MD5SIG_EXT */
+#define TCP_MD5SIG_FLAG_PREFIX 1   /* address prefix length */
+
 struct tcp_md5sig {
struct __kernel_sockaddr_storage tcpm_addr; /* address associated */
-   __u16   __tcpm_pad1;/* zero */
+   __u8tcpm_flags; /* extension flags */
+   __u8tcpm_prefixlen; /* address prefix */
__u16   tcpm_keylen;/* key length */
-   __u32   __tcpm_pad2;/* zero */
+   __u32   __tcpm_pad; /* zero */
__u8tcpm_key[TCP_MD5SIG_MAXKEYLEN]; /* key (binary) */
 };
 
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 1e4c76d2b827..2a68221d2e55 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -2666,8 +2666,9 @@ static int do_tcp_setsockopt(struct sock *sk, int level,
 
 #ifdef CONFIG_TCP_MD5SIG
case TCP_MD5SIG:
+   case TCP_MD5SIG_EXT:
/* Read the IP->Key mappings from userspace */
-   err = tp->af_specific->md5_parse(sk, optval, optlen);
+   err = tp->af_specific->md5_parse(sk, optname, optval, optlen);
break;
 #endif
case TCP_USER_TIMEOUT:
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 51ca3bd5a8a3..81d6c16aecdc 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1064,11 +1064,12 @@ static void tcp_clear_md5_list(struct sock *sk)
}
 }
 
-static int tcp_v4_parse_md5_keys(struct sock *sk, char __user *optval,
-int optlen)
+static int tcp_v4_parse_md5_keys(struct sock *sk, int optname,
+char __user *optval, int optlen)
 {
struct tcp_md5sig cmd;
struct sockaddr_in *sin = (struct sockaddr_in *)_addr;
+   u8 prefixlen = 32;
 
if (optlen < sizeof(cmd))
return -EINVAL;
@@ -1079,15 +1080,22 @@ static int tcp_v4_parse_md5_keys(struct sock *sk, char 
__user *optval,
if (sin->sin_family != AF_INET)
return -EINVAL;
 
+   if (optname == TCP_MD5SIG_EXT &&
+   cmd.tcpm_flags & TCP_MD5SIG_FLAG_PREFIX) {
+   prefixlen = cmd.tcpm_prefixlen;
+   if (prefixlen > 32)
+   return -EINVAL;
+   }
+
if (!cmd.tcpm_keylen)
return tcp_md5_do_del(sk, (union tcp_md5_addr 
*)>sin_addr.s_addr,
- AF_INET, 32);
+ AF_INET, prefixlen);
 
if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN)
return -EINVAL;
 
return tcp_md5_do_add(sk, (union tcp_md5_addr *)>sin_addr.s_addr,
- AF_INET, 32, cmd.tcpm_key, cmd.tcpm_keylen,
+ AF_INET, prefixlen, cmd.tcpm_key, cmd.tcpm_keylen,
  GFP_KERNE

Re: [PATCH v2 2/2] tcp: md5: extend the tcp_md5sig struct to specify a key address prefix

2017-06-12 Thread Ivan Delalande
On Sat, Jun 10, 2017 at 06:58:11PM -0400, David Miller wrote:
> From: Ivan Delalande <col...@arista.com>
> Date: Fri,  9 Jun 2017 19:14:49 -0700
> 
> > Add a flag field and address prefix length at the end of the tcp_md5sig
> > structure so users can configure an address prefix length along with a
> > key. Make sure shorter option values are still accepted in
> > tcp_v4_parse_md5_keys and tcp_v6_parse_md5_keys to maintain backward
> > compatibility.
> > 
> > Signed-off-by: Bob Gilligan <gilli...@arista.com>
> > Signed-off-by: Eric Mowat <mo...@arista.com>
> > Signed-off-by: Ivan Delalande <col...@arista.com>
> 
> As I believe was previously stated, the problem with this approach is
> that if a new tool requests the prefix length and is run on an older
> kernel, the kernel will return success even though the prefix length
> was not taken into account.
> 
> We do not want to get a success back when the operation requested was
> not performed.

Ah yeah that's right, sorry, definitely not great.

So I guess our only other option is to add a new socket option, like
TCP_MD5SIG_EXT which would use the extended version of struct tcp_md5sig
from this patch. Is it justified for this feature, or do you see any
other way to achieve this?

Thanks,
-- 
Ivan Delalande
Arista Networks


[PATCH v2 2/2] tcp: md5: extend the tcp_md5sig struct to specify a key address prefix

2017-06-09 Thread Ivan Delalande
Add a flag field and address prefix length at the end of the tcp_md5sig
structure so users can configure an address prefix length along with a
key. Make sure shorter option values are still accepted in
tcp_v4_parse_md5_keys and tcp_v6_parse_md5_keys to maintain backward
compatibility.

Signed-off-by: Bob Gilligan <gilli...@arista.com>
Signed-off-by: Eric Mowat <mo...@arista.com>
Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/uapi/linux/tcp.h |  8 
 net/ipv4/tcp_ipv4.c  | 15 +++
 net/ipv6/tcp_ipv6.c  | 24 +---
 3 files changed, 36 insertions(+), 11 deletions(-)

diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
index 38a2b07afdff..440a8d983e4b 100644
--- a/include/uapi/linux/tcp.h
+++ b/include/uapi/linux/tcp.h
@@ -233,6 +233,12 @@ enum {
 
 /* for TCP_MD5SIG socket option */
 #define TCP_MD5SIG_MAXKEYLEN   80
+/* original struct stopped at tcpm_key and must still be considered valid */
+#define TCP_MD5SIG_LEGACY_LEN  (offsetof(struct tcp_md5sig, tcpm_key) + \
+TCP_MD5SIG_MAXKEYLEN)
+
+/* tcp_md5sig flags */
+#define TCP_MD5SIG_FLAG_PREFIX 1   /* address prefix length */
 
 struct tcp_md5sig {
struct __kernel_sockaddr_storage tcpm_addr; /* address associated */
@@ -240,6 +246,8 @@ struct tcp_md5sig {
__u16   tcpm_keylen;/* key length */
__u32   __tcpm_pad2;/* zero */
__u8tcpm_key[TCP_MD5SIG_MAXKEYLEN]; /* key (binary) */
+   __u8tcpm_flags; /* flags */
+   __u8tcpm_prefixlen; /* address prefix */
 };
 
 #endif /* _UAPI_LINUX_TCP_H */
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 51ca3bd5a8a3..96a56224b913 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1069,25 +1069,32 @@ static int tcp_v4_parse_md5_keys(struct sock *sk, char 
__user *optval,
 {
struct tcp_md5sig cmd;
struct sockaddr_in *sin = (struct sockaddr_in *)_addr;
+   u8 prefixlen = 32;
 
-   if (optlen < sizeof(cmd))
+   if (optlen < TCP_MD5SIG_LEGACY_LEN)
return -EINVAL;
 
-   if (copy_from_user(, optval, sizeof(cmd)))
+   if (copy_from_user(, optval, min_t(size_t, sizeof(cmd), optlen)))
return -EFAULT;
 
if (sin->sin_family != AF_INET)
return -EINVAL;
 
+   if (optlen >= sizeof(cmd) && cmd.tcpm_flags & TCP_MD5SIG_FLAG_PREFIX) {
+   prefixlen = cmd.tcpm_prefixlen;
+   if (prefixlen > 32)
+   return -EINVAL;
+   }
+
if (!cmd.tcpm_keylen)
return tcp_md5_do_del(sk, (union tcp_md5_addr 
*)>sin_addr.s_addr,
- AF_INET, 32);
+ AF_INET, prefixlen);
 
if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN)
return -EINVAL;
 
return tcp_md5_do_add(sk, (union tcp_md5_addr *)>sin_addr.s_addr,
- AF_INET, 32, cmd.tcpm_key, cmd.tcpm_keylen,
+ AF_INET, prefixlen, cmd.tcpm_key, cmd.tcpm_keylen,
  GFP_KERNEL);
 }
 
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index 5cf19dab60aa..aff909e19b3d 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -519,22 +519,32 @@ static int tcp_v6_parse_md5_keys(struct sock *sk, char 
__user *optval,
 {
struct tcp_md5sig cmd;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)_addr;
+   u8 prefixlen;
 
-   if (optlen < sizeof(cmd))
+   if (optlen < TCP_MD5SIG_LEGACY_LEN)
return -EINVAL;
 
-   if (copy_from_user(, optval, sizeof(cmd)))
+   if (copy_from_user(, optval, min_t(size_t, sizeof(cmd), optlen)))
return -EFAULT;
 
if (sin6->sin6_family != AF_INET6)
return -EINVAL;
 
+   if (optlen >= sizeof(cmd) && cmd.tcpm_flags & TCP_MD5SIG_FLAG_PREFIX) {
+   prefixlen = cmd.tcpm_prefixlen;
+   if (prefixlen > 128 || (ipv6_addr_v4mapped(>sin6_addr) &&
+   prefixlen > 32))
+   return -EINVAL;
+   } else {
+   prefixlen = ipv6_addr_v4mapped(>sin6_addr) ? 32 : 128;
+   }
+
if (!cmd.tcpm_keylen) {
if (ipv6_addr_v4mapped(>sin6_addr))
return tcp_md5_do_del(sk, (union tcp_md5_addr 
*)>sin6_addr.s6_addr32[3],
- AF_INET, 32);
+ AF_INET, prefixlen);
return tcp_md5_do_del(sk, (union tcp_md5_addr 
*)>sin6_addr,
- AF_INET6, 128);
+ AF_INET6, p

[PATCH v2 1/2] tcp: md5: add an address prefix for key lookup

2017-06-09 Thread Ivan Delalande
This allows the keys used for TCP MD5 signature to be used for whole
range of addresses, specified with a prefix length, instead of only one
address as it currently is.

Signed-off-by: Bob Gilligan <gilli...@arista.com>
Signed-off-by: Eric Mowat <mo...@arista.com>
Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/net/tcp.h   |  6 +++--
 net/ipv4/tcp_ipv4.c | 68 ++---
 net/ipv6/tcp_ipv6.c | 12 ++
 3 files changed, 70 insertions(+), 16 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 38a7427ae902..2b68023ab095 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1395,6 +1395,7 @@ struct tcp_md5sig_key {
u8  keylen;
u8  family; /* AF_INET or AF_INET6 */
union tcp_md5_addr  addr;
+   u8  prefixlen;
u8  key[TCP_MD5SIG_MAXKEYLEN];
struct rcu_head rcu;
 };
@@ -1438,9 +1439,10 @@ struct tcp_md5sig_pool {
 int tcp_v4_md5_hash_skb(char *md5_hash, const struct tcp_md5sig_key *key,
const struct sock *sk, const struct sk_buff *skb);
 int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr,
-  int family, const u8 *newkey, u8 newkeylen, gfp_t gfp);
+  int family, u8 prefixlen, const u8 *newkey, u8 newkeylen,
+  gfp_t gfp);
 int tcp_md5_do_del(struct sock *sk, const union tcp_md5_addr *addr,
-  int family);
+  int family, u8 prefixlen);
 struct tcp_md5sig_key *tcp_v4_md5_lookup(const struct sock *sk,
 const struct sock *addr_sk);
 
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 5ab2aac5ca19..51ca3bd5a8a3 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -80,6 +80,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include 
 #include 
@@ -906,6 +907,9 @@ struct tcp_md5sig_key *tcp_md5_do_lookup(const struct sock 
*sk,
struct tcp_md5sig_key *key;
unsigned int size = sizeof(struct in_addr);
const struct tcp_md5sig_info *md5sig;
+   __be32 mask;
+   struct tcp_md5sig_key *best_match = NULL;
+   bool match;
 
/* caller either holds rcu_read_lock() or socket lock */
md5sig = rcu_dereference_check(tp->md5sig_info,
@@ -919,12 +923,55 @@ struct tcp_md5sig_key *tcp_md5_do_lookup(const struct 
sock *sk,
hlist_for_each_entry_rcu(key, >head, node) {
if (key->family != family)
continue;
-   if (!memcmp(>addr, addr, size))
+
+   if (family == AF_INET) {
+   mask = inet_make_mask(key->prefixlen);
+   match = (key->addr.a4.s_addr & mask) ==
+   (addr->a4.s_addr & mask);
+#if IS_ENABLED(CONFIG_IPV6)
+   } else if (family == AF_INET6) {
+   match = ipv6_prefix_equal(>addr.a6, >a6,
+ key->prefixlen);
+#endif
+   } else {
+   match = false;
+   }
+
+   if (match && (!best_match ||
+ key->prefixlen > best_match->prefixlen))
+   best_match = key;
+   }
+   return best_match;
+}
+EXPORT_SYMBOL(tcp_md5_do_lookup);
+
+struct tcp_md5sig_key *tcp_md5_do_lookup_exact(const struct sock *sk,
+  const union tcp_md5_addr *addr,
+  int family, u8 prefixlen)
+{
+   const struct tcp_sock *tp = tcp_sk(sk);
+   struct tcp_md5sig_key *key;
+   unsigned int size = sizeof(struct in_addr);
+   const struct tcp_md5sig_info *md5sig;
+
+   /* caller either holds rcu_read_lock() or socket lock */
+   md5sig = rcu_dereference_check(tp->md5sig_info,
+  lockdep_sock_is_held(sk));
+   if (!md5sig)
+   return NULL;
+#if IS_ENABLED(CONFIG_IPV6)
+   if (family == AF_INET6)
+   size = sizeof(struct in6_addr);
+#endif
+   hlist_for_each_entry_rcu(key, >head, node) {
+   if (key->family != family)
+   continue;
+   if (!memcmp(>addr, addr, size) &&
+   key->prefixlen == prefixlen)
return key;
}
return NULL;
 }
-EXPORT_SYMBOL(tcp_md5_do_lookup);
 
 struct tcp_md5sig_key *tcp_v4_md5_lookup(const struct sock *sk,
 const struct sock *addr_sk)
@@ -938,14 +985,15 @@ EXPORT_SYMBOL(tcp_v4_md5_lookup);
 
 /* This can be called on a newly created socket, from other files */
 int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr,
-  int family, const u8 *ne

Re: [PATCH 2/2] tcp: md5: add fields to the tcp_md5sig struct to set a key address prefix

2017-06-07 Thread Ivan Delalande
On Tue, Jun 06, 2017 at 09:08:22PM -0700, Eric Dumazet wrote:
> On Tue, 2017-06-06 at 17:54 -0700, Ivan Delalande wrote:
>> diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
>> index 38a2b07afdff..52ac30aa0652 100644
>> --- a/include/uapi/linux/tcp.h
>> +++ b/include/uapi/linux/tcp.h
>> @@ -234,9 +234,13 @@ enum {
>>  /* for TCP_MD5SIG socket option */
>>  #define TCP_MD5SIG_MAXKEYLEN80
>>  
>> +/* tcp_md5sig flags */
>> +#define TCP_MD5SIG_FLAG_PREFIX  1   /* address prefix 
>> length */
>> +
>>  struct tcp_md5sig {
>>  struct __kernel_sockaddr_storage tcpm_addr; /* address associated */
>> -__u16   __tcpm_pad1;/* zero */
>> +__u8tcpm_flags; /* flags */
>> +__u8tcpm_prefixlen; /* address prefix */
>>  __u16   tcpm_keylen;/* key length */
>>  __u32   __tcpm_pad2;/* zero */
>>  __u8tcpm_key[TCP_MD5SIG_MAXKEYLEN]; /* key (binary) */
> 
> This will break some applications that maybe did not clear the
> __tcpm_pad1 field ?
> 
> 
> You need to find another way to maintain compatibility with old
> applications.

All right, I thought this was acceptable after seeing a few examples of
this in commits extending other structures in uapi, but the context and
use were probably different for those.

We had another version of this patch which steals a bit from tcpm_keylen
to use as a flag for this feature specifically and with the prefixlen at
the same place as this patch. So when the flag is set we know we can
safely interpret this part of the padding field as a prefix as all valid
calls from older user programs should not have a key length greater than
80 bytes.

Would this be better? Programs compiled with the new headers could break
on older kernels if they don't check the version, I don't know if that's
a concern.

Or should we just add these two new fields at the end of tcp_md5sig and
use them only if the value of optlen in the parse function called from
setsockopt is large enough?


Thank you,
-- 
Ivan Delalande
Arista Networks


[PATCH 1/2] tcp: md5: add an address prefix for key lookup

2017-06-06 Thread Ivan Delalande
This allows the keys used for TCP MD5 signature to be used for whole
range of addresses, specified with a prefix length, instead of only one
address as it currently is.

Signed-off-by: Bob Gilligan <gilli...@arista.com>
Signed-off-by: Eric Mowat <mo...@arista.com>
Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/net/tcp.h   |  6 +++--
 net/ipv4/tcp_ipv4.c | 68 ++---
 net/ipv6/tcp_ipv6.c | 12 ++
 3 files changed, 70 insertions(+), 16 deletions(-)

diff --git a/include/net/tcp.h b/include/net/tcp.h
index 38a7427ae902..2b68023ab095 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -1395,6 +1395,7 @@ struct tcp_md5sig_key {
u8  keylen;
u8  family; /* AF_INET or AF_INET6 */
union tcp_md5_addr  addr;
+   u8  prefixlen;
u8  key[TCP_MD5SIG_MAXKEYLEN];
struct rcu_head rcu;
 };
@@ -1438,9 +1439,10 @@ struct tcp_md5sig_pool {
 int tcp_v4_md5_hash_skb(char *md5_hash, const struct tcp_md5sig_key *key,
const struct sock *sk, const struct sk_buff *skb);
 int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr,
-  int family, const u8 *newkey, u8 newkeylen, gfp_t gfp);
+  int family, u8 prefixlen, const u8 *newkey, u8 newkeylen,
+  gfp_t gfp);
 int tcp_md5_do_del(struct sock *sk, const union tcp_md5_addr *addr,
-  int family);
+  int family, u8 prefixlen);
 struct tcp_md5sig_key *tcp_v4_md5_lookup(const struct sock *sk,
 const struct sock *addr_sk);
 
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 5ab2aac5ca19..51ca3bd5a8a3 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -80,6 +80,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include 
 #include 
@@ -906,6 +907,9 @@ struct tcp_md5sig_key *tcp_md5_do_lookup(const struct sock 
*sk,
struct tcp_md5sig_key *key;
unsigned int size = sizeof(struct in_addr);
const struct tcp_md5sig_info *md5sig;
+   __be32 mask;
+   struct tcp_md5sig_key *best_match = NULL;
+   bool match;
 
/* caller either holds rcu_read_lock() or socket lock */
md5sig = rcu_dereference_check(tp->md5sig_info,
@@ -919,12 +923,55 @@ struct tcp_md5sig_key *tcp_md5_do_lookup(const struct 
sock *sk,
hlist_for_each_entry_rcu(key, >head, node) {
if (key->family != family)
continue;
-   if (!memcmp(>addr, addr, size))
+
+   if (family == AF_INET) {
+   mask = inet_make_mask(key->prefixlen);
+   match = (key->addr.a4.s_addr & mask) ==
+   (addr->a4.s_addr & mask);
+#if IS_ENABLED(CONFIG_IPV6)
+   } else if (family == AF_INET6) {
+   match = ipv6_prefix_equal(>addr.a6, >a6,
+ key->prefixlen);
+#endif
+   } else {
+   match = false;
+   }
+
+   if (match && (!best_match ||
+ key->prefixlen > best_match->prefixlen))
+   best_match = key;
+   }
+   return best_match;
+}
+EXPORT_SYMBOL(tcp_md5_do_lookup);
+
+struct tcp_md5sig_key *tcp_md5_do_lookup_exact(const struct sock *sk,
+  const union tcp_md5_addr *addr,
+  int family, u8 prefixlen)
+{
+   const struct tcp_sock *tp = tcp_sk(sk);
+   struct tcp_md5sig_key *key;
+   unsigned int size = sizeof(struct in_addr);
+   const struct tcp_md5sig_info *md5sig;
+
+   /* caller either holds rcu_read_lock() or socket lock */
+   md5sig = rcu_dereference_check(tp->md5sig_info,
+  lockdep_sock_is_held(sk));
+   if (!md5sig)
+   return NULL;
+#if IS_ENABLED(CONFIG_IPV6)
+   if (family == AF_INET6)
+   size = sizeof(struct in6_addr);
+#endif
+   hlist_for_each_entry_rcu(key, >head, node) {
+   if (key->family != family)
+   continue;
+   if (!memcmp(>addr, addr, size) &&
+   key->prefixlen == prefixlen)
return key;
}
return NULL;
 }
-EXPORT_SYMBOL(tcp_md5_do_lookup);
 
 struct tcp_md5sig_key *tcp_v4_md5_lookup(const struct sock *sk,
 const struct sock *addr_sk)
@@ -938,14 +985,15 @@ EXPORT_SYMBOL(tcp_v4_md5_lookup);
 
 /* This can be called on a newly created socket, from other files */
 int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr,
-  int family, const u8 *ne

[PATCH 2/2] tcp: md5: add fields to the tcp_md5sig struct to set a key address prefix

2017-06-06 Thread Ivan Delalande
Replace padding in the socket option structure tcp_md5sig with a new
flag field and address prefix length so it can be specified when
configuring a new key with the TCP_MD5SIG socket option.

Signed-off-by: Bob Gilligan <gilli...@arista.com>
Signed-off-by: Eric Mowat <mo...@arista.com>
Signed-off-by: Ivan Delalande <col...@arista.com>
---
 include/uapi/linux/tcp.h |  6 +-
 net/ipv4/tcp_ipv4.c  | 13 +++--
 net/ipv6/tcp_ipv6.c  | 20 +++-
 3 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/include/uapi/linux/tcp.h b/include/uapi/linux/tcp.h
index 38a2b07afdff..52ac30aa0652 100644
--- a/include/uapi/linux/tcp.h
+++ b/include/uapi/linux/tcp.h
@@ -234,9 +234,13 @@ enum {
 /* for TCP_MD5SIG socket option */
 #define TCP_MD5SIG_MAXKEYLEN   80
 
+/* tcp_md5sig flags */
+#define TCP_MD5SIG_FLAG_PREFIX 1   /* address prefix length */
+
 struct tcp_md5sig {
struct __kernel_sockaddr_storage tcpm_addr; /* address associated */
-   __u16   __tcpm_pad1;/* zero */
+   __u8tcpm_flags; /* flags */
+   __u8tcpm_prefixlen; /* address prefix */
__u16   tcpm_keylen;/* key length */
__u32   __tcpm_pad2;/* zero */
__u8tcpm_key[TCP_MD5SIG_MAXKEYLEN]; /* key (binary) */
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index 51ca3bd5a8a3..2b1bb67b3388 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -1069,6 +1069,7 @@ static int tcp_v4_parse_md5_keys(struct sock *sk, char 
__user *optval,
 {
struct tcp_md5sig cmd;
struct sockaddr_in *sin = (struct sockaddr_in *)_addr;
+   u8 prefixlen;
 
if (optlen < sizeof(cmd))
return -EINVAL;
@@ -1079,15 +1080,23 @@ static int tcp_v4_parse_md5_keys(struct sock *sk, char 
__user *optval,
if (sin->sin_family != AF_INET)
return -EINVAL;
 
+   if (cmd.tcpm_flags & TCP_MD5SIG_FLAG_PREFIX) {
+   prefixlen = cmd.tcpm_prefixlen;
+   if (prefixlen > 32)
+   return -EINVAL;
+   } else {
+   prefixlen = 32;
+   }
+
if (!cmd.tcpm_keylen)
return tcp_md5_do_del(sk, (union tcp_md5_addr 
*)>sin_addr.s_addr,
- AF_INET, 32);
+ AF_INET, prefixlen);
 
if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN)
return -EINVAL;
 
return tcp_md5_do_add(sk, (union tcp_md5_addr *)>sin_addr.s_addr,
- AF_INET, 32, cmd.tcpm_key, cmd.tcpm_keylen,
+ AF_INET, prefixlen, cmd.tcpm_key, cmd.tcpm_keylen,
  GFP_KERNEL);
 }
 
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
index 5cf19dab60aa..f293fc69e88b 100644
--- a/net/ipv6/tcp_ipv6.c
+++ b/net/ipv6/tcp_ipv6.c
@@ -519,6 +519,7 @@ static int tcp_v6_parse_md5_keys(struct sock *sk, char 
__user *optval,
 {
struct tcp_md5sig cmd;
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)_addr;
+   u8 prefixlen;
 
if (optlen < sizeof(cmd))
return -EINVAL;
@@ -529,12 +530,21 @@ static int tcp_v6_parse_md5_keys(struct sock *sk, char 
__user *optval,
if (sin6->sin6_family != AF_INET6)
return -EINVAL;
 
+   if (cmd.tcpm_flags & TCP_MD5SIG_FLAG_PREFIX) {
+   prefixlen = cmd.tcpm_prefixlen;
+   if (prefixlen > 128 || (ipv6_addr_v4mapped(>sin6_addr) &&
+   prefixlen > 32))
+   return -EINVAL;
+   } else {
+   prefixlen = ipv6_addr_v4mapped(>sin6_addr) ? 32 : 128;
+   }
+
if (!cmd.tcpm_keylen) {
if (ipv6_addr_v4mapped(>sin6_addr))
return tcp_md5_do_del(sk, (union tcp_md5_addr 
*)>sin6_addr.s6_addr32[3],
- AF_INET, 32);
+ AF_INET, prefixlen);
return tcp_md5_do_del(sk, (union tcp_md5_addr 
*)>sin6_addr,
- AF_INET6, 128);
+ AF_INET6, prefixlen);
}
 
if (cmd.tcpm_keylen > TCP_MD5SIG_MAXKEYLEN)
@@ -542,12 +552,12 @@ static int tcp_v6_parse_md5_keys(struct sock *sk, char 
__user *optval,
 
if (ipv6_addr_v4mapped(>sin6_addr))
return tcp_md5_do_add(sk, (union tcp_md5_addr 
*)>sin6_addr.s6_addr32[3],
- AF_INET, 32, cmd.tcpm_key,
+ AF_INET, prefixlen, cmd.tcpm_key,
  cmd.tcpm_keylen, GFP_KERNEL);
 
return tcp_md5_do_add(sk, (union tcp_md5_addr