Index: net/if.c
===================================================================
RCS file: /cvs/src/sys/net/if.c,v
diff -u -p -r1.762 if.c
--- net/if.c	3 Jan 2026 14:10:04 -0000	1.762
+++ net/if.c	17 Mar 2026 01:14:07 -0000
@@ -68,6 +68,8 @@
 #include "pf.h"
 #include "ppp.h"
 #include "pppoe.h"
+#include "if_wg.h"
+#include "kstat.h"
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -123,6 +125,10 @@
 #include <net/pfvar.h>
 #endif
 
+#if NKSTAT > 0
+#include <sys/kstat.h>
+#endif
+
 #include <sys/device.h>
 
 void	if_attachsetup(struct ifnet *);
@@ -164,6 +170,11 @@ void	ifa_print_all(void);
 
 void	if_qstart_compat(struct ifqueue *);
 
+#if NKSTAT > 0
+static void	if_kstat_attach(struct ifnet *);
+static void	if_kstat_detach(struct ifnet *);
+#endif
+
 struct softnet *
 	net_sn(unsigned int);
 
@@ -529,6 +540,10 @@ if_attachsetup(struct ifnet *ifp)
 
 	/* Announce the interface. */
 	rtm_ifannounce(ifp, IFAN_ARRIVAL);
+
+#if NKSTAT > 0
+	if_kstat_attach(ifp);
+#endif
 }
 
 /*
@@ -1056,7 +1071,6 @@ if_input_process(struct ifnet *ifp, stru
 	ml_init(&ns->ns_tcp6_ml);
 #endif
 
-	NET_LOCK_SHARED();
 	while ((m = ml_dequeue(ml)) != NULL)
 		(*ifp->if_input)(ifp, m, ns);
 
@@ -1071,6 +1085,10 @@ if_input_process(struct ifnet *ifp, stru
 				m_freem(m);
 		}
 
+		if (ml_empty(&ns->ns_proto))
+			break;
+
+		NET_LOCK_SHARED();
 		while ((m = ml_dequeue(&ns->ns_proto)) != NULL) {
 			smr_read_enter();
 			ifp = if_idxmap_get(m->m_pkthdr.ph_ifidx);
@@ -1086,8 +1104,8 @@ if_input_process(struct ifnet *ifp, stru
 #ifdef INET6
 		tcp_input_mlist(&ns->ns_tcp6_ml, AF_INET6);
 #endif
+		NET_UNLOCK_SHARED();
 	} while (!ml_empty(&ns->ns_input));
-	NET_UNLOCK_SHARED();
 }
 
 void
@@ -1286,6 +1304,10 @@ if_detach(struct ifnet *ifp)
 #endif
 
 	NET_LOCK();
+#if NKSTAT > 0
+	if_kstat_detach(ifp);
+#endif
+
 	s = splnet();
 	ifp->if_ioctl = if_detached_ioctl;
 	ifp->if_watchdog = NULL;
@@ -1887,6 +1909,7 @@ if_linkstate(struct ifnet *ifp)
 		rt_if_track(ifp);
 	}
 
+	ifp->if_nlinkstatech++;
 	if_hooks_run(&ifp->if_linkstatehooks);
 }
 
@@ -3004,6 +3027,130 @@ if_getdata(struct ifnet *ifp, struct if_
 		ifiq_add_data(ifiq, data);
 	}
 }
+
+#if NKSTAT
+struct if_kstat_data {
+	struct kstat_kv	kd_up;
+	struct kstat_kv	kd_link;
+	struct kstat_kv kd_nlinkch;
+	struct kstat_kv	kd_baudrate;
+	struct kstat_kv	kd_ibytes;
+	struct kstat_kv	kd_ipackets;
+	struct kstat_kv	kd_ierrors;
+	struct kstat_kv	kd_iqdrops;
+	struct kstat_kv	kd_obytes;
+	struct kstat_kv	kd_opackets;
+	struct kstat_kv	kd_oerrors;
+	struct kstat_kv	kd_oqdrops;
+};
+
+static const struct if_kstat_data if_kstat_data_template = {
+	KSTAT_KV_INITIALIZER("up", KSTAT_KV_T_BOOL),
+	KSTAT_KV_INITIALIZER("link", KSTAT_KV_T_BOOL),
+	KSTAT_KV_INITIALIZER("link-changes", KSTAT_KV_T_COUNTER32),
+	KSTAT_KV_UNIT_INITIALIZER("baudrate",
+	    KSTAT_KV_T_UINT64, KSTAT_KV_U_NONE),
+
+	KSTAT_KV_UNIT_INITIALIZER("ibytes",
+	    KSTAT_KV_T_COUNTER64, KSTAT_KV_U_BYTES),
+	KSTAT_KV_UNIT_INITIALIZER("ipackets",
+	    KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
+	KSTAT_KV_UNIT_INITIALIZER("ierrors",
+	    KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
+	KSTAT_KV_UNIT_INITIALIZER("iqdrops",
+	    KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
+
+	KSTAT_KV_UNIT_INITIALIZER("obytes",
+	    KSTAT_KV_T_COUNTER64, KSTAT_KV_U_BYTES),
+	KSTAT_KV_UNIT_INITIALIZER("opackets",
+	    KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
+	KSTAT_KV_UNIT_INITIALIZER("oerrors",
+	    KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
+	KSTAT_KV_UNIT_INITIALIZER("oqdrops",
+	    KSTAT_KV_T_COUNTER64, KSTAT_KV_U_PACKETS),
+};
+
+static int if_kstat_read(struct kstat *);
+
+static void
+if_kstat_attach(struct ifnet *ifp)
+{
+	struct kstat *ks;
+	struct if_kstat_data *kd;
+
+	kd = malloc(sizeof(*kd), M_DEVBUF, M_WAITOK|M_CANFAIL|M_ZERO);
+	if (kd == NULL)
+		return;
+
+	ks = kstat_create(ifp->if_xname, 0, "ifstat", 0, KSTAT_T_KV, 0);
+	if (ks == NULL) {
+		free(kd, M_DEVBUF, sizeof(*kd));
+		return;
+	}
+
+	*kd = if_kstat_data_template;
+
+	/* which lock? */
+	ks->ks_softc = ifp;
+	ks->ks_data = kd;
+	ks->ks_datalen = sizeof(*kd);
+	ks->ks_read = if_kstat_read;
+
+	ifp->if_kstat = ks;
+
+	kstat_install(ks);
+}
+
+static int
+if_kstat_read(struct kstat *ks)
+{
+	struct ifnet *ifp = ks->ks_softc;
+	struct if_kstat_data *kd = ks->ks_data;
+	struct if_data data;
+
+	memset(&data, 0, sizeof(data));
+
+	NET_LOCK_SHARED();
+	if_getdata(ifp, &data);
+
+	kstat_kv_bool(&kd->kd_up) = !!ISSET(ifp->if_flags, IFF_RUNNING);
+	kstat_kv_bool(&kd->kd_link) = LINK_STATE_IS_UP(ifp->if_link_state);
+	kstat_kv_u32(&kd->kd_nlinkch) = ifp->if_nlinkstatech;
+	NET_UNLOCK_SHARED();
+
+	nanouptime(&ks->ks_updated);
+
+	kstat_kv_u64(&kd->kd_baudrate) = data.ifi_baudrate;
+
+	kstat_kv_u64(&kd->kd_ibytes) = data.ifi_ibytes;
+	kstat_kv_u64(&kd->kd_ipackets) = data.ifi_ipackets;
+	kstat_kv_u64(&kd->kd_ierrors) = data.ifi_ierrors;
+	kstat_kv_u64(&kd->kd_iqdrops) = data.ifi_iqdrops;
+
+	kstat_kv_u64(&kd->kd_obytes) = data.ifi_obytes;
+	kstat_kv_u64(&kd->kd_opackets) = data.ifi_opackets;
+	kstat_kv_u64(&kd->kd_oerrors) = data.ifi_oerrors;
+	kstat_kv_u64(&kd->kd_oqdrops) = data.ifi_oqdrops;
+
+	return (0);
+}
+
+static void
+if_kstat_detach(struct ifnet *ifp)
+{
+	struct kstat *ks = ifp->if_kstat;
+	struct if_kstat_data *kd;
+
+	if (ks == NULL)
+		return;
+
+	kstat_remove(ks);
+	kd = ks->ks_data;
+	kstat_destroy(ks);
+
+	free(kd, M_DEVBUF, sizeof(*kd));
+}
+#endif
 
 /*
  * Dummy functions replaced in ifnet during detach (if protocols decide to
Index: net/if_tun.c
===================================================================
RCS file: /cvs/src/sys/net/if_tun.c,v
diff -u -p -r1.256 if_tun.c
--- net/if_tun.c	14 Dec 2025 01:51:26 -0000	1.256
+++ net/if_tun.c	17 Mar 2026 01:14:07 -0000
@@ -1092,7 +1092,6 @@ tun_input_process(struct ifnet *ifp, str
 		return;
 	}
 
-	NET_LOCK_SHARED();
 	/* use the ref we already have to process this first packet */
 	(*ifp->if_input)(ifp, m, ns);
 	/* if_vinput ends here */
@@ -1108,6 +1107,10 @@ tun_input_process(struct ifnet *ifp, str
 			if_put(ifp);
 		}
 
+		if (ml_empty(&ns->ns_proto))
+			break;
+
+		NET_LOCK_SHARED();
 		while ((m = ml_dequeue(&ns->ns_proto)) != NULL) {
 			ifp = if_get(m->m_pkthdr.ph_ifidx);
 			if (ifp != NULL)
@@ -1121,8 +1124,8 @@ tun_input_process(struct ifnet *ifp, str
 #ifdef INET6
 		tcp_input_mlist(&ns->ns_tcp6_ml, AF_INET6);
 #endif
+		NET_UNLOCK_SHARED();
 	} while (!ml_empty(&ns->ns_input));
-	NET_UNLOCK_SHARED();
 
 	rtfree(ns->ns_route.ro_rt);
 }
Index: net/if_var.h
===================================================================
RCS file: /cvs/src/sys/net/if_var.h,v
diff -u -p -r1.147 if_var.h
--- net/if_var.h	3 Jan 2026 14:10:04 -0000	1.147
+++ net/if_var.h	17 Mar 2026 01:14:07 -0000
@@ -88,8 +88,8 @@
 
 struct rtentry;
 struct ifnet;
-struct task;
 struct cpumem;
+struct kstat;
 
 struct netstack {
 	struct mbuf_list	 ns_input;
@@ -195,6 +195,7 @@ struct ifnet {				/* and the entries */
 	uint64_t if_data_counters[ifc_ncounters];
 
 	struct	cpumem *if_counters;	/* per cpu stats */
+	uint32_t if_nlinkstatech;	/* [N] number of link changes */
 	uint32_t if_hardmtu;		/* [d] maximum MTU device supports */
 	char	if_description[IFDESCRSIZE]; /* [c] interface description */
 	u_short	if_rtlabelid;		/* [c] next route label */
@@ -228,6 +229,7 @@ struct ifnet {				/* and the entries */
 	struct	ifiqueue if_rcv;	/* rx/input queue */
 	struct	ifiqueue **if_iqs;	/* [I] pointer to the array of iqs */
 	unsigned int if_niqs;		/* [I] number of input queues */
+	struct kstat *if_kstat;
 
 	struct sockaddr_dl *if_sadl;	/* [N] pointer to our sockaddr_dl */
 
