+static int arp_verbose;
+SYSCTL_INT(_net_link_ether_inet, OID_AUTO, verbose,
+ CTLFLAG_RW | CTLFLAG_LOCKED, &arp_verbose, 0, "");
+
+/*
+ * Generally protected by rnh_lock; use atomic operations on fields
+ * that are also modified outside of that lock (if needed).
+ */
+struct arpstat arpstat __attribute__((aligned(sizeof (uint64_t))));
+SYSCTL_PROC(_net_link_ether_inet, OID_AUTO, stats,
+ CTLTYPE_STRUCT | CTLFLAG_RD | CTLFLAG_LOCKED,
+ 0, 0, arp_getstat, "S,arpstat",
+ "ARP statistics (struct arpstat, net/if_arp.h)");
+
+static struct zone *llinfo_arp_zone;
+#define LLINFO_ARP_ZONE_MAX 256 /* maximum elements in zone */
+#define LLINFO_ARP_ZONE_NAME "llinfo_arp" /* name for zone */
+
+void
+arp_init(void)
+{
+ VERIFY(!arpinit_done);
+
+ LIST_INIT(&llinfo_arp);
+
+ llinfo_arp_zone = zinit(sizeof (struct llinfo_arp),
+ LLINFO_ARP_ZONE_MAX * sizeof (struct llinfo_arp), 0,
+ LLINFO_ARP_ZONE_NAME);
+ if (llinfo_arp_zone == NULL)
+ panic("%s: failed allocating llinfo_arp_zone", __func__);
+
+ zone_change(llinfo_arp_zone, Z_EXPAND, TRUE);
+ zone_change(llinfo_arp_zone, Z_CALLERACCT, FALSE);
+
+ arpinit_done = 1;
+}
+
+static struct llinfo_arp *
+arp_llinfo_alloc(int how)
+{
+ struct llinfo_arp *la;
+
+ la = (how == M_WAITOK) ? zalloc(llinfo_arp_zone) :
+ zalloc_noblock(llinfo_arp_zone);
+ if (la != NULL) {
+ bzero(la, sizeof (*la));
+ /*
+ * The type of queue (Q_DROPHEAD) here is just a hint;
+ * the actual logic that works on this queue performs
+ * a head drop, details in arp_llinfo_addq().
+ */
+ _qinit(&la->la_holdq, Q_DROPHEAD, (arp_maxhold == 0) ?
+ (uint32_t)-1 : arp_maxhold, QP_MBUF);
+ }
+
+ return (la);
+}
+
+static void
+arp_llinfo_free(void *arg)
+{
+ struct llinfo_arp *la = arg;
+
+ if (la->la_le.le_next != NULL || la->la_le.le_prev != NULL) {
+ panic("%s: trying to free %p when it is in use", __func__, la);
+ /* NOTREACHED */
+ }
+
+ /* Free any held packets */
+ (void) arp_llinfo_flushq(la);
+
+ /* Purge any link-layer info caching */
+ VERIFY(la->la_rt->rt_llinfo == la);
+ if (la->la_rt->rt_llinfo_purge != NULL)
+ la->la_rt->rt_llinfo_purge(la->la_rt);
+
+ zfree(llinfo_arp_zone, la);
+}
+
+static void
+arp_llinfo_addq(struct llinfo_arp *la, struct mbuf *m)
+{
+ if (qlen(&la->la_holdq) >= qlimit(&la->la_holdq)) {
+ struct mbuf *_m;
+ /* prune less than CTL, else take what's at the head */
+ _m = _getq_scidx_lt(&la->la_holdq, SCIDX_CTL);
+ if (_m == NULL)
+ _m = _getq(&la->la_holdq);
+ VERIFY(_m != NULL);
+ if (arp_verbose) {
+ log(LOG_DEBUG, "%s: dropping packet (scidx %u)\n",
+ __func__, MBUF_SCIDX(mbuf_get_service_class(_m)));
+ }
+ m_freem(_m);
+ atomic_add_32(&arpstat.dropped, 1);
+ atomic_add_32(&arpstat.held, -1);
+ }
+ _addq(&la->la_holdq, m);
+ atomic_add_32(&arpstat.held, 1);
+ if (arp_verbose) {
+ log(LOG_DEBUG, "%s: enqueued packet (scidx %u), qlen now %u\n",
+ __func__, MBUF_SCIDX(mbuf_get_service_class(m)),
+ qlen(&la->la_holdq));
+ }
+}
+
+static uint32_t
+arp_llinfo_flushq(struct llinfo_arp *la)
+{
+ uint32_t held = qlen(&la->la_holdq);
+
+ if (held != 0) {
+ atomic_add_32(&arpstat.purged, held);
+ atomic_add_32(&arpstat.held, -held);
+ _flushq(&la->la_holdq);
+ }
+ la->la_prbreq_cnt = 0;
+ VERIFY(qempty(&la->la_holdq));
+ return (held);
+}
+
+static void
+arp_llinfo_purge(struct rtentry *rt)
+{
+ struct llinfo_arp *la = rt->rt_llinfo;
+
+ RT_LOCK_ASSERT_HELD(rt);
+ VERIFY(rt->rt_llinfo_purge == arp_llinfo_purge && la != NULL);
+
+ if (la->la_llreach != NULL) {
+ RT_CONVERT_LOCK(rt);
+ ifnet_llreach_free(la->la_llreach);
+ la->la_llreach = NULL;
+ }
+ la->la_lastused = 0;
+}
+
+static void
+arp_llinfo_get_ri(struct rtentry *rt, struct rt_reach_info *ri)
+{
+ struct llinfo_arp *la = rt->rt_llinfo;
+ struct if_llreach *lr = la->la_llreach;
+
+ if (lr == NULL) {
+ bzero(ri, sizeof (*ri));
+ ri->ri_rssi = IFNET_RSSI_UNKNOWN;
+ ri->ri_lqm = IFNET_LQM_THRESH_OFF;
+ ri->ri_npm = IFNET_NPM_THRESH_UNKNOWN;
+ } else {
+ IFLR_LOCK(lr);
+ /* Export to rt_reach_info structure */
+ ifnet_lr2ri(lr, ri);
+ /* Export ARP send expiration (calendar) time */
+ ri->ri_snd_expire =
+ ifnet_llreach_up2calexp(lr, la->la_lastused);
+ IFLR_UNLOCK(lr);
+ }
+}
+
+static void
+arp_llinfo_get_iflri(struct rtentry *rt, struct ifnet_llreach_info *iflri)
+{
+ struct llinfo_arp *la = rt->rt_llinfo;
+ struct if_llreach *lr = la->la_llreach;
+
+ if (lr == NULL) {
+ bzero(iflri, sizeof (*iflri));
+ iflri->iflri_rssi = IFNET_RSSI_UNKNOWN;
+ iflri->iflri_lqm = IFNET_LQM_THRESH_OFF;
+ iflri->iflri_npm = IFNET_NPM_THRESH_UNKNOWN;
+ } else {
+ IFLR_LOCK(lr);
+ /* Export to ifnet_llreach_info structure */
+ ifnet_lr2iflri(lr, iflri);
+ /* Export ARP send expiration (uptime) time */
+ iflri->iflri_snd_expire =
+ ifnet_llreach_up2upexp(lr, la->la_lastused);
+ IFLR_UNLOCK(lr);
+ }
+}
+
+static void
+arp_llinfo_refresh(struct rtentry *rt)
+{
+ uint64_t timenow = net_uptime();
+ /*
+ * If route entry is permanent or if expiry is less
+ * than timenow and extra time taken for unicast probe
+ * we can't expedite the refresh
+ */
+ if ((rt->rt_expire == 0) ||
+ (rt->rt_flags & RTF_STATIC) ||
+ !(rt->rt_flags & RTF_LLINFO)) {
+ return;
+ }
+
+ if (rt->rt_expire > timenow)
+ rt->rt_expire = timenow;
+ return;
+}
+
+void
+arp_llreach_set_reachable(struct ifnet *ifp, void *addr, unsigned int alen)
+{
+ /* Nothing more to do if it's disabled */
+ if (arp_llreach_base == 0)
+ return;
+
+ ifnet_llreach_set_reachable(ifp, ETHERTYPE_IP, addr, alen);
+}
+
+static __inline void
+arp_llreach_use(struct llinfo_arp *la)
+{
+ if (la->la_llreach != NULL)
+ la->la_lastused = net_uptime();
+}
+
+static __inline int
+arp_llreach_reachable(struct llinfo_arp *la)
+{
+ struct if_llreach *lr;
+ const char *why = NULL;
+
+ /* Nothing more to do if it's disabled; pretend it's reachable */
+ if (arp_llreach_base == 0)
+ return (1);
+
+ if ((lr = la->la_llreach) == NULL) {
+ /*
+ * Link-layer reachability record isn't present for this
+ * ARP entry; pretend it's reachable and use it as is.
+ */
+ return (1);
+ } else if (ifnet_llreach_reachable(lr)) {
+ /*
+ * Record is present, it's not shared with other ARP
+ * entries and a packet has recently been received
+ * from the remote host; consider it reachable.
+ */
+ if (lr->lr_reqcnt == 1)
+ return (1);
+
+ /* Prime it up, if this is the first time */
+ if (la->la_lastused == 0) {
+ VERIFY(la->la_llreach != NULL);
+ arp_llreach_use(la);
+ }
+
+ /*
+ * Record is present and shared with one or more ARP
+ * entries, and a packet has recently been received
+ * from the remote host. Since it's shared by more
+ * than one IP addresses, we can't rely on the link-
+ * layer reachability alone; consider it reachable if
+ * this ARP entry has been used "recently."
+ */
+ if (ifnet_llreach_reachable_delta(lr, la->la_lastused))
+ return (1);
+
+ why = "has alias(es) and hasn't been used in a while";
+ } else {
+ why = "haven't heard from it in a while";
+ }
+
+ if (arp_verbose > 1) {
+ char tmp[MAX_IPv4_STR_LEN];
+ u_int64_t now = net_uptime();
+
+ log(LOG_DEBUG, "%s: ARP probe(s) needed for %s; "
+ "%s [lastused %lld, lastrcvd %lld] secs ago\n",
+ if_name(lr->lr_ifp), inet_ntop(AF_INET,
+ &SIN(rt_key(la->la_rt))->sin_addr, tmp, sizeof (tmp)), why,
+ (la->la_lastused ? (int64_t)(now - la->la_lastused) : -1),
+ (lr->lr_lastrcvd ? (int64_t)(now - lr->lr_lastrcvd) : -1));
+
+ }
+ return (0);
+}
+
+/*
+ * Obtain a link-layer source cache entry for the sender.
+ *
+ * NOTE: This is currently only for ARP/Ethernet.
+ */
+static void
+arp_llreach_alloc(struct rtentry *rt, struct ifnet *ifp, void *addr,
+ unsigned int alen, boolean_t solicited, uint32_t *p_rt_event_code)
+{
+ VERIFY(rt->rt_expire == 0 || rt->rt_rmx.rmx_expire != 0);
+ VERIFY(rt->rt_expire != 0 || rt->rt_rmx.rmx_expire == 0);
+
+ if (arp_llreach_base != 0 && rt->rt_expire != 0 &&
+ !(rt->rt_ifp->if_flags & IFF_LOOPBACK) &&
+ ifp->if_addrlen == IF_LLREACH_MAXLEN && /* Ethernet */
+ alen == ifp->if_addrlen) {
+ struct llinfo_arp *la = rt->rt_llinfo;
+ struct if_llreach *lr;
+ const char *why = NULL, *type = "";
+
+ /* Become a regular mutex, just in case */
+ RT_CONVERT_LOCK(rt);
+
+ if ((lr = la->la_llreach) != NULL) {
+ type = (solicited ? "ARP reply" : "ARP announcement");
+ /*
+ * If target has changed, create a new record;
+ * otherwise keep existing record.
+ */
+ IFLR_LOCK(lr);
+ if (bcmp(addr, lr->lr_key.addr, alen) != 0) {
+ IFLR_UNLOCK(lr);
+ /* Purge any link-layer info caching */
+ VERIFY(rt->rt_llinfo_purge != NULL);
+ rt->rt_llinfo_purge(rt);
+ lr = NULL;
+ why = " for different target HW address; "
+ "using new llreach record";
+ *p_rt_event_code = ROUTE_LLENTRY_CHANGED;
+ } else {
+ /*
+ * If we were doing unicast probing, we need to
+ * deliver an event for neighbor cache resolution
+ */
+ if (lr->lr_probes != 0)
+ *p_rt_event_code = ROUTE_LLENTRY_RESOLVED;
+
+ lr->lr_probes = 0; /* reset probe count */
+ IFLR_UNLOCK(lr);
+ if (solicited) {
+ why = " for same target HW address; "
+ "keeping existing llreach record";
+ }
+ }
+ }
+
+ if (lr == NULL) {
+ lr = la->la_llreach = ifnet_llreach_alloc(ifp,
+ ETHERTYPE_IP, addr, alen, arp_llreach_base);
+ if (lr != NULL) {
+ lr->lr_probes = 0; /* reset probe count */
+ if (why == NULL)
+ why = "creating new llreach record";
+ }
+ *p_rt_event_code = ROUTE_LLENTRY_RESOLVED;
+ }
+
+ if (arp_verbose > 1 && lr != NULL && why != NULL) {
+ char tmp[MAX_IPv4_STR_LEN];
+
+ log(LOG_DEBUG, "%s: %s%s for %s\n", if_name(ifp),
+ type, why, inet_ntop(AF_INET,
+ &SIN(rt_key(rt))->sin_addr, tmp, sizeof (tmp)));
+ }
+ }
+}
+
+struct arptf_arg {
+ boolean_t draining;
+ boolean_t probing;
+ uint32_t killed;
+ uint32_t aging;
+ uint32_t sticky;
+ uint32_t found;
+ uint32_t qlen;
+ uint32_t qsize;
+};