summaryrefslogtreecommitdiffstats
path: root/sys/netinet/if_ether.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/netinet/if_ether.c')
-rw-r--r--sys/netinet/if_ether.c129
1 files changed, 129 insertions, 0 deletions
diff --git a/sys/netinet/if_ether.c b/sys/netinet/if_ether.c
index 3a9d262..4631be5 100644
--- a/sys/netinet/if_ether.c
+++ b/sys/netinet/if_ether.c
@@ -131,6 +131,28 @@ SYSCTL_VNET_INT(_net_link_ether_inet, OID_AUTO, maxhold, CTLFLAG_RW,
&VNET_NAME(arp_maxhold), 0,
"Number of packets to hold per ARP entry");
+/*
+ * Due to the exponential backoff algorithm used for the interval between GARP
+ * retransmissions, the maximum number of retransmissions is limited for
+ * sanity. This limit corresponds to a maximum interval between retransmissions
+ * of 2^16 seconds ~= 18 hours.
+ *
+ * Making this limit more dynamic is more complicated than worthwhile,
+ * especially since sending out GARPs spaced days apart would be of little
+ * use. A maximum dynamic limit would look something like:
+ *
+ * const int max = fls(INT_MAX / hz) - 1;
+ */
+#define MAX_GARP_RETRANSMITS 16
+static int sysctl_garp_rexmit(SYSCTL_HANDLER_ARGS);
+static int garp_rexmit_count = 0; /* GARP retransmission setting. */
+
+SYSCTL_PROC(_net_link_ether_inet, OID_AUTO, garp_rexmit_count,
+ CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_MPSAFE,
+ &garp_rexmit_count, 0, sysctl_garp_rexmit, "I",
+ "Number of times to retransmit GARP packets;"
+ " 0 to disable, maximum of 16");
+
static void arp_init(void);
static void arpintr(struct mbuf *);
static void arptimer(void *);
@@ -955,6 +977,109 @@ drop:
}
#endif
+/*
+ * Handle the garp_rexmit_count. Like sysctl_handle_int(), but limits the range
+ * of valid values.
+ */
+static int
+sysctl_garp_rexmit(SYSCTL_HANDLER_ARGS)
+{
+ int error;
+ int rexmit_count = *(int *)arg1;
+
+ error = sysctl_handle_int(oidp, &rexmit_count, 0, req);
+
+ /* Enforce limits on any new value that may have been set. */
+ if (!error && req->newptr) {
+ /* A new value was set. */
+ if (rexmit_count < 0) {
+ rexmit_count = 0;
+ } else if (rexmit_count > MAX_GARP_RETRANSMITS) {
+ rexmit_count = MAX_GARP_RETRANSMITS;
+ }
+ *(int *)arg1 = rexmit_count;
+ }
+
+ return (error);
+}
+
+/*
+ * Retransmit a Gratuitous ARP (GARP) and, if necessary, schedule a callout to
+ * retransmit it again. A pending callout owns a reference to the ifa.
+ */
+static void
+garp_rexmit(void *arg)
+{
+ struct in_ifaddr *ia = arg;
+
+ if (callout_pending(&ia->ia_garp_timer) ||
+ !callout_active(&ia->ia_garp_timer)) {
+ IFA_UNLOCK(&ia->ia_ifa);
+ ifa_free(&ia->ia_ifa);
+ return;
+ }
+
+ /*
+ * Drop ifa lock while the ARP request is generated.
+ */
+ IFA_UNLOCK(&ia->ia_ifa);
+
+ arprequest(ia->ia_ifa.ifa_ifp, &IA_SIN(ia)->sin_addr,
+ &IA_SIN(ia)->sin_addr, IF_LLADDR(ia->ia_ifa.ifa_ifp));
+
+ /*
+ * Increment the count of retransmissions. If the count has reached the
+ * maximum value, stop sending the GARP packets. Otherwise, schedule
+ * the callout to retransmit another GARP packet.
+ */
+ ++ia->ia_garp_count;
+ if (ia->ia_garp_count >= garp_rexmit_count) {
+ ifa_free(&ia->ia_ifa);
+ } else {
+ int rescheduled;
+ IFA_LOCK(&ia->ia_ifa);
+ rescheduled = callout_reset(&ia->ia_garp_timer,
+ (1 << ia->ia_garp_count) * hz,
+ garp_rexmit, ia);
+ IFA_UNLOCK(&ia->ia_ifa);
+ if (rescheduled) {
+ ifa_free(&ia->ia_ifa);
+ }
+ }
+}
+
+/*
+ * Start the GARP retransmit timer.
+ *
+ * A single GARP is always transmitted when an IPv4 address is added
+ * to an interface and that is usually sufficient. However, in some
+ * circumstances, such as when a shared address is passed between
+ * cluster nodes, this single GARP may occasionally be dropped or
+ * lost. This can lead to neighbors on the network link working with a
+ * stale ARP cache and sending packets destined for that address to
+ * the node that previously owned the address, which may not respond.
+ *
+ * To avoid this situation, GARP retransmits can be enabled by setting
+ * the net.link.ether.inet.garp_rexmit_count sysctl to a value greater
+ * than zero. The setting represents the maximum number of
+ * retransmissions. The interval between retransmissions is calculated
+ * using an exponential backoff algorithm, doubling each time, so the
+ * retransmission intervals are: {1, 2, 4, 8, 16, ...} (seconds).
+ */
+static void
+garp_timer_start(struct ifaddr *ifa)
+{
+ struct in_ifaddr *ia = (struct in_ifaddr *) ifa;
+
+ IFA_LOCK(ifa);
+ ia->ia_garp_count = 0;
+ if (callout_reset(&ia->ia_garp_timer, (1 << ia->ia_garp_count) * hz,
+ garp_rexmit, ia) == 0) {
+ ifa_ref(ifa);
+ }
+ IFA_UNLOCK(ifa);
+}
+
void
arp_ifinit(struct ifnet *ifp, struct ifaddr *ifa)
{
@@ -966,6 +1091,10 @@ arp_ifinit(struct ifnet *ifp, struct ifaddr *ifa)
if (ntohl(IA_SIN(ifa)->sin_addr.s_addr) != INADDR_ANY) {
arprequest(ifp, &IA_SIN(ifa)->sin_addr,
&IA_SIN(ifa)->sin_addr, IF_LLADDR(ifp));
+ if (garp_rexmit_count > 0) {
+ garp_timer_start(ifa);
+ }
+
/*
* interface address is considered static entry
* because the output of the arp utility shows
OpenPOWER on IntegriCloud