+static int arpinit_done;
+
+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)
+{
+ if (arpinit_done) {
+ log(LOG_NOTICE, "arp_init called more than once (ignored)\n");
+ return;
+ }
+
+ 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;
+
+ /* start timer */
+ timeout(arptimer, (caddr_t)0, hz);
+}
+
+static struct llinfo_arp *
+arp_llinfo_alloc(void)
+{
+ return (zalloc(llinfo_arp_zone));
+}
+
+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 */
+ }
+
+ /* Just in case there's anything there, free it */
+ if (la->la_hold != NULL) {
+ m_freem(la->la_hold);
+ la->la_hold = NULL;
+ }
+
+ /* 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_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);
+ }
+}
+
+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 (log_arp_warnings) {
+ char tmp[MAX_IPv4_STR_LEN];
+ u_int64_t now = net_uptime();
+
+ log(LOG_DEBUG, "%s%d: ARP probe(s) needed for %s; "
+ "%s [lastused %lld, lastrcvd %lld] secs ago\n",
+ lr->lr_ifp->if_name, lr->lr_ifp->if_unit, 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)
+{
+ 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 != lo_ifp &&
+ 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";
+ } else {
+ 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";
+ }
+ }
+
+ if (log_arp_warnings && lr != NULL && why != NULL) {
+ char tmp[MAX_IPv4_STR_LEN];
+
+ log(LOG_DEBUG, "%s%d: %s%s for %s\n", ifp->if_name,
+ ifp->if_unit, type, why, inet_ntop(AF_INET,
+ &SIN(rt_key(rt))->sin_addr, tmp, sizeof (tmp)));
+ }
+ }
+}
+