Index: sys/kern/kern_sysctl.c =================================================================== --- sys/kern/kern_sysctl.c +++ sys/kern/kern_sysctl.c @@ -1134,7 +1134,7 @@ if ((oidp->oid_kind & CTLTYPE) != CTLTYPE_NODE) return (2); /* If node does not have an iterator, treat it as leaf */ - if (oidp->oid_handler) + if (!SYSCTL_NODE_CAN_ITERATE(oidp)) return (2); /* Report oid as a node to iterate */ @@ -1167,17 +1167,17 @@ */ if (!honor_skip) return (2); - if ((oidp->oid_kind & CTLTYPE) != CTLTYPE_NODE) + if (!SYSCTL_IS_NODE(oidp)) return (2); /* If node does not have an iterator, treat it as leaf */ - if (oidp->oid_handler) + if (!SYSCTL_NODE_CAN_ITERATE(oidp)) return (2); return (1); } /* exact match at a given level */ if ((oidp->oid_kind & CTLTYPE) != CTLTYPE_NODE) return (0); - if (oidp->oid_handler) + if (!SYSCTL_NODE_CAN_ITERATE(oidp)) return (0); return (1); @@ -1188,12 +1188,16 @@ * Returns true and fills in next oid data in @next and @len if oid is found. */ static bool -sysctl_sysctl_next_ls(struct sysctl_oid_list *lsp, int *name, u_int namelen, +sysctl_sysctl_next_ls(struct sysctl_oid_list *lsp, int *name, unsigned int namelen, int *next, int *len, int level, bool honor_skip) { struct sysctl_oid *oidp; bool success = false; - int ret; + int next_namelen, ret; + int *next_name; + + next_namelen = (namelen > 0) ? namelen - 1 : 0; + next_name = name + 1; SYSCTL_ASSERT_LOCKED(); SLIST_FOREACH(oidp, lsp, oid_link) { @@ -1206,28 +1210,30 @@ } /* ret == 1 means we need to iterate over node children */ - lsp = SYSCTL_CHILDREN(oidp); - if (namelen == 0) { - success = sysctl_sysctl_next_ls(lsp, NULL, 0, - next + 1, len, level + 1, honor_skip); + if (oidp->oid_handlers != NULL) { + success = oidp->oid_handlers->nextoid(oidp, + next_name, next_namelen, next + 1, len, level + 1, + honor_skip); } else { - success = sysctl_sysctl_next_ls(lsp, name + 1, namelen - 1, - next + 1, len, level + 1, honor_skip); - if (!success) { - - /* - * We maintain the invariant that current node oid - * is >= the oid provided in @name. - * As there are no usable children at this node, - * current node oid is strictly > than the requested - * oid. - * Hence, reduce namelen to 0 to allow for picking first - * nodes/leafs in the next node in list. - */ - namelen = 0; - } + success = sysctl_sysctl_next_ls(SYSCTL_CHILDREN(oidp), + next_name, next_namelen, next + 1, len, level + 1, + honor_skip); } - if (success) + if (!success) { + + /* + * We maintain the invariant that current node oid + * is >= the oid provided in @name. + * As there are no usable children at this node, + * current node oid is strictly > than the requested + * oid. + * Hence, reduce namelen to 0 to allow for picking first + * nodes/leafs in the next node in list. + */ + namelen = 0; + next_name = NULL; + next_namelen = 0; + } else break; } @@ -1304,8 +1310,11 @@ if ((oidp->oid_kind & CTLTYPE) != CTLTYPE_NODE) break; - if (oidp->oid_handler) + if (oidp->oid_handler) { + if (oidp->oid_handlers != NULL) + return (oidp->oid_handlers->name2oid(name, oid, len)); break; + } lsp = SYSCTL_CHILDREN(oidp); } @@ -1317,7 +1326,6 @@ { char *p; int error, oid[CTL_MAXNAME], len = 0; - struct sysctl_oid *op = NULL; struct rm_priotracker tracker; char buf[32]; @@ -1340,7 +1348,7 @@ p [req->newlen] = '\0'; SYSCTL_RLOCK(&tracker); - error = name2oid(p, oid, &len, &op); + error = name2oid(p, oid, &len, NULL); SYSCTL_RUNLOCK(&tracker); if (p != buf) @@ -1366,14 +1374,19 @@ { struct sysctl_oid *oid; struct rm_priotracker tracker; - int error; + int error, nindx; + unsigned int oid_kind; + const char *oid_fmt; + char oid_fmt_buf[8]; + int *name = (int *)arg1; + unsigned int namelen = (u_int)arg2; error = sysctl_wire_old_buffer(req, 0); if (error) return (error); SYSCTL_RLOCK(&tracker); - error = sysctl_find_oid(arg1, arg2, &oid, NULL, req); + error = sysctl_find_oid(arg1, arg2, &oid, &nindx, req); if (error) goto out; @@ -1381,10 +1394,21 @@ error = ENOENT; goto out; } - error = SYSCTL_OUT(req, &oid->oid_kind, sizeof(oid->oid_kind)); + if ((namelen > nindx) && SYSCTL_IS_NODE(oid) && (oid->oid_handlers != NULL)) { + error = oid->oid_handlers->oidfmt(oid, + &name[nindx], namelen - nindx, &oid_kind, + oid_fmt_buf, sizeof(oid_fmt_buf)); + if (error != 0) + goto out; + oid_fmt = oid_fmt_buf; + } else { + oid_kind = oid->oid_kind; + oid_fmt = oid->oid_fmt; + } + error = SYSCTL_OUT(req, &oid_kind, sizeof(oid_kind)); if (error) goto out; - error = SYSCTL_OUT(req, oid->oid_fmt, strlen(oid->oid_fmt) + 1); + error = SYSCTL_OUT(req, oid_fmt, strlen(oid_fmt) + 1); out: SYSCTL_RUNLOCK(&tracker); return (error); @@ -1451,6 +1475,96 @@ static SYSCTL_NODE(_sysctl, CTL_SYSCTL_OIDLABEL, oidlabel, CTLFLAG_RD | CTLFLAG_MPSAFE | CTLFLAG_CAPRD, sysctl_sysctl_oidlabel, ""); +/* + * Helper functions for versioned sysctls + */ + +static char * +print_oid_buf(char *buf, size_t bufsize, struct sysctl_oid *oidp) +{ + size_t rem = bufsize - 1; + struct sysctl_oid *curr; + char obuf[6]; + const char *name; + int len; + + buf[rem] = '\0'; + for (curr = oidp; curr != NULL; curr = SYSCTL_PARENT(curr)) { + len = strlen(curr->oid_name); + if (len == 0) { + snprintf(obuf, sizeof(obuf), "%d", curr->oid_number); + name = obuf; + len = strlen(name); + } else + name = curr->oid_name; + rem -= len; + if (curr != oidp) + rem -= 1; + if (rem < 0) + return (NULL); + memcpy(&buf[rem], name, len); + if (curr != oidp) + buf[rem + len] = '.'; + } + + return (&buf[rem]); +} + +static void +print_unversioned_warning(struct proc *p, struct sysctl_oid *oidp) +{ + char path[96], *oid_str; + + bzero(&path, sizeof(path)); + oid_str = print_oid_buf(path, sizeof(path), oidp); + if (oid_str == NULL) + oid_str = ""; + printf("WARNING: pid %d (%s) uses unversioned sysctl %s\n", + p->p_pid, p->p_comm, oid_str); +} + +uint32_t +sysctl_get_oid_version(SYSCTL_HANDLER_ARGS) +{ + int args = (int)arg2; + int *pargs = (int *)arg1; + uint32_t version; + + if (args > 0) { + version = (uint32_t)pargs[0]; + } else { + /* Compat */ + struct proc *p = curproc; + + if (P_OSREL_MAJOR(p->p_osrel) == P_OSREL_MAJOR(__FreeBSD_version)) + print_unversioned_warning(p, oidp); + version = 0; + } + + return (version); +} + +/* + * Copies MIN(structure_size, buffer_size) bytes of the structure + * to the userland-provided buffer. + * Simplest "default" handler to fill-in multiple verisions of + * append-only structures. + */ +int +sysctl_copyout_ver(SYSCTL_HANDLER_ARGS, void *new_data, size_t new_datalen) +{ + uint32_t version; + + version = sysctl_get_oid_version(oidp, arg1, arg2, req); + /* If version != 0 -> means it exists as supported sysctl */ + + printf("COPYOUT STRUCT %p len %lu REQUESTED VER %u\n", + new_data, new_datalen, version); + + return (SYSCTL_OUT(req, new_data, new_datalen)); +} + + /* * Default "handler" functions. */ Index: sys/net/vnet.h =================================================================== --- sys/net/vnet.h +++ sys/net/vnet.h @@ -145,6 +145,27 @@ SYSCTL_PROC(parent, nbr, name, \ CTLFLAG_VNET | CTLTYPE_OPAQUE | CTLFLAG_RW | CTLFLAG_NEEDGIANT, \ NULL, 0, array ## _sysctl, "I", desc) + +/* Versioned sysctl helper */ +#define VNET_PCPUSTAT_FUNC_VER(_type, _array, _func) \ +static int \ +_array##_sysctl_versioned(SYSCTL_HANDLER_ARGS) \ +{ \ + _type s; \ + CTASSERT((sizeof(_type) / sizeof(uint64_t)) == \ + (sizeof(VNET(_array)) / sizeof(counter_u64_t))); \ + COUNTER_ARRAY_COPY(VNET(_array), &s, sizeof(_type) / sizeof(uint64_t));\ + if (req->newptr) \ + COUNTER_ARRAY_ZERO(VNET(_array), \ + sizeof(_type) / sizeof(uint64_t)); \ + return (_func(oidp, arg1, arg2, req, &s, sizeof(_type))); \ +} +#define SYSCTL_VNET_PCPUSTAT_VER(parent,nbr,name,type,array,min_v,max_v,_h,desc)\ + VNET_PCPUSTAT_FUNC_VER(type, array, _h); \ + SYSCTL_OPAQUE_VER_HANDLERS(array, min_v, max_v); \ + SYSCTL_OPAQUE_VER(parent, nbr, name, type, array, desc); + + #endif /* SYSCTL_OID */ #ifdef VIMAGE Index: sys/netinet/icmp6.h =================================================================== --- sys/netinet/icmp6.h +++ sys/netinet/icmp6.h @@ -642,6 +642,7 @@ uint64_t icp6s_invlhlim; /* Invalid hop limit. */ uint64_t icp6s_spare[32]; }; +#define ICMP6STAT_VER 1 #ifdef _KERNEL #include Index: sys/netinet6/in6_proto.c =================================================================== --- sys/netinet6/in6_proto.c +++ sys/netinet6/in6_proto.c @@ -463,6 +463,32 @@ return (0); } +static int +icmp6_copyout_ver(SYSCTL_HANDLER_ARGS, void *new_data, size_t new_datalen) +{ + size_t sz = new_datalen; + +#define ICMP6STAT_VER1_DIFF sizeof(uint64_t) * (33 + 4) + + /* Reminder to change the code here when changing version */ + _Static_assert(ICMP6STAT_VER == 1, "icmp6_copyout_ver() requires fixing"); + uint32_t version = sysctl_get_oid_version(oidp, arg1, arg2, req); + switch(version) { + case ICMP6STAT_VER: + break; + case 0: + sz -= ICMP6STAT_VER1_DIFF; + break; + default: + return (ENOTSUP); + } + + printf("COPYOUT STRUCT %p len %lu REQUESTED VER %u sz %lu RES: %lu\n", + new_data, new_datalen, version, req->oldlen, sz); + + return (SYSCTL_OUT(req, new_data, sz)); +} + SYSCTL_INT(_net_inet6_ip6, IPV6CTL_FORWARDING, forwarding, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(ip6_forwarding), 0, "Enable forwarding of IPv6 packets between interfaces"); @@ -560,8 +586,8 @@ SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_REDIRTIMEOUT, redirtimeout, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(icmp6_redirtimeout), 0, "Delay in seconds before expiring redirect route"); -SYSCTL_VNET_PCPUSTAT(_net_inet6_icmp6, ICMPV6CTL_STATS, stats, - struct icmp6stat, icmp6stat, +SYSCTL_VNET_PCPUSTAT_VER(_net_inet6_icmp6, ICMPV6CTL_STATS, stats, + struct icmp6stat, icmp6stat, 0, ICMP6STAT_VER, icmp6_copyout_ver, "ICMPv6 statistics (struct icmp6stat, netinet/icmp6.h)"); SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_ND6_PRUNE, nd6_prune, CTLFLAG_VNET | CTLFLAG_RW, &VNET_NAME(nd6_prune), 0, Index: sys/sys/sysctl.h =================================================================== --- sys/sys/sysctl.h +++ sys/sys/sysctl.h @@ -181,6 +181,21 @@ SLIST_HEAD(sysctl_oid_list, sysctl_oid); +struct sysctl_oid; +typedef bool oid_handler_next_oid_t(struct sysctl_oid *oidp, + const int *name, int namelen, int *next, int *plen, int level, + bool honor_skip); +typedef int oid_handler_name2oid_t(char *name, int *oid, int *len); +typedef int oid_handler_oidfmt_t(struct sysctl_oid *oidp, + const int *name, int namelen, unsigned int *oid_kind, + char *oid_fmt, size_t fmt_sz); + +struct sysctl_node_handlers { + oid_handler_next_oid_t *nextoid; + oid_handler_name2oid_t *name2oid; + oid_handler_oidfmt_t *oidfmt; +}; + /* * This describes one "oid" in the MIB tree. Potentially more nodes can * be hidden behind it, expanded by the handler. @@ -191,7 +206,10 @@ SLIST_ENTRY(sysctl_oid) oid_link; int oid_number; u_int oid_kind; - void *oid_arg1; + union { + void *oid_arg1; /* !CTLTYPE_NODE */ + struct sysctl_node_handlers *oid_handlers; /* Node */ + }; intmax_t oid_arg2; const char *oid_name; int (*oid_handler)(SYSCTL_HANDLER_ARGS); @@ -230,6 +248,9 @@ int sysctl_dpcpu_long(SYSCTL_HANDLER_ARGS); int sysctl_dpcpu_quad(SYSCTL_HANDLER_ARGS); +uint32_t sysctl_get_oid_version(SYSCTL_HANDLER_ARGS); +int sysctl_copyout_ver(SYSCTL_HANDLER_ARGS, void *new_data, size_t new_datalen); + /* * These functions are used to add/remove an oid from the mib. */ @@ -262,6 +283,9 @@ #define SYSCTL_NODE_CHILDREN(parent, name) \ sysctl__##parent##_##name.oid_children +#define SYSCTL_IS_NODE(_n) (((_n)->oid_kind & CTLTYPE) == CTLTYPE_NODE) +#define SYSCTL_NODE_CAN_ITERATE(_n) \ + (((_n)->oid_handler == NULL) || ((_n)->oid_handlers != NULL)) #ifndef NO_SYSCTL_DESCR #define __DESCR(d) d @@ -331,8 +355,15 @@ SYSCTL_NODE_WITH_LABEL(parent, nbr, name, access, handler, descr, NULL) #define SYSCTL_NODE_WITH_LABEL(parent, nbr, name, access, handler, descr, label) \ + SYSCTL_NODE_RAW(parent, nbr, name, access, handler, NULL, descr, label) + +/* TODO: static checks on handlers */ +#define SYSCTL_NODE_WITH_HANDLERS(parent, nbr, name, access, handler, handlers, descr) \ + SYSCTL_NODE_RAW(parent, nbr, name, access, handler, handlers, descr, NULL) + +#define SYSCTL_NODE_RAW(parent, nbr, name, access, handler, handlers, descr, label) \ SYSCTL_OID_GLOBAL(parent, nbr, name, CTLTYPE_NODE|(access), \ - NULL, 0, handler, "N", descr, label); \ + handlers, 0, handler, "N", descr, label); \ SYSCTL_ENFORCE_FLAGS(access); \ CTASSERT(((access) & CTLTYPE) == 0 || \ ((access) & SYSCTL_CT_ASSERT_MASK) == CTLTYPE_NODE) @@ -905,6 +936,60 @@ NULL); \ }) +/* Opaque versioned structures helpers */ +#define SYSCTL_OPAQUE_VER_HANDLERS(_array,_min_v,_max_v) \ +static bool \ +_array##_sysctl_iterator(struct sysctl_oid *oidp, const int *name, int namelen,\ + int *next, int *plen, int level, bool honor_skip) \ +{ \ + if (namelen == 0) { \ + *next = _min_v; \ + *plen = level; \ + return (true); \ + } \ + if (*name >= _min_v && *name < _max_v) { \ + *next = *name + 1; \ + *plen = level; \ + return (true); \ + } \ + return (false); \ +} \ +static int \ +_array##_sysctl_oidfmt(struct sysctl_oid *oidp, const int *name, int namelen,\ + unsigned int *oid_kind, char *oidfmt, size_t oidfmt_sz) \ +{ \ + if ((namelen != 1) || (*name < _min_v) || (*name > _max_v)) \ + return (ENOENT); \ + *oid_kind = CTLTYPE_OPAQUE; \ + strlcpy(oidfmt, "S", oidfmt_sz); \ + return (0); \ +} \ +static int \ +_array##_sysctl_name2oid(char *name, int *oid, int *len) \ +{ \ + char *endptr; \ + int ret; \ + ret = strtol(name, &endptr, 10); \ + if ((ret >= _min_v) && (ret <= _max_v) && (*endptr == '\0')) { \ + *oid = ret; \ + (*len)++; \ + return (0); \ + } \ + return (ENOENT); \ +} \ +static struct sysctl_node_handlers _array##_sysctl_handlers = { \ + .nextoid = _array##_sysctl_iterator, \ + .name2oid = _array##_sysctl_name2oid, \ + .oidfmt = _array##_sysctl_oidfmt \ +}; \ + +#define SYSCTL_OPAQUE_VER(_parent, _nbr, _name, _type, _array, _desc) \ +SYSCTL_NODE_WITH_HANDLERS(_parent, _nbr, _name, \ + CTLFLAG_RW | CTLFLAG_MPSAFE, \ + _array##_sysctl_versioned, &_array##_sysctl_handlers, \ + _desc) + + /* * A macro to generate a read-only sysctl to indicate the presence of optional * kernel features. Index: usr.bin/netstat/inet6.c =================================================================== --- usr.bin/netstat/inet6.c +++ usr.bin/netstat/inet6.c @@ -950,8 +950,8 @@ struct icmp6stat icmp6stat; int i, first; - if (fetch_stats("net.inet6.icmp6.stats", off, &icmp6stat, - sizeof(icmp6stat), kread_counters) != 0) + if (fetch_stats("net.inet6.icmp6.stats." __XSTRING(ICMP6STAT_VER), + off, &icmp6stat, sizeof(icmp6stat), kread_counters) != 0) return; xo_emit("{T:/%s}:\n", name);