+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;
+};
+