Index: sys/netinet6/in6.c =================================================================== --- sys/netinet6/in6.c +++ sys/netinet6/in6.c @@ -104,6 +104,7 @@ #include #include +#include #include #include #include @@ -125,6 +126,14 @@ VNET_DECLARE(int, icmp6_nodeinfo_oldmcprefix); #define V_icmp6_nodeinfo_oldmcprefix VNET(icmp6_nodeinfo_oldmcprefix) +SYSCTL_DECL(_net_inet6_icmp6); + +static int in6_llt_max_entries; +SYSCTL_INT(_net_inet6_icmp6, OID_AUTO, in6_llt_max_entries, + CTLFLAG_RW, &in6_llt_max_entries, 0, + "Maximum number of IPv6 neighbour entries per interface. " + "0 accept unlimited."); + /* * Definitions of some costant IP6 addresses. */ @@ -164,7 +173,22 @@ #define ifa2ia6(ifa) ((struct in6_ifaddr *)(ifa)) #define ia62ifa(ia6) (&((ia6)->ia_ifa)) +void +in6_init(void) +{ + /* + * physmem is the number of physical memory in pages. This means that + * up to 16GB we are scaling the number of table entries according to + * memory (1 table entry for each MB of physical memory). That way a + * small devices might not be overloaded as quickly in case of a flood + * as would big servers be. We ensure that there is a minimum of 128 + * entries in all cases per-interface and cap the default value at + * 16k neighbour entries to avoid the DoS problem. + */ + in6_llt_max_entries = max(128, min(physmem / 256, 16384)); +} + void in6_newaddrmsg(struct in6_ifaddr *ia, int cmd) { @@ -2420,6 +2444,8 @@ llt = lltable_allocate_htbl(IN6_LLTBL_DEFAULT_HSIZE); llt->llt_af = AF_INET6; llt->llt_ifp = ifp; + /* XXX-BZ re-think SYSCTL_PROC to update? Make it more integrated? */ + llt->llt_maxentries = in6_llt_max_entries; llt->llt_lookup = in6_lltable_lookup; llt->llt_alloc_entry = in6_lltable_alloc; Index: sys/netinet6/nd6.h =================================================================== --- sys/netinet6/nd6.h +++ sys/netinet6/nd6.h @@ -352,6 +352,9 @@ #define nd_opts_last nd_opt_each.last #define nd_opts_done nd_opt_each.done +/* in6.c */ +void in6_init(void); + /* XXX: need nd6_var.h?? */ /* nd6.c */ void nd6_init(void); Index: sys/netinet6/nd6.c =================================================================== --- sys/netinet6/nd6.c +++ sys/netinet6/nd6.c @@ -231,6 +231,8 @@ nd6_dad_init(); if (IS_DEFAULT_VNET(curvnet)) { + in6_init(); + lle_event_eh = EVENTHANDLER_REGISTER(lle_event, nd6_lle_event, NULL, EVENTHANDLER_PRI_ANY); iflladdr_event_eh = EVENTHANDLER_REGISTER(iflladdr_event, @@ -1929,7 +1931,7 @@ struct mbuf *chain = NULL; u_char linkhdr[LLE_MAX_LINKHDR]; size_t linkhdrsize; - int lladdr_off; + int linked, lladdr_off; NET_EPOCH_ASSERT(); IF_AFDATA_UNLOCK_ASSERT(ifp); @@ -1976,8 +1978,14 @@ LLE_WLOCK(ln); /* Prefer any existing lle over newly-created one */ ln_tmp = nd6_lookup(from, LLE_EXCLUSIVE, ifp); - if (ln_tmp == NULL) - lltable_link_entry(LLTABLE6(ifp), ln); + if (ln_tmp == NULL) { + linked = lltable_link_entry(LLTABLE6(ifp), ln); + if (linked < 0) { + IF_AFDATA_WUNLOCK(ifp); + lltable_free_entry(LLTABLE6(ifp), ln); + return; + } + } IF_AFDATA_WUNLOCK(ifp); if (ln_tmp == NULL) { /* No existing lle, mark as new entry (6,7) */ @@ -2285,7 +2293,7 @@ { struct llentry *lle = NULL, *lle_tmp; struct in6_addr *psrc, src; - int send_ns, ll_len; + int linked, send_ns, ll_len; char *lladdr; NET_EPOCH_ASSERT(); @@ -2320,8 +2328,12 @@ /* Prefer any existing entry over newly-created one */ lle_tmp = nd6_lookup(&dst->sin6_addr, LLE_EXCLUSIVE, ifp); if (lle_tmp == NULL) - lltable_link_entry(LLTABLE6(ifp), lle); + linked = lltable_link_entry(LLTABLE6(ifp), lle); IF_AFDATA_WUNLOCK(ifp); + if (lle_tmp == NULL && linked < 0) { + lltable_free_entry(LLTABLE6(ifp), lle); + lle = NULL; + } if (lle_tmp != NULL) { lltable_free_entry(LLTABLE6(ifp), lle); lle = lle_tmp; @@ -2506,6 +2518,7 @@ struct ifnet *ifp; struct llentry *ln, *ln_tmp; struct sockaddr *dst; + int error, linked; ifp = ia->ia_ifa.ifa_ifp; if (nd6_need_cache(ifp) == 0) @@ -2517,24 +2530,44 @@ if (ln == NULL) return (ENOBUFS); + error = 0; IF_AFDATA_WLOCK(ifp); LLE_WLOCK(ln); - /* Unlink any entry if exists */ + /* + * Historically we would unlink any entry if it existed and then link + * the new one. Given we could still fail linking after unlink (e.g., + * if the table limits were lowered) we are going through some extra + * complications here so that we do not change the status-quo in case + * we cannot link the new entry. + * It may seem that we could link before lookup but that could change + * out later lookup result. This is why the order is: lookup, link, + * unlink, announce (in the non-error case). + */ ln_tmp = lla_lookup(LLTABLE6(ifp), LLE_EXCLUSIVE, dst); - if (ln_tmp != NULL) - lltable_unlink_entry(LLTABLE6(ifp), ln_tmp); - lltable_link_entry(LLTABLE6(ifp), ln); + linked = lltable_link_entry(LLTABLE6(ifp), ln); + if (linked < 0) { + lltable_free_entry(LLTABLE6(ifp), ln); + error = ENOBUFS; + } + if (ln_tmp != NULL) { + if (error == 0) + lltable_unlink_entry(LLTABLE6(ifp), ln_tmp); + else + LLE_WUNLOCK(ln_tmp); + } IF_AFDATA_WUNLOCK(ifp); - if (ln_tmp != NULL) - EVENTHANDLER_INVOKE(lle_event, ln_tmp, LLENTRY_EXPIRED); - EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_RESOLVED); + if (error == 0) { + if (ln_tmp != NULL) + EVENTHANDLER_INVOKE(lle_event, ln_tmp, LLENTRY_EXPIRED); + EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_RESOLVED); - LLE_WUNLOCK(ln); - if (ln_tmp != NULL) - llentry_free(ln_tmp); + LLE_WUNLOCK(ln); + if (ln_tmp != NULL) + llentry_free(ln_tmp); + } - return (0); + return (error); } /*