networking: add 'ip neigh' command
authorCurt Brune <curt@cumulusnetworks.com>
Wed, 14 Oct 2015 10:53:47 +0000 (12:53 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Wed, 14 Oct 2015 10:53:47 +0000 (12:53 +0200)
This patch ports the 'ip neigh' command, originally written by Alexey
Kuznetsov, <kuznet@ms2.inr.ac.ru>, to busybox.

The base of the port is the version of iproute that shipped with
Debian Squeeze, taken from:

  http://http.debian.net/debian/pool/main/i/iproute/iproute_20100519.orig.tar.gz

This patch has actively been used by the Open Network Install
Environment (ONIE) project for over 3 years without incident.

function                                             old     new   delta
print_neigh                                            -     933    +933
ipneigh_list_or_flush                                  -     742    +742
get_hz                                                 -     109    +109
do_ipneigh                                             -      62     +62
do_iproute                                          2112    2153     +41
packed_usage                                       30647   30667     +20
ipneigh_main                                           -      14     +14
static.ip_neigh_commands                               -      12     +12
static.nuds                                            -       9      +9
static.ip_func_ptrs                                   32      36      +4
print_route                                         1858    1727    -131
------------------------------------------------------------------------------
(add/remove: 8/0 grow/shrink: 3/1 up/down: 1946/-131)        Total: 1815 bytes

Signed-off-by: Curt Brune <curt@cumulusnetworks.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
networking/Config.src
networking/ip.c
networking/libiproute/Kbuild.src
networking/libiproute/ip_common.h
networking/libiproute/ipneigh.c [new file with mode: 0644]
networking/libiproute/iproute.c
networking/libiproute/utils.c
networking/libiproute/utils.h

index 43ccbf385f90990b91bd8ba197ccd75f9a775df8..8c7417f86bf1eb024eeb75d12d78db3877882059 100644 (file)
@@ -554,6 +554,13 @@ config FEATURE_IP_RULE
        help
          Add support for rule commands to "ip".
 
+config FEATURE_IP_NEIGH
+       bool "ip neighbor"
+       default y
+       depends on IP
+       help
+         Add support for neighbor commands to "ip".
+
 config FEATURE_IP_SHORT_FORMS
        bool "Support short forms of ip commands"
        default y
@@ -565,6 +572,7 @@ config FEATURE_IP_SHORT_FORMS
          ip route  -> iproute
          ip tunnel -> iptunnel
          ip rule   -> iprule
+         ip neigh  -> ipneigh
 
          Say N unless you desparately need the short form of the ip
          object commands.
@@ -604,6 +612,11 @@ config IPRULE
        default y
        depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_RULE
 
+config IPNEIGH
+       bool
+       default y
+       depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_NEIGH
+
 config IPCALC
        bool "ipcalc"
        default y
index d35345c36fbfbed9dae8b58744672e0272f5d671..ddfe74e9cddf2af2d4232c4c9bb8c14e278faa9f 100644 (file)
@@ -16,6 +16,7 @@
 //usage:       IF_FEATURE_IP_ROUTE("route | ")
 //usage:       IF_FEATURE_IP_LINK("link | ")
 //usage:       IF_FEATURE_IP_TUNNEL("tunnel | ")
+//usage:       IF_FEATURE_IP_NEIGH("neigh | ")
 //usage:       IF_FEATURE_IP_RULE("rule")
 //usage:       "} {COMMAND}"
 //usage:#define ip_full_usage "\n\n"
@@ -25,6 +26,7 @@
 //usage:       IF_FEATURE_IP_ROUTE("route | ")
 //usage:       IF_FEATURE_IP_LINK("link | ")
 //usage:       IF_FEATURE_IP_TUNNEL("tunnel | ")
+//usage:       IF_FEATURE_IP_NEIGH("neigh | ")
 //usage:       IF_FEATURE_IP_RULE("rule")
 //usage:       "}\n"
 //usage:       "OPTIONS := { -f[amily] { inet | inet6 | link } | -o[neline] }"
 //usage:       "       [mode { ipip | gre | sit }] [remote ADDR] [local ADDR]\n"
 //usage:       "       [[i|o]seq] [[i|o]key KEY] [[i|o]csum]\n"
 //usage:       "       [ttl TTL] [tos TOS] [[no]pmtudisc] [dev PHYS_DEV]"
+//usage:
+//usage:#define ipneigh_trivial_usage
+//usage:       "{ show | flush} [ to PREFIX ] [ dev DEV ] [ nud STATE ]"
+//usage:#define ipneigh_full_usage "\n\n"
+//usage:       "ipneigh { show | flush} [ to PREFIX ] [ dev DEV ] [ nud STATE ]"
 
 #include "libbb.h"
 
@@ -90,7 +97,8 @@
  || ENABLE_FEATURE_IP_ROUTE \
  || ENABLE_FEATURE_IP_LINK \
  || ENABLE_FEATURE_IP_TUNNEL \
- || ENABLE_FEATURE_IP_RULE
+ || ENABLE_FEATURE_IP_RULE \
+ || ENABLE_FEATURE_IP_NEIGH
 
 static int FAST_FUNC ip_print_help(char **argv UNUSED_PARAM)
 {
@@ -140,6 +148,13 @@ int iptunnel_main(int argc UNUSED_PARAM, char **argv)
        return ip_do(do_iptunnel, argv);
 }
 #endif
+#if ENABLE_FEATURE_IP_NEIGH
+int ipneigh_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ipneigh_main(int argc UNUSED_PARAM, char **argv)
+{
+       return ip_do(do_ipneigh, argv);
+}
+#endif
 
 
 int ip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
@@ -153,6 +168,7 @@ int ip_main(int argc UNUSED_PARAM, char **argv)
                IF_FEATURE_IP_TUNNEL("tunnel\0")
                IF_FEATURE_IP_TUNNEL("tunl\0")
                IF_FEATURE_IP_RULE("rule\0")
+               IF_FEATURE_IP_NEIGH("neigh\0")
                ;
        static const ip_func_ptr_t ip_func_ptrs[] = {
                ip_print_help,
@@ -163,6 +179,7 @@ int ip_main(int argc UNUSED_PARAM, char **argv)
                IF_FEATURE_IP_TUNNEL(do_iptunnel,)
                IF_FEATURE_IP_TUNNEL(do_iptunnel,)
                IF_FEATURE_IP_RULE(do_iprule,)
+               IF_FEATURE_IP_NEIGH(do_ipneigh,)
        };
        ip_func_ptr_t ip_func;
        int key;
index 7c78f3c6a5cdead876b9ced4e254dfc681204c8c..c20e2fee863bcce7595dd07fa848c4b10aa63d8f 100644 (file)
@@ -64,3 +64,11 @@ lib-$(CONFIG_FEATURE_IP_RULE) += \
        iprule.o \
        rt_names.o \
        utils.o
+
+lib-$(CONFIG_FEATURE_IP_NEIGH) += \
+       ip_parse_common_args.o \
+       ipneigh.o \
+       libnetlink.o \
+       ll_map.o \
+       rt_names.o \
+       utils.o
index 30c7e595b59ca7ebcd609bdb29a83f595238ef8c..40171bed9aa58e38ddfedb7c748efdcfdf73cdae 100644 (file)
@@ -24,7 +24,7 @@ int FAST_FUNC ipaddr_list_or_flush(char **argv, int flush);
 int FAST_FUNC do_ipaddr(char **argv);
 int FAST_FUNC do_iproute(char **argv);
 int FAST_FUNC do_iprule(char **argv);
-//int FAST_FUNC do_ipneigh(char **argv);
+int FAST_FUNC do_ipneigh(char **argv);
 int FAST_FUNC do_iptunnel(char **argv);
 int FAST_FUNC do_iplink(char **argv);
 //int FAST_FUNC do_ipmonitor(char **argv);
diff --git a/networking/libiproute/ipneigh.c b/networking/libiproute/ipneigh.c
new file mode 100644 (file)
index 0000000..03a15d8
--- /dev/null
@@ -0,0 +1,354 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under GPLv2 or later, see file LICENSE in this source tree.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Ported to Busybox by:  Curt Brune <curt@cumulusnetworks.com>
+ */
+
+#include "ip_common.h"  /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+#include <linux/neighbour.h>
+#include <net/if_arp.h>
+
+//static int xshow_stats = 3;
+enum { xshow_stats = 3 };
+
+static inline uint32_t rta_getattr_u32(const struct rtattr *rta)
+{
+       return *(uint32_t *)RTA_DATA(rta);
+}
+
+#ifndef RTAX_RTTVAR
+#define RTAX_RTTVAR RTAX_HOPS
+#endif
+
+
+struct filter_t {
+       int family;
+       int index;
+       int state;
+       int unused_only;
+       inet_prefix pfx;
+       int flushed;
+       char *flushb;
+       int flushp;
+       int flushe;
+       struct rtnl_handle *rth;
+} FIX_ALIASING;
+typedef struct filter_t filter_t;
+
+#define G_filter (*(filter_t*)&bb_common_bufsiz1)
+
+static int flush_update(void)
+{
+       if (rtnl_send(G_filter.rth, G_filter.flushb, G_filter.flushp) < 0) {
+               bb_perror_msg("can't send flush request");
+               return -1;
+       }
+       G_filter.flushp = 0;
+       return 0;
+}
+
+static unsigned nud_state_a2n(char *arg)
+{
+       static const char keywords[] ALIGN1 =
+               /* "ip neigh show/flush" parameters: */
+               "permanent\0" "reachable\0"   "noarp\0"  "none\0"
+               "stale\0"     "incomplete\0"  "delay\0"  "probe\0"
+               "failed\0"
+               ;
+       static uint8_t nuds[] = {
+               NUD_PERMANENT,NUD_REACHABLE, NUD_NOARP,NUD_NONE,
+               NUD_STALE,    NUD_INCOMPLETE,NUD_DELAY,NUD_PROBE,
+               NUD_FAILED
+       };
+       int id;
+
+       BUILD_BUG_ON(
+               (NUD_PERMANENT|NUD_REACHABLE| NUD_NOARP|NUD_NONE|
+               NUD_STALE|    NUD_INCOMPLETE|NUD_DELAY|NUD_PROBE|
+               NUD_FAILED) > 0xff
+       );
+
+       id = index_in_substrings(keywords, arg);
+       if (id < 0)
+               bb_error_msg_and_die(bb_msg_invalid_arg, arg, "nud state");
+       return nuds[id];
+}
+
+#ifndef NDA_RTA
+#define NDA_RTA(r) \
+       ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg))))
+#endif
+
+
+static int FAST_FUNC print_neigh(const struct sockaddr_nl *who UNUSED_PARAM,
+                                struct nlmsghdr *n, void *arg UNUSED_PARAM)
+{
+       struct ndmsg *r = NLMSG_DATA(n);
+       int len = n->nlmsg_len;
+       struct rtattr *tb[NDA_MAX+1];
+       char abuf[256];
+
+       if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH) {
+               bb_error_msg_and_die("not RTM_NEWNEIGH: %08x %08x %08x",
+                                    n->nlmsg_len, n->nlmsg_type,
+                                    n->nlmsg_flags);
+       }
+       len -= NLMSG_LENGTH(sizeof(*r));
+       if (len < 0) {
+               bb_error_msg_and_die("BUG: wrong nlmsg len %d", len);
+       }
+
+       if (G_filter.flushb && n->nlmsg_type != RTM_NEWNEIGH)
+               return 0;
+
+       if (G_filter.family && G_filter.family != r->ndm_family)
+               return 0;
+       if (G_filter.index && G_filter.index != r->ndm_ifindex)
+               return 0;
+       if (!(G_filter.state&r->ndm_state) &&
+           !(r->ndm_flags & NTF_PROXY) &&
+           (r->ndm_state || !(G_filter.state & 0x100)) &&
+           (r->ndm_family != AF_DECnet))
+               return 0;
+
+       parse_rtattr(tb, NDA_MAX, NDA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
+
+       if (tb[NDA_DST]) {
+               if (G_filter.pfx.family) {
+                       inet_prefix dst;
+                       memset(&dst, 0, sizeof(dst));
+                       dst.family = r->ndm_family;
+                       memcpy(&dst.data, RTA_DATA(tb[NDA_DST]), RTA_PAYLOAD(tb[NDA_DST]));
+                       if (inet_addr_match(&dst, &G_filter.pfx, G_filter.pfx.bitlen))
+                               return 0;
+               }
+       }
+       if (G_filter.unused_only && tb[NDA_CACHEINFO]) {
+               struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]);
+               if (ci->ndm_refcnt)
+                       return 0;
+       }
+
+       if (G_filter.flushb) {
+               struct nlmsghdr *fn;
+               if (NLMSG_ALIGN(G_filter.flushp) + n->nlmsg_len > G_filter.flushe) {
+                       if (flush_update())
+                               return -1;
+               }
+               fn = (struct nlmsghdr*)(G_filter.flushb + NLMSG_ALIGN(G_filter.flushp));
+               memcpy(fn, n, n->nlmsg_len);
+               fn->nlmsg_type = RTM_DELNEIGH;
+               fn->nlmsg_flags = NLM_F_REQUEST;
+               fn->nlmsg_seq = ++(G_filter.rth->seq);
+               G_filter.flushp = (((char*)fn) + n->nlmsg_len) - G_filter.flushb;
+               G_filter.flushed++;
+               if (xshow_stats < 2)
+                       return 0;
+       }
+
+       if (tb[NDA_DST]) {
+               printf("%s ",
+                      format_host(r->ndm_family,
+                                  RTA_PAYLOAD(tb[NDA_DST]),
+                                  RTA_DATA(tb[NDA_DST]),
+                                  abuf, sizeof(abuf)));
+       }
+       if (!G_filter.index && r->ndm_ifindex)
+               printf("dev %s ", ll_index_to_name(r->ndm_ifindex));
+       if (tb[NDA_LLADDR]) {
+               SPRINT_BUF(b1);
+               printf("lladdr %s", ll_addr_n2a(RTA_DATA(tb[NDA_LLADDR]),
+                                               RTA_PAYLOAD(tb[NDA_LLADDR]),
+                                               ARPHRD_ETHER,
+                                               b1, sizeof(b1)));
+       }
+       if (r->ndm_flags & NTF_ROUTER) {
+               printf(" router");
+       }
+       if (r->ndm_flags & NTF_PROXY) {
+               printf(" proxy");
+       }
+       if (tb[NDA_CACHEINFO] && xshow_stats) {
+               struct nda_cacheinfo *ci = RTA_DATA(tb[NDA_CACHEINFO]);
+               int hz = get_hz();
+
+               if (ci->ndm_refcnt)
+                       printf(" ref %d", ci->ndm_refcnt);
+               printf(" used %d/%d/%d", ci->ndm_used/hz,
+                      ci->ndm_confirmed/hz, ci->ndm_updated/hz);
+       }
+
+       if (tb[NDA_PROBES] && xshow_stats) {
+               uint32_t p = rta_getattr_u32(tb[NDA_PROBES]);
+               printf(" probes %u", p);
+       }
+
+       /*if (r->ndm_state)*/ {
+               int nud = r->ndm_state;
+               char c = ' ';
+#define PRINT_FLAG(f) \
+               if (nud & NUD_##f) { \
+                       printf("%c"#f, c); \
+                       c = ','; \
+               }
+               PRINT_FLAG(INCOMPLETE);
+               PRINT_FLAG(REACHABLE);
+               PRINT_FLAG(STALE);
+               PRINT_FLAG(DELAY);
+               PRINT_FLAG(PROBE);
+               PRINT_FLAG(FAILED);
+               PRINT_FLAG(NOARP);
+               PRINT_FLAG(PERMANENT);
+#undef PRINT_FLAG
+       }
+       bb_putchar('\n');
+
+       return 0;
+}
+
+static void ipneigh_reset_filter(void)
+{
+       memset(&G_filter, 0, sizeof(G_filter));
+       G_filter.state = ~0;
+}
+
+#define MAX_ROUNDS     10
+/* Return value becomes exitcode. It's okay to not return at all */
+static int FAST_FUNC ipneigh_list_or_flush(char **argv, int flush)
+{
+       static const char keywords[] ALIGN1 =
+               /* "ip neigh show/flush" parameters: */
+               "to\0" "dev\0"   "nud\0";
+       enum {
+               KW_to, KW_dev, KW_nud,
+       };
+       struct rtnl_handle rth;
+       struct ndmsg ndm = { 0 };
+       char *filter_dev = NULL;
+       int state_given = 0;
+       int arg;
+
+       ipneigh_reset_filter();
+
+       if (flush && !*argv)
+               bb_error_msg_and_die(bb_msg_requires_arg, "\"ip neigh flush\"");
+
+       if (!G_filter.family)
+               G_filter.family = preferred_family;
+
+       G_filter.state = (flush) ?
+               ~(NUD_PERMANENT|NUD_NOARP) : 0xFF & ~NUD_NOARP;
+
+       while (*argv) {
+               arg = index_in_substrings(keywords, *argv);
+               if (arg == KW_dev) {
+                       NEXT_ARG();
+                       filter_dev = *argv;
+               } else if (arg == KW_nud) {
+                       unsigned state;
+                       NEXT_ARG();
+                       if (!state_given) {
+                               state_given = 1;
+                               G_filter.state = 0;
+                       }
+                       if (strcmp(*argv, "all") == 0) {
+                               state = ~0;
+                               if (flush)
+                                       state &= ~NUD_NOARP;
+                       } else {
+                               state = nud_state_a2n(*argv);
+                       }
+                       if (state == 0)
+                               state = 0x100;
+                       G_filter.state |= state;
+               } else {
+                       if (arg == KW_to) {
+                               NEXT_ARG();
+                       }
+                       get_prefix(&G_filter.pfx, *argv, G_filter.family);
+                       if (G_filter.family == AF_UNSPEC)
+                               G_filter.family = G_filter.pfx.family;
+               }
+               argv++;
+       }
+
+       xrtnl_open(&rth);
+       ll_init_map(&rth);
+
+       if (filter_dev)  {
+               if ((G_filter.index = xll_name_to_index(filter_dev)) == 0) {
+                       bb_error_msg_and_die(bb_msg_invalid_arg,
+                                            filter_dev, "Cannot find device");
+               }
+       }
+
+       if (flush) {
+               int round = 0;
+               char flushb[4096-512];
+               G_filter.flushb = flushb;
+               G_filter.flushp = 0;
+               G_filter.flushe = sizeof(flushb);
+               G_filter.state &= ~NUD_FAILED;
+               G_filter.rth = &rth;
+
+               while (round < MAX_ROUNDS) {
+                       if (xrtnl_wilddump_request(&rth, G_filter.family, RTM_GETNEIGH) < 0) {
+                               bb_perror_msg_and_die("can't send dump request");
+                       }
+                       G_filter.flushed = 0;
+                       if (xrtnl_dump_filter(&rth, print_neigh, NULL) < 0) {
+                               bb_perror_msg_and_die("flush terminated");
+                       }
+                       if (G_filter.flushed == 0) {
+                               if (round == 0)
+                                       puts("Nothing to flush");
+                               else
+                                       printf("*** Flush is complete after %d round(s) ***\n", round);
+                               return 0;
+                       }
+                       round++;
+                       if (flush_update() < 0)
+                               xfunc_die();
+                       printf("\n*** Round %d, deleting %d entries ***\n", round, G_filter.flushed);
+               }
+               bb_error_msg_and_die("*** Flush not complete bailing out after %d rounds", MAX_ROUNDS);
+       }
+
+       ndm.ndm_family = G_filter.family;
+
+       if (rtnl_dump_request(&rth, RTM_GETNEIGH, &ndm, sizeof(struct ndmsg)) < 0) {
+               bb_perror_msg_and_die("can't send dump request");
+       }
+
+       if (xrtnl_dump_filter(&rth, print_neigh, NULL) < 0) {
+               bb_error_msg_and_die("dump terminated");
+       }
+
+       return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int FAST_FUNC do_ipneigh(char **argv)
+{
+       static const char ip_neigh_commands[] ALIGN1 =
+               /*0-1*/ "show\0"  "flush\0";
+       int command_num;
+
+       if (!*argv)
+               return ipneigh_list_or_flush(argv, 0);
+
+       command_num = index_in_substrings(ip_neigh_commands, *argv);
+       switch (command_num) {
+               case 0: /* show */
+                       return ipneigh_list_or_flush(argv + 1, 0);
+               case 1: /* flush */
+                       return ipneigh_list_or_flush(argv + 1, 1);
+       }
+       invarg(*argv, applet_name);
+       return 1;
+}
index 6ecd5f719760684f5df0564f7d1a48293d4b4b86..f7209b126616cdf6464cc90bb1bb29c1dc12bd87 100644 (file)
@@ -55,28 +55,6 @@ static int flush_update(void)
        return 0;
 }
 
-static unsigned get_hz(void)
-{
-       static unsigned hz_internal;
-       FILE *fp;
-
-       if (hz_internal)
-               return hz_internal;
-
-       fp = fopen_for_read("/proc/net/psched");
-       if (fp) {
-               unsigned nom, denom;
-
-               if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2)
-                       if (nom == 1000000)
-                               hz_internal = denom;
-               fclose(fp);
-       }
-       if (!hz_internal)
-               hz_internal = bb_clk_tck();
-       return hz_internal;
-}
-
 static int FAST_FUNC print_route(const struct sockaddr_nl *who UNUSED_PARAM,
                struct nlmsghdr *n, void *arg UNUSED_PARAM)
 {
@@ -217,7 +195,7 @@ static int FAST_FUNC print_route(const struct sockaddr_nl *who UNUSED_PARAM,
 
                if (NLMSG_ALIGN(G_filter.flushp) + n->nlmsg_len > G_filter.flushe) {
                        if (flush_update())
-                               bb_error_msg_and_die("flush");
+                               xfunc_die();
                }
                fn = (void*)(G_filter.flushb + NLMSG_ALIGN(G_filter.flushp));
                memcpy(fn, n, n->nlmsg_len);
@@ -954,7 +932,7 @@ int FAST_FUNC do_iproute(char **argv)
                case 11: /* flush */
                        return iproute_list_or_flush(argv+1, 1);
                default:
-                       bb_error_msg_and_die("unknown command %s", *argv);
+                       invarg(*argv, applet_name);
        }
 
        return iproute_modify(cmd, flags, argv+1);
index d0fe30605c815a3e9afc0c6ea02d59d7332b0dce..37b5311f0a6d7f4f97ba934a975a223d8f218f7a 100644 (file)
 #include "utils.h"
 #include "inet_common.h"
 
+unsigned get_hz(void)
+{
+       static unsigned hz_internal;
+       FILE *fp;
+
+       if (hz_internal)
+               return hz_internal;
+
+       fp = fopen_for_read("/proc/net/psched");
+       if (fp) {
+               unsigned nom, denom;
+
+               if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2)
+                       if (nom == 1000000)
+                               hz_internal = denom;
+               fclose(fp);
+       }
+       if (!hz_internal)
+               hz_internal = bb_clk_tck();
+       return hz_internal;
+}
+
 unsigned get_unsigned(char *arg, const char *errmsg)
 {
        unsigned long res;
index 5fb4a862cc81d3ab93ca8cdabde0a4bace41f45e..cd15b706be681e9d99d8a89dd7a0dc8e42c0cefe 100644 (file)
@@ -85,6 +85,8 @@ int dnet_pton(int af, const char *src, void *addr);
 const char *ipx_ntop(int af, const void *addr, char *str, size_t len);
 int ipx_pton(int af, const char *src, void *addr);
 
+unsigned get_hz(void);
+
 POP_SAVED_FUNCTION_VISIBILITY
 
 #endif