netfilter: nft_reject: introduce icmp code abstraction for inet and bridge
authorPablo Neira Ayuso <pablo@netfilter.org>
Fri, 26 Sep 2014 12:35:14 +0000 (14:35 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Thu, 2 Oct 2014 16:29:57 +0000 (18:29 +0200)
This patch introduces the NFT_REJECT_ICMPX_UNREACH type which provides
an abstraction to the ICMP and ICMPv6 codes that you can use from the
inet and bridge tables, they are:

* NFT_REJECT_ICMPX_NO_ROUTE: no route to host - network unreachable
* NFT_REJECT_ICMPX_PORT_UNREACH: port unreachable
* NFT_REJECT_ICMPX_HOST_UNREACH: host unreachable
* NFT_REJECT_ICMPX_ADMIN_PROHIBITED: administratevely prohibited

You can still use the specific codes when restricting the rule to match
the corresponding layer 3 protocol.

I decided to not overload the existing NFT_REJECT_ICMP_UNREACH to have
different semantics depending on the table family and to allow the user
to specify ICMP family specific codes if they restrict it to the
corresponding family.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/net/netfilter/ipv4/nf_reject.h
include/net/netfilter/nft_reject.h
include/uapi/linux/netfilter/nf_tables.h
net/bridge/netfilter/nft_reject_bridge.c
net/ipv4/netfilter/nft_reject_ipv4.c
net/netfilter/nft_reject.c
net/netfilter/nft_reject_inet.c

index f713b5a31d622b7e29542d40bbe6a099b8a96720..8ce06385a552f62cf29f1b62cc2b1c849cf41c9d 100644 (file)
@@ -5,6 +5,7 @@
 #include <net/tcp.h>
 #include <net/route.h>
 #include <net/dst.h>
+#include <net/icmp.h>
 
 static inline void nf_send_unreach(struct sk_buff *skb_in, int code)
 {
index 36b0da2d55bb7db470991e873d02b0fbf400d2ac..60fa1530006b45bc229dbf46bfe93ff690bc7b42 100644 (file)
@@ -14,12 +14,7 @@ int nft_reject_init(const struct nft_ctx *ctx,
 
 int nft_reject_dump(struct sk_buff *skb, const struct nft_expr *expr);
 
-void nft_reject_ipv4_eval(const struct nft_expr *expr,
-                         struct nft_data data[NFT_REG_MAX + 1],
-                         const struct nft_pktinfo *pkt);
-
-void nft_reject_ipv6_eval(const struct nft_expr *expr,
-                         struct nft_data data[NFT_REG_MAX + 1],
-                         const struct nft_pktinfo *pkt);
+int nft_reject_icmp_code(u8 code);
+int nft_reject_icmpv6_code(u8 code);
 
 #endif
index b72ccfeaf86527e12b61230597798d1cb902f949..c26df6787fb075a4d2317a9d9e8e897813df8e0f 100644 (file)
@@ -749,12 +749,33 @@ enum nft_queue_attributes {
  *
  * @NFT_REJECT_ICMP_UNREACH: reject using ICMP unreachable
  * @NFT_REJECT_TCP_RST: reject using TCP RST
+ * @NFT_REJECT_ICMPX_UNREACH: abstracted ICMP unreachable for bridge and inet
  */
 enum nft_reject_types {
        NFT_REJECT_ICMP_UNREACH,
        NFT_REJECT_TCP_RST,
+       NFT_REJECT_ICMPX_UNREACH,
 };
 
+/**
+ * enum nft_reject_code - Generic reject codes for IPv4/IPv6
+ *
+ * @NFT_REJECT_ICMPX_NO_ROUTE: no route to host / network unreachable
+ * @NFT_REJECT_ICMPX_PORT_UNREACH: port unreachable
+ * @NFT_REJECT_ICMPX_HOST_UNREACH: host unreachable
+ * @NFT_REJECT_ICMPX_ADMIN_PROHIBITED: administratively prohibited
+ *
+ * These codes are mapped to real ICMP and ICMPv6 codes.
+ */
+enum nft_reject_inet_code {
+       NFT_REJECT_ICMPX_NO_ROUTE       = 0,
+       NFT_REJECT_ICMPX_PORT_UNREACH,
+       NFT_REJECT_ICMPX_HOST_UNREACH,
+       NFT_REJECT_ICMPX_ADMIN_PROHIBITED,
+       __NFT_REJECT_ICMPX_MAX
+};
+#define NFT_REJECT_ICMPX_MAX   (__NFT_REJECT_ICMPX_MAX + 1)
+
 /**
  * enum nft_reject_attributes - nf_tables reject expression netlink attributes
  *
index ee3ffe93e14ec73e330da1c1fcbdd6b18cc74be6..a76479535df2a5005968c18422d999aa897b3556 100644 (file)
 #include <linux/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables.h>
 #include <net/netfilter/nft_reject.h>
+#include <net/netfilter/ipv4/nf_reject.h>
+#include <net/netfilter/ipv6/nf_reject.h>
 
 static void nft_reject_bridge_eval(const struct nft_expr *expr,
                                 struct nft_data data[NFT_REG_MAX + 1],
                                 const struct nft_pktinfo *pkt)
 {
+       struct nft_reject *priv = nft_expr_priv(expr);
+       struct net *net = dev_net((pkt->in != NULL) ? pkt->in : pkt->out);
+
        switch (eth_hdr(pkt->skb)->h_proto) {
        case htons(ETH_P_IP):
-               return nft_reject_ipv4_eval(expr, data, pkt);
+               switch (priv->type) {
+               case NFT_REJECT_ICMP_UNREACH:
+                       nf_send_unreach(pkt->skb, priv->icmp_code);
+                       break;
+               case NFT_REJECT_TCP_RST:
+                       nf_send_reset(pkt->skb, pkt->ops->hooknum);
+                       break;
+               case NFT_REJECT_ICMPX_UNREACH:
+                       nf_send_unreach(pkt->skb,
+                                       nft_reject_icmp_code(priv->icmp_code));
+                       break;
+               }
+               break;
        case htons(ETH_P_IPV6):
-               return nft_reject_ipv6_eval(expr, data, pkt);
+               switch (priv->type) {
+               case NFT_REJECT_ICMP_UNREACH:
+                       nf_send_unreach6(net, pkt->skb, priv->icmp_code,
+                                        pkt->ops->hooknum);
+                       break;
+               case NFT_REJECT_TCP_RST:
+                       nf_send_reset6(net, pkt->skb, pkt->ops->hooknum);
+                       break;
+               case NFT_REJECT_ICMPX_UNREACH:
+                       nf_send_unreach6(net, pkt->skb,
+                                        nft_reject_icmpv6_code(priv->icmp_code),
+                                        pkt->ops->hooknum);
+                       break;
+               }
+               break;
        default:
                /* No explicit way to reject this protocol, drop it. */
-               data[NFT_REG_VERDICT].verdict = NF_DROP;
                break;
        }
+       data[NFT_REG_VERDICT].verdict = NF_DROP;
+}
+
+static int nft_reject_bridge_init(const struct nft_ctx *ctx,
+                                 const struct nft_expr *expr,
+                                 const struct nlattr * const tb[])
+{
+       struct nft_reject *priv = nft_expr_priv(expr);
+       int icmp_code;
+
+       if (tb[NFTA_REJECT_TYPE] == NULL)
+               return -EINVAL;
+
+       priv->type = ntohl(nla_get_be32(tb[NFTA_REJECT_TYPE]));
+       switch (priv->type) {
+       case NFT_REJECT_ICMP_UNREACH:
+       case NFT_REJECT_ICMPX_UNREACH:
+               if (tb[NFTA_REJECT_ICMP_CODE] == NULL)
+                       return -EINVAL;
+
+               icmp_code = nla_get_u8(tb[NFTA_REJECT_ICMP_CODE]);
+               if (priv->type == NFT_REJECT_ICMPX_UNREACH &&
+                   icmp_code > NFT_REJECT_ICMPX_MAX)
+                       return -EINVAL;
+
+               priv->icmp_code = icmp_code;
+               break;
+       case NFT_REJECT_TCP_RST:
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int nft_reject_bridge_dump(struct sk_buff *skb,
+                                 const struct nft_expr *expr)
+{
+       const struct nft_reject *priv = nft_expr_priv(expr);
+
+       if (nla_put_be32(skb, NFTA_REJECT_TYPE, htonl(priv->type)))
+               goto nla_put_failure;
+
+       switch (priv->type) {
+       case NFT_REJECT_ICMP_UNREACH:
+       case NFT_REJECT_ICMPX_UNREACH:
+               if (nla_put_u8(skb, NFTA_REJECT_ICMP_CODE, priv->icmp_code))
+                       goto nla_put_failure;
+               break;
+       }
+
+       return 0;
+
+nla_put_failure:
+       return -1;
 }
 
 static struct nft_expr_type nft_reject_bridge_type;
@@ -36,8 +121,8 @@ static const struct nft_expr_ops nft_reject_bridge_ops = {
        .type           = &nft_reject_bridge_type,
        .size           = NFT_EXPR_SIZE(sizeof(struct nft_reject)),
        .eval           = nft_reject_bridge_eval,
-       .init           = nft_reject_init,
-       .dump           = nft_reject_dump,
+       .init           = nft_reject_bridge_init,
+       .dump           = nft_reject_bridge_dump,
 };
 
 static struct nft_expr_type nft_reject_bridge_type __read_mostly = {
index e79718a382f2e373890bc5fbb2be4fa2468162c8..ed33299c56d15089055f4664ce28ef3da36f9ee3 100644 (file)
@@ -16,7 +16,6 @@
 #include <linux/netfilter.h>
 #include <linux/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables.h>
-#include <net/icmp.h>
 #include <net/netfilter/ipv4/nf_reject.h>
 #include <net/netfilter/nft_reject.h>
 
index f3448c2964468abc08d2b3630be18953820e1aff..ec8a456092a73d7b8f92e92db46be986770a85e7 100644 (file)
@@ -17,6 +17,8 @@
 #include <linux/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables.h>
 #include <net/netfilter/nft_reject.h>
+#include <linux/icmp.h>
+#include <linux/icmpv6.h>
 
 const struct nla_policy nft_reject_policy[NFTA_REJECT_MAX + 1] = {
        [NFTA_REJECT_TYPE]              = { .type = NLA_U32 },
@@ -70,5 +72,40 @@ nla_put_failure:
 }
 EXPORT_SYMBOL_GPL(nft_reject_dump);
 
+static u8 icmp_code_v4[NFT_REJECT_ICMPX_MAX] = {
+       [NFT_REJECT_ICMPX_NO_ROUTE]             = ICMP_NET_UNREACH,
+       [NFT_REJECT_ICMPX_PORT_UNREACH]         = ICMP_PORT_UNREACH,
+       [NFT_REJECT_ICMPX_HOST_UNREACH]         = ICMP_HOST_UNREACH,
+       [NFT_REJECT_ICMPX_ADMIN_PROHIBITED]     = ICMP_PKT_FILTERED,
+};
+
+int nft_reject_icmp_code(u8 code)
+{
+       if (code > NFT_REJECT_ICMPX_MAX)
+               return -EINVAL;
+
+       return icmp_code_v4[code];
+}
+
+EXPORT_SYMBOL_GPL(nft_reject_icmp_code);
+
+
+static u8 icmp_code_v6[NFT_REJECT_ICMPX_MAX] = {
+       [NFT_REJECT_ICMPX_NO_ROUTE]             = ICMPV6_NOROUTE,
+       [NFT_REJECT_ICMPX_PORT_UNREACH]         = ICMPV6_PORT_UNREACH,
+       [NFT_REJECT_ICMPX_HOST_UNREACH]         = ICMPV6_ADDR_UNREACH,
+       [NFT_REJECT_ICMPX_ADMIN_PROHIBITED]     = ICMPV6_ADM_PROHIBITED,
+};
+
+int nft_reject_icmpv6_code(u8 code)
+{
+       if (code > NFT_REJECT_ICMPX_MAX)
+               return -EINVAL;
+
+       return icmp_code_v6[code];
+}
+
+EXPORT_SYMBOL_GPL(nft_reject_icmpv6_code);
+
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
index b718a52a46544036ba81a4105da149d8261d30c3..7b5f9d58680ad0ebca03c13e4a6f43037283bdc1 100644 (file)
 #include <linux/netfilter/nf_tables.h>
 #include <net/netfilter/nf_tables.h>
 #include <net/netfilter/nft_reject.h>
+#include <net/netfilter/ipv4/nf_reject.h>
+#include <net/netfilter/ipv6/nf_reject.h>
 
 static void nft_reject_inet_eval(const struct nft_expr *expr,
                                 struct nft_data data[NFT_REG_MAX + 1],
                                 const struct nft_pktinfo *pkt)
 {
+       struct nft_reject *priv = nft_expr_priv(expr);
+       struct net *net = dev_net((pkt->in != NULL) ? pkt->in : pkt->out);
+
        switch (pkt->ops->pf) {
        case NFPROTO_IPV4:
-               return nft_reject_ipv4_eval(expr, data, pkt);
+               switch (priv->type) {
+               case NFT_REJECT_ICMP_UNREACH:
+                       nf_send_unreach(pkt->skb, priv->icmp_code);
+                       break;
+               case NFT_REJECT_TCP_RST:
+                       nf_send_reset(pkt->skb, pkt->ops->hooknum);
+                       break;
+               case NFT_REJECT_ICMPX_UNREACH:
+                       nf_send_unreach(pkt->skb,
+                                       nft_reject_icmp_code(priv->icmp_code));
+                       break;
+               }
+               break;
        case NFPROTO_IPV6:
-               return nft_reject_ipv6_eval(expr, data, pkt);
+               switch (priv->type) {
+               case NFT_REJECT_ICMP_UNREACH:
+                       nf_send_unreach6(net, pkt->skb, priv->icmp_code,
+                                        pkt->ops->hooknum);
+                       break;
+               case NFT_REJECT_TCP_RST:
+                       nf_send_reset6(net, pkt->skb, pkt->ops->hooknum);
+                       break;
+               case NFT_REJECT_ICMPX_UNREACH:
+                       nf_send_unreach6(net, pkt->skb,
+                                        nft_reject_icmpv6_code(priv->icmp_code),
+                                        pkt->ops->hooknum);
+                       break;
+               }
+               break;
+       }
+       data[NFT_REG_VERDICT].verdict = NF_DROP;
+}
+
+static int nft_reject_inet_init(const struct nft_ctx *ctx,
+                               const struct nft_expr *expr,
+                               const struct nlattr * const tb[])
+{
+       struct nft_reject *priv = nft_expr_priv(expr);
+       int icmp_code;
+
+       if (tb[NFTA_REJECT_TYPE] == NULL)
+               return -EINVAL;
+
+       priv->type = ntohl(nla_get_be32(tb[NFTA_REJECT_TYPE]));
+       switch (priv->type) {
+       case NFT_REJECT_ICMP_UNREACH:
+       case NFT_REJECT_ICMPX_UNREACH:
+               if (tb[NFTA_REJECT_ICMP_CODE] == NULL)
+                       return -EINVAL;
+
+               icmp_code = nla_get_u8(tb[NFTA_REJECT_ICMP_CODE]);
+               if (priv->type == NFT_REJECT_ICMPX_UNREACH &&
+                   icmp_code > NFT_REJECT_ICMPX_MAX)
+                       return -EINVAL;
+
+               priv->icmp_code = icmp_code;
+               break;
+       case NFT_REJECT_TCP_RST:
+               break;
+       default:
+               return -EINVAL;
        }
+       return 0;
+}
+
+static int nft_reject_inet_dump(struct sk_buff *skb,
+                               const struct nft_expr *expr)
+{
+       const struct nft_reject *priv = nft_expr_priv(expr);
+
+       if (nla_put_be32(skb, NFTA_REJECT_TYPE, htonl(priv->type)))
+               goto nla_put_failure;
+
+       switch (priv->type) {
+       case NFT_REJECT_ICMP_UNREACH:
+       case NFT_REJECT_ICMPX_UNREACH:
+               if (nla_put_u8(skb, NFTA_REJECT_ICMP_CODE, priv->icmp_code))
+                       goto nla_put_failure;
+               break;
+       }
+
+       return 0;
+
+nla_put_failure:
+       return -1;
 }
 
 static struct nft_expr_type nft_reject_inet_type;
@@ -32,8 +118,8 @@ static const struct nft_expr_ops nft_reject_inet_ops = {
        .type           = &nft_reject_inet_type,
        .size           = NFT_EXPR_SIZE(sizeof(struct nft_reject)),
        .eval           = nft_reject_inet_eval,
-       .init           = nft_reject_init,
-       .dump           = nft_reject_dump,
+       .init           = nft_reject_inet_init,
+       .dump           = nft_reject_inet_dump,
 };
 
 static struct nft_expr_type nft_reject_inet_type __read_mostly = {