]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[ipv4] Send gratuitous ARPs whenever a new IPv4 address is applied
authorMichael Brown <mcb30@ipxe.org>
Tue, 12 Jul 2016 07:47:27 +0000 (08:47 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 12 Jul 2016 08:01:01 +0000 (09:01 +0100)
In a busy network (such as a public cloud), IPv4 addresses may be
recycled rapidly.  When this happens, unidirectional traffic (such as
UDP syslog) will succeed, but bidirectional traffic (such as TCP
connections) may fail due to stale ARP cache entries on other nodes.
The remote ARP cache expiry timeout is likely to exceed iPXE's
connection timeout, meaning that boot attempts can fail before the
problem is automatically resolved.

Fix by sending gratuitous ARPs whenever an IPv4 address is changed, to
attempt to update stale remote ARP cache entries.  Note that this is
not a guaranteed fix, since ARP is an unreliable protocol.

We avoid sending gratuitous ARPs unconditionally, since otherwise any
unrelated settings change (e.g. "set dns 192.168.0.1") would cause
unexpected gratuitous ARPs to be sent.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/net/ipv4.c

index 8eb04a65f7dfd5aff30d66dab9084d0ced9dff66..865cfb60fd57212e6c7bad81e4bb9261c2e26964 100644 (file)
@@ -79,11 +79,11 @@ static struct profiler ipv4_rx_profiler __profiler = { .name = "ipv4.rx" };
  * @v address          IPv4 address
  * @v netmask          Subnet mask
  * @v gateway          Gateway address (if any)
- * @ret miniroute      Routing table entry, or NULL
+ * @ret rc             Return status code
  */
-static struct ipv4_miniroute * __malloc
-add_ipv4_miniroute ( struct net_device *netdev, struct in_addr address,
-                    struct in_addr netmask, struct in_addr gateway ) {
+static int add_ipv4_miniroute ( struct net_device *netdev,
+                               struct in_addr address, struct in_addr netmask,
+                               struct in_addr gateway ) {
        struct ipv4_miniroute *miniroute;
 
        DBGC ( netdev, "IPv4 add %s", inet_ntoa ( address ) );
@@ -96,7 +96,7 @@ add_ipv4_miniroute ( struct net_device *netdev, struct in_addr address,
        miniroute = malloc ( sizeof ( *miniroute ) );
        if ( ! miniroute ) {
                DBGC ( netdev, "IPv4 could not add miniroute\n" );
-               return NULL;
+               return -ENOMEM;
        }
 
        /* Record routing information */
@@ -114,7 +114,7 @@ add_ipv4_miniroute ( struct net_device *netdev, struct in_addr address,
                list_add ( &miniroute->list, &ipv4_miniroutes );
        }
 
-       return miniroute;
+       return 0;
 }
 
 /**
@@ -812,33 +812,69 @@ const struct setting gateway_setting __setting ( SETTING_IP, gateway ) = {
 };
 
 /**
- * Create IPv4 routing table based on configured settings
+ * Send gratuitous ARP, if applicable
  *
+ * @v netdev           Network device
+ * @v address          IPv4 address
+ * @v netmask          Subnet mask
+ * @v gateway          Gateway address (if any)
  * @ret rc             Return status code
  */
-static int ipv4_create_routes ( void ) {
-       struct ipv4_miniroute *miniroute;
-       struct ipv4_miniroute *tmp;
+static int ipv4_gratuitous_arp ( struct net_device *netdev,
+                                struct in_addr address,
+                                struct in_addr netmask __unused,
+                                struct in_addr gateway __unused ) {
+       int rc;
+
+       /* Do nothing if network device already has this IPv4 address */
+       if ( ipv4_has_addr ( netdev, address ) )
+               return 0;
+
+       /* Transmit gratuitous ARP */
+       DBGC ( netdev, "IPv4 sending gratuitous ARP for %s via %s\n",
+              inet_ntoa ( address ), netdev->name );
+       if ( ( rc = arp_tx_request ( netdev, &ipv4_protocol, &address,
+                                    &address ) ) != 0 ) {
+               DBGC ( netdev, "IPv4 could not transmit gratuitous ARP: %s\n",
+                      strerror ( rc ) );
+               /* Treat failures as non-fatal */
+       }
+
+       return 0;
+}
+
+/**
+ * Process IPv4 network device settings
+ *
+ * @v apply            Application method
+ * @ret rc             Return status code
+ */
+static int ipv4_settings ( int ( * apply ) ( struct net_device *netdev,
+                                            struct in_addr address,
+                                            struct in_addr netmask,
+                                            struct in_addr gateway ) ) {
        struct net_device *netdev;
        struct settings *settings;
        struct in_addr address = { 0 };
        struct in_addr netmask = { 0 };
        struct in_addr gateway = { 0 };
+       int rc;
 
-       /* Delete all existing routes */
-       list_for_each_entry_safe ( miniroute, tmp, &ipv4_miniroutes, list )
-               del_ipv4_miniroute ( miniroute );
-
-       /* Create a route for each configured network device */
+       /* Process settings for each network device */
        for_each_netdev ( netdev ) {
+
+               /* Get network device settings */
                settings = netdev_settings ( netdev );
+
                /* Get IPv4 address */
                address.s_addr = 0;
                fetch_ipv4_setting ( settings, &ip_setting, &address );
                if ( ! address.s_addr )
                        continue;
+
                /* Get subnet mask */
                fetch_ipv4_setting ( settings, &netmask_setting, &netmask );
+
                /* Calculate default netmask, if necessary */
                if ( ! netmask.s_addr ) {
                        if ( IN_IS_CLASSA ( address.s_addr ) ) {
@@ -849,18 +885,42 @@ static int ipv4_create_routes ( void ) {
                                netmask.s_addr = INADDR_NET_CLASSC;
                        }
                }
+
                /* Get default gateway, if present */
                fetch_ipv4_setting ( settings, &gateway_setting, &gateway );
-               /* Configure route */
-               miniroute = add_ipv4_miniroute ( netdev, address,
-                                                netmask, gateway );
-               if ( ! miniroute )
-                       return -ENOMEM;
+
+               /* Apply settings */
+               if ( ( rc = apply ( netdev, address, netmask, gateway ) ) != 0 )
+                       return rc;
        }
 
        return 0;
 }
 
+/**
+ * Create IPv4 routing table based on configured settings
+ *
+ * @ret rc             Return status code
+ */
+static int ipv4_create_routes ( void ) {
+       struct ipv4_miniroute *miniroute;
+       struct ipv4_miniroute *tmp;
+       int rc;
+
+       /* Send gratuitous ARPs for any new IPv4 addresses */
+       ipv4_settings ( ipv4_gratuitous_arp );
+
+       /* Delete all existing routes */
+       list_for_each_entry_safe ( miniroute, tmp, &ipv4_miniroutes, list )
+               del_ipv4_miniroute ( miniroute );
+
+       /* Create a route for each configured network device */
+       if ( ( rc = ipv4_settings ( add_ipv4_miniroute ) ) != 0 )
+               return rc;
+
+       return 0;
+}
+
 /** IPv4 settings applicator */
 struct settings_applicator ipv4_settings_applicator __settings_applicator = {
        .apply = ipv4_create_routes,