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