]> git.saurik.com Git - apple/configd.git/blobdiff - Plugins/IPMonitor/ip_plugin.c
configd-699.1.5.tar.gz
[apple/configd.git] / Plugins / IPMonitor / ip_plugin.c
index 328ceb98a81f35feee4f7dc5f7a1e4d2cdf38947..d111ad438d113f868cb585e1c8c7de94be7c600d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000-2013 Apple Inc.  All Rights Reserved.
+ * Copyright (c) 2000-2014 Apple Inc.  All Rights Reserved.
  *
  * @APPLE_LICENSE_HEADER_START@
  * 
@@ -66,6 +66,9 @@
  *
  * December 5, 2007    Dieter Siegmund (dieter@apple.com)
  * - added support for multiple scoped routes
+ *
+ * November 13, 2013   Dieter Siegmund (dieter@apple.com)
+ * - added generic IPv4 routing support
  */
 
 #include <stdlib.h>
 #include <dnsinfo.h>
 #include "dnsinfo_server.h"
 
-#if    defined(HAVE_IPSEC_STATUS) || defined(HAVE_VPN_STATUS)
 #include <ppp/PPPControllerPriv.h>
-#endif // !defined(HAVE_IPSEC_STATUS) || defined(HAVE_VPN_STATUS)
 
 #include <dns_sd.h>
 #ifndef        kDNSServiceCompMulticastDNS
 #include "network_information_priv.h"
 #include "network_information_server.h"
 #include <ppp/ppp_msg.h>
+#include "ip_plugin.h"
+#if    !TARGET_IPHONE_SIMULATOR
+#include "set-hostname.h"
+#endif /* !TARGET_IPHONE_SIMULATOR */
 
-enum {
-    kProtocolFlagsNone         = 0x0,
-    kProtocolFlagsIPv4         = 0x1,
-    kProtocolFlagsIPv6         = 0x2
+#include "dns-configuration.h"
+#include "proxy-configuration.h"
+
+#if    !TARGET_OS_IPHONE
+#include "smb-configuration.h"
+#endif /* !TARGET_OS_IPHONE */
+
+#define kLoopbackInterface     "lo0"
+#define EROUTENOTAPPLIED       1001
+
+typedef CF_ENUM(uint8_t, ProtocolFlags) {
+    kProtocolFlagsNone = 0x0,
+    kProtocolFlagsIPv4 = 0x1,
+    kProtocolFlagsIPv6 = 0x2
 };
-typedef uint8_t        ProtocolFlags;
 
 enum {
     kDebugFlag1                = 0x00000001,
@@ -133,22 +147,172 @@ enum {
     kDebugFlagAll      = 0xffffffff
 };
 
-#ifdef TEST_IPV4_ROUTELIST
-#define ROUTELIST_DEBUG(a, f) { if ((S_IPMonitor_debug & (f)) != 0)  printf a ;}
-#else
-#define ROUTELIST_DEBUG(a, f)
-#endif
+typedef unsigned int   IFIndex;
 
-#if    !TARGET_IPHONE_SIMULATOR
-#include "set-hostname.h"
-#endif /* !TARGET_IPHONE_SIMULATOR */
+#ifndef TEST_ROUTELIST
 
-#include "dns-configuration.h"
-#include "proxy-configuration.h"
+#define ROUTELIST_DEBUG(flag, fmt, ...)
 
-#if    !TARGET_OS_IPHONE
-#include "smb-configuration.h"
-#endif /* !TARGET_OS_IPHONE */
+static struct if_nameindex *   S_if_nameindex_cache;
+
+__private_extern__ IFIndex
+my_if_nametoindex(const char * ifname)
+{
+    IFIndex            idx = 0;
+    struct if_nameindex *      scan;
+
+    if (S_if_nameindex_cache == NULL) {
+       return (if_nametoindex(ifname));
+    }
+    for (scan = S_if_nameindex_cache;
+        scan->if_index != 0 && scan->if_name != NULL;
+        scan++) {
+       if (strcmp(scan->if_name, ifname) == 0) {
+           idx = scan->if_index;
+           break;
+       }
+    }
+    return (idx);
+}
+
+__private_extern__ const char *
+my_if_indextoname(IFIndex idx, char if_name[IFNAMSIZ])
+{
+    const char *               name = NULL;
+    struct if_nameindex *      scan;
+
+    if (S_if_nameindex_cache == NULL) {
+       return (if_indextoname(idx, if_name));
+    }
+    for (scan = S_if_nameindex_cache;
+        scan->if_index != 0 && scan->if_name != NULL;
+        scan++) {
+       if (scan->if_index == idx) {
+           name = if_name;
+           strlcpy(if_name, scan->if_name, IFNAMSIZ);
+           break;
+       }
+    }
+    return (name);
+}
+
+static void
+my_if_freenameindex(void)
+{
+    if (S_if_nameindex_cache != NULL) {
+       if_freenameindex(S_if_nameindex_cache);
+       S_if_nameindex_cache = NULL;
+    }
+    return;
+}
+
+static void
+my_if_nameindex(void)
+{
+    my_if_freenameindex();
+    S_if_nameindex_cache = if_nameindex();
+    return;
+}
+
+
+#else /* TEST_ROUTELIST */
+
+#define ROUTELIST_DEBUG(flags, format, ...)    { if (((S_IPMonitor_debug & (flags)) != 0)) printf((format), ## __VA_ARGS__ ); }
+
+
+static const char * *  list;
+static int             list_count;
+static int             list_size;
+
+__private_extern__ IFIndex
+my_if_nametoindex(const char * ifname)
+{
+    IFIndex            ret;
+
+    if (list == NULL) {
+       list_size = 4;
+       list_count = 2;
+       list = (const char * *)malloc(sizeof(*list) * list_size);
+       list[0] = strdup("");
+       list[1] = strdup(kLoopbackInterface);
+    }
+    else {
+       int     i;
+
+       for (i = 1; i < list_count; i++) {
+           if (strcmp(list[i], ifname) == 0) {
+               ret = i;
+               goto done;
+           }
+       }
+    }
+    if (list_count == list_size) {
+       list_size += 2;
+       list = (const char * *)realloc(list, sizeof(*list) * list_size);
+    }
+    list[list_count] = strdup(ifname);
+    ret = list_count;
+    list_count++;
+ done:
+    return (ret);
+}
+
+__private_extern__ const char *
+my_if_indextoname(IFIndex idx, char if_name[IFNAMSIZ])
+{
+    const char *       name = NULL;
+
+    if (idx < list_count) {
+       name = if_name;
+       strlcpy(if_name, list[idx], IFNAMSIZ);
+    }
+    return (name);
+}
+
+static void
+my_if_nameindex(void)
+{
+}
+
+static void
+my_if_freenameindex(void)
+{
+}
+
+#endif /* TEST_ROUTELIST */
+
+static const char *
+my_if_indextoname2(IFIndex ifindex, char ifname[IFNAMSIZ])
+{
+    if (ifindex == 0) {
+       return (NULL);
+    }
+    if (my_if_indextoname(ifindex, ifname) == NULL) {
+       snprintf(ifname, IFNAMSIZ, "[%d]", ifindex);
+    }
+    return (ifname);
+}
+
+
+static IFIndex
+lo0_ifindex(void)
+{
+    static IFIndex             idx;
+
+    if (idx == 0) {
+       idx = my_if_nametoindex(kLoopbackInterface);
+    }
+    return (idx);
+}
+
+
+/*
+ * Property: kServiceOptionRankAssertion
+ * Purpose:
+ *   Key used in the service options dictionary to hold the RankAssertion
+ *   derived from the kSCPropNetServicePrimaryRank string.
+ */
+#define kServiceOptionRankAssertion    CFSTR("RankAssertion")  /* number */
 
 /*
  * Property: kIPIsCoupled
@@ -169,77 +333,123 @@ enum {
 #define IP_CH(ip)      ((u_char *)(ip))
 #define IP_LIST(ip)    IP_CH(ip)[0],IP_CH(ip)[1],IP_CH(ip)[2],IP_CH(ip)[3]
 
-#include "ip_plugin.h"
-#if    ((__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) || (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000))
 static SCLoggerRef     S_IPMonitor_logger;
-#endif // ((__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) || (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000))
 
-static boolean_t       S_bundle_logging_verbose;
+static Boolean S_bundle_logging_verbose;
 
 /*
  * IPv4 Route management
  */
 
-typedef uint32_t       RouteFlags;
+typedef CF_ENUM(uint16_t, RouteFlags) {
+    kRouteFlagsIsScoped                = 0x0001,
+    kRouteFlagsHasGateway      = 0x0002,
+    kRouteFlagsIsHost          = 0x0004,
+    kRouteFlagsIsNULL          = 0x0008,
+    kRouteFlagsKernelManaged   = 0x0010
+};
 
-enum {
-    kRouteIsDirectToInterfaceFlag      = 0x00000001,
-    kRouteIsNotSubnetLocalFlag         = 0x00000002,
-    kRouteIsScopedFlag                 = 0x00000004,
-    kRouteIsNULLFlag                   = 0x00000008
+typedef CF_ENUM(uint16_t, ControlFlags) {
+    kControlFlagsProcessed     = 0x0001,
+    kControlFlagsAdded         = 0x0002,
 };
 
+#define ROUTE_COMMON                           \
+    int                        prefix_length;          \
+    IFIndex            ifindex;                \
+    IFIndex            exclude_ifindex;        \
+    Rank               rank;                   \
+    RouteFlags         flags;                  \
+    ControlFlags       control_flags;
+
+typedef struct {
+    ROUTE_COMMON
+} Route, * RouteRef;
+
 typedef struct {
+    ROUTE_COMMON
     struct in_addr     dest;
     struct in_addr     mask;
     struct in_addr     gateway;
-    char               ifname[IFNAMSIZ];
-    unsigned int       ifindex;
     struct in_addr     ifa;
-    Rank               rank;
-    RouteFlags         flags;
-} IPv4Route, *IPv4RouteRef;
+} IPv4Route, * IPv4RouteRef;
 
 typedef struct {
-    int                        count;
-    int                        size;
-    boolean_t          exclude_from_nwi;
+    ROUTE_COMMON
+    struct in6_addr    dest;
+    struct in6_addr    gateway;
+    struct in6_addr    ifa;
+} IPv6Route, * IPv6RouteRef;
+
+typedef CF_ENUM(uint16_t, RouteListFlags) {
+    kRouteListFlagsExcludeNWI  = 0x0001,
+    kRouteListFlagsHasDefault  = 0x0002
+};
+
+#define ROUTELIST_COMMON                       \
+    int                        count;                  \
+    int                        size;                   \
+    RouteListFlags     flags;
+
+typedef struct {
+    ROUTELIST_COMMON
+} RouteListCommon, * RouteListRef;
+
+typedef struct {
+    ROUTELIST_COMMON
     IPv4Route          list[1];        /* variable length */
-} IPv4RouteList, *IPv4RouteListRef;
+} IPv4RouteList, * IPv4RouteListRef;
 
-enum {
-    kIPv4RouteListAddRouteCommand,
-    kIPv4RouteListRemoveRouteCommand
-};
+typedef struct {
+    ROUTELIST_COMMON
+    IPv6Route          list[1];        /* variable length */
+} IPv6RouteList, * IPv6RouteListRef;
+
+typedef union {
+    void *             ptr;
+    RouteListRef       common;
+    IPv4RouteListRef   v4;
+    IPv6RouteListRef   v6;
+} RouteListUnion;
+
+typedef enum {
+    kRouteCommandAdd,
+    kRouteCommandRemove
+} RouteCommand;
 
 /*
  * Election Information
  * - information about the current best services
  */
+typedef union {
+    struct in_addr     v4;
+    struct in6_addr    v6;
+} in_addr;
+
+typedef union {
+    struct sockaddr_in         v4;
+    struct sockaddr_in6 v6;
+} in_sockaddr;
+
 typedef struct Candidate {
-    CFStringRef                serviceID;
-    CFStringRef                if_name;
-    union {
-       struct in_addr  v4;
-       struct in6_addr v6;
-    } addr;
-    Rank                               rank;
-    boolean_t                          ip_is_coupled;
-    SCNetworkReachabilityFlags         reachability_flags;
-    union {
-       struct sockaddr_in vpn_server_addr4;
-       struct sockaddr_in6 vpn_server_addr6;
-    } vpn_server_addr;
-    CFStringRef                                signature;
+    CFStringRef                        serviceID;
+    CFStringRef                        if_name;
+    Rank                       rank;
+    boolean_t                  ip_is_coupled;
+    SCNetworkReachabilityFlags reachability_flags;
+    in_addr                    addr;
+    in_sockaddr                        vpn_server_addr;
+    CFStringRef                        signature;
 } Candidate, * CandidateRef;
 
 typedef struct ElectionResults {
-    int                        count;
-    int                        size;
-    Candidate          candidates[1];
+    int                                af;
+    int                                count;
+    int                                size;
+    Candidate                  candidates[1];
 } ElectionResults, * ElectionResultsRef;
 
-static __inline__ unsigned int
+static __inline__ size_t
 ElectionResultsComputeSize(unsigned int n)
 {
     return (offsetof(ElectionResults, candidates[n]));
@@ -250,8 +460,8 @@ ElectionResultsComputeSize(unsigned int n)
  * Purpose:
  *   A 32-bit value to encode the relative rank of a service.
  *
- *   The top 8 bits are used to hold the rank assertion (first, last
- *   never, default).
+ *   The top 8 bits are used to hold the rank assertion (first, default, last,
+ *   never, scoped);
  *
  *   The bottom 24 bits are used to store the service index (i.e. the
  *   position within the service order array).
@@ -261,9 +471,10 @@ ElectionResultsComputeSize(unsigned int n)
 #define kRankAssertionDefault          RANK_ASSERTION_MAKE(1)
 #define kRankAssertionLast             RANK_ASSERTION_MAKE(2)
 #define kRankAssertionNever            RANK_ASSERTION_MAKE(3)
+#define kRankAssertionScoped           RANK_ASSERTION_MAKE(4)
 #define kRankAssertionMask             RANK_ASSERTION_MAKE(0xff)
 #define RANK_ASSERTION_MASK(r)         ((Rank)(r) & kRankAssertionMask)
-
+#define RANK_ASSERTION_GET(r)          ((Rank)(r) >> 24)
 #define RANK_INDEX_MAKE(r)             ((Rank)(r))
 #define kRankIndexMask                 RANK_INDEX_MAKE(0xffffff)
 #define RANK_INDEX_MASK(r)             ((Rank)(r) & kRankIndexMask)
@@ -274,24 +485,59 @@ RankMake(uint32_t service_index, Rank primary_rank)
     return (RANK_INDEX_MASK(service_index) | RANK_ASSERTION_MASK(primary_rank));
 }
 
-static __inline__ Rank
-PrimaryRankGetRankAssertion(CFStringRef primaryRank)
+static Rank
+InterfaceRankGetRankAssertion(CFNumberRef rank_cf, Boolean * ret_is_set)
 {
-    if (CFEqual(primaryRank, kSCValNetServicePrimaryRankNever)) {
-       return kRankAssertionNever;
-    } else if (CFEqual(primaryRank, kSCValNetServicePrimaryRankFirst)) {
-       return kRankAssertionFirst;
-    } else if (CFEqual(primaryRank, kSCValNetServicePrimaryRankLast)) {
-       return kRankAssertionLast;
+    SCNetworkServicePrimaryRank if_rank;
+    Boolean                    is_set = FALSE;
+    Rank                       rank = kRankAssertionDefault;
+
+    if (rank_cf != NULL
+       && CFNumberGetValue(rank_cf, kCFNumberSInt32Type, &if_rank)
+       && if_rank != kSCNetworkServicePrimaryRankDefault) {
+       if (if_rank == kSCNetworkServicePrimaryRankFirst) {
+           rank = kRankAssertionFirst;
+       }
+       else {
+           rank = RANK_ASSERTION_MAKE(if_rank);
+       }
+       is_set = TRUE;
     }
-    return kRankAssertionDefault;
+    if (ret_is_set != NULL) {
+       *ret_is_set = is_set;
+    }
+    return (rank);
 }
 
-typedef uint32_t       IPv4RouteListApplyCommand;
+static Rank
+PrimaryRankGetRankAssertion(CFStringRef rank_str, Boolean * is_set)
+{
+    int                                i;
+    struct {
+       const CFStringRef *     name;
+       Rank                    rank_assertion;
+    } values[] = {
+       { &kSCValNetServicePrimaryRankFirst, kRankAssertionFirst },
+       { &kSCValNetServicePrimaryRankLast, kRankAssertionLast },
+       { &kSCValNetServicePrimaryRankNever, kRankAssertionNever },
+       { &kSCValNetServicePrimaryRankScoped, kRankAssertionScoped }
+    };
 
-typedef void IPv4RouteListApplyCallBackFunc(IPv4RouteListApplyCommand cmd,
-                                           IPv4RouteRef route, void * arg);
-typedef IPv4RouteListApplyCallBackFunc * IPv4RouteListApplyCallBackFuncPtr;
+    if (rank_str != NULL) {
+       for (i = 0; i < countof(values); i++) {
+           if (CFEqual(rank_str, *(values[i].name))) {
+               if (is_set != NULL) {
+                   *is_set = TRUE;
+               }
+               return (values[i].rank_assertion);
+           }
+       }
+    }
+    if (is_set != NULL) {
+       *is_set = FALSE;
+    }
+    return (kRankAssertionDefault);
+}
 
 /* SCDynamicStore session */
 static SCDynamicStoreRef       S_session = NULL;
@@ -304,10 +550,8 @@ static Boolean                     S_IPMonitor_verbose = FALSE;
 static boolean_t               S_netboot = FALSE;
 
 /* is scoped routing enabled? */
-#ifdef RTF_IFSCOPE
 static boolean_t               S_scopedroute = FALSE;
 static boolean_t               S_scopedroute_v6 = FALSE;
-#endif /* RTF_IFSCOPE */
 
 /* dictionary to hold per-service state: key is the serviceID */
 static CFMutableDictionaryRef  S_service_state_dict = NULL;
@@ -315,7 +559,7 @@ static CFMutableDictionaryRef       S_ipv4_service_rank_dict = NULL;
 static CFMutableDictionaryRef  S_ipv6_service_rank_dict = NULL;
 
 /* dictionary to hold per-interface rank information */
-static CFMutableDictionaryRef  S_if_rank_dict = NULL;
+static CFDictionaryRef         S_if_rank_dict;
 
 /* if set, a PPP interface overrides the primary */
 static boolean_t               S_ppp_override_primary = FALSE;
@@ -343,10 +587,9 @@ static CFStringRef         S_private_resolvers = NULL;
 
 #if    !TARGET_IPHONE_SIMULATOR
 static IPv4RouteListRef                S_ipv4_routelist = NULL;
-#endif /* !TARGET_IPHONE_SIMULATOR */
+static IPv6RouteListRef                S_ipv6_routelist = NULL;
 
-static const struct in_addr    S_ip_zeros = { 0 };
-static const struct in6_addr   S_ip6_zeros = IN6ADDR_ANY_INIT;
+#endif /* !TARGET_IPHONE_SIMULATOR */
 
 static boolean_t               S_append_state = FALSE;
 
@@ -383,13 +626,13 @@ static CFDictionaryRef            S_smb_dict = NULL;
 
 #ifndef KERN_NETBOOT
 #define KERN_NETBOOT           40      /* int: are we netbooted? 1=yes,0=no */
-#endif //KERN_NETBOOT
+#endif /* KERN_NETBOOT */
 
 /**
  ** entityType*, GetEntityChanges*
  ** - definitions for the entity types we handle
  **/
-enum {
+typedef enum {
     kEntityTypeIPv4    = 0,
     kEntityTypeIPv6,
     kEntityTypeDNS,
@@ -398,10 +641,9 @@ enum {
     kEntityTypeSMB,
 #endif /* !TARGET_OS_IPHONE */
     ENTITY_TYPES_COUNT,
-    kEntityTypeVPNStatus,
+    kEntityTypeTransientStatus,
     kEntityTypeServiceOptions  = 31
-};
-typedef uint32_t       EntityType;
+} EntityType;
 
 static const CFStringRef *entityTypeNames[ENTITY_TYPES_COUNT] = {
     &kSCEntNetIPv4,    /* 0 */
@@ -428,41 +670,72 @@ ipvx_other_char(int af)
     return ((af == AF_INET) ? '6' : '4');
 }
 
-static IPv4RouteListRef
-ipv4_dict_get_routelist(CFDictionaryRef ipv4_dict)
+/*
+ * IPv4/IPv6 Service Dict keys: kIPDictRoutes, IPDictService
+ *
+ * The IPv4/IPv6 service dictionary contains two sub-dictionaries:
+ *     Routes          CFData containing IPv4RouteList/IPv6RouteList
+ *      Service                dictionary containing kSCEntNetIPv[46] service entity
+ */
+#define kIPDictRoutes          CFSTR("Routes")         /* data */
+#define        kIPDictService          CFSTR("Service")        /* dict */
+
+static CFDictionaryRef
+ipdict_create(CFDictionaryRef dict, CFDataRef routes_data)
 {
-    CFDataRef          routes;
-    IPv4RouteListRef   routes_list = NULL;
+    CFStringRef        keys[2];
+    CFTypeRef  values[2];
 
-    if (isA_CFDictionary(ipv4_dict) == NULL) {
-       return (NULL);
-    }
+    keys[0] = kIPDictService;
+    values[0] = dict;
+    keys[1] = kIPDictRoutes;
+    values[1] = routes_data;
+    return (CFDictionaryCreate(NULL,
+                              (const void * *)keys,
+                              values,
+                              countof(keys),
+                              &kCFTypeDictionaryKeyCallBacks,
+                              &kCFTypeDictionaryValueCallBacks));
+}
+
+static void *
+ipdict_get_routelist(CFDictionaryRef dict)
+{
+    void *     routes_list = NULL;
 
-    routes = CFDictionaryGetValue(ipv4_dict, kIPv4DictRoutes);
+    if (dict != NULL) {
+       CFDataRef       routes;
 
-    if (routes != NULL) {
-       routes_list = (IPv4RouteListRef)(void*)CFDataGetBytePtr(routes);
+       routes = CFDictionaryGetValue(dict, kIPDictRoutes);
+       if (routes != NULL) {
+           routes_list = (void *)CFDataGetBytePtr(routes);
+       }
     }
     return (routes_list);
 }
 
-static CFStringRef
-ipv4_dict_get_ifname(CFDictionaryRef ipv4_dict)
+static CFDictionaryRef
+ipdict_get_service(CFDictionaryRef dict)
 {
-    CFDictionaryRef ipv4_service_dict = NULL;
+    CFDictionaryRef    ip_dict = NULL;
 
-    if (isA_CFDictionary(ipv4_dict) == NULL) {
-       return (NULL);
+    if (dict != NULL) {
+       ip_dict = CFDictionaryGetValue(dict, kIPDictService);
     }
+    return (ip_dict);
+}
 
-    ipv4_service_dict = CFDictionaryGetValue(ipv4_dict,
-                                            kIPv4DictService);
+static CFStringRef
+ipdict_get_ifname(CFDictionaryRef dict)
+{
+    CFStringRef                ifname = NULL;
+    CFDictionaryRef    ip_dict;
 
-    if (isA_CFDictionary(ipv4_service_dict) == NULL) {
-       return NULL;
+    ip_dict = ipdict_get_service(dict);
+    if (ip_dict != NULL) {
+       ifname = CFDictionaryGetValue(ip_dict, kSCPropInterfaceName);
     }
-
-    return CFDictionaryGetValue(ipv4_service_dict, kSCPropInterfaceName);
+    return (ifname);
 }
 
 typedef boolean_t GetEntityChangesFunc(CFStringRef serviceID,
@@ -509,27 +782,6 @@ typedef struct {
     CFMutableDictionaryRef     set;
 } keyChangeList, * keyChangeListRef;
 
-static CFStringRef
-my_CFStringCopyComponent(CFStringRef path, CFStringRef separator,
-                        CFIndex component_index)
-{
-    CFArrayRef                 arr;
-    CFStringRef                        component = NULL;
-
-    arr = CFStringCreateArrayBySeparatingStrings(NULL, path, separator);
-    if (arr == NULL) {
-       goto done;
-    }
-    if (CFArrayGetCount(arr) <= component_index) {
-       goto done;
-    }
-    component = CFRetain(CFArrayGetValueAtIndex(arr, component_index));
-
- done:
-    my_CFRelease(&arr);
-    return (component);
-}
-
 static void
 keyChangeListInit(keyChangeListRef keys)
 {
@@ -634,7 +886,7 @@ S_nwi_ifstate_dump(nwi_ifstate_t ifstate, int i)
                               sizeof(vpn_ntopbuf));
     }
     my_log(LOG_DEBUG,
-          "    [%d]: %s%s%s%s rank 0x%x iaddr: %s%s%s reachability_flags %u",
+          "    [%d]: %s%s%s%s rank 0x%x iaddr %s%s%s reach_flags 0x%x",
           i, ifstate->ifname,
           ifstate->diff_str != NULL ? ifstate->diff_str : "",
           (ifstate->flags & NWI_IFSTATE_FLAGS_HAS_DNS) != 0
@@ -661,8 +913,8 @@ S_nwi_state_dump(nwi_state_t state)
     }
     my_log(LOG_DEBUG,
           "nwi_state = { "
-          "gen = %llu size = %u #ipv4 = %u #ipv6 = %u "
-          "reach_flags_v4 = %u reach_flags_v6 %u }",
+          "gen=%llu size=%u #v4=%u #v6=%u "
+          "reach_flags=(v4=0x%x, v6=0x%x) }",
           state->generation_count,
           state->size,
           state->ipv4_count,
@@ -700,14 +952,28 @@ S_is_network_boot()
     return (netboot);
 }
 
+static int     rtm_seq = 0;
+
 #if    !TARGET_IPHONE_SIMULATOR
+static int
+open_routing_socket(void)
+{
+    int sockfd;
+
+    if ((sockfd = socket(PF_ROUTE, SOCK_RAW, PF_ROUTE)) == -1) {
+       my_log(LOG_NOTICE,
+              "IPMonitor: open_routing_socket: socket failed, %s",
+              strerror(errno));
+    }
+    return (sockfd);
+}
+
 static __inline__ int
 inet6_dgram_socket()
 {
     return (socket(AF_INET6, SOCK_DGRAM, 0));
 }
 
-#ifdef SIOCDRADD_IN6
 static int
 siocdradd_in6(int s, int if_index, const struct in6_addr * addr, u_char flags)
 {
@@ -738,10 +1004,9 @@ siocdrdel_in6(int s, int if_index, const struct in6_addr * addr)
     dr.if_index = if_index;
     return (ioctl(s, SIOCDRDEL_IN6, &dr));
 }
-#endif /* SIOCDRADD_IN6 */
+
 #endif /* !TARGET_IPHONE_SIMULATOR */
 
-#ifdef RTF_IFSCOPE
 static boolean_t
 S_is_scoped_routing_enabled()
 {
@@ -771,7 +1036,6 @@ S_is_scoped_v6_routing_enabled()
     }
     return (scopedroute_v6);
 }
-#endif /* RTF_IFSCOPE */
 
 static void
 my_CFArrayAppendUniqueValue(CFMutableArrayRef arr, CFTypeRef new)
@@ -799,6 +1063,18 @@ my_CFArrayRemoveValue(CFMutableArrayRef arr, CFStringRef key)
     return;
 }
 
+static CFArrayRef
+my_CFArrayCreateCombinedArray(CFArrayRef array1, CFArrayRef array2)
+{
+    CFMutableArrayRef  combined;
+
+    combined = CFArrayCreateMutableCopy(NULL, 0, array1);
+    CFArrayAppendArray(combined,
+                      array2,
+                      CFRangeMake(0, CFArrayGetCount(array2)));
+    return (combined);
+}
+
 static void
 my_CFRelease(void * t)
 {
@@ -875,6 +1151,15 @@ cfstring_to_ip6(CFStringRef str, struct in6_addr * ip6_p)
     return (cfstring_to_ipvx(AF_INET6, str, ip6_p, sizeof(*ip6_p)));
 }
 
+static boolean_t
+cfnumber_to_int(CFNumberRef num, int * int_val)
+{
+    if (isA_CFNumber(num) == NULL) {
+       return (FALSE);
+    }
+    return (CFNumberGetValue(num, kCFNumberIntType, int_val));
+}
+
 static CF_RETURNS_RETAINED CFStringRef
 setup_service_key(CFStringRef serviceID, CFStringRef entity)
 {
@@ -893,6 +1178,15 @@ state_service_key(CFStringRef serviceID, CFStringRef entity)
                                                        entity));
 }
 
+static CFStringRef
+interface_entity_key_copy(CFStringRef ifname, CFStringRef entity)
+{
+    return (SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
+                                                         kSCDynamicStoreDomainState,
+                                                         ifname,
+                                                         entity));
+}
+
 static CFDictionaryRef
 get_service_setup_entity(CFDictionaryRef services_info, CFStringRef serviceID,
                         CFStringRef entity)
@@ -933,6 +1227,63 @@ dict_get_first_ip(CFDictionaryRef dict, CFStringRef prop, struct in_addr * ip_p)
     return (FALSE);
 }
 
+static boolean_t
+dict_get_first_ipv6(CFDictionaryRef dict, CFStringRef prop,
+                   struct in6_addr * ip_p)
+{
+    CFArrayRef         ip_list;
+
+    ip_list = CFDictionaryGetValue(dict, prop);
+    if (isA_CFArray(ip_list) != NULL
+       && CFArrayGetCount(ip_list) > 0
+       && cfstring_to_ip6(CFArrayGetValueAtIndex(ip_list, 0), ip_p)) {
+       return (TRUE);
+    }
+    return (FALSE);
+}
+
+static boolean_t
+dict_get_first_int(CFDictionaryRef dict, CFStringRef prop,
+                  int * val)
+{
+    CFArrayRef         list;
+
+    list = CFDictionaryGetValue(dict, prop);
+    if (isA_CFArray(list) != NULL
+       && CFArrayGetCount(list) > 0
+       && cfnumber_to_int(CFArrayGetValueAtIndex(list, 0), val)) {
+       return (TRUE);
+    }
+    return (FALSE);
+}
+
+static boolean_t
+dict_get_ip(CFDictionaryRef dict, CFStringRef prop, struct in_addr * ip_p)
+{
+    CFStringRef                val;
+
+    val = CFDictionaryGetValue(dict, prop);
+    return (cfstring_to_ip(val, ip_p));
+}
+
+static boolean_t
+dict_get_ipv6(CFDictionaryRef dict, CFStringRef prop, struct in6_addr * ip_p)
+{
+    CFStringRef                val;
+
+    val = CFDictionaryGetValue(dict, prop);
+    return (cfstring_to_ip6(val, ip_p));
+}
+
+static boolean_t
+dict_get_int(CFDictionaryRef dict, CFStringRef prop, int * intval)
+{
+    CFNumberRef                val;
+
+    val = CFDictionaryGetValue(dict, prop);
+    return (cfnumber_to_int(val, intval));
+}
+
 static boolean_t
 get_override_primary(CFDictionaryRef dict)
 {
@@ -956,126 +1307,110 @@ get_override_primary(CFDictionaryRef dict)
 }
 
 /**
- ** IPv4Route*
+ ** Route*
  **/
 
-static __inline__ struct in_addr
-subnet_addr(struct in_addr addr, struct in_addr mask)
-{
-    struct in_addr     net;
+typedef size_t
+(*RouteListComputeSize)(CFIndex n);
 
-    net.s_addr = addr.s_addr & mask.s_addr;
-    return (net);
-}
+typedef boolean_t
+(*RouteIsEqual)(RouteRef a, RouteRef b);
 
-static __inline__ int
-in_addr_cmp(struct in_addr a, struct in_addr b)
-{
-    return (uint32_cmp(ntohl(a.s_addr), ntohl(b.s_addr)));
-}
+typedef int
+(*RouteApply)(RouteRef route, int cmd, int sockfd);
 
-static __inline__ int
-RouteFlagsCompare(RouteFlags a, RouteFlags b)
-{
-    return (uint32_cmp(a, b));
-}
+typedef const void *
+(*RouteGateway)(RouteRef route);
 
-static void
-IPv4RouteCopyDescriptionWithString(IPv4RouteRef r, CFMutableStringRef str)
-{
-    Rank rank_assertion = RANK_ASSERTION_MASK(r->rank);
-
-    CFStringAppendFormat(str, NULL,
-                        CFSTR("Dest " IP_FORMAT
-                              " Mask " IP_FORMAT
-                              " Gate " IP_FORMAT
-                              " Ifp %s Ifa " IP_FORMAT),
-                        IP_LIST(&r->dest),
-                        IP_LIST(&r->mask),
-                        IP_LIST(&r->gateway),
-                        (r->ifname[0] != '\0') ? r->ifname : "<none>",
-                        IP_LIST(&r->ifa));
-    if ((r->flags & kRouteIsNULLFlag) != 0) {
-       CFStringAppend(str, CFSTR(" [null]"));
-    }
-    else {
-       if ((r->flags & kRouteIsNotSubnetLocalFlag) != 0) {
-           CFStringAppend(str, CFSTR(" [non-local]"));
-       }
-       else if ((r->flags & kRouteIsDirectToInterfaceFlag) != 0) {
-           CFStringAppend(str, CFSTR(" [direct]"));
-       }
-       switch (rank_assertion) {
-       case kRankAssertionFirst:
-           CFStringAppend(str, CFSTR(" [first]"));
-           break;
-       case kRankAssertionLast:
-           CFStringAppend(str, CFSTR(" [last]"));
-           break;
-       case kRankAssertionNever:
-           CFStringAppend(str, CFSTR(" [never]"));
-           break;
-       default:
-           break;
-       }
-       if ((r->flags & kRouteIsScopedFlag) != 0) {
-           CFStringAppend(str, CFSTR(" [SCOPED]"));
-       }
-    }
-    return;
-}
+typedef void
+(*RouteSetGateway)(RouteRef route, const void * address);
 
-static CFStringRef
-IPv4RouteCopyDescription(IPv4RouteRef r)
-{
-    CFMutableStringRef str;
+typedef const void *
+(*RouteDestination)(RouteRef route);
 
-    str = CFStringCreateMutable(NULL, 0);
-    IPv4RouteCopyDescriptionWithString(r, str);
-    return (str);
-}
+typedef boolean_t
+(*RouteSameSubnet)(RouteRef route, const void * address);
 
-static __inline__ void
-IPv4RoutePrint(IPv4RouteRef route)
-{
-    CFStringRef        str = IPv4RouteCopyDescription(route);
+typedef CFStringRef
+(*RouteCopyDescription)(RouteRef route);
 
-    SCPrint(TRUE, stdout, CFSTR("%@\n"), str);
-    CFRelease(str);
-    return;
-}
+typedef void
+(*RouteLog)(int priority, RouteRef route, const char * msg);
 
-static __inline__ void
-IPv4RouteLog(int level, IPv4RouteRef route)
-{
-    CFStringRef        str = IPv4RouteCopyDescription(route);
+typedef struct {
+    RouteListComputeSize       list_compute_size;
 
-    my_log(level, "%@", str);
-    CFRelease(str);
-    return;
+    RouteIsEqual               route_equal;
+    RouteApply                 route_apply;
+    RouteGateway               route_gateway;
+    RouteSetGateway            route_set_gateway;
+    RouteDestination           route_destination;
+    RouteSameSubnet            route_same_subnet;
+    RouteLog                   route_log;
+    RouteCopyDescription       route_copy_description;
+
+    int                                element_size;
+    int                                address_size;
+    int                                all_bits_set;
+} RouteListInfo;
+
+typedef const RouteListInfo * RouteListInfoRef;
+
+typedef struct {
+    RouteListInfoRef   info;
+    RouteListRef       old_routes;
+    RouteListRef       new_routes;
+    int                        sockfd;
+    int                        depth;
+} RouteListApplyContext, * RouteListApplyContextRef;
+
+
+static int
+RouteAddressCompare(RouteListInfoRef info,
+                   const void * addr1,
+                   const void * addr2)
+{
+    return (memcmp(addr1, addr2, info->address_size));
 }
 
 static int
-IPv4RouteCompare(IPv4RouteRef a, Rank a_rank,
-                IPv4RouteRef b, Rank b_rank, boolean_t * same_dest)
+RouteCompare(RouteListInfoRef info,
+            RouteRef a, Rank a_rank,
+            RouteRef b, Rank b_rank, boolean_t * same_dest)
 {
-    int                cmp;
+    int                                cmp;
+    RouteDestination           route_destination;
+    RouteCopyDescription       route_copy_description;
 
     *same_dest = FALSE;
-    cmp = in_addr_cmp(a->dest, b->dest);
+    route_destination = info->route_destination;
+    route_copy_description = info->route_copy_description;
+    cmp = RouteAddressCompare(info,
+                             (*route_destination)(a),
+                             (*route_destination)(b));
     if (cmp == 0) {
-       cmp = in_addr_cmp(a->mask, b->mask);
+       cmp = a->prefix_length - b->prefix_length;
        if (cmp == 0) {
-           int         name_cmp = strcmp(a->ifname, b->ifname);
+           int         index_cmp = a->ifindex - b->ifindex;
 
-           if (name_cmp == 0) {
+           if (index_cmp == 0) {
+               cmp = 0;
+           }
+           else if ((a->ifindex == 0 || b->ifindex == 0)
+                    && (a->flags & kRouteFlagsIsScoped) == 0
+                    && (b->flags & kRouteFlagsIsScoped) == 0) {
+               /*
+                * Either of the routes specifies no interface and neither
+                * route is scoped. Claim they are equal to eliminate the
+                * duplicate route.
+                */
                cmp = 0;
            }
            else {
                *same_dest = TRUE;
                cmp = RankCompare(a_rank, b_rank);
                if (cmp == 0) {
-                   cmp = name_cmp;
+                   cmp = index_cmp;
                }
            }
        }
@@ -1094,8 +1429,8 @@ IPv4RouteCompare(IPv4RouteRef a, Rank a_rank,
        else {
            ch = '>';
        }
-       a_str = IPv4RouteCopyDescription(a);
-       b_str = IPv4RouteCopyDescription(b);
+       a_str = (*route_copy_description)(a);
+       b_str = (*route_copy_description)(b);
        my_log(LOG_DEBUG, "%@ rank 0x%x %c %@ rank 0x%x",
               a_str, a_rank, ch, b_str, b_rank);
        CFRelease(a_str);
@@ -1104,126 +1439,101 @@ IPv4RouteCompare(IPv4RouteRef a, Rank a_rank,
     return (cmp);
 }
 
-static CFMutableStringRef
-IPv4RouteListCopyDescription(IPv4RouteListRef routes)
+static RouteRef
+RouteListGetRouteAtIndexSimple(RouteListInfoRef info, RouteListRef routes,
+                              CFIndex where)
 {
-    int                        i;
-    IPv4RouteRef       r;
-    CFMutableStringRef str;
+    return ((void *)routes + (*info->list_compute_size)(where));
+}
 
-    str = CFStringCreateMutable(NULL, 0);
-    CFStringAppendFormat(str, NULL, CFSTR("<IPv4RouteList[%d]> = {"),
-                        routes->count);
-    for (i = 0, r = routes->list; i < routes->count; i++, r++) {
-       CFStringAppendFormat(str, NULL, CFSTR("\n%2d. "), i);
-       IPv4RouteCopyDescriptionWithString(r, str);
+static RouteRef
+RouteListGetRouteAtIndex(RouteListInfoRef info, RouteListRef routes,
+                        CFIndex where)
+{
+    if (routes->count == 0
+       || where >= routes->count) {
+       return (NULL);
     }
-    CFStringAppend(str, CFSTR("\n}"));
-    return (str);
+    return (RouteListGetRouteAtIndexSimple(info, routes, where));
 }
 
-static __inline__ void
-IPv4RouteListPrint(IPv4RouteListRef routes)
+static RouteRef
+RouteListGetFirstRoute(RouteListInfoRef info, RouteListRef routes)
 {
-    CFStringRef        str = IPv4RouteListCopyDescription(routes);
-
-    SCPrint(TRUE, stdout, CFSTR("%@\n"), str);
-    CFRelease(str);
-    return;
+    return (RouteListGetRouteAtIndexSimple(info, routes, 0));
 }
 
-static __inline__ void
-IPv4RouteListLog(int level, IPv4RouteListRef routes)
+#if    !TARGET_IPHONE_SIMULATOR
+static CFIndex
+RouteListRouteIndex(RouteListInfoRef info, RouteListRef routes,
+                   RouteRef route)
 {
-    CFStringRef        str = IPv4RouteListCopyDescription(routes);
-
-    my_log(level, "%@", str);
-    CFRelease(str);
-    return;
+    return (((void *)route
+            - (void *)RouteListGetFirstRoute(info, routes))
+           / info->element_size);
 }
+#endif /* !TARGET_IPHONE_SIMULATOR */
 
-static __inline__ unsigned int
-IPv4RouteListComputeSize(unsigned int n)
+static RouteRef
+RouteGetNextRoute(RouteListInfoRef info, RouteRef route)
 {
-    return (offsetof(IPv4RouteList, list[n]));
+    return ((RouteRef)(((void *)route) + info->element_size));
 }
 
-#if    !TARGET_IPHONE_SIMULATOR
-static IPv4RouteRef
-IPv4RouteListFindRoute(IPv4RouteListRef routes, IPv4RouteRef route)
+static RouteRef
+RouteListAddRouteAtIndex(RouteListInfoRef info, RouteListRef routes,
+                        RouteRef this_route, CFIndex where)
 {
-    int                        i;
-    IPv4RouteRef       scan_result = NULL;
-    IPv4RouteRef       scan;
+    RouteRef   insert_route;
 
-    for (i = 0, scan = routes->list; i < routes->count; i++, scan++) {
-       if ((scan->dest.s_addr == route->dest.s_addr)
-           && (scan->mask.s_addr == route->mask.s_addr)
-           && (strcmp(scan->ifname, route->ifname) == 0)
-           && (scan->ifa.s_addr == route->ifa.s_addr)
-           && (scan->gateway.s_addr == route->gateway.s_addr)
-           && (scan->flags == route->flags)) {
-               scan_result = scan;
-               break;
-       }
+    if (where == kCFNotFound) {
+       /* add it to the end */
+       insert_route
+           = RouteListGetRouteAtIndexSimple(info, routes, routes->count);
+    }
+    else {
+       /* make space at [where] */
+       insert_route = RouteListGetRouteAtIndexSimple(info, routes, where);
+       bcopy(insert_route,
+             (void *)insert_route + info->element_size,
+             info->element_size * (routes->count - where));
     }
-    return (scan_result);
+    /* copy the route */
+    bcopy(this_route, insert_route, info->element_size);
+    routes->count++;
+    return (insert_route);
 }
 
 static void
-IPv4RouteListApply(IPv4RouteListRef old_routes, IPv4RouteListRef new_routes,
-                  IPv4RouteListApplyCallBackFuncPtr func, void * arg)
+RouteListRemoveRouteAtIndex(RouteListInfoRef info, RouteListRef routes,
+                           CFIndex where)
 {
-    int                        i;
-    IPv4RouteRef       scan;
-
-    if (old_routes == new_routes && old_routes == NULL) {
-       /* both old and new are NULL, so there's nothing to do */
+    if (routes->count == 0
+       || where >= routes->count) {
        return;
     }
-    if (old_routes != NULL) {
-       for (i = 0, scan = old_routes->list;
-            i < old_routes->count;
-            i++, scan++) {
-           IPv4RouteRef        new_route = NULL;
-
-           if (new_routes != NULL) {
-               new_route = IPv4RouteListFindRoute(new_routes, scan);
-           }
-           if (new_route == NULL) {
-               if (func != NULL) {
-                   (*func)(kIPv4RouteListRemoveRouteCommand, scan, arg);
-               }
-           }
-       }
+    routes->count--;
+    if (where == routes->count) {
+       /* last slot, decrementing gets rid of it */
     }
-    if (new_routes != NULL) {
-       for (i = 0, scan = new_routes->list;
-            i < new_routes->count;
-            i++, scan++) {
-           IPv4RouteRef        old_route = NULL;
+    else {
+       RouteRef        remove_route;
 
-           if (old_routes != NULL) {
-               old_route = IPv4RouteListFindRoute(old_routes, scan);
-           }
-           if (old_route == NULL) {
-               if (func != NULL) {
-                   (*func)(kIPv4RouteListAddRouteCommand, scan, arg);
-               }
-           }
-       }
+       remove_route = RouteListGetRouteAtIndexSimple(info, routes, where);
+       bcopy((void *)remove_route + info->element_size,
+             remove_route,
+             info->element_size * (routes->count - where));
     }
     return;
 }
-#endif /* !TARGET_IPHONE_SIMULATOR */
 
 /*
- * Function: IPv4RouteListAddRoute
+ * Function: RouteListAddRoute
  *
  * Purpose:
- *   Add the given IPv4Route to the list of routes, eliminating lower-ranked
+ *   Add the given route to the list of routes, eliminating lower-ranked
  *   duplicates on the same interface, and marking any lower ranked duplicates
- *   on other interfaces with kRouteIsScopedFlag.
+ *   on other interfaces with kRouteFlagsIsScoped.
  *
  *   This routine assumes that if routes is not NULL, it is malloc'd memory.
  *
@@ -1232,51 +1542,54 @@ IPv4RouteListApply(IPv4RouteListRef old_routes, IPv4RouteListRef new_routes,
  *   due to using realloc'd memory.
  */
 
-enum {
+typedef enum {
     kScopeNone = 0,
     kScopeThis = 1,
     kScopeNext = 2
-};
+} Scope;
 
-static IPv4RouteListRef
-IPv4RouteListAddRoute(IPv4RouteListRef routes, int init_size,
-                     IPv4RouteRef this_route, Rank this_rank)
+static RouteListRef
+RouteListAddRoute(RouteListInfoRef info,
+                 RouteListRef routes, int init_size,
+                 RouteRef this_route, Rank this_rank)
 {
-    int                        i;
-    IPv4RouteRef       first_scan = NULL;
-    int                        scope_which = kScopeNone;
-    IPv4RouteRef       scan;
-    int                        where = -1;
+    CFIndex            i;
+    RouteRef           first_scan = NULL;
+    RouteFlags         flags;
+    RouteRef           scan;
+    Scope              scope_which = kScopeNone;
+    CFIndex            where = kCFNotFound;
 
     if (routes == NULL) {
-       routes = (IPv4RouteListRef)malloc(IPv4RouteListComputeSize(init_size));
+       size_t  alloc_size = (*info->list_compute_size)(init_size);
+
+       routes = (RouteListRef)malloc(alloc_size);
        bzero(routes, sizeof(*routes));
        routes->size = init_size;
-       routes->count = 0;
     }
-    for (i = 0, scan = routes->list; i < routes->count;
-        i++, scan++) {
+    for (i = 0, scan = RouteListGetFirstRoute(info, routes);
+        i < routes->count;
+        i++, scan = RouteGetNextRoute(info, scan)) {
        int             cmp;
        boolean_t       same_dest;
 
-       cmp = IPv4RouteCompare(this_route, this_rank, scan, scan->rank, &same_dest);
-
+       cmp = RouteCompare(info, this_route, this_rank, scan, scan->rank,
+                          &same_dest);
        if (same_dest == TRUE && first_scan == NULL) {
            first_scan = scan;
        }
-
        if (cmp < 0) {
-           if (where == -1) {
+           if (where == kCFNotFound) {
                if (same_dest == TRUE
-                   && (first_scan->flags & kRouteIsScopedFlag) == 0) {
-                   if ((scan->flags & kRouteIsScopedFlag) != 0) {
-                       ROUTELIST_DEBUG(("Hit 1: set scope on self\n"),
-                                       kDebugFlag8);
+                   && (first_scan->flags & kRouteFlagsIsScoped) == 0) {
+                   if ((scan->flags & kRouteFlagsIsScoped) != 0) {
+                       ROUTELIST_DEBUG(kDebugFlag8,
+                                       "Hit 1: set scope on self\n");
                        scope_which = kScopeThis;
                    }
                    else {
-                       ROUTELIST_DEBUG(("Hit 2: set scope on next\n"),
-                                       kDebugFlag8);
+                       ROUTELIST_DEBUG(kDebugFlag8,
+                                       "Hit 2: set scope on next\n");
                        scope_which = kScopeNext;
                    }
                }
@@ -1286,37 +1599,53 @@ IPv4RouteListAddRoute(IPv4RouteListRef routes, int init_size,
        }
        else if (cmp == 0) {
            /* exact match */
-           if (where != -1) {
+           /* exact match */
+           if (where != kCFNotFound
+               && scan->ifindex == this_route->ifindex
+               && scan->exclude_ifindex == 0
+               && this_route->exclude_ifindex == 0) {
                /* this route is a duplicate */
-               ROUTELIST_DEBUG(("Hit 3: removing [%d]\n", i), kDebugFlag8);
-               routes->count--;
-               if (i == routes->count) {
-                   /* last slot, decrementing gets rid of it */
-               }
-               else {
-                   bcopy(routes->list + i + 1,
-                         routes->list + i,
-                         sizeof(routes->list[0]) * (routes->count - i));
-               }
+               ROUTELIST_DEBUG(kDebugFlag8, "Hit 3: removing [%ld]\n", i);
+               RouteListRemoveRouteAtIndex(info, routes, i);
                break;
            }
-           /* resolve conflict using rank */
-           if (this_rank < scan->rank) {
+           /*
+            * this_route is "better" than scan if this_route is not excluded
+            * and scan is excluded or this_route sorts ahead of scan
+            */
+           if (this_route->exclude_ifindex == 0
+               && (scan->exclude_ifindex != 0 || this_rank < scan->rank)) {
+               IFIndex         ifindex = 0;
                boolean_t       is_scoped = FALSE;
 
-               if (scan->flags & kRouteIsScopedFlag) {
+               if (scan->flags & kRouteFlagsIsScoped) {
                    is_scoped = TRUE;
                }
-               ROUTELIST_DEBUG(("Hit 4:replacing [%d] rank 0x%x < 0x%x\n",
-                                i,
-                                this_rank,
-                                scan->rank), kDebugFlag8);
-               *scan = *this_route;
+               if (this_rank < scan->rank) {
+                   ROUTELIST_DEBUG(kDebugFlag8,
+                                   "Hit 4a: replacing [%ld]"
+                                   " rank 0x%x < 0x%x\n",
+                                   i, this_rank, scan->rank);
+               }
+               else {
+                   ROUTELIST_DEBUG(kDebugFlag8,
+                                   "Hit 4b: replacing [%ld] excluded route\n",
+                                   i);
+               }
+               if (scan->ifindex != 0) {
+                   ifindex = scan->ifindex;
+               }
+               else if (this_route->ifindex != 0) {
+                   ifindex = this_route->ifindex;
+               }
+               bcopy(this_route, scan, info->element_size);
                scan->rank = this_rank;
+               scan->ifindex = ifindex;
+               scan->exclude_ifindex = 0;
                if (is_scoped) {
                    /* preserve whether route was scoped */
-                   ROUTELIST_DEBUG(("Hit 5: preserved scope\n"), kDebugFlag8);
-                   scan->flags |= kRouteIsScopedFlag;
+                   ROUTELIST_DEBUG(kDebugFlag8, "Hit 5: preserved scope\n");
+                   scan->flags |= kRouteFlagsIsScoped;
                }
            }
            /* we're done */
@@ -1325,5447 +1654,7746 @@ IPv4RouteListAddRoute(IPv4RouteListRef routes, int init_size,
        else {
            if (same_dest == TRUE) {
                if (scope_which == kScopeNone) {
-                   ROUTELIST_DEBUG(("Hit 10: set scope on self\n"),
-                                   kDebugFlag8);
+                   ROUTELIST_DEBUG(kDebugFlag8, "Hit 6: set scope on self\n");
                    scope_which = kScopeThis;
                }
            }
-#ifdef TEST_IPV4_ROUTELIST
-           else if (where != -1) {
+#ifdef TEST_ROUTELIST
+           else if (where != kCFNotFound) {
                /* not possible because we maintain a sorted list */
-               ROUTELIST_DEBUG(("Hit 11: moved past routes - can't happen\n"),
-                               kDebugFlag8);
+               fprintf(stderr,
+                       "Hit 7: moved past routes - can't happen\n");
+               exit(2);
                break;
            }
-#endif /* TEST_IPV4_ROUTELIST */
+#endif /* TEST_ROUTELIST */
        }
     }
+
     if (routes->size == routes->count) {
-       int                     how_many;
-       IPv4RouteListRef        new_routes;
-       int                     old_size;
+       int             how_many;
+       RouteListRef    new_routes;
+       int             old_size;
 
        /* double the size */
        old_size = routes->size;
        how_many = old_size * 2;
-       new_routes = (IPv4RouteListRef)
-           realloc(routes, IPv4RouteListComputeSize(how_many));
+       new_routes = (RouteListRef)
+           reallocf(routes, (*info->list_compute_size)(how_many));
        if (new_routes == NULL) {
            /* no memory */
+           routes = NULL;
            goto done;
        }
-       ROUTELIST_DEBUG(("increasing size from %d to %d\n", old_size,
-                        how_many), kDebugFlag8);
+       ROUTELIST_DEBUG(kDebugFlag8, "increasing size from %d to %d\n",
+                       old_size, how_many);
        new_routes->size = how_many;
        routes = new_routes;
     }
-    if (where == -1) {
-       /* add it to the end */
-       where = routes->count;
-    }
-    else {
-       /* insert it at [where] */
-       bcopy(routes->list + where,
-             routes->list + where + 1,
-             sizeof(routes->list[0]) * (routes->count - where));
-    }
-    /* copy the route */
-    routes->list[where] = *this_route;
-    routes->list[where].rank = this_rank;
+
+    /* add/insert the new route */
+    this_route = RouteListAddRouteAtIndex(info, routes, this_route, where);
+    this_route->rank = this_rank;
+    flags = 0;
     if (RANK_ASSERTION_MASK(this_rank) == kRankAssertionNever) {
-       routes->list[where].flags |= kRouteIsScopedFlag;
+       flags |= kRouteFlagsIsScoped;
     }
-
-    /* set the scope */
     switch (scope_which) {
     case kScopeThis:
-       routes->list[where].flags |= kRouteIsScopedFlag;
+       flags |= kRouteFlagsIsScoped;
        break;
     case kScopeNext:
-       routes->list[where + 1].flags |= kRouteIsScopedFlag;
+       this_route = RouteListGetRouteAtIndex(info, routes, where + 1);
+       flags |= kRouteFlagsIsScoped;
        break;
     default:
     case kScopeNone:
        break;
     }
-    routes->count++;
+    if (this_route != NULL && flags != 0) {
+       this_route->flags |= flags;
+    }
+
  done:
     return (routes);
 }
 
 /*
- * Function: IPv4RouteListAddRouteList
- *
+ * Function: RouteListAddRouteList
  * Purpose:
- *   Invoke IPv4RouteListAddRoute for each route in the given list.
+ *   Invoke RouteListAddRoute for each route in the given list
+ *   'service_routes' combining them into a combined list 'routes'.
  *
  * Returns:
- *   See IPv4RouteListAddRoute for more information.
+ *   See RouteListAddRoute for more information.
  */
-static IPv4RouteListRef
-IPv4RouteListAddRouteList(IPv4RouteListRef routes, int init_size,
-                         IPv4RouteListRef service_routes, Rank rank)
+static RouteListRef
+RouteListAddRouteList(RouteListInfoRef info,
+                     RouteListRef routes, int init_size,
+                     RouteListRef service_routes, Rank rank)
 {
-    int                        i;
-    IPv4RouteRef       scan;
+    int                i;
+    RouteRef   scan;
+
+    for (i = 0, scan = RouteListGetFirstRoute(info, service_routes);
+        i < service_routes->count;
+        i++, scan = RouteGetNextRoute(info, scan)) {
+       Rank    this_rank;
 
-    for (i = 0, scan = service_routes->list;
-        i < service_routes->count; i++, scan++) {
-       routes = IPv4RouteListAddRoute(routes, init_size, scan, rank);
+       if (i == 0
+           && (service_routes->flags & kRouteListFlagsHasDefault) != 0) {
+           /* only apply rank to first element of the list (default route) */
+           this_rank = rank;
+       }
+       else {
+           this_rank = RANK_INDEX_MASK(rank) | RANK_ASSERTION_MASK(scan->rank);
+       }
+       routes = RouteListAddRoute(info, routes, init_size, scan, this_rank);
     }
     return (routes);
 }
 
-static boolean_t
-plist_get_cstring(CFDictionaryRef dict, CFStringRef prop_name,
-                 char * buf, int buf_size)
+static void
+RouteAddInterfaceToDescription(RouteRef r, CFMutableStringRef str)
 {
-    CFStringRef        val;
+    char       if_name[IFNAMSIZ];
 
-    val = CFDictionaryGetValue(dict, prop_name);
-    if (isA_CFString(val) == NULL) {
-       return (FALSE);
+    if (my_if_indextoname2(r->ifindex, if_name) != NULL) {
+       CFStringAppendFormat(str, NULL,
+                            CFSTR(" Ifp %s"),
+                            if_name);
     }
-    if (CFStringGetCString(val, buf, buf_size, kCFStringEncodingASCII)
-       == FALSE) {
-       return (FALSE);
+    if (my_if_indextoname2(r->exclude_ifindex, if_name) != NULL) {
+       CFStringAppendFormat(str, NULL,
+                            CFSTR(" !Ifp %s"),
+                            if_name);
     }
-    return (TRUE);
+    return;
 }
 
-/*
- * Function: IPv4RouteListCreateWithDictionary
- *
- * Purpose:
- *   Given the service ipv4 entity dictionary, generate the list of routes.
- *   Currently, this includes just the default route and subnet route,
- *   if the service has a subnet mask.
- *
- * Returns:
- *   If the passed in route_list is NULL or too small, this routine
- *   allocates malloc'd memory to hold the routes.
- */
-static IPv4RouteListRef
-IPv4RouteListCreateWithDictionary(IPv4RouteListRef routes,
-                                 CFDictionaryRef dict,
-                                 CFStringRef primaryRank)
+static void
+RouteAddFlagsToDescription(RouteRef r, CFMutableStringRef str)
 {
-    struct in_addr     addr = { 0 };
-    boolean_t          exclude_from_nwi = FALSE;
-    RouteFlags         flags = 0;
-    unsigned int       ifindex;
-    char               ifn[IFNAMSIZ];
-    struct in_addr     mask = { 0 };
-    int                        n = 0;
-    boolean_t          add_default = FALSE;
-    boolean_t          add_subnet = FALSE;
-    IPv4RouteRef       r;
-    struct in_addr     subnet = { 0 };
-    struct in_addr     router = { 0 };
-    Rank               rank = kRankAssertionDefault;
-
-    if (dict == NULL) {
-       return (NULL);
+    if ((r->flags & kRouteFlagsIsNULL) != 0) {
+       CFStringAppend(str, CFSTR(" [null]"));
     }
-    if (plist_get_cstring(dict, kSCPropInterfaceName, ifn, sizeof(ifn))
-       == FALSE) {
-       return (NULL);
+    else {
+       Rank    rank_assertion = RANK_ASSERTION_MASK(r->rank);
+
+       switch (rank_assertion) {
+       case kRankAssertionFirst:
+           CFStringAppend(str, CFSTR(" [first]"));
+           break;
+       case kRankAssertionLast:
+           CFStringAppend(str, CFSTR(" [last]"));
+           break;
+       case kRankAssertionNever:
+           CFStringAppend(str, CFSTR(" [never]"));
+           break;
+       default:
+           break;
+       }
+       if ((r->flags & kRouteFlagsKernelManaged) != 0) {
+           CFStringAppend(str, CFSTR(" [kern]"));
+       }
+       if ((r->flags & kRouteFlagsIsScoped) != 0) {
+           CFStringAppend(str, CFSTR(" [SCOPED]"));
+       }
     }
-#ifdef TEST_IPV4_ROUTELIST
-    ifindex = 0;
-#else /* TEST_IPV4_ROUTELIST */
-    ifindex = if_nametoindex(ifn);
-    if (ifindex == 0) {
-       /* interface doesn't exist */
-       return (NULL);
+    return;
+}
+
+#if    !TARGET_IPHONE_SIMULATOR
+static RouteRef
+RouteListFindRoute(RouteListInfoRef info, RouteListRef routes, RouteRef route)
+{
+    int                i;
+    RouteRef   match = NULL;
+    RouteRef   scan;
+
+    for (i = 0, scan = RouteListGetFirstRoute(info, routes);
+        i < routes->count;
+        i++, scan = RouteGetNextRoute(info, scan)) {
+       if ((*info->route_equal)(scan, route)) {
+           match = scan;
+           break;
+       }
+
     }
-#endif /* TEST_IPV4_ROUTELIST */
-    if (cfstring_to_ip(CFDictionaryGetValue(dict, kSCPropNetIPv4Router),
-                      &router) == 0) {
-       (void)dict_get_first_ip(dict, kSCPropNetIPv4DestAddresses, &router);
-    }
-    if (dict_get_first_ip(dict, kSCPropNetIPv4Addresses, &addr)
-       && dict_get_first_ip(dict, kSCPropNetIPv4SubnetMasks, &mask)) {
-       /* subnet route */
-       subnet = subnet_addr(addr, mask);
-       /* ignore link-local subnets, let IPConfiguration handle them for now */
-       if (ntohl(subnet.s_addr) != IN_LINKLOCALNETNUM) {
-           add_subnet = TRUE;
-           n++;
-       } else if (router.s_addr == 0) {
-           exclude_from_nwi = TRUE;
+    return (match);
+}
+
+typedef enum {
+    kRouteLookupFlagsNone = 0x0,
+    kRouteLookupFlagsExcludeInterface = 0x1
+} RouteLookupFlags;
+
+static RouteRef
+RouteListLookup(RouteListInfoRef info,
+               RouteListRef routes,
+               const void * address,
+               int n_bits,
+               IFIndex ifindex,
+               RouteLookupFlags lookup_flags)
+{
+    RouteRef   best_match = NULL;
+    RouteRef   candidate;
+    int                i;
+
+    for (i = 0, candidate = RouteListGetFirstRoute(info, routes);
+        i < routes->count;
+        i++, candidate = RouteGetNextRoute(info, candidate)) {
+       if (candidate->ifindex == 0 || candidate->exclude_ifindex != 0) {
+           /* ignore exclude routes */
+           continue;
        }
-    }
-    if (addr.s_addr == 0) {
-       /* thanks for playing */
-       return (NULL);
-    }
-    if (router.s_addr == 0) {
-       /*
-        * If no router is configured, demote the rank. If there's already
-        * a rank assertion that indicates RankNever, use that, otherwise
-        * use RankLast.
-        */
-       flags |= kRouteIsDirectToInterfaceFlag;
-       if (primaryRank != NULL
-           && PrimaryRankGetRankAssertion(primaryRank) == kRankAssertionNever) {
-           rank = kRankAssertionNever;
+       if ((lookup_flags & kRouteLookupFlagsExcludeInterface) != 0) {
+           /* exclude interfaces with the same interface index */
+           if (ifindex == candidate->ifindex) {
+               continue;
+           }
        }
-       else {
-           rank = kRankAssertionLast;
+       else if (ifindex != candidate->ifindex) {
+           continue;
        }
-    }
-    else {
-       /*
-        * If the router address is our address and the subnet mask is
-        * not 255.255.255.255, assume all routes are local to the interface.
-        */
-       if (addr.s_addr == router.s_addr
-           && mask.s_addr != INADDR_BROADCAST) {
-           flags |= kRouteIsDirectToInterfaceFlag;
+       if ((candidate->flags & kRouteFlagsHasGateway) != 0
+           && RouteAddressCompare(info,
+                                  (*info->route_gateway)(candidate),
+                                  address) == 0) {
+           /* skip route whose gateway is the address we're looking for */
+           continue;
        }
-       if (primaryRank != NULL) {
-           rank = PrimaryRankGetRankAssertion(primaryRank);
-       } else if (get_override_primary(dict)) {
-           rank = kRankAssertionFirst;
+       if ((candidate->flags & kRouteFlagsIsHost) != 0) {
+           /* if host route and we're looking for an exact match */
+           if (n_bits == info->all_bits_set
+               && RouteAddressCompare(info,
+                                      (*info->route_destination)(candidate),
+                                      address) == 0) {
+               /* found exact match */
+               best_match = candidate;
+               break;
+           }
+           /* skip it */
+           continue;
+       }
+       /* verify that address is on the same subnet */
+       if ((*info->route_same_subnet)(candidate, address) == FALSE) {
+           /* different subnet */
+           continue;
        }
-    }
 
-    if (S_dict_get_boolean(dict, kIsNULL, FALSE)) {
-       exclude_from_nwi = TRUE;
-       flags |= kRouteIsNULLFlag;
+       if (candidate->prefix_length == n_bits) {
+           /* exact match */
+           best_match = candidate;
+           break;
+       }
+       if (candidate->prefix_length > n_bits) {
+           /* matched too many bits */
+           continue;
+       }
+       if (best_match == NULL
+           || candidate->prefix_length > best_match->prefix_length) {
+           best_match = candidate;
+       }
     }
+    return (best_match);
+}
 
-    if (rank == kRankAssertionNever) {
-       flags |= kRouteIsScopedFlag;
-    }
 
-    if (add_subnet && (flags & kRouteIsDirectToInterfaceFlag) == 0
-       && subnet.s_addr != subnet_addr(router, mask).s_addr) {
-       flags |= kRouteIsNotSubnetLocalFlag;
-    }
+/*
+ * Function: RouteProcess
+ * Purpose:
+ *   Function to process adding or removing the specified route.
+ *   In the case of adding, that may involve first processing the gateway
+ *   route (recursively).
+ */
+static boolean_t
+RouteProcess(RouteRef route,
+            RouteCommand cmd,
+            RouteListApplyContextRef context)
+{
+    RouteLog           route_log = context->info->route_log;
+    RouteApply         route_apply = context->info->route_apply;
+    RouteGateway       route_gateway = context->info->route_gateway;
+    int                        retval;
 
-    if (strncmp(ifn, "lo0", sizeof(ifn)) != 0) {
-       add_default = TRUE;
-       n++;
+    switch (cmd) {
+    case kRouteCommandAdd:
+       if ((route->control_flags & kControlFlagsProcessed) != 0) {
+           return ((route->control_flags & kControlFlagsAdded) != 0);
+       }
+       route->control_flags |= kControlFlagsProcessed;
+       if ((route->flags & kRouteFlagsHasGateway) != 0) {
+           boolean_t           added;
+           RouteRef            gateway_route;
+
+           gateway_route
+               = RouteListLookup(context->info,
+                                 context->new_routes,
+                                 (*route_gateway)(route),
+                                 context->info->all_bits_set,
+                                 route->ifindex,
+                                 kRouteLookupFlagsNone);
+           if (gateway_route == NULL) {
+               (*route_log)(LOG_NOTICE, route,
+                            "IPMonitor RouteProcess: no gateway route");
+           }
+           else {
+#define MAX_RECURSE_DEPTH      10
+               /* avoid infinite recursion */
+               if (context->depth == MAX_RECURSE_DEPTH) {
+                   (*route_log)(LOG_NOTICE, route,
+                                "IPMonitor RouteProcess: "
+                                "routing loop detected, not adding");
+                   return (FALSE);
+               }
+               /* recurse to add gateway route */
+               context->depth++;
+               added = RouteProcess(gateway_route,
+                                    kRouteCommandAdd,
+                                    context);
+               context->depth--;
+               if (added == FALSE) {
+                   (*route_log)(LOG_NOTICE, route,
+                                "IPMonitor RouteProcess: failed to add");
+                   return (FALSE);
+               }
+           }
+       }
+       retval = (*route_apply)(route, RTM_ADD, context->sockfd);
+       if (retval == EEXIST) {
+           /* delete and add again */
+           (void)(*route_apply)(route, RTM_DELETE, context->sockfd);
+           retval = (*route_apply)(route, RTM_ADD, context->sockfd);
+       }
+       switch (retval) {
+       default:
+           my_log(LOG_NOTICE,
+                  "IPMonitor RouteProcess failed to add route, %s:",
+                  strerror(retval));
+           (*route_log)(LOG_NOTICE, route, NULL);
+           break;
+       case 0:
+       case EROUTENOTAPPLIED:
+           if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+               char            buf[64];
+               const char *    str;
+
+               str = (retval == EROUTENOTAPPLIED) ? "!" : "";
+               snprintf(buf, sizeof(buf), "%sAdd new[%ld]",
+                        str,
+                        RouteListRouteIndex(context->info,
+                                            context->new_routes,
+                                            route));
+               (*route_log)(LOG_DEBUG, route, buf);
+           }
+           route->control_flags |= kControlFlagsAdded;
+           break;
+       }
+       break;
+    case kRouteCommandRemove:
+       retval = (*route_apply)(route, RTM_DELETE, context->sockfd);
+       switch (retval) {
+       case 0:
+       case ESRCH:
+       case EROUTENOTAPPLIED:
+           if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+               char            buf[64];
+               const char *    str;
+
+               str = (retval == EROUTENOTAPPLIED) ? "!" : "";
+               snprintf(buf, sizeof(buf), "%sRemove old[%ld]%s",
+                        str,
+                        RouteListRouteIndex(context->info,
+                                            context->old_routes,
+                                            route),
+                        (retval == ESRCH) ? "(ESRCH)" : "");
+               (*route_log)(LOG_DEBUG, route, buf);
+           }
+           break;
+       default:
+           my_log(LOG_NOTICE,
+                  "IPMonitor RouteProcess failed to remove"
+                  " route, %s", strerror(retval));
+           (*route_log)(LOG_NOTICE, route, NULL);
+           break;
+       }
+       break;
+    default:
+       break;
     }
+    return (TRUE);
+}
 
-    if (routes == NULL || routes->size < n) {
-       routes = (IPv4RouteListRef)malloc(IPv4RouteListComputeSize(n));
-       routes->size = n;
-    }
-    bzero(routes, IPv4RouteListComputeSize(n));
-    routes->count = n;
-    routes->exclude_from_nwi = exclude_from_nwi;
+static void
+RouteListApply(RouteListInfoRef info,
+              RouteListRef old_routes, RouteListRef new_routes,
+              int sockfd)
+{
+    RouteListApplyContext      context;
+    int                                i;
+    RouteRef                   scan;
 
-    /* start at the beginning */
-    r = routes->list;
+    if (old_routes == new_routes && old_routes == NULL) {
+       /* both old and new are NULL, so there's nothing to do */
+       return;
+    }
+    bzero(&context, sizeof(context));
+    context.old_routes = old_routes;
+    context.new_routes = new_routes;
+    context.sockfd = sockfd;
+    context.info = info;
+    if (old_routes != NULL) {
+       for (i = 0, scan = RouteListGetFirstRoute(info, old_routes);
+            i < old_routes->count;
+            i++, scan = RouteGetNextRoute(info, scan)) {
+           RouteRef    new_route = NULL;
 
-    if (add_default) {
-       /* add the default route */
-       r->ifindex = ifindex;
-       strlcpy(r->ifname, ifn, sizeof(r->ifname));
-       r->ifa = addr;
-       r->flags = flags;
-       if ((flags & kRouteIsDirectToInterfaceFlag) == 0) {
-           r->gateway = router;
-       }
-       else {
-           r->gateway = addr;
+           if (new_routes != NULL) {
+               new_route = RouteListFindRoute(info, new_routes, scan);
+           }
+           if (new_route == NULL) {
+               if ((scan->control_flags & kControlFlagsAdded) != 0) {
+                   RouteProcess(scan, kRouteCommandRemove, &context);
+               }
+           }
        }
-       r->rank = rank;
-       r++;
     }
-
-    /* add the subnet route */
-    if (add_subnet) {
-       if ((flags & kRouteIsNULLFlag) != 0) {
-           r->flags |= kRouteIsNULLFlag;
+    if (new_routes != NULL) {
+       if (old_routes != NULL) {
+           /* preserve the control flags from any old routes */
+           for (i = 0, scan = RouteListGetFirstRoute(info, new_routes);
+                i < new_routes->count;
+                i++, scan = RouteGetNextRoute(info, scan)) {
+               RouteRef        old_route = NULL;
+
+               old_route = RouteListFindRoute(info, old_routes, scan);
+               if (old_route != NULL) {
+                   /* preserve the control state in the new route */
+                   scan->control_flags = old_route->control_flags;
+               }
+           }
+       }
+       /* add any routes that need to be added */
+       for (i = 0, scan = RouteListGetFirstRoute(info, new_routes);
+            i < new_routes->count;
+            i++, scan = RouteGetNextRoute(info, scan)) {
+           if ((scan->control_flags & kControlFlagsProcessed) != 0) {
+               continue;
+           }
+           RouteProcess(scan, kRouteCommandAdd, &context);
        }
-       r->ifindex = ifindex;
-       r->gateway = addr;
-       r->dest = subnet;
-       r->mask = mask;
-       strlcpy(r->ifname, ifn, sizeof(r->ifname));
-       r->ifa = addr;
-       r->rank = rank;
     }
-    return (routes);
+    return;
 }
-
 /*
- * Function: parse_component
+ * Function: RouteListFinalize
  * Purpose:
- *   Given a string 'key' and a string prefix 'prefix',
- *   return the next component in the slash '/' separated
- *   key.
+ *   Look for excluded routes. If the excluded route does not have an assigned
+ *   interface, search for a route that *does not* go over the excluded
+ *   interface.
  *
- * Examples:
- * 1. key = "a/b/c" prefix = "a/"
- *    returns "b"
- * 2. key = "a/b/c" prefix = "a/b/"
- *    returns "c"
+ *   If the excluded route does have an assigned interface, search for a route
+ *   that *does* go over the assigned interface.
+ *
+ *   Set the gateway on the excluded route to match the gateway of the found
+ *   route.
  */
-static CF_RETURNS_RETAINED CFStringRef
-parse_component(CFStringRef key, CFStringRef prefix)
+static void
+RouteListFinalize(RouteListInfoRef info, RouteListRef routes)
 {
-    CFMutableStringRef comp;
-    CFRange            range;
+    int                        i;
+    RouteRef           scan;
 
-    if (CFStringHasPrefix(key, prefix) == FALSE) {
-       return (NULL);
-    }
-    comp = CFStringCreateMutableCopy(NULL, 0, key);
-    if (comp == NULL) {
-       return (NULL);
+    if (routes == NULL) {
+       return;
     }
-    CFStringDelete(comp, CFRangeMake(0, CFStringGetLength(prefix)));
-    range = CFStringFind(comp, CFSTR("/"), 0);
-    if (range.location == kCFNotFound) {
-       return (comp);
+    for (i = 0, scan = RouteListGetFirstRoute(info, routes);
+        i < routes->count;
+        i++, scan = RouteGetNextRoute(info, scan)) {
+       RouteRef                route;
+       IFIndex                 ifindex;
+       RouteLookupFlags        flags;
+
+       if (scan->exclude_ifindex == 0) {
+           continue;
+       }
+       if (scan->ifindex == 0) {
+           ifindex = scan->exclude_ifindex;
+           flags = kRouteLookupFlagsExcludeInterface;
+       }
+       else {
+           ifindex = scan->ifindex;
+           flags = kRouteLookupFlagsNone;
+       }
+       route = RouteListLookup(info, routes,
+                               (*info->route_destination)(scan),
+                               scan->prefix_length, ifindex, flags);
+       if (route == NULL) {
+           (*info->route_log)(LOG_NOTICE, (RouteRef)scan,
+                              "IPMonitor: can't resolve excluded route");
+       }
+       else {
+           if ((S_IPMonitor_debug & kDebugFlag8) != 0) {
+               (*info->route_log)(LOG_DEBUG, (RouteRef)scan, "Excluded route");
+               (*info->route_log)(LOG_DEBUG, (RouteRef)route, "Resolved to");
+           }
+           scan->ifindex = route->ifindex;
+           if ((route->flags & kRouteFlagsHasGateway) != 0) {
+               (*info->route_set_gateway)(scan, (*info->route_gateway)(route));
+               scan->flags |= kRouteFlagsHasGateway;
+               if (scan->prefix_length == info->all_bits_set) {
+                   scan->flags |= kRouteFlagsIsHost;
+               }
+           }
+           else {
+               /* routes directly to interface */
+               scan->flags &= ~(kRouteFlagsHasGateway | kRouteFlagsIsHost);
+           }
+       }
     }
-    range.length = CFStringGetLength(comp) - range.location;
-    CFStringDelete(comp, range);
-    return (comp);
+    return;
 }
+#endif /* !TARGET_IPHONE_SIMULATOR */
 
-static CFMutableDictionaryRef
-service_dict_copy(CFStringRef serviceID)
+/**
+ ** IPv4Route*
+ **/
+
+#define IPV4_ROUTE_ALL_BITS_SET                32
+
+static __inline__ struct in_addr
+subnet_addr(struct in_addr addr, struct in_addr mask)
 {
-    CFDictionaryRef            d = NULL;
-    CFMutableDictionaryRef     service_dict;
+    struct in_addr     net;
 
-    /* create a modifyable dictionary, a copy or a new one */
-    d = CFDictionaryGetValue(S_service_state_dict, serviceID);
-    if (d == NULL) {
-       service_dict
-           = CFDictionaryCreateMutable(NULL, 0,
-                                       &kCFTypeDictionaryKeyCallBacks,
-                                       &kCFTypeDictionaryValueCallBacks);
-    }
-    else {
-       service_dict = CFDictionaryCreateMutableCopy(NULL, 0, d);
-    }
-    return (service_dict);
+    net.s_addr = addr.s_addr & mask.s_addr;
+    return (net);
 }
 
 static void
-log_service_entity(int level, CFStringRef serviceID, CFStringRef entity,
-                  CFStringRef operation, CFTypeRef val)
+IPv4RouteCopyDescriptionWithString(IPv4RouteRef r, CFMutableStringRef str)
 {
+    if ((r->flags & kRouteFlagsIsHost) != 0) {
+       CFStringAppendFormat(str, NULL,
+                            CFSTR("Host " IP_FORMAT),
+                            IP_LIST(&r->dest));
+    }
+    else {
+       CFStringAppendFormat(str, NULL,
+                            CFSTR("Net " IP_FORMAT),
+                            IP_LIST(&r->dest));
+       CFStringAppendFormat(str, NULL, CFSTR("/%d"),
+                            r->prefix_length);
+    }
+    if ((r->flags & kRouteFlagsHasGateway) != 0) {
+       CFStringAppendFormat(str, NULL,
+                            CFSTR(" Gate " IP_FORMAT),
+                            IP_LIST(&r->gateway));
+    }
+    RouteAddInterfaceToDescription((RouteRef)r, str);
+    if (r->ifa.s_addr != 0) {
+       CFStringAppendFormat(str, NULL,
+                            CFSTR(" Ifa " IP_FORMAT),
+                            IP_LIST(&r->ifa));
+    }
+    RouteAddFlagsToDescription((RouteRef)r, str);
+    return;
+}
 
-    CFDataRef  route_list;
-    CFMutableStringRef this_val = NULL;
+static CFStringRef
+IPv4RouteCopyDescription(RouteRef r)
+{
+    CFMutableStringRef str;
 
-    if (CFEqual(entity, kSCEntNetIPv4) && isA_CFDictionary(val) != NULL) {
-       CFDictionaryRef    service_dict = NULL;
+    str = CFStringCreateMutable(NULL, 0);
+    IPv4RouteCopyDescriptionWithString((IPv4RouteRef)r, str);
+    return (str);
+}
 
-       route_list = CFDictionaryGetValue(val, kIPv4DictRoutes);
-       if (route_list != NULL) {
-           /* ALIGN: CF should align to at least 8-byte boundaries */
-           this_val = IPv4RouteListCopyDescription((IPv4RouteListRef)
-                                                   (void *)CFDataGetBytePtr(route_list));
-       }
+#ifdef TEST_IPV4_ROUTELIST
+static CFMutableStringRef
+IPv4RouteListCopyDescription(IPv4RouteListRef routes);
 
-       service_dict = CFDictionaryGetValue(val, kIPv4DictService);
+static void
+IPv4RouteLog(int level, RouteRef route, const char * msg)
+{
+    CFStringRef        str = IPv4RouteCopyDescription(route);
 
-       if (service_dict != NULL && isA_CFDictionary(service_dict) != NULL) {
-           if (this_val == NULL) {
-               this_val = CFStringCreateMutable(NULL, 0);
-           }
-           CFStringAppendFormat(this_val, NULL, CFSTR("\n <IPv4Dictionary>: %@"), service_dict);
-       }
-       val = this_val;
+    if (msg == NULL) {
+       SCPrint(TRUE, stdout, CFSTR("%@\n"), str);
     }
-    if (val == NULL) {
-       val = CFSTR("<none>");
+    else {
+       SCPrint(TRUE, stdout, CFSTR("%s: %@\n"), msg, str);
     }
-    my_log(level, "IPMonitor: serviceID %@ %@ %@ value = %@",
-          serviceID, operation, entity, val);
-    my_CFRelease(&this_val);
+    CFRelease(str);
     return;
 }
 
-static boolean_t
-service_dict_set(CFStringRef serviceID, CFStringRef entity,
-                CFTypeRef new_val)
+static __inline__ void
+IPv4RouteListPrint(IPv4RouteListRef routes)
 {
-    boolean_t                  changed = FALSE;
-    CFTypeRef                  old_val;
-    CFMutableDictionaryRef     service_dict;
+    CFStringRef        str = IPv4RouteListCopyDescription(routes);
 
-    service_dict = service_dict_copy(serviceID);
-    old_val = CFDictionaryGetValue(service_dict, entity);
-    if (new_val == NULL) {
-       if (old_val != NULL) {
-           if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-               log_service_entity(LOG_DEBUG, serviceID, entity,
-                                  CFSTR("Removed:"), old_val);
-           }
-           CFDictionaryRemoveValue(service_dict, entity);
-           changed = TRUE;
-       }
-    }
-    else {
-       if (old_val == NULL || CFEqual(new_val, old_val) == FALSE) {
-           if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-               log_service_entity(LOG_DEBUG, serviceID, entity,
-                                  CFSTR("Changed: old"), old_val);
-               log_service_entity(LOG_DEBUG, serviceID, entity,
-                                  CFSTR("Changed: new"), new_val);
-           }
-           CFDictionarySetValue(service_dict, entity, new_val);
-           changed = TRUE;
-       }
-    }
-    if (CFDictionaryGetCount(service_dict) == 0) {
-       CFDictionaryRemoveValue(S_service_state_dict, serviceID);
-    }
-    else {
-       CFDictionarySetValue(S_service_state_dict, serviceID, service_dict);
-    }
-    my_CFRelease(&service_dict);
-    return (changed);
-}
-
-static CFDictionaryRef
-service_dict_get(CFStringRef serviceID, CFStringRef entity)
-{
-    CFDictionaryRef    service_dict;
-
-    service_dict = CFDictionaryGetValue(S_service_state_dict, serviceID);
-    if (service_dict == NULL) {
-       return (NULL);
-    }
-    return (CFDictionaryGetValue(service_dict, entity));
+    SCPrint(TRUE, stdout, CFSTR("%@\n"), str);
+    CFRelease(str);
+    return;
 }
 
-#ifndef kSCPropNetHostname
-#define kSCPropNetHostname CFSTR("Hostname")
-#endif
+#else /* TEST_IPV4_ROUTELIST */
 
-__private_extern__
-CFStringRef
-copy_dhcp_hostname(CFStringRef serviceID)
+static __inline__ void
+IPv4RouteLog(int level, RouteRef route, const char * msg)
 {
-    CFDictionaryRef    dict = NULL;
-    CFStringRef                hostname = NULL;
-    CFDictionaryRef    service_dict = NULL;
-
-    dict = service_dict_get(serviceID, kSCEntNetIPv4);
+    CFStringRef        str = IPv4RouteCopyDescription(route);
 
-    if (dict == NULL || isA_CFDictionary(dict) == NULL) {
-       return (NULL);
+    if (msg == NULL) {
+       my_log(level, "%@", str);
     }
-
-    service_dict =
-       CFDictionaryGetValue(dict, kIPv4DictService);
-
-    if (service_dict == NULL
-       || isA_CFDictionary(service_dict) == NULL) {
-       return (NULL);
+    else {
+       my_log(level, "%s: %@", msg, str);
     }
+    CFRelease(str);
+    return;
+}
 
-    hostname =
-       CFDictionaryGetValue(service_dict, kSCPropNetHostname);
+#endif /* TEST_IPV4_ROUTELIST */
 
-    if (hostname != NULL) {
-       CFRetain(hostname);
-    }
+static boolean_t
+IPv4RouteIsEqual(RouteRef r_scan, RouteRef r_route)
+{
+    IPv4RouteRef       route = (IPv4RouteRef)r_route;
+    IPv4RouteRef       scan = (IPv4RouteRef)r_scan;
 
-    return (hostname);
+    return ((scan->dest.s_addr == route->dest.s_addr)
+           && (scan->mask.s_addr == route->mask.s_addr)
+           && (scan->ifindex == route->ifindex)
+           && (scan->ifa.s_addr == route->ifa.s_addr)
+           && (scan->gateway.s_addr == route->gateway.s_addr)
+           && (scan->flags == route->flags));
 }
 
-#if    !TARGET_IPHONE_SIMULATOR
-static void
-ipv6_service_update_router(CFStringRef serviceID, CFDictionaryRef new_val)
+static CFMutableStringRef
+IPv4RouteListCopyDescription(IPv4RouteListRef routes)
 {
-#ifdef SIOCDRADD_IN6
-    int                        if_index;
-    char               ifn[IFNAMSIZ];
-    CFStringRef                new_router = NULL;
-    char               ntopbuf[INET6_ADDRSTRLEN];
-    CFDictionaryRef    old_val = NULL;
-    CFStringRef                old_router = NULL;
-    struct in6_addr    router_ip;
-    int                        s = -1;
+    int                        i;
+    IPv4RouteRef       r;
+    CFMutableStringRef str;
 
-    ifn[0] = '\0';
-    old_val = service_dict_get(serviceID, kSCEntNetIPv6);
-    if (old_val != NULL) {
-       plist_get_cstring(old_val, kSCPropInterfaceName, ifn, sizeof(ifn));
-       old_router = CFDictionaryGetValue(old_val, kSCPropNetIPv6Router);
-    }
-    if (ifn[0] == '\0') {
-       if (new_val == NULL
-           || plist_get_cstring(new_val, kSCPropInterfaceName,
-                                ifn, sizeof(ifn)) == FALSE) {
-           /* no InterfaceName property, ignore it */
-           goto done;
-       }
-    }
-    if_index = if_nametoindex(ifn);
-    if (if_index == 0) {
-       goto done;
-    }
-    s = inet6_dgram_socket();
-    if (s < 0) {
-       my_log(LOG_ERR,
-              "IPMonitor: ipv6_service_update_router: socket failed, %s",
-              strerror(errno));
-       goto done;
-    }
-    if (new_val != NULL) {
-       new_router = CFDictionaryGetValue(new_val, kSCPropNetIPv6Router);
-    }
-    if (S_dict_get_boolean(old_val, kIsNULL, FALSE) == FALSE
-       && old_router != NULL
-       && (new_router == NULL || CFEqual(old_router, new_router) == FALSE)) {
-       /* remove the old Router */
-       if (cfstring_to_ip6(old_router, &router_ip)) {
-           if (IN6_IS_ADDR_LINKLOCAL(&router_ip) ||
-               IN6_IS_ADDR_MC_LINKLOCAL(&router_ip)) {
-               /* scope it */
-               router_ip.__u6_addr.__u6_addr16[1] = htons(if_index);
-           }
-           if (siocdrdel_in6(s, if_index, &router_ip) < 0) {
-               if (errno != EINVAL) {
-                   my_log(LOG_ERR,
-                          "IPMonitor: siocdrdel_in6(%s, %s) failed, %s",
-                          ifn,
-                          inet_ntop(AF_INET6, &router_ip,
-                                    ntopbuf, sizeof(ntopbuf)),
-                          strerror(errno));
-               }
-           }
-           else if (S_IPMonitor_debug & kDebugFlag1) {
-               my_log(LOG_DEBUG,
-                      "IPMonitor: %s removed default route %s",
-                      ifn,
-                      inet_ntop(AF_INET6, &router_ip,
-                                ntopbuf, sizeof(ntopbuf)));
-           }
-       }
-    }
-    /* add the new Router */
-    if (S_dict_get_boolean(new_val, kIsNULL, FALSE) == FALSE
-       && cfstring_to_ip6(new_router, &router_ip)) {
-       if (IN6_IS_ADDR_LINKLOCAL(&router_ip) ||
-           IN6_IS_ADDR_MC_LINKLOCAL(&router_ip)) {
-           /* scope it */
-           router_ip.__u6_addr.__u6_addr16[1] = htons(if_index);
-       }
-       if (siocdradd_in6(s, if_index, &router_ip, 0) < 0) {
-           if (errno != EINVAL) {
-               my_log(LOG_ERR,
-                      "IPMonitor: siocdradd_in6(%s, %s) failed, %s",
-                      ifn,
-                      inet_ntop(AF_INET6, &router_ip,
-                                ntopbuf, sizeof(ntopbuf)),
-                      strerror(errno));
-           }
-       }
-       else if (S_IPMonitor_debug & kDebugFlag1) {
-           my_log(LOG_DEBUG,
-                  "IPMonitor: %s added default route %s",
-                  ifn,
-                  inet_ntop(AF_INET6, &router_ip,
-                            ntopbuf, sizeof(ntopbuf)));
-       }
+    str = CFStringCreateMutable(NULL, 0);
+    CFStringAppendFormat(str, NULL, CFSTR("<IPv4RouteList[%d]> = {"),
+                        routes->count);
+    for (i = 0, r = routes->list; i < routes->count; i++, r++) {
+       CFStringAppendFormat(str, NULL, CFSTR("\n%2d. "), i);
+       IPv4RouteCopyDescriptionWithString(r, str);
     }
-    close(s);
+    CFStringAppend(str, CFSTR("\n}"));
+    return (str);
+}
 
-  done:
-#endif /* SIOCDRADD_IN6 */
-    return;
+static size_t
+IPv4RouteListComputeSize(CFIndex n)
+{
+    return (offsetof(IPv4RouteList, list[n]));
 }
-#endif /* !TARGET_IPHONE_SIMULATOR */
 
-#define        ALLOW_EMPTY_STRING      0x1
+static int
+count_prefix_bits_set(uint32_t n)
+{
+    int        count;
+    const static int8_t bits[16] = {
+       0,      /* 0000 */
+       -1,     /* 0001 */
+       -1,     /* 0010 */
+       -1,     /* 0011 */
+       -1,     /* 0100 */
+       -1,     /* 0101 */
+       -1,     /* 0110 */
+       -1,     /* 0111 */
+       1,      /* 1000 */
+       -1,     /* 1001 */
+       -1,     /* 1010 */
+       -1,     /* 1011 */
+       2,      /* 1100 */
+       -1,     /* 1101 */
+       3,      /* 1110 */
+       4,      /* 1111 */
+    };
 
-static CF_RETURNS_RETAINED CFTypeRef
-sanitize_prop(CFTypeRef val, uint32_t flags)
-{
-    if (val != NULL) {
-       if (isA_CFString(val)) {
-           CFMutableStringRef  str;
+    for (count = 0; n != 0; n >>= 4) {
+       int     nbits = bits[n & 0x0f];
 
-           str = CFStringCreateMutableCopy(NULL, 0, (CFStringRef)val);
-           CFStringTrimWhitespace(str);
-           if (!(flags & ALLOW_EMPTY_STRING) && (CFStringGetLength(str) == 0)) {
-               CFRelease(str);
-               str = NULL;
-           }
-           val = str;
-       } else {
-           CFRetain(val);
+       if (nbits < 0) {
+           return (-1);
        }
+       count += nbits;
     }
-
-    return val;
+    return (count);
 }
 
-static void
-merge_array_prop(CFMutableDictionaryRef        dict,
-                CFStringRef            key,
-                CFDictionaryRef        state_dict,
-                CFDictionaryRef        setup_dict,
-                uint32_t               flags,
-                Boolean                append)
+static uint32_t
+prefix_to_mask32(unsigned int prefix_length)
 {
-    CFMutableArrayRef  merge_prop;
-    CFArrayRef         setup_prop = NULL;
-    CFArrayRef         state_prop = NULL;
-
-    if (setup_dict != NULL) {
-       setup_prop = isA_CFArray(CFDictionaryGetValue(setup_dict, key));
-    }
-    if (state_dict != NULL) {
-       state_prop = isA_CFArray(CFDictionaryGetValue(state_dict, key));
-    }
-
-    if ((setup_prop == NULL) && (state_prop == NULL)) {
-       return;
+    if (prefix_length > 32 || prefix_length == 0) {
+       return (0);
     }
+    return (0xffffffff << (32 - prefix_length));
+}
 
-    merge_prop = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
-    if (setup_prop != NULL) {
-       CFIndex i;
-       CFIndex n;
+static int
+mask_get_prefix_length(struct in_addr mask)
+{
+    int                count;
 
-       n = CFArrayGetCount(setup_prop);
-       for (i = 0; i < n; i++) {
-           CFTypeRef   val;
+    count = count_prefix_bits_set(mask.s_addr);
+    if (count >= 0) {
+       uint32_t        val;
 
-           val = CFArrayGetValueAtIndex(setup_prop, i);
-           val = sanitize_prop(val, flags);
-           if (val != NULL) {
-               CFArrayAppendValue(merge_prop, val);
-               CFRelease(val);
-           }
+       val = prefix_to_mask32(count);
+       if (ntohl(mask.s_addr) != val) {
+           /* expected mask based on prefix length doesn't match */
+           return (-1);
        }
     }
-    if (state_prop != NULL
-       && (setup_prop == NULL || S_append_state)) {
-       CFIndex i;
-       CFIndex n;
-       CFRange setup_range     = CFRangeMake(0, CFArrayGetCount(merge_prop));
+    return (count);
+}
 
-       n = CFArrayGetCount(state_prop);
-       for (i = 0; i < n; i++) {
-           CFTypeRef   val;
+static boolean_t
+IPv4RouteSetPrefixLength(IPv4RouteRef route)
+{
+    int                length;
 
-           val = CFArrayGetValueAtIndex(state_prop, i);
-           val = sanitize_prop(val, flags);
-           if (val != NULL) {
-               if (append || !CFArrayContainsValue(merge_prop, setup_range, val)) {
-                   CFArrayAppendValue(merge_prop, val);
-               }
-               CFRelease(val);
-           }
-       }
-    }
-    if (CFArrayGetCount(merge_prop) > 0) {
-       CFDictionarySetValue(dict, key, merge_prop);
+    length = mask_get_prefix_length(route->mask);
+    if (length < 0) {
+       return (FALSE);
     }
-    CFRelease(merge_prop);
-    return;
+    route->prefix_length = length;
+    return (TRUE);
+}
+
+static const void *
+IPv4RouteGateway(RouteRef r_route)
+{
+    IPv4RouteRef       route = (IPv4RouteRef)r_route;
+    return (&route->gateway);
 }
 
 static void
-pick_prop(CFMutableDictionaryRef       dict,
-         CFStringRef                   key,
-         CFDictionaryRef               state_dict,
-         CFDictionaryRef               setup_dict,
-         uint32_t                      flags)
+IPv4RouteSetGateway(RouteRef r_route, const void * address)
 {
-       CFTypeRef       val     = NULL;
+    IPv4RouteRef       route = (IPv4RouteRef)r_route;
 
-       if (setup_dict != NULL) {
-           val = CFDictionaryGetValue(setup_dict, key);
-           val = sanitize_prop(val, flags);
-       }
-       if (val == NULL && state_dict != NULL) {
-           val = CFDictionaryGetValue(state_dict, key);
-           val = sanitize_prop(val, flags);
-       }
-       if (val != NULL) {
-           CFDictionarySetValue(dict, key, val);
-           CFRelease(val);
-       }
+    route->gateway = *((struct in_addr *)address);
+    return;
+}
 
-       return;
+static const void *
+IPv4RouteDestination(RouteRef r_route)
+{
+    IPv4RouteRef       route = (IPv4RouteRef)r_route;
+    return (&route->dest);
 }
 
-/**
- ** GetEntityChangesFunc functions
- **/
 static boolean_t
-get_ipv4_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
-                CFDictionaryRef setup_dict, CFDictionaryRef info)
+IPv4RouteSameSubnet(RouteRef r_route, const void * addr)
 {
-    CFDictionaryRef            aggregated_dict = NULL;
-    boolean_t                  changed         = FALSE;
-    CFMutableDictionaryRef     dict            = NULL;
-    CFStringRef                        primaryRank     = NULL;
-    IPv4RouteListRef           r;
-#define R_STATIC               3
-    IPv4RouteListRef           routes;
-    /* ALIGN: force align */
-    uint32_t                   routes_buf[roundup(IPv4RouteListComputeSize(R_STATIC), sizeof(uint32_t))];
-    CFDataRef                  routes_data     = NULL;
-    CFDictionaryRef            service_options;
+    const struct in_addr *     address;
+    IPv4RouteRef               route = (IPv4RouteRef)r_route;
 
-    if (state_dict == NULL) {
-       goto done;
-    }
-    service_options = service_dict_get(serviceID, kSCEntNetService);
-    if (service_options != NULL) {
-       primaryRank = CFDictionaryGetValue(service_options, kSCPropNetServicePrimaryRank);
-    }
-    dict = CFDictionaryCreateMutableCopy(NULL, 0, state_dict);
-    if (setup_dict != NULL) {
-       CFStringRef     router;
-       struct in_addr  router_ip;
+    address = (const struct in_addr *)addr;
+    return ((address->s_addr & route->mask.s_addr) == route->dest.s_addr);
+}
 
-       router = CFDictionaryGetValue(setup_dict,
-                                     kSCPropNetIPv4Router);
-       if (router != NULL
-           && cfstring_to_ip(router, &router_ip)) {
-           CFDictionarySetValue(dict,
-                                kSCPropNetIPv4Router,
-                                router);
-       }
+/*
+ * Define: ROUTE_MSG_ADDRS_SPACE
+ * Purpose:
+ *   Since sizeof(sockaddr_dl) > sizeof(sockaddr_in), we need space for
+ *   3 sockaddr_in's and 2 sockaddr_dl's, but pad it just in case
+ *   someone changes the code and doesn't think to modify this.
+ */
+#define ROUTE_MSG_ADDRS_SPACE  (3 * sizeof(struct sockaddr_in) \
+                                + 2 * sizeof(struct sockaddr_dl) \
+                                + 128)
+typedef struct {
+    struct rt_msghdr   hdr;
+    char               addrs[ROUTE_MSG_ADDRS_SPACE];
+} route_msg;
+
+/*
+ * Function: IPv4RouteApply
+ * Purpose:
+ *   Add or remove the specified route to/from the kernel routing table.
+ */
+static int
+IPv4RouteApply(RouteRef r_route, int cmd, int sockfd)
+{
+    int                                len;
+    int                                ret = 0;
+    IPv4RouteRef               route = (IPv4RouteRef)r_route;
+    route_msg                  rtmsg;
+    union {
+       struct sockaddr_in *    in_p;
+       struct sockaddr_dl *    dl_p;
+       void *                  ptr;
+    } rtaddr;
+
+    if (S_netboot && route->dest.s_addr == 0) {
+       /* don't touch the default route */
+       return (EROUTENOTAPPLIED);
     }
-    routes = (IPv4RouteListRef)(void *)routes_buf;
-    routes->size = R_STATIC;
-    routes->count = 0;
-    r = IPv4RouteListCreateWithDictionary(routes, dict, primaryRank);
-    if (r != NULL) {
-       routes_data = CFDataCreate(NULL,
-                                  (const void *)r,
-                                  IPv4RouteListComputeSize(r->count));
-       if (r != routes) {
-           free(r);
-       }
+    if ((route->flags & kRouteFlagsIsScoped) != 0
+       && !S_scopedroute) {
+       return (EROUTENOTAPPLIED);
     }
-    else {
+    if ((route->flags & kRouteFlagsIsNULL) != 0) {
+       return (EROUTENOTAPPLIED);
+    }
+    if (route->ifindex == 0) {
        my_log(LOG_NOTICE,
-              "IPMonitor: %@ invalid IPv4 dictionary = %@",
-              serviceID,
-              dict);
+              "IPMonitor IPv4RouteApply: " IP_FORMAT
+              " no interface specified, ignoring",
+              IP_LIST(&route->dest));
+       return (ENXIO);
     }
-  done:
-    if (routes_data != NULL) {
-       CFStringRef     keys[2];
-       CFTypeRef       values[2];
-
-       keys[0] = kIPv4DictService;
-       values[0] = dict;
-       keys[1] = kIPv4DictRoutes;
-       values[1] = routes_data;
-
-       aggregated_dict = CFDictionaryCreate(NULL,
-                                            (const void**)keys,
-                                            values,
-                                            sizeof(keys)/sizeof(keys[0]),
-                                            &kCFTypeDictionaryKeyCallBacks,
-                                            &kCFTypeDictionaryValueCallBacks);
-
+    if (sockfd == -1) {
+#ifdef TEST_IPV4_ROUTELIST
+       return (0);
+#else /* TEST_IPV4_ROUTELIST */
+       return (EBADF);
+#endif /* TEST_IPV4_ROUTELIST */
     }
-    changed = service_dict_set(serviceID, kSCEntNetIPv4, aggregated_dict);
-    if (routes_data == NULL) {
-       /* clean up the rank too */
-       CFDictionaryRemoveValue(S_ipv4_service_rank_dict, serviceID);
+    memset(&rtmsg, 0, sizeof(rtmsg));
+    rtmsg.hdr.rtm_type = cmd;
+    rtmsg.hdr.rtm_version = RTM_VERSION;
+    rtmsg.hdr.rtm_seq = ++rtm_seq;
+    rtmsg.hdr.rtm_addrs        = RTA_DST | RTA_GATEWAY | RTA_IFP;
+    if (route->ifa.s_addr != 0) {
+       rtmsg.hdr.rtm_addrs |= RTA_IFA;
+    }
+    rtmsg.hdr.rtm_flags = RTF_UP | RTF_STATIC;
+    if ((route->flags & kRouteFlagsIsHost) != 0) {
+       rtmsg.hdr.rtm_flags |= RTF_HOST;
+    }
+    else {
+       rtmsg.hdr.rtm_addrs |= RTA_NETMASK;
+       if ((route->flags & kRouteFlagsHasGateway) == 0) {
+           rtmsg.hdr.rtm_flags |= RTF_CLONING;
+       }
+    }
+    if ((route->flags & kRouteFlagsHasGateway) != 0) {
+       rtmsg.hdr.rtm_flags |= RTF_GATEWAY;
+    }
+    if ((route->flags & kRouteFlagsIsScoped) != 0) {
+       rtmsg.hdr.rtm_index = route->ifindex;
+       rtmsg.hdr.rtm_flags |= RTF_IFSCOPE;
     }
-    my_CFRelease(&dict);
-    my_CFRelease(&aggregated_dict);
-    my_CFRelease(&routes_data);
-    return (changed);
-}
 
-static boolean_t
-get_ipv6_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
-                CFDictionaryRef setup_dict, CFDictionaryRef info)
-{
-    struct in6_addr            addr;
-    CFArrayRef                 addrs;
-    boolean_t                  changed = FALSE;
-    CFMutableDictionaryRef     dict = NULL;
-    CFDictionaryRef            new_dict = NULL;
-    CFStringRef                        router = NULL;
-    struct in6_addr            router_ip;
-    boolean_t                  valid_ip = FALSE;
+    rtaddr.ptr = rtmsg.addrs;
 
-    if (state_dict == NULL) {
-       goto done;
+    /* dest */
+    rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
+    rtaddr.in_p->sin_family = AF_INET;
+    rtaddr.in_p->sin_addr = route->dest;
+    rtaddr.ptr += sizeof(*rtaddr.in_p);
+
+    /* gateway */
+    if ((rtmsg.hdr.rtm_flags & RTF_GATEWAY) != 0) {
+       /* gateway is an IP address */
+       rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
+       rtaddr.in_p->sin_family = AF_INET;
+       rtaddr.in_p->sin_addr = route->gateway;
+       rtaddr.ptr += sizeof(*rtaddr.in_p);
     }
-    addrs = isA_CFArray(CFDictionaryGetValue(state_dict,
-                                            kSCPropNetIPv6Addresses));
-    if (addrs != NULL && CFArrayGetCount(addrs) > 0) {
-       valid_ip = cfstring_to_ip6(CFArrayGetValueAtIndex(addrs, 0), &addr);
+    else {
+       /* gateway is the interface itself */
+       rtaddr.dl_p->sdl_len = sizeof(*rtaddr.dl_p);
+       rtaddr.dl_p->sdl_family = AF_LINK;
+       rtaddr.dl_p->sdl_index = route->ifindex;
+       rtaddr.ptr += sizeof(*rtaddr.dl_p);
     }
-    if (valid_ip == FALSE) {
-       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-           my_log(LOG_DEBUG,
-                  "IPMonitor: %@ has no valid IPv6 address, ignoring",
-                  serviceID);
-       }
-       goto done;
+
+    /* mask */
+    if ((rtmsg.hdr.rtm_addrs & RTA_NETMASK) != 0) {
+       rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
+       rtaddr.in_p->sin_family = AF_INET;
+       rtaddr.in_p->sin_addr = route->mask;
+       rtaddr.ptr += sizeof(*rtaddr.in_p);
     }
-    dict = CFDictionaryCreateMutableCopy(NULL, 0, state_dict);
-    if (setup_dict != NULL) {
-       router = CFDictionaryGetValue(setup_dict,
-                                     kSCPropNetIPv6Router);
-       if (router != NULL && cfstring_to_ip6(router, &router_ip)) {
-           CFDictionarySetValue(dict,
-                                kSCPropNetIPv6Router,
-                                router);
-       }
+
+    /* interface */
+    if ((rtmsg.hdr.rtm_addrs & RTA_IFP) != 0) {
+       rtaddr.dl_p->sdl_len = sizeof(*rtaddr.dl_p);
+       rtaddr.dl_p->sdl_family = AF_LINK;
+       rtaddr.dl_p->sdl_index = route->ifindex;
+       rtaddr.ptr += sizeof(*rtaddr.dl_p);
     }
-    else {
-       router = CFDictionaryGetValue(dict,
-                                     kSCPropNetIPv6Router);
-       if (router != NULL
-           && cfstring_to_ip6(router, &router_ip) == FALSE) {
-           CFDictionaryRemoveValue(dict, kSCPropNetIPv6Router);
-       }
+    /* interface address */
+    if ((rtmsg.hdr.rtm_addrs & RTA_IFA) != 0) {
+       rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
+       rtaddr.in_p->sin_family = AF_INET;
+       rtaddr.in_p->sin_addr = route->ifa;
+       rtaddr.ptr += sizeof(*rtaddr.in_p);
     }
-    new_dict = dict;
 
- done:
+    /* apply the route */
+    len = (int)(sizeof(rtmsg.hdr) + (rtaddr.ptr - (void *)rtmsg.addrs));
+    rtmsg.hdr.rtm_msglen = len;
+    if (write(sockfd, &rtmsg, len) == -1) {
+       ret = errno;
+    }
+    return (ret);
+}
+
+static const RouteListInfo IPv4RouteListInfo = {
+    IPv4RouteListComputeSize,
+
+    IPv4RouteIsEqual,
+    IPv4RouteApply,
+    IPv4RouteGateway,
+    IPv4RouteSetGateway,
+    IPv4RouteDestination,
+    IPv4RouteSameSubnet,
+    IPv4RouteLog,
+    IPv4RouteCopyDescription,
+
+    sizeof(IPv4Route),
+    sizeof(struct in_addr),
+    IPV4_ROUTE_ALL_BITS_SET
+};
 
 #if    !TARGET_IPHONE_SIMULATOR
-    ipv6_service_update_router(serviceID, new_dict);
-#endif /* !TARGET_IPHONE_SIMULATOR */
+static __inline__ void
+IPv4RouteListLog(int level, IPv4RouteListRef routes)
+{
+    CFStringRef        str = IPv4RouteListCopyDescription(routes);
 
-    changed = service_dict_set(serviceID, kSCEntNetIPv6, new_dict);
-    if (new_dict == NULL) {
-       /* clean up the rank too */
-       CFDictionaryRemoveValue(S_ipv6_service_rank_dict, serviceID);
-    }
-    my_CFRelease(&new_dict);
-    return (changed);
+    my_log(level, "%@", str);
+    CFRelease(str);
+    return;
 }
 
 static void
-accumulate_dns_servers(CFArrayRef in_servers, ProtocolFlags active_protos,
-                      CFMutableArrayRef out_servers, CFStringRef interface)
+IPv4RouteListApply(IPv4RouteListRef old_routes, IPv4RouteListRef new_routes,
+                  int sockfd)
 {
-    int                        count;
-    int                        i;
+    RouteListApply(&IPv4RouteListInfo,
+                  (RouteListRef)old_routes, (RouteListRef)new_routes,
+                  sockfd);
+    return;
+}
 
-    count = CFArrayGetCount(in_servers);
-    for (i = 0; i < count; i++) {
-       CFStringRef     addr;
-       struct in6_addr ipv6_addr;
-       struct in_addr  ip_addr;
+static void
+IPv4RouteListFinalize(IPv4RouteListRef routes)
+{
+    RouteListFinalize(&IPv4RouteListInfo, (RouteListRef)routes);
+    return;
+}
+#endif /* !TARGET_IPHONE_SIMULATOR */
 
-       addr = CFArrayGetValueAtIndex(in_servers, i);
-       assert(addr != NULL);
+#ifdef TEST_IPV4_ROUTELIST
+static IPv4RouteListRef
+IPv4RouteListAddRouteList(IPv4RouteListRef routes, int init_size,
+                         IPv4RouteListRef service_routes, Rank rank)
+{
+    return ((IPv4RouteListRef)
+           RouteListAddRouteList(&IPv4RouteListInfo,
+                                 (RouteListRef)routes, init_size,
+                                 (RouteListRef)service_routes, rank));
+}
+#endif /* TEST_IPV4_ROUTELIST */
 
-       if (cfstring_to_ip(addr, &ip_addr)) {
-           /* IPv4 address */
-           if ((active_protos & kProtocolFlagsIPv4) == 0
-               && ntohl(ip_addr.s_addr) != INADDR_LOOPBACK) {
-               if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-                   my_log(LOG_DEBUG,
-                          "IPMonitor: no IPv4 connectivity, "
-                          "ignoring DNS server address ", IP_FORMAT,
-                          IP_LIST(&ip_addr));
-               }
-               continue;
-           }
+static CFStringRef
+plist_get_string(CFDictionaryRef dict, CFStringRef prop_name,
+                char * buf, int buf_size)
+{
+    CFStringRef        val;
 
-           CFRetain(addr);
-       }
-       else if (cfstring_to_ip6(addr, &ipv6_addr)) {
-           /* IPv6 address */
-           if ((active_protos & kProtocolFlagsIPv6) == 0
-               && !IN6_IS_ADDR_LOOPBACK(&ipv6_addr)) {
-               if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-                   char        ntopbuf[INET6_ADDRSTRLEN];
+    val = CFDictionaryGetValue(dict, prop_name);
+    if (isA_CFString(val) == NULL) {
+       return (NULL);
+    }
+    if (CFStringGetCString(val, buf, buf_size, kCFStringEncodingUTF8)
+       == FALSE) {
+       return (NULL);
+    }
+    return (val);
+}
 
-                   my_log(LOG_DEBUG,
-                          "IPMonitor: no IPv6 connectivity, "
-                          "ignoring DNS server address %s",
-                           inet_ntop(AF_INET6, &ipv6_addr,
-                                     ntopbuf, sizeof(ntopbuf)));
-               }
-               continue;
-           }
+typedef struct {
+    struct in_addr     addr;
+    int *              count_p;
+    IFIndex            ifindex;
+    IFIndex            exclude_ifindex;
+    IPv4RouteRef *     route_p;
+    Rank               rank;
+    const char *       descr;
+} AddIPv4RouteContext, * AddIPv4RouteContextRef;
 
-           if ((IN6_IS_ADDR_LINKLOCAL(&ipv6_addr) ||
-                IN6_IS_ADDR_MC_LINKLOCAL(&ipv6_addr))
-               && (interface != NULL)
-               && (CFStringFind(addr, CFSTR("%"), 0).location == kCFNotFound)) {
-               // append interface name to IPv6 link local address
-               addr = CFStringCreateWithFormat(NULL, NULL,
-                                               CFSTR("%@%%%@"),
-                                               addr,
-                                               interface);
-           } else {
-               CFRetain(addr);
-           }
+static void
+AddIPv4Route(const void * value, void * context)
+{
+    AddIPv4RouteContextRef     ctx = (AddIPv4RouteContextRef)context;
+    CFDictionaryRef            dict = (CFDictionaryRef)value;
+    IPv4RouteRef               r = *ctx->route_p;
+
+    dict = isA_CFDictionary(dict);
+    if (dict == NULL
+       || !dict_get_ip(dict, kSCPropNetIPv4RouteDestinationAddress, &r->dest)
+       || !dict_get_ip(dict, kSCPropNetIPv4RouteSubnetMask, &r->mask)) {
+       /* one less route than we expected */
+       if (dict == NULL) {
+           my_log(LOG_NOTICE, "IPMonitor: %s route is not a dictionary",
+                  ctx->descr);
        }
        else {
-           /* bad IP address */
-           my_log(LOG_NOTICE,
-                  "IPMonitor: ignoring bad DNS server address '%@'",
-                  addr);
-           continue;
+           my_log(LOG_NOTICE, "IPMonitor: %s route is invalid, %@",
+                  ctx->descr, dict);
+       }
+       goto skip;
+    }
+    if (IPv4RouteSetPrefixLength(r) == FALSE) {
+       my_log(LOG_NOTICE, "IPMonitor: %s route has invalid subnet mask, %@",
+              ctx->descr, dict);
+       goto skip;
+    }
+    r->rank = ctx->rank;
+    r->exclude_ifindex = ctx->exclude_ifindex;
+    if (ctx->ifindex != 0) {
+       r->ifindex = ctx->ifindex;
+       r->ifa = ctx->addr;
+       if (ctx->exclude_ifindex == 0
+           && dict_get_ip(dict,
+                          kSCPropNetIPv4RouteGatewayAddress,
+                          &r->gateway)) {
+           r->flags |= kRouteFlagsHasGateway;
+           if (r->prefix_length == IPV4_ROUTE_ALL_BITS_SET) {
+               r->flags |= kRouteFlagsIsHost;
+           }
        }
+    }
+    else {
+       char            ifname[IFNAMSIZ];
 
-       /* DNS server is valid and one we want */
-       CFArrayAppendValue(out_servers, addr);
-       CFRelease(addr);
+       if (plist_get_string(dict, kSCPropNetIPv4RouteInterfaceName,
+                            ifname, sizeof(ifname)) != NULL) {
+           IFIndex     ifindex;
+
+           ifindex = my_if_nametoindex(ifname);
+           if (ifindex == 0) {
+               my_log(LOG_NOTICE,
+                      "IPMonitor %s: interface %s does not exist, %@",
+                      ctx->descr, ifname, dict);
+               goto skip;
+           }
+           else if (ifindex == ctx->ifindex) {
+               my_log(LOG_NOTICE,
+                      "IPMonitor %s: interface %s unexpected, %@",
+                      ctx->descr, ifname, dict);
+               goto skip;
+           }
+           r->ifindex = ifindex;
+       }
     }
+    (*ctx->route_p)++;
+    return;
+
+ skip:
+    (*ctx->count_p)--;
     return;
+
 }
 
-static void
-merge_dns_servers(CFMutableDictionaryRef new_dict,
-                 CFArrayRef state_servers,
-                 CFArrayRef setup_servers,
-                 Boolean have_setup,
-                 ProtocolFlags active_protos,
-                 CFStringRef interface)
+static boolean_t
+confirm_interface_name(CFDictionaryRef dict, CFStringRef ifname)
 {
-    CFMutableArrayRef  dns_servers;
-    Boolean            have_dns_setup  = FALSE;
+    CFStringRef                confirmed_ifname;
+    boolean_t          confirmed;
 
-    if (state_servers == NULL && setup_servers == NULL) {
-       /* no DNS servers */
-       return;
+    confirmed_ifname
+       = CFDictionaryGetValue(dict, kSCPropConfirmedInterfaceName);
+    if (isA_CFString(confirmed_ifname) != NULL) {
+       confirmed = CFEqual(confirmed_ifname, ifname);
     }
-    dns_servers = CFArrayCreateMutable(NULL, 0,
-                                      &kCFTypeArrayCallBacks);
-    if (setup_servers != NULL) {
-       accumulate_dns_servers(setup_servers, active_protos,
-                              dns_servers, interface);
-       if (CFArrayGetCount(dns_servers) > 0) {
-           have_dns_setup = TRUE;
+    else {
+       confirmed = TRUE;
+    }
+    return (confirmed);
+}
+
+/*
+ * Function: IPv4RouteListCreateWithDictionary
+ *
+ * Purpose:
+ *   Given the service ipv4 entity dictionary, generate the list of routes.
+ *   Currently, this includes just the default route and subnet route,
+ *   if the service has a subnet mask.
+ *
+ * Returns:
+ *   If the passed in route_list is NULL or too small, this routine
+ *   allocates malloc'd memory to hold the routes.
+ */
+static IPv4RouteListRef
+IPv4RouteListCreateWithDictionary(IPv4RouteListRef routes,
+                                 CFDictionaryRef dict,
+                                 CFNumberRef rank_assertion)
+{
+    boolean_t          add_default = FALSE;
+    boolean_t          add_router_subnet = FALSE;
+    boolean_t          add_subnet = FALSE;
+    struct in_addr     addr = { 0 };
+    CFArrayRef         additional_routes = NULL;
+    CFIndex            additional_routes_count;
+    boolean_t          allow_additional_routes = FALSE;
+    boolean_t          exclude_from_nwi = FALSE;
+    CFArrayRef         excluded_routes = NULL;
+    CFIndex            excluded_routes_count;
+    RouteFlags         flags = 0;
+    IFIndex            ifindex;
+    char               ifname[IFNAMSIZ];
+    CFStringRef                ifname_cf;
+    struct in_addr     mask = { 0 };
+    int                        n = 0;
+    int                        prefix_length = 0;
+    Rank               primary_rank = kRankAssertionDefault;
+    IPv4RouteRef       r;
+    Rank               rank = kRankAssertionDefault;
+    struct in_addr     router = { 0 };
+    struct in_addr     subnet = { 0 };
+
+    if (dict == NULL) {
+       return (NULL);
+    }
+    ifname_cf = plist_get_string(dict, kSCPropInterfaceName,
+                                ifname, sizeof(ifname));
+    if (ifname_cf == NULL) {
+       return (NULL);
+    }
+    ifindex = my_if_nametoindex(ifname);
+    if (ifindex == 0) {
+       /* interface doesn't exist */
+       return (NULL);
+    }
+    allow_additional_routes = confirm_interface_name(dict, ifname_cf);
+    if (dict_get_ip(dict, kSCPropNetIPv4Router, &router) == FALSE) {
+       (void)dict_get_first_ip(dict, kSCPropNetIPv4DestAddresses, &router);
+    }
+    if (dict_get_first_ip(dict, kSCPropNetIPv4Addresses, &addr)
+       && dict_get_first_ip(dict, kSCPropNetIPv4SubnetMasks, &mask)) {
+       /* subnet route */
+       subnet = subnet_addr(addr, mask);
+       /* ignore link-local subnets, let IPConfiguration handle them for now */
+       if (ntohl(subnet.s_addr) != IN_LINKLOCALNETNUM) {
+           prefix_length = mask_get_prefix_length(mask);
+           if (prefix_length < 0) {
+               my_log(LOG_NOTICE,
+                      "IPMonitor: ignoring bad subnet mask "
+                      IP_FORMAT " on %s",
+                      IP_LIST(&mask), ifname);
+           }
+           else {
+               add_subnet = TRUE;
+               n++;
+           }
+       }
+       else if (router.s_addr == 0) {
+           exclude_from_nwi = TRUE;
        }
     }
-    if ((CFArrayGetCount(dns_servers) == 0 || S_append_state)
-       && state_servers != NULL) {
-       accumulate_dns_servers(state_servers, active_protos,
-                              dns_servers, NULL);
+    if (addr.s_addr == 0) {
+       /* invalid/non-existent address */
+       return (NULL);
     }
-
-    /*
-     * Here, we determine whether or not we want all queries for this DNS
-     * configuration to be bound to the associated network interface.
-     *
-     * For dynamically derived network configurations (i.e. from State:)
-     * this would be the preferred option using the argument "Hey, the
-     * server told us to use these servers on this network so let's not
-     * argue".
-     *
-     * But, when a DNS configuration has been provided by the user/admin
-     * via the Network pref pane (i.e. from Setup:) we opt to not force
-     * binding of the outbound queries.  The simplest example why we take
-     * this stance is with a multi-homing configuration.  Consider a system
-     * with one network service associated with "en0" and a second service
-     * associated with "en1".  The "en0" service has been set higher in
-     * the network service order so it would be primary but the user/admin
-     * wants the DNS queries to go to a server only accessible via "en1".
-     * Without this exception we would take the DNS server addresses from
-     * the Network pref pane (for "en0") and have the queries bound to
-     * "en0" where they'd never reach their intended destination (via
-     * "en1").  So, our exception to the rule is that we will not bind
-     * user/admin configurations to any specific network interface.
-     *
-     * We also add an exception to the "follow the dynamically derived
-     * network configuration" path for on-the-fly (no Setup: content)
-     * network services.
-     */
-    if (CFArrayGetCount(dns_servers) != 0) {
-       CFDictionarySetValue(new_dict,
-                            kSCPropNetDNSServerAddresses, dns_servers);
-       if (have_setup && !have_dns_setup) {
-           CFDictionarySetValue(new_dict, DNS_CONFIGURATION_SCOPED_QUERY_KEY, kCFBooleanTrue);
+    if (rank_assertion != NULL) {
+       (void)CFNumberGetValue(rank_assertion, kCFNumberSInt32Type,
+                              &primary_rank);
+    }
+    if (router.s_addr == 0) {
+       /* if no router is configured, demote the rank if necessary */
+       switch (primary_rank) {
+       case kRankAssertionLast:
+       case kRankAssertionNever:
+       case kRankAssertionScoped:
+           /* rank is already demoted */
+           break;
+       default:
+           /* demote to RankLast */
+           primary_rank = kRankAssertionLast;
+           break;
+       }
+    }
+    else {
+       /*
+        * If the router address is our address and the subnet mask is
+        * not 255.255.255.255, assume all routes are local to the interface.
+        */
+       if (addr.s_addr == router.s_addr
+           && mask.s_addr != INADDR_BROADCAST) {
+           ; /* all routes local */
+       }
+       else {
+           flags |= kRouteFlagsHasGateway;
+       }
+       if (rank_assertion == NULL && get_override_primary(dict)) {
+           primary_rank = kRankAssertionFirst;
        }
     }
 
-    my_CFRelease(&dns_servers);
-    return;
-}
-
-
-static boolean_t
-get_dns_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
-               CFDictionaryRef setup_dict, CFDictionaryRef info)
-{
-    ProtocolFlags              active_protos   = kProtocolFlagsNone;
-    boolean_t                  changed         = FALSE;
-    CFStringRef                        domain;
-    Boolean                    have_setup      = FALSE;
-    CFStringRef                        interface       = NULL;
-    CFDictionaryRef            ipv4;
-    CFDictionaryRef            ipv6;
-    int                                i;
-    const struct {
-       CFStringRef     key;
-       uint32_t        flags;
-       Boolean         append;
-    } merge_list[] = {
-       { kSCPropNetDNSSearchDomains,                   0,                      FALSE },
-       { kSCPropNetDNSSortList,                        0,                      FALSE },
-       { kSCPropNetDNSSupplementalMatchDomains,        ALLOW_EMPTY_STRING,     TRUE  },
-       { kSCPropNetDNSSupplementalMatchOrders,         0,                      TRUE  },
-    };
-    CFMutableDictionaryRef      new_dict = NULL;
-    const CFStringRef          pick_list[] = {
-       kSCPropNetDNSDomainName,
-       kSCPropNetDNSOptions,
-       kSCPropNetDNSSearchOrder,
-       kSCPropNetDNSServerPort,
-       kSCPropNetDNSServerTimeout,
-       kSCPropNetDNSServiceIdentifier,
-       kSCPropNetDNSSupplementalMatchDomainsNoSearch,
-    };
-    IPv4RouteListRef           routes = NULL;
+    if (S_dict_get_boolean(dict, kIsNULL, FALSE)) {
+       exclude_from_nwi = TRUE;
+       flags |= kRouteFlagsIsNULL;
+    }
 
-    if ((state_dict == NULL) && (setup_dict == NULL)) {
-       /* there is no DNS content */
-       goto done;
+    switch (primary_rank) {
+    case kRankAssertionScoped:
+       /* Scoped means all routes for the service get scoped */
+       primary_rank = rank = kRankAssertionNever;
+       flags |= kRouteFlagsIsScoped;
+       break;
+    case kRankAssertionNever:
+       /* Never means just the default route gets scoped */
+       rank = kRankAssertionLast;
+       flags |= kRouteFlagsIsScoped;
+       break;
+    default:
+       rank = primary_rank;
+       break;
     }
 
-    ipv4 = service_dict_get(serviceID, kSCEntNetIPv4);
-    routes = ipv4_dict_get_routelist(ipv4);
+    if ((flags & kRouteFlagsHasGateway) != 0) {
+       add_router_subnet = TRUE;
+       n++;
+    }
 
-    if (routes != NULL) {
-       if (get_service_setup_entity(info, serviceID, kSCEntNetIPv4) != NULL) {
-           have_setup = TRUE;
+    if (ifindex != lo0_ifindex()) {
+       add_default = TRUE;
+       n++;
+    }
+    if (allow_additional_routes) {
+       additional_routes
+           = CFDictionaryGetValue(dict, kSCPropNetIPv4AdditionalRoutes);
+       additional_routes = isA_CFArray(additional_routes);
+       if (additional_routes != NULL) {
+           additional_routes_count = CFArrayGetCount(additional_routes);
+           n += additional_routes_count;
+       }
+       excluded_routes
+           = CFDictionaryGetValue(dict, kSCPropNetIPv4ExcludedRoutes);
+       excluded_routes = isA_CFArray(excluded_routes);
+       if (excluded_routes != NULL) {
+           excluded_routes_count = CFArrayGetCount(excluded_routes);
+           n += excluded_routes_count;
        }
+    }
+    if (routes == NULL || routes->size < n) {
+       routes = (IPv4RouteListRef)malloc(IPv4RouteListComputeSize(n));
+       routes->size = n;
+    }
+    bzero(routes, IPv4RouteListComputeSize(n));
+    routes->count = n;
+    if (exclude_from_nwi) {
+       routes->flags |= kRouteListFlagsExcludeNWI;
+    }
 
-       active_protos |= kProtocolFlagsIPv4;
+    /* start at the beginning */
+    r = routes->list;
 
-       interface = ipv4_dict_get_ifname(ipv4);
+    if (add_default) {
+       /* add the default route */
+       routes->flags |= kRouteListFlagsHasDefault;
+       r->ifindex = ifindex;
+       r->ifa = addr;
+       r->flags = flags;
+       if ((flags & kRouteFlagsHasGateway) != 0) {
+           r->gateway = router;
+       }
+       else {
+           r->gateway = addr;
+       }
+       r->rank = primary_rank;
+       r++;
     }
 
-    ipv6 = service_dict_get(serviceID, kSCEntNetIPv6);
-    if (ipv6 != NULL) {
-       if (!have_setup &&
-           get_service_setup_entity(info, serviceID, kSCEntNetIPv6) != NULL) {
-           have_setup = TRUE;
+    /* add the subnet route */
+    if (add_subnet) {
+       if ((flags & kRouteFlagsIsNULL) != 0) {
+           r->flags |= kRouteFlagsIsNULL;
        }
+       r->ifindex = ifindex;
+       r->gateway = addr;
+       r->dest = subnet;
+       r->mask = mask;
+       r->prefix_length = prefix_length;
+       r->ifa = addr;
+       r->rank = rank;
+       r++;
+    }
 
-       active_protos |= kProtocolFlagsIPv6;
-
-       if (interface == NULL) {
-           interface = CFDictionaryGetValue(ipv6,
-                                            kSCPropInterfaceName);
+    /* add the router subnet route */
+    if (add_router_subnet) {
+       if ((flags & kRouteFlagsIsNULL) != 0) {
+           r->flags |= kRouteFlagsIsNULL;
        }
+       r->ifindex = ifindex;
+       r->gateway = addr;
+       r->dest = router;
+       r->mask.s_addr = INADDR_BROADCAST;
+       r->prefix_length = IPV4_ROUTE_ALL_BITS_SET;
+       r->ifa = addr;
+       r->rank = rank;
+       r++;
     }
 
+    if (additional_routes != NULL || excluded_routes != NULL) {
+       AddIPv4RouteContext             context;
 
-    if (active_protos == kProtocolFlagsNone) {
-       /* there is no IPv4 nor IPv6 */
-       if (state_dict == NULL) {
-           /* ... and no DNS content that we care about */
-           goto done;
+       bzero(&context, sizeof(context));
+       context.count_p = &routes->count;
+       context.route_p = &r;
+       context.rank = rank;
+
+       /* additional routes */
+       if (additional_routes != NULL) {
+           context.ifindex = ifindex;
+           context.addr = addr;
+           context.descr = "AdditionalRoutes";
+           CFArrayApplyFunction(additional_routes,
+                                CFRangeMake(0, additional_routes_count),
+                                AddIPv4Route, &context);
+       }
+       /* excluded routes */
+       if (excluded_routes != NULL) {
+           context.descr = "ExcludedRoutes";
+           /* exclude this interface */
+           context.ifindex = 0;
+           context.exclude_ifindex = ifindex;
+           CFArrayApplyFunction(excluded_routes,
+                                CFRangeMake(0, excluded_routes_count),
+                                AddIPv4Route, &context);
        }
-       setup_dict = NULL;
     }
+    return (routes);
+}
 
-    /* merge DNS configuration */
-    new_dict = CFDictionaryCreateMutable(NULL, 0,
-                                        &kCFTypeDictionaryKeyCallBacks,
-                                        &kCFTypeDictionaryValueCallBacks);
+/**
+ ** IPv6Route*
+ **/
+#define IPV6_ROUTE_ALL_BITS_SET                128
 
-    if (active_protos == kProtocolFlagsNone) {
-       merge_dns_servers(new_dict,
-                         my_CFDictionaryGetArray(state_dict,
-                                                 kSCPropNetDNSServerAddresses),
-                         NULL,
-                         FALSE,
-                         kProtocolFlagsIPv4 | kProtocolFlagsIPv6,
-                         NULL);
-    }
-    else {
-       merge_dns_servers(new_dict,
-                         my_CFDictionaryGetArray(state_dict,
-                                                 kSCPropNetDNSServerAddresses),
-                         my_CFDictionaryGetArray(setup_dict,
-                                                 kSCPropNetDNSServerAddresses),
-                         have_setup,
-                         active_protos,
-                         interface);
+static boolean_t
+ipv6_prefix_length_is_valid(int prefix_length)
+{
+    if (prefix_length < 0 || prefix_length > IPV6_ROUTE_ALL_BITS_SET) {
+       return (FALSE);
     }
+    return (TRUE);
+}
 
-    for (i = 0; i < sizeof(merge_list)/sizeof(merge_list[0]); i++) {
-       merge_array_prop(new_dict,
-                        merge_list[i].key,
-                        state_dict,
-                        setup_dict,
-                        merge_list[i].flags,
-                        merge_list[i].append);
-    }
-
-    for (i = 0; i < sizeof(pick_list)/sizeof(pick_list[0]); i++) {
-       pick_prop(new_dict,
-                 pick_list[i],
-                 state_dict,
-                 setup_dict,
-                 0);
-    }
-
-    if (active_protos == kProtocolFlagsNone) {
-       /* there is no IPv4 nor IPv6, only supplemental or service-specific DNS */
-       if (CFDictionaryContainsKey(new_dict,
-                                   kSCPropNetDNSSupplementalMatchDomains)) {
-           /* only keep State: supplemental */
-           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSDomainName);
-           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSearchDomains);
-           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSearchOrder);
-           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSortList);
+/*
+ * from netinet6/in6.c
+ */
+static void
+in6_len2mask(struct in6_addr * mask, int len)
+{
+    int i;
 
-           if ((interface == NULL) && (setup_dict == NULL) && (state_dict != NULL)) {
-               /*
-                * for supplemental-only configurations, add any scoped (or
-                * wild-card "*") interface
-                */
-               interface = CFDictionaryGetValue(state_dict, kSCPropInterfaceName);
-           }
-       } else if (CFDictionaryContainsKey(new_dict, kSCPropNetDNSServiceIdentifier) &&
-                  (interface == NULL) &&
-                  (state_dict != NULL)) {
-           interface = CFDictionaryGetValue(state_dict, kSCPropInterfaceName);
-       } else {
-           goto done;
-       }
-    }
+    bzero(mask, sizeof(*mask));
+    for (i = 0; i < len / 8; i++)
+       mask->s6_addr[i] = 0xff;
+    if (len % 8)
+       mask->s6_addr[i] = (0xff00 >> (len % 8)) & 0xff;
+}
 
-    if (CFDictionaryGetCount(new_dict) == 0) {
-       my_CFRelease(&new_dict);
-       goto done;
-    }
+static void
+in6_maskaddr(struct in6_addr * addr, const struct in6_addr * mask)
+{
+    int i;
 
-    if (interface != NULL) {
-       CFDictionarySetValue(new_dict, kSCPropInterfaceName, interface);
+    for (i = 0; i < sizeof(addr->s6_addr); i++) {
+       addr->s6_addr[i] &= mask->s6_addr[i];
     }
+    return;
+}
 
-    if (S_append_state) {
-       /*
-        * ensure any specified domain name (e.g. the domain returned by
-        * a DHCP server) is in the search list.
-        */
-       domain = CFDictionaryGetValue(new_dict, kSCPropNetDNSDomainName);
-       if (isA_CFString(domain)) {
-           CFArrayRef      search;
+static void
+in6_netaddr(struct in6_addr * addr, int len)
+{
+    struct in6_addr    mask;
 
-           search = CFDictionaryGetValue(new_dict, kSCPropNetDNSSearchDomains);
-           if (isA_CFArray(search) &&
-               !CFArrayContainsValue(search, CFRangeMake(0, CFArrayGetCount(search)), domain)) {
-               CFMutableArrayRef   new_search;
+    in6_len2mask(&mask, len);
+    in6_maskaddr(addr, &mask);
+    return;
+}
 
-               new_search = CFArrayCreateMutableCopy(NULL, 0, search);
-               CFArrayAppendValue(new_search, domain);
-               CFDictionarySetValue(new_dict, kSCPropNetDNSSearchDomains, new_search);
-               my_CFRelease(&new_search);
-           }
-       }
+static void
+in6_addr_scope_linklocal(struct in6_addr * addr, IFIndex ifindex)
+{
+    if (IN6_IS_ADDR_LINKLOCAL(addr)) {
+       addr->__u6_addr.__u6_addr16[1] = htons(ifindex);
     }
-
- done:
-    changed = service_dict_set(serviceID, kSCEntNetDNS, new_dict);
-    my_CFRelease(&new_dict);
-    return (changed);
+    return;
 }
 
 static void
-merge_dict(const void *key, const void *value, void *context)
+string_append_in6_addr(CFMutableStringRef str, const struct in6_addr * addr)
 {
-       CFMutableDictionaryRef  dict    = (CFMutableDictionaryRef)context;
+    char                       ntopbuf[INET6_ADDRSTRLEN];
 
-       CFDictionarySetValue(dict, key, value);
-       return;
+    CFStringAppendCString(str,
+                         inet_ntop(AF_INET6, addr, ntopbuf, sizeof(ntopbuf)),
+                         kCFStringEncodingASCII);
+    return;
 }
 
-#define        PROXY_AUTO_DISCOVERY_URL        252
-
-static CF_RETURNS_RETAINED CFStringRef
-wpadURL_dhcp(CFDictionaryRef dhcp_options)
+static void
+IPv6RouteCopyDescriptionWithString(IPv6RouteRef r, CFMutableStringRef str)
 {
-    CFStringRef        urlString       = NULL;
-
-    if (isA_CFDictionary(dhcp_options)) {
-       CFDataRef       data;
+    if ((r->flags & kRouteFlagsIsHost) != 0) {
+       CFStringAppend(str, CFSTR("Host "));
+       string_append_in6_addr(str, &r->dest);
+    }
+    else {
+       CFStringAppend(str, CFSTR("Net "));
+       string_append_in6_addr(str, &r->dest);
+       CFStringAppendFormat(str, NULL, CFSTR("/%d"),
+                            r->prefix_length);
+    }
+    if ((r->flags & kRouteFlagsHasGateway) != 0) {
+       CFStringAppend(str, CFSTR(" Gate "));
+       string_append_in6_addr(str, &r->gateway);
+    }
+    RouteAddInterfaceToDescription((RouteRef)r, str);
+    if (!IN6_ARE_ADDR_EQUAL(&r->ifa, &in6addr_any)) {
+       CFStringAppend(str, CFSTR(" Ifa "));
+       string_append_in6_addr(str, &r->ifa);
+    }
+    RouteAddFlagsToDescription((RouteRef)r, str);
+    return;
+}
 
-       data = DHCPInfoGetOptionData(dhcp_options, PROXY_AUTO_DISCOVERY_URL);
-       if (data != NULL) {
-           CFURLRef    url;
-           const UInt8 *urlBytes;
-           CFIndex     urlLen;
+static CFStringRef
+IPv6RouteCopyDescription(RouteRef r)
+{
+    CFMutableStringRef str;
 
-           urlBytes = CFDataGetBytePtr(data);
-           urlLen   = CFDataGetLength(data);
-           while ((urlLen > 0) && (urlBytes[urlLen - 1] == 0)) {
-               // remove trailing NUL
-               urlLen--;
-           }
+    str = CFStringCreateMutable(NULL, 0);
+    IPv6RouteCopyDescriptionWithString((IPv6RouteRef)r, str);
+    return (str);
+}
 
-           if (urlLen <= 0) {
-               return NULL;
-           }
+static CFMutableStringRef
+IPv6RouteListCopyDescription(IPv6RouteListRef routes)
+{
+    int                        i;
+    IPv6RouteRef       r;
+    CFMutableStringRef str;
 
-           url = CFURLCreateWithBytes(NULL, urlBytes, urlLen, kCFStringEncodingUTF8, NULL);
-           if (url != NULL) {
-               urlString = CFURLGetString(url);
-               if (urlString != NULL) {
-                   CFRetain(urlString);
-               }
-               CFRelease(url);
-           }
-       }
+    str = CFStringCreateMutable(NULL, 0);
+    CFStringAppendFormat(str, NULL, CFSTR("<IPv6RouteList[%d]> = {"),
+                        routes->count);
+    for (i = 0, r = routes->list; i < routes->count; i++, r++) {
+       CFStringAppendFormat(str, NULL, CFSTR("\n%2d. "), i);
+       IPv6RouteCopyDescriptionWithString(r, str);
     }
-
-    return urlString;
+    CFStringAppend(str, CFSTR("\n}"));
+    return (str);
 }
 
-static CF_RETURNS_RETAINED CFStringRef
-wpadURL_dns(void)
+#ifdef TEST_IPV6_ROUTELIST
+
+static void
+IPv6RouteLog(int level, RouteRef route, const char * msg)
 {
-    CFURLRef   url;
-    CFStringRef        urlString       = NULL;
+    CFStringRef        str = IPv6RouteCopyDescription(route);
 
-    url = CFURLCreateWithString(NULL, CFSTR("http://wpad/wpad.dat"), NULL);
-    if (url != NULL) {
-       urlString = CFURLGetString(url);
-       if (urlString != NULL) {
-           CFRetain(urlString);
-       }
-       CFRelease(url);
+    if (msg == NULL) {
+       SCPrint(TRUE, stdout, CFSTR("%@\n"), str);
+    }
+    else {
+       SCPrint(TRUE, stdout, CFSTR("%s: %@\n"), msg, str);
     }
+    CFRelease(str);
+    return;
+}
 
-    return urlString;
+static __inline__ void
+IPv6RouteListPrint(IPv6RouteListRef routes)
+{
+    CFStringRef        str = IPv6RouteListCopyDescription(routes);
+
+    SCPrint(TRUE, stdout, CFSTR("%@\n"), str);
+    CFRelease(str);
+    return;
 }
 
-static boolean_t
-get_proxies_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
-                   CFDictionaryRef setup_dict, CFDictionaryRef info)
+#else /* TEST_IPV6_ROUTELIST */
+
+static __inline__ void
+IPv6RouteLog(int level, RouteRef route, const char * msg)
 {
-    ProtocolFlags              active_protos   = kProtocolFlagsNone;
-    boolean_t                  changed         = FALSE;
-    CFStringRef                        interface       = NULL;
-    CFDictionaryRef            ipv4;
-    CFDictionaryRef            ipv6;
-    CFMutableDictionaryRef     new_dict        = NULL;
-    const struct {
-       CFStringRef     key;
-       uint32_t        flags;
-       Boolean         append;
-    } merge_list[] = {
-       { kSCPropNetProxiesSupplementalMatchDomains,    ALLOW_EMPTY_STRING,     TRUE  },
-       { kSCPropNetProxiesSupplementalMatchOrders,     0,                      TRUE  },
-    };
-    const struct {
-           CFStringRef key1;   /* an "enable" key */
-           CFStringRef key2;
-           CFStringRef key3;
-    } pick_list[] = {
-           { kSCPropNetProxiesFTPEnable,       kSCPropNetProxiesFTPProxy,      kSCPropNetProxiesFTPPort        },
-           { kSCPropNetProxiesGopherEnable,    kSCPropNetProxiesGopherProxy,   kSCPropNetProxiesGopherPort     },
-           { kSCPropNetProxiesHTTPEnable,      kSCPropNetProxiesHTTPProxy,     kSCPropNetProxiesHTTPPort       },
-           { kSCPropNetProxiesHTTPSEnable,     kSCPropNetProxiesHTTPSProxy,    kSCPropNetProxiesHTTPSPort      },
-           { kSCPropNetProxiesRTSPEnable,      kSCPropNetProxiesRTSPProxy,     kSCPropNetProxiesRTSPPort       },
-           { kSCPropNetProxiesSOCKSEnable,     kSCPropNetProxiesSOCKSProxy,    kSCPropNetProxiesSOCKSPort      },
-           { kSCPropNetProxiesProxyAutoConfigEnable,
-             kSCPropNetProxiesProxyAutoConfigURLString,
-             kSCPropNetProxiesProxyAutoConfigJavaScript, },
-           { kSCPropNetProxiesProxyAutoDiscoveryEnable,
-             NULL,
-             NULL, }
-    };
-    IPv4RouteListRef           routes          = NULL;
+    CFStringRef        str = IPv6RouteCopyDescription(route);
 
-    if ((state_dict == NULL) && (setup_dict == NULL)) {
-       /* there is no proxy content */
-       goto done;
+    if (msg == NULL) {
+       my_log(level, "%@", str);
+    }
+    else {
+       my_log(level, "%s: %@", msg, str);
     }
+    CFRelease(str);
+    return;
+}
 
-    ipv4 = service_dict_get(serviceID, kSCEntNetIPv4);
-    routes = ipv4_dict_get_routelist(ipv4);
+#endif /* TEST_IPV6_ROUTELIST */
 
-    if (routes != NULL) {
-       active_protos |= kProtocolFlagsIPv4;
+static size_t
+IPv6RouteListComputeSize(CFIndex n)
+{
+    return (offsetof(IPv6RouteList, list[n]));
+}
 
-       interface = ipv4_dict_get_ifname(ipv4);
-    }
 
-    ipv6 = service_dict_get(serviceID, kSCEntNetIPv6);
-    if (ipv6 != NULL) {
-       active_protos |= kProtocolFlagsIPv6;
+typedef struct {
+    struct in6_addr *  addr;
+    int *              count_p;
+    IFIndex            ifindex;
+    IFIndex            exclude_ifindex;
+    IPv6RouteRef *     route_p;
+    Rank               rank;
+    const char *       descr;
+} AddIPv6RouteContext, * AddIPv6RouteContextRef;
 
-       if (interface == NULL) {
-           interface = CFDictionaryGetValue(ipv6,
-                                            kSCPropInterfaceName);
+static void
+AddIPv6Route(const void * value, void * context)
+{
+    AddIPv6RouteContextRef     ctx = (AddIPv6RouteContextRef)context;
+    CFDictionaryRef            dict = (CFDictionaryRef)value;
+    IPv6RouteRef               r = *ctx->route_p;
+
+    dict = isA_CFDictionary(dict);
+    if (dict == NULL
+       || !dict_get_ipv6(dict, kSCPropNetIPv6RouteDestinationAddress, &r->dest)
+       || !dict_get_int(dict, kSCPropNetIPv6RoutePrefixLength,
+                        &r->prefix_length)
+       || !ipv6_prefix_length_is_valid(r->prefix_length)) {
+       /* one less route than we expected */
+       if (dict == NULL) {
+           my_log(LOG_NOTICE, "IPMonitor: %s route is not a dictionary",
+                  ctx->descr);
+       }
+       else {
+           my_log(LOG_NOTICE, "IPMonitor: %s route is invalid, %@",
+                  ctx->descr, dict);
+       }
+       goto skip;
+    }
+    r->rank = ctx->rank;
+    r->exclude_ifindex = ctx->exclude_ifindex;
+    if (ctx->ifindex != 0) {
+       r->ifindex = ctx->ifindex;
+       r->ifa = *ctx->addr;
+       if (ctx->exclude_ifindex == 0
+           && dict_get_ipv6(dict,
+                            kSCPropNetIPv6RouteGatewayAddress,
+                            &r->gateway)) {
+           r->flags |= kRouteFlagsHasGateway;
+           if (r->prefix_length == IPV6_ROUTE_ALL_BITS_SET) {
+               r->flags |= kRouteFlagsIsHost;
+           }
        }
     }
+    else {
+       char            ifname[IFNAMSIZ];
 
-    if (active_protos == kProtocolFlagsNone) {
-       /* there is no IPv4 nor IPv6 */
-       if (state_dict == NULL) {
-           /* ... and no proxy content that we care about */
-           goto done;
+       if (plist_get_string(dict, kSCPropNetIPv6RouteInterfaceName,
+                            ifname, sizeof(ifname)) != NULL) {
+           IFIndex     ifindex;
+
+           ifindex = my_if_nametoindex(ifname);
+           if (ifindex == 0) {
+               my_log(LOG_NOTICE,
+                      "IPMonitor %s: interface %s does not exist, %@",
+                      ctx->descr, ifname, dict);
+               goto skip;
+           }
+           else if (ifindex == ctx->ifindex) {
+               my_log(LOG_NOTICE,
+                      "IPMonitor %s: interface %s unexpected, %@",
+                      ctx->descr, ifname, dict);
+               goto skip;
+           }
+           r->ifindex = ifindex;
        }
-       setup_dict = NULL;
     }
+    (*ctx->route_p)++;
+    return;
 
-    if ((setup_dict != NULL) && (state_dict != NULL)) {
-       CFIndex                 i;
-       CFMutableDictionaryRef  setup_copy;
+ skip:
+    (*ctx->count_p)--;
+    return;
 
-       /*
-        * Merge the per-service "Setup:" and "State:" proxy information with
-        * the "Setup:" information always taking precedence.  Additionally,
-        * ensure that if any group of "Setup:" values (e.g. Enabled, Proxy,
-        * Port) is defined than all of the values for that group will be
-        * used.  That is, we don't allow mixing some of the values from
-        * the "Setup:" keys and others from the "State:" keys.
-        */
-       new_dict   = CFDictionaryCreateMutableCopy(NULL, 0, state_dict);
+}
 
-       for (i = 0; i < sizeof(merge_list)/sizeof(merge_list[0]); i++) {
-           merge_array_prop(new_dict,
-                            merge_list[i].key,
-                            state_dict,
-                            setup_dict,
-                            merge_list[i].flags,
-                            merge_list[i].append);
-       }
+/*
+ * Function: IPv6RouteListCreateWithDictionary
+ *
+ * Purpose:
+ *   Given the service IPv6 entity dictionary, generate the list of routes.
+ *
+ * Returns:
+ *   If the passed in route_list is NULL or too small, this routine
+ *   allocates malloc'd memory to hold the routes.
+ */
+static IPv6RouteListRef
+IPv6RouteListCreateWithDictionary(IPv6RouteListRef routes,
+                                 CFDictionaryRef dict,
+                                 CFNumberRef rank_assertion)
+{
+    boolean_t          add_default = FALSE;
+    boolean_t          add_prefix = FALSE;
+    struct in6_addr    addr;
+    CFArrayRef         additional_routes = NULL;
+    CFIndex            additional_routes_count;
+    boolean_t          allow_additional_routes = FALSE;
+    boolean_t          exclude_from_nwi = FALSE;
+    CFArrayRef         excluded_routes = NULL;
+    CFIndex            excluded_routes_count;
+    RouteFlags         flags = 0;
+    IFIndex            ifindex;
+    char               ifname[IFNAMSIZ];
+    CFStringRef                ifname_cf;
+    int                        n = 0;
+    int                        prefix_length = 0;
+    Rank               primary_rank = kRankAssertionDefault;
+    IPv6RouteRef       r;
+    Rank               rank = kRankAssertionDefault;
+    struct in6_addr    router = in6addr_any;
 
-       setup_copy = CFDictionaryCreateMutableCopy(NULL, 0, setup_dict);
-       for (i = 0; i < sizeof(pick_list)/sizeof(pick_list[0]); i++) {
-           if (CFDictionaryContainsKey(setup_copy, pick_list[i].key1)) {
-               /*
-                * if a "Setup:" enabled key has been provided than we want to
-                * ignore all of the "State:" keys
-                */
-               CFDictionaryRemoveValue(new_dict, pick_list[i].key1);
-               if (pick_list[i].key2 != NULL) {
-                   CFDictionaryRemoveValue(new_dict, pick_list[i].key2);
-               }
-               if (pick_list[i].key3 != NULL) {
-                   CFDictionaryRemoveValue(new_dict, pick_list[i].key3);
-               }
-           } else if (CFDictionaryContainsKey(state_dict, pick_list[i].key1) ||
-                      ((pick_list[i].key2 != NULL) && CFDictionaryContainsKey(state_dict, pick_list[i].key2)) ||
-                      ((pick_list[i].key3 != NULL) && CFDictionaryContainsKey(state_dict, pick_list[i].key3))) {
-               /*
-                * if a "Setup:" enabled key has not been provided and we have
-                * some" "State:" keys than we remove all of of "Setup:" keys
-                */
-               CFDictionaryRemoveValue(setup_copy, pick_list[i].key1);
-               if (pick_list[i].key2 != NULL) {
-                   CFDictionaryRemoveValue(setup_copy, pick_list[i].key2);
-               }
-               if (pick_list[i].key3 != NULL) {
-                   CFDictionaryRemoveValue(setup_copy, pick_list[i].key3);
-               }
-           }
+    if (dict == NULL) {
+       return (NULL);
+    }
+    ifname_cf = plist_get_string(dict, kSCPropInterfaceName,
+                                ifname, sizeof(ifname));
+    if (ifname_cf == NULL) {
+       return (NULL);
+    }
+    ifindex = my_if_nametoindex(ifname);
+    if (ifindex == 0) {
+       /* interface doesn't exist */
+       return (NULL);
+    }
+    allow_additional_routes = confirm_interface_name(dict, ifname_cf);
+    if (dict_get_ipv6(dict, kSCPropNetIPv6Router, &router) == FALSE) {
+       (void)dict_get_first_ipv6(dict, kSCPropNetIPv6DestAddresses, &router);
+    }
+    if (dict_get_first_ipv6(dict, kSCPropNetIPv6Addresses, &addr)) {
+       if (IN6_IS_ADDR_UNSPECIFIED(&addr)) {
+           return (NULL);
+       }
+       if (dict_get_first_int(dict, kSCPropNetIPv6PrefixLength,
+                              &prefix_length)
+           && !IN6_IS_ADDR_LINKLOCAL(&addr)
+           && ipv6_prefix_length_is_valid(prefix_length)) {
+           add_prefix = TRUE;
+           n++;
+       }
+       else {
+           prefix_length = 0;
        }
-
-       /* merge the "Setup:" keys */
-       CFDictionaryApplyFunction(setup_copy, merge_dict, new_dict);
-       CFRelease(setup_copy);
     }
-    else if (setup_dict != NULL) {
-       new_dict = CFDictionaryCreateMutableCopy(NULL, 0, setup_dict);
+    else {
+       /* no addresses */
+       return (NULL);
     }
-    else if (state_dict != NULL) {
-       new_dict = CFDictionaryCreateMutableCopy(NULL, 0, state_dict);
+    if (rank_assertion != NULL) {
+       (void)CFNumberGetValue(rank_assertion, kCFNumberSInt32Type,
+                              &primary_rank);
+    }
+    if (!IN6_IS_ADDR_UNSPECIFIED(&router)) {
+       if (ifindex != lo0_ifindex()) {
+           add_default = TRUE;
+           n++;
+       }
+       /*
+        * If the router address is our address and the prefix length is
+        * not 128, assume all routes are local to the interface.
+        */
+       if (IN6_ARE_ADDR_EQUAL(&router, &addr)
+           && prefix_length != IPV6_ROUTE_ALL_BITS_SET) {
+           ; /* all routes local */
+       }
+       else {
+           flags |= kRouteFlagsHasGateway;
+       }
+       if (rank_assertion == NULL && get_override_primary(dict)) {
+           primary_rank = kRankAssertionFirst;
+       }
+    }
+    if (S_dict_get_boolean(dict, kIsNULL, FALSE)) {
+       exclude_from_nwi = TRUE;
+       flags |= kRouteFlagsIsNULL;
     }
 
-    if ((new_dict != NULL) && (CFDictionaryGetCount(new_dict) == 0)) {
-       CFRelease(new_dict);
-       new_dict = NULL;
+    switch (primary_rank) {
+    case kRankAssertionScoped:
+       /* Scoped means all routes for the service get scoped */
+       primary_rank = rank = kRankAssertionNever;
+       flags |= kRouteFlagsIsScoped;
+       break;
+    case kRankAssertionNever:
+       /* Never means just the default route gets scoped */
+       rank = kRankAssertionLast;
+       flags |= kRouteFlagsIsScoped;
+       break;
+    default:
+       rank = primary_rank;
+       break;
     }
 
-    if ((new_dict != NULL) && (interface != NULL)) {
-       CFDictionarySetValue(new_dict, kSCPropInterfaceName, interface);
+    if (allow_additional_routes) {
+       additional_routes
+           = CFDictionaryGetValue(dict, kSCPropNetIPv6AdditionalRoutes);
+       additional_routes = isA_CFArray(additional_routes);
+       if (additional_routes != NULL) {
+           additional_routes_count = CFArrayGetCount(additional_routes);
+           n += additional_routes_count;
+       }
+       excluded_routes = CFDictionaryGetValue(dict,
+                                              kSCPropNetIPv6ExcludedRoutes);
+       excluded_routes = isA_CFArray(excluded_routes);
+       if (excluded_routes != NULL) {
+           excluded_routes_count = CFArrayGetCount(excluded_routes);
+           n += excluded_routes_count;
+       }
+    }
+    if (n == 0) {
+       return (NULL);
     }
 
-    /* process WPAD */
-    if (new_dict != NULL) {
-       CFDictionaryRef dhcp_options;
-       CFNumberRef     num;
-       CFNumberRef     wpad        = NULL;
-       int             wpadEnabled = 0;
-       CFStringRef     wpadURL     = NULL;
+    /* need IPv6LL subnet route */
+    n++;
 
-       if (CFDictionaryGetValueIfPresent(new_dict,
-                                         kSCPropNetProxiesProxyAutoDiscoveryEnable,
-                                         (const void **)&num) &&
-           isA_CFNumber(num)) {
-           /* if we have a WPAD key */
-           wpad = num;
-           if (!CFNumberGetValue(num, kCFNumberIntType, &wpadEnabled)) {
-               /* if we don't like the enabled key/value */
-               wpadEnabled = 0;
-           }
-       }
+    if (routes == NULL || routes->size < n) {
+       routes = (IPv6RouteListRef)malloc(IPv6RouteListComputeSize(n));
+       routes->size = n;
+    }
+    bzero(routes, IPv6RouteListComputeSize(n));
+    routes->count = n;
+    if (exclude_from_nwi) {
+       routes->flags |= kRouteListFlagsExcludeNWI;
+    }
 
-       if (wpadEnabled) {
-           int pacEnabled  = 0;
+    /* start at the beginning */
+    r = routes->list;
+    if (add_default) {
+       /* add the default route */
+       routes->flags |= kRouteListFlagsHasDefault;
+       r->ifindex = ifindex;
+       r->ifa = addr;
+       r->flags = flags;
+       if ((flags & kRouteFlagsHasGateway) != 0) {
+           r->gateway = router;
+       }
+       else {
+           r->gateway = addr;
+       }
+       r->rank = primary_rank;
+       if (S_scopedroute_v6) {
+           r->flags |= kRouteFlagsKernelManaged;
+       }
+       r++;
+    }
 
-           num = CFDictionaryGetValue(new_dict, kSCPropNetProxiesProxyAutoConfigEnable);
-           if (!isA_CFNumber(num) ||
-               !CFNumberGetValue(num, kCFNumberIntType, &pacEnabled)) {
-               /* if we don't like the enabled key/value */
-               pacEnabled = 0;
-           }
 
-           if (pacEnabled) {
-               CFStringRef     pacURL;
+    /* add IPv6LL route */
+    r->ifindex = ifindex;
+    r->dest.s6_addr[0] = 0xfe;
+    r->dest.s6_addr[1] = 0x80;
+    r->prefix_length = 64;
+    r->rank = rank;
+    r->flags |= kRouteFlagsKernelManaged;
+    r++;
 
-               pacURL = CFDictionaryGetValue(new_dict, kSCPropNetProxiesProxyAutoConfigURLString);
-               if (pacURL != NULL) {
-                   if (!isA_CFString(pacURL)) {
-                       /* if we don't like the PAC URL */
-                       pacEnabled = 0;
-                   }
-               } else {
-                   CFStringRef pacJS;
 
-                   pacJS = CFDictionaryGetValue(new_dict, kSCPropNetProxiesProxyAutoConfigJavaScript);
-                   if (!isA_CFString(pacJS)) {
-                       /* if we don't have (or like) the PAC JavaScript */
-                       pacEnabled = 0;
-                   }
-               }
-           }
+    /* add the prefix route(s) */
+    if (add_prefix) {
+       r->flags |= kRouteFlagsKernelManaged;
+       if ((flags & kRouteFlagsIsNULL) != 0) {
+           r->flags |= kRouteFlagsIsNULL;
+       }
+       r->ifindex = ifindex;
+       r->gateway = addr;
+       r->dest = addr;
+       in6_netaddr(&r->dest, prefix_length);
+       r->prefix_length = prefix_length;
+       r->ifa = addr;
+       r->rank = rank;
+       r++;
+    }
 
-           if (pacEnabled) {
-               /*
-                * we already have a PAC URL so disable WPAD.
-                */
-               wpadEnabled = 0;
-               goto setWPAD;
-           }
+    if (additional_routes != NULL || excluded_routes != NULL) {
+       AddIPv6RouteContext             context;
 
-           /*
-            * if WPAD is enabled and we don't already have a PAC URL then
-            * we check for a DHCP provided URL.  If not available, we use
-            * a PAC URL pointing to a well-known file (wpad.dat) on a
-            * well-known host (wpad.<domain>).
-            */
-           dhcp_options = get_service_state_entity(info, serviceID, kSCEntNetDHCP);
-           wpadURL = wpadURL_dhcp(dhcp_options);
-           if (wpadURL == NULL) {
-               wpadURL = wpadURL_dns();
-           }
-           if (wpadURL == NULL) {
-               wpadEnabled = 0;    /* if we don't have a WPAD URL */
-               goto setWPAD;
-           }
+       bzero(&context, sizeof(context));
+       context.count_p = &routes->count;
+       context.route_p = &r;
+       context.rank = rank;
 
-           pacEnabled = 1;
-           num = CFNumberCreate(NULL, kCFNumberIntType, &pacEnabled);
-           CFDictionarySetValue(new_dict,
-                                kSCPropNetProxiesProxyAutoConfigEnable,
-                                num);
-           CFRelease(num);
-           CFDictionarySetValue(new_dict,
-                                kSCPropNetProxiesProxyAutoConfigURLString,
-                                wpadURL);
-           CFRelease(wpadURL);
+       /* additional routes */
+       if (additional_routes != NULL) {
+           context.ifindex = ifindex;
+           context.addr = &addr;
+           context.descr = "AdditionalRoutes";
+           CFArrayApplyFunction(additional_routes,
+                                CFRangeMake(0, additional_routes_count),
+                                AddIPv6Route, &context);
        }
-
-     setWPAD:
-       if (wpad != NULL) {
-           num = CFNumberCreate(NULL, kCFNumberIntType, &wpadEnabled);
-           CFDictionarySetValue(new_dict,
-                                kSCPropNetProxiesProxyAutoDiscoveryEnable,
-                                num);
-           CFRelease(num);
+       /* excluded routes */
+       if (excluded_routes != NULL) {
+           context.descr = "ExcludedRoutes";
+           /* exclude this interface */
+           context.ifindex = 0;
+           context.exclude_ifindex = ifindex;
+           context.addr = NULL;
+           CFArrayApplyFunction(excluded_routes,
+                                CFRangeMake(0, excluded_routes_count),
+                                AddIPv6Route, &context);
        }
     }
-
- done:
-    changed = service_dict_set(serviceID, kSCEntNetProxies, new_dict);
-    my_CFRelease(&new_dict);
-    return (changed);
+    return (routes);
 }
 
-#if    !TARGET_OS_IPHONE
-static boolean_t
-get_smb_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
-               CFDictionaryRef setup_dict, CFDictionaryRef info)
+static const void *
+IPv6RouteGateway(RouteRef r_route)
 {
-    boolean_t                  changed = FALSE;
-    int                                i;
-    CFMutableDictionaryRef      new_dict = NULL;
-    const CFStringRef          pick_list[] = {
-       kSCPropNetSMBNetBIOSName,
-       kSCPropNetSMBNetBIOSNodeType,
-#ifdef ADD_NETBIOS_SCOPE
-       kSCPropNetSMBNetBIOSScope,
-#endif // ADD_NETBIOS_SCOPE
-       kSCPropNetSMBWorkgroup,
-    };
+    IPv6RouteRef       route = (IPv6RouteRef)r_route;
+    return (&route->gateway);
+}
 
-    if (service_dict_get(serviceID, kSCEntNetIPv4) == NULL) {
-       /* there is no IPv4 */
-       goto done;
-    }
+static void
+IPv6RouteSetGateway(RouteRef r_route, const void * address)
+{
+    IPv6RouteRef       route = (IPv6RouteRef)r_route;
 
-    if (state_dict == NULL && setup_dict == NULL) {
-       /* there is no SMB */
-       goto done;
-    }
+    route->gateway = *((struct in6_addr *)address);
+    return;
+}
 
-    /* merge SMB configuration */
-    new_dict = CFDictionaryCreateMutable(NULL, 0,
-                                        &kCFTypeDictionaryKeyCallBacks,
-                                        &kCFTypeDictionaryValueCallBacks);
+static const void *
+IPv6RouteDestination(RouteRef r_route)
+{
+    IPv6RouteRef       route = (IPv6RouteRef)r_route;
+    return (&route->dest);
+}
 
-    merge_array_prop(new_dict,
-                    kSCPropNetSMBWINSAddresses,
-                    state_dict,
-                    setup_dict,
-                    0,
-                    FALSE);
-    for (i = 0; i < sizeof(pick_list)/sizeof(pick_list[0]); i++) {
-       pick_prop(new_dict,
-                 pick_list[i],
-                 state_dict,
-                 setup_dict,
-                 0);
-    }
-
-    if (CFDictionaryGetCount(new_dict) == 0) {
-       my_CFRelease(&new_dict);
-       goto done;
-    }
-
- done:
-    changed = service_dict_set(serviceID, kSCEntNetSMB, new_dict);
-    my_CFRelease(&new_dict);
-    return (changed);
+static __inline__ int
+in6_addr_cmp(const struct in6_addr * a, const struct in6_addr * b)
+{
+    return (memcmp(a->s6_addr, b->s6_addr, sizeof(struct in6_addr)));
 }
-#endif /* !TARGET_OS_IPHONE */
 
-static CFStringRef
-services_info_get_interface(CFDictionaryRef services_info,
-                           CFStringRef serviceID)
+static boolean_t
+IPv6RouteIsEqual(RouteRef r_route1, RouteRef r_route2)
 {
-    CFStringRef                interface = NULL;
-    CFDictionaryRef    ipv4_dict;
-
-    ipv4_dict = get_service_state_entity(services_info, serviceID,
-                                        kSCEntNetIPv4);
-    if (isA_CFDictionary(ipv4_dict) != NULL) {
-       interface = CFDictionaryGetValue(ipv4_dict, kSCPropInterfaceName);
-    }
-    else {
-       CFDictionaryRef         ipv6_dict;
+    IPv6RouteRef       route1 = (IPv6RouteRef)r_route1;
+    IPv6RouteRef       route2 = (IPv6RouteRef)r_route2;
 
-       ipv6_dict = get_service_state_entity(services_info, serviceID,
-                                            kSCEntNetIPv6);
-       if (isA_CFDictionary(ipv6_dict) != NULL) {
-           interface = CFDictionaryGetValue(ipv6_dict, kSCPropInterfaceName);
-       }
-    }
-    return (interface);
+    return (route1->prefix_length == route2->prefix_length
+           && route1->ifindex == route2->ifindex
+           && route1->flags == route2->flags
+           && in6_addr_cmp(&route1->dest, &route2->dest) == 0
+           && in6_addr_cmp(&route1->ifa, &route2->ifa) == 0
+           && in6_addr_cmp(&route1->gateway, &route2->gateway) == 0);
 }
 
-
-
-static const CFStringRef *statusEntityNames[] = {
-    &kSCEntNetIPSec,
-    &kSCEntNetPPP,
-    &kSCEntNetVPN,
-};
-
-static Boolean
-get_transient_service_changes(CFStringRef serviceID, CFDictionaryRef services_info)
+static boolean_t
+IPv6RouteSameSubnet(RouteRef r_route, const void * addr)
 {
-    boolean_t  changed = FALSE;
-    int                i;
+    const struct in6_addr *    address = (const struct in6_addr *)addr;
+    struct in6_addr            netaddr;
+    IPv6RouteRef               route = (IPv6RouteRef)r_route;
 
-    static const struct {
-       const CFStringRef       *entityName;
-       const CFStringRef       *statusKey;
-    } transientServiceInfo[] = {
-       { &kSCEntNetIPSec,      &kSCPropNetIPSecStatus  },
-       { &kSCEntNetPPP,        &kSCPropNetPPPStatus    },
-       { &kSCEntNetVPN,        &kSCPropNetVPNStatus    },
-    };
-
-    for (i = 0; i < sizeof(transientServiceInfo)/sizeof(transientServiceInfo[0]); i++) {
-       CFDictionaryRef         dict;
-       CFNumberRef             status          = NULL;
-       CFMutableDictionaryRef  ts_dict         = NULL;
+    netaddr = *address;
+    in6_netaddr(&netaddr, route->prefix_length);
+    return (in6_addr_cmp(&netaddr, &route->dest) == 0);
+}
 
-       dict = get_service_state_entity(services_info, serviceID, *transientServiceInfo[i].entityName);
 
-       if (isA_CFDictionary(dict) != NULL) {
-           status = CFDictionaryGetValue(dict, *transientServiceInfo[i].statusKey);
-       }
+#define V6_ROUTE_MSG_ADDRS_SPACE (5 * sizeof(struct sockaddr_dl) + 128)
 
-       if (isA_CFNumber(status) != NULL) {
-           ts_dict = CFDictionaryCreateMutable(NULL,
-                                                0,
-                                                &kCFTypeDictionaryKeyCallBacks,
-                                                &kCFTypeDictionaryValueCallBacks);
-           CFDictionaryAddValue(ts_dict,
-                                *transientServiceInfo[i].statusKey,
-                                status);
-       }
+typedef struct {
+    struct rt_msghdr   hdr;
+    char               addrs[V6_ROUTE_MSG_ADDRS_SPACE];
+} v6_route_msg;
 
-       if (service_dict_set(serviceID, *transientServiceInfo[i].entityName, ts_dict)) {
-           changed = TRUE;
-       }
+/*
+ * Function: IPv6RouteApply
+ * Purpose:
+ *   Add or remove the specified route to/from the kernel routing table.
+ */
+static int
+IPv6RouteApply(RouteRef r_route, int cmd, int sockfd)
+{
+    int                                len;
+    int                                ret = 0;
+    IPv6RouteRef               route = (IPv6RouteRef)r_route;
+    v6_route_msg               rtmsg;
+    union {
+       struct sockaddr_in6 *   in_p;
+       struct sockaddr_dl *    dl_p;
+       void *                  ptr;
+    } rtaddr;
 
-       if (ts_dict != NULL) {
-           CFRelease(ts_dict);
+    if ((route->flags & kRouteFlagsIsScoped) != 0
+       && !S_scopedroute_v6) {
+       return (EROUTENOTAPPLIED);
+    }
+    if ((route->flags & kRouteFlagsKernelManaged) != 0) {
+       /* the kernel manages this route, don't touch it */
+       return (EROUTENOTAPPLIED);
+    }
+    if ((route->flags & kRouteFlagsIsNULL) != 0) {
+       return (EROUTENOTAPPLIED);
+    }
+    if (route->ifindex == 0) {
+       IPv6RouteLog(LOG_NOTICE, (RouteRef)route,
+                    "IPMonitor IPv6RouteApply: no interface specified");
+       return (ENXIO);
+    }
+    if (sockfd == -1) {
+#ifdef TEST_IPV6_ROUTELIST
+       return (0);
+#else /* TEST_IPV6_ROUTELIST */
+       return (EBADF);
+#endif /* TEST_IPV6_ROUTELIST */
+    }
+    memset(&rtmsg, 0, sizeof(rtmsg));
+    rtmsg.hdr.rtm_type = cmd;
+    rtmsg.hdr.rtm_version = RTM_VERSION;
+    rtmsg.hdr.rtm_seq = ++rtm_seq;
+    rtmsg.hdr.rtm_addrs        = RTA_DST | RTA_GATEWAY | RTA_IFP;
+    if (!IN6_IS_ADDR_UNSPECIFIED(&route->ifa)) {
+       rtmsg.hdr.rtm_addrs |= RTA_IFA;
+    }
+    rtmsg.hdr.rtm_flags = RTF_UP | RTF_STATIC;
+    if ((route->flags & kRouteFlagsIsHost) != 0) {
+       rtmsg.hdr.rtm_flags |= RTF_HOST;
+    }
+    else {
+       rtmsg.hdr.rtm_addrs |= RTA_NETMASK;
+       if ((route->flags & kRouteFlagsHasGateway) == 0) {
+           rtmsg.hdr.rtm_flags |= RTF_CLONING;
        }
     }
-    return (changed);
-}
-
-static boolean_t
-get_rank_changes(CFStringRef serviceID, CFDictionaryRef state_options,
-                CFDictionaryRef setup_options, CFDictionaryRef services_info)
-{
-    boolean_t                  changed         = FALSE;
-    CFBooleanRef               ip_is_coupled   = NULL;
-    CFMutableDictionaryRef      new_dict       = NULL;
-    CFStringRef                        new_rank        = NULL;
-    CFStringRef                        setup_rank      = NULL;
-    CFStringRef                        state_rank      = NULL;
+    if ((route->flags & kRouteFlagsHasGateway) != 0) {
+       rtmsg.hdr.rtm_flags |= RTF_GATEWAY;
+    }
+    if ((route->flags & kRouteFlagsIsScoped) != 0) {
+       rtmsg.hdr.rtm_index = route->ifindex;
+       rtmsg.hdr.rtm_flags |= RTF_IFSCOPE;
+    }
 
+    rtaddr.ptr = rtmsg.addrs;
 
-    /*
-     * Check "PrimaryRank" setting
-     *
-     * Note 1: Rank setting in setup/state option overwrites the
-     *         Rank setting in interface
-     * Within each rank setting, the following precedence is defined:
-     *
-     * Note 2: Rank Never > Rank Last > Rank First > Rank None
-     */
-    if (isA_CFDictionary(state_options)) {
-       CFBooleanRef    coupled;
+    /* dest */
+    rtaddr.in_p->sin6_len = sizeof(*rtaddr.in_p);
+    rtaddr.in_p->sin6_family = AF_INET6;
+    rtaddr.in_p->sin6_addr = route->dest;
+    in6_addr_scope_linklocal(&rtaddr.in_p->sin6_addr, route->ifindex);
+    rtaddr.ptr += sizeof(*rtaddr.in_p);
 
-       state_rank
-           = CFDictionaryGetValue(state_options, kSCPropNetServicePrimaryRank);
-       state_rank = isA_CFString(state_rank);
-       coupled = CFDictionaryGetValue(state_options, kIPIsCoupled);
-       if (isA_CFBoolean(coupled) != NULL) {
-           ip_is_coupled = coupled;
-       }
+    /* gateway */
+    if ((rtmsg.hdr.rtm_flags & RTF_GATEWAY) != 0) {
+       /* gateway is an IP address */
+       rtaddr.in_p->sin6_len = sizeof(*rtaddr.in_p);
+       rtaddr.in_p->sin6_family = AF_INET6;
+       rtaddr.in_p->sin6_addr = route->gateway;
+       in6_addr_scope_linklocal(&rtaddr.in_p->sin6_addr, route->ifindex);
+       rtaddr.ptr += sizeof(*rtaddr.in_p);
     }
-    if (isA_CFDictionary(setup_options)) {
-       CFBooleanRef    coupled;
-
-       setup_rank
-           = CFDictionaryGetValue(setup_options, kSCPropNetServicePrimaryRank);
-       setup_rank = isA_CFString(setup_rank);
-
-       coupled = CFDictionaryGetValue(setup_options, kIPIsCoupled);
-       if (isA_CFBoolean(coupled) != NULL) {
-           ip_is_coupled = coupled;
-       }
+    else {
+       /* gateway is the interface itself */
+       rtaddr.dl_p->sdl_len = sizeof(*rtaddr.dl_p);
+       rtaddr.dl_p->sdl_family = AF_LINK;
+       rtaddr.dl_p->sdl_index = route->ifindex;
+       rtaddr.ptr += sizeof(*rtaddr.dl_p);
     }
 
-    if (((setup_rank != NULL) && CFEqual(setup_rank, kSCValNetServicePrimaryRankNever)) ||
-       ((state_rank != NULL) && CFEqual(state_rank, kSCValNetServicePrimaryRankNever))) {
-       new_rank = kSCValNetServicePrimaryRankNever;
+    /* mask */
+    if ((rtmsg.hdr.rtm_addrs & RTA_NETMASK) != 0) {
+       rtaddr.in_p->sin6_len = sizeof(*rtaddr.in_p);
+       rtaddr.in_p->sin6_family = AF_INET6;
+       in6_len2mask(&rtaddr.in_p->sin6_addr, route->prefix_length);
+       rtaddr.ptr += sizeof(*rtaddr.in_p);
     }
-    else if (((setup_rank != NULL) && CFEqual(setup_rank, kSCValNetServicePrimaryRankLast)) ||
-            ((state_rank != NULL) && CFEqual(state_rank, kSCValNetServicePrimaryRankLast))) {
-       new_rank = kSCValNetServicePrimaryRankLast;
+
+    /* interface */
+    if ((rtmsg.hdr.rtm_addrs & RTA_IFP) != 0) {
+       rtaddr.dl_p->sdl_len = sizeof(*rtaddr.dl_p);
+       rtaddr.dl_p->sdl_family = AF_LINK;
+       rtaddr.dl_p->sdl_index = route->ifindex;
+       rtaddr.ptr += sizeof(*rtaddr.dl_p);
     }
-    else if (((setup_rank != NULL) && CFEqual(setup_rank, kSCValNetServicePrimaryRankFirst)) ||
-            ((state_rank != NULL) && CFEqual(state_rank, kSCValNetServicePrimaryRankFirst))) {
-       new_rank = kSCValNetServicePrimaryRankFirst;
+    /* interface address */
+    if ((rtmsg.hdr.rtm_addrs & RTA_IFA) != 0) {
+       rtaddr.in_p->sin6_len = sizeof(*rtaddr.in_p);
+       rtaddr.in_p->sin6_family = AF_INET6;
+       rtaddr.in_p->sin6_addr = route->ifa;
+       rtaddr.ptr += sizeof(*rtaddr.in_p);
     }
 
-    /* This corresponds to Note 1 */
-    if (setup_rank == NULL && state_rank == NULL) {
-       /* Fetch the interface associated with the service */
-       CFStringRef interface;
+    /* apply the route */
+    len = (int)(sizeof(rtmsg.hdr) + (rtaddr.ptr - (void *)rtmsg.addrs));
+    rtmsg.hdr.rtm_msglen = len;
+    if (write(sockfd, &rtmsg, len) == -1) {
+       ret = errno;
+    }
+    return (ret);
+}
 
-       interface = services_info_get_interface(services_info, serviceID);
+static const RouteListInfo IPv6RouteListInfo = {
+    IPv6RouteListComputeSize,
 
-       /* Get the rank on that interface */
-       if (interface != NULL) {
-           new_rank = CFDictionaryGetValue(S_if_rank_dict, interface);
-           if (S_IPMonitor_debug & kDebugFlag1) {
-               my_log(LOG_DEBUG,
-                      "serviceID %@ interface %@ rank = %@",
-                      serviceID, interface,
-                      (new_rank != NULL) ? new_rank : CFSTR("<none>"));
-           }
-       }
-    }
+    IPv6RouteIsEqual,
+    IPv6RouteApply,
+    IPv6RouteGateway,
+    IPv6RouteSetGateway,
+    IPv6RouteDestination,
+    IPv6RouteSameSubnet,
+    IPv6RouteLog,
+    IPv6RouteCopyDescription,
 
+    sizeof(IPv6Route),
+    sizeof(struct in6_addr),
+    IPV6_ROUTE_ALL_BITS_SET
+};
 
-    if (ip_is_coupled != NULL && CFBooleanGetValue(ip_is_coupled) == FALSE) {
-       /* don't bother setting a value if it's the default */
-       ip_is_coupled = NULL;
-    }
-    if (new_rank != NULL || ip_is_coupled != NULL) {
-       new_dict = CFDictionaryCreateMutable(NULL, 0,
-                                            &kCFTypeDictionaryKeyCallBacks,
-                                            &kCFTypeDictionaryValueCallBacks);
-       if (new_rank != NULL) {
-           CFDictionarySetValue(new_dict, kSCPropNetServicePrimaryRank,
-                                new_rank);
-       }
-       if (ip_is_coupled != NULL) {
-           CFDictionarySetValue(new_dict, kIPIsCoupled, kCFBooleanTrue);
-       }
-    }
-    changed = service_dict_set(serviceID, kSCEntNetService, new_dict);
-    my_CFRelease(&new_dict);
-    return (changed);
+#ifdef TEST_IPV6_ROUTELIST
+static IPv6RouteListRef
+IPv6RouteListAddRouteList(IPv6RouteListRef routes, int init_size,
+                         IPv6RouteListRef service_routes, Rank rank)
+{
+    return ((IPv6RouteListRef)
+           RouteListAddRouteList(&IPv6RouteListInfo,
+                                 (RouteListRef)routes, init_size,
+                                 (RouteListRef)service_routes, rank));
 }
+#endif /* TEST_IPV6_ROUTELIST */
 
-static CFStringRef
-if_rank_key_copy(CFStringRef ifname)
+#if    !TARGET_IPHONE_SIMULATOR
+static __inline__ void
+IPv6RouteListLog(int level, IPv6RouteListRef routes)
 {
-    return (SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
-                                                         kSCDynamicStoreDomainState,
-                                                         ifname,
-                                                         kSCEntNetService));
+    CFStringRef        str = IPv6RouteListCopyDescription(routes);
+
+    my_log(level, "%@", str);
+    CFRelease(str);
+    return;
 }
 
 static void
-if_rank_set(CFStringRef ifname, CFDictionaryRef rank_dict)
+IPv6RouteListFinalize(IPv6RouteListRef routes)
 {
-    CFStringRef                rank = NULL;
-
-    if (isA_CFDictionary(rank_dict) != NULL) {
-       rank = CFDictionaryGetValue(rank_dict, kSCPropNetServicePrimaryRank);
-       rank = isA_CFString(rank);
-    }
-
-    /* specific rank is asserted */
-    if (rank != NULL) {
-       if (S_IPMonitor_debug & kDebugFlag1) {
-           my_log(LOG_DEBUG, "Interface %@ asserted rank %@",
-                  ifname, rank);
-       }
-       CFDictionarySetValue(S_if_rank_dict, ifname, rank);
-    } else {
-       if (S_IPMonitor_debug & kDebugFlag1) {
-           my_log(LOG_DEBUG, "Interface %@ removed rank",
-                  ifname);
-       }
-       CFDictionaryRemoveValue(S_if_rank_dict, ifname);
-    }
+    RouteListFinalize(&IPv6RouteListInfo, (RouteListRef)routes);
     return;
 }
 
 static void
-if_rank_apply(const void * key, const void * value, void * context)
+IPv6RouteListApply(IPv6RouteListRef old_routes, IPv6RouteListRef new_routes,
+                  int sockfd)
 {
-    CFStringRef                ifname;
-    CFDictionaryRef    rank_dict = (CFDictionaryRef)value;
-
-    /* State:/Network/Interface/<ifname>/Service, <ifname> is at index 3 */
-    ifname = my_CFStringCopyComponent(key, CFSTR("/"), 3);
-    if (ifname != NULL) {
-       if_rank_set(ifname, rank_dict);
-       CFRelease(ifname);
-    }
+    RouteListApply(&IPv6RouteListInfo,
+                  (RouteListRef)old_routes, (RouteListRef)new_routes,
+                  sockfd);
     return;
 }
+#endif /* !TARGET_IPHONE_SIMULATOR */
 
-static void
-if_rank_dict_init(void)
+/*
+ * Function: parse_component
+ * Purpose:
+ *   Given a string 'key' and a string prefix 'prefix',
+ *   return the next component in the slash '/' separated
+ *   key.
+ *
+ * Examples:
+ * 1. key = "a/b/c" prefix = "a/"
+ *    returns "b"
+ * 2. key = "a/b/c" prefix = "a/b/"
+ *    returns "c"
+ */
+static CF_RETURNS_RETAINED CFStringRef
+parse_component(CFStringRef key, CFStringRef prefix)
 {
-    CFDictionaryRef    info;
-    CFStringRef                pattern;
-    CFArrayRef         patterns;
+    CFMutableStringRef comp;
+    CFRange            range;
 
-    S_if_rank_dict
-       = CFDictionaryCreateMutable(NULL, 0,
-                                   &kCFTypeDictionaryKeyCallBacks,
-                                   &kCFTypeDictionaryValueCallBacks);
-    pattern = if_rank_key_copy(kSCCompAnyRegex);
-    patterns = CFArrayCreate(NULL,
-                            (const void **)&pattern, 1,
-                            &kCFTypeArrayCallBacks);
-    CFRelease(pattern);
-    info = SCDynamicStoreCopyMultiple(S_session, NULL, patterns);
-    CFRelease(patterns);
-    if (info != NULL) {
-       CFDictionaryApplyFunction(info, if_rank_apply, NULL);
-       CFRelease(info);
+    if (CFStringHasPrefix(key, prefix) == FALSE) {
+       return (NULL);
     }
-    return;
-
+    comp = CFStringCreateMutableCopy(NULL, 0, key);
+    if (comp == NULL) {
+       return (NULL);
+    }
+    CFStringDelete(comp, CFRangeMake(0, CFStringGetLength(prefix)));
+    range = CFStringFind(comp, CFSTR("/"), 0);
+    if (range.location == kCFNotFound) {
+       return (comp);
+    }
+    range.length = CFStringGetLength(comp) - range.location;
+    CFStringDelete(comp, range);
+    return (comp);
 }
 
-static void
-add_service_keys(CFStringRef serviceID, CFMutableArrayRef keys, CFMutableArrayRef patterns)
+__private_extern__ boolean_t
+service_contains_protocol(CFDictionaryRef service, int af)
 {
-    int                        i;
-    CFStringRef                key;
+    boolean_t          contains_protocol = FALSE;
+    CFStringRef                entity;
+    RouteListRef       routes;
+    CFDictionaryRef    dict;
 
-    if (CFEqual(serviceID, kSCCompAnyRegex)) {
-       keys = patterns;
+    entity = (af == AF_INET) ? kSCEntNetIPv4 : kSCEntNetIPv6;
+    dict = CFDictionaryGetValue(service, entity);
+    if (dict == NULL) {
+       goto done;
+    }
+    routes = ipdict_get_routelist(dict);
+    if (routes == NULL) {
+       goto done;
+    }
+    if ((routes->flags & kRouteListFlagsExcludeNWI) != 0) {
+       goto done;
     }
+    contains_protocol = TRUE;
 
-    for (i = 0; i < ENTITY_TYPES_COUNT; i++) {
-       key = setup_service_key(serviceID, *entityTypeNames[i]);
-       CFArrayAppendValue(keys, key);
-       CFRelease(key);
-       key = state_service_key(serviceID, *entityTypeNames[i]);
-       CFArrayAppendValue(keys, key);
-       CFRelease(key);
-    }
+ done:
+    return (contains_protocol);
+}
 
-    key = state_service_key(serviceID, kSCEntNetDHCP);
-    CFArrayAppendValue(patterns, key);
-    CFRelease(key);
 
-    key = setup_service_key(serviceID, NULL);
-    CFArrayAppendValue(patterns, key);
-    CFRelease(key);
-    key = state_service_key(serviceID, NULL);
-    CFArrayAppendValue(patterns, key);
-    CFRelease(key);
+static CFMutableDictionaryRef
+service_dict_copy(CFStringRef serviceID)
+{
+    CFDictionaryRef            d = NULL;
+    CFMutableDictionaryRef     service_dict;
 
-    return;
+    /* create a modifyable dictionary, a copy or a new one */
+    d = CFDictionaryGetValue(S_service_state_dict, serviceID);
+    if (d == NULL) {
+       service_dict
+           = CFDictionaryCreateMutable(NULL, 0,
+                                       &kCFTypeDictionaryKeyCallBacks,
+                                       &kCFTypeDictionaryValueCallBacks);
+    }
+    else {
+       service_dict = CFDictionaryCreateMutableCopy(NULL, 0, d);
+    }
+    return (service_dict);
 }
 
 static void
-add_status_keys(CFStringRef service_id, CFMutableArrayRef patterns)
+log_service_entity(int level, CFStringRef serviceID, CFStringRef entity,
+                  CFStringRef operation, CFTypeRef val)
 {
-    int            i;
+    CFMutableStringRef this_val = NULL;
 
-    for (i = 0; i < sizeof(statusEntityNames)/sizeof(statusEntityNames[0]); i++) {
-       CFStringRef     pattern;
+    if (val != NULL) {
+       boolean_t       is_ipv4;
+       boolean_t       is_ipv6;
 
-       pattern = state_service_key(service_id, *statusEntityNames[i]);
-       CFArrayAppendValue(patterns, pattern);
-       CFRelease(pattern);
-    }
+       if ((is_ipv4 = CFEqual(entity, kSCEntNetIPv4))
+           || (is_ipv6 = CFEqual(entity, kSCEntNetIPv6))) {
+           RouteListUnion      routes;
 
+           routes.ptr = ipdict_get_routelist(val);
+           if (routes.ptr != NULL) {
+               CFDictionaryRef service_dict = NULL;
+
+               if (is_ipv4) {
+                   this_val = IPv4RouteListCopyDescription(routes.v4);
+               }
+               else {
+                   this_val = IPv6RouteListCopyDescription(routes.v6);
+               }
+               service_dict = ipdict_get_service(val);
+               if (service_dict != NULL) {
+                   CFStringAppendFormat(this_val, NULL,
+                                        CFSTR("\n<Service> = %@"),
+                                        service_dict);
+               }
+               val = this_val;
+           }
+       }
+    }
+    if (val == NULL) {
+       val = CFSTR("<none>");
+    }
+    my_log(level, "IPMonitor: serviceID %@ %@ %@ value = %@",
+          serviceID, operation, entity, val);
+    my_CFRelease(&this_val);
     return;
 }
 
-static const CFStringRef *reachabilitySetupKeys[] = {
-    &kSCEntNetPPP,
-    &kSCEntNetInterface,
-    &kSCEntNetIPv4,
-    &kSCEntNetIPv6,
-};
-
-
-static void
-add_reachability_keys(CFMutableArrayRef patterns)
+static boolean_t
+service_dict_set(CFStringRef serviceID, CFStringRef entity,
+                CFTypeRef new_val)
 {
-    int                i;
+    boolean_t                  changed = FALSE;
+    CFTypeRef                  old_val;
+    CFMutableDictionaryRef     service_dict;
 
-    for (i = 0; i < sizeof(reachabilitySetupKeys)/(sizeof(reachabilitySetupKeys[0])); i++)
-    {
-       CFStringRef pattern;
-       pattern = setup_service_key(kSCCompAnyRegex, *reachabilitySetupKeys[i]);
-       CFArrayAppendValue(patterns, pattern);
-       CFRelease(pattern);
+    service_dict = service_dict_copy(serviceID);
+    old_val = CFDictionaryGetValue(service_dict, entity);
+    if (new_val == NULL) {
+       if (old_val != NULL) {
+           if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+               log_service_entity(LOG_DEBUG, serviceID, entity,
+                                  CFSTR("Removed:"), old_val);
+           }
+           CFDictionaryRemoveValue(service_dict, entity);
+           changed = TRUE;
+       }
+    }
+    else {
+       if (old_val == NULL || CFEqual(new_val, old_val) == FALSE) {
+           if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+               log_service_entity(LOG_DEBUG, serviceID, entity,
+                                  CFSTR("Changed: old"), old_val);
+               log_service_entity(LOG_DEBUG, serviceID, entity,
+                                  CFSTR("Changed: new"), new_val);
+           }
+           CFDictionarySetValue(service_dict, entity, new_val);
+           changed = TRUE;
+       }
     }
+    if (CFDictionaryGetCount(service_dict) == 0) {
+       CFDictionaryRemoveValue(S_service_state_dict, serviceID);
+    }
+    else {
+       CFDictionarySetValue(S_service_state_dict, serviceID, service_dict);
+    }
+    my_CFRelease(&service_dict);
+    return (changed);
 }
 
-
-static void
-add_vpn_keys(CFMutableArrayRef patterns)
+static CFDictionaryRef
+service_dict_get(CFStringRef serviceID, CFStringRef entity)
 {
-    CFStringRef        pattern;
+    CFDictionaryRef    service_dict;
 
-    pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetVPN);
-    CFArrayAppendValue(patterns, pattern);
-    CFRelease(pattern);
+    service_dict = CFDictionaryGetValue(S_service_state_dict, serviceID);
+    if (service_dict == NULL) {
+       return (NULL);
+    }
+    return (CFDictionaryGetValue(service_dict, entity));
 }
 
+#ifndef kSCPropNetHostname
+#define kSCPropNetHostname CFSTR("Hostname")
+#endif
 
-static CFDictionaryRef
-services_info_copy(SCDynamicStoreRef session, CFArrayRef service_list,
-                  CFArrayRef if_rank_list)
+__private_extern__
+CFStringRef
+copy_dhcp_hostname(CFStringRef serviceID)
 {
-    int                        count;
-    CFMutableArrayRef  get_keys;
-    CFMutableArrayRef  get_patterns;
-    int                        if_count;
-    CFDictionaryRef    info;
-    int                        s;
-
-    count = CFArrayGetCount(service_list);
-    get_keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
-    get_patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
-
-    CFArrayAppendValue(get_keys, S_setup_global_ipv4);
-    CFArrayAppendValue(get_keys, S_multicast_resolvers);
-    CFArrayAppendValue(get_keys, S_private_resolvers);
-
-    for (s = 0; s < count; s++) {
-       CFStringRef     serviceID = CFArrayGetValueAtIndex(service_list, s);
+    CFDictionaryRef    dict = NULL;
+    CFStringRef                hostname = NULL;
+    CFDictionaryRef    service_dict = NULL;
 
-       add_service_keys(serviceID, get_keys, get_patterns);
-       add_status_keys(serviceID, get_keys);
+    dict = service_dict_get(serviceID, kSCEntNetIPv4);
+    if (dict == NULL) {
+       return (NULL);
     }
-
-    add_reachability_keys(get_patterns);
-
-    add_vpn_keys(get_patterns);
-
-    if_count = (if_rank_list != NULL)
-              ? CFArrayGetCount(if_rank_list) : 0;
-    for (s = 0; s < if_count; s++) {
-       CFStringRef     ifname = CFArrayGetValueAtIndex(if_rank_list, s);
-       CFStringRef     key;
-
-       key = if_rank_key_copy(ifname);
-       CFArrayAppendValue(get_keys, key);
-       CFRelease(key);
+    service_dict = ipdict_get_service(dict);
+    if (service_dict == NULL) {
+       return (NULL);
     }
-
-    info = SCDynamicStoreCopyMultiple(session, get_keys, get_patterns);
-    my_CFRelease(&get_keys);
-    my_CFRelease(&get_patterns);
-    return (info);
+    hostname = CFDictionaryGetValue(service_dict, kSCPropNetHostname);
+    if (hostname != NULL) {
+       CFRetain(hostname);
+    }
+    return (hostname);
 }
 
 #if    !TARGET_IPHONE_SIMULATOR
-static int     rtm_seq = 0;
-#endif /* !TARGET_IPHONE_SIMULATOR */
 
-#if    !TARGET_IPHONE_SIMULATOR
-static int
-route_open_socket(void)
+static struct in6_addr *
+ipv6_service_get_router(CFDictionaryRef service,
+                       IFIndex * ifindex_p, CFStringRef * ifname_p)
 {
-    int sockfd;
+    IPv6RouteListRef   routes;
+    struct in6_addr *  router = NULL;
 
-    if ((sockfd = socket(PF_ROUTE, SOCK_RAW, PF_ROUTE)) == -1) {
-       my_log(LOG_NOTICE,
-              "IPMonitor: route_open_socket: socket failed, %s",
-              strerror(errno));
+    routes = ipdict_get_routelist(service);
+    if (routes != NULL
+       && (routes->flags & kRouteListFlagsExcludeNWI) == 0
+       && (routes->flags & kRouteListFlagsHasDefault) != 0) {
+       router = &routes->list[0].gateway;
+       if (*ifindex_p == 0) {
+           *ifindex_p = routes->list[0].ifindex;
+       }
+       if (*ifname_p == NULL) {
+           *ifname_p = ipdict_get_ifname(service);
+       }
     }
-    return (sockfd);
+    return (router);
 }
-#endif /* !TARGET_IPHONE_SIMULATOR */
-
-/*
- * Define: ROUTE_MSG_ADDRS_SPACE
- * Purpose:
- *   Since sizeof(sockaddr_dl) > sizeof(sockaddr_in), we need space for
- *   3 sockaddr_in's and 2 sockaddr_dl's, but pad it just in case
- *   someone changes the code and doesn't think to modify this.
- */
-#define ROUTE_MSG_ADDRS_SPACE  (3 * sizeof(struct sockaddr_in) \
-                                + 2 * sizeof(struct sockaddr_dl) \
-                                + 128)
-typedef struct {
-    struct rt_msghdr   hdr;
-    char               addrs[ROUTE_MSG_ADDRS_SPACE];
-} route_msg;
 
-#if    !TARGET_IPHONE_SIMULATOR
-static int
-ipv4_route(int sockfd,
-          int cmd, struct in_addr gateway, struct in_addr netaddr,
-          struct in_addr netmask, char * ifname, unsigned int ifindex,
-          struct in_addr ifa, RouteFlags flags)
+static void
+ipv6_service_update_router(CFStringRef serviceID, CFDictionaryRef new_service)
 {
-    boolean_t                  default_route = (netaddr.s_addr == 0);
-    int                                len;
-    int                                ret = 0;
-    route_msg                  rtmsg;
-    union {
-       struct sockaddr_in *    in_p;
-       struct sockaddr_dl *    dl_p;
-       void *                  ptr;
-    } rtaddr;
-
-    if (default_route && S_netboot) {
-       return (0);
-    }
+    IFIndex            ifindex = 0;
+    CFStringRef                ifname = NULL;
+    char               ntopbuf[INET6_ADDRSTRLEN];
+    CFDictionaryRef    old_service;
+    struct in6_addr *  old_router;
+    struct in6_addr *  new_router;
+    int                        s = -1;
 
-    if (ifname == NULL) {
-       /* this should not happen, but rather than crash, return an error */
-       my_log(LOG_NOTICE,
-              "IPMonitor: ipv4_route ifname is NULL on network address %s",
-              inet_ntoa(netaddr));
-       return (EBADF);
-    }
-    if ((flags & kRouteIsNULLFlag) != 0) {
-       my_log(LOG_DEBUG, "IPMonitor: ignoring route %s on %s",
-              inet_ntoa(netaddr), ifname);
-       return (0);
-    }
-    memset(&rtmsg, 0, sizeof(rtmsg));
-    rtmsg.hdr.rtm_type = cmd;
-    rtmsg.hdr.rtm_version = RTM_VERSION;
-    rtmsg.hdr.rtm_seq = ++rtm_seq;
-    rtmsg.hdr.rtm_addrs
-       = RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_IFP | RTA_IFA;
-    if (default_route
-       && (flags & kRouteIsDirectToInterfaceFlag) == 0) {
-       rtmsg.hdr.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
+    old_service = service_dict_get(serviceID, kSCEntNetIPv6);
+    old_router = ipv6_service_get_router(old_service, &ifindex, &ifname);
+    new_router = ipv6_service_get_router(new_service, &ifindex, &ifname);
+    if (ifname == NULL || ifindex == 0) {
+       return;
     }
-    else {
-       rtmsg.hdr.rtm_flags = RTF_UP | RTF_CLONING | RTF_STATIC;
+    s = inet6_dgram_socket();
+    if (s < 0) {
+       my_log(LOG_ERR,
+              "IPMonitor: ipv6_service_update_router: socket failed, %s",
+              strerror(errno));
+       goto done;
     }
-    if ((flags & kRouteIsScopedFlag) != 0) {
-#ifdef RTF_IFSCOPE
-       if (!S_scopedroute) {
-           return (0);
+    /* remove the old router if it was defined */
+    if (old_router != NULL
+       && (new_router == NULL
+           || !IN6_ARE_ADDR_EQUAL(old_router, new_router))) {
+       if (siocdrdel_in6(s, ifindex, old_router) < 0) {
+           if (errno != EINVAL
+               || (S_IPMonitor_debug & kDebugFlag1) != 0) {
+               my_log((errno == EINVAL) ? LOG_DEBUG : LOG_ERR,
+                      "IPMonitor: siocdrdel_in6(%@, %s) failed, %s",
+                      ifname,
+                      inet_ntop(AF_INET6, old_router,
+                                ntopbuf, sizeof(ntopbuf)),
+                      strerror(errno));
+           }
        }
-       if (ifindex == 0) {
-           /* specifically asked for a scoped route, yet no index supplied */
-           my_log(LOG_NOTICE,
-                  "IPMonitor: ipv4_route index is 0 on %s-scoped route %s",
-                  ifname, inet_ntoa(netaddr));
-           return (EBADF);
+       else if (S_IPMonitor_debug & kDebugFlag1) {
+           my_log(LOG_DEBUG,
+                  "IPMonitor: %@ removed default route %s",
+                  ifname,
+                  inet_ntop(AF_INET6, old_router, ntopbuf, sizeof(ntopbuf)));
+       }
+    }
+    /* add the new router if it is defined */
+    if (new_router != NULL
+       && (old_router == NULL
+           || !IN6_ARE_ADDR_EQUAL(old_router, new_router))) {
+       if (siocdradd_in6(s, ifindex, new_router, 0) < 0) {
+           if (errno != EINVAL
+               || (S_IPMonitor_debug & kDebugFlag1) != 0) {
+               my_log((errno == EINVAL) ? LOG_DEBUG : LOG_ERR,
+                      "IPMonitor: siocdradd_in6(%@, %s) failed, %s",
+                      ifname,
+                      inet_ntop(AF_INET6, new_router,
+                                ntopbuf, sizeof(ntopbuf)),
+                      strerror(errno));
+           }
+       }
+       else if (S_IPMonitor_debug & kDebugFlag1) {
+           my_log(LOG_DEBUG,
+                  "IPMonitor: %@ added default route %s",
+                  ifname,
+                  inet_ntop(AF_INET6, new_router, ntopbuf, sizeof(ntopbuf)));
        }
-       rtmsg.hdr.rtm_index = ifindex;
-       rtmsg.hdr.rtm_flags |= RTF_IFSCOPE;
-#else /* RTF_IFSCOPE */
-       return (0);
-#endif /* RTF_IFSCOPE */
     }
+    close(s);
 
-    rtaddr.ptr = rtmsg.addrs;
+  done:
+    return;
+}
+#endif /* !TARGET_IPHONE_SIMULATOR */
 
-    /* dest */
-    rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
-    rtaddr.in_p->sin_family = AF_INET;
-    rtaddr.in_p->sin_addr = netaddr;
-    rtaddr.ptr += sizeof(*rtaddr.in_p);
+#define        ALLOW_EMPTY_STRING      0x1
 
-    /* gateway */
-    if ((rtmsg.hdr.rtm_flags & RTF_GATEWAY) != 0) {
-       /* gateway is an IP address */
-       rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
-       rtaddr.in_p->sin_family = AF_INET;
-       rtaddr.in_p->sin_addr = gateway;
-       rtaddr.ptr += sizeof(*rtaddr.in_p);
+static CF_RETURNS_RETAINED CFTypeRef
+sanitize_prop(CFTypeRef val, uint32_t flags)
+{
+    if (val != NULL) {
+       if (isA_CFString(val)) {
+           CFMutableStringRef  str;
+
+           str = CFStringCreateMutableCopy(NULL, 0, (CFStringRef)val);
+           CFStringTrimWhitespace(str);
+           if (!(flags & ALLOW_EMPTY_STRING) && (CFStringGetLength(str) == 0)) {
+               CFRelease(str);
+               str = NULL;
+           }
+           val = str;
+       } else {
+           CFRetain(val);
+       }
     }
-    else {
-       /* gateway is the interface itself */
-       rtaddr.dl_p->sdl_len = sizeof(*rtaddr.dl_p);
-       rtaddr.dl_p->sdl_family = AF_LINK;
-       rtaddr.dl_p->sdl_nlen = strlen(ifname);
-       bcopy(ifname, rtaddr.dl_p->sdl_data, rtaddr.dl_p->sdl_nlen);
-       rtaddr.ptr += sizeof(*rtaddr.dl_p);
+
+    return val;
+}
+
+static void
+merge_array_prop(CFMutableDictionaryRef        dict,
+                CFStringRef            key,
+                CFDictionaryRef        state_dict,
+                CFDictionaryRef        setup_dict,
+                uint32_t               flags,
+                Boolean                append)
+{
+    CFMutableArrayRef  merge_prop;
+    CFArrayRef         setup_prop = NULL;
+    CFArrayRef         state_prop = NULL;
+
+    if (setup_dict != NULL) {
+       setup_prop = isA_CFArray(CFDictionaryGetValue(setup_dict, key));
+    }
+    if (state_dict != NULL) {
+       state_prop = isA_CFArray(CFDictionaryGetValue(state_dict, key));
     }
 
-    /* mask */
-    rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
-    rtaddr.in_p->sin_family = AF_INET;
-    rtaddr.in_p->sin_addr = netmask;
-    rtaddr.ptr += sizeof(*rtaddr.in_p);
+    if ((setup_prop == NULL) && (state_prop == NULL)) {
+       return;
+    }
 
-    /* interface name */
-    rtaddr.dl_p->sdl_len = sizeof(*rtaddr.dl_p);
-    rtaddr.dl_p->sdl_family = AF_LINK;
-    rtaddr.dl_p->sdl_nlen = strlen(ifname);
-    bcopy(ifname, rtaddr.dl_p->sdl_data, rtaddr.dl_p->sdl_nlen);
-    rtaddr.ptr += sizeof(*rtaddr.dl_p);
+    merge_prop = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+    if (setup_prop != NULL) {
+       CFIndex i;
+       CFIndex n;
 
-    /* interface address */
-    rtaddr.in_p->sin_len = sizeof(*rtaddr.in_p);
-    rtaddr.in_p->sin_family = AF_INET;
-    rtaddr.in_p->sin_addr = ifa;
-    rtaddr.ptr += sizeof(*rtaddr.in_p);
+       n = CFArrayGetCount(setup_prop);
+       for (i = 0; i < n; i++) {
+           CFTypeRef   val;
 
-    len = sizeof(rtmsg.hdr) + (rtaddr.ptr - (void *)rtmsg.addrs);
-    rtmsg.hdr.rtm_msglen = len;
-    if (write(sockfd, &rtmsg, len) == -1) {
-       ret = errno;
+           val = CFArrayGetValueAtIndex(setup_prop, i);
+           val = sanitize_prop(val, flags);
+           if (val != NULL) {
+               CFArrayAppendValue(merge_prop, val);
+               CFRelease(val);
+           }
+       }
     }
-    return (ret);
+    if (state_prop != NULL
+       && (setup_prop == NULL || S_append_state)) {
+       CFIndex i;
+       CFIndex n;
+       CFRange setup_range     = CFRangeMake(0, CFArrayGetCount(merge_prop));
+
+       n = CFArrayGetCount(state_prop);
+       for (i = 0; i < n; i++) {
+           CFTypeRef   val;
+
+           val = CFArrayGetValueAtIndex(state_prop, i);
+           val = sanitize_prop(val, flags);
+           if (val != NULL) {
+               if (append || !CFArrayContainsValue(merge_prop, setup_range, val)) {
+                   CFArrayAppendValue(merge_prop, val);
+               }
+               CFRelease(val);
+           }
+       }
+    }
+    if (CFArrayGetCount(merge_prop) > 0) {
+       CFDictionarySetValue(dict, key, merge_prop);
+    }
+    CFRelease(merge_prop);
+    return;
 }
-#endif /* !TARGET_IPHONE_SIMULATOR */
 
-#if    !TARGET_IPHONE_SIMULATOR
-static boolean_t
-ipv6_route(int cmd, struct in6_addr gateway, struct in6_addr netaddr,
-          struct in6_addr netmask, char * ifname, boolean_t is_direct)
+static void
+pick_prop(CFMutableDictionaryRef       dict,
+         CFStringRef                   key,
+         CFDictionaryRef               state_dict,
+         CFDictionaryRef               setup_dict,
+         uint32_t                      flags)
 {
-    boolean_t                  default_route;
-    int                                len;
-    boolean_t                  ret = TRUE;
-    struct {
-       struct rt_msghdr        hdr;
-       struct sockaddr_in6     dst;
-       struct sockaddr_in6     gway;
-       struct sockaddr_in6     mask;
-       struct sockaddr_dl      ifp;
-    }                          rtmsg;
-    int                                sockfd = -1;
-    struct in6_addr            zeroes = IN6ADDR_ANY_INIT;
-
-    default_route = (bcmp(&zeroes, &netaddr, sizeof(netaddr)) == 0);
-
-    if ((IN6_IS_ADDR_LINKLOCAL(&gateway) ||
-        IN6_IS_ADDR_MC_LINKLOCAL(&gateway)) &&
-       (ifname != NULL)) {
-       unsigned int    index = if_nametoindex(ifname);
-
-       /* add the scope id to the link local address */
-       gateway.__u6_addr.__u6_addr16[1] = (uint16_t)htons(index);
-    }
-    sockfd = route_open_socket();
-    if (sockfd == -1) {
-       return (FALSE);
-    }
-    memset(&rtmsg, 0, sizeof(rtmsg));
-    rtmsg.hdr.rtm_type = cmd;
-    if (default_route) {
-       if (is_direct) {
-           /* if router is directly reachable, don't set the gateway flag */
-           rtmsg.hdr.rtm_flags = RTF_UP | RTF_STATIC;
+       CFTypeRef       val     = NULL;
+
+       if (setup_dict != NULL) {
+           val = CFDictionaryGetValue(setup_dict, key);
+           val = sanitize_prop(val, flags);
        }
-       else {
-           rtmsg.hdr.rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
+       if (val == NULL && state_dict != NULL) {
+           val = CFDictionaryGetValue(state_dict, key);
+           val = sanitize_prop(val, flags);
+       }
+       if (val != NULL) {
+           CFDictionarySetValue(dict, key, val);
+           CFRelease(val);
+       }
+
+       return;
+}
+
+/**
+ ** GetEntityChangesFunc functions
+ **/
+#define IPV4_ROUTES_N_STATIC           5
+#define IPV4_ROUTES_ALIGN_BUF_SIZE_UINT32                              \
+    (roundup(IPv4RouteListComputeSize(IPV4_ROUTES_N_STATIC),           \
+            sizeof(uint32_t))                                          \
+     / sizeof(uint32_t))
+
+#define IPV4_ROUTES_BUF_DECL(routes)           \
+    IPv4RouteListRef   routes;                                         \
+    uint32_t           routes_buf[IPV4_ROUTES_ALIGN_BUF_SIZE_UINT32];  \
+                                                                       \
+    routes = (IPv4RouteListRef)(void *)routes_buf;                     \
+    routes->size = IPV4_ROUTES_N_STATIC;                               \
+    routes->count = 0;                                                 \
+    routes->flags = 0;
+
+static CFDataRef
+IPv4RouteListDataCreate(CFDictionaryRef dict, CFNumberRef rank_assertion)
+{
+    IPv4RouteListRef   r;
+    CFDataRef          routes_data;
+    IPV4_ROUTES_BUF_DECL(routes);
+
+    r = IPv4RouteListCreateWithDictionary(routes, dict, rank_assertion);
+    if (r != NULL) {
+       routes_data = CFDataCreate(NULL,
+                                  (const void *)r,
+                                  IPv4RouteListComputeSize(r->count));
+       if (r != routes) {
+           free(r);
        }
     }
     else {
-       rtmsg.hdr.rtm_flags = RTF_UP | RTF_CLONING | RTF_STATIC;
+       routes_data = NULL;
     }
-    rtmsg.hdr.rtm_version = RTM_VERSION;
-    rtmsg.hdr.rtm_seq = ++rtm_seq;
-    rtmsg.hdr.rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
-    rtmsg.dst.sin6_len = sizeof(rtmsg.dst);
-    rtmsg.dst.sin6_family = AF_INET6;
-    rtmsg.dst.sin6_addr = netaddr;
-    rtmsg.gway.sin6_len = sizeof(rtmsg.gway);
-    rtmsg.gway.sin6_family = AF_INET6;
-    rtmsg.gway.sin6_addr = gateway;
-    rtmsg.mask.sin6_len = sizeof(rtmsg.mask);
-    rtmsg.mask.sin6_family = AF_INET6;
-    rtmsg.mask.sin6_addr = netmask;
-
-    len = sizeof(rtmsg);
-    if (ifname) {
-       rtmsg.ifp.sdl_len = sizeof(rtmsg.ifp);
-       rtmsg.ifp.sdl_family = AF_LINK;
-       rtmsg.ifp.sdl_nlen = strlen(ifname);
-       rtmsg.hdr.rtm_addrs |= RTA_IFP;
-       bcopy(ifname, rtmsg.ifp.sdl_data, rtmsg.ifp.sdl_nlen);
+    return (routes_data);
+}
+#define IPV6_ROUTES_N_STATIC           3
+#define IPV6_ROUTES_ALIGN_BUF_SIZE_UINT32                              \
+    (roundup(IPv6RouteListComputeSize(IPV6_ROUTES_N_STATIC),           \
+            sizeof(uint32_t))                                          \
+     / sizeof(uint32_t))
+
+#define IPV6_ROUTES_BUF_DECL(routes) \
+    IPv6RouteListRef   routes;                                        \
+    uint32_t           routes_buf[IPV6_ROUTES_ALIGN_BUF_SIZE_UINT32]; \
+                                                                      \
+    routes = (IPv6RouteListRef)(void *)routes_buf;                    \
+    routes->size = IPV6_ROUTES_N_STATIC;                              \
+    routes->count = 0;                                                \
+    routes->flags = 0;
+
+static CFDataRef
+IPv6RouteListDataCreate(CFDictionaryRef dict, CFNumberRef rank_assertion)
+{
+    IPv6RouteListRef   r;
+    CFDataRef          routes_data;
+    IPV6_ROUTES_BUF_DECL(routes);
+
+    r = IPv6RouteListCreateWithDictionary(routes, dict, rank_assertion);
+    if (r != NULL) {
+       routes_data = CFDataCreate(NULL,
+                                  (const void *)r,
+                                  IPv6RouteListComputeSize(r->count));
+       if (r != routes) {
+           free(r);
+       }
     }
     else {
-       /* no ifp information */
-       len -= sizeof(rtmsg.ifp);
+       routes_data = NULL;
     }
-    rtmsg.hdr.rtm_msglen = len;
-    if (write(sockfd, &rtmsg, len) == -1) {
-       if ((cmd == RTM_ADD) && (errno == EEXIST)) {
-           /* no sense complaining about a route that already exists */
+    return (routes_data);
+}
+
+static CFDictionaryRef
+IPDictCreate(int af, CFDictionaryRef state_dict, CFDictionaryRef setup_dict,
+            CFNumberRef rank_assertion)
+{
+    CFDictionaryRef            aggregated_dict = NULL;
+    CFDictionaryRef            dict;
+    CFMutableDictionaryRef     modified_dict = NULL;
+    CFDataRef                  routes_data;
+
+    dict = state_dict;
+    if (dict != NULL && setup_dict != NULL) {
+       /* look for keys in Setup: that override/merge with State: */
+       CFArrayRef      additional_routes;
+       CFStringRef     router;
+       in_addr         router_ip;
+       CFStringRef     router_prop;
+       CFStringRef     route_list_prop;
+
+       /* Router */
+       switch (af) {
+       case AF_INET:
+           router_prop = kSCPropNetIPv4Router;
+           route_list_prop = kSCPropNetIPv4AdditionalRoutes;
+           break;
+       default:
+       case AF_INET6:
+           router_prop = kSCPropNetIPv6Router;
+           route_list_prop = kSCPropNetIPv6AdditionalRoutes;
+           break;
        }
-       else if ((cmd == RTM_DELETE) && (errno == ESRCH)) {
-           /* no sense complaining about a route that isn't there */
+       router = CFDictionaryGetValue(setup_dict, router_prop);
+       if (router != NULL
+           && !cfstring_to_ipvx(af, router, &router_ip, sizeof(router_ip))) {
+           router = NULL;
        }
-       else {
-           if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-               my_log(LOG_DEBUG,
-                      "IPMonitor ipv6_route: write routing"
-                      " socket failed, %s", strerror(errno));
+
+       /* AdditionalRoutes */
+       additional_routes
+           = CFDictionaryGetValue(setup_dict, route_list_prop);
+       additional_routes = isA_CFArray(additional_routes);
+
+       if (router != NULL || additional_routes != NULL) {
+           modified_dict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
+           if (router != NULL) {
+               CFDictionarySetValue(modified_dict,
+                                    router_prop,
+                                    router);
+           }
+           if (additional_routes != NULL) {
+               CFArrayRef      combined_routes = NULL;
+               CFArrayRef      state_routes;
+
+               state_routes
+                   = CFDictionaryGetValue(state_dict,
+                                          route_list_prop);
+               if (isA_CFArray(state_routes) != NULL) {
+                   combined_routes
+                       = my_CFArrayCreateCombinedArray(additional_routes,
+                                                       state_routes);
+                   additional_routes = combined_routes;
+               }
+               CFDictionarySetValue(modified_dict,
+                                    route_list_prop,
+                                    additional_routes);
+               if (combined_routes != NULL) {
+                   CFRelease(combined_routes);
+               }
            }
-           ret = FALSE;
+           dict = modified_dict;
        }
     }
-
-    close(sockfd);
-    return (ret);
+    switch (af) {
+    case AF_INET:
+       routes_data = IPv4RouteListDataCreate(dict, rank_assertion);
+       break;
+    default:
+    case AF_INET6:
+       routes_data = IPv6RouteListDataCreate(dict, rank_assertion);
+       break;
+    }
+    if (routes_data != NULL) {
+       aggregated_dict = ipdict_create(dict, routes_data);
+       CFRelease(routes_data);
+    }
+    if (modified_dict != NULL) {
+       CFRelease(modified_dict);
+    }
+    return (aggregated_dict);
 }
 
 static boolean_t
-ipv6_default_route_delete(void)
+get_ipv4_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
+                CFDictionaryRef setup_dict, CFDictionaryRef info)
 {
-    if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-       my_log(LOG_DEBUG, "IPMonitor: IPv6 route delete default");
+    CFDictionaryRef            dict = NULL;
+    boolean_t                  changed = FALSE;
+    CFNumberRef                        rank_assertion = NULL;
+    CFDictionaryRef            service_options;
+
+    if (state_dict == NULL) {
+       goto done;
+    }
+    service_options = service_dict_get(serviceID, kSCEntNetService);
+    if (service_options != NULL) {
+       rank_assertion
+           = CFDictionaryGetValue(service_options,
+                                  kServiceOptionRankAssertion);
+    }
+    dict = IPDictCreate(AF_INET, state_dict, setup_dict, rank_assertion);
+
+  done:
+    changed = service_dict_set(serviceID, kSCEntNetIPv4, dict);
+    if (dict == NULL) {
+       /* clean up the rank too */
+       CFDictionaryRemoveValue(S_ipv4_service_rank_dict, serviceID);
     }
-    return (ipv6_route(RTM_DELETE, S_ip6_zeros, S_ip6_zeros, S_ip6_zeros,
-                      NULL, FALSE));
+    my_CFRelease(&dict);
+    return (changed);
 }
 
 static boolean_t
-ipv6_default_route_add(struct in6_addr router, char * ifname,
-                      boolean_t is_direct)
+get_ipv6_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
+                CFDictionaryRef setup_dict, CFDictionaryRef info)
 {
-    if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-       char    ntopbuf[INET6_ADDRSTRLEN];
+    CFDictionaryRef            dict = NULL;
+    boolean_t                  changed = FALSE;
+    CFNumberRef                        rank_assertion = NULL;
+    CFDictionaryRef            service_options;
 
-       my_log(LOG_DEBUG,
-              "IPMonitor: IPv6 route add default"
-              " %s interface %s direct %d",
-              inet_ntop(AF_INET6, &router, ntopbuf, sizeof(ntopbuf)),
-              ifname, is_direct);
+    if (state_dict == NULL) {
+       goto done;
+    }
+    service_options = service_dict_get(serviceID, kSCEntNetService);
+    if (service_options != NULL) {
+       rank_assertion
+           = CFDictionaryGetValue(service_options,
+                                  kServiceOptionRankAssertion);
+    }
+    dict = IPDictCreate(AF_INET6, state_dict, setup_dict, rank_assertion);
+
+  done:
+#if    !TARGET_IPHONE_SIMULATOR
+    ipv6_service_update_router(serviceID, dict);
+#endif /* !TARGET_IPHONE_SIMULATOR */
+    changed = service_dict_set(serviceID, kSCEntNetIPv6, dict);
+    if (dict == NULL) {
+       /* clean up the rank too */
+       CFDictionaryRemoveValue(S_ipv6_service_rank_dict, serviceID);
     }
-    return (ipv6_route(RTM_ADD, router, S_ip6_zeros, S_ip6_zeros,
-                      ifname, is_direct));
+    my_CFRelease(&dict);
+    return (changed);
 }
-#endif /* !TARGET_IPHONE_SIMULATOR */
 
 
-#if    !TARGET_IPHONE_SIMULATOR
-static int
-multicast_route_delete(int sockfd)
+#ifdef TEST_DNS
+__private_extern__ CFDictionaryRef
+ipv4_dict_create(CFDictionaryRef state_dict)
 {
-    struct in_addr gateway = { htonl(INADDR_LOOPBACK) };
-    struct in_addr netaddr = { htonl(INADDR_UNSPEC_GROUP) };
-    struct in_addr netmask = { htonl(IN_CLASSD_NET) };
-
-    return (ipv4_route(sockfd, RTM_DELETE, gateway, netaddr, netmask, "lo0", 0,
-                      gateway, 0));
+    return (IPDictCreate(AF_INET, state_dict, NULL, NULL));
 }
 
-static int
-multicast_route_add(int sockfd)
+__private_extern__ CFDictionaryRef
+ipv6_dict_create(CFDictionaryRef state_dict)
 {
-    struct in_addr gateway = { htonl(INADDR_LOOPBACK) };
-    struct in_addr netaddr = { htonl(INADDR_UNSPEC_GROUP) };
-    struct in_addr netmask = { htonl(IN_CLASSD_NET) };
-
-    return (ipv4_route(sockfd, RTM_ADD, gateway, netaddr, netmask, "lo0", 0,
-                      gateway, 0));
+    return (IPDictCreate(AF_INET6, state_dict, NULL, NULL));
 }
-#endif /* !TARGET_IPHONE_SIMULATOR */
 
-#if    !TARGET_IPHONE_SIMULATOR
-#ifdef RTF_IFSCOPE
+#endif /* TEST_DNS */
+
 static void
-set_ipv6_default_interface(char * ifname)
+accumulate_dns_servers(CFArrayRef in_servers, ProtocolFlags active_protos,
+                      CFMutableArrayRef out_servers, CFStringRef interface)
 {
-    struct in6_ndifreq ndifreq;
-    int                        sock;
+    CFIndex    count;
+    CFIndex    i;
 
-    bzero((char *)&ndifreq, sizeof(ndifreq));
-    if (ifname != NULL) {
-       strlcpy(ndifreq.ifname, ifname, sizeof(ndifreq.ifname));
-       ndifreq.ifindex = if_nametoindex(ifname);
-    } else {
-       strlcpy(ndifreq.ifname, "lo0", sizeof(ndifreq.ifname));
-       ndifreq.ifindex = 0;
-    }
+    count = CFArrayGetCount(in_servers);
+    for (i = 0; i < count; i++) {
+       CFStringRef     addr;
+       struct in6_addr ipv6_addr;
+       struct in_addr  ip_addr;
 
-    sock = inet6_dgram_socket();
-    if (sock == -1) {
-       my_log(LOG_ERR,
-              "IPMonitor: set_ipv6_default_interface: socket failed, %s",
-              strerror(errno));
-       return;
-    }
-    if (ioctl(sock, SIOCSDEFIFACE_IN6, (caddr_t)&ndifreq) == -1) {
-       my_log(LOG_ERR,
-              "IPMonitor: set_ipv6_default_interface: ioctl(SIOCSDEFIFACE_IN6) failed, %s",
-              strerror(errno));
-    }
-    close(sock);
-    return;
-}
-#endif /* RTF_IFSCOPE */
-
-static void
-set_ipv6_router(struct in6_addr * router, char * ifname, boolean_t is_direct)
-{
-    /* assign the new default route, ensure local multicast route available */
-    (void)ipv6_default_route_delete();
-    if (router != NULL) {
-       (void)ipv6_default_route_add(*router, ifname, is_direct);
-    }
-    return;
-}
-#endif /* !TARGET_IPHONE_SIMULATOR */
-
-#if    !TARGET_OS_IPHONE
-static __inline__ void
-empty_dns()
-{
-    (void)unlink(VAR_RUN_RESOLV_CONF);
-}
-
-static void
-set_dns(CFArrayRef val_search_domains,
-       CFStringRef val_domain_name,
-       CFArrayRef val_servers,
-       CFArrayRef val_sortlist)
-{
-    FILE * f = fopen(VAR_RUN_RESOLV_CONF "-", "w");
-
-    /* publish new resolv.conf */
-    if (f) {
-       CFIndex i;
-       CFIndex n;
-
-       SCPrint(TRUE, f, CFSTR("#\n"));
-       SCPrint(TRUE, f, CFSTR("# Mac OS X Notice\n"));
-       SCPrint(TRUE, f, CFSTR("#\n"));
-       SCPrint(TRUE, f, CFSTR("# This file is not used by the host name and address resolution\n"));
-       SCPrint(TRUE, f, CFSTR("# or the DNS query routing mechanisms used by most processes on\n"));
-       SCPrint(TRUE, f, CFSTR("# this Mac OS X system.\n"));
-       SCPrint(TRUE, f, CFSTR("#\n"));
-       SCPrint(TRUE, f, CFSTR("# This file is automatically generated.\n"));
-       SCPrint(TRUE, f, CFSTR("#\n"));
-
-       if (isA_CFArray(val_search_domains)) {
-           SCPrint(TRUE, f, CFSTR("search"));
-           n = CFArrayGetCount(val_search_domains);
-           for (i = 0; i < n; i++) {
-               CFStringRef     domain;
+       addr = CFArrayGetValueAtIndex(in_servers, i);
+       assert(addr != NULL);
 
-               domain = CFArrayGetValueAtIndex(val_search_domains, i);
-               if (isA_CFString(domain)) {
-                   SCPrint(TRUE, f, CFSTR(" %@"), domain);
+       if (cfstring_to_ip(addr, &ip_addr)) {
+           /* IPv4 address */
+           if ((active_protos & kProtocolFlagsIPv4) == 0
+               && ntohl(ip_addr.s_addr) != INADDR_LOOPBACK) {
+               if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+                   my_log(LOG_DEBUG,
+                          "IPMonitor: no IPv4 connectivity, "
+                          "ignoring DNS server address " IP_FORMAT,
+                          IP_LIST(&ip_addr));
                }
+               continue;
            }
-           SCPrint(TRUE, f, CFSTR("\n"));
-       }
-       else if (isA_CFString(val_domain_name)) {
-               SCPrint(TRUE, f, CFSTR("domain %@\n"), val_domain_name);
-       }
 
-       if (isA_CFArray(val_servers)) {
-           n = CFArrayGetCount(val_servers);
-           for (i = 0; i < n; i++) {
-               CFStringRef     nameserver;
+           CFRetain(addr);
+       }
+       else if (cfstring_to_ip6(addr, &ipv6_addr)) {
+           /* IPv6 address */
+           if ((active_protos & kProtocolFlagsIPv6) == 0
+               && !IN6_IS_ADDR_LOOPBACK(&ipv6_addr)) {
+               if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+                   char        ntopbuf[INET6_ADDRSTRLEN];
 
-               nameserver = CFArrayGetValueAtIndex(val_servers, i);
-               if (isA_CFString(nameserver)) {
-                   SCPrint(TRUE, f, CFSTR("nameserver %@\n"), nameserver);
+                   my_log(LOG_DEBUG,
+                          "IPMonitor: no IPv6 connectivity, "
+                          "ignoring DNS server address %s",
+                           inet_ntop(AF_INET6, &ipv6_addr,
+                                     ntopbuf, sizeof(ntopbuf)));
                }
+               continue;
            }
-       }
-
-       if (isA_CFArray(val_sortlist)) {
-           SCPrint(TRUE, f, CFSTR("sortlist"));
-           n = CFArrayGetCount(val_sortlist);
-           for (i = 0; i < n; i++) {
-               CFStringRef     address;
 
-               address = CFArrayGetValueAtIndex(val_sortlist, i);
-               if (isA_CFString(address)) {
-                   SCPrint(TRUE, f, CFSTR(" %@"), address);
-               }
+           if ((IN6_IS_ADDR_LINKLOCAL(&ipv6_addr) ||
+                IN6_IS_ADDR_MC_LINKLOCAL(&ipv6_addr))
+               && (interface != NULL)
+               && (CFStringFind(addr, CFSTR("%"), 0).location == kCFNotFound)) {
+               // append interface name to IPv6 link local address
+               addr = CFStringCreateWithFormat(NULL, NULL,
+                                               CFSTR("%@%%%@"),
+                                               addr,
+                                               interface);
+           } else {
+               CFRetain(addr);
            }
-           SCPrint(TRUE, f, CFSTR("\n"));
+       }
+       else {
+           /* bad IP address */
+           my_log(LOG_NOTICE,
+                  "IPMonitor: ignoring bad DNS server address '%@'",
+                  addr);
+           continue;
        }
 
-       fclose(f);
-       rename(VAR_RUN_RESOLV_CONF "-", VAR_RUN_RESOLV_CONF);
+       /* DNS server is valid and one we want */
+       CFArrayAppendValue(out_servers, addr);
+       CFRelease(addr);
     }
     return;
 }
-#endif /* !TARGET_OS_IPHONE */
 
-#if    !TARGET_IPHONE_SIMULATOR
-static boolean_t
-router_is_our_ipv6_address(CFStringRef router, CFArrayRef addr_list)
+static void
+merge_dns_servers(CFMutableDictionaryRef new_dict,
+                 CFArrayRef state_servers,
+                 CFArrayRef setup_servers,
+                 Boolean have_setup,
+                 ProtocolFlags active_protos,
+                 CFStringRef interface)
 {
-    CFIndex            i;
-    CFIndex            n = CFArrayGetCount(addr_list);
-    struct in6_addr    r;
-
-    (void)cfstring_to_ip6(router, &r);
-    for (i = 0; i < n; i++) {
-       struct in6_addr ip;
+    CFMutableArrayRef  dns_servers;
+    Boolean            have_dns_setup  = FALSE;
 
-       if (cfstring_to_ip6(CFArrayGetValueAtIndex(addr_list, i), &ip)
-           && bcmp(&r, &ip, sizeof(r)) == 0) {
-           return (TRUE);
+    if (state_servers == NULL && setup_servers == NULL) {
+       /* no DNS servers */
+       return;
+    }
+    dns_servers = CFArrayCreateMutable(NULL, 0,
+                                      &kCFTypeArrayCallBacks);
+    if (setup_servers != NULL) {
+       accumulate_dns_servers(setup_servers, active_protos,
+                              dns_servers, interface);
+       if (CFArrayGetCount(dns_servers) > 0) {
+           have_dns_setup = TRUE;
        }
     }
-    return (FALSE);
-}
-#endif /* !TARGET_IPHONE_SIMULATOR */
-
-static IPv4RouteListRef
-service_dict_get_ipv4_routelist(CFDictionaryRef service_dict)
-{
-    CFDictionaryRef    dict;
+    if ((CFArrayGetCount(dns_servers) == 0 || S_append_state)
+       && state_servers != NULL) {
+       accumulate_dns_servers(state_servers, active_protos,
+                              dns_servers, NULL);
+    }
 
-    dict = CFDictionaryGetValue(service_dict, kSCEntNetIPv4);
+    /*
+     * Here, we determine whether or not we want all queries for this DNS
+     * configuration to be bound to the associated network interface.
+     *
+     * For dynamically derived network configurations (i.e. from State:)
+     * this would be the preferred option using the argument "Hey, the
+     * server told us to use these servers on this network so let's not
+     * argue".
+     *
+     * But, when a DNS configuration has been provided by the user/admin
+     * via the Network pref pane (i.e. from Setup:) we opt to not force
+     * binding of the outbound queries.  The simplest example why we take
+     * this stance is with a multi-homing configuration.  Consider a system
+     * with one network service associated with "en0" and a second service
+     * associated with "en1".  The "en0" service has been set higher in
+     * the network service order so it would be primary but the user/admin
+     * wants the DNS queries to go to a server only accessible via "en1".
+     * Without this exception we would take the DNS server addresses from
+     * the Network pref pane (for "en0") and have the queries bound to
+     * "en0" where they'd never reach their intended destination (via
+     * "en1").  So, our exception to the rule is that we will not bind
+     * user/admin configurations to any specific network interface.
+     *
+     * We also add an exception to the "follow the dynamically derived
+     * network configuration" path for on-the-fly (no Setup: content)
+     * network services.
+     */
+    if (CFArrayGetCount(dns_servers) != 0) {
+       CFDictionarySetValue(new_dict,
+                            kSCPropNetDNSServerAddresses, dns_servers);
+       if (have_setup && !have_dns_setup) {
+           CFDictionarySetValue(new_dict, DNS_CONFIGURATION_SCOPED_QUERY_KEY, kCFBooleanTrue);
+       }
+    }
 
-    return (ipv4_dict_get_routelist(dict));
+    my_CFRelease(&dns_servers);
+    return;
 }
 
-static CFStringRef
-service_dict_get_ipv4_ifname(CFDictionaryRef service_dict)
-{
-    CFDictionaryRef    dict;
-
-    dict = CFDictionaryGetValue(service_dict, kSCEntNetIPv4);
-    return (ipv4_dict_get_ifname(dict));
-}
 
 static boolean_t
-service_get_ip_is_coupled(CFStringRef serviceID)
+get_dns_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
+               CFDictionaryRef setup_dict, CFDictionaryRef info)
 {
-    CFDictionaryRef    dict;
-    boolean_t          ip_is_coupled = FALSE;
+    ProtocolFlags              active_protos   = kProtocolFlagsNone;
+    boolean_t                  changed         = FALSE;
+    CFStringRef                        domain;
+    Boolean                    have_setup      = FALSE;
+    CFStringRef                        interface       = NULL;
+    CFDictionaryRef            ipv4;
+    CFDictionaryRef            ipv6;
+    int                                i;
+    const struct {
+       CFStringRef     key;
+       uint32_t        flags;
+       Boolean         append;
+    } merge_list[] = {
+       { kSCPropNetDNSSearchDomains,                   0,                      FALSE },
+       { kSCPropNetDNSSortList,                        0,                      FALSE },
+       { kSCPropNetDNSSupplementalMatchDomains,        ALLOW_EMPTY_STRING,     TRUE  },
+       { kSCPropNetDNSSupplementalMatchOrders,         0,                      TRUE  },
+    };
+    CFMutableDictionaryRef      new_dict = NULL;
+    const CFStringRef          pick_list[] = {
+       kSCPropNetDNSDomainName,
+       kSCPropNetDNSOptions,
+       kSCPropNetDNSSearchOrder,
+       kSCPropNetDNSServerPort,
+       kSCPropNetDNSServerTimeout,
+       kSCPropNetDNSServiceIdentifier,
+       kSCPropNetDNSSupplementalMatchDomainsNoSearch,
+    };
 
-    dict = service_dict_get(serviceID, kSCEntNetService);
-    if (dict != NULL) {
-       if (CFDictionaryContainsKey(dict, kIPIsCoupled)) {
-           ip_is_coupled = TRUE;
+    if ((state_dict == NULL) && (setup_dict == NULL)) {
+       /* there is no DNS content */
+       goto done;
+    }
+
+    ipv4 = service_dict_get(serviceID, kSCEntNetIPv4);
+    if (ipv4 != NULL) {
+       if (get_service_setup_entity(info, serviceID, kSCEntNetIPv4) != NULL) {
+           have_setup = TRUE;
        }
+       active_protos |= kProtocolFlagsIPv4;
+       interface = ipdict_get_ifname(ipv4);
     }
-    return (ip_is_coupled);
-}
 
-#if    !TARGET_IPHONE_SIMULATOR
+    ipv6 = service_dict_get(serviceID, kSCEntNetIPv6);
+    if (ipv6 != NULL) {
+       if (!have_setup
+           && (get_service_setup_entity(info, serviceID, kSCEntNetIPv6)
+               != NULL)) {
+           have_setup = TRUE;
+       }
+       active_protos |= kProtocolFlagsIPv6;
+       if (interface == NULL) {
+           interface = ipdict_get_ifname(ipv6);
+       }
+    }
 
-typedef struct apply_ipv4_route_context {
-    IPv4RouteListRef   old;
-    IPv4RouteListRef   new;
-    int                        sockfd;
-} apply_ipv4_route_context_t;
 
-/* add/remove a router/32 subnet */
-static int
-ipv4_route_gateway(int sockfd, int cmd, char * ifn_p,
-                  IPv4RouteRef def_route)
-{
-    struct in_addr             mask;
+    if (active_protos == kProtocolFlagsNone) {
+       /* there is no IPv4 nor IPv6 */
+       if (state_dict == NULL) {
+           /* ... and no DNS content that we care about */
+           goto done;
+       }
+       setup_dict = NULL;
+    }
 
-    mask.s_addr = htonl(INADDR_BROADCAST);
-    return (ipv4_route(sockfd, cmd, def_route->ifa,
-                      def_route->gateway, mask, ifn_p, def_route->ifindex,
-                      def_route->ifa, def_route->flags));
-}
+    /* merge DNS configuration */
+    new_dict = CFDictionaryCreateMutable(NULL, 0,
+                                        &kCFTypeDictionaryKeyCallBacks,
+                                        &kCFTypeDictionaryValueCallBacks);
 
-/*
- * Function: apply_ipv4_route
- * Purpose:
- *   Callback function that adds/removes the specified route.
- */
-static void
-apply_ipv4_route(IPv4RouteListApplyCommand cmd, IPv4RouteRef route, void * arg)
-{
-    apply_ipv4_route_context_t *context = (apply_ipv4_route_context_t *)arg;
-    char *                     ifn_p;
-    int                                retval;
+    if (active_protos == kProtocolFlagsNone) {
+       merge_dns_servers(new_dict,
+                         my_CFDictionaryGetArray(state_dict,
+                                                 kSCPropNetDNSServerAddresses),
+                         NULL,
+                         FALSE,
+                         kProtocolFlagsIPv4 | kProtocolFlagsIPv6,
+                         NULL);
+    }
+    else {
+       merge_dns_servers(new_dict,
+                         my_CFDictionaryGetArray(state_dict,
+                                                 kSCPropNetDNSServerAddresses),
+                         my_CFDictionaryGetArray(setup_dict,
+                                                 kSCPropNetDNSServerAddresses),
+                         have_setup,
+                         active_protos,
+                         interface);
+    }
 
-    ifn_p = route->ifname;
-    switch (cmd) {
-    case kIPv4RouteListAddRouteCommand:
-       if ((route->flags & kRouteIsNotSubnetLocalFlag) != 0) {
-           retval = ipv4_route_gateway(context->sockfd, RTM_ADD,
-                                       ifn_p, route);
-           if (retval == EEXIST) {
-               /* delete and add again */
-               (void)ipv4_route_gateway(context->sockfd, RTM_DELETE,
-                                        ifn_p, route);
-               retval = ipv4_route_gateway(context->sockfd, RTM_ADD,
-                                           ifn_p, route);
-           }
-           if (retval != 0) {
-               my_log(LOG_NOTICE,
-                      "IPMonitor apply_ipv4_route failed to add"
-                      " %s/32 route, %s",
-                      inet_ntoa(route->gateway), strerror(retval));
-           }
-           else if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-               my_log(LOG_DEBUG, "Added IPv4 Route %s/32",
-                      inet_ntoa(route->gateway));
-           }
-       }
-       retval = ipv4_route(context->sockfd,
-                           RTM_ADD, route->gateway,
-                           route->dest, route->mask, ifn_p, route->ifindex,
-                           route->ifa, route->flags);
-       if (retval == EEXIST) {
-           /* delete and add again */
-           (void)ipv4_route(context->sockfd,
-                            RTM_DELETE, route->gateway,
-                            route->dest, route->mask, ifn_p, route->ifindex,
-                            route->ifa, route->flags);
-           retval = ipv4_route(context->sockfd,
-                               RTM_ADD, route->gateway,
-                               route->dest, route->mask,
-                               ifn_p, route->ifindex,
-                               route->ifa, route->flags);
-       }
-       if (retval != 0) {
-           my_log(LOG_NOTICE,
-                  "IPMonitor apply_ipv4_route failed to add"
-                  " route, %s:", strerror(retval));
-           IPv4RouteLog(LOG_NOTICE, route);
-       }
-       else if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-           my_log(LOG_DEBUG,
-                  "Added IPv4 route new[%d] = ",
-                  route - context->new->list);
-           IPv4RouteLog(LOG_DEBUG, route);
-       }
-       break;
-    case kIPv4RouteListRemoveRouteCommand:
-       retval = ipv4_route(context->sockfd,
-                           RTM_DELETE, route->gateway,
-                           route->dest, route->mask, ifn_p, route->ifindex,
-                           route->ifa, route->flags);
-       if (retval != 0) {
-           if (retval != ESRCH) {
-               my_log(LOG_NOTICE,
-                      "IPMonitor apply_ipv4_route failed to remove"
-                      " route, %s: ", strerror(retval));
-               IPv4RouteLog(LOG_NOTICE, route);
+    for (i = 0; i < countof(merge_list); i++) {
+       merge_array_prop(new_dict,
+                        merge_list[i].key,
+                        state_dict,
+                        setup_dict,
+                        merge_list[i].flags,
+                        merge_list[i].append);
+    }
+
+    for (i = 0; i < countof(pick_list); i++) {
+       pick_prop(new_dict,
+                 pick_list[i],
+                 state_dict,
+                 setup_dict,
+                 0);
+    }
+
+    if (active_protos == kProtocolFlagsNone) {
+       /* there is no IPv4 nor IPv6, only supplemental or service-specific DNS */
+       if (CFDictionaryContainsKey(new_dict,
+                                   kSCPropNetDNSSupplementalMatchDomains)) {
+           /* only keep State: supplemental */
+           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSDomainName);
+           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSearchDomains);
+           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSearchOrder);
+           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSortList);
+
+           if ((interface == NULL) && (setup_dict == NULL) && (state_dict != NULL)) {
+               /*
+                * for supplemental-only configurations, add any scoped (or
+                * wild-card "*") interface
+                */
+               interface = CFDictionaryGetValue(state_dict, kSCPropInterfaceName);
            }
+       } else if (CFDictionaryContainsKey(new_dict, kSCPropNetDNSServiceIdentifier) &&
+                  (interface == NULL) &&
+                  (state_dict != NULL)) {
+           interface = CFDictionaryGetValue(state_dict, kSCPropInterfaceName);
+       } else {
+           goto done;
        }
-       else if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-           my_log(LOG_DEBUG,
-                  "Removed IPv4 route old[%d] = ",
-                  route - context->old->list);
-           IPv4RouteLog(LOG_DEBUG, route);
-       }
-       if ((route->flags & kRouteIsNotSubnetLocalFlag) != 0) {
-           retval = ipv4_route_gateway(context->sockfd, RTM_DELETE,
-                                       ifn_p, route);
-           if (retval != 0) {
-               my_log(LOG_NOTICE,
-                      "IPMonitor apply_ipv4_route failed to remove"
-                      " %s/32 route, %s: ",
-                      inet_ntoa(route->gateway), strerror(retval));
-           }
-           else if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-               my_log(LOG_DEBUG, "Removed IPv4 Route %s/32",
-                      inet_ntoa(route->gateway));
+    }
+
+    if (CFDictionaryGetCount(new_dict) == 0) {
+       my_CFRelease(&new_dict);
+       goto done;
+    }
+
+    if (interface != NULL) {
+       CFDictionarySetValue(new_dict, kSCPropInterfaceName, interface);
+    }
+
+    if (S_append_state) {
+       /*
+        * ensure any specified domain name (e.g. the domain returned by
+        * a DHCP server) is in the search list.
+        */
+       domain = CFDictionaryGetValue(new_dict, kSCPropNetDNSDomainName);
+       if (isA_CFString(domain)) {
+           CFArrayRef      search;
+
+           search = CFDictionaryGetValue(new_dict, kSCPropNetDNSSearchDomains);
+           if (isA_CFArray(search) &&
+               !CFArrayContainsValue(search, CFRangeMake(0, CFArrayGetCount(search)), domain)) {
+               CFMutableArrayRef   new_search;
+
+               new_search = CFArrayCreateMutableCopy(NULL, 0, search);
+               CFArrayAppendValue(new_search, domain);
+               CFDictionarySetValue(new_dict, kSCPropNetDNSSearchDomains, new_search);
+               my_CFRelease(&new_search);
            }
        }
-       break;
-    default:
-       break;
     }
-    return;
+
+ done:
+    changed = service_dict_set(serviceID, kSCEntNetDNS, new_dict);
+    my_CFRelease(&new_dict);
+    return (changed);
 }
-#endif /* !TARGET_IPHONE_SIMULATOR */
 
-/*
- * Function: update_ipv4
- *
- * Purpose:
- *   Update the IPv4 configuration based on the latest information.
- *   Publish the State:/Network/Global/IPv4 information, and update the
- *   IPv4 routing table.  IPv4RouteListApply() invokes our callback,
- *   apply_ipv4_route(), to install/remove the routes.
- */
 static void
-update_ipv4(CFStringRef                primary,
-           IPv4RouteListRef    new_routelist,
-           keyChangeListRef    keys)
+merge_dict(const void *key, const void *value, void *context)
 {
-#if    !TARGET_IPHONE_SIMULATOR
-    apply_ipv4_route_context_t context;
-#endif /* !TARGET_IPHONE_SIMULATOR */
+       CFMutableDictionaryRef  dict    = (CFMutableDictionaryRef)context;
 
-    if (keys != NULL) {
-       if (new_routelist != NULL && primary != NULL) {
-           char *                      ifn_p = NULL;
-           IPv4RouteRef                r;
-           CFMutableDictionaryRef      dict = NULL;
+       CFDictionarySetValue(dict, key, value);
+       return;
+}
 
-           dict = CFDictionaryCreateMutable(NULL, 0,
-                                            &kCFTypeDictionaryKeyCallBacks,
-                                            &kCFTypeDictionaryValueCallBacks);
-           /* the first entry is the default route */
-           r = new_routelist->list;
-           if (r->gateway.s_addr != 0) {
-               CFStringRef             router;
+#define        PROXY_AUTO_DISCOVERY_URL        252
 
-               router = CFStringCreateWithCString(NULL,
-                                                  inet_ntoa(r->gateway),
-                                                  kCFStringEncodingASCII);
-               if (router != NULL) {
-                   CFDictionarySetValue(dict, kSCPropNetIPv4Router, router);
-                   CFRelease(router);
-               }
-           }
-           if (r->ifname[0] != '\0') {
-               ifn_p = r->ifname;
-           }
-           if (ifn_p != NULL) {
-               CFStringRef             ifname_cf;
+static CF_RETURNS_RETAINED CFStringRef
+wpadURL_dhcp(CFDictionaryRef dhcp_options)
+{
+    CFStringRef        urlString       = NULL;
 
-               ifname_cf = CFStringCreateWithCString(NULL,
-                                                     ifn_p,
-                                                     kCFStringEncodingASCII);
-               if (ifname_cf != NULL) {
-                   CFDictionarySetValue(dict,
-                                        kSCDynamicStorePropNetPrimaryInterface,
-                                        ifname_cf);
-                   CFRelease(ifname_cf);
-               }
-           }
-           CFDictionarySetValue(dict, kSCDynamicStorePropNetPrimaryService,
-                                primary);
-           keyChangeListSetValue(keys, S_state_global_ipv4, dict);
-           CFRelease(dict);
-       }
-       else {
-           keyChangeListRemoveValue(keys, S_state_global_ipv4);
-       }
-    }
+    if (dhcp_options != NULL) {
+       CFDataRef       data;
 
-#if    !TARGET_IPHONE_SIMULATOR
-    bzero(&context, sizeof(context));
-    context.sockfd = route_open_socket();
-    if (context.sockfd != -1) {
-       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-           if (S_ipv4_routelist == NULL) {
-               my_log(LOG_DEBUG, "Old Routes = <none>");
-           }
-           else {
-               my_log(LOG_DEBUG, "Old Routes = ");
-               IPv4RouteListLog(LOG_DEBUG, S_ipv4_routelist);
+       data = DHCPInfoGetOptionData(dhcp_options, PROXY_AUTO_DISCOVERY_URL);
+       if (data != NULL) {
+           CFURLRef    url;
+           const UInt8 *urlBytes;
+           CFIndex     urlLen;
+
+           urlBytes = CFDataGetBytePtr(data);
+           urlLen   = CFDataGetLength(data);
+           while ((urlLen > 0) && (urlBytes[urlLen - 1] == 0)) {
+               // remove trailing NUL
+               urlLen--;
            }
-           if (new_routelist == NULL) {
-               my_log(LOG_DEBUG, "New Routes = <none>");
+
+           if (urlLen <= 0) {
+               return NULL;
            }
-           else {
-               my_log(LOG_DEBUG, "New Routes = ");
-               IPv4RouteListLog(LOG_DEBUG, new_routelist);
+
+           url = CFURLCreateWithBytes(NULL, urlBytes, urlLen, kCFStringEncodingUTF8, NULL);
+           if (url != NULL) {
+               urlString = CFURLGetString(url);
+               if (urlString != NULL) {
+                   CFRetain(urlString);
+               }
+               CFRelease(url);
            }
        }
-       context.old = S_ipv4_routelist;
-       context.new = new_routelist;
-       IPv4RouteListApply(S_ipv4_routelist, new_routelist,
-                          &apply_ipv4_route, (void *)&context);
-       if (new_routelist != NULL) {
-           (void)multicast_route_delete(context.sockfd);
-       }
-       else {
-           (void)multicast_route_add(context.sockfd);
-       }
-       close(context.sockfd);
     }
-    if (S_ipv4_routelist != NULL) {
-       free(S_ipv4_routelist);
-    }
-    S_ipv4_routelist = new_routelist;
-#endif /* !TARGET_IPHONE_SIMULATOR */
 
-    return;
+    return urlString;
 }
 
-static void
-update_ipv6(CFStringRef                primary,
-           keyChangeListRef    keys)
+static CF_RETURNS_RETAINED CFStringRef
+wpadURL_dns(void)
 {
-    CFDictionaryRef    ipv6_dict = NULL;
+    CFURLRef   url;
+    CFStringRef        urlString       = NULL;
 
-    if (primary != NULL) {
-       ipv6_dict = service_dict_get(primary, kSCEntNetIPv6);
-    }
-    if (ipv6_dict != NULL) {
+    url = CFURLCreateWithString(NULL, CFSTR("http://wpad/wpad.dat"), NULL);
+    if (url != NULL) {
+       urlString = CFURLGetString(url);
+       if (urlString != NULL) {
+           CFRetain(urlString);
+       }
+       CFRelease(url);
+    }
+
+    return urlString;
+}
+
+static boolean_t
+get_proxies_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
+                   CFDictionaryRef setup_dict, CFDictionaryRef info)
+{
+    ProtocolFlags              active_protos   = kProtocolFlagsNone;
+    boolean_t                  changed         = FALSE;
+    CFStringRef                        interface       = NULL;
+    CFDictionaryRef            ipv4;
+    CFDictionaryRef            ipv6;
+    CFMutableDictionaryRef     new_dict        = NULL;
+    const struct {
+       CFStringRef     key;
+       uint32_t        flags;
+       Boolean         append;
+    } merge_list[] = {
+       { kSCPropNetProxiesSupplementalMatchDomains,    ALLOW_EMPTY_STRING,     TRUE  },
+       { kSCPropNetProxiesSupplementalMatchOrders,     0,                      TRUE  },
+    };
+    const struct {
+           CFStringRef key1;   /* an "enable" key */
+           CFStringRef key2;
+           CFStringRef key3;
+    } pick_list[] = {
+           { kSCPropNetProxiesFTPEnable,       kSCPropNetProxiesFTPProxy,      kSCPropNetProxiesFTPPort        },
+           { kSCPropNetProxiesGopherEnable,    kSCPropNetProxiesGopherProxy,   kSCPropNetProxiesGopherPort     },
+           { kSCPropNetProxiesHTTPEnable,      kSCPropNetProxiesHTTPProxy,     kSCPropNetProxiesHTTPPort       },
+           { kSCPropNetProxiesHTTPSEnable,     kSCPropNetProxiesHTTPSProxy,    kSCPropNetProxiesHTTPSPort      },
+           { kSCPropNetProxiesRTSPEnable,      kSCPropNetProxiesRTSPProxy,     kSCPropNetProxiesRTSPPort       },
+           { kSCPropNetProxiesSOCKSEnable,     kSCPropNetProxiesSOCKSProxy,    kSCPropNetProxiesSOCKSPort      },
+           { kSCPropNetProxiesProxyAutoConfigEnable,
+             kSCPropNetProxiesProxyAutoConfigURLString,
+             kSCPropNetProxiesProxyAutoConfigJavaScript, },
+           { kSCPropNetProxiesProxyAutoDiscoveryEnable,
+             NULL,
+             NULL, }
+    };
+
+    if ((state_dict == NULL) && (setup_dict == NULL)) {
+       /* there is no proxy content */
+       goto done;
+    }
+    ipv4 = service_dict_get(serviceID, kSCEntNetIPv4);
+    if (ipdict_get_routelist(ipv4) != NULL) {
+       active_protos |= kProtocolFlagsIPv4;
+       interface = ipdict_get_ifname(ipv4);
+    }
+    ipv6 = service_dict_get(serviceID, kSCEntNetIPv6);
+    if (ipdict_get_routelist(ipv6) != NULL) {
+       active_protos |= kProtocolFlagsIPv6;
+       if (interface == NULL) {
+           interface = ipdict_get_ifname(ipv6);
+       }
+    }
+    if (active_protos == kProtocolFlagsNone) {
+       /* there is no IPv4 nor IPv6 */
+       if (state_dict == NULL) {
+           /* ... and no proxy content that we care about */
+           goto done;
+       }
+       setup_dict = NULL;
+    }
+
+    if ((setup_dict != NULL) && (state_dict != NULL)) {
+       CFIndex                 i;
+       CFMutableDictionaryRef  setup_copy;
+
+       /*
+        * Merge the per-service "Setup:" and "State:" proxy information with
+        * the "Setup:" information always taking precedence.  Additionally,
+        * ensure that if any group of "Setup:" values (e.g. Enabled, Proxy,
+        * Port) is defined than all of the values for that group will be
+        * used.  That is, we don't allow mixing some of the values from
+        * the "Setup:" keys and others from the "State:" keys.
+        */
+       new_dict = CFDictionaryCreateMutableCopy(NULL, 0, state_dict);
+       for (i = 0; i < countof(merge_list); i++) {
+           merge_array_prop(new_dict,
+                            merge_list[i].key,
+                            state_dict,
+                            setup_dict,
+                            merge_list[i].flags,
+                            merge_list[i].append);
+       }
+
+       setup_copy = CFDictionaryCreateMutableCopy(NULL, 0, setup_dict);
+       for (i = 0; i < countof(pick_list); i++) {
+           if (CFDictionaryContainsKey(setup_copy, pick_list[i].key1)) {
+               /*
+                * if a "Setup:" enabled key has been provided than we want to
+                * ignore all of the "State:" keys
+                */
+               CFDictionaryRemoveValue(new_dict, pick_list[i].key1);
+               if (pick_list[i].key2 != NULL) {
+                   CFDictionaryRemoveValue(new_dict, pick_list[i].key2);
+               }
+               if (pick_list[i].key3 != NULL) {
+                   CFDictionaryRemoveValue(new_dict, pick_list[i].key3);
+               }
+           } else if (CFDictionaryContainsKey(state_dict, pick_list[i].key1) ||
+                      ((pick_list[i].key2 != NULL) && CFDictionaryContainsKey(state_dict, pick_list[i].key2)) ||
+                      ((pick_list[i].key3 != NULL) && CFDictionaryContainsKey(state_dict, pick_list[i].key3))) {
+               /*
+                * if a "Setup:" enabled key has not been provided and we have
+                * some" "State:" keys than we remove all of of "Setup:" keys
+                */
+               CFDictionaryRemoveValue(setup_copy, pick_list[i].key1);
+               if (pick_list[i].key2 != NULL) {
+                   CFDictionaryRemoveValue(setup_copy, pick_list[i].key2);
+               }
+               if (pick_list[i].key3 != NULL) {
+                   CFDictionaryRemoveValue(setup_copy, pick_list[i].key3);
+               }
+           }
+       }
+
+       /* merge the "Setup:" keys */
+       CFDictionaryApplyFunction(setup_copy, merge_dict, new_dict);
+       CFRelease(setup_copy);
+    }
+    else if (setup_dict != NULL) {
+       new_dict = CFDictionaryCreateMutableCopy(NULL, 0, setup_dict);
+    }
+    else if (state_dict != NULL) {
+       new_dict = CFDictionaryCreateMutableCopy(NULL, 0, state_dict);
+    }
+
+    if ((new_dict != NULL) && (CFDictionaryGetCount(new_dict) == 0)) {
+       CFRelease(new_dict);
+       new_dict = NULL;
+    }
+
+    if ((new_dict != NULL) && (interface != NULL)) {
+       CFDictionarySetValue(new_dict, kSCPropInterfaceName, interface);
+    }
+
+    /* process WPAD */
+    if (new_dict != NULL) {
+       CFDictionaryRef dhcp_options;
+       CFNumberRef     num;
+       CFNumberRef     wpad        = NULL;
+       int             wpadEnabled = 0;
+       CFStringRef     wpadURL     = NULL;
+
+       if (CFDictionaryGetValueIfPresent(new_dict,
+                                         kSCPropNetProxiesProxyAutoDiscoveryEnable,
+                                         (const void **)&num) &&
+           isA_CFNumber(num)) {
+           /* if we have a WPAD key */
+           wpad = num;
+           if (!CFNumberGetValue(num, kCFNumberIntType, &wpadEnabled)) {
+               /* if we don't like the enabled key/value */
+               wpadEnabled = 0;
+           }
+       }
+
+       if (wpadEnabled) {
+           int pacEnabled  = 0;
+
+           num = CFDictionaryGetValue(new_dict, kSCPropNetProxiesProxyAutoConfigEnable);
+           if (!isA_CFNumber(num) ||
+               !CFNumberGetValue(num, kCFNumberIntType, &pacEnabled)) {
+               /* if we don't like the enabled key/value */
+               pacEnabled = 0;
+           }
+
+           if (pacEnabled) {
+               CFStringRef     pacURL;
+
+               pacURL = CFDictionaryGetValue(new_dict, kSCPropNetProxiesProxyAutoConfigURLString);
+               if (pacURL != NULL) {
+                   if (!isA_CFString(pacURL)) {
+                       /* if we don't like the PAC URL */
+                       pacEnabled = 0;
+                   }
+               } else {
+                   CFStringRef pacJS;
+
+                   pacJS = CFDictionaryGetValue(new_dict, kSCPropNetProxiesProxyAutoConfigJavaScript);
+                   if (!isA_CFString(pacJS)) {
+                       /* if we don't have (or like) the PAC JavaScript */
+                       pacEnabled = 0;
+                   }
+               }
+           }
+
+           if (pacEnabled) {
+               /*
+                * we already have a PAC URL so disable WPAD.
+                */
+               wpadEnabled = 0;
+               goto setWPAD;
+           }
+
+           /*
+            * if WPAD is enabled and we don't already have a PAC URL then
+            * we check for a DHCP provided URL.  If not available, we use
+            * a PAC URL pointing to a well-known file (wpad.dat) on a
+            * well-known host (wpad.<domain>).
+            */
+           dhcp_options = get_service_state_entity(info, serviceID, kSCEntNetDHCP);
+           wpadURL = wpadURL_dhcp(dhcp_options);
+           if (wpadURL == NULL) {
+               wpadURL = wpadURL_dns();
+           }
+           if (wpadURL == NULL) {
+               wpadEnabled = 0;    /* if we don't have a WPAD URL */
+               goto setWPAD;
+           }
+
+           pacEnabled = 1;
+           num = CFNumberCreate(NULL, kCFNumberIntType, &pacEnabled);
+           CFDictionarySetValue(new_dict,
+                                kSCPropNetProxiesProxyAutoConfigEnable,
+                                num);
+           CFRelease(num);
+           CFDictionarySetValue(new_dict,
+                                kSCPropNetProxiesProxyAutoConfigURLString,
+                                wpadURL);
+           CFRelease(wpadURL);
+       }
+
+     setWPAD:
+       if (wpad != NULL) {
+           num = CFNumberCreate(NULL, kCFNumberIntType, &wpadEnabled);
+           CFDictionarySetValue(new_dict,
+                                kSCPropNetProxiesProxyAutoDiscoveryEnable,
+                                num);
+           CFRelease(num);
+       }
+    }
+
+ done:
+    changed = service_dict_set(serviceID, kSCEntNetProxies, new_dict);
+    my_CFRelease(&new_dict);
+    return (changed);
+}
+
+#if    !TARGET_OS_IPHONE
+static boolean_t
+get_smb_changes(CFStringRef serviceID, CFDictionaryRef state_dict,
+               CFDictionaryRef setup_dict, CFDictionaryRef info)
+{
+    boolean_t                  changed = FALSE;
+    int                                i;
+    CFMutableDictionaryRef      new_dict = NULL;
+    const CFStringRef          pick_list[] = {
+       kSCPropNetSMBNetBIOSName,
+       kSCPropNetSMBNetBIOSNodeType,
+#ifdef ADD_NETBIOS_SCOPE
+       kSCPropNetSMBNetBIOSScope,
+#endif // ADD_NETBIOS_SCOPE
+       kSCPropNetSMBWorkgroup,
+    };
+
+    if (state_dict == NULL && setup_dict == NULL) {
+       /* there is no SMB */
+       goto done;
+    }
+    if (service_dict_get(serviceID, kSCEntNetIPv4) == NULL
+       && service_dict_get(serviceID, kSCEntNetIPv6) == NULL) {
+       /* there is no IPv4 or IPv6 */
+       goto done;
+    }
+
+    /* merge SMB configuration */
+    new_dict = CFDictionaryCreateMutable(NULL, 0,
+                                        &kCFTypeDictionaryKeyCallBacks,
+                                        &kCFTypeDictionaryValueCallBacks);
+    merge_array_prop(new_dict,
+                    kSCPropNetSMBWINSAddresses,
+                    state_dict,
+                    setup_dict,
+                    0,
+                    FALSE);
+    for (i = 0; i < countof(pick_list); i++) {
+       pick_prop(new_dict,
+                 pick_list[i],
+                 state_dict,
+                 setup_dict,
+                 0);
+    }
+
+    if (CFDictionaryGetCount(new_dict) == 0) {
+       my_CFRelease(&new_dict);
+       goto done;
+    }
+
+ done:
+    changed = service_dict_set(serviceID, kSCEntNetSMB, new_dict);
+    my_CFRelease(&new_dict);
+    return (changed);
+}
+#endif /* !TARGET_OS_IPHONE */
+
+static CFStringRef
+services_info_get_interface(CFDictionaryRef services_info,
+                           CFStringRef serviceID)
+{
+    CFStringRef                interface = NULL;
+    CFDictionaryRef    ipv4_dict;
+
+    ipv4_dict = get_service_state_entity(services_info, serviceID,
+                                        kSCEntNetIPv4);
+    if (ipv4_dict != NULL) {
+       interface = CFDictionaryGetValue(ipv4_dict, kSCPropInterfaceName);
+    }
+    else {
+       CFDictionaryRef         ipv6_dict;
+
+       ipv6_dict = get_service_state_entity(services_info, serviceID,
+                                            kSCEntNetIPv6);
+       if (ipv6_dict != NULL) {
+           interface = CFDictionaryGetValue(ipv6_dict, kSCPropInterfaceName);
+       }
+    }
+    return (interface);
+}
+
+
+static const struct {
+    const CFStringRef *        entityName;
+    const CFStringRef *        statusKey;
+} transientServiceInfo[] = {
+    { &kSCEntNetIPSec, &kSCPropNetIPSecStatus  },
+    { &kSCEntNetPPP,   &kSCPropNetPPPStatus    },
+    { &kSCEntNetVPN,   &kSCPropNetVPNStatus    },
+};
+
+static Boolean
+get_transient_status_changes(CFStringRef serviceID,
+                            CFDictionaryRef services_info)
+{
+    boolean_t  changed = FALSE;
+    int                i;
+
+    for (i = 0; i < countof(transientServiceInfo); i++) {
+       CFDictionaryRef         dict;
+       CFNumberRef             status          = NULL;
+       CFMutableDictionaryRef  ts_dict         = NULL;
+
+       dict = get_service_state_entity(services_info, serviceID,
+                                       *transientServiceInfo[i].entityName);
+
+       if (dict != NULL) {
+           status = CFDictionaryGetValue(dict,
+                                         *transientServiceInfo[i].statusKey);
+       }
+
+       if (isA_CFNumber(status) != NULL) {
+           ts_dict = CFDictionaryCreateMutable(NULL,
+                                                0,
+                                                &kCFTypeDictionaryKeyCallBacks,
+                                                &kCFTypeDictionaryValueCallBacks);
+           CFDictionaryAddValue(ts_dict,
+                                *transientServiceInfo[i].statusKey,
+                                status);
+       }
+
+       if (service_dict_set(serviceID, *transientServiceInfo[i].entityName,
+                            ts_dict)) {
+           changed = TRUE;
+       }
+
+       if (ts_dict != NULL) {
+           CFRelease(ts_dict);
+       }
+    }
+    return (changed);
+}
+
+static boolean_t
+service_is_expensive(CFStringRef serviceID, CFDictionaryRef services_info)
+{
+    CFStringRef                ifname;
+    boolean_t          is_expensive = FALSE;
+
+    ifname = services_info_get_interface(services_info, serviceID);
+    if (ifname != NULL) {
+       CFDictionaryRef         if_dict;
+       CFStringRef             key;
+
+       key = interface_entity_key_copy(ifname, kSCEntNetLink);
+       if_dict = CFDictionaryGetValue(services_info, key);
+       CFRelease(key);
+       if (isA_CFDictionary(if_dict) != NULL) {
+           CFBooleanRef        expensive;
+
+           expensive = CFDictionaryGetValue(if_dict, kSCPropNetLinkExpensive);
+           if (isA_CFBoolean(expensive) != NULL
+               && CFBooleanGetValue(expensive)) {
+               is_expensive = TRUE;
+           }
+       }
+    }
+    return (is_expensive);
+}
+
+static boolean_t
+get_rank_changes(CFStringRef serviceID, CFDictionaryRef state_options,
+                CFDictionaryRef setup_options, CFDictionaryRef services_info)
+{
+    boolean_t                  changed         = FALSE;
+    boolean_t                  ip_is_coupled   = FALSE;
+    CFMutableDictionaryRef      new_dict       = NULL;
+    Rank                       rank_assertion = kRankAssertionDefault;
+    Boolean                    rank_assertion_is_set = FALSE;
+    CFStringRef                        setup_rank      = NULL;
+    CFStringRef                        state_rank      = NULL;
+
+
+    if (state_options != NULL) {
+       CFBooleanRef    coupled;
+
+       state_rank
+           = CFDictionaryGetValue(state_options, kSCPropNetServicePrimaryRank);
+       state_rank = isA_CFString(state_rank);
+       coupled = CFDictionaryGetValue(state_options, kIPIsCoupled);
+       if (isA_CFBoolean(coupled) != NULL && CFBooleanGetValue(coupled)) {
+           ip_is_coupled = TRUE;
+       }
+    }
+    if (setup_options != NULL) {
+       CFBooleanRef    coupled;
+
+       setup_rank
+           = CFDictionaryGetValue(setup_options, kSCPropNetServicePrimaryRank);
+       setup_rank = isA_CFString(setup_rank);
+       coupled = CFDictionaryGetValue(setup_options, kIPIsCoupled);
+       if (isA_CFBoolean(coupled) != NULL && CFBooleanGetValue(coupled)) {
+           ip_is_coupled = TRUE;
+       }
+    }
+
+    if (ip_is_coupled == FALSE) {
+       ip_is_coupled = service_is_expensive(serviceID, services_info);
+    }
+    if (setup_rank != NULL || state_rank != NULL) {
+       /* rank assertion is set on the service */
+       Rank    setup_assertion;
+       Rank    state_assertion;
+       Boolean state_assertion_is_set = FALSE;
+
+       setup_assertion = PrimaryRankGetRankAssertion(setup_rank, NULL);
+       state_assertion = PrimaryRankGetRankAssertion(state_rank,
+                                                     &state_assertion_is_set);
+       if (setup_assertion > state_assertion) {
+           rank_assertion = setup_assertion;
+           rank_assertion_is_set = TRUE;
+       }
+       else if (state_assertion_is_set) {
+           rank_assertion = state_assertion;
+           rank_assertion_is_set = TRUE;
+       }
+    }
+
+    if (rank_assertion_is_set == FALSE) {
+       /* check for a rank assertion on the interface */
+       CFStringRef interface;
+
+       interface = services_info_get_interface(services_info, serviceID);
+       if (interface != NULL) {
+           CFNumberRef if_rank = NULL;
+
+           if (S_if_rank_dict != NULL) {
+               if_rank = CFDictionaryGetValue(S_if_rank_dict, interface);
+           }
+           rank_assertion
+               = InterfaceRankGetRankAssertion(if_rank,
+                                               &rank_assertion_is_set);
+           if (S_IPMonitor_debug & kDebugFlag1) {
+               my_log(LOG_DEBUG,
+                      "serviceID %@ interface %@ rank = %@",
+                      serviceID, interface, if_rank);
+           }
+       }
+    }
+
+
+    if (rank_assertion_is_set || ip_is_coupled) {
+       new_dict = CFDictionaryCreateMutable(NULL, 0,
+                                            &kCFTypeDictionaryKeyCallBacks,
+                                            &kCFTypeDictionaryValueCallBacks);
+       if (rank_assertion_is_set) {
+           CFNumberRef         new_rank;
+
+           new_rank = CFNumberCreate(NULL, kCFNumberSInt32Type,
+                                     (const void *)&rank_assertion);
+           CFDictionarySetValue(new_dict, kServiceOptionRankAssertion,
+                                new_rank);
+           CFRelease(new_rank);
+       }
+       if (ip_is_coupled) {
+           CFDictionarySetValue(new_dict, kIPIsCoupled, kCFBooleanTrue);
+       }
+    }
+    changed = service_dict_set(serviceID, kSCEntNetService, new_dict);
+    my_CFRelease(&new_dict);
+    return (changed);
+}
+
+static void
+add_service_keys(CFStringRef serviceID,
+                CFMutableArrayRef keys, CFMutableArrayRef patterns)
+{
+    int                        i;
+    CFStringRef                key;
+
+    if (CFEqual(serviceID, kSCCompAnyRegex)) {
+       keys = patterns;
+    }
+
+    for (i = 0; i < ENTITY_TYPES_COUNT; i++) {
+       key = setup_service_key(serviceID, *entityTypeNames[i]);
+       CFArrayAppendValue(keys, key);
+       CFRelease(key);
+       key = state_service_key(serviceID, *entityTypeNames[i]);
+       CFArrayAppendValue(keys, key);
+       CFRelease(key);
+    }
+
+    key = state_service_key(serviceID, kSCEntNetDHCP);
+    CFArrayAppendValue(patterns, key);
+    CFRelease(key);
+
+    key = setup_service_key(serviceID, NULL);
+    CFArrayAppendValue(patterns, key);
+    CFRelease(key);
+    key = state_service_key(serviceID, NULL);
+    CFArrayAppendValue(patterns, key);
+    CFRelease(key);
+
+    return;
+}
+
+static void
+add_transient_status_keys(CFStringRef service_id, CFMutableArrayRef patterns)
+{
+    int            i;
+
+    for (i = 0; i < countof(transientServiceInfo); i++) {
+       CFStringRef     pattern;
+
+       pattern = state_service_key(service_id,
+                                   *transientServiceInfo[i].entityName);
+       CFArrayAppendValue(patterns, pattern);
+       CFRelease(pattern);
+    }
+
+    return;
+}
+
+static const CFStringRef *reachabilitySetupKeys[] = {
+    &kSCEntNetPPP,
+    &kSCEntNetInterface,
+    &kSCEntNetIPv4,
+    &kSCEntNetIPv6,
+};
+
+
+static void
+add_reachability_patterns(CFMutableArrayRef patterns)
+{
+    int                i;
+
+    for (i = 0; i < countof(reachabilitySetupKeys); i++) {
+       CFStringRef pattern;
+       pattern = setup_service_key(kSCCompAnyRegex, *reachabilitySetupKeys[i]);
+       CFArrayAppendValue(patterns, pattern);
+       CFRelease(pattern);
+    }
+}
+
+
+static void
+add_vpn_pattern(CFMutableArrayRef patterns)
+{
+    CFStringRef        pattern;
+
+    pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetVPN);
+    CFArrayAppendValue(patterns, pattern);
+    CFRelease(pattern);
+}
+
+static void
+add_interface_link_pattern(CFMutableArrayRef patterns)
+{
+    CFStringRef        pattern;
+
+    pattern = interface_entity_key_copy(kSCCompAnyRegex, kSCEntNetLink);
+    CFArrayAppendValue(patterns, pattern);
+    CFRelease(pattern);
+}
+
+static CFDictionaryRef
+services_info_copy(SCDynamicStoreRef session, CFArrayRef service_list)
+{
+    CFIndex            count;
+    CFMutableArrayRef  get_keys;
+    CFMutableArrayRef  get_patterns;
+    CFDictionaryRef    info;
+    CFIndex            s;
+
+    count = CFArrayGetCount(service_list);
+    get_keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+    get_patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+
+    CFArrayAppendValue(get_keys, S_setup_global_ipv4);
+    CFArrayAppendValue(get_keys, S_multicast_resolvers);
+    CFArrayAppendValue(get_keys, S_private_resolvers);
+
+    for (s = 0; s < count; s++) {
+       CFStringRef     serviceID = CFArrayGetValueAtIndex(service_list, s);
+
+       add_service_keys(serviceID, get_keys, get_patterns);
+       add_transient_status_keys(serviceID, get_keys);
+    }
+
+    add_reachability_patterns(get_patterns);
+
+    add_vpn_pattern(get_patterns);
+
+    add_interface_link_pattern(get_patterns);
+
+    info = SCDynamicStoreCopyMultiple(session, get_keys, get_patterns);
+    my_CFRelease(&get_keys);
+    my_CFRelease(&get_patterns);
+    return (info);
+}
+
+#if    !TARGET_IPHONE_SIMULATOR
+
+static int
+multicast_route(int sockfd, int cmd)
+{
+    IPv4Route  route;
+
+    bzero(&route, sizeof(route));
+    route.dest.s_addr = htonl(INADDR_UNSPEC_GROUP);
+    route.mask.s_addr = htonl(IN_CLASSD_NET);
+    route.ifindex = lo0_ifindex();
+    return (IPv4RouteApply((RouteRef)&route, cmd, sockfd));
+}
+
+#endif /* !TARGET_IPHONE_SIMULATOR */
+
+#if    !TARGET_IPHONE_SIMULATOR
+
+static boolean_t
+set_ipv6_default_interface(IFIndex ifindex)
+{
+    struct in6_ndifreq ndifreq;
+    int                        sock;
+    boolean_t          success = FALSE;
+
+    bzero((char *)&ndifreq, sizeof(ndifreq));
+    strlcpy(ndifreq.ifname, kLoopbackInterface, sizeof(ndifreq.ifname));
+    if (ifindex != 0) {
+       ndifreq.ifindex = ifindex;
+    }
+    else {
+       ndifreq.ifindex = lo0_ifindex();
+    }
+    sock = inet6_dgram_socket();
+    if (sock == -1) {
+       my_log(LOG_ERR,
+              "IPMonitor: set_ipv6_default_interface: socket failed, %s",
+              strerror(errno));
+    }
+    else {
+       if (ioctl(sock, SIOCSDEFIFACE_IN6, (caddr_t)&ndifreq) == -1) {
+           my_log(LOG_ERR,
+                  "IPMonitor: ioctl(SIOCSDEFIFACE_IN6) failed, %s",
+                  strerror(errno));
+       }
+       else {
+           success = TRUE;
+       }
+       close(sock);
+    }
+    return (success);
+}
+
+#endif /* !TARGET_IPHONE_SIMULATOR */
+
+#if    !TARGET_OS_IPHONE
+static __inline__ void
+empty_dns()
+{
+    (void)unlink(VAR_RUN_RESOLV_CONF);
+}
+
+static void
+set_dns(CFArrayRef val_search_domains,
+       CFStringRef val_domain_name,
+       CFArrayRef val_servers,
+       CFArrayRef val_sortlist)
+{
+    FILE * f = fopen(VAR_RUN_RESOLV_CONF "-", "w");
+
+    /* publish new resolv.conf */
+    if (f) {
+       CFIndex i;
+       CFIndex n;
+
+       SCPrint(TRUE, f, CFSTR("#\n"));
+       SCPrint(TRUE, f, CFSTR("# Mac OS X Notice\n"));
+       SCPrint(TRUE, f, CFSTR("#\n"));
+       SCPrint(TRUE, f, CFSTR("# This file is not used by the host name and address resolution\n"));
+       SCPrint(TRUE, f, CFSTR("# or the DNS query routing mechanisms used by most processes on\n"));
+       SCPrint(TRUE, f, CFSTR("# this Mac OS X system.\n"));
+       SCPrint(TRUE, f, CFSTR("#\n"));
+       SCPrint(TRUE, f, CFSTR("# This file is automatically generated.\n"));
+       SCPrint(TRUE, f, CFSTR("#\n"));
+
+       if (isA_CFArray(val_search_domains)) {
+           SCPrint(TRUE, f, CFSTR("search"));
+           n = CFArrayGetCount(val_search_domains);
+           for (i = 0; i < n; i++) {
+               CFStringRef     domain;
+
+               domain = CFArrayGetValueAtIndex(val_search_domains, i);
+               if (isA_CFString(domain)) {
+                   SCPrint(TRUE, f, CFSTR(" %@"), domain);
+               }
+           }
+           SCPrint(TRUE, f, CFSTR("\n"));
+       }
+       else if (isA_CFString(val_domain_name)) {
+               SCPrint(TRUE, f, CFSTR("domain %@\n"), val_domain_name);
+       }
+
+       if (isA_CFArray(val_servers)) {
+           n = CFArrayGetCount(val_servers);
+           for (i = 0; i < n; i++) {
+               CFStringRef     nameserver;
+
+               nameserver = CFArrayGetValueAtIndex(val_servers, i);
+               if (isA_CFString(nameserver)) {
+                   SCPrint(TRUE, f, CFSTR("nameserver %@\n"), nameserver);
+               }
+           }
+       }
+
+       if (isA_CFArray(val_sortlist)) {
+           SCPrint(TRUE, f, CFSTR("sortlist"));
+           n = CFArrayGetCount(val_sortlist);
+           for (i = 0; i < n; i++) {
+               CFStringRef     address;
+
+               address = CFArrayGetValueAtIndex(val_sortlist, i);
+               if (isA_CFString(address)) {
+                   SCPrint(TRUE, f, CFSTR(" %@"), address);
+               }
+           }
+           SCPrint(TRUE, f, CFSTR("\n"));
+       }
+
+       fclose(f);
+       rename(VAR_RUN_RESOLV_CONF "-", VAR_RUN_RESOLV_CONF);
+    }
+    return;
+}
+#endif /* !TARGET_OS_IPHONE */
+
+static boolean_t
+service_get_ip_is_coupled(CFStringRef serviceID)
+{
+    CFDictionaryRef    dict;
+    boolean_t          ip_is_coupled = FALSE;
+
+    dict = service_dict_get(serviceID, kSCEntNetService);
+    if (dict != NULL) {
+       if (CFDictionaryContainsKey(dict, kIPIsCoupled)) {
+           ip_is_coupled = TRUE;
+       }
+    }
+    return (ip_is_coupled);
+}
+
+static CFStringRef
+my_CFStringCreateWithInAddr(struct in_addr ip)
+{
+    CFStringRef        str;
+
+    str = CFStringCreateWithFormat(NULL, NULL, CFSTR(IP_FORMAT), IP_LIST(&ip));
+    return (str);
+}
+
+static CFStringRef
+my_CFStringCreateWithIn6Addr(const struct in6_addr * ip)
+{
+    char       ntopbuf[INET6_ADDRSTRLEN];
+
+    (void)inet_ntop(AF_INET6, ip, ntopbuf, sizeof(ntopbuf));
+    return (CFStringCreateWithFormat(NULL, NULL, CFSTR("%s"), ntopbuf));
+}
+
+/*
+ * Function: update_ipv4
+ * Purpose:
+ *   Update the IPv4 configuration based on the latest information.
+ *   Publish the State:/Network/Global/IPv4 information, and update the
+ *   IPv4 routing table.
+ */
+static void
+update_ipv4(CFStringRef                primary,
+           IPv4RouteListRef    new_routelist,
+           keyChangeListRef    keys)
+{
+#if    !TARGET_IPHONE_SIMULATOR
+    int                sockfd;
+#endif /* !TARGET_IPHONE_SIMULATOR */
+
+    if (keys != NULL) {
+       if (new_routelist != NULL && primary != NULL) {
+           const char *                ifn_p = NULL;
+           char                        ifname[IFNAMSIZ];
+           IPv4RouteRef                r;
+           CFMutableDictionaryRef      dict = NULL;
+
+           dict = CFDictionaryCreateMutable(NULL, 0,
+                                            &kCFTypeDictionaryKeyCallBacks,
+                                            &kCFTypeDictionaryValueCallBacks);
+           /* the first entry is the default route */
+           r = new_routelist->list;
+           if (r->gateway.s_addr != 0) {
+               CFStringRef             str;
+
+               str = my_CFStringCreateWithInAddr(r->gateway);
+               CFDictionarySetValue(dict, kSCPropNetIPv4Router, str);
+               CFRelease(str);
+           }
+           ifn_p = my_if_indextoname(r->ifindex, ifname);
+           if (ifn_p != NULL) {
+               CFStringRef             ifname_cf;
+
+               ifname_cf = CFStringCreateWithCString(NULL,
+                                                     ifn_p,
+                                                     kCFStringEncodingASCII);
+               if (ifname_cf != NULL) {
+                   CFDictionarySetValue(dict,
+                                        kSCDynamicStorePropNetPrimaryInterface,
+                                        ifname_cf);
+                   CFRelease(ifname_cf);
+               }
+           }
+           CFDictionarySetValue(dict, kSCDynamicStorePropNetPrimaryService,
+                                primary);
+           keyChangeListSetValue(keys, S_state_global_ipv4, dict);
+           CFRelease(dict);
+       }
+       else {
+           keyChangeListRemoveValue(keys, S_state_global_ipv4);
+       }
+    }
+
+#if    !TARGET_IPHONE_SIMULATOR
+    sockfd = open_routing_socket();
+    if (sockfd != -1) {
+       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+           if (S_ipv4_routelist == NULL) {
+               my_log(LOG_DEBUG, "Old Routes = <none>");
+           }
+           else {
+               my_log(LOG_DEBUG, "Old Routes = ");
+               IPv4RouteListLog(LOG_DEBUG, S_ipv4_routelist);
+           }
+           if (new_routelist == NULL) {
+               my_log(LOG_DEBUG, "New Routes = <none>");
+           }
+           else {
+               my_log(LOG_DEBUG, "New Routes = ");
+               IPv4RouteListLog(LOG_DEBUG, new_routelist);
+           }
+       }
+       /* go through routelist and bind any unbound routes */
+       IPv4RouteListFinalize(new_routelist);
+       IPv4RouteListApply(S_ipv4_routelist, new_routelist, sockfd);
+       if (new_routelist != NULL) {
+           (void)multicast_route(sockfd, RTM_DELETE);
+       }
+       else {
+           (void)multicast_route(sockfd, RTM_ADD);
+       }
+       close(sockfd);
+    }
+    if (S_ipv4_routelist != NULL) {
+       free(S_ipv4_routelist);
+    }
+    S_ipv4_routelist = new_routelist;
+#else  /* !TARGET_IPHONE_SIMULATOR */
+    if (new_routelist != NULL) {
+       free(new_routelist);
+    }
+#endif /* !TARGET_IPHONE_SIMULATOR */
+
+    return;
+}
+
+/*
+ * Function: update_ipv6
+ * Purpose:
+ *   Update the IPv6 configuration based on the latest information.
+ *   Publish the State:/Network/Global/IPv6 information, and update the
+ *   IPv6 routing table.
+ */
+static void
+update_ipv6(CFStringRef                primary,
+           IPv6RouteListRef    new_routelist,
+           keyChangeListRef    keys)
+{
+#if    !TARGET_IPHONE_SIMULATOR
+    int                sockfd;
+#endif /* !TARGET_IPHONE_SIMULATOR */
+
+    if (keys != NULL) {
+       if (new_routelist != NULL && primary != NULL) {
+           const char *                ifn_p = NULL;
+           char                        ifname[IFNAMSIZ];
+           IPv6RouteRef                r;
+           CFMutableDictionaryRef      dict = NULL;
+
+           dict = CFDictionaryCreateMutable(NULL, 0,
+                                            &kCFTypeDictionaryKeyCallBacks,
+                                            &kCFTypeDictionaryValueCallBacks);
+           /* the first entry is the default route */
+           r = new_routelist->list;
+           if ((r->flags & kRouteFlagsHasGateway) != 0) {
+               CFStringRef             router;
+
+               router = my_CFStringCreateWithIn6Addr(&r->gateway);
+               CFDictionarySetValue(dict, kSCPropNetIPv6Router, router);
+               CFRelease(router);
+           }
+           ifn_p = my_if_indextoname(r->ifindex, ifname);
+           if (ifn_p != NULL) {
+               CFStringRef             ifname_cf;
+
+               ifname_cf = CFStringCreateWithCString(NULL,
+                                                     ifn_p,
+                                                     kCFStringEncodingASCII);
+               if (ifname_cf != NULL) {
+                   CFDictionarySetValue(dict,
+                                        kSCDynamicStorePropNetPrimaryInterface,
+                                        ifname_cf);
+                   CFRelease(ifname_cf);
+               }
+           }
+           CFDictionarySetValue(dict, kSCDynamicStorePropNetPrimaryService,
+                                primary);
+           keyChangeListSetValue(keys, S_state_global_ipv6, dict);
+           CFRelease(dict);
+#if    !TARGET_IPHONE_SIMULATOR
+           if (S_scopedroute_v6) {
+               set_ipv6_default_interface(r->ifindex);
+           }
+#endif /* !TARGET_IPHONE_SIMULATOR */
+       }
+       else {
 #if    !TARGET_IPHONE_SIMULATOR
-       CFArrayRef              addrs;
+           if (S_scopedroute_v6) {
+               set_ipv6_default_interface(0);
+           }
 #endif /* !TARGET_IPHONE_SIMULATOR */
-       CFMutableDictionaryRef  dict = NULL;
-       CFStringRef             if_name = NULL;
+           keyChangeListRemoveValue(keys, S_state_global_ipv6);
+       }
+    }
+
 #if    !TARGET_IPHONE_SIMULATOR
-       char                    ifn[IFNAMSIZ] = { '\0' };
-       char *                  ifn_p = NULL;
-       boolean_t               is_direct = FALSE;
+    sockfd = open_routing_socket();
+    if (sockfd != -1) {
+       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+           if (S_ipv6_routelist == NULL) {
+               my_log(LOG_DEBUG, "Old Routes = <none>");
+           }
+           else {
+               my_log(LOG_DEBUG, "Old Routes = ");
+               IPv6RouteListLog(LOG_DEBUG, S_ipv6_routelist);
+           }
+           if (new_routelist == NULL) {
+               my_log(LOG_DEBUG, "New Routes = <none>");
+           }
+           else {
+               my_log(LOG_DEBUG, "New Routes = ");
+               IPv6RouteListLog(LOG_DEBUG, new_routelist);
+           }
+       }
+       /* go through routelist and bind any unbound routes */
+       IPv6RouteListFinalize(new_routelist);
+       IPv6RouteListApply(S_ipv6_routelist, new_routelist, sockfd);
+       close(sockfd);
+    }
+    if (S_ipv6_routelist != NULL) {
+       free(S_ipv6_routelist);
+    }
+    S_ipv6_routelist = new_routelist;
+#else  /* !TARGET_IPHONE_SIMULATOR */
+    if (new_routelist != NULL) {
+       free(new_routelist);
+    }
 #endif /* !TARGET_IPHONE_SIMULATOR */
-       CFStringRef             val_router = NULL;
 
-       dict = CFDictionaryCreateMutable(NULL, 0,
-                                        &kCFTypeDictionaryKeyCallBacks,
-                                        &kCFTypeDictionaryValueCallBacks);
+    return;
+}
+
+static Boolean
+update_dns(CFDictionaryRef     services_info,
+          CFStringRef          primary,
+          keyChangeListRef     keys)
+{
+    Boolean            changed = FALSE;
+    CFDictionaryRef    dict    = NULL;
+
+    if (primary != NULL) {
+       CFDictionaryRef service_dict;
+
+       service_dict = CFDictionaryGetValue(S_service_state_dict, primary);
+       if (service_dict != NULL) {
+           dict = CFDictionaryGetValue(service_dict, kSCEntNetDNS);
+       }
+    }
+
+    if (!_SC_CFEqual(S_dns_dict, dict)) {
+       if (dict == NULL) {
+#if    !TARGET_OS_IPHONE
+           empty_dns();
+#endif /* !TARGET_OS_IPHONE */
+           keyChangeListRemoveValue(keys, S_state_global_dns);
+       } else {
+           CFMutableDictionaryRef      new_dict;
+
+#if    !TARGET_OS_IPHONE
+           set_dns(CFDictionaryGetValue(dict, kSCPropNetDNSSearchDomains),
+                   CFDictionaryGetValue(dict, kSCPropNetDNSDomainName),
+                   CFDictionaryGetValue(dict, kSCPropNetDNSServerAddresses),
+                   CFDictionaryGetValue(dict, kSCPropNetDNSSortList));
+#endif /* !TARGET_OS_IPHONE */
+           new_dict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
+           CFDictionaryRemoveValue(new_dict, kSCPropInterfaceName);
+           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSupplementalMatchDomains);
+           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSupplementalMatchOrders);
+           CFDictionaryRemoveValue(new_dict, DNS_CONFIGURATION_SCOPED_QUERY_KEY);
+           keyChangeListSetValue(keys, S_state_global_dns, new_dict);
+           CFRelease(new_dict);
+       }
+       changed = TRUE;
+    }
+
+    if (dict != NULL) CFRetain(dict);
+    if (S_dns_dict != NULL) CFRelease(S_dns_dict);
+    S_dns_dict = dict;
+
+    return changed;
+}
+
+static Boolean
+update_dnsinfo(CFDictionaryRef services_info,
+              CFStringRef      primary,
+              keyChangeListRef keys,
+              CFArrayRef       service_order)
+{
+    Boolean            changed;
+    CFDictionaryRef    dict    = NULL;
+    CFArrayRef         multicastResolvers;
+    CFArrayRef         privateResolvers;
+
+    multicastResolvers = CFDictionaryGetValue(services_info, S_multicast_resolvers);
+    privateResolvers   = CFDictionaryGetValue(services_info, S_private_resolvers);
+
+    if (primary != NULL) {
+       CFDictionaryRef service_dict;
+
+       service_dict = CFDictionaryGetValue(S_service_state_dict, primary);
+       if (service_dict != NULL) {
+           dict = CFDictionaryGetValue(service_dict, kSCEntNetDNS);
+       }
+    }
+
+    changed = dns_configuration_set(dict,
+                                   S_service_state_dict,
+                                   service_order,
+                                   multicastResolvers,
+                                   privateResolvers);
+    if (changed) {
+       keyChangeListNotifyKey(keys, S_state_global_dns);
+    }
+    return changed;
+}
+
+static Boolean
+update_nwi(nwi_state_t state)
+{
+    unsigned char              signature[CC_SHA1_DIGEST_LENGTH];
+    static unsigned char       signature_last[CC_SHA1_DIGEST_LENGTH];
+
+    _nwi_state_signature(state, signature, sizeof(signature));
+    if (bcmp(signature, signature_last, sizeof(signature)) == 0) {
+       return FALSE;
+    }
+
+    // save [new] signature
+    bcopy(signature, signature_last, sizeof(signature));
+
+    // save [new] configuration
+    if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+       my_log(LOG_DEBUG, "Updating network information");
+       S_nwi_state_dump(state);
+    }
+    if (_nwi_state_store(state) == FALSE) {
+       my_log(LOG_ERR, "Notifying nwi_state_store failed");
+    }
+
+    return TRUE;
+}
+
+static Boolean
+update_proxies(CFDictionaryRef services_info,
+              CFStringRef      primary,
+              keyChangeListRef keys,
+              CFArrayRef       service_order)
+{
+    Boolean        changed     = FALSE;
+    CFDictionaryRef dict       = NULL;
+    CFDictionaryRef new_dict;
+
+    if (primary != NULL) {
+       CFDictionaryRef service_dict;
+
+       service_dict = CFDictionaryGetValue(S_service_state_dict, primary);
+       if (service_dict != NULL) {
+           dict = CFDictionaryGetValue(service_dict, kSCEntNetProxies);
+       }
+    }
+
+    new_dict = proxy_configuration_update(dict,
+                                         S_service_state_dict,
+                                         service_order,
+                                         services_info);
+    if (!_SC_CFEqual(S_proxies_dict, new_dict)) {
+       if (new_dict == NULL) {
+           keyChangeListRemoveValue(keys, S_state_global_proxies);
+       } else {
+           keyChangeListSetValue(keys, S_state_global_proxies, new_dict);
+       }
+       changed = TRUE;
+    }
+
+    if (S_proxies_dict != NULL) CFRelease(S_proxies_dict);
+    S_proxies_dict = new_dict;
+
+    return changed;
+}
+
+#if    !TARGET_OS_IPHONE
+static Boolean
+update_smb(CFDictionaryRef     services_info,
+          CFStringRef          primary,
+          keyChangeListRef     keys)
+{
+    Boolean            changed = FALSE;
+    CFDictionaryRef    dict    = NULL;
+
+    if (primary != NULL) {
+       CFDictionaryRef service_dict;
+
+       service_dict = CFDictionaryGetValue(S_service_state_dict, primary);
+       if (service_dict != NULL) {
+           dict = CFDictionaryGetValue(service_dict, kSCEntNetSMB);
+       }
+    }
+
+    if (!_SC_CFEqual(S_smb_dict, dict)) {
+       if (dict == NULL) {
+           keyChangeListRemoveValue(keys, S_state_global_smb);
+       } else {
+           keyChangeListSetValue(keys, S_state_global_smb, dict);
+       }
+       changed = TRUE;
+    }
+
+    if (dict != NULL) CFRetain(dict);
+    if (S_smb_dict != NULL) CFRelease(S_smb_dict);
+    S_smb_dict = dict;
+
+    return changed;
+}
+#endif /* !TARGET_OS_IPHONE */
+
+static Rank
+get_service_rank(CFArrayRef order, int n_order, CFStringRef serviceID)
+{
+    CFIndex            i;
+    Rank               rank = kRankIndexMask;
+
+    if (serviceID != NULL && order != NULL && n_order > 0) {
+       for (i = 0; i < n_order; i++) {
+           CFStringRef s = isA_CFString(CFArrayGetValueAtIndex(order, i));
+
+           if (s == NULL) {
+               continue;
+           }
+           if (CFEqual(serviceID, s)) {
+               rank = (Rank)i + 1;
+               break;
+           }
+       }
+    }
+    return (rank);
+}
+
+/**
+ ** Service election:
+ **/
+/*
+ * Function: rank_dict_get_service_rank
+ * Purpose:
+ *   Retrieve the service rank in the given dictionary.
+ */
+static Rank
+rank_dict_get_service_rank(CFDictionaryRef rank_dict, CFStringRef serviceID)
+{
+    CFNumberRef                rank;
+    Rank               rank_val;
+
+    rank_val = RankMake(kRankIndexMask, kRankAssertionDefault);
+    rank = CFDictionaryGetValue(rank_dict, serviceID);
+    if (rank != NULL) {
+       CFNumberGetValue(rank, kCFNumberSInt32Type, &rank_val);
+    }
+    return (rank_val);
+}
+
+/*
+ * Function: rank_dict_set_service_rank
+ * Purpose:
+ *   Save the results of ranking the service so we can look it up later without
+ *   repeating all of the ranking code.
+ */
+static void
+rank_dict_set_service_rank(CFMutableDictionaryRef rank_dict,
+                          CFStringRef serviceID, Rank rank_val)
+{
+    CFNumberRef                rank;
+
+    rank = CFNumberCreate(NULL, kCFNumberSInt32Type, (const void *)&rank_val);
+    if (rank != NULL) {
+       CFDictionarySetValue(rank_dict, serviceID, rank);
+       CFRelease(rank);
+    }
+    return;
+}
+
+static const CFStringRef *transientInterfaceEntityNames[] = {
+    &kSCEntNetPPP,
+};
+
+
+static void
+CollectTransientServices(const void * key,
+                        const void * value,
+                        void * context)
+{
+    int                        i;
+    CFStringRef                service = key;
+    CFMutableArrayRef  vif_setup_keys = context;
+
+    /* This service is either a vpn type service or a comm center service */
+    if (!CFStringHasPrefix(service, kSCDynamicStoreDomainSetup)) {
+       return;
+    }
+
+    for (i = 0; i < countof(transientInterfaceEntityNames); i++) {
+       if (CFStringHasSuffix(service, *transientInterfaceEntityNames[i])) {
+           CFArrayAppendValue(vif_setup_keys, service);
+           break;
+       }
+    }
+
+    return;
+}
+
+
+static SCNetworkReachabilityFlags
+GetReachabilityFlagsFromVPN(CFDictionaryRef services_info,
+                           CFStringRef     service_id,
+                           CFStringRef     entity,
+                           CFStringRef     vpn_setup_key)
+{
+    CFStringRef                        key;
+    CFDictionaryRef            dict;
+    SCNetworkReachabilityFlags flags = 0;
+
+
+    key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                     kSCDynamicStoreDomainSetup,
+                                                     service_id,
+                                                     kSCEntNetInterface);
+    dict = CFDictionaryGetValue(services_info, key);
+    CFRelease(key);
+
+    if (isA_CFDictionary(dict)
+       && CFDictionaryContainsKey(dict, kSCPropNetInterfaceDeviceName)) {
+
+       flags = (kSCNetworkReachabilityFlagsReachable
+               | kSCNetworkReachabilityFlagsTransientConnection
+               | kSCNetworkReachabilityFlagsConnectionRequired);
+
+       if (CFEqual(entity, kSCEntNetPPP)) {
+           CFNumberRef num;
+           CFDictionaryRef p_dict = CFDictionaryGetValue(services_info, vpn_setup_key);
+
+           if (!isA_CFDictionary(p_dict)) {
+               return (flags);
+           }
+
+           // get PPP dial-on-traffic status
+           num = CFDictionaryGetValue(p_dict, kSCPropNetPPPDialOnDemand);
+           if (isA_CFNumber(num)) {
+               int32_t ppp_demand;
+
+               if (CFNumberGetValue(num, kCFNumberSInt32Type, &ppp_demand)) {
+                   if (ppp_demand) {
+                       flags |= kSCNetworkReachabilityFlagsConnectionOnTraffic;
+                   }
+               }
+           }
+       }
+    }
+    return (flags);
+}
+
+static Boolean
+S_dict_get_boolean(CFDictionaryRef dict, CFStringRef key, Boolean def_value)
+{
+    Boolean            ret = def_value;
+
+    if (dict != NULL) {
+       CFBooleanRef    val;
+
+       val = CFDictionaryGetValue(dict, key);
+       if (isA_CFBoolean(val) != NULL) {
+           ret = CFBooleanGetValue(val);
+       }
+    }
+    return (ret);
+}
+
+
+static void
+GetReachabilityFlagsFromTransientServices(CFDictionaryRef services_info,
+                                         SCNetworkReachabilityFlags *reach_flags_v4,
+                                         SCNetworkReachabilityFlags *reach_flags_v6)
+{
+    CFIndex            i;
+    CFIndex            count;
+    CFMutableArrayRef  vif_setup_keys;
+
+    vif_setup_keys = CFArrayCreateMutable(NULL,
+                                         0,
+                                         &kCFTypeArrayCallBacks);
+    CFDictionaryApplyFunction(services_info, CollectTransientServices,
+                             vif_setup_keys);
+    count = CFArrayGetCount(vif_setup_keys);
+    for (i = 0; i < count; i++) {
+       CFArrayRef          components = NULL;
+       CFStringRef         entity;
+       CFStringRef         service_id;
+       CFStringRef         vif_setup_key;
+
+       vif_setup_key = CFArrayGetValueAtIndex(vif_setup_keys, i);
+
+       /*
+        * setup key in the following format:
+        * Setup:/Network/Service/<Service ID>/<Entity>
+        */
+       components = CFStringCreateArrayBySeparatingStrings(NULL, vif_setup_key, CFSTR("/"));
+
+       if (CFArrayGetCount(components) != 5) {
+           // invalid Setup key encountered
+           goto skip;
+       }
+
+       /* service id is the 3rd element */
+       service_id = CFArrayGetValueAtIndex(components, 3);
+
+       /* entity id is the 4th element */
+       entity = CFArrayGetValueAtIndex(components, 4);
+
+
+       if (CFEqual(entity, kSCEntNetPPP)) {
+           SCNetworkReachabilityFlags  flags;
+           CFStringRef                 key;
 
-#if    !TARGET_IPHONE_SIMULATOR
-       addrs = CFDictionaryGetValue(ipv6_dict,
-                                    kSCPropNetIPv6Addresses);
-#endif /* !TARGET_IPHONE_SIMULATOR */
+           flags = GetReachabilityFlagsFromVPN(services_info,
+                                               service_id,
+                                               entity,
+                                               vif_setup_key);
 
-       val_router = CFDictionaryGetValue(ipv6_dict, kSCPropNetIPv6Router);
-       if (val_router != NULL) {
-#if    !TARGET_IPHONE_SIMULATOR
-           is_direct = router_is_our_ipv6_address(val_router, addrs);
-#endif /* !TARGET_IPHONE_SIMULATOR */
-           /* no router if router is one of our IP addresses */
-           CFDictionarySetValue(dict, kSCPropNetIPv6Router,
-                                val_router);
-       }
-#if    !TARGET_IPHONE_SIMULATOR
-       else {
-           val_router = CFArrayGetValueAtIndex(addrs, 0);
-           is_direct = TRUE;
-       }
-#endif /* !TARGET_IPHONE_SIMULATOR */
-       if_name = CFDictionaryGetValue(ipv6_dict, kSCPropInterfaceName);
-       if (if_name) {
-           CFDictionarySetValue(dict,
-                                kSCDynamicStorePropNetPrimaryInterface,
-                                if_name);
-#if    !TARGET_IPHONE_SIMULATOR
-           if (CFStringGetCString(if_name, ifn, sizeof(ifn),
-                                  kCFStringEncodingASCII)) {
-               ifn_p = ifn;
+           /* Check for the v4 reachability flags */
+           key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                             kSCDynamicStoreDomainSetup,
+                                                             service_id,
+                                                             kSCEntNetIPv4);
+
+           if (CFDictionaryContainsKey(services_info, key)) {
+               *reach_flags_v4 |= flags;
+               my_log(LOG_DEBUG, "Service %@ setting ipv4 reach flags: %d", service_id, *reach_flags_v4);
            }
-#endif /* !TARGET_IPHONE_SIMULATOR */
-       }
-       CFDictionarySetValue(dict, kSCDynamicStorePropNetPrimaryService,
-                            primary);
-       keyChangeListSetValue(keys, S_state_global_ipv6, dict);
-       CFRelease(dict);
 
-#if    !TARGET_IPHONE_SIMULATOR
-#ifdef RTF_IFSCOPE
-       if (S_scopedroute_v6) {
-           set_ipv6_default_interface(ifn_p);
-       } else
-#endif /* RTF_IFSCOPE */
-       { /* route add default ... */
-           struct in6_addr     router;
+           CFRelease(key);
+
+           /* Check for the v6 reachability flags */
+           key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                             kSCDynamicStoreDomainSetup,
+                                                             service_id,
+                                                             kSCEntNetIPv6);
+
+           if (CFDictionaryContainsKey(services_info, key)) {
+               *reach_flags_v6 |= flags;
+               my_log(LOG_DEBUG, "Service %@ setting ipv6 reach flags: %d", service_id, *reach_flags_v6);
+           }
+           CFRelease(key);
 
-           (void)cfstring_to_ip6(val_router, &router);
-           set_ipv6_router(&router, ifn_p, is_direct);
+           if (flags != 0) {
+               if (components != NULL) {
+                   CFRelease(components);
+               }
+               goto done;
+           }
        }
-#endif /* !TARGET_IPHONE_SIMULATOR */
-    }
-    else {
-       keyChangeListRemoveValue(keys, S_state_global_ipv6);
-#if    !TARGET_IPHONE_SIMULATOR
-#ifdef RTF_IFSCOPE
-       if (S_scopedroute_v6) {
-           set_ipv6_default_interface(NULL);
-       } else
-#endif /* RTF_IFSCOPE */
-       { /* route delete default ... */
-           set_ipv6_router(NULL, NULL, FALSE);
+skip:
+       if (components != NULL) {
+           CFRelease(components);
        }
-#endif /* !TARGET_IPHONE_SIMULATOR */
     }
+done:
+    CFRelease(vif_setup_keys);
     return;
 }
 
-static Boolean
-update_dns(CFDictionaryRef     services_info,
-          CFStringRef          primary,
-          keyChangeListRef     keys)
+static SCNetworkReachabilityFlags
+GetReachFlagsFromStatus(CFStringRef entity, int status)
 {
-    Boolean            changed = FALSE;
-    CFDictionaryRef    dict    = NULL;
-
-    if (primary != NULL) {
-       CFDictionaryRef service_dict;
+    SCNetworkReachabilityFlags flags = 0;
 
-       service_dict = CFDictionaryGetValue(S_service_state_dict, primary);
-       if (service_dict != NULL) {
-           dict = CFDictionaryGetValue(service_dict, kSCEntNetDNS);
+    if (CFEqual(entity, kSCEntNetPPP)) {
+       switch (status) {
+           case PPP_RUNNING :
+               /* if we're really UP and RUNNING */
+               break;
+           case PPP_ONHOLD :
+               /* if we're effectively UP and RUNNING */
+               break;
+           case PPP_IDLE :
+               /* if we're not connected at all */
+               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
+               break;
+           case PPP_STATERESERVED :
+               // if we're not connected at all
+               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
+               break;
+           default :
+               /* if we're in the process of [dis]connecting */
+               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
+               break;
+       }
+    }
+    else if (CFEqual(entity, kSCEntNetIPSec)) {
+       switch (status) {
+           case IPSEC_RUNNING :
+               /* if we're really UP and RUNNING */
+               break;
+           case IPSEC_IDLE :
+               /* if we're not connected at all */
+               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
+               break;
+           default :
+               /* if we're in the process of [dis]connecting */
+               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
+               break;
+       }
+    }
+    else if  (CFEqual(entity, kSCEntNetVPN)) {
+       switch (status) {
+           case VPN_RUNNING :
+               /* if we're really UP and RUNNING */
+               break;
+           case VPN_IDLE :
+           case VPN_LOADING :
+           case VPN_LOADED :
+           case VPN_UNLOADING :
+               /* if we're not connected at all */
+               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
+               break;
+           default :
+               /* if we're in the process of [dis]connecting */
+               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
+               break;
        }
     }
+    return (flags);
+}
 
-    if (!_SC_CFEqual(S_dns_dict, dict)) {
-       if (dict == NULL) {
-#if    !TARGET_OS_IPHONE
-           empty_dns();
-#endif /* !TARGET_OS_IPHONE */
-           keyChangeListRemoveValue(keys, S_state_global_dns);
-       } else {
-           CFMutableDictionaryRef      new_dict;
+static void
+VPNAttributesGet(CFStringRef               service_id,
+                CFDictionaryRef            services_info,
+                SCNetworkReachabilityFlags *flags,
+                CFStringRef                *server_address,
+                int                        af)
+{
+    int                                i;
+    CFDictionaryRef            entity_dict;
+    CFNumberRef                        num;
+    CFDictionaryRef            p_state = NULL;
+    int                                status = 0;
+    CFStringRef                transient_entity = NULL;
 
-#if    !TARGET_OS_IPHONE
-           set_dns(CFDictionaryGetValue(dict, kSCPropNetDNSSearchDomains),
-                   CFDictionaryGetValue(dict, kSCPropNetDNSDomainName),
-                   CFDictionaryGetValue(dict, kSCPropNetDNSServerAddresses),
-                   CFDictionaryGetValue(dict, kSCPropNetDNSSortList));
-#endif /* !TARGET_OS_IPHONE */
-           new_dict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
-           CFDictionaryRemoveValue(new_dict, kSCPropInterfaceName);
-           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSupplementalMatchDomains);
-           CFDictionaryRemoveValue(new_dict, kSCPropNetDNSSupplementalMatchOrders);
-           CFDictionaryRemoveValue(new_dict, DNS_CONFIGURATION_SCOPED_QUERY_KEY);
-           keyChangeListSetValue(keys, S_state_global_dns, new_dict);
-           CFRelease(new_dict);
-       }
-       changed = TRUE;
+    if (af == AF_INET) {
+       entity_dict = service_dict_get(service_id, kSCEntNetIPv4);
+    }
+    else {
+       entity_dict = service_dict_get(service_id, kSCEntNetIPv6);
+    }
+    entity_dict = ipdict_get_service(entity_dict);
+    if (entity_dict == NULL) {
+       return;
     }
 
-    if (dict != NULL) CFRetain(dict);
-    if (S_dns_dict != NULL) CFRelease(S_dns_dict);
-    S_dns_dict = dict;
+    for (i = 0; i < countof(transientServiceInfo); i++) {
+       CFStringRef     entity = *transientServiceInfo[i].entityName;
 
-    return changed;
-}
+       p_state = service_dict_get(service_id, entity);
 
-static Boolean
-update_dnsinfo(CFDictionaryRef services_info,
-              CFStringRef      primary,
-              keyChangeListRef keys,
-              CFArrayRef       service_order)
-{
-    Boolean            changed;
-    CFDictionaryRef    dict    = NULL;
-    CFArrayRef         multicastResolvers;
-    CFArrayRef         privateResolvers;
+       /* ensure that this is a VPN Type service */
+       if (isA_CFDictionary(p_state)) {
+           transient_entity = entity;
+           break;
+       }
+    }
 
-    multicastResolvers = CFDictionaryGetValue(services_info, S_multicast_resolvers);
-    privateResolvers   = CFDictionaryGetValue(services_info, S_private_resolvers);
+    /* Did we find a vpn type service?  If not, we are done.*/
+    if (transient_entity == NULL) {
+       return;
+    }
 
-    if (primary != NULL) {
-       CFDictionaryRef service_dict;
+    *flags |= (kSCNetworkReachabilityFlagsReachable
+              | kSCNetworkReachabilityFlagsTransientConnection);
 
-       service_dict = CFDictionaryGetValue(S_service_state_dict, primary);
-       if (service_dict != NULL) {
-           dict = CFDictionaryGetValue(service_dict, kSCEntNetDNS);
+    /* Get the Server Address */
+    if (server_address != NULL) {
+       *server_address = CFDictionaryGetValue(entity_dict,
+                                              CFSTR("ServerAddress"));
+       *server_address = isA_CFString(*server_address);
+       if (*server_address != NULL) {
+           CFRetain(*server_address);
        }
     }
 
-    changed = dns_configuration_set(dict,
-                                   S_service_state_dict,
-                                   service_order,
-                                   multicastResolvers,
-                                   privateResolvers);
-    if (changed) {
-       keyChangeListNotifyKey(keys, S_state_global_dns);
+    /* get status */
+    if (!CFDictionaryGetValueIfPresent(p_state,
+                                      kSCPropNetVPNStatus,             // IPSecStatus, PPPStatus, VPNStatus
+                                      (const void **)&num) ||
+       !isA_CFNumber(num) ||
+       !CFNumberGetValue(num, kCFNumberSInt32Type, &status)) {
+       return;
     }
-    return changed;
+
+    *flags |= GetReachFlagsFromStatus(transient_entity, status);
+    if (CFEqual(transient_entity, kSCEntNetPPP)) {
+       CFStringRef     key;
+       CFDictionaryRef p_setup;
+       int             ppp_demand;
+
+       key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                         kSCDynamicStoreDomainSetup,
+                                                         service_id,
+                                                         kSCEntNetPPP);
+       p_setup = CFDictionaryGetValue(services_info, key);
+       CFRelease(key);
+
+       /* get dial-on-traffic status */
+       if (isA_CFDictionary(p_setup) &&
+           CFDictionaryGetValueIfPresent(p_setup,
+                                         kSCPropNetPPPDialOnDemand,
+                                         (const void **)&num) &&
+           isA_CFNumber(num) &&
+           CFNumberGetValue(num, kCFNumberSInt32Type, &ppp_demand) &&
+           (ppp_demand != 0)) {
+           *flags |= kSCNetworkReachabilityFlagsConnectionOnTraffic;
+           if (status == PPP_IDLE) {
+               *flags |= kSCNetworkReachabilityFlagsInterventionRequired;
+           }
+       }
+    }
+    return;
 }
 
-static Boolean
-update_nwi(nwi_state_t state)
+
+typedef struct ElectionInfo {
+    int                                af;
+    CFStringRef                        entity;
+    int                                n_services;
+    CFArrayRef                 order;
+    int                                n_order;
+    ElectionResultsRef         results;
+    CFMutableDictionaryRef     rank_dict;
+} ElectionInfo, * ElectionInfoRef;
+
+typedef CFDictionaryApplierFunction    ElectionFuncRef;
+
+static void
+CandidateRelease(CandidateRef candidate)
 {
-    unsigned char              signature[CC_SHA1_DIGEST_LENGTH];
-    static unsigned char       signature_last[CC_SHA1_DIGEST_LENGTH];
+    my_CFRelease(&candidate->serviceID);
+    my_CFRelease(&candidate->if_name);
+    my_CFRelease(&candidate->signature);
+    return;
+}
 
-    _nwi_state_signature(state, signature, sizeof(signature));
-    if (bcmp(signature, signature_last, sizeof(signature)) == 0) {
-       return FALSE;
+static void
+CandidateCopy(CandidateRef dest, CandidateRef src)
+{
+    *dest = *src;
+    if (dest->serviceID) {
+       CFRetain(dest->serviceID);
     }
-
-    // save [new] signature
-    bcopy(signature, signature_last, sizeof(signature));
-
-    // save [new] configuration
-    if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-       my_log(LOG_DEBUG, "Updating network information");
-       S_nwi_state_dump(state);
+    if (dest->if_name) {
+       CFRetain(dest->if_name);
     }
-    if (_nwi_state_store(state) == FALSE) {
-       my_log(LOG_ERR, "Notifying nwi_state_store failed");
+    if(dest->signature) {
+       CFRetain(dest->signature);
     }
-
-    return TRUE;
+    return;
 }
 
-static Boolean
-update_proxies(CFDictionaryRef services_info,
-              CFStringRef      primary,
-              keyChangeListRef keys,
-              CFArrayRef       service_order)
+static ElectionResultsRef
+ElectionResultsAlloc(int af, int size)
 {
-    Boolean        changed     = FALSE;
-    CFDictionaryRef dict       = NULL;
-    CFDictionaryRef new_dict;
+    ElectionResultsRef results;
 
-    if (primary != NULL) {
-       CFDictionaryRef service_dict;
+    results = (ElectionResultsRef)malloc(ElectionResultsComputeSize(size));
+    results->af = af;
+    results->count = 0;
+    results->size = size;
+    return (results);
+}
 
-       service_dict = CFDictionaryGetValue(S_service_state_dict, primary);
-       if (service_dict != NULL) {
-           dict = CFDictionaryGetValue(service_dict, kSCEntNetProxies);
-       }
-    }
+static void
+ElectionResultsRelease(ElectionResultsRef results)
+{
+    int                        i;
+    CandidateRef       scan;
 
-    new_dict = proxy_configuration_update(dict,
-                                         S_service_state_dict,
-                                         service_order,
-                                         services_info);
-    if (!_SC_CFEqual(S_proxies_dict, new_dict)) {
-       if (new_dict == NULL) {
-           keyChangeListRemoveValue(keys, S_state_global_proxies);
-       } else {
-           keyChangeListSetValue(keys, S_state_global_proxies, new_dict);
-       }
-       changed = TRUE;
+    for (i = 0, scan = results->candidates;
+        i < results->count;
+        i++, scan++) {
+       CandidateRelease(scan);
     }
-
-    if (S_proxies_dict != NULL) CFRelease(S_proxies_dict);
-    S_proxies_dict = new_dict;
-
-    return changed;
+    free(results);
+    return;
 }
 
-#if    !TARGET_OS_IPHONE
-static Boolean
-update_smb(CFDictionaryRef     services_info,
-          CFStringRef          primary,
-          keyChangeListRef     keys)
+static void
+ElectionResultsLog(int level, ElectionResultsRef results, const char * prefix)
 {
-    Boolean            changed = FALSE;
-    CFDictionaryRef    dict    = NULL;
-
-    if (primary != NULL) {
-       CFDictionaryRef service_dict;
+    int                        i;
+    CandidateRef       scan;
 
-       service_dict = CFDictionaryGetValue(S_service_state_dict, primary);
-       if (service_dict != NULL) {
-           dict = CFDictionaryGetValue(service_dict, kSCEntNetSMB);
-       }
+    if (results == NULL) {
+       my_log(level, "%s: no candidates", prefix);
+       return;
     }
+    my_log(level, "%s: %d candidates", prefix, results->count);
+    for (i = 0, scan = results->candidates;
+        i < results->count;
+        i++, scan++) {
+       char    ntopbuf[INET6_ADDRSTRLEN];
 
-    if (!_SC_CFEqual(S_smb_dict, dict)) {
-       if (dict == NULL) {
-           keyChangeListRemoveValue(keys, S_state_global_smb);
-       } else {
-           keyChangeListSetValue(keys, S_state_global_smb, dict);
-       }
-       changed = TRUE;
+       (void)inet_ntop(results->af, &scan->addr, ntopbuf, sizeof(ntopbuf));
+       my_log(level, "%d. %@ serviceID=%@ addr=%s rank=0x%x",
+              i, scan->if_name, scan->serviceID, ntopbuf, scan->rank);
     }
-
-    if (dict != NULL) CFRetain(dict);
-    if (S_smb_dict != NULL) CFRelease(S_smb_dict);
-    S_smb_dict = dict;
-
-    return changed;
+    return;
 }
-#endif /* !TARGET_OS_IPHONE */
 
-static Rank
-get_service_rank(CFArrayRef order, int n_order, CFStringRef serviceID)
+/*
+ * Function: ElectionResultsAddCandidate
+ * Purpose:
+ *   Add the candidate into the election results. Find the insertion point
+ *   by comparing the rank of the candidate with existing entries.
+ */
+static void
+ElectionResultsAddCandidate(ElectionResultsRef results, CandidateRef candidate)
 {
     CFIndex            i;
-    Rank               rank = kRankIndexMask;
+    CFIndex            where;
 
-    if (serviceID != NULL && order != NULL && n_order > 0) {
-       for (i = 0; i < n_order; i++) {
-           CFStringRef s = isA_CFString(CFArrayGetValueAtIndex(order, i));
+    if (results->count == results->size) {
+       /* this should not happen */
+       my_log(LOG_NOTICE, "can't fit another candidate");
+       return;
+    }
 
-           if (s == NULL) {
-               continue;
-           }
-           if (CFEqual(serviceID, s)) {
-               rank = i + 1;
-               break;
-           }
+    /* find the insertion point */
+    where = kCFNotFound;
+    for (i = 0; i < results->count; i++) {
+       CandidateRef    this_candidate = results->candidates + i;
+
+       if (candidate->rank < this_candidate->rank) {
+           where = i;
+           break;
        }
     }
-    return (rank);
+    /* add it to the end */
+    if (where == kCFNotFound) {
+       CandidateCopy(results->candidates + results->count, candidate);
+       results->count++;
+       return;
+    }
+    /* slide existing entries over */
+    for (i = results->count; i > where; i--) {
+       results->candidates[i] = results->candidates[i - 1];
+    }
+    /* insert element */
+    CandidateCopy(results->candidates + where, candidate);
+    results->count++;
+    return;
 }
 
-/**
- ** Service election:
- **/
+static void
+elect_ip(const void * key, const void * value, void * context);
+
 /*
- * Function: rank_dict_get_service_rank
+ * Function: ElectionResultsCopy
  * Purpose:
- *   Retrieve the service rank in the given dictionary.
+ *   Visit all of the services and invoke the protocol-specific election
+ *   function.  Return the results of the election.
  */
-static Rank
-rank_dict_get_service_rank(CFDictionaryRef rank_dict, CFStringRef serviceID)
+static ElectionResultsRef
+ElectionResultsCopy(int af, CFArrayRef order, int n_order)
 {
-    CFNumberRef                rank;
-    Rank               rank_val = RankMake(kRankIndexMask, kRankAssertionDefault);
+    int                        count;
+    ElectionInfo       info;
 
-    rank = CFDictionaryGetValue(rank_dict, serviceID);
-    if (rank != NULL) {
-       CFNumberGetValue(rank, kCFNumberSInt32Type, &rank_val);
+    count = (int)CFDictionaryGetCount(S_service_state_dict);
+    if (count == 0) {
+       return (NULL);
     }
-    return (rank_val);
+    info.af = af;
+    if (af == AF_INET) {
+       info.entity = kSCEntNetIPv4;
+       info.rank_dict = S_ipv4_service_rank_dict;
+    }
+    else {
+       info.entity = kSCEntNetIPv6;
+       info.rank_dict = S_ipv6_service_rank_dict;
+    }
+    info.results = ElectionResultsAlloc(af, count);
+    info.n_services = count;
+    info.order = order;
+    info.n_order = n_order;
+    CFDictionaryApplyFunction(S_service_state_dict, elect_ip, (void *)&info);
+    if (info.results->count == 0) {
+       ElectionResultsRelease(info.results);
+       info.results = NULL;
+    }
+    return (info.results);
 }
 
 /*
- * Function: rank_dict_set_service_rank
+ * Function: ElectionResultsCandidateNeedsDemotion
  * Purpose:
- *   Save the results of ranking the service so we can look it up later without
- *   repeating all of the ranking code.
+ *   Check whether the given candidate requires demotion. A candidate
+ *   might need to be demoted if its IPv4 and IPv6 services must be coupled
+ *   but a higher ranked service has IPv4 or IPv6.
  */
-static void
-rank_dict_set_service_rank(CFMutableDictionaryRef rank_dict,
-                          CFStringRef serviceID, Rank rank_val)
+static Boolean
+ElectionResultsCandidateNeedsDemotion(ElectionResultsRef other_results,
+                                     CandidateRef candidate)
 {
-    CFNumberRef                rank;
+    CandidateRef       other_candidate;
+    Boolean            ret = FALSE;
 
-    rank = CFNumberCreate(NULL, kCFNumberSInt32Type, (const void *)&rank_val);
-    if (rank != NULL) {
-       CFDictionarySetValue(rank_dict, serviceID, rank);
-       CFRelease(rank);
+    if (other_results == NULL
+       || candidate->ip_is_coupled == FALSE
+       || RANK_ASSERTION_MASK(candidate->rank) == kRankAssertionNever) {
+       goto done;
     }
-    return;
-}
-
-static const CFStringRef *transientInterfaceEntityNames[] = {
-    &kSCEntNetPPP,
-};
-
-
-static void
-CollectTransientServices(const void * key,
-                        const void * value,
-                        void * context)
-{
-    int                        i;
-    CFStringRef                service = key;
-    CFMutableArrayRef  vif_setup_keys = context;
-
-    /* This service is either a vpn type service or a comm center service */
-    if (!CFStringHasPrefix(service, kSCDynamicStoreDomainSetup)) {
-       return;
+    other_candidate = other_results->candidates;
+    if (CFEqual(other_candidate->if_name, candidate->if_name)) {
+       /* they are over the same interface, no need to demote */
+       goto done;
+    }
+    if (CFStringHasPrefix(other_candidate->if_name, CFSTR("stf"))) {
+       /* avoid creating a feedback loop */
+       goto done;
+    }
+    if (RANK_ASSERTION_MASK(other_candidate->rank) == kRankAssertionNever) {
+       /* the other candidate isn't eligible to become primary, ignore */
+       goto done;
+    }
+    if (candidate->rank < other_candidate->rank) {
+       /* we're higher ranked than the other candidate, ignore */
+       goto done;
     }
+    ret = TRUE;
 
-    for (i = 0; i < sizeof(transientInterfaceEntityNames)/sizeof(transientInterfaceEntityNames[0]); i++) {
-       if (!CFStringHasSuffix(service, *transientInterfaceEntityNames[i])) {
-           continue;
-       }
+ done:
+    return (ret);
 
-       CFArrayAppendValue(vif_setup_keys, service);
-    }
-    return;
 }
 
 
-static SCNetworkReachabilityFlags
-GetReachabilityFlagsFromVPN(CFDictionaryRef services_info,
-                           CFStringRef     service_id,
-                           CFStringRef     entity,
-                           CFStringRef     vpn_setup_key)
+static void
+get_signature_sha1(CFStringRef         signature,
+                  unsigned char        * sha1)
 {
-    CFStringRef                        key;
-    CFDictionaryRef            dict;
-    SCNetworkReachabilityFlags flags = 0;
-
+    CC_SHA1_CTX            ctx;
+    CFDataRef      signature_data;
 
-    key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
-                                                       kSCDynamicStoreDomainSetup,
-                                                       service_id,
-                                                       kSCEntNetInterface);
-    dict = CFDictionaryGetValue(services_info, key);
-    CFRelease(key);
+    signature_data = CFStringCreateExternalRepresentation(NULL,
+                                                         signature,
+                                                         kCFStringEncodingUTF8,
+                                                         0);
 
-    if (isA_CFDictionary(dict)
-       && CFDictionaryContainsKey(dict, kSCPropNetInterfaceDeviceName)) {
+    CC_SHA1_Init(&ctx);
+    CC_SHA1_Update(&ctx,
+                  signature_data,
+                  (CC_LONG)CFDataGetLength(signature_data));
+    CC_SHA1_Final(sha1, &ctx);
 
-       flags = (kSCNetworkReachabilityFlagsReachable
-               | kSCNetworkReachabilityFlagsTransientConnection
-               | kSCNetworkReachabilityFlagsConnectionRequired);
+    CFRelease(signature_data);
 
-       if (CFEqual(entity, kSCEntNetPPP)) {
-           CFNumberRef num;
-           CFDictionaryRef p_dict = CFDictionaryGetValue(services_info, vpn_setup_key);
+    return;
+}
 
-           if (!isA_CFDictionary(p_dict)) {
-               return (flags);
-           }
 
-           // get PPP dial-on-traffic status
-           num = CFDictionaryGetValue(p_dict, kSCPropNetPPPDialOnDemand);
-           if (isA_CFNumber(num)) {
-               int32_t ppp_demand;
+static void
+add_candidate_to_nwi_state(nwi_state_t nwi_state, int af,
+                          CandidateRef candidate, Rank rank)
+{
+    uint64_t           flags = 0;
+    char               ifname[IFNAMSIZ];
+    nwi_ifstate_t      ifstate;
 
-               if (CFNumberGetValue(num, kCFNumberSInt32Type, &ppp_demand)) {
-                   if (ppp_demand) {
-                       flags |= kSCNetworkReachabilityFlagsConnectionOnTraffic;
-                   }
-               }
-           }
-       }
+    if (nwi_state == NULL) {
+       /* can't happen */
+       return;
     }
-    return (flags);
-}
-
-static Boolean
-S_dict_get_boolean(CFDictionaryRef dict, CFStringRef key, Boolean def_value)
-{
-    Boolean            ret = def_value;
+    if (RANK_ASSERTION_MASK(rank) == kRankAssertionNever) {
+       flags |= NWI_IFSTATE_FLAGS_NOT_IN_LIST;
+    }
+    if (service_dict_get(candidate->serviceID, kSCEntNetDNS) != NULL) {
+       flags |= NWI_IFSTATE_FLAGS_HAS_DNS;
+    }
+    CFStringGetCString(candidate->if_name, ifname, sizeof(ifname),
+                      kCFStringEncodingASCII);
+    if ((S_IPMonitor_debug & kDebugFlag2) != 0) {
+       char    ntopbuf[INET6_ADDRSTRLEN];
 
-    if (dict != NULL) {
-       CFBooleanRef    val;
+       (void)inet_ntop(af, &candidate->addr, ntopbuf, sizeof(ntopbuf));
+       my_log(LOG_DEBUG,
+              "Inserting IPv%c [%s] %s "
+              "with flags 0x%llx rank 0x%x reach_flags 0x%x",
+              ipvx_char(af), ifname, ntopbuf,
+              flags, rank, candidate->reachability_flags);
+    }
+    ifstate = nwi_insert_ifstate(nwi_state, ifname, af, flags, rank,
+                                (void *)&candidate->addr,
+                                (void *)&candidate->vpn_server_addr,
+                                candidate->reachability_flags);
+    if (ifstate != NULL && candidate->signature) {
+       uint8_t     hash[CC_SHA1_DIGEST_LENGTH];
 
-       val = CFDictionaryGetValue(dict, key);
-       if (isA_CFBoolean(val) != NULL) {
-           ret = CFBooleanGetValue(val);
-       }
+       get_signature_sha1(candidate->signature, hash);
+       nwi_ifstate_set_signature(ifstate, hash);
     }
-    return (ret);
+    return;
 }
 
 
 static void
-GetReachabilityFlagsFromTransientServices(CFDictionaryRef services_info,
-                                         SCNetworkReachabilityFlags *reach_flags_v4,
-                                         SCNetworkReachabilityFlags *reach_flags_v6)
+add_reachability_flags_to_candidate(CandidateRef candidate, CFDictionaryRef services_info, int af)
 {
-    int i;
-    int count;
-    CFMutableArrayRef vif_setup_keys;
-
-    vif_setup_keys = CFArrayCreateMutable(NULL,
-                                         0,
-                                         &kCFTypeArrayCallBacks);
-
-    CFDictionaryApplyFunction(services_info, CollectTransientServices, vif_setup_keys);
+    SCNetworkReachabilityFlags flags = kSCNetworkReachabilityFlagsReachable;
+    CFStringRef                        vpn_server_address = NULL;
 
-    count = CFArrayGetCount(vif_setup_keys);
+    VPNAttributesGet(candidate->serviceID,
+                    services_info,
+                    &flags,
+                    &vpn_server_address,
+                    af);
 
-    if (count != 0) {
-       my_log(LOG_DEBUG, "Collected the following VIF Setup Keys: %@", vif_setup_keys);
-    }
+    candidate->reachability_flags = flags;
 
-    for (i = 0; i < count; i++) {
-       CFArrayRef          components = NULL;
-       CFStringRef         entity;
-       CFStringRef         service_id;
-       CFStringRef         vif_setup_key;
+    if (vpn_server_address == NULL) {
+       bzero(&candidate->vpn_server_addr, sizeof(candidate->vpn_server_addr));
+    } else {
+       char buf[128];
 
-       vif_setup_key = CFArrayGetValueAtIndex(vif_setup_keys, i);
+       CFStringGetCString(vpn_server_address, buf, sizeof(buf),
+                          kCFStringEncodingASCII);
+       _SC_string_to_sockaddr(buf,
+                              AF_UNSPEC,
+                              (void *)&candidate->vpn_server_addr,
+                              sizeof(candidate->vpn_server_addr));
 
-       /*
-        * setup key in the following format:
-        * Setup:/Network/Service/<Service ID>/<Entity>
-        */
-       components = CFStringCreateArrayBySeparatingStrings(NULL, vif_setup_key, CFSTR("/"));
+       CFRelease(vpn_server_address);
+    }
+    return;
+}
+/*
+ * Function: ElectionResultsCopyPrimary
+ * Purpose:
+ *   Use the results of the current protocol and the other protocol to
+ *   determine which service should become primary.
+ *
+ *   At the same time, generate the IPv4/IPv6 routing table and
+ *   the nwi_state for the protocol.
+ */
+static CFStringRef
+ElectionResultsCopyPrimary(ElectionResultsRef results,
+                          ElectionResultsRef other_results,
+                          nwi_state_t nwi_state, int af,
+                          RouteListRef * ret_routes,
+                          CFDictionaryRef services_info)
+{
+    CFStringRef                primary = NULL;
+    Boolean            primary_is_null = FALSE;
+    RouteListRef       routes = NULL;
 
-       if (CFArrayGetCount(components) != 5) {
-           my_log(LOG_ERR, "Invalid Setup Key encountered: %@", vif_setup_key);
-           goto skip;
+    if (nwi_state != NULL) {
+       nwi_state_clear(nwi_state, af);
+    }
+    if (results != NULL) {
+       CandidateRef            deferred[results->count];
+       int                     deferred_count;
+       CFStringRef             entity_name;
+       int                     i;
+       int                     initial_size;
+       RouteListInfoRef        info;
+       CandidateRef            scan;
+
+       switch (af) {
+       case AF_INET:
+           entity_name = kSCEntNetIPv4;
+           info = &IPv4RouteListInfo;
+           initial_size = results->count * IPV4_ROUTES_N_STATIC;
+           break;
+       default:
+       case AF_INET6:
+           entity_name = kSCEntNetIPv6;
+           info = &IPv6RouteListInfo;
+           initial_size = results->count * IPV6_ROUTES_N_STATIC;
+           break;
        }
+       deferred_count = 0;
+       for (i = 0, scan = results->candidates;
+            i < results->count;
+            i++, scan++) {
+           Boolean             is_primary = FALSE;
+           Rank                rank = scan->rank;
+           CFDictionaryRef     service_dict;
+           RouteListRef        service_routes;
+           Boolean             skip = FALSE;
 
-       /* service id is the 3rd element */
-       service_id = CFArrayGetValueAtIndex(components, 3);
-
-       /* entity id is the 4th element */
-       entity = CFArrayGetValueAtIndex(components, 4);
-
-       my_log(LOG_DEBUG, "Service %@ is a %@ Entity", service_id, entity);
-
-
-       if (CFEqual(entity, kSCEntNetPPP)) {
-           SCNetworkReachabilityFlags  flags;
-           CFStringRef                 key;
-
-           flags = GetReachabilityFlagsFromVPN(services_info,
-                                               service_id,
-                                               entity,
-                                               vif_setup_key);
-
-           /* Check for the v4 reachability flags */
-           key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
-                                                             kSCDynamicStoreDomainSetup,
-                                                             service_id,
-                                                             kSCEntNetIPv4);
-
-           if (CFDictionaryContainsKey(services_info, key)) {
-               *reach_flags_v4 |= flags;
-               my_log(LOG_DEBUG,"Service %@ setting ipv4 reach flags: %d", service_id, *reach_flags_v4);
+           if (primary == NULL
+               && RANK_ASSERTION_MASK(rank) != kRankAssertionNever) {
+               if (ElectionResultsCandidateNeedsDemotion(other_results,
+                                                         scan)) {
+                   /* demote to RankNever */
+                   my_log(LOG_NOTICE,
+                          "IPv%c over %@ demoted: not primary for IPv%c",
+                          ipvx_char(af), scan->if_name, ipvx_other_char(af));
+                   rank = RankMake(rank, kRankAssertionNever);
+                   deferred[deferred_count++] = scan;
+                   skip = TRUE;
+               }
+               else {
+                   primary = CFRetain(scan->serviceID);
+                   is_primary = TRUE;
+               }
            }
-
-           CFRelease(key);
-
-           /* Check for the v6 reachability flags */
-           key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
-                                                             kSCDynamicStoreDomainSetup,
-                                                             service_id,
-                                                             kSCEntNetIPv6);
-
-           if (CFDictionaryContainsKey(services_info, key)) {
-               *reach_flags_v6 |= flags;
-               my_log(LOG_DEBUG,"Service %@ setting ipv6 reach flags: %d", service_id, *reach_flags_v6);
+           /* contribute to the routing table */
+           service_dict = service_dict_get(scan->serviceID, entity_name);
+           service_routes = ipdict_get_routelist(service_dict);
+           if (service_routes != NULL) {
+               routes = RouteListAddRouteList(info, routes, initial_size,
+                                              service_routes, rank);
+               if ((service_routes->flags & kRouteListFlagsExcludeNWI) != 0) {
+                   skip = TRUE;
+               }
            }
-           CFRelease(key);
-
-           if (flags != 0) {
-               if (components != NULL) {
-                   CFRelease(components);
+           else {
+               skip = TRUE;
+           }
+           if (skip) {
+               /* if we're skipping the primary, it's NULL */
+               if (is_primary) {
+                   primary_is_null = TRUE;
                }
-               goto done;
+           }
+           else {
+               if (primary_is_null) {
+                   /* everything after the primary must be Never */
+                   rank = RankMake(rank, kRankAssertionNever);
+               }
+               add_reachability_flags_to_candidate(scan, services_info, af);
+               add_candidate_to_nwi_state(nwi_state, af, scan, rank);
            }
        }
-skip:
-       if (components != NULL) {
-           CFRelease(components);
+       for (i = 0; i < deferred_count; i++) {
+           CandidateRef        candidate = deferred[i];
+           Rank                rank;
+
+           /* demote to RankNever */
+           rank = RankMake(candidate->rank, kRankAssertionNever);
+           add_reachability_flags_to_candidate(candidate, services_info, af);
+           add_candidate_to_nwi_state(nwi_state, af, candidate, rank);
        }
     }
-done:
-    CFRelease(vif_setup_keys);
-    return;
+    if (nwi_state != NULL) {
+       nwi_state_set_last(nwi_state, af);
+    }
+    if (ret_routes != NULL) {
+       *ret_routes = routes;
+    }
+    else if (routes != NULL) {
+       free(routes);
+    }
+    if (primary_is_null) {
+       my_CFRelease(&primary);
+    }
+    return (primary);
 }
 
-static SCNetworkReachabilityFlags
-GetReachFlagsFromStatus(CFStringRef entity, int status)
+
+static inline
+CFStringRef
+service_dict_get_signature(CFDictionaryRef service_dict)
 {
-    SCNetworkReachabilityFlags flags = 0;
+    CFStringRef                ifname;
 
-    if (CFEqual(entity, kSCEntNetPPP)) {
-       switch (status) {
-           case PPP_RUNNING :
-               /* if we're really UP and RUNNING */
-               break;
-           case PPP_ONHOLD :
-               /* if we're effectively UP and RUNNING */
-               break;
-           case PPP_IDLE :
-               /* if we're not connected at all */
-               my_log(LOG_INFO, "PPP link idle");
-               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
-               break;
-           case PPP_STATERESERVED :
-               // if we're not connected at all
-               my_log(LOG_INFO, "PPP link idle, dial-on-traffic to connect");
-               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
-               break;
-           default :
-               /* if we're in the process of [dis]connecting */
-               my_log(LOG_INFO, "PPP link, connection in progress");
-               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
-               break;
-       }
-    }
-#ifdef HAVE_IPSEC_STATUS
-    else if (CFEqual(entity, kSCEntNetIPSec)) {
-       switch (status) {
-           case IPSEC_RUNNING :
-               /* if we're really UP and RUNNING */
-               break;
-           case IPSEC_IDLE :
-               /* if we're not connected at all */
-               my_log(LOG_INFO, "IPSec link idle");
-               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
-               break;
-           default :
-               /* if we're in the process of [dis]connecting */
-               my_log(LOG_INFO, "IPSec link, connection in progress");
-               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
-               break;
-       }
-    }
-#endif // HAVE_IPSEC_STATUS
-#ifdef HAVE_VPN_STATUS
-    else if  (CFEqual(entity, kSCEntNetVPN)) {
-       switch (status) {
-           case VPN_RUNNING :
-               /* if we're really UP and RUNNING */
-               break;
-           case VPN_IDLE :
-           case VPN_LOADING :
-           case VPN_LOADED :
-           case VPN_UNLOADING :
-               /* if we're not connected at all */
-               my_log(LOG_INFO, "%s  VPN link idle");
-               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
-               break;
-           default :
-               /* if we're in the process of [dis]connecting */
-               my_log(LOG_INFO, "VPN link, connection in progress");
-               flags |= kSCNetworkReachabilityFlagsConnectionRequired;
-               break;
-       }
+    ifname = CFDictionaryGetValue(service_dict, kSCPropInterfaceName);
+    if (isA_CFString(ifname) == NULL
+       || confirm_interface_name(service_dict, ifname) == FALSE) {
+       return (NULL);
     }
-#endif // HAVE_VPN_STATUS
-    return (flags);
+    return (CFDictionaryGetValue(service_dict, kStoreKeyNetworkSignature));
 }
 
+/*
+ * Function: elect_ip
+ * Purpose:
+ *   Evaluate the service and determine what rank the service should have.
+ *   If it's a suitable candidate, add it to the election results.
+ */
 static void
-VPNAttributesGet(CFStringRef               service_id,
-                CFDictionaryRef            services_info,
-                SCNetworkReachabilityFlags *flags,
-                CFStringRef                *server_address,
-                int                        af)
+elect_ip(const void * key, const void * value, void * context)
 {
-    int                                i;
-    CFDictionaryRef            entity_dict;
-    boolean_t                  found = FALSE;
-    CFNumberRef                        num;
-    CFDictionaryRef            p_state = NULL;
-    int                                status = 0;
+    CFDictionaryRef    all_entities_dict = (CFDictionaryRef)value;
+    Candidate          candidate;
+    Rank               default_rank;
+    ElectionInfoRef    elect_info;
+    CFStringRef                if_name;
+    CFDictionaryRef    ipdict;
+    Rank               primary_rank;
+    RouteListUnion     routelist;
+    CFDictionaryRef    service_dict;
 
-    /* if the IPv[4/6] exist */
-    entity_dict = service_dict_get(service_id, (af == AF_INET) ? kSCEntNetIPv4 : kSCEntNetIPv6);
-    if (!isA_CFDictionary(entity_dict)) {
-       return;
+    elect_info = (ElectionInfoRef)context;
+    ipdict = CFDictionaryGetValue(all_entities_dict, elect_info->entity);
+    if (ipdict != NULL) {
+       routelist.ptr = ipdict_get_routelist(ipdict);
+       service_dict = ipdict_get_service(ipdict);
     }
-
-    if (af == AF_INET) {
-       entity_dict = CFDictionaryGetValue(entity_dict, kIPv4DictService);
-       if (!isA_CFDictionary(entity_dict)) {
-           return;
-       }
+    else {
+       routelist.ptr = NULL;
     }
-
-    for (i = 0; i < sizeof(statusEntityNames)/sizeof(statusEntityNames[0]); i++) {
-       p_state = service_dict_get(service_id, *statusEntityNames[i]);
-       /* ensure that this is a VPN Type service */
-       if (isA_CFDictionary(p_state)) {
-           found = TRUE;
-           break;
-       }
+    if (routelist.ptr == NULL || service_dict == NULL) {
+       /* no connectivity */
+       return;
     }
-
-    /* Did we find a vpn type service?  If not, we are done.*/
-    if (!found) {
+    if ((routelist.common->flags & kRouteListFlagsHasDefault) == 0) {
+       /* no default route, not a candidate for being primary */
        return;
     }
-
-    *flags |= (kSCNetworkReachabilityFlagsReachable| kSCNetworkReachabilityFlagsTransientConnection);
-
-    /* Get the Server Address */
-    if (server_address != NULL) {
-       *server_address = CFDictionaryGetValue(entity_dict, CFSTR("ServerAddress"));
-       *server_address = isA_CFString(*server_address);
-       if (*server_address != NULL) {
-           CFRetain(*server_address);
-       }
+    if_name = CFDictionaryGetValue(service_dict, kSCPropInterfaceName);
+    if (if_name == NULL) {
+       /* need an interface name */
+       return;
     }
-
-    /* get status */
-    if (!CFDictionaryGetValueIfPresent(p_state,
-                                      kSCPropNetVPNStatus,
-                                      (const void **)&num) ||
-       !isA_CFNumber(num) ||
-       !CFNumberGetValue(num, kCFNumberSInt32Type, &status)) {
+    if (CFEqual(if_name, CFSTR(kLoopbackInterface))) {
+       /* don't process loopback */
        return;
     }
+    bzero(&candidate, sizeof(candidate));
+    candidate.serviceID = (CFStringRef)key;
+    candidate.rank = get_service_rank(elect_info->order, elect_info->n_order,
+                                     candidate.serviceID);
+    if (elect_info->af == AF_INET) {
+       default_rank = routelist.v4->list->rank;
+       candidate.addr.v4 = routelist.v4->list->ifa;
+    }
+    else {
+       default_rank = routelist.v6->list->rank;
+       candidate.addr.v6 = routelist.v6->list->ifa;
+    }
+    primary_rank = RANK_ASSERTION_MASK(default_rank);
+    if (S_ppp_override_primary) {
+       char    ifn[IFNAMSIZ];
 
-    *flags |= GetReachFlagsFromStatus(*statusEntityNames[i], status);
-
-    if (CFEqual(*statusEntityNames[i], kSCEntNetPPP)) {
-       CFStringRef     key;
-       CFDictionaryRef p_setup;
-       int             ppp_demand;
-
-       key = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
-                                                         kSCDynamicStoreDomainSetup,
-                                                         service_id,
-                                                         kSCEntNetPPP);
-       p_setup = CFDictionaryGetValue(services_info, key);
-       CFRelease(key);
-
-       /* get dial-on-traffic status */
-       if (isA_CFDictionary(p_setup) &&
-           CFDictionaryGetValueIfPresent(p_setup,
-                                         kSCPropNetPPPDialOnDemand,
-                                         (const void **)&num) &&
-           isA_CFNumber(num) &&
-           CFNumberGetValue(num, kCFNumberSInt32Type, &ppp_demand) &&
-           (ppp_demand != 0)) {
-           *flags |= kSCNetworkReachabilityFlagsConnectionOnTraffic;
-           if (status == PPP_IDLE) {
-               *flags |= kSCNetworkReachabilityFlagsInterventionRequired;
-           }
+       if (CFStringGetCString(if_name, ifn, sizeof(ifn),
+                              kCFStringEncodingASCII)
+           && (strncmp(PPP_PREFIX, ifn, sizeof(PPP_PREFIX) - 1) == 0)) {
+           /* PPP override: make ppp* look the best */
+           primary_rank = kRankAssertionFirst;
        }
     }
+    candidate.rank = RankMake(candidate.rank, primary_rank);
+    candidate.ip_is_coupled = service_get_ip_is_coupled(candidate.serviceID);
+    candidate.if_name = if_name;
+    rank_dict_set_service_rank(elect_info->rank_dict,
+                              candidate.serviceID, candidate.rank);
+    candidate.signature = service_dict_get_signature(service_dict);
+    ElectionResultsAddCandidate(elect_info->results, &candidate);
     return;
 }
 
 
-typedef struct ElectionInfo {
-    int                        n_services;
-    CFArrayRef         order;
-    int                        n_order;
-    ElectionResultsRef results;
-} ElectionInfo, * ElectionInfoRef;
-
-typedef CFDictionaryApplierFunction    ElectionFuncRef;
-
-static void
-CandidateRelease(CandidateRef candidate)
+static uint32_t
+service_changed(CFDictionaryRef services_info, CFStringRef serviceID)
 {
-    my_CFRelease(&candidate->serviceID);
-    my_CFRelease(&candidate->if_name);
-    my_CFRelease(&candidate->signature);
-    return;
-}
+    uint32_t           changed = 0;
+    int                        i;
 
-static void
-CandidateCopy(CandidateRef dest, CandidateRef src)
-{
-    *dest = *src;
-    if (dest->serviceID) {
-       CFRetain(dest->serviceID);
+    /* update service options first (e.g. rank) */
+    if (get_rank_changes(serviceID,
+                        get_service_state_entity(services_info, serviceID,
+                                                 NULL),
+                        get_service_setup_entity(services_info, serviceID,
+                                                 NULL),
+                        services_info)) {
+       changed |= (1 << kEntityTypeServiceOptions);
     }
-    if (dest->if_name) {
-       CFRetain(dest->if_name);
+    /* update IPv4, IPv6, DNS, Proxies, SMB, ... */
+    for (i = 0; i < ENTITY_TYPES_COUNT; i++) {
+       GetEntityChangesFuncRef func = entityChangeFunc[i];
+       if ((*func)(serviceID,
+                   get_service_state_entity(services_info, serviceID,
+                                            *entityTypeNames[i]),
+                   get_service_setup_entity(services_info, serviceID,
+                                            *entityTypeNames[i]),
+                   services_info)) {
+           changed |= (1 << i);
+       }
     }
-    if(dest->signature) {
-       CFRetain(dest->signature);
+
+    if (get_transient_status_changes(serviceID, services_info)) {
+       changed |= (1 << kEntityTypeTransientStatus);
     }
-    return;
+
+    return (changed);
 }
 
-static ElectionResultsRef
-ElectionResultsAlloc(int size)
+static CFArrayRef
+service_order_get(CFDictionaryRef services_info)
 {
-    ElectionResultsRef results;
+    CFArrayRef         order = NULL;
+    CFDictionaryRef    ipv4_dict;
 
-    results = (ElectionResultsRef)malloc(ElectionResultsComputeSize(size));
-    results->count = 0;
-    results->size = size;
-    return (results);
+    ipv4_dict = my_CFDictionaryGetDictionary(services_info,
+                                            S_setup_global_ipv4);
+    if (ipv4_dict != NULL) {
+       CFNumberRef     ppp_override;
+       int             ppp_val = 0;
+
+       order = CFDictionaryGetValue(ipv4_dict, kSCPropNetServiceOrder);
+       order = isA_CFArray(order);
+
+       /* get ppp override primary */
+       ppp_override = CFDictionaryGetValue(ipv4_dict,
+                                           kSCPropNetPPPOverridePrimary);
+       ppp_override = isA_CFNumber(ppp_override);
+       if (ppp_override != NULL) {
+           CFNumberGetValue(ppp_override, kCFNumberIntType, &ppp_val);
+       }
+       S_ppp_override_primary = (ppp_val != 0) ? TRUE : FALSE;
+    }
+    else {
+       S_ppp_override_primary = FALSE;
+    }
+    return (order);
 }
 
-static void
-ElectionResultsRelease(ElectionResultsRef results)
+static boolean_t
+set_new_primary(CFStringRef * primary_p, CFStringRef new_primary,
+               const char * entity)
 {
-    int                        i;
-    CandidateRef       scan;
+    boolean_t          changed = FALSE;
+    CFStringRef                primary = *primary_p;
 
-    for (i = 0, scan = results->candidates;
-        i < results->count;
-        i++, scan++) {
-       CandidateRelease(scan);
+    if (new_primary != NULL) {
+       if (primary != NULL && CFEqual(new_primary, primary)) {
+           if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+               my_log(LOG_DEBUG,
+                      "IPMonitor: %@ is still primary %s",
+                      new_primary, entity);
+           }
+       }
+       else {
+           my_CFRelease(primary_p);
+           *primary_p = CFRetain(new_primary);
+           if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+               my_log(LOG_DEBUG,
+                      "IPMonitor: %@ is the new primary %s",
+                      new_primary, entity);
+           }
+           changed = TRUE;
+       }
     }
-    free(results);
-    return;
+    else if (primary != NULL) {
+       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+           my_log(LOG_DEBUG,
+                  "IPMonitor: %@ is no longer primary %s",
+                  primary, entity);
+       }
+       my_CFRelease(primary_p);
+       changed = TRUE;
+    }
+    return (changed);
 }
 
-static void
-ElectionResultsLog(int level, ElectionResultsRef results, const char * prefix)
+static Rank
+rank_service_entity(CFDictionaryRef rank_dict, CFStringRef serviceID,
+                   CFStringRef entity)
 {
-    int                        i;
-    CandidateRef       scan;
-
-    if (results == NULL) {
-       my_log(level, "%s: no candidates", prefix);
-       return;
-    }
-    my_log(level, "%s: %d candidates", prefix, results->count);
-    for (i = 0, scan = results->candidates;
-        i < results->count;
-        i++, scan++) {
-       my_log(level, "%d. %@ Rank=0x%x serviceID=%@", i, scan->if_name,
-              scan->rank, scan->serviceID);
+    if (service_dict_get(serviceID, entity) == NULL) {
+       return (RankMake(kRankIndexMask, kRankAssertionDefault));
     }
-    return;
+    return (rank_dict_get_service_rank(rank_dict, serviceID));
 }
 
-/*
- * Function: ElectionResultsAddCandidate
- * Purpose:
- *   Add the candidate into the election results. Find the insertion point
- *   by comparing the rank of the candidate with existing entries.
- */
 static void
-ElectionResultsAddCandidate(ElectionResultsRef results, CandidateRef candidate)
+append_serviceIDs_for_interface(CFMutableArrayRef services_changed,
+                               CFStringRef ifname)
 {
-    int                        i;
-    int                        where;
+    CFIndex            count;
+    CFIndex            i;
+    void * *           keys;
+#define N_KEYS_VALUES_STATIC    10
+    void *             keys_values_buf[N_KEYS_VALUES_STATIC * 2];
+    void * *           values;
 
-#define BAD_INDEX      (-1)
-    if (results->count == results->size) {
-       /* this should not happen */
-       my_log(LOG_NOTICE, "can't fit another candidate");
-       return;
+    count = CFDictionaryGetCount(S_service_state_dict);
+    if (count <= N_KEYS_VALUES_STATIC) {
+       keys = keys_values_buf;
+    } else {
+       keys = (void * *)malloc(sizeof(*keys) * count * 2);
     }
+    values = keys + count;
+    CFDictionaryGetKeysAndValues(S_service_state_dict,
+                                (const void * *)keys,
+                                (const void * *)values);
 
-    /* find the insertion point */
-    where = BAD_INDEX;
-    for (i = 0; i < results->count; i++) {
-       CandidateRef    this_candidate = results->candidates + i;
+    for (i = 0; i < count; i++) {
+       CFDictionaryRef         ipdict = NULL;
+       CFStringRef             interface = NULL;
+       CFStringRef             serviceID;
+       CFDictionaryRef         service_dict;
 
-       if (candidate->rank < this_candidate->rank) {
-           where = i;
-           break;
+       serviceID = (CFStringRef)keys[i];
+       service_dict = (CFDictionaryRef)values[i];
+
+       /* check whether service has IPv4 or IPv6 */
+       ipdict = CFDictionaryGetValue(service_dict, kSCEntNetIPv4);
+       if (ipdict == NULL) {
+           ipdict = CFDictionaryGetValue(service_dict, kSCEntNetIPv6);
+           if (ipdict == NULL) {
+               continue;
+           }
+       }
+       interface = ipdict_get_ifname(ipdict);
+       if (interface != NULL && CFEqual(interface, ifname)) {
+           if (S_IPMonitor_debug & kDebugFlag1) {
+               my_log(LOG_DEBUG,
+                      "Found IP service %@ on interface %@.",
+                      serviceID, ifname);
+           }
+           my_CFArrayAppendUniqueValue(services_changed, serviceID);
        }
     }
-    /* add it to the end */
-    if (where == BAD_INDEX) {
-       CandidateCopy(results->candidates + results->count, candidate);
-       results->count++;
-       return;
-    }
-    /* slide existing entries over */
-    for (i = results->count; i > where; i--) {
-       results->candidates[i] = results->candidates[i - 1];
+    if (keys != keys_values_buf) {
+       free(keys);
     }
-    /* insert element */
-    CandidateCopy(results->candidates + where, candidate);
-    results->count++;
     return;
 }
 
-/*
- * Function: ElectionResultsCopy
- * Purpose:
- *   Visit all of the services and invoke the protocol-specific election
- *   function.  Return the results of the election.
- */
-static ElectionResultsRef
-ElectionResultsCopy(ElectionFuncRef elect_func, CFArrayRef order, int n_order)
+static __inline__ const char *
+get_changed_str(CFStringRef serviceID, CFStringRef entity,
+               CFDictionaryRef old_dict)
 {
-    int                        count;
-    ElectionInfo       info;
+    CFDictionaryRef new_dict    = NULL;
 
-    count = CFDictionaryGetCount(S_service_state_dict);
-    if (count == 0) {
-       return (NULL);
+    if (serviceID != NULL) {
+       new_dict = service_dict_get(serviceID, entity);
     }
-    info.results = ElectionResultsAlloc(count);
-    info.n_services = count;
-    info.order = order;
-    info.n_order = n_order;
-    CFDictionaryApplyFunction(S_service_state_dict, elect_func, (void *)&info);
-    if (info.results->count == 0) {
-       ElectionResultsRelease(info.results);
-       info.results = NULL;
+
+    if (old_dict == NULL) {
+       if (new_dict != NULL) {
+           return "+";
+       }
+    } else {
+       if (new_dict == NULL) {
+           return "-";
+       } else if (!CFEqual(old_dict, new_dict)) {
+           return "!";
+       }
     }
-    return (info.results);
+    return "";
 }
 
-/*
- * Function: ElectionResultsCandidateNeedsDemotion
- * Purpose:
- *   Check whether the given candidate requires demotion. A candidate
- *   might need to be demoted if its IPv4 and IPv6 services must be coupled
- *   but a higher ranked service has IPv4 or IPv6.
- */
-static Boolean
-ElectionResultsCandidateNeedsDemotion(ElectionResultsRef other_results,
-                                     CandidateRef candidate)
+static CF_RETURNS_RETAINED CFStringRef
+generate_log_changes(nwi_state_t       changes_state,
+                    boolean_t          dns_changed,
+                    boolean_t          dnsinfo_changed,
+                    CFDictionaryRef    old_primary_dns,
+                    boolean_t          proxy_changed,
+                    CFDictionaryRef    old_primary_proxy,
+                    boolean_t          smb_changed,
+                    CFDictionaryRef    old_primary_smb
+                    )
 {
-    CandidateRef       other_candidate;
-    Boolean            ret = FALSE;
+    int idx;
+    CFMutableStringRef log_output;
+    nwi_ifstate_t scan;
 
-    if (other_results == NULL
-       || candidate->ip_is_coupled == FALSE
-       || RANK_ASSERTION_MASK(candidate->rank) == kRankAssertionNever) {
-       goto done;
-    }
-    other_candidate = other_results->candidates;
-    if (CFEqual(other_candidate->if_name, candidate->if_name)) {
-       /* they are over the same interface, no need to demote */
-       goto done;
-    }
-    if (CFStringHasPrefix(other_candidate->if_name, CFSTR("stf"))) {
-       /* avoid creating a feedback loop */
-       goto done;
+    log_output = CFStringCreateMutable(NULL, 0);
+
+    if (changes_state != NULL) {
+       for (idx = 0; idx < countof(nwi_af_list); idx++) {
+           CFMutableStringRef changes = NULL;
+           CFMutableStringRef primary_str = NULL;
+
+           scan = nwi_state_get_first_ifstate(changes_state, nwi_af_list[idx]);
+
+           while (scan != NULL) {
+               const char * changed_str;
+
+               changed_str = nwi_ifstate_get_diff_str(scan);
+               if (changed_str != NULL) {
+                   void *              address;
+                   const char *        addr_str;
+                   char                ntopbuf[INET6_ADDRSTRLEN];
+
+                   address = (void *)nwi_ifstate_get_address(scan);
+                   addr_str = inet_ntop(scan->af, address, ntopbuf,
+                                        sizeof(ntopbuf));
+                   if (primary_str ==  NULL) {
+                       primary_str = CFStringCreateMutable(NULL, 0);
+                       CFStringAppendFormat(primary_str, NULL,
+                                            CFSTR("%s%s:%s"),
+                                            nwi_ifstate_get_ifname(scan),
+                                            changed_str, addr_str);
+                   } else {
+                       if (changes == NULL) {
+                           changes = CFStringCreateMutable(NULL, 0);
+                       }
+                       CFStringAppendFormat(changes, NULL, CFSTR(", %s"),
+                                            nwi_ifstate_get_ifname(scan));
+                       if (strcmp(changed_str,  "") != 0) {
+                           CFStringAppendFormat(changes, NULL, CFSTR("%s:%s"),
+                                                changed_str, addr_str);
+                       }
+                   }
+               }
+               scan = nwi_ifstate_get_next(scan, scan->af);
+           }
+
+           if (primary_str != NULL) {
+               CFStringAppendFormat(log_output, NULL, CFSTR(" %s(%@"),
+                                    nwi_af_list[idx] == AF_INET ? "v4" : "v6",
+                                    primary_str);
+
+               if (changes != NULL && CFStringGetLength(changes) != 0) {
+                   CFStringAppendFormat(log_output, NULL, CFSTR("%@"),
+                                        changes);
+               }
+               CFStringAppend(log_output, CFSTR(")"));
+
+               my_CFRelease(&primary_str);
+               my_CFRelease(&changes);
+           }
+       }
     }
-    if (RANK_ASSERTION_MASK(other_candidate->rank) == kRankAssertionNever) {
-       /* the other candidate isn't eligible to become primary, ignore */
-       goto done;
+
+    if (dns_changed || dnsinfo_changed) {
+       const char    *str;
+
+       str = get_changed_str(S_primary_dns, kSCEntNetDNS, old_primary_dns);
+       if ((strcmp(str, "") == 0) && dnsinfo_changed) {
+           str = "*";      // dnsinfo change w/no change to primary
+       }
+       CFStringAppendFormat(log_output, NULL, CFSTR(" DNS%s"), str);
+    } else if (S_primary_dns != NULL) {
+       CFStringAppend(log_output, CFSTR(" DNS"));
     }
-    if (candidate->rank < other_candidate->rank) {
-       /* we're higher ranked than the other candidate, ignore */
-       goto done;
+
+    if (proxy_changed) {
+       const char    *str;
+
+       str = get_changed_str(S_primary_proxies, kSCEntNetProxies, old_primary_proxy);
+       CFStringAppendFormat(log_output, NULL, CFSTR(" Proxy%s"), str);
+    } else if (S_primary_proxies != NULL) {
+       CFStringAppend(log_output, CFSTR(" Proxy"));
     }
-    ret = TRUE;
 
- done:
-    return (ret);
+#if    !TARGET_OS_IPHONE
+    if (smb_changed) {
+       const char    *str;
+
+       str = get_changed_str(S_primary_smb, kSCEntNetSMB, old_primary_smb);
+       CFStringAppendFormat(log_output, NULL, CFSTR(" SMB%s"), str);
+    } else if (S_primary_smb != NULL) {
+       CFStringAppend(log_output, CFSTR(" SMB"));
+    }
+#endif // !TARGET_OS_IPHONE
 
+    return log_output;
 }
 
+#pragma mark -
+#pragma mark Network changed notification
 
-static void
-get_signature_sha1(CFStringRef         signature,
-                  unsigned char        * sha1)
+static dispatch_queue_t
+__network_change_queue()
 {
-    CC_SHA1_CTX            ctx;
-    CFDataRef      signature_data;
-
-    signature_data = CFStringCreateExternalRepresentation(NULL,
-                                                         signature,
-                                                         kCFStringEncodingUTF8,
-                                                         0);
-
-    CC_SHA1_Init(&ctx);
-    CC_SHA1_Update(&ctx,
-                  signature_data,
-                  CFDataGetLength(signature_data));
-    CC_SHA1_Final(sha1, &ctx);
+    static dispatch_once_t     once;
+    static dispatch_queue_t    q;
 
-    CFRelease(signature_data);
+    dispatch_once(&once, ^{
+       q = dispatch_queue_create("network change queue", NULL);
+    });
 
-    return;
+    return q;
 }
 
-
+// Note: must run on __network_change_queue()
 static void
-add_candidate_to_nwi_state(nwi_state_t nwi_state, int af,
-                          CandidateRef candidate, Rank rank)
+post_network_change_when_ready()
 {
-    uint64_t           flags = 0;
-    char               ifname[IFNAMSIZ];
-    nwi_ifstate_t      ifstate;
+    int                    status;
 
-    if (nwi_state == NULL) {
-       /* can't happen */
+    if (S_network_change_needed == 0) {
        return;
     }
-    if (RANK_ASSERTION_MASK(rank) == kRankAssertionNever) {
-       flags |= NWI_IFSTATE_FLAGS_NOT_IN_LIST;
-    }
-    if (service_dict_get(candidate->serviceID, kSCEntNetDNS) != NULL) {
-       flags |= NWI_IFSTATE_FLAGS_HAS_DNS;
-    }
-    CFStringGetCString(candidate->if_name, ifname, sizeof(ifname),
-                      kCFStringEncodingASCII);
-    if ((S_IPMonitor_debug & kDebugFlag2) != 0) {
-       my_log(LOG_DEBUG,
-              "Inserting IPv%c [%s] with flags 0x%x primary_rank 0x%x reach_flags %d",
-              ipvx_char(af), ifname, rank, candidate->reachability_flags);
-    }
-    ifstate = nwi_insert_ifstate(nwi_state, ifname, af, flags, rank,
-                                (void *)&candidate->addr,
-                                (void *)&candidate->vpn_server_addr,
-                                candidate->reachability_flags);
-    if (ifstate != NULL && candidate->signature) {
-       uint8_t     hash[CC_SHA1_DIGEST_LENGTH];
-
-       get_signature_sha1(candidate->signature, hash);
-       nwi_ifstate_set_signature(ifstate, hash);
-    }
-    return;
-}
 
+    if (!S_network_change_timeout &&
+       (!S_dnsinfo_synced || !S_nwi_synced)) {
+       // if we [still] need to wait for the DNS configuration
+       // or network information changes to be ack'd
 
-static void
-add_reachability_flags_to_candidate(CandidateRef candidate, CFDictionaryRef services_info, int af)
-{
-    SCNetworkReachabilityFlags flags = kSCNetworkReachabilityFlagsReachable;
-    CFStringRef                        vpn_server_address = NULL;
+       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+           my_log(LOG_DEBUG,
+                  "Defer \"" _SC_NOTIFY_NETWORK_CHANGE "\" (%s, %s)",
+                  S_dnsinfo_synced ? "DNS" : "!DNS",
+                  S_nwi_synced     ? "nwi" : "!nwi");
+       }
+       return;
+    }
 
-    VPNAttributesGet(candidate->serviceID,
-                    services_info,
-                    &flags,
-                    &vpn_server_address,
-                    af);
+    // cancel any running timer
+    if (S_network_change_timer != NULL) {
+       dispatch_source_cancel(S_network_change_timer);
+       dispatch_release(S_network_change_timer);
+       S_network_change_timer = NULL;
+       S_network_change_timeout = FALSE;
+    }
 
-    candidate->reachability_flags = flags;
+    // set (and log?) the post time
+    if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+       struct timeval  elapsed;
+       struct timeval  end;
 
-    if (vpn_server_address == NULL) {
-       bzero(&candidate->vpn_server_addr, sizeof(candidate->vpn_server_addr));
-    } else {
-       char buf[128];
-       CFStringGetCString(vpn_server_address, buf, sizeof(buf), kCFStringEncodingASCII);
+       (void) gettimeofday(&end, NULL);
+       timersub(&end, &S_network_change_start, &elapsed);
 
-       _SC_string_to_sockaddr(buf,
-                              AF_UNSPEC,
-                              (void *)&candidate->vpn_server_addr,
-                              sizeof(candidate->vpn_server_addr));
+#define        QUERY_TIME__FMT "%ld.%6.6d"
+#define        QUERY_TIME__DIV 1
 
-       CFRelease(vpn_server_address);
+       my_log(LOG_DEBUG,
+              "Post \"" _SC_NOTIFY_NETWORK_CHANGE "\" (%s: " QUERY_TIME__FMT ": 0x%x)",
+              S_network_change_timeout ? "timeout" : "delayed",
+              elapsed.tv_sec,
+              elapsed.tv_usec / QUERY_TIME__DIV,
+              S_network_change_needed);
     }
-    return;
-}
-/*
- * Function: ElectionResultsCopyPrimary
- * Purpose:
- *   Use the results of the current protocol and the other protocol to
- *   determine which service should become primary.
- *
- *   At the same time, generate the nwi_state for the protocol.
- *
- *   For IPv4, also generate the IPv4 routing table.
- */
-static CFStringRef
-ElectionResultsCopyPrimary(ElectionResultsRef results,
-                          ElectionResultsRef other_results,
-                          nwi_state_t nwi_state, int af,
-                          IPv4RouteListRef * ret_routes,
-                          CFDictionaryRef services_info)
-{
-    CFStringRef                primary = NULL;
-    Boolean            primary_is_null = FALSE;
-    IPv4RouteListRef   routes = NULL;
 
-    if (nwi_state != NULL) {
-       nwi_state_clear(nwi_state, af);
+    if ((S_network_change_needed & NETWORK_CHANGE_NET) != 0) {
+       status = notify_post(_SC_NOTIFY_NETWORK_CHANGE_NWI);
+       if (status != NOTIFY_STATUS_OK) {
+           my_log(LOG_ERR,
+                  "IPMonitor: notify_post(" _SC_NOTIFY_NETWORK_CHANGE_NWI ") failed: error=%d", status);
+       }
     }
-    if (results != NULL) {
-       CandidateRef    deferred[results->count];
-       int             deferred_count;
-       int             i;
-       CandidateRef    scan;
-
-       deferred_count = 0;
-       for (i = 0, scan = results->candidates;
-            i < results->count;
-            i++, scan++) {
-           Boolean             is_primary = FALSE;
-           Rank                rank = scan->rank;
-           Boolean             skip = FALSE;
-
-           if (primary == NULL
-               && RANK_ASSERTION_MASK(rank) != kRankAssertionNever) {
-               if (ElectionResultsCandidateNeedsDemotion(other_results,
-                                                         scan)) {
-                   /* demote to RankNever */
-                   my_log(LOG_NOTICE,
-                          "IPv%c over %@ demoted: not primary for IPv%c",
-                          ipvx_char(af), scan->if_name, ipvx_other_char(af));
-                   rank = RankMake(rank, kRankAssertionNever);
-                   deferred[deferred_count++] = scan;
-                   skip = TRUE;
-               }
-               else {
-                   primary = CFRetain(scan->serviceID);
-                   is_primary = TRUE;
-               }
-           }
-           if (af == AF_INET) {
-               /* generate the routing table for IPv4 */
-               CFDictionaryRef         service_dict;
-               IPv4RouteListRef        service_routes;
-
-               service_dict
-                   = service_dict_get(scan->serviceID, kSCEntNetIPv4);
-               service_routes = ipv4_dict_get_routelist(service_dict);
-               if (service_routes != NULL) {
-                   routes = IPv4RouteListAddRouteList(routes,
-                                                      results->count * 3,
-                                                      service_routes,
-                                                      rank);
-                   if (service_routes->exclude_from_nwi) {
-                       skip = TRUE;
-                   }
-               }
-               else {
-                   skip = TRUE;
-               }
-           }
-           else {
-               /* a NULL service must be excluded from nwi */
-               CFDictionaryRef         ipv6_dict;
-
-               ipv6_dict = service_dict_get(scan->serviceID, kSCEntNetIPv6);
 
-               if (S_dict_get_boolean(ipv6_dict, kIsNULL, FALSE)) {
-                   skip = TRUE;
-               }
-           }
-           if (skip) {
-               /* if we're skipping the primary, it's NULL */
-               if (is_primary) {
-                   primary_is_null = TRUE;
-               }
-           }
-           else {
-               if (primary_is_null) {
-                   /* everything after the primary must be Never */
-                   rank = RankMake(rank, kRankAssertionNever);
-               }
-               add_reachability_flags_to_candidate(scan, services_info, af);
-               add_candidate_to_nwi_state(nwi_state, af, scan, rank);
-           }
+    if ((S_network_change_needed & NETWORK_CHANGE_DNS) != 0) {
+       status = notify_post(_SC_NOTIFY_NETWORK_CHANGE_DNS);
+       if (status != NOTIFY_STATUS_OK) {
+           my_log(LOG_ERR,
+                  "IPMonitor: notify_post(" _SC_NOTIFY_NETWORK_CHANGE_DNS ") failed: error=%d", status);
        }
-       for (i = 0; i < deferred_count; i++) {
-           CandidateRef        candidate = deferred[i];
-           Rank                rank;
+    }
 
-           /* demote to RankNever */
-           rank = RankMake(candidate->rank, kRankAssertionNever);
-           add_reachability_flags_to_candidate(candidate, services_info, af);
-           add_candidate_to_nwi_state(nwi_state, af, candidate, rank);
+    if ((S_network_change_needed & NETWORK_CHANGE_PROXY) != 0) {
+       status = notify_post(_SC_NOTIFY_NETWORK_CHANGE_PROXY);
+       if (status != NOTIFY_STATUS_OK) {
+           my_log(LOG_ERR,
+                  "IPMonitor: notify_post(" _SC_NOTIFY_NETWORK_CHANGE_PROXY ") failed: error=%d", status);
        }
     }
-    if (nwi_state != NULL) {
-       nwi_state_set_last(nwi_state, af);
+
+    status = notify_post(_SC_NOTIFY_NETWORK_CHANGE);
+    if (status != NOTIFY_STATUS_OK) {
+       my_log(LOG_ERR,
+              "IPMonitor: notify_post(" _SC_NOTIFY_NETWORK_CHANGE ") failed: error=%d", status);
     }
-    if (ret_routes != NULL) {
-       *ret_routes = routes;
+
+    S_network_change_needed = 0;
+    return;
+}
+
+#define TRAILING_EDGE_TIMEOUT_NSEC     5 * NSEC_PER_SEC    // 5s
+
+// Note: must run on __network_change_queue()
+static void
+post_network_change(uint32_t change)
+{
+    if (S_network_change_needed == 0) {
+       // set the start time
+       (void) gettimeofday(&S_network_change_start, NULL);
     }
-    else if (routes != NULL) {
-       free(routes);
+
+    // indicate that we need to post a change for ...
+    S_network_change_needed |= change;
+
+    // cancel any running timer
+    if (S_network_change_timer != NULL) {
+       dispatch_source_cancel(S_network_change_timer);
+       dispatch_release(S_network_change_timer);
+       S_network_change_timer = NULL;
+       S_network_change_timeout = FALSE;
     }
-    if (primary_is_null) {
-       my_CFRelease(&primary);
+
+    // if needed, start new timer
+    if (!S_dnsinfo_synced || !S_nwi_synced) {
+       S_network_change_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
+                                                       0,
+                                                       0,
+                                                       __network_change_queue());
+       dispatch_source_set_event_handler(S_network_change_timer, ^{
+           S_network_change_timeout = TRUE;
+           post_network_change_when_ready();
+       });
+       dispatch_source_set_timer(S_network_change_timer,
+                                 dispatch_time(DISPATCH_TIME_NOW,
+                                               TRAILING_EDGE_TIMEOUT_NSEC),    // start
+                                 DISPATCH_TIME_FOREVER,                        // interval
+                                 10 * NSEC_PER_MSEC);                          // leeway
+       dispatch_resume(S_network_change_timer);
     }
-    return (primary);
-}
 
+    post_network_change_when_ready();
 
-static inline
-CFStringRef
-service_dict_get_signature(CFDictionaryRef service_dict)
-{
-    return (CFDictionaryGetValue(service_dict, kStoreKeyNetworkSignature));
+    return;
 }
 
+#pragma mark -
+#pragma mark Process network (SCDynamicStore) changes
 
-/*
- * Function: elect_ipv4
- * Purpose:
- *   Evaluate the service and determine what rank the service should have.
- *   If it's a suitable candidate, add it to the election results.
- */
 static void
-elect_ipv4(const void * key, const void * value, void * context)
+IPMonitorProcessChanges(SCDynamicStoreRef session, CFArrayRef changed_keys,
+                       CFArrayRef if_rank_changes)
 {
-    Candidate          candidate;
-    CFStringRef                if_name;
-    ElectionInfoRef    info;
-    Rank               primary_rank;
-    CFDictionaryRef    service_dict = (CFDictionaryRef)value;
-    IPv4RouteListRef   service_routes;
-    CFDictionaryRef    v4_dict;
-    CFDictionaryRef    v4_service_dict;
-
-    service_routes = service_dict_get_ipv4_routelist(service_dict);
-    if (service_routes == NULL) {
-       /* no service routes, no service */
-       return;
+    CFIndex            count                   = 0;
+    uint32_t           changes                 = 0;
+    nwi_state_t                changes_state           = NULL;
+    boolean_t          dns_changed             = FALSE;
+    boolean_t          dnsinfo_changed         = FALSE;
+    boolean_t          global_ipv4_changed     = FALSE;
+    boolean_t          global_ipv6_changed     = FALSE;
+    CFIndex            i;
+    keyChangeList      keys;
+    CFIndex            n;
+    CFStringRef                network_change_msg      = NULL;
+    int                        n_services;
+    int                        n_service_order         = 0;
+    nwi_state_t                old_nwi_state           = NULL;
+    CFDictionaryRef    old_primary_dns         = NULL;
+    CFDictionaryRef    old_primary_proxy       = NULL;
+#if    !TARGET_OS_IPHONE
+    CFDictionaryRef    old_primary_smb         = NULL;
+#endif // !TARGET_OS_IPHONE
+    boolean_t          proxies_changed         = FALSE;
+    boolean_t          reachability_changed    = FALSE;
+    CFArrayRef         service_order;
+    CFMutableArrayRef  service_changes         = NULL;
+    CFDictionaryRef    services_info           = NULL;
+#if    !TARGET_OS_IPHONE
+    boolean_t          smb_changed             = FALSE;
+#endif // !TARGET_OS_IPHONE
+
+    /* populate name/index cache */
+    my_if_nameindex();
+
+    if (changed_keys != NULL) {
+       count = CFArrayGetCount(changed_keys);
+       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+           my_log(LOG_DEBUG,
+                  "IPMonitor: changed keys %@ (%ld)", changed_keys, count);
+       }
     }
-    if_name = service_dict_get_ipv4_ifname(service_dict);
-    if (if_name == NULL) {
-       /* need an interface name */
+    if (if_rank_changes == NULL && count == 0) {
        return;
     }
-    if (CFEqual(if_name, CFSTR("lo0"))) {
-       /* don't ever elect loopback */
-       return;
+
+    if (S_primary_dns != NULL) {
+       old_primary_dns = service_dict_get(S_primary_dns, kSCEntNetDNS);
+       if (old_primary_dns != NULL) {
+           old_primary_dns = CFDictionaryCreateCopy(NULL, old_primary_dns);
+       }
     }
-    info = (ElectionInfoRef)context;
-    bzero(&candidate, sizeof(candidate));
-    candidate.serviceID = (CFStringRef)key;
-    candidate.rank = get_service_rank(info->order, info->n_order,
-                                     candidate.serviceID);
-    primary_rank = RANK_ASSERTION_MASK(service_routes->list->rank);
-    if (S_ppp_override_primary
-       && (strncmp(PPP_PREFIX, service_routes->list->ifname,
-                   sizeof(PPP_PREFIX) - 1) == 0)) {
-       /* PPP override: make ppp* look the best */
-       /* Hack: should use interface type, not interface name */
-       primary_rank = kRankAssertionFirst;
+
+    if (S_primary_proxies != NULL) {
+       old_primary_proxy
+           = service_dict_get(S_primary_proxies, kSCEntNetProxies);
+       if (old_primary_proxy != NULL) {
+           old_primary_proxy = CFDictionaryCreateCopy(NULL, old_primary_proxy);
+       }
     }
-    candidate.rank = RankMake(candidate.rank, primary_rank);
-    candidate.ip_is_coupled = service_get_ip_is_coupled(candidate.serviceID);
-    candidate.if_name = if_name;
-    candidate.addr.v4 = service_routes->list->ifa;
-    rank_dict_set_service_rank(S_ipv4_service_rank_dict,
-                              candidate.serviceID, candidate.rank);
-    v4_dict = CFDictionaryGetValue(service_dict, kSCEntNetIPv4);
-    v4_service_dict = CFDictionaryGetValue(v4_dict, kIPv4DictService);
-    candidate.signature = service_dict_get_signature(v4_service_dict);
-    ElectionResultsAddCandidate(info->results, &candidate);
-    return;
-}
 
+#if    !TARGET_OS_IPHONE
+    if (S_primary_smb != NULL) {
+       old_primary_smb = service_dict_get(S_primary_smb, kSCEntNetSMB);
+       if (old_primary_smb != NULL) {
+           old_primary_smb = CFDictionaryCreateCopy(NULL, old_primary_smb);
+       }
+    }
+#endif // !TARGET_OS_IPHONE
+
+    keyChangeListInit(&keys);
+    service_changes = CFArrayCreateMutable(NULL, 0,
+                                          &kCFTypeArrayCallBacks);
 
-/*
- * Function: elect_ipv6
- * Purpose:
- *   Evaluate the service and determine what rank the service should have.
- *   If it's a suitable candidate, add it to the election results.
- */
-static void
-elect_ipv6(const void * key, const void * value, void * context)
-{
-    CFArrayRef         addrs;
-    Candidate          candidate;
-    CFStringRef                if_name;
-    ElectionInfoRef    info;
-    Rank               primary_rank = kRankAssertionDefault;
-    CFDictionaryRef    ipv6_dict;
-    CFStringRef                router;
-    CFDictionaryRef    service_dict = (CFDictionaryRef)value;
-    CFDictionaryRef    service_options;
+    for (i = 0; i < count; i++) {
+       CFStringRef     change = CFArrayGetValueAtIndex(changed_keys, i);
+       if (CFEqual(change, S_setup_global_ipv4)) {
+           global_ipv4_changed = TRUE;
+           global_ipv6_changed = TRUE;
+       }
+       else if (CFEqual(change, S_multicast_resolvers)) {
+           dnsinfo_changed = TRUE;
+       }
+       else if (CFEqual(change, S_private_resolvers)) {
+           dnsinfo_changed = TRUE;
+       }
+#if    !TARGET_OS_IPHONE
+       else if (CFEqual(change, CFSTR(_PATH_RESOLVER_DIR))) {
+           dnsinfo_changed = TRUE;
+       }
+#endif /* !TARGET_OS_IPHONE */
+       else if (CFStringHasPrefix(change, S_state_service_prefix)) {
+           CFStringRef serviceID;
 
+           serviceID = parse_component(change, S_state_service_prefix);
+           if (serviceID) {
+               my_CFArrayAppendUniqueValue(service_changes, serviceID);
+               CFRelease(serviceID);
+           }
+       }
+       else if (CFStringHasPrefix(change, S_setup_service_prefix)) {
+           int j;
 
-    ipv6_dict = CFDictionaryGetValue(service_dict, kSCEntNetIPv6);
-    if (ipv6_dict == NULL) {
-       /* no IPv6 */
-       return;
+           CFStringRef serviceID = parse_component(change,
+                                                   S_setup_service_prefix);
+           if (serviceID) {
+               my_CFArrayAppendUniqueValue(service_changes, serviceID);
+               CFRelease(serviceID);
+           }
+
+           for (j = 0; j < countof(transientInterfaceEntityNames); j++) {
+               if (CFStringHasSuffix(change,
+                                     *transientInterfaceEntityNames[j])) {
+                   reachability_changed = TRUE;
+                   break;
+               }
+           }
+
+           if (CFStringHasSuffix(change, kSCEntNetInterface)) {
+                reachability_changed = TRUE;
+           }
+       }
     }
-    if_name = CFDictionaryGetValue(ipv6_dict, kSCPropInterfaceName);
-    if (if_name == NULL) {
-       /* need an interface name */
-       return;
+
+    /* determine which serviceIDs are impacted by the interface rank changes */
+    if (if_rank_changes != NULL) {
+       n = CFArrayGetCount(if_rank_changes);
+       for (i = 0; i < n; i++) {
+           CFStringRef ifname = CFArrayGetValueAtIndex(if_rank_changes, i);
+
+           if (S_IPMonitor_debug & kDebugFlag1) {
+               my_log(LOG_DEBUG, "Interface rank changed %@",
+                      ifname);
+           }
+           append_serviceIDs_for_interface(service_changes, ifname);
+       }
     }
-    if (CFEqual(if_name, CFSTR("lo0"))) {
-       /* don't ever elect loopback */
-       return;
+
+    /* grab a snapshot of everything we need */
+    services_info = services_info_copy(session, service_changes);
+    service_order = service_order_get(services_info);
+    if (service_order != NULL) {
+       n_service_order = (int)CFArrayGetCount(service_order);
+       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+           my_log(LOG_DEBUG,
+                  "IPMonitor: service_order %@ ", service_order);
+       }
     }
-    router = CFDictionaryGetValue(ipv6_dict, kSCPropNetIPv6Router);
-    if (router == NULL) {
-       /* don't care about services without a router */
-       return;
+
+    n = CFArrayGetCount(service_changes);
+    for (i = 0; i < n; i++) {
+       uint32_t        changes;
+       CFStringRef     serviceID;
+
+       serviceID = CFArrayGetValueAtIndex(service_changes, i);
+       changes = service_changed(services_info, serviceID);
+       if ((changes & (1 << kEntityTypeServiceOptions)) != 0) {
+           /* if __Service__ (e.g. PrimaryRank) changed */
+           global_ipv4_changed = TRUE;
+           global_ipv6_changed = TRUE;
+       }
+       else {
+           if ((changes & (1 << kEntityTypeIPv4)) != 0) {
+               global_ipv4_changed = TRUE;
+               dnsinfo_changed = TRUE;
+               proxies_changed = TRUE;
+           }
+           if ((changes & (1 << kEntityTypeIPv6)) != 0) {
+               global_ipv6_changed = TRUE;
+               dnsinfo_changed = TRUE;
+               proxies_changed = TRUE;
+           }
+       }
+       if ((changes & (1 << kEntityTypeDNS)) != 0) {
+           if (S_primary_dns != NULL && CFEqual(S_primary_dns, serviceID)) {
+               dns_changed = TRUE;
+           }
+           dnsinfo_changed = TRUE;
+       }
+       if ((changes & (1 << kEntityTypeProxies)) != 0) {
+           proxies_changed = TRUE;
+       }
+#if    !TARGET_OS_IPHONE
+       if ((changes & (1 << kEntityTypeSMB)) != 0) {
+           if (S_primary_smb != NULL && CFEqual(S_primary_smb, serviceID)) {
+               smb_changed = TRUE;
+           }
+       }
+#endif
+       if ((changes & (1 << kEntityTypeTransientStatus)) != 0
+           && (service_dict_get(serviceID, kSCEntNetIPv4) != NULL
+               || service_dict_get(serviceID, kSCEntNetIPv6) != NULL)) {
+           dnsinfo_changed = TRUE;
+       }
     }
-    info = (ElectionInfoRef)context;
-    bzero(&candidate, sizeof(candidate));
-    candidate.serviceID = (CFStringRef)key;
-    candidate.if_name = if_name;
-    addrs = CFDictionaryGetValue(ipv6_dict, kSCPropNetIPv6Addresses);
-    (void)cfstring_to_ip6(CFArrayGetValueAtIndex(addrs, 0),
-                         &candidate.addr.v6);
-    candidate.rank = get_service_rank(info->order, info->n_order,
-                                     candidate.serviceID);
-    service_options
-       = service_dict_get(candidate.serviceID, kSCEntNetService);
-    if (service_options != NULL) {
-       CFStringRef     primaryRankStr = NULL;
 
-       primaryRankStr = CFDictionaryGetValue(service_options,
-                                             kSCPropNetServicePrimaryRank);
-       if (primaryRankStr != NULL) {
-           primary_rank = PrimaryRankGetRankAssertion(primaryRankStr);
+    /* ensure S_nwi_state can hold as many services as we have currently */
+    n_services = (int)CFDictionaryGetCount(S_service_state_dict);
+    old_nwi_state = nwi_state_copy_priv(S_nwi_state);
+    S_nwi_state = nwi_state_new(S_nwi_state, n_services);
+
+    if (global_ipv4_changed) {
+       if (S_ipv4_results != NULL) {
+           ElectionResultsRelease(S_ipv4_results);
+       }
+       S_ipv4_results
+           = ElectionResultsCopy(AF_INET, service_order, n_service_order);
+       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+           ElectionResultsLog(LOG_DEBUG, S_ipv4_results, "IPv4");
        }
-       candidate.ip_is_coupled
-           = CFDictionaryContainsKey(service_options, kIPIsCoupled);
     }
-    if (primary_rank != kRankAssertionNever) {
-       if (get_override_primary(ipv6_dict)) {
-           primary_rank = kRankAssertionFirst;
+    if (global_ipv6_changed) {
+       if (S_ipv6_results != NULL) {
+           ElectionResultsRelease(S_ipv6_results);
        }
-       else if (S_ppp_override_primary
-                && CFStringHasPrefix(if_name, CFSTR(PPP_PREFIX))) {
-           /* PPP override: make ppp* look the best */
-           /* Hack: should use interface type, not interface name */
-           primary_rank = kRankAssertionFirst;
+       S_ipv6_results
+           = ElectionResultsCopy(AF_INET6, service_order, n_service_order);
+       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+           ElectionResultsLog(LOG_DEBUG, S_ipv6_results, "IPv6");
        }
     }
-    candidate.rank = RankMake(candidate.rank, primary_rank);
-    rank_dict_set_service_rank(S_ipv6_service_rank_dict,
-                              candidate.serviceID, candidate.rank);
-    candidate.signature = service_dict_get_signature(ipv6_dict);
-    ElectionResultsAddCandidate(info->results, &candidate);
-    return;
-}
-
-static uint32_t
-service_changed(CFDictionaryRef services_info, CFStringRef serviceID)
-{
-    uint32_t           changed = 0;
-    int                        i;
+    if (global_ipv4_changed || global_ipv6_changed || dnsinfo_changed) {
+       CFStringRef             new_primary;
+       RouteListUnion          new_routelist;
 
-    /* update service options first (e.g. rank) */
-    if (get_rank_changes(serviceID,
-                        get_service_state_entity(services_info, serviceID,
-                                                 NULL),
-                        get_service_setup_entity(services_info, serviceID,
-                                                 NULL),
-                        services_info)) {
-       changed |= (1 << kEntityTypeServiceOptions);
-    }
-    /* update IPv4, IPv6, DNS, Proxies, SMB, ... */
-    for (i = 0; i < ENTITY_TYPES_COUNT; i++) {
-       GetEntityChangesFuncRef func = entityChangeFunc[i];
-       if ((*func)(serviceID,
-                   get_service_state_entity(services_info, serviceID,
-                                            *entityTypeNames[i]),
-                   get_service_setup_entity(services_info, serviceID,
-                                            *entityTypeNames[i]),
-                   services_info)) {
-           changed |= (1 << i);
+       /* IPv4 */
+       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+           my_log(LOG_DEBUG,
+                  "IPMonitor: electing IPv4 primary");
        }
-    }
+       new_routelist.ptr = NULL;
+       new_primary = ElectionResultsCopyPrimary(S_ipv4_results,
+                                                S_ipv6_results,
+                                                S_nwi_state, AF_INET,
+                                                &new_routelist.common,
+                                                services_info);
+       (void)set_new_primary(&S_primary_ipv4, new_primary, "IPv4");
+       update_ipv4(S_primary_ipv4, new_routelist.v4, &keys);
+       my_CFRelease(&new_primary);
 
-    if (get_transient_service_changes(serviceID, services_info)) {
-       changed |= (1 << kEntityTypeVPNStatus);
+       /* IPv6 */
+       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
+           my_log(LOG_DEBUG,
+                  "IPMonitor: electing IPv6 primary");
+       }
+       new_routelist.ptr = NULL;
+       new_primary = ElectionResultsCopyPrimary(S_ipv6_results,
+                                                S_ipv4_results,
+                                                S_nwi_state, AF_INET6,
+                                                &new_routelist.common,
+                                                services_info);
+       (void)set_new_primary(&S_primary_ipv6, new_primary, "IPv6");
+       update_ipv6(S_primary_ipv6, new_routelist.v6, &keys);
+       my_CFRelease(&new_primary);
     }
 
-    return (changed);
-}
-
-static CFArrayRef
-service_order_get(CFDictionaryRef services_info)
-{
-    CFArrayRef         order = NULL;
-    CFDictionaryRef    ipv4_dict;
-
-    ipv4_dict = my_CFDictionaryGetDictionary(services_info,
-                                            S_setup_global_ipv4);
-    if (ipv4_dict != NULL) {
-       CFNumberRef     ppp_override;
-       int             ppp_val = 0;
+    if (global_ipv4_changed || global_ipv6_changed) {
+       CFStringRef     new_primary_dns     = NULL;
+       CFStringRef     new_primary_proxies = NULL;
+#if    !TARGET_OS_IPHONE
+       CFStringRef     new_primary_smb     = NULL;
+#endif /* !TARGET_OS_IPHONE */
 
-       order = CFDictionaryGetValue(ipv4_dict, kSCPropNetServiceOrder);
-       order = isA_CFArray(order);
+       if (S_primary_ipv4 != NULL && S_primary_ipv6 != NULL) {
+           /* decide between IPv4 and IPv6 */
+           if (rank_service_entity(S_ipv4_service_rank_dict,
+                                   S_primary_ipv4, kSCEntNetDNS)
+               <= rank_service_entity(S_ipv6_service_rank_dict,
+                                      S_primary_ipv6, kSCEntNetDNS)) {
+               new_primary_dns = S_primary_ipv4;
+           }
+           else {
+               new_primary_dns = S_primary_ipv6;
+           }
+           if (rank_service_entity(S_ipv4_service_rank_dict,
+                                   S_primary_ipv4, kSCEntNetProxies)
+               <= rank_service_entity(S_ipv6_service_rank_dict,
+                                      S_primary_ipv6, kSCEntNetProxies)) {
+               new_primary_proxies = S_primary_ipv4;
+           }
+           else {
+               new_primary_proxies = S_primary_ipv6;
+           }
+#if    !TARGET_OS_IPHONE
+           if (rank_service_entity(S_ipv4_service_rank_dict,
+                                   S_primary_ipv4, kSCEntNetSMB)
+               <= rank_service_entity(S_ipv6_service_rank_dict,
+                                      S_primary_ipv6, kSCEntNetSMB)) {
+               new_primary_smb = S_primary_ipv4;
+           }
+           else {
+               new_primary_smb = S_primary_ipv6;
+           }
+#endif /* !TARGET_OS_IPHONE */
 
-       /* get ppp override primary */
-       ppp_override = CFDictionaryGetValue(ipv4_dict,
-                                           kSCPropNetPPPOverridePrimary);
-       ppp_override = isA_CFNumber(ppp_override);
-       if (ppp_override != NULL) {
-           CFNumberGetValue(ppp_override, kCFNumberIntType, &ppp_val);
        }
-       S_ppp_override_primary = (ppp_val != 0) ? TRUE : FALSE;
-    }
-    else {
-       S_ppp_override_primary = FALSE;
-    }
-    return (order);
-}
-
-static boolean_t
-set_new_primary(CFStringRef * primary_p, CFStringRef new_primary,
-               const char * entity)
-{
-    boolean_t          changed = FALSE;
-    CFStringRef                primary = *primary_p;
+       else if (S_primary_ipv6 != NULL) {
+           new_primary_dns     = S_primary_ipv6;
+           new_primary_proxies = S_primary_ipv6;
+#if    !TARGET_OS_IPHONE
+           new_primary_smb     = S_primary_ipv6;
+#endif /* !TARGET_OS_IPHONE */
+       }
+       else if (S_primary_ipv4 != NULL) {
+           new_primary_dns     = S_primary_ipv4;
+           new_primary_proxies = S_primary_ipv4;
+#if    !TARGET_OS_IPHONE
+           new_primary_smb     = S_primary_ipv4;
+#endif /* !TARGET_OS_IPHONE */
+       }
 
-    if (new_primary != NULL) {
-       if (primary != NULL && CFEqual(new_primary, primary)) {
-           if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-               my_log(LOG_DEBUG,
-                      "IPMonitor: %@ is still primary %s",
-                      new_primary, entity);
-           }
+       if (set_new_primary(&S_primary_dns, new_primary_dns, "DNS")) {
+           dns_changed = TRUE;
+           dnsinfo_changed = TRUE;
        }
-       else {
-           my_CFRelease(primary_p);
-           *primary_p = CFRetain(new_primary);
-           if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-               my_log(LOG_DEBUG,
-                      "IPMonitor: %@ is the new primary %s",
-                      new_primary, entity);
-           }
-           changed = TRUE;
+       if (set_new_primary(&S_primary_proxies, new_primary_proxies,
+                           "Proxies")) {
+           proxies_changed = TRUE;
        }
-    }
-    else if (primary != NULL) {
-       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-           my_log(LOG_DEBUG,
-                  "IPMonitor: %@ is no longer primary %s",
-                  primary, entity);
+#if    !TARGET_OS_IPHONE
+       if (set_new_primary(&S_primary_smb, new_primary_smb, "SMB")) {
+           smb_changed = TRUE;
        }
-       my_CFRelease(primary_p);
-       changed = TRUE;
+#endif /* !TARGET_OS_IPHONE */
     }
-    return (changed);
-}
 
-static Rank
-rank_service_entity(CFDictionaryRef rank_dict, CFStringRef serviceID,
-                   CFStringRef entity)
-{
-    if (service_dict_get(serviceID, entity) == NULL) {
-       return (RankMake(kRankIndexMask, kRankAssertionDefault));
+    if (!proxies_changed && dnsinfo_changed
+       && ((G_supplemental_proxies_follow_dns != NULL)
+           && CFBooleanGetValue(G_supplemental_proxies_follow_dns))) {
+       proxies_changed = TRUE;
     }
-    return (rank_dict_get_service_rank(rank_dict, serviceID));
-}
 
-static void
-update_interface_rank(CFDictionaryRef services_info, CFStringRef ifname)
-{
-    CFStringRef                if_rank_key;
-    CFDictionaryRef    rank_dict;
-
-    if_rank_key = if_rank_key_copy(ifname);
-    rank_dict = CFDictionaryGetValue(services_info, if_rank_key);
-    CFRelease(if_rank_key);
-    if_rank_set(ifname, rank_dict);
-    return;
-}
+    changes_state = nwi_state_diff(old_nwi_state, S_nwi_state);
 
-static void
-append_serviceIDs_for_interface(CFMutableArrayRef services_changed,
-                               CFStringRef ifname)
-{
-    int                        count;
-    int                        i;
-    void * *           keys;
-#define N_KEYS_VALUES_STATIC    10
-    void *             keys_values_buf[N_KEYS_VALUES_STATIC * 2];
-    void * *           values;
+    if (global_ipv4_changed || global_ipv6_changed
+       || dnsinfo_changed || reachability_changed) {
+       if (S_nwi_state != NULL) {
+           S_nwi_state->generation_count = mach_absolute_time();
+           if (global_ipv4_changed || global_ipv6_changed
+               || reachability_changed) {
+               SCNetworkReachabilityFlags reach_flags_v4 = 0;
+               SCNetworkReachabilityFlags reach_flags_v6 = 0;
 
-    count = CFDictionaryGetCount(S_service_state_dict);
-    if (count <= N_KEYS_VALUES_STATIC) {
-       keys = keys_values_buf;
-    } else {
-       keys = (void * *)malloc(sizeof(*keys) * count * 2);
-    }
-    values = keys + count;
-    CFDictionaryGetKeysAndValues(S_service_state_dict,
-                                (const void * *)keys,
-                                (const void * *)values);
+               GetReachabilityFlagsFromTransientServices(services_info,
+                                                         &reach_flags_v4,
+                                                         &reach_flags_v6);
 
-    for (i = 0; i < count; i++) {
-       CFDictionaryRef         ipv4 = NULL;
-       CFStringRef             interface = NULL;
-       CFStringRef             serviceID;
-       CFDictionaryRef         service_dict;
+               _nwi_state_set_reachability_flags(S_nwi_state, reach_flags_v4,
+                                                 reach_flags_v6);
+           }
 
-       serviceID = (CFStringRef)keys[i];
-       service_dict = (CFDictionaryRef)values[i];
+           /* Update the per-interface generation count */
+           _nwi_state_update_interface_generations(old_nwi_state, S_nwi_state,
+                                                   changes_state);
+       }
 
-       /* check if this is a ipv4 dictionary */
-       ipv4  = CFDictionaryGetValue(service_dict, kSCEntNetIPv4);
-       if (ipv4 != NULL) {
-           interface = ipv4_dict_get_ifname(ipv4);
-           if (interface != NULL && CFEqual(interface, ifname)) {
-               if (S_IPMonitor_debug & kDebugFlag1) {
-                   my_log(LOG_DEBUG,
-                          "Found ipv4 service %@ on interface %@.",
-                          serviceID, ifname);
-               }
+       if (update_nwi(S_nwi_state)) {
+           changes |= NETWORK_CHANGE_NET;
 
-               my_CFArrayAppendUniqueValue(services_changed, serviceID);
-           }
+           /*
+            * the DNS configuration includes per-resolver configuration
+            * reachability flags that are based on the nwi state.  Let's
+            * make sure that we check for changes
+            */
+           dnsinfo_changed = TRUE;
+       }
+    }
+    if (dns_changed) {
+       if (update_dns(services_info, S_primary_dns, &keys)) {
+           changes |= NETWORK_CHANGE_DNS;
+           dnsinfo_changed = TRUE;
+       } else {
+           dns_changed = FALSE;
+       }
+    }
+    if (dnsinfo_changed) {
+       if (update_dnsinfo(services_info, S_primary_dns,
+                          &keys, service_order)) {
+           changes |= NETWORK_CHANGE_DNS;
        } else {
-           CFDictionaryRef     proto_dict;
-
-           /* check if this is a ipv6 dictionary */
-           proto_dict = CFDictionaryGetValue(service_dict, kSCEntNetIPv6);
-           if (proto_dict == NULL) {
-               continue;
-           }
-           interface = CFDictionaryGetValue(proto_dict, kSCPropInterfaceName);
-           if (interface != NULL && CFEqual(interface, ifname)) {
-               if (S_IPMonitor_debug & kDebugFlag1) {
-                   my_log(LOG_DEBUG, "Found ipv6 service %@ on interface %@.",
-                          serviceID, ifname);
-               }
-
-               my_CFArrayAppendUniqueValue(services_changed, serviceID);
-           }
+           dnsinfo_changed = FALSE;
+       }
+    }
+    if (proxies_changed) {
+       // if proxy change OR supplemental Proxies follow supplemental DNS
+       if (update_proxies(services_info, S_primary_proxies,
+                          &keys, service_order)) {
+           changes |= NETWORK_CHANGE_PROXY;
+       } else {
+           proxies_changed = FALSE;
+       }
+    }
+#if    !TARGET_OS_IPHONE
+    if (smb_changed) {
+       if (update_smb(services_info, S_primary_smb, &keys)) {
+           changes |= NETWORK_CHANGE_SMB;
+       } else {
+           smb_changed = FALSE;
        }
     }
+#endif /* !TARGET_OS_IPHONE */
+    my_CFRelease(&service_changes);
+    my_CFRelease(&services_info);
 
-    if (keys != keys_values_buf) {
-       free(keys);
+    if (changes != 0) {
+       network_change_msg =
+           generate_log_changes(changes_state,
+                                dns_changed,
+                                dnsinfo_changed,
+                                old_primary_dns,
+                                proxies_changed,
+                                old_primary_proxy,
+#if    !TARGET_OS_IPHONE
+                                smb_changed,
+                                old_primary_smb
+#else  // !TARGET_OS_IPHONE
+                                FALSE,         // smb_changed
+                                NULL           // old_primary_smb
+#endif // !TARGET_OS_IPHONE
+                                );
     }
-}
 
-static __inline__ const char *
-get_changed_str(CFStringRef serviceID, CFStringRef entity, CFDictionaryRef old_dict)
-{
-    CFDictionaryRef new_dict    = NULL;
+    keyChangeListApplyToStore(&keys, session);
+    my_CFRelease(&old_primary_dns);
+    my_CFRelease(&old_primary_proxy);
+#if    !TARGET_OS_IPHONE
+    my_CFRelease(&old_primary_smb);
+#endif // !TARGET_OS_IPHONE
 
-    if (serviceID != NULL) {
-       new_dict = service_dict_get(serviceID, entity);
+    if (changes != 0) {
+       dispatch_async(__network_change_queue(), ^{
+           post_network_change(changes);
+       });
     }
 
-    if (old_dict == NULL) {
-       if (new_dict != NULL) {
-           return "+";
-       }
+    if ((network_change_msg != NULL)
+       && (CFStringGetLength(network_change_msg) != 0)) {
+       my_log(LOG_NOTICE, "network changed:%@", network_change_msg);
+    } else if (keyChangeListActive(&keys)) {
+       my_log(LOG_NOTICE, "network changed.");
     } else {
-       if (new_dict == NULL) {
-           return "-";
-       } else if (!CFEqual(old_dict, new_dict)) {
-           return "!";
-       }
+       my_log(LOG_DEBUG, "network event w/no changes");
     }
-    return "";
-}
-
-static CF_RETURNS_RETAINED CFStringRef
-generate_log_changes(nwi_state_t       changes_state,
-                    boolean_t          dns_changed,
-                    boolean_t          dnsinfo_changed,
-                    CFDictionaryRef    old_primary_dns,
-                    boolean_t          proxy_changed,
-                    CFDictionaryRef    old_primary_proxy,
-                    boolean_t          smb_changed,
-                    CFDictionaryRef    old_primary_smb
-                    )
-{
-    int idx;
-    CFMutableStringRef log_output;
-    nwi_ifstate_t scan;
 
-    log_output = CFStringCreateMutable(NULL, 0);
+    my_CFRelease(&network_change_msg);
 
     if (changes_state != NULL) {
-       for (idx = 0; idx < sizeof(nwi_af_list)/sizeof(nwi_af_list[0]); idx++) {
-           CFMutableStringRef changes = NULL;
-           CFMutableStringRef primary_str = NULL;
-
-           scan = nwi_state_get_first_ifstate(changes_state, nwi_af_list[idx]);
-
-           while (scan != NULL) {
-               const char * changed_str;
-
-               changed_str = nwi_ifstate_get_diff_str(scan);
-               if (changed_str != NULL) {
-                   void *              address;
-                   const char *        addr_str;
-                   char                ntopbuf[INET6_ADDRSTRLEN];
-
-                   address = (void *)nwi_ifstate_get_address(scan);
-                   addr_str = inet_ntop(scan->af, address,
-                               ntopbuf, sizeof(ntopbuf));
+       nwi_state_release(changes_state);
+    }
+    if (old_nwi_state != NULL) {
+       nwi_state_release(old_nwi_state);
+    }
+    keyChangeListFree(&keys);
 
-                   if (primary_str ==  NULL) {
-                       primary_str = CFStringCreateMutable(NULL, 0);
-                       CFStringAppendFormat(primary_str, NULL, CFSTR("%s%s:%s"),
-                                            nwi_ifstate_get_ifname(scan),
-                                            changed_str, addr_str);
-                   } else {
-                       if (changes == NULL) {
-                           changes = CFStringCreateMutable(NULL, 0);
-                       }
-                       CFStringAppendFormat(changes, NULL, CFSTR(", %s"),
-                                            nwi_ifstate_get_ifname(scan));
-                       if (strcmp(changed_str,  "") != 0) {
-                           CFStringAppendFormat(changes, NULL, CFSTR("%s:%s"),
-                                                changed_str, addr_str);
-                       }
-                   }
-               }
-               scan = nwi_ifstate_get_next(scan, scan->af);
-           }
+    /* release the name/index cache */
+    my_if_freenameindex();
 
-           if (primary_str != NULL) {
-               CFStringAppendFormat(log_output, NULL, CFSTR(" %s(%@"),
-                                    nwi_af_list[idx] == AF_INET ? "v4" : "v6",
-                                    primary_str);
+    return;
+}
 
-               if (changes != NULL && CFStringGetLength(changes) != 0) {
-                   CFStringAppendFormat(log_output, NULL, CFSTR("%@"),
-                                        changes);
-               }
-               CFStringAppendFormat(log_output, NULL, CFSTR(")"));
+static void
+IPMonitorNotify(SCDynamicStoreRef session, CFArrayRef changed_keys,
+               void * not_used)
+{
+    IPMonitorProcessChanges(session, changed_keys, NULL);
+    return;
+}
 
-               my_CFRelease(&primary_str);
-               my_CFRelease(&changes);
-           }
-       }
-    }
+static void
+watch_proxies()
+{
+#if    !TARGET_OS_IPHONE
+    const _scprefs_observer_type type = scprefs_observer_type_mcx;
+#else
+    const _scprefs_observer_type type = scprefs_observer_type_global;
+#endif
+    static dispatch_queue_t proxy_cb_queue;
 
-    if (dns_changed || dnsinfo_changed) {
-       const char    *str;
+    proxy_cb_queue = dispatch_queue_create("com.apple.SystemConfiguration.IPMonitor.proxy", NULL);
+    _scprefs_observer_watch(type,
+                            "com.apple.SystemConfiguration.plist",
+                            proxy_cb_queue,
+                            ^{
+                                SCDynamicStoreNotifyValue(NULL, S_state_global_proxies);
+                                notify_post(_SC_NOTIFY_NETWORK_CHANGE_PROXY);
+                                my_log(LOG_DEBUG, "IPMonitor: Notifying:\n%@",
+                                       S_state_global_proxies);
+                            });
+    return;
+}
 
-       str = get_changed_str(S_primary_dns, kSCEntNetDNS, old_primary_dns);
-       if ((strcmp(str, "") == 0) && dnsinfo_changed) {
-           str = "*";      // dnsinfo change w/no change to primary
-       }
-       CFStringAppendFormat(log_output, NULL, CFSTR(" DNS%s"), str);
-    } else if (S_primary_dns != NULL) {
-       CFStringAppendFormat(log_output, NULL, CFSTR(" DNS"));
-    }
+#include "IPMonitorControlPrefs.h"
 
-    if (proxy_changed) {
-       const char    *str;
+__private_extern__ SCLoggerRef
+my_log_get_logger()
+{
+    return (S_IPMonitor_logger);
+}
 
-       str = get_changed_str(S_primary_proxies, kSCEntNetProxies, old_primary_proxy);
-       CFStringAppendFormat(log_output, NULL, CFSTR(" Proxy%s"), str);
-    } else if (S_primary_proxies != NULL) {
-       CFStringAppendFormat(log_output, NULL, CFSTR(" Proxy"));
+static void
+prefs_changed(__unused SCPreferencesRef prefs)
+{
+    if (S_bundle_logging_verbose || IPMonitorControlPrefsIsVerbose()) {
+       S_IPMonitor_debug = kDebugFlagDefault;
+       S_IPMonitor_verbose = TRUE;
+       SCLoggerSetFlags(S_IPMonitor_logger, kSCLoggerFlagsFile | kSCLoggerFlagsDefault);
+       my_log(LOG_DEBUG, "IPMonitor: Setting logging verbose mode on.");
+    } else {
+       my_log(LOG_DEBUG, "IPMonitor: Setting logging verbose mode off.");
+       S_IPMonitor_debug = 0;
+       S_IPMonitor_verbose = FALSE;
+       SCLoggerSetFlags(S_IPMonitor_logger, kSCLoggerFlagsDefault);
     }
+    return;
+}
 
-#if    !TARGET_OS_IPHONE
-    if (smb_changed) {
-       const char    *str;
-
-       str = get_changed_str(S_primary_smb, kSCEntNetSMB, old_primary_smb);
-       CFStringAppendFormat(log_output, NULL, CFSTR(" SMB%s"), str);
-    } else if (S_primary_smb != NULL) {
-       CFStringAppendFormat(log_output, NULL, CFSTR(" SMB"));
+#define LOGGER_ID CFSTR("com.apple.networking.IPMonitor")
+static void
+my_log_init()
+{
+    if (S_IPMonitor_logger != NULL) {
+       return;
     }
-#endif // !TARGET_OS_IPHONE
+    S_IPMonitor_logger = SCLoggerCreate(LOGGER_ID);
+    return;
 
-    return log_output;
 }
 
-#pragma mark -
-#pragma mark Network changed notification
 
-static dispatch_queue_t
-__network_change_queue()
+#if    !TARGET_IPHONE_SIMULATOR
+static int
+flush_routes(int s)
 {
-    static dispatch_once_t     once;
-    static dispatch_queue_t    q;
+    char *             buf = NULL;
+    int                        i;
+    char *             lim;
+#define N_MIB          6
+    int                mib[N_MIB];
+    size_t             needed;
+    char *             next;
+    struct rt_msghdr * rtm;
+    struct sockaddr_in *sin;
+
+    mib[0] = CTL_NET;
+    mib[1] = PF_ROUTE;
+    mib[2] = 0;
+    mib[3] = AF_INET;
+    mib[4] = NET_RT_FLAGS;
+    mib[5] = RTF_STATIC | RTF_DYNAMIC;
+    for (i = 0; i < 3; i++) {
+       if (sysctl(mib, N_MIB, NULL, &needed, NULL, 0) < 0) {
+           break;
+       }
+       if ((buf = malloc(needed)) == NULL) {
+           break;
+       }
+       if (sysctl(mib, N_MIB, buf, &needed, NULL, 0) >= 0) {
+           break;
+       }
+       free(buf);
+       buf = NULL;
+    }
+    if (buf == NULL) {
+       return (-1);
+    }
+    lim = buf + needed;
+    for (next = buf; next < lim; next += rtm->rtm_msglen) {
+       uint32_t        addr;
 
-    dispatch_once(&once, ^{
-       q = dispatch_queue_create("network change queue", NULL);
-    });
+       /* ALIGN: assume kernel provides necessary alignment */
+       rtm = (struct rt_msghdr *)(void *)next;
+       sin = (struct sockaddr_in *)(rtm + 1);
 
-    return q;
+       addr = ntohl(sin->sin_addr.s_addr);
+       if (IN_LOOPBACK(addr)) {
+           my_log(LOG_DEBUG,
+                  "IPMonitor: flush_routes: ignoring loopback route");
+           continue;
+       }
+       if (IN_LOCAL_GROUP(addr)) {
+           my_log(LOG_DEBUG,
+                  "IPMonitor: flush_routes: ignoring multicast route");
+           continue;
+       }
+       rtm->rtm_type = RTM_DELETE;
+       rtm->rtm_seq = ++rtm_seq;
+       if (write(s, rtm, rtm->rtm_msglen) < 0) {
+           my_log(LOG_DEBUG,
+                  "IPMonitor: flush_routes: removing route for "
+                  IP_FORMAT " failed, %s",
+                  IP_LIST(&sin->sin_addr),
+                  strerror(errno));
+       }
+       else {
+           my_log(LOG_DEBUG,
+                  "IPMonitor: flush_routes: removed route for " IP_FORMAT,
+                  IP_LIST(&sin->sin_addr));
+       }
+    }
+    free(buf);
+    return (0);
 }
 
-// Note: must run on __network_change_queue()
 static void
-post_network_change_when_ready()
+flush_inet_routes(void)
 {
-    int                    status;
+    int        s;
 
-    if (S_network_change_needed == 0) {
-       return;
+    s = open_routing_socket();
+    if (s != -1) {
+       flush_routes(s);
+       close(s);
     }
+}
 
-    if (!S_network_change_timeout &&
-       (!S_dnsinfo_synced || !S_nwi_synced)) {
-       // if we [still] need to wait for the DNS configuration
-       // or network information changes to be ack'd
+#else  /* !TARGET_IPHONE_SIMULATOR */
 
-       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-           my_log(LOG_DEBUG,
-                  "Defer \"" _SC_NOTIFY_NETWORK_CHANGE "\" (%s, %s)",
-                  S_dnsinfo_synced ? "DNS" : "!DNS",
-                  S_nwi_synced     ? "nwi" : "!nwi");
-       }
-       return;
-    }
+static void
+flush_inet_routes(void)
+{
+}
 
-    // cancel any running timer
-    if (S_network_change_timer != NULL) {
-       dispatch_source_cancel(S_network_change_timer);
-       dispatch_release(S_network_change_timer);
-       S_network_change_timer = NULL;
-       S_network_change_timeout = FALSE;
-    }
+#endif /* !TARGET_IPHONE_SIMULATOR */
 
-    // set (and log?) the post time
-    if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-       struct timeval  elapsed;
-       struct timeval  end;
 
-       (void) gettimeofday(&end, NULL);
-       timersub(&end, &S_network_change_start, &elapsed);
 
-#define        QUERY_TIME__FMT "%d.%6.6d"
-#define        QUERY_TIME__DIV 1
+static void
+ip_plugin_init()
+{
+    CFMutableArrayRef  keys = NULL;
+    CFStringRef                pattern;
+    CFMutableArrayRef  patterns = NULL;
+    CFRunLoopSourceRef rls = NULL;
 
-       my_log(LOG_DEBUG,
-              "Post \"" _SC_NOTIFY_NETWORK_CHANGE "\" (%s: " QUERY_TIME__FMT ": 0x%x)",
-              S_network_change_timeout ? "timeout" : "delayed",
-              elapsed.tv_sec,
-              elapsed.tv_usec / QUERY_TIME__DIV,
-              S_network_change_needed);
+    if (S_is_network_boot() != 0) {
+       S_netboot = TRUE;
     }
-
-    if ((S_network_change_needed & NETWORK_CHANGE_NET) != 0) {
-       status = notify_post(_SC_NOTIFY_NETWORK_CHANGE_NWI);
-       if (status != NOTIFY_STATUS_OK) {
-           my_log(LOG_ERR,
-                  "IPMonitor: notify_post(" _SC_NOTIFY_NETWORK_CHANGE_NWI ") failed: error=%ld", status);
-       }
+    else {
+       /* flush routes */
+       flush_inet_routes();
     }
 
-    if ((S_network_change_needed & NETWORK_CHANGE_DNS) != 0) {
-       status = notify_post(_SC_NOTIFY_NETWORK_CHANGE_DNS);
-       if (status != NOTIFY_STATUS_OK) {
-           my_log(LOG_ERR,
-                  "IPMonitor: notify_post(" _SC_NOTIFY_NETWORK_CHANGE_DNS ") failed: error=%ld", status);
-       }
+    if (S_is_scoped_routing_enabled() != 0) {
+       S_scopedroute = TRUE;
     }
 
-    if ((S_network_change_needed & NETWORK_CHANGE_PROXY) != 0) {
-       status = notify_post(_SC_NOTIFY_NETWORK_CHANGE_PROXY);
-       if (status != NOTIFY_STATUS_OK) {
-           my_log(LOG_ERR,
-                  "IPMonitor: notify_post(" _SC_NOTIFY_NETWORK_CHANGE_PROXY ") failed: error=%ld", status);
-       }
+    if (S_is_scoped_v6_routing_enabled() != 0) {
+       S_scopedroute_v6 = TRUE;
     }
 
-    status = notify_post(_SC_NOTIFY_NETWORK_CHANGE);
-    if (status != NOTIFY_STATUS_OK) {
+    S_session = SCDynamicStoreCreate(NULL, CFSTR("IPMonitor"),
+                                  IPMonitorNotify, NULL);
+    if (S_session == NULL) {
        my_log(LOG_ERR,
-              "IPMonitor: notify_post(" _SC_NOTIFY_NETWORK_CHANGE ") failed: error=%ld", status);
+              "IPMonitor ip_plugin_init SCDynamicStoreCreate failed: %s",
+              SCErrorString(SCError()));
+       return;
     }
+    S_state_global_ipv4
+       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                    kSCDynamicStoreDomainState,
+                                                    kSCEntNetIPv4);
+    S_state_global_ipv6
+       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                    kSCDynamicStoreDomainState,
+                                                    kSCEntNetIPv6);
+    S_state_global_dns
+       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                    kSCDynamicStoreDomainState,
+                                                    kSCEntNetDNS);
+    S_state_global_proxies
+       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                    kSCDynamicStoreDomainState,
+                                                    kSCEntNetProxies);
+#if    !TARGET_OS_IPHONE
+    S_state_global_smb
+       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                    kSCDynamicStoreDomainState,
+                                                    kSCEntNetSMB);
+#endif /* !TARGET_OS_IPHONE */
+    S_setup_global_ipv4
+       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
+                                                    kSCDynamicStoreDomainSetup,
+                                                    kSCEntNetIPv4);
+    S_state_service_prefix
+       = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                     kSCDynamicStoreDomainState,
+                                                     CFSTR(""),
+                                                     NULL);
+    S_setup_service_prefix
+       = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                     kSCDynamicStoreDomainSetup,
+                                                     CFSTR(""),
+                                                     NULL);
+    S_service_state_dict
+       = CFDictionaryCreateMutable(NULL, 0,
+                                   &kCFTypeDictionaryKeyCallBacks,
+                                   &kCFTypeDictionaryValueCallBacks);
 
-    S_network_change_needed = 0;
-    return;
-}
-
-#define TRAILING_EDGE_TIMEOUT_NSEC     5 * NSEC_PER_SEC    // 5s
-
-// Note: must run on __network_change_queue()
-static void
-post_network_change(uint32_t change)
-{
-    if (S_network_change_needed == 0) {
-       // set the start time
-       (void) gettimeofday(&S_network_change_start, NULL);
-    }
+    S_ipv4_service_rank_dict
+       = CFDictionaryCreateMutable(NULL, 0,
+                                   &kCFTypeDictionaryKeyCallBacks,
+                                   &kCFTypeDictionaryValueCallBacks);
 
-    // indicate that we need to post a change for ...
-    S_network_change_needed |= change;
+    S_ipv6_service_rank_dict
+       = CFDictionaryCreateMutable(NULL, 0,
+                                   &kCFTypeDictionaryKeyCallBacks,
+                                   &kCFTypeDictionaryValueCallBacks);
 
-    // cancel any running timer
-    if (S_network_change_timer != NULL) {
-       dispatch_source_cancel(S_network_change_timer);
-       dispatch_release(S_network_change_timer);
-       S_network_change_timer = NULL;
-       S_network_change_timeout = FALSE;
-    }
+    keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+    patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
 
-    // if needed, start new timer
-    if (!S_dnsinfo_synced || !S_nwi_synced) {
-       S_network_change_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
-                                                       0,
-                                                       0,
-                                                       __network_change_queue());
-       dispatch_source_set_event_handler(S_network_change_timer, ^{
-           S_network_change_timeout = TRUE;
-           post_network_change_when_ready();
-       });
-       dispatch_source_set_timer(S_network_change_timer,
-                                 dispatch_time(DISPATCH_TIME_NOW,
-                                               TRAILING_EDGE_TIMEOUT_NSEC),    // start
-                                 0,                                            // interval
-                                 10 * NSEC_PER_MSEC);                          // leeway
-       dispatch_resume(S_network_change_timer);
-    }
+    /* register for State: and Setup: per-service notifications */
+    add_service_keys(kSCCompAnyRegex, keys, patterns);
 
-    post_network_change_when_ready();
+    pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetPPP);
+    CFArrayAppendValue(patterns, pattern);
+    CFRelease(pattern);
 
-    return;
-}
+    pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetVPN);
+    CFArrayAppendValue(patterns, pattern);
+    CFRelease(pattern);
 
-#pragma mark -
-#pragma mark Process network (SCDynamicStore) changes
+    pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetInterface);
+    CFArrayAppendValue(patterns, pattern);
+    CFRelease(pattern);
 
-static void
-IPMonitorNotify(SCDynamicStoreRef session, CFArrayRef changed_keys,
-               void * not_used)
-{
-    CFIndex            count;
-    uint32_t           changes                 = 0;
-    nwi_state_t                changes_state           = NULL;
-    boolean_t          dns_changed             = FALSE;
-    boolean_t          dnsinfo_changed         = FALSE;
-    boolean_t          global_ipv4_changed     = FALSE;
-    boolean_t          global_ipv6_changed     = FALSE;
-    int                        i;
-    CFMutableArrayRef  if_rank_changes         = NULL;
-    keyChangeList      keys;
-    CFIndex            n;
-    CFStringRef                network_change_msg      = NULL;
-    int                        n_services;
-    int                        n_service_order         = 0;
-    nwi_state_t                old_nwi_state           = NULL;
-    CFDictionaryRef    old_primary_dns         = NULL;
-    CFDictionaryRef    old_primary_proxy       = NULL;
-#if    !TARGET_OS_IPHONE
-    CFDictionaryRef    old_primary_smb         = NULL;
-#endif // !TARGET_OS_IPHONE
-    boolean_t          proxies_changed         = FALSE;
-    boolean_t          reachability_changed    = FALSE;
-    CFArrayRef         service_order;
-    CFMutableArrayRef  service_changes         = NULL;
-    CFDictionaryRef    services_info           = NULL;
-#if    !TARGET_OS_IPHONE
-    boolean_t          smb_changed             = FALSE;
-#endif // !TARGET_OS_IPHONE
+    /* register for State: per-service PPP/VPN/IPSec status notifications */
+    add_transient_status_keys(kSCCompAnyRegex, patterns);
 
-    count = CFArrayGetCount(changed_keys);
-    if (count == 0) {
-       return;
-    }
+    /* add notifier for ServiceOrder/PPPOverridePrimary changes for IPv4 */
+    CFArrayAppendValue(keys, S_setup_global_ipv4);
 
-    if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-       my_log(LOG_DEBUG,
-              "IPMonitor: changes %@ (%d)", changed_keys, count);
-    }
+    /* add notifier for multicast DNS configuration (Bonjour/.local) */
+    S_multicast_resolvers = SCDynamicStoreKeyCreate(NULL, CFSTR("%@/%@/%@"),
+                                                   kSCDynamicStoreDomainState,
+                                                   kSCCompNetwork,
+                                                   CFSTR(kDNSServiceCompMulticastDNS));
+    CFArrayAppendValue(keys, S_multicast_resolvers);
 
-    if (S_primary_dns != NULL) {
-       old_primary_dns = service_dict_get(S_primary_dns, kSCEntNetDNS);
-       if (old_primary_dns != NULL) {
-           old_primary_dns = CFDictionaryCreateCopy(NULL, old_primary_dns);
-       }
-    }
+    /* add notifier for private DNS configuration (Back to My Mac) */
+    S_private_resolvers = SCDynamicStoreKeyCreate(NULL, CFSTR("%@/%@/%@"),
+                                                 kSCDynamicStoreDomainState,
+                                                 kSCCompNetwork,
+                                                 CFSTR(kDNSServiceCompPrivateDNS));
+    CFArrayAppendValue(keys, S_private_resolvers);
 
-    if (S_primary_proxies != NULL) {
-       old_primary_proxy = service_dict_get(S_primary_proxies, kSCEntNetProxies);
-       if (old_primary_proxy != NULL) {
-           old_primary_proxy = CFDictionaryCreateCopy(NULL, old_primary_proxy);
-       }
+    if (!SCDynamicStoreSetNotificationKeys(S_session, keys, patterns)) {
+       my_log(LOG_ERR,
+              "IPMonitor ip_plugin_init "
+              "SCDynamicStoreSetNotificationKeys failed: %s",
+             SCErrorString(SCError()));
+       goto done;
     }
 
-#if    !TARGET_OS_IPHONE
-    if (S_primary_smb != NULL) {
-       old_primary_smb = service_dict_get(S_primary_smb, kSCEntNetSMB);
-       if (old_primary_smb != NULL) {
-           old_primary_smb = CFDictionaryCreateCopy(NULL, old_primary_smb);
-       }
+    rls = SCDynamicStoreCreateRunLoopSource(NULL, S_session, 0);
+    if (rls == NULL) {
+       my_log(LOG_ERR,
+              "IPMonitor ip_plugin_init "
+              "SCDynamicStoreCreateRunLoopSource failed: %s",
+              SCErrorString(SCError()));
+       goto done;
     }
-#endif // !TARGET_OS_IPHONE
 
-    keyChangeListInit(&keys);
-    service_changes = CFArrayCreateMutable(NULL, 0,
-                                          &kCFTypeArrayCallBacks);
+    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
+    CFRelease(rls);
 
-    for (i = 0; i < count; i++) {
-       CFStringRef     change = CFArrayGetValueAtIndex(changed_keys, i);
-       if (CFEqual(change, S_setup_global_ipv4)) {
-           global_ipv4_changed = TRUE;
-           global_ipv6_changed = TRUE;
-       }
-       else if (CFEqual(change, S_multicast_resolvers)) {
-           dnsinfo_changed = TRUE;
-       }
-       else if (CFEqual(change, S_private_resolvers)) {
-           dnsinfo_changed = TRUE;
-       }
+    /* initialize dns configuration */
+    (void)dns_configuration_set(NULL, NULL, NULL, NULL, NULL);
 #if    !TARGET_OS_IPHONE
-       else if (CFEqual(change, CFSTR(_PATH_RESOLVER_DIR))) {
-           dnsinfo_changed = TRUE;
-       }
+    empty_dns();
 #endif /* !TARGET_OS_IPHONE */
-       else if (CFStringHasPrefix(change, S_state_service_prefix)) {
-           int         i;
-           CFStringRef serviceID;
-
-           for (i = 0; i < sizeof(statusEntityNames)/sizeof(statusEntityNames[0]); i++) {
-               if (CFStringHasSuffix(change, *statusEntityNames[i])) {
-                   dnsinfo_changed = TRUE;
-                   break;
-               }
-           }
-
-           serviceID = parse_component(change, S_state_service_prefix);
-           if (serviceID) {
-               my_CFArrayAppendUniqueValue(service_changes, serviceID);
-               CFRelease(serviceID);
-           }
-       }
-       else if (CFStringHasPrefix(change, S_setup_service_prefix)) {
-           int j;
+    (void)SCDynamicStoreRemoveValue(S_session, S_state_global_dns);
 
-           CFStringRef serviceID = parse_component(change,
-                                                   S_setup_service_prefix);
-           if (serviceID) {
-               my_CFArrayAppendUniqueValue(service_changes, serviceID);
-               CFRelease(serviceID);
-           }
+#if    !TARGET_OS_IPHONE
+    /* initialize SMB configuration */
+    (void)SCDynamicStoreRemoveValue(S_session, S_state_global_smb);
+#endif /* !TARGET_OS_IPHONE */
 
-           for (j = 0; j < sizeof(transientInterfaceEntityNames)/sizeof(transientInterfaceEntityNames[0]); j++) {
-               if (CFStringHasSuffix(change, *transientInterfaceEntityNames[j])) {
-                   reachability_changed = TRUE;
-                   break;
-               }
-           }
+    watch_proxies();
 
-           if (CFStringHasSuffix(change, kSCEntNetInterface)) {
-                reachability_changed = TRUE;
-           }
+  done:
+    my_CFRelease(&keys);
+    my_CFRelease(&patterns);
+    return;
+}
 
+__private_extern__
+void
+prime_IPMonitor()
+{
+    /* initialize multicast route */
+    update_ipv4(NULL, NULL, NULL);
+    return;
+}
 
-       }
-       else if (CFStringHasSuffix(change, kSCEntNetService)) {
-           CFStringRef ifname = my_CFStringCopyComponent(change, CFSTR("/"), 3);
+static boolean_t
+S_get_plist_boolean(CFDictionaryRef plist, CFStringRef key,
+                   boolean_t def)
+{
+    CFBooleanRef       b;
+    boolean_t          ret = def;
 
-           if (ifname != NULL) {
-               if (if_rank_changes == NULL) {
-                   if_rank_changes = CFArrayCreateMutable(NULL, 0,
-                                                          &kCFTypeArrayCallBacks);
-               }
-               my_CFArrayAppendUniqueValue(if_rank_changes, ifname);
-               CFRelease(ifname);
-           }
-       }
+    b = isA_CFBoolean(CFDictionaryGetValue(plist, key));
+    if (b != NULL) {
+       ret = CFBooleanGetValue(b);
     }
+    return (ret);
+}
 
-    /* determine which serviceIDs are impacted by the interface rank changes */
-    if (if_rank_changes != NULL) {
-       n = CFArrayGetCount(if_rank_changes);
-       for (i = 0; i < n; i++) {
-           CFStringRef ifname = CFArrayGetValueAtIndex(if_rank_changes, i);
+#if    !TARGET_IPHONE_SIMULATOR
+#include "IPMonitorControlServer.h"
 
-           if (S_IPMonitor_debug & kDebugFlag1) {
-               my_log(LOG_DEBUG, "Interface rank changed %@",
-                      ifname);
-           }
-           append_serviceIDs_for_interface(service_changes, ifname);
-       }
-    }
+static void
+InterfaceRankChanged(void * info)
+{
+    CFDictionaryRef    assertions = NULL;
+    CFArrayRef         changes;
 
-    /* grab a snapshot of everything we need */
-    services_info = services_info_copy(session, service_changes,
-                                      if_rank_changes);
-    service_order = service_order_get(services_info);
-    if (service_order != NULL) {
-       n_service_order = CFArrayGetCount(service_order);
-       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-           my_log(LOG_DEBUG,
-                  "IPMonitor: service_order %@ ", service_order);
-       }
+    changes = IPMonitorControlServerCopyInterfaceRankInformation(&assertions);
+    if (S_if_rank_dict != NULL) {
+       CFRelease(S_if_rank_dict);
     }
-
-    if (if_rank_changes != NULL) {
-       for (i = 0; i < n; i++) {
-           CFStringRef ifname = CFArrayGetValueAtIndex(if_rank_changes, i);
-           update_interface_rank(services_info, ifname);
-       }
+    S_if_rank_dict = assertions;
+    if (changes != NULL) {
+       IPMonitorProcessChanges(S_session, NULL, changes);
+       CFRelease(changes);
     }
+    return;
+}
 
-    n = CFArrayGetCount(service_changes);
-    for (i = 0; i < n; i++) {
-       uint32_t        changes;
-       CFStringRef     serviceID;
+static void
+StartIPMonitorControlServer(void)
+{
+    CFRunLoopSourceContext     context;
+    CFRunLoopSourceRef rls;
 
-       serviceID = CFArrayGetValueAtIndex(service_changes, i);
-       changes = service_changed(services_info, serviceID);
-       if ((changes & (1 << kEntityTypeServiceOptions)) != 0) {
-           /* if __Service__ (e.g. PrimaryRank) changed */
-           global_ipv4_changed = TRUE;
-           global_ipv6_changed = TRUE;
-       }
-       else {
-           if ((changes & (1 << kEntityTypeIPv4)) != 0) {
-               global_ipv4_changed = TRUE;
-               dnsinfo_changed = TRUE;
-               proxies_changed = TRUE;
-           }
-           if ((changes & (1 << kEntityTypeIPv6)) != 0) {
-               global_ipv6_changed = TRUE;
-               dnsinfo_changed = TRUE;
-               proxies_changed = TRUE;
-           }
-       }
-       if ((changes & (1 << kEntityTypeDNS)) != 0) {
-           if (S_primary_dns != NULL && CFEqual(S_primary_dns, serviceID)) {
-               dns_changed = TRUE;
-           }
-           dnsinfo_changed = TRUE;
-       }
-       if ((changes & (1 << kEntityTypeProxies)) != 0) {
-           proxies_changed = TRUE;
-       }
-#if    !TARGET_OS_IPHONE
-       if ((changes & (1 << kEntityTypeSMB)) != 0) {
-           if (S_primary_smb != NULL && CFEqual(S_primary_smb, serviceID)) {
-               smb_changed = TRUE;
-           }
-       }
-#endif
+    bzero(&context, sizeof(context));
+    context.perform = InterfaceRankChanged;
+    rls = CFRunLoopSourceCreate(NULL, 0, &context);
+    if (IPMonitorControlServerStart(CFRunLoopGetCurrent(), rls,
+                                   &S_bundle_logging_verbose) == FALSE) {
+       my_log(LOG_ERR, "IPMonitorControlServerStart failed");
+    }
+    else {
+       CFRunLoopAddSource(CFRunLoopGetCurrent(), rls,
+                          kCFRunLoopDefaultMode);
     }
+    CFRelease(rls);
+    return;
+}
 
-       if ((changes & (1 <<kEntityTypeVPNStatus)) != 0) {
-           global_ipv4_changed = TRUE;
-           global_ipv6_changed = TRUE;
-       }
+#endif /* !TARGET_IPHONE_SIMULATOR */
 
-    /* ensure S_nwi_state can hold as many services as we have currently */
-    n_services = CFDictionaryGetCount(S_service_state_dict);
-    old_nwi_state = nwi_state_copy_priv(S_nwi_state);
-    S_nwi_state = nwi_state_new(S_nwi_state, n_services);
+__private_extern__
+void
+load_IPMonitor(CFBundleRef bundle, Boolean bundleVerbose)
+{
+    CFDictionaryRef    info_dict;
 
-    if (global_ipv4_changed) {
-       if (S_ipv4_results != NULL) {
-           ElectionResultsRelease(S_ipv4_results);
-       }
-       S_ipv4_results
-           = ElectionResultsCopy(elect_ipv4, service_order, n_service_order);
-       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-           ElectionResultsLog(LOG_DEBUG, S_ipv4_results, "IPv4");
-       }
+    info_dict = CFBundleGetInfoDictionary(bundle);
+
+    if (info_dict != NULL) {
+       S_append_state
+           = S_get_plist_boolean(info_dict,
+                                 CFSTR("AppendStateArrayToSetupArray"),
+                                 FALSE);
     }
-    if (global_ipv6_changed) {
-       if (S_ipv6_results != NULL) {
-           ElectionResultsRelease(S_ipv6_results);
-       }
-       S_ipv6_results
-           = ElectionResultsCopy(elect_ipv6, service_order, n_service_order);
-       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-           ElectionResultsLog(LOG_DEBUG, S_ipv6_results, "IPv6");
-       }
+    if (bundleVerbose) {
+       S_IPMonitor_debug = kDebugFlagDefault;
+       S_bundle_logging_verbose = bundleVerbose;
+       S_IPMonitor_verbose = TRUE;
     }
-    if (global_ipv4_changed || global_ipv6_changed || dnsinfo_changed) {
-       CFStringRef             new_primary;
-       IPv4RouteListRef        new_routelist = NULL;
 
-       /* IPv4 */
-       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-           my_log(LOG_DEBUG,
-                  "IPMonitor: electing IPv4 primary");
-       }
-       new_primary = ElectionResultsCopyPrimary(S_ipv4_results,
-                                                S_ipv6_results,
-                                                S_nwi_state, AF_INET,
-                                                &new_routelist,
-                                                services_info);
-       (void)set_new_primary(&S_primary_ipv4, new_primary, "IPv4");
-       update_ipv4(S_primary_ipv4, new_routelist, &keys);
-       my_CFRelease(&new_primary);
+    my_log_init();
 
-       /* IPv6 */
-       if ((S_IPMonitor_debug & kDebugFlag1) != 0) {
-           my_log(LOG_DEBUG,
-                  "IPMonitor: electing IPv6 primary");
-       }
-       new_primary = ElectionResultsCopyPrimary(S_ipv6_results,
-                                                S_ipv4_results,
-                                                S_nwi_state, AF_INET6,
-                                                NULL,
-                                                services_info);
-       (void)set_new_primary(&S_primary_ipv6, new_primary, "IPv6");
-       update_ipv6(S_primary_ipv6, &keys);
-       my_CFRelease(&new_primary);
-    }
+    /* register to receive changes to verbose and read the initial setting  */
+    IPMonitorControlPrefsInit(CFRunLoopGetCurrent(), prefs_changed);
+    prefs_changed(NULL);
+
+
+    load_DNSConfiguration(bundle,                      // bundle
+                         S_IPMonitor_logger,           // SCLogger
+                         &S_bundle_logging_verbose,    // bundleVerbose
+                         ^(Boolean inSync) {           // syncHandler
+                             dispatch_async(__network_change_queue(), ^{
+                                 S_dnsinfo_synced = inSync;
+
+                                 if (inSync &&
+                                     ((S_network_change_needed & NETWORK_CHANGE_DNS) == 0)) {
+                                     // all of the mDNSResponder ack's should result
+                                     // in a [new] network change being posted
+                                     post_network_change(NETWORK_CHANGE_DNS);
+                                 } else {
+                                     post_network_change_when_ready();
+                                 }
+                             });
+                         });
 
-    if (global_ipv4_changed || global_ipv6_changed) {
-       CFStringRef     new_primary_dns     = NULL;
-       CFStringRef     new_primary_proxies = NULL;
-#if    !TARGET_OS_IPHONE
-       CFStringRef     new_primary_smb     = NULL;
+    load_NetworkInformation(bundle,                    // bundle
+                           S_IPMonitor_logger,         // SCLogger
+                           &S_bundle_logging_verbose,  // bundleVerbose
+                           ^(Boolean inSync) {         // syncHandler
+                               dispatch_async(__network_change_queue(), ^{
+                                   S_nwi_synced = inSync;
+                                   post_network_change_when_ready();
+                               });
+                           });
+#if    !TARGET_IPHONE_SIMULATOR
+    StartIPMonitorControlServer();
 #endif /* !TARGET_OS_IPHONE */
 
-       if (S_primary_ipv4 != NULL && S_primary_ipv6 != NULL) {
-           /* decide between IPv4 and IPv6 */
-           if (rank_service_entity(S_ipv4_service_rank_dict,
-                                   S_primary_ipv4, kSCEntNetDNS)
-               <= rank_service_entity(S_ipv6_service_rank_dict,
-                                      S_primary_ipv6, kSCEntNetDNS)) {
-               new_primary_dns = S_primary_ipv4;
-           }
-           else {
-               new_primary_dns = S_primary_ipv6;
-           }
-           if (rank_service_entity(S_ipv4_service_rank_dict,
-                                   S_primary_ipv4, kSCEntNetProxies)
-               <= rank_service_entity(S_ipv6_service_rank_dict,
-                                      S_primary_ipv6, kSCEntNetProxies)) {
-               new_primary_proxies = S_primary_ipv4;
-           }
-           else {
-               new_primary_proxies = S_primary_ipv6;
-           }
-#if    !TARGET_OS_IPHONE
-           if (rank_service_entity(S_ipv4_service_rank_dict,
-                                   S_primary_ipv4, kSCEntNetSMB)
-               <= rank_service_entity(S_ipv6_service_rank_dict,
-                                      S_primary_ipv6, kSCEntNetSMB)) {
-               new_primary_smb = S_primary_ipv4;
-           }
-           else {
-               new_primary_smb = S_primary_ipv6;
-           }
-#endif /* !TARGET_OS_IPHONE */
+    dns_configuration_init(bundle);
+
+    proxy_configuration_init(bundle);
+
+    ip_plugin_init();
 
-       }
-       else if (S_primary_ipv6 != NULL) {
-           new_primary_dns     = S_primary_ipv6;
-           new_primary_proxies = S_primary_ipv6;
-#if    !TARGET_OS_IPHONE
-           new_primary_smb     = S_primary_ipv6;
-#endif /* !TARGET_OS_IPHONE */
-       }
-       else if (S_primary_ipv4 != NULL) {
-           new_primary_dns     = S_primary_ipv4;
-           new_primary_proxies = S_primary_ipv4;
 #if    !TARGET_OS_IPHONE
-           new_primary_smb     = S_primary_ipv4;
+    if (S_session != NULL) {
+       dns_configuration_monitor(S_session, IPMonitorNotify);
+    }
 #endif /* !TARGET_OS_IPHONE */
-       }
 
-       if (set_new_primary(&S_primary_dns, new_primary_dns, "DNS")) {
-           dns_changed = TRUE;
-           dnsinfo_changed = TRUE;
-       }
-       if (set_new_primary(&S_primary_proxies, new_primary_proxies, "Proxies")) {
-           proxies_changed = TRUE;
-       }
+#if    !TARGET_IPHONE_SIMULATOR
+    load_hostname((S_IPMonitor_debug & kDebugFlag1) != 0);
+#endif /* !TARGET_IPHONE_SIMULATOR */
+
 #if    !TARGET_OS_IPHONE
-       if (set_new_primary(&S_primary_smb, new_primary_smb, "SMB")) {
-           smb_changed = TRUE;
-       }
+    load_smb_configuration((S_IPMonitor_debug & kDebugFlag1) != 0);
 #endif /* !TARGET_OS_IPHONE */
-    }
 
-    if (!proxies_changed && dnsinfo_changed &&
-       ((G_supplemental_proxies_follow_dns != NULL) && CFBooleanGetValue(G_supplemental_proxies_follow_dns))) {
-       proxies_changed = TRUE;
-    }
+    return;
+}
 
-    changes_state = nwi_state_diff(old_nwi_state, S_nwi_state);
 
-    if (global_ipv4_changed || global_ipv6_changed || dnsinfo_changed || reachability_changed) {
-       if (S_nwi_state != NULL) {
-           S_nwi_state->generation_count = mach_absolute_time();
-           if (global_ipv4_changed || global_ipv6_changed || reachability_changed) {
-               SCNetworkReachabilityFlags reach_flags_v4 = 0;
-               SCNetworkReachabilityFlags reach_flags_v6 = 0;
+#pragma mark -
+#pragma mark Standalone test code
 
-               GetReachabilityFlagsFromTransientServices(services_info,
-                                                         &reach_flags_v4,
-                                                         &reach_flags_v6);
 
-               _nwi_state_set_reachability_flags(S_nwi_state, reach_flags_v4, reach_flags_v6);
-           }
+#ifdef TEST_IPMONITOR
 
-           /* Update the per-interface generation count */
-           _nwi_state_update_interface_generations(old_nwi_state, S_nwi_state, changes_state);
-       }
+#include "dns-configuration.c"
 
-       if (update_nwi(S_nwi_state)) {
-           changes |= NETWORK_CHANGE_NET;
+#if    !TARGET_IPHONE_SIMULATOR
+#include "set-hostname.c"
+#endif /* !TARGET_IPHONE_SIMULATOR */
 
-           /*
-            * the DNS configuration includes per-resolver configuration
-            * reachability flags that are based on the nwi state.  Let's
-            * make sure that we check for changes
-            */
-           dnsinfo_changed = TRUE;
-       }
-    }
-    if (dns_changed) {
-       if (update_dns(services_info, S_primary_dns, &keys)) {
-           changes |= NETWORK_CHANGE_DNS;
-           dnsinfo_changed = TRUE;
-       } else {
-           dns_changed = FALSE;
-       }
-    }
-    if (dnsinfo_changed) {
-       if (update_dnsinfo(services_info, S_primary_dns, &keys, service_order)) {
-           changes |= NETWORK_CHANGE_DNS;
-       } else {
-           dnsinfo_changed = FALSE;
-       }
-    }
-    if (proxies_changed) {
-       // if proxy change OR supplemental Proxies follow supplemental DNS
-       if (update_proxies(services_info, S_primary_proxies, &keys, service_order)) {
-           changes |= NETWORK_CHANGE_PROXY;
-       } else {
-           proxies_changed = FALSE;
-       }
-    }
-#if    !TARGET_OS_IPHONE
-    if (smb_changed) {
-       if (update_smb(services_info, S_primary_smb, &keys)) {
-           changes |= NETWORK_CHANGE_SMB;
-       } else {
-           smb_changed = FALSE;
-       }
-    }
-#endif /* !TARGET_OS_IPHONE */
-    my_CFRelease(&service_changes);
-    my_CFRelease(&services_info);
-    my_CFRelease(&if_rank_changes);
+int
+main(int argc, char **argv)
+{
+    _sc_log     = FALSE;
 
-    if (changes != 0) {
-       network_change_msg =
-           generate_log_changes(changes_state,
-                                dns_changed,
-                                dnsinfo_changed,
-                                old_primary_dns,
-                                proxies_changed,
-                                old_primary_proxy,
-#if    !TARGET_OS_IPHONE
-                                smb_changed,
-                                old_primary_smb
-#else  // !TARGET_OS_IPHONE
-                                FALSE,         // smb_changed
-                                NULL           // old_primary_smb
-#endif // !TARGET_OS_IPHONE
-                                );
+    S_IPMonitor_debug = kDebugFlag1;
+    if (argc > 1) {
+       S_IPMonitor_debug = strtoul(argv[1], NULL, 0);
     }
 
-    keyChangeListApplyToStore(&keys, session);
-    my_CFRelease(&old_primary_dns);
-    my_CFRelease(&old_primary_proxy);
-#if    !TARGET_OS_IPHONE
-    my_CFRelease(&old_primary_smb);
-#endif // !TARGET_OS_IPHONE
+    load_IPMonitor(CFBundleGetMainBundle(), FALSE);
+    prime_IPMonitor();
+    S_IPMonitor_debug = kDebugFlag1;
+    CFRunLoopRun();
+    /* not reached */
+    exit(0);
+    return 0;
+}
+#endif /* TEST_IPMONITOR */
 
-    if (changes != 0) {
-       dispatch_async(__network_change_queue(), ^{
-           post_network_change(changes);
-       });
-    }
+#ifdef TEST_ROUTELIST
+#include "dns-configuration.c"
+#include "set-hostname.c"
 
-    if ((network_change_msg != NULL) && (CFStringGetLength(network_change_msg) != 0)) {
-       my_log(LOG_NOTICE, "network changed:%@", network_change_msg);
-    } else if (keyChangeListActive(&keys)) {
-       my_log(LOG_NOTICE, "network changed.");
-    } else {
-       my_log(LOG_DEBUG, "network event w/no changes");
-    }
+struct route {
+    const char *       dest;
+    int                        prefix_length;
+    const char *       gateway;
+    const char *       ifname;
+};
+
+#endif
+
+#ifdef TEST_IPV4_ROUTELIST
+
+typedef struct {
+    const char *       addr;
+    int                        prefix_length;
+    const char *       dest;
+    const char *       router;
+    const char *       ifname;
+    Rank               rank;
+    const CFStringRef *        primary_rank;
+    struct route *     additional_routes;
+    int                        additional_routes_count;
+    struct route *     excluded_routes;
+    int                        excluded_routes_count;
+} IPv4ServiceContents;
+
+typedef const IPv4ServiceContents * IPv4ServiceContentsRef;
+
+struct route loop_routelist[] = {
+    { "1.1.1.1", 32, "1.1.1.2", NULL },
+    { "1.1.1.2", 32, "1.1.1.3", NULL },
+    { "1.1.1.3", 32, "1.1.1.4", NULL },
+    { "1.1.1.4", 32, "1.1.1.5", NULL },
+    { "1.1.1.5", 32, "1.1.1.6", NULL },
+    { "1.1.1.6", 32, "1.1.1.7", NULL },
+    { "1.1.1.7", 32, "1.1.1.8", NULL },
+    { "1.1.1.8", 32, "1.1.1.9", NULL },
+    { "1.1.1.9", 32, "1.1.1.10", NULL },
+    { "1.1.1.10", 32, "1.1.1.11", NULL },
+    { "1.1.1.11", 32, "1.1.1.1", NULL },
+};
+
+struct route vpn_routelist[] = {
+    { "10.1.3.0", 24, "17.153.46.24", NULL },
+    { "10.1.4.0", 24, "17.153.46.24", NULL },
+    { "10.1.5.0", 24, "17.153.46.24", NULL },
+    { "10.1.6.0", 24, "17.153.46.24", NULL },
+    { "10.1.7.0", 24, "17.153.46.24", NULL },
+    { "10.16.0.0", 12, "17.153.46.24", NULL },
+    { "10.45.0.0", 16, "17.153.46.24", NULL },
+    { "10.53.0.0", 16, "17.153.46.24", NULL },
+    { "10.70.0.0", 15, "17.153.46.24", NULL },
+    { "10.74.0.0", 15, "17.153.46.24", NULL },
+    { "10.90.0.0", 15, "17.153.46.24", NULL },
+    { "10.91.0.0", 16, "17.153.46.24", NULL },
+    { "10.100.0.0", 16, "17.153.46.24", NULL },
+    { "10.113.0.0", 16, "17.153.46.24", NULL },
+    { "10.128.0.0", 9, "17.153.46.24", NULL },
+    { "17.0.0.0", 9, "17.153.46.24", NULL },
+    { "17.34.0.0", 16, "17.153.46.24", NULL },
+    { "17.112.156.53", 32, "17.153.46.24", NULL },
+    { "17.128.0.0", 10, "17.153.46.24", NULL },
+    { "17.149.0.121", 32, "17.153.46.24", NULL },
+    { "17.149.7.200", 32, "17.153.46.24", NULL },
+    { "17.153.46.24", 32, "17.153.46.24", NULL },
+    { "17.192.0.0", 12, "17.153.46.24", NULL },
+    { "17.208.0.0", 15, "17.153.46.24", NULL },
+    { "17.211.0.0", 16, "17.153.46.24", NULL },
+    { "17.212.0.0", 14, "17.153.46.24", NULL },
+    { "17.216.0.0", 13, "17.153.46.24", NULL },
+    { "17.224.0.0", 12, "17.153.46.24", NULL },
+    { "17.240.0.0", 16, "17.153.46.24", NULL },
+    { "17.241.0.0", 16, "17.153.46.24", NULL },
+    { "17.248.0.0", 14, "17.153.46.24", NULL },
+    { "17.251.104.200", 32, "17.153.46.24", NULL },
+    { "17.252.0.0", 16, "17.153.46.24", NULL },
+    { "17.253.0.0", 16, "17.153.46.24", NULL },
+    { "17.254.0.0", 16, "17.153.46.24", NULL },
+    { "17.255.0.0", 16, "17.153.46.24", NULL },
+    { "151.193.141.0", 27, "17.153.46.24", NULL },
+    { "172.16.2.0", 24, "17.153.46.24", NULL },
+    { "192.35.50.0", 24, "17.153.46.24", NULL },
+    { "204.179.20.0", 24, "17.153.46.24", NULL },
+    { "206.112.116.0", 24, "17.153.46.24", NULL },
+};
+
+struct route vpn_routelist_ext[] = {
+    { "17.151.63.82", 32, "10.0.0.1", "en0" },
+    { "17.151.63.81", 32, "17.151.63.81", "en0" },
+    { "17.151.63.80", 32, NULL, NULL },
+    { "17.1.0.0", 16, NULL, NULL },
+    { "17.2.0.0", 24, NULL, NULL },
+    { "10.0.0.0", 24, NULL, NULL },
+};
+
+/*
+ *  addr       prefix  dest    router      ifname      pri  rank additional-routes+count excluded-routes+count
+ */
+const IPv4ServiceContents en0_10 = {
+    "10.0.0.10", 24, NULL,     "10.0.0.1", "en0", 10, NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents en0_15 = {
+    "10.0.0.19", 24, NULL,     "10.0.0.1", "en0", 15, NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents en0_30 = {
+    "10.0.0.11", 24, NULL,     "10.0.0.1", "en0", 30, NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents en0_40 = {
+    "10.0.0.12", 24, NULL,     "10.0.0.1", "en0", 40, NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents en0_50 = {
+    "10.0.0.13", 24, NULL,     "10.0.0.1", "en0", 50, NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents en0_110 = {
+    "192.168.2.10", 24, NULL, "192.168.2.1", "en0",    110, NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents en0_1 = {
+    "17.202.40.191", 22, NULL, "17.202.20.1", "en0", 1,  NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents en1_20 = {
+    "10.0.0.20", 24, NULL, "10.0.0.1", "en1", 20, NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents en1_2 = {
+    "17.202.42.24", 22, NULL, "17.202.20.1", "en1", 2,  NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents en1_125 = {
+    "192.168.2.20", 24, NULL, "192.168.2.1", "en1", 125, NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents fw0_25 = {
+    "192.168.2.30", 24, NULL, "192.168.2.1", "fw0", 25, NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents fw0_21 = {
+    "192.168.3.30", 24, NULL, "192.168.3.1", "fw0", 21, NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents ppp0_0_1 = {
+    "17.219.156.22", -1, "17.219.156.1", "17.219.156.1", "ppp0", 0,  NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents utun0 = {
+    "17.153.46.24", -1, "17.153.46.24", "17.153.46.24", "utun0", 20,  NULL, vpn_routelist, countof(vpn_routelist), vpn_routelist_ext, countof(vpn_routelist_ext)
+};
+
+const IPv4ServiceContents en0_test6 = {
+    "17.202.42.113", 22, NULL, "17.202.40.1", "en0", 2,  NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents en1_test6 = {
+    "17.202.42.111", 22, NULL, "17.202.40.1", "en1", 3,  NULL, NULL, 0, NULL, 0
+};
+
+const IPv4ServiceContents en2_test6 = {
+    "17.255.98.164", 20, NULL, "17.255.96.1", "en2", 1,  NULL, NULL, 0, NULL, 0
+};
 
-    my_CFRelease(&network_change_msg);
+const IPv4ServiceContents en0_test7 = {
+    "17.202.42.113", 22, NULL, "17.202.40.1", "en0", 3,  NULL, NULL, 0, NULL, 0
+};
 
-    if (changes_state != NULL) {
-       nwi_state_release(changes_state);
-    }
-    if (old_nwi_state != NULL) {
-       nwi_state_release(old_nwi_state);
-    }
-    keyChangeListFree(&keys);
-    return;
-}
+const IPv4ServiceContents en1_test7 = {
+    "17.202.42.111", 22, NULL, "17.202.40.1", "en1", 2,  NULL, NULL, 0, NULL, 0
+};
 
-static void
-watch_proxies()
-{
-#if    !TARGET_OS_IPHONE
-    const _scprefs_observer_type type = scprefs_observer_type_mcx;
-#else
-    const _scprefs_observer_type type = scprefs_observer_type_global;
-#endif
-    static dispatch_queue_t proxy_cb_queue;
+const IPv4ServiceContents en2_test7 = {
+    "17.255.98.164", 20, NULL, "17.255.96.1", "en2", 1,  NULL, NULL, 0, NULL, 0
+};
 
-    proxy_cb_queue = dispatch_queue_create("com.apple.SystemConfiguration.IPMonitor.proxy", NULL);
-    _scprefs_observer_watch(type,
-                            "com.apple.SystemConfiguration.plist",
-                            proxy_cb_queue,
-                            ^{
-                                SCDynamicStoreNotifyValue(NULL, S_state_global_proxies);
-                                notify_post(_SC_NOTIFY_NETWORK_CHANGE_PROXY);
-                                my_log(LOG_DEBUG, "IPMonitor: Notifying:\n%@",
-                                       S_state_global_proxies);
-                            });
-    return;
-}
+const IPv4ServiceContents fw0_test6_and_7 = {
+    "169.254.11.33", 16, NULL, NULL,   "fw0", 0x0ffffff, NULL, NULL, 0, NULL, 0
+};
 
-#if    ((__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) || (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000))
-#include "IPMonitorControlPrefs.h"
+const IPv4ServiceContents en0_10_last = {
+    "10.0.0.10", 24, NULL,     "10.0.0.1", "en0", 10, &kSCValNetServicePrimaryRankLast, NULL, 0, NULL, 0
+};
 
-__private_extern__ SCLoggerRef
-my_log_get_logger()
-{
-    return (S_IPMonitor_logger);
-}
+const IPv4ServiceContents en0_10_never = {
+    "10.0.0.10", 24, NULL,     "10.0.0.1", "en0", 10, &kSCValNetServicePrimaryRankNever, NULL, 0, NULL, 0
+};
 
-static void
-prefs_changed(__unused SCPreferencesRef prefs)
-{
-    if (S_bundle_logging_verbose || IPMonitorControlPrefsIsVerbose()) {
-       S_IPMonitor_debug = kDebugFlagDefault;
-       S_IPMonitor_verbose = TRUE;
-       SCLoggerSetFlags(S_IPMonitor_logger, kSCLoggerFlagsFile | kSCLoggerFlagsDefault);
-       my_log(LOG_DEBUG, "IPMonitor: Setting logging verbose mode on.");
-    } else {
-       my_log(LOG_DEBUG, "IPMonitor: Setting logging verbose mode off.");
-       S_IPMonitor_debug = 0;
-       S_IPMonitor_verbose = FALSE;
-       SCLoggerSetFlags(S_IPMonitor_logger, kSCLoggerFlagsDefault);
-    }
-    return;
-}
+const IPv4ServiceContents en1_20_first = {
+    "10.0.0.20", 24, NULL,     "10.0.0.1", "en1", 20, &kSCValNetServicePrimaryRankFirst, NULL, 0, NULL, 0
+};
 
-#define LOGGER_ID CFSTR("com.apple.networking.IPMonitor")
-static void
-my_log_init()
-{
-    if (S_IPMonitor_logger != NULL) {
-       return;
-    }
-    S_IPMonitor_logger = SCLoggerCreate(LOGGER_ID);
-    return;
+const IPv4ServiceContents en1_20_never = {
+    "10.0.0.20", 24, NULL,     "10.0.0.1", "en1", 20, &kSCValNetServicePrimaryRankNever, NULL, 0, NULL, 0
+};
 
-}
+const IPv4ServiceContents en1_20_other_never = {
+    "192.168.2.50", 24, NULL, "192.168.2.1", "en1", 20, &kSCValNetServicePrimaryRankNever, NULL, 0, NULL, 0
+};
 
-#else  // ((__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) || (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000))
+const IPv4ServiceContents en0_linklocal = {
+    "169.254.22.44", 16, NULL, NULL,   "en0",  0xfffff, NULL, NULL, 0, NULL, 0
+};
 
-static void
-my_log_init()
-{
-    return;
-}
+const IPv4ServiceContents en0_route_loop = {
+    "192.168.130.16", 24, NULL, "192.168.130.1", "en0", 2, NULL, loop_routelist, countof(loop_routelist), NULL, 0
+};
 
-#endif // ((__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) || (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000))
+typedef struct {
+    const char *               name;
+    IPv4ServiceContentsRef     test[];
+} IPv4RouteTest, * IPv4RouteTestRef;
 
+static IPv4RouteTest test1 = {
+    "test1",
+    {
+       &en0_40,
+       &en0_15,
+       &fw0_25,
+       &en0_30,
+       &en1_20,
+       &en0_50,
+       &en0_10,
+       NULL
+    }
+};
 
-static void
-ip_plugin_init()
-{
-    CFMutableArrayRef  keys = NULL;
-    CFStringRef                pattern;
-    CFMutableArrayRef  patterns = NULL;
-    CFRunLoopSourceRef rls = NULL;
+static IPv4RouteTest test2 = {
+    "test2",
+    {
+       &en0_40,
+       &fw0_25,
+       &en0_30,
+       &en1_20,
+       &en0_50,
+       &en0_10,
+       NULL
+    }
+};
 
-    if (S_is_network_boot() != 0) {
-       S_netboot = TRUE;
+static IPv4RouteTest test3 = {
+    "test3",
+    {
+       &en0_40,
+       &en1_20,
+       &en0_50,
+       &en0_10,
+       &en0_110,
+       &en1_125,
+       &fw0_25,
+       &fw0_21,
+       &en0_40,
+       &en0_30,
+       NULL
     }
+};
 
-#ifdef RTF_IFSCOPE
-    if (S_is_scoped_routing_enabled() != 0) {
-       S_scopedroute = TRUE;
+static IPv4RouteTest test4 = {
+    "test4",
+    {
+       &en0_1,
+       &en0_40,
+       &en0_30,
+       &en1_20,
+       &en1_2,
+       NULL
     }
+};
 
-    if (S_is_scoped_v6_routing_enabled() != 0) {
-       S_scopedroute_v6 = TRUE;
+static IPv4RouteTest test5 = {
+    "test5",
+    {
+       &ppp0_0_1,
+       &en0_1,
+       &en0_40,
+       &en0_30,
+       &en1_20,
+       &en1_2,
+       NULL
     }
-#endif /* RTF_IFSCOPE */
+};
 
-    S_session = SCDynamicStoreCreate(NULL, CFSTR("IPMonitor"),
-                                  IPMonitorNotify, NULL);
-    if (S_session == NULL) {
-       my_log(LOG_ERR,
-              "IPMonitor ip_plugin_init SCDynamicStoreCreate failed: %s",
-              SCErrorString(SCError()));
-       return;
+static IPv4RouteTest test6 = {
+    "test6",
+    {
+       &en0_test6,
+       &en1_test6,
+       &en2_test6,
+       &fw0_test6_and_7,
+       NULL
     }
-    S_state_global_ipv4
-       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
-                                                    kSCDynamicStoreDomainState,
-                                                    kSCEntNetIPv4);
-    S_state_global_ipv6
-       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
-                                                    kSCDynamicStoreDomainState,
-                                                    kSCEntNetIPv6);
-    S_state_global_dns
-       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
-                                                    kSCDynamicStoreDomainState,
-                                                    kSCEntNetDNS);
-    S_state_global_proxies
-       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
-                                                    kSCDynamicStoreDomainState,
-                                                    kSCEntNetProxies);
-#if    !TARGET_OS_IPHONE
-    S_state_global_smb
-       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
-                                                    kSCDynamicStoreDomainState,
-                                                    kSCEntNetSMB);
-#endif /* !TARGET_OS_IPHONE */
-    S_setup_global_ipv4
-       = SCDynamicStoreKeyCreateNetworkGlobalEntity(NULL,
-                                                    kSCDynamicStoreDomainSetup,
-                                                    kSCEntNetIPv4);
-    S_state_service_prefix
-       = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
-                                                     kSCDynamicStoreDomainState,
-                                                     CFSTR(""),
-                                                     NULL);
-    S_setup_service_prefix
-       = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
-                                                     kSCDynamicStoreDomainSetup,
-                                                     CFSTR(""),
-                                                     NULL);
-    S_service_state_dict
-       = CFDictionaryCreateMutable(NULL, 0,
-                                   &kCFTypeDictionaryKeyCallBacks,
-                                   &kCFTypeDictionaryValueCallBacks);
+};
 
-    S_ipv4_service_rank_dict
-       = CFDictionaryCreateMutable(NULL, 0,
-                                   &kCFTypeDictionaryKeyCallBacks,
-                                   &kCFTypeDictionaryValueCallBacks);
+static IPv4RouteTest test7 = {
+    "test7",
+    {
+       &en0_test7,
+       &en1_test7,
+       &en2_test7,
+       &fw0_test6_and_7,
+       NULL
+    }
+};
 
-    S_ipv6_service_rank_dict
-       = CFDictionaryCreateMutable(NULL, 0,
-                                   &kCFTypeDictionaryKeyCallBacks,
-                                   &kCFTypeDictionaryValueCallBacks);
+static IPv4RouteTest test8 = {
+    "test8",
+    {
+       &en0_10,
+       &en1_20,
+       NULL
+    }
+};
 
-    keys = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
-    patterns = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+static IPv4RouteTest test9 = {
+    "test9",
+    {
+       &en0_10,
+       &en1_20_first,
+       &fw0_25,
+       NULL
+    }
+};
 
-    /* register for State: and Setup: per-service notifications */
-    add_service_keys(kSCCompAnyRegex, keys, patterns);
+static IPv4RouteTest test10 = {
+    "test10",
+    {
+       &en0_10_last,
+       &en1_20,
+       &fw0_25,
+       NULL
+    }
+};
 
-    pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetPPP);
-    CFArrayAppendValue(patterns, pattern);
-    CFRelease(pattern);
+static IPv4RouteTest test11 = {
+    "test11",
+    {
+       &en0_10_never,
+       &en1_20,
+       &fw0_25,
+       NULL
+    }
+};
 
-    pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetVPN);
-    CFArrayAppendValue(patterns, pattern);
-    CFRelease(pattern);
+static IPv4RouteTest test12 = {
+    "test12",
+    {
+       &en0_10,
+       &en1_20,
+       NULL
+    }
+};
 
-    pattern = setup_service_key(kSCCompAnyRegex, kSCEntNetInterface);
-    CFArrayAppendValue(patterns, pattern);
-    CFRelease(pattern);
+static IPv4RouteTest test13 = {
+    "test13",
+    {
+       &en0_10,
+       &en1_20_never,
+       NULL
+    }
+};
 
-    /* register for State: per-service PPP/VPN/IPSec status notifications */
-    add_status_keys(kSCCompAnyRegex, patterns);
+static IPv4RouteTest test14 = {
+    "test14",
+    {
+       &en1_20_never,
+       NULL
+    }
+};
 
-    /* register for interface rank notifications */
-    pattern = if_rank_key_copy(kSCCompAnyRegex);
-    CFArrayAppendValue(patterns, pattern);
-    CFRelease(pattern);
+static IPv4RouteTest test15 = {
+    "test15",
+    {
+       &en0_linklocal,
+       NULL
+    }
+};
 
-    /* add notifier for ServiceOrder/PPPOverridePrimary changes for IPv4 */
-    CFArrayAppendValue(keys, S_setup_global_ipv4);
+static IPv4RouteTest test16 = {
+    "test16",
+    {
+       &en0_10,
+       &utun0,
+       NULL
+    }
+};
 
-    /* add notifier for multicast DNS configuration (Bonjour/.local) */
-    S_multicast_resolvers = SCDynamicStoreKeyCreate(NULL, CFSTR("%@/%@/%@"),
-                                                   kSCDynamicStoreDomainState,
-                                                   kSCCompNetwork,
-                                                   CFSTR(kDNSServiceCompMulticastDNS));
-    CFArrayAppendValue(keys, S_multicast_resolvers);
+static IPv4RouteTest test17 = {
+    "test17",
+    {
+       &en0_10,
+       &en1_20_other_never,
+       NULL
+    }
+};
 
-    /* add notifier for private DNS configuration (Back to My Mac) */
-    S_private_resolvers = SCDynamicStoreKeyCreate(NULL, CFSTR("%@/%@/%@"),
-                                                 kSCDynamicStoreDomainState,
-                                                 kSCCompNetwork,
-                                                 CFSTR(kDNSServiceCompPrivateDNS));
-    CFArrayAppendValue(keys, S_private_resolvers);
+static IPv4RouteTest test18 = {
+    "test18",
+    {
+       &en0_route_loop,
+       NULL
+    }
+};
 
-    if (!SCDynamicStoreSetNotificationKeys(S_session, keys, patterns)) {
-       my_log(LOG_ERR,
-              "IPMonitor ip_plugin_init "
-              "SCDynamicStoreSetNotificationKeys failed: %s",
-             SCErrorString(SCError()));
-       goto done;
+static IPv4RouteTestRef ipv4_tests[] = {
+    &test1,
+    &test2,
+    &test3,
+    &test4,
+    &test5,
+    &test6,
+    &test7,
+    &test8,
+    &test9,
+    &test10,
+    &test11,
+    &test12,
+    &test13,
+    &test14,
+    &test15,
+    &test16,
+    &test17,
+    &test18,
+    NULL
+};
+
+static boolean_t
+ipv4_prefix_length_is_valid(int prefix_length)
+{
+    if (prefix_length < 0 || prefix_length > IPV4_ROUTE_ALL_BITS_SET) {
+       return (FALSE);
     }
+    return (TRUE);
+}
 
-    rls = SCDynamicStoreCreateRunLoopSource(NULL, S_session, 0);
-    if (rls == NULL) {
-       my_log(LOG_ERR,
-              "IPMonitor ip_plugin_init "
-              "SCDynamicStoreCreateRunLoopSource failed: %s",
-              SCErrorString(SCError()));
-       goto done;
+static void
+dict_add_string(CFMutableDictionaryRef dict, CFStringRef prop_name,
+               const char * str)
+{
+    CFStringRef                prop_val;
+
+    if (str == NULL) {
+       return;
     }
+    prop_val = CFStringCreateWithCString(NULL,
+                                        str,
+                                        kCFStringEncodingASCII);
+    CFDictionarySetValue(dict, prop_name, prop_val);
+    CFRelease(prop_val);
+    return;
+}
 
-    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
-    CFRelease(rls);
-
-    /* initialize dns configuration */
-    (void)dns_configuration_set(NULL, NULL, NULL, NULL, NULL);
-#if    !TARGET_OS_IPHONE
-    empty_dns();
-#endif /* !TARGET_OS_IPHONE */
-    (void)SCDynamicStoreRemoveValue(S_session, S_state_global_dns);
+static void
+dict_add_string_as_array(CFMutableDictionaryRef dict, CFStringRef prop_name,
+                        const char * str)
+{
+    CFArrayRef         array;
+    CFStringRef                prop_val;
 
-#if    !TARGET_OS_IPHONE
-    /* initialize SMB configuration */
-    (void)SCDynamicStoreRemoveValue(S_session, S_state_global_smb);
-#endif /* !TARGET_OS_IPHONE */
+    if (str == NULL) {
+       return;
+    }
+    prop_val = CFStringCreateWithCString(NULL,
+                                        str,
+                                        kCFStringEncodingASCII);
+    array = CFArrayCreate(NULL,
+                         (const void **)&prop_val, 1,
+                         &kCFTypeArrayCallBacks);
+    CFRelease(prop_val);
+    CFDictionarySetValue(dict, prop_name, array);
+    CFRelease(array);
+    return;
+}
 
-    if_rank_dict_init();
-    watch_proxies();
+static void
+dict_add_ip(CFMutableDictionaryRef dict, CFStringRef prop_name,
+           struct in_addr ip)
+{
+    CFStringRef                str;
 
-  done:
-    my_CFRelease(&keys);
-    my_CFRelease(&patterns);
+    str = my_CFStringCreateWithInAddr(ip);
+    CFDictionarySetValue(dict, prop_name, str);
+    CFRelease(str);
     return;
 }
 
-__private_extern__
-void
-prime_IPMonitor()
+static void
+dict_add_ip_as_array(CFMutableDictionaryRef dict, CFStringRef prop_name,
+                    struct in_addr ip)
 {
-    /* initialize multicast route */
-    update_ipv4(NULL, NULL, NULL);
+    CFArrayRef         array;
+    CFStringRef                str;
+
+    str = my_CFStringCreateWithInAddr(ip);
+    array = CFArrayCreate(NULL,
+                         (const void **)&str, 1,
+                         &kCFTypeArrayCallBacks);
+    CFRelease(str);
+    CFDictionarySetValue(dict, prop_name, array);
+    CFRelease(array);
     return;
 }
 
-static boolean_t
-S_get_plist_boolean(CFDictionaryRef plist, CFStringRef key,
-                   boolean_t def)
+static void
+dict_insert_routes(CFMutableDictionaryRef dict, CFStringRef prop_name,
+                  struct route * routes, int routes_count)
 {
-    CFBooleanRef       b;
-    boolean_t          ret = def;
+    int                        i;
+    CFMutableArrayRef  route_list;
+    struct route *     scan;
 
-    b = isA_CFBoolean(CFDictionaryGetValue(plist, key));
-    if (b != NULL) {
-       ret = CFBooleanGetValue(b);
+    if (routes == NULL || routes_count == 0) {
+       return;
     }
-    return (ret);
+    route_list = CFArrayCreateMutable(NULL, routes_count,
+                                     &kCFTypeArrayCallBacks);
+    for (i = 0, scan = routes; i < routes_count; i++, scan++) {
+       struct in_addr                  mask;
+       CFMutableDictionaryRef          route_dict;
+
+       route_dict
+           = CFDictionaryCreateMutable(NULL, 0,
+                                       &kCFTypeDictionaryKeyCallBacks,
+                                       &kCFTypeDictionaryValueCallBacks);
+       dict_add_string(route_dict, kSCPropNetIPv4RouteDestinationAddress,
+                       scan->dest);
+       if (ipv4_prefix_length_is_valid(scan->prefix_length)) {
+           mask.s_addr = htonl(prefix_to_mask32(scan->prefix_length));
+           dict_add_ip(route_dict, kSCPropNetIPv4RouteSubnetMask, mask);
+       }
+       dict_add_string(route_dict, kSCPropNetIPv4RouteGatewayAddress,
+                       scan->gateway);
+       dict_add_string(route_dict, kSCPropNetIPv4RouteInterfaceName,
+                       scan->ifname);
+       CFArrayAppendValue(route_list, route_dict);
+       CFRelease(route_dict);
+    }
+    CFDictionarySetValue(dict, prop_name, route_list);
+    CFRelease(route_list);
+    return;
 }
 
-__private_extern__
-void
-load_IPMonitor(CFBundleRef bundle, Boolean bundleVerbose)
+static CFDictionaryRef
+make_IPv4_dict(IPv4ServiceContentsRef t)
 {
-    CFDictionaryRef    info_dict;
+    CFMutableDictionaryRef     dict;
 
-    info_dict = CFBundleGetInfoDictionary(bundle);
+    dict = CFDictionaryCreateMutable(NULL, 0,
+                                    &kCFTypeDictionaryKeyCallBacks,
+                                    &kCFTypeDictionaryValueCallBacks);
+    dict_add_string_as_array(dict, kSCPropNetIPv4Addresses, t->addr);
+    if (ipv4_prefix_length_is_valid(t->prefix_length)) {
+       struct in_addr          mask;
 
-    if (info_dict != NULL) {
-       S_append_state
-           = S_get_plist_boolean(info_dict,
-                                 CFSTR("AppendStateArrayToSetupArray"),
-                                 FALSE);
-    }
-    if (bundleVerbose) {
-       S_IPMonitor_debug = kDebugFlagDefault;
-       S_bundle_logging_verbose = bundleVerbose;
-       S_IPMonitor_verbose = TRUE;
+       mask.s_addr = htonl(prefix_to_mask32(t->prefix_length));
+       dict_add_ip_as_array(dict, kSCPropNetIPv4SubnetMasks, mask);
     }
+    dict_add_string_as_array(dict, kSCPropNetIPv4DestAddresses, t->dest);
+    dict_add_string(dict, kSCPropNetIPv4Router, t->router);
+    dict_add_string(dict, kSCPropInterfaceName, t->ifname);
+    dict_add_string(dict, kSCPropConfirmedInterfaceName, t->ifname);
+    dict_insert_routes(dict, kSCPropNetIPv4AdditionalRoutes,
+                      t->additional_routes, t->additional_routes_count);
+    dict_insert_routes(dict, kSCPropNetIPv4ExcludedRoutes,
+                      t->excluded_routes, t->excluded_routes_count);
+    return (dict);
+}
 
-    my_log_init();
-
-#if ((__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) || (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000))
-    /* register to receive changes to verbose and read the initial setting  */
-    IPMonitorControlPrefsInit(CFRunLoopGetCurrent(), prefs_changed);
-    prefs_changed(NULL);
-
-#endif // ((__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080) || (__IPHONE_OS_VERSION_MIN_REQUIRED >= 60000))
+typedef enum {
+    kDirectionForwards = 0,
+    kDirectionBackwards = 1
+} Direction;
 
-    load_DNSConfiguration(bundle,                      // bundle
-                         S_IPMonitor_logger,           // SCLogger
-                         &S_IPMonitor_verbose,         // bundleVerbose
-                         ^(Boolean inSync) {           // syncHandler
-                             dispatch_async(__network_change_queue(), ^{
-                                 S_dnsinfo_synced = inSync;
+typedef enum {
+    kLogRouteDisabled = 0,
+    kLogRouteEnabled = 1
+} LogRoute;
 
-                                 if (inSync &&
-                                     ((S_network_change_needed & NETWORK_CHANGE_DNS) == 0)) {
-                                     // all of the mDNSResponder ack's should result
-                                     // in a [new] network change being posted
-                                     post_network_change(NETWORK_CHANGE_DNS);
-                                 } else {
-                                     post_network_change_when_ready();
-                                 }
-                             });
-                         });
+static IPv4RouteListRef
+make_IPv4RouteList_for_test(IPv4RouteListRef list,
+                           IPv4ServiceContentsRef test,
+                           LogRoute log_it)
+{
+    CFDictionaryRef    dict;
+    IPv4RouteListRef   r;
+    Rank               rank;
+    Rank               rank_assertion = kRankAssertionDefault;
+    CFNumberRef                rank_assertion_cf = NULL;
+    Boolean            rank_assertion_is_set = FALSE;
+    IPv4RouteListRef   ret = NULL;
+    IPV4_ROUTES_BUF_DECL(routes);
 
-    load_NetworkInformation(bundle,                    // bundle
-                           S_IPMonitor_logger,         // SCLogger
-                           &S_IPMonitor_verbose,       // bundleVerbose
-                           ^(Boolean inSync) {         // syncHandler
-                               dispatch_async(__network_change_queue(), ^{
-                                   S_nwi_synced = inSync;
-                                   post_network_change_when_ready();
-                               });
-                           });
+    dict = make_IPv4_dict(test);
+    if (dict == NULL) {
+       fprintf(stderr, "make_IPv4_dict failed\n");
+       exit(1);
+    }
+    if (test->primary_rank != NULL) {
+       rank_assertion
+           = PrimaryRankGetRankAssertion(*test->primary_rank,
+                                         &rank_assertion_is_set);
+       if (rank_assertion_is_set) {
+           rank_assertion_cf
+               = CFNumberCreate(NULL, kCFNumberSInt32Type, &rank_assertion);
+       }
+    }
+    r = IPv4RouteListCreateWithDictionary(routes, dict,
+                                         rank_assertion_cf);
+    my_CFRelease(&rank_assertion_cf);
+    if (r == NULL) {
+       fprintf(stderr, "IPv4RouteListCreateWithDictionary failed\n");
+       exit(1);
+    }
 
-    dns_configuration_init(bundle);
+    if (rank_assertion == kRankAssertionScoped) {
+       rank_assertion = kRankAssertionNever;
+    }
+    rank = RankMake(test->rank, rank_assertion);
+    if (log_it == kLogRouteEnabled
+       && (S_IPMonitor_debug & kDebugFlag4) != 0) {
+       CFStringRef     descr;
 
-    proxy_configuration_init(bundle);
+       descr = IPv4RouteListCopyDescription(r);
+       SCLog(TRUE, LOG_NOTICE, CFSTR("Adding %@"), descr);
+       CFRelease(descr);
+    }
+    ret = IPv4RouteListAddRouteList(list, 1, r, rank);
+    if (r != routes) {
+       free(r);
+    }
+    CFRelease(dict);
+    return (ret);
+}
 
-    ip_plugin_init();
+static IPv4RouteListRef
+make_IPv4RouteList(IPv4ServiceContentsRef * test, Direction direction,
+                  LogRoute log_it)
+{
+    IPv4RouteListRef           ret = NULL;
+    IPv4ServiceContentsRef *   scan;
 
-#if    !TARGET_OS_IPHONE
-    if (S_session != NULL) {
-       dns_configuration_monitor(S_session, IPMonitorNotify);
+    switch (direction) {
+    case kDirectionBackwards:
+       for (scan = test; *scan != NULL; scan++) {
+           /* find the end of the list */
+       }
+       for (scan--; scan >= test; scan--) {
+           ret = make_IPv4RouteList_for_test(ret, *scan, log_it);
+       }
+       break;
+    default:
+    case kDirectionForwards:
+       for (scan = test; *scan != NULL; scan++) {
+           ret = make_IPv4RouteList_for_test(ret, *scan, log_it);
+       }
+       break;
     }
-#endif /* !TARGET_OS_IPHONE */
+    IPv4RouteListFinalize(ret);
+    return (ret);
+}
 
-#if    !TARGET_IPHONE_SIMULATOR
-    load_hostname((S_IPMonitor_debug & kDebugFlag1) != 0);
-#endif /* !TARGET_IPHONE_SIMULATOR */
+#define EMPHASIS_CHARS         "================="
 
-#if    !TARGET_OS_IPHONE
-    load_smb_configuration((S_IPMonitor_debug & kDebugFlag1) != 0);
-#endif /* !TARGET_OS_IPHONE */
+/*
+ * Function: routelist_build_test
+ * Purpose:
+ *   Runs through the given set of routes first in the forward direction,
+ *   then again backwards.  We should end up with exactly the same set of
+ *   routes at the end.
+ */
+static boolean_t
+routelist_build_test(IPv4RouteTestRef test)
+{
+    CFStringRef                        descr;
+    boolean_t                  ret = FALSE;
+    IPv4RouteListRef           routes1;
+    IPv4RouteListRef           routes2;
 
-    return;
+    printf("\n" EMPHASIS_CHARS  "> RouteList Build '%s' <"
+          EMPHASIS_CHARS "\n",
+          test->name);
+
+    routes1 = make_IPv4RouteList(test->test, kDirectionForwards,
+                                kLogRouteEnabled);
+    if ((S_IPMonitor_debug & kDebugFlag4) != 0) {
+       if (routes1 != NULL) {
+           descr = IPv4RouteListCopyDescription(routes1);
+           SCPrint(TRUE, stdout, CFSTR("Routes are %@\n"), descr);
+           CFRelease(descr);
+       }
+    }
+    routes2 = make_IPv4RouteList(test->test, kDirectionBackwards,
+                                kLogRouteEnabled);
+    if ((S_IPMonitor_debug & kDebugFlag4) != 0) {
+       if (routes2 != NULL) {
+           descr = IPv4RouteListCopyDescription(routes2);
+           SCPrint(TRUE, stdout, CFSTR("Routes are %@\n"), descr);
+           CFRelease(descr);
+       }
+    }
+    if ((routes1 != NULL && routes2 == NULL)
+       || (routes1 == NULL && routes2 != NULL)) {
+       fprintf(stderr, "routes1 is %sNULL but routes2 is %sNULL\n",
+              (routes1 != NULL) ? "not " : "",
+              (routes2 != NULL) ? "not " : "");
+    }
+    else if (routes1 != NULL && routes2 != NULL) {
+       /* check if they are different */
+       if (routes1->count != routes2->count) {
+           fprintf(stderr, "routes1 count %d != routes 2 count %d\n",
+                   routes1->count, routes2->count);
+       }
+       else if (bcmp(routes1, routes2,
+                     IPv4RouteListComputeSize(routes1->count)) != 0) {
+           fprintf(stderr, "routes1 and routes2 are different\n");
+       }
+       else {
+           printf("routes1 and routes2 are the same\n");
+           ret = TRUE;
+       }
+    }
+    if (routes1 != NULL) {
+       free(routes1);
+    }
+    if (routes2 != NULL) {
+       free(routes2);
+    }
+    printf(EMPHASIS_CHARS  "> RouteList Build '%s': %s <"
+          EMPHASIS_CHARS "\n",
+          test->name, ret ? "PASSED" : "FAILED");
+    return (ret);
 }
 
+static void
+apply_test(IPv4RouteTestRef old_test, IPv4RouteTestRef new_test)
+{
+    IPv4RouteListRef   new_routes;
+    IPv4RouteListRef   old_routes;
 
-#pragma mark -
-#pragma mark Standalone test code
+    printf("\n" EMPHASIS_CHARS  "> Apply '%s', '%s' Begin <"
+          EMPHASIS_CHARS "\n",
+          old_test->name, new_test->name);
 
+    old_routes = make_IPv4RouteList(old_test->test, kDirectionForwards,
+                                   kLogRouteDisabled);
+    new_routes = make_IPv4RouteList(new_test->test, kDirectionForwards,
+                                   kLogRouteDisabled);
+    if (old_routes == NULL) {
+       printf("No Old Routes\n");
+    }
+    else {
+       printf("Old routes ('%s') = ", old_test->name);
+       IPv4RouteListPrint(old_routes);
+    }
 
-#ifdef TEST_IPMONITOR
+    /* apply the old routes */
+    IPv4RouteListApply(NULL, old_routes, -1);
 
-#include "dns-configuration.c"
+    if (new_routes == NULL) {
+       printf("No New Routes\n");
+    }
+    else {
+       printf("New Routes ('%s') = ", new_test->name);
+       IPv4RouteListPrint(new_routes);
+    }
 
-#if    !TARGET_IPHONE_SIMULATOR
-#include "set-hostname.c"
-#endif /* !TARGET_IPHONE_SIMULATOR */
+    /* apply the new routes */
+    IPv4RouteListApply(old_routes, new_routes, -1);
+
+    if (old_routes != NULL) {
+       free(old_routes);
+    }
+    if (new_routes != NULL) {
+       free(new_routes);
+    }
+    printf(EMPHASIS_CHARS  "> Apply '%s', '%s' End <"
+          EMPHASIS_CHARS "\n",
+          old_test->name, new_test->name);
+    return;
+}
 
 int
 main(int argc, char **argv)
 {
-    _sc_log     = FALSE;
+    IPv4RouteTestRef * test;
 
-    S_IPMonitor_debug = kDebugFlag1;
+    _sc_log     = FALSE;
+    _sc_verbose = (argc > 1) ? TRUE : FALSE;
+    S_IPMonitor_debug = kDebugFlag1 | kDebugFlag2 | kDebugFlag4;
     if (argc > 1) {
        S_IPMonitor_debug = strtoul(argv[1], NULL, 0);
     }
+    S_scopedroute = (argc < 3);
+    for (test = ipv4_tests; *test != NULL; test++) {
+       if (routelist_build_test(*test) == FALSE) {
+           fprintf(stderr, "%s failed\n", (*test)->name);
+           exit(1);
+       }
+    }
+    for (test = ipv4_tests; *test != NULL; test++) {
+       IPv4RouteTestRef *      test2;
 
-    load_IPMonitor(CFBundleGetMainBundle(), FALSE);
-    prime_IPMonitor();
-    S_IPMonitor_debug = kDebugFlag1;
-    CFRunLoopRun();
-    /* not reached */
+       for (test2 = test + 1; *test2 != NULL; test2++) {
+           apply_test(*test, *test2);
+           apply_test(*test2, *test);
+       }
+    }
+
+    {
+       char    cmd[128];
+
+       printf("\nChecking for leaks\n");
+       sprintf(cmd, "leaks %d 2>&1", getpid());
+       fflush(stdout);
+       (void)system(cmd);
+    }
     exit(0);
-    return 0;
+    return (0);
 }
-#endif /* TEST_IPMONITOR */
-
-#ifdef TEST_IPV4_ROUTELIST
 
-#include "dns-configuration.c"
+#endif /* TEST_IPV4_ROUTELIST */
 
-#if    !TARGET_IPHONE_SIMULATOR
-#include "set-hostname.c"
-#endif /* !TARGET_IPHONE_SIMULATOR */
+#ifdef TEST_IPV6_ROUTELIST
 
-struct ipv4_service_contents {
+typedef struct {
     const char *       addr;
-    const char *       mask;
+    int                        prefix_length;
     const char *       dest;
+} IPv6Address;
+
+typedef const IPv6Address * IPv6AddressRef;
+
+typedef struct {
+    IPv6AddressRef     addr;
+    int                        addr_count;
     const char *       router;
     const char *       ifname;
     Rank               rank;
-    const CFStringRef  *primaryRank;
+    const CFStringRef *        primary_rank;
+    struct route *     additional_routes;
+    int                        additional_routes_count;
+    struct route *     excluded_routes;
+    int                        excluded_routes_count;
+} IPv6ServiceContents;
+
+typedef const IPv6ServiceContents * IPv6ServiceContentsRef;
+
+struct route loop_routelist[] = {
+    { "2620:149:4:f01:225:ff:fecc:89a1", 128,
+      "2620:149:4:f01:225:ff:fecc:89a2", NULL },
+    { "2620:149:4:f01:225:ff:fecc:89a2", 128,
+      "2620:149:4:f01:225:ff:fecc:89a3", NULL },
+    { "2620:149:4:f01:225:ff:fecc:89a3", 128,
+      "2620:149:4:f01:225:ff:fecc:89a4", NULL },
+    { "2620:149:4:f01:225:ff:fecc:89a4", 128,
+      "2620:149:4:f01:225:ff:fecc:89a5", NULL },
+    { "2620:149:4:f01:225:ff:fecc:89a5", 128,
+      "2620:149:4:f01:225:ff:fecc:89a6", NULL },
+    { "2620:149:4:f01:225:ff:fecc:89a6", 128,
+      "2620:149:4:f01:225:ff:fecc:89a7", NULL },
+    { "2620:149:4:f01:225:ff:fecc:89a7", 128,
+      "2620:149:4:f01:225:ff:fecc:89a8", NULL },
+    { "2620:149:4:f01:225:ff:fecc:89a8", 128,
+      "2620:149:4:f01:225:ff:fecc:89a9", NULL },
+    { "2620:149:4:f01:225:ff:fecc:89a9", 128,
+      "2620:149:4:f01:225:ff:fecc:89aa", NULL },
+    { "2620:149:4:f01:225:ff:fecc:89aa", 128,
+      "2620:149:4:f01:225:ff:fecc:89ab", NULL },
+    { "2620:149:4:f01:225:ff:fecc:89ab", 128,
+      "2620:149:4:f01:225:ff:fecc:89a1", NULL },
 };
 
-/*
- *  addr       mask            dest            router          ifname  pri  primaryRank
- */
-struct ipv4_service_contents en0_10 = {
-    "10.0.0.10", "255.255.255.0", NULL,                "10.0.0.1",     "en0",  10,  NULL
+struct route vpn_routelist[] = {
+    { "2010:470:1f05:3cb::", 64,
+      "fe80::2d0:bcff:fe3d:8c00", NULL },
+    { "2010:222:3fa5:acb::", 48,
+      "fe80::2d0:bcff:fe3d:8c00", NULL },
+    { "2010:222:3fa5:1234::", 40,
+      "fe80::2d0:bcff:fe3d:8c00", NULL },
+    { "2010:222:3fa5:5678::", 40,
+      NULL, NULL },
 };
 
-struct ipv4_service_contents en0_15 = {
-    "10.0.0.19", "255.255.255.0", NULL,                "10.0.0.1",     "en0",  15,  NULL
+struct route vpn_routelist_ext[] = {
+    { "2020:299:a:e02:825:1ed:fecc:abab", 128, NULL, NULL },
 };
 
-struct ipv4_service_contents en0_30 = {
-    "10.0.0.11", "255.255.255.0", NULL,                "10.0.0.1",     "en0",  30,  NULL
+struct route en1_routelist_ext[] = {
+    { "2020:299:abcd:ef12::", 64, NULL, NULL },
 };
 
-struct ipv4_service_contents en0_40 = {
-    "10.0.0.12", "255.255.255.0", NULL,                "10.0.0.1",     "en0",  40,  NULL
-};
 
-struct ipv4_service_contents en0_50 = {
-    "10.0.0.13", "255.255.255.0", NULL,                "10.0.0.1",     "en0",  50,  NULL
+static const IPv6Address en0_addr1[] = {
+    { "2001:470:1f05:3cb:cabc:c8ff:fe96:9601", 64, NULL },
+    { "2001:470:1f05:3cb:5c95:58b1:b956:6101", 64, NULL }
 };
 
-struct ipv4_service_contents en0_110 = {
-    "192.168.2.10", "255.255.255.0", NULL,     "192.168.2.1",  "en0",  110, NULL
+static const IPv6Address en0_addr2[] = {
+    { "2001:470:1f05:3cb:cabc:c8ff:fe96:9602", 64, NULL },
+    { "2001:470:1f05:3cb:5c95:58b1:b956:6102", 64, NULL }
 };
 
-struct ipv4_service_contents en0_1 = {
-    "17.202.40.191", "255.255.252.0", NULL,    "17.202.20.1",  "en0",  1,   NULL
+static const IPv6Address en0_addr3[] = {
+    { "2001:470:1f05:3cb:cabc:c8ff:fe96:9603", 64, NULL },
+    { "2001:470:1f05:3cb:5c95:58b1:b956:6103", 64, NULL }
 };
 
-struct ipv4_service_contents en1_20 = {
-    "10.0.0.20", "255.255.255.0", NULL,                "10.0.0.1",     "en1",  20,  NULL
+static const IPv6Address en0_addr4[] = {
+    { "2001:470:1f05:3cb:cabc:c8ff:fe96:9604", 64, NULL },
+    { "2001:470:1f05:3cb:5c95:58b1:b956:6104", 64, NULL }
 };
 
-struct ipv4_service_contents en1_2 = {
-    "17.202.42.24", "255.255.252.0", NULL,     "17.202.20.1",  "en1",  2,   NULL
+static const IPv6Address en0_addr5[] = {
+    { "2001:470:1f05:3cb:cabc:c8ff:fe96:9605", 64, NULL },
+    { "2001:470:1f05:3cb:5c95:58b1:b956:6105", 64, NULL }
 };
 
-struct ipv4_service_contents en1_125 = {
-    "192.168.2.20", "255.255.255.0", NULL,     "192.168.2.1",  "en1",  125, NULL
+static const IPv6Address en0_addr6[] = {
+    { "2020:299:abcd:ef12:1:2:3:4", 64, NULL },
 };
 
-struct ipv4_service_contents fw0_25 = {
-    "192.168.2.30", "255.255.255.0", NULL,     "192.168.2.1",  "fw0",  25,  NULL
+static const IPv6Address en0_lladdr[] = {
+    { "fe80::cabc:c8ff:fe96:96af", 64, NULL }
 };
 
-struct ipv4_service_contents fw0_21 = {
-    "192.168.3.30", "255.255.255.0", NULL,     "192.168.3.1",  "fw0",  21,  NULL
+static const IPv6Address en1_addr[] = {
+    { "2001:470:1f05:3cb:cabc:c8ff:fed9:125a", 64, NULL },
+    { "2001:470:1f05:3cb:2d5e:4ec3:304:5b9c", 64, NULL }
 };
 
-struct ipv4_service_contents ppp0_0_1 = {
-    "17.219.156.22", NULL, "17.219.156.1",     "17.219.156.1", "ppp0", 0,   NULL
+static const IPv6Address utun0_addr[] = {
+    { "2620:149:4:f01:225:ff:fecc:89aa", 64, NULL },
 };
 
-struct ipv4_service_contents en0_test6 = {
-    "17.202.42.113",  "255.255.252.0", NULL,   "17.202.40.1",  "en0",  2,   NULL
-};
-struct ipv4_service_contents en1_test6 = {
-    "17.202.42.111",  "255.255.252.0", NULL,   "17.202.40.1",  "en1",  3,   NULL
-};
-struct ipv4_service_contents en2_test6 = {
-    "17.255.98.164",  "255.255.240.0", NULL,   "17.255.96.1",  "en2",  1,   NULL
+static const IPv6Address fw0_addr1[] = {
+    { "2011:470:1f05:3cb:cabc:c8ff:fe96:ab01", 64, NULL },
+    { "2011:470:1f05:3cb:5c95:58b1:b956:ab01", 64, NULL }
 };
 
-struct ipv4_service_contents en0_test7 = {
-    "17.202.42.113",  "255.255.252.0", NULL,   "17.202.40.1",  "en0",  3,   NULL
-};
-struct ipv4_service_contents en1_test7 = {
-    "17.202.42.111",  "255.255.252.0", NULL,   "17.202.40.1",  "en1",  2,   NULL
-};
-struct ipv4_service_contents en2_test7 = {
-    "17.255.98.164",  "255.255.240.0", NULL,   "17.255.96.1",  "en2",  1,   NULL
-};
-struct ipv4_service_contents fw0_test6_and_7 = {
-    "169.254.11.33",  "255.255.0.0", NULL,     NULL,           "fw0", 0x0ffffff,  NULL
-};
+/*
+ * address+address-count
+ * router ifname pri rank additional-routes+count excluded-routes+count
+ */
 
-struct ipv4_service_contents en0_10_last = {
-    "10.0.0.10", "255.255.255.0", NULL,                "10.0.0.1",     "en0",  10,  &kSCValNetServicePrimaryRankLast
+static const IPv6ServiceContents en0_10 = {
+    en0_addr1, countof(en0_addr1),
+    "fe80::21f:f3ff:fe43:1abf", "en0", 10, NULL, NULL, 0, NULL, 0
 };
 
-struct ipv4_service_contents en0_10_never = {
-    "10.0.0.10", "255.255.255.0", NULL,                "10.0.0.1",     "en0",  10,  &kSCValNetServicePrimaryRankNever
+static const IPv6ServiceContents en0_15 = {
+    en0_addr2, countof(en0_addr2),
+    "fe80::21f:f3ff:fe43:1abf", "en0", 15, NULL, NULL, 0, NULL, 0
 };
 
-struct ipv4_service_contents en1_20_first = {
-    "10.0.0.20", "255.255.255.0", NULL,                "10.0.0.1",     "en1",  20,  &kSCValNetServicePrimaryRankFirst
+static const IPv6ServiceContents en0_30 = {
+    en0_addr3, countof(en0_addr3),
+    "fe80::21f:f3ff:fe43:1abf", "en0", 30, NULL, NULL, 0, NULL, 0
 };
 
-struct ipv4_service_contents en1_20_never = {
-    "10.0.0.20", "255.255.255.0", NULL,                "10.0.0.1",     "en1",  20,  &kSCValNetServicePrimaryRankNever
+static const IPv6ServiceContents en0_40 = {
+    en0_addr4, countof(en0_addr4),
+    "fe80::21f:f3ff:fe43:1abf", "en0", 40, NULL, NULL, 0, NULL, 0
 };
 
-struct ipv4_service_contents en0_linklocal = {
-    "169.254.22.44", "255.255.0.0", NULL,      NULL,           "en0",  0xfffff,  NULL
+static const IPv6ServiceContents en0_50 = {
+    en0_addr5, countof(en0_addr5),
+    "fe80::21f:f3ff:fe43:1abf", "en0", 50, NULL, NULL, 0, NULL, 0
 };
 
-struct ipv4_service_contents * test1[] = {
-    &en0_40,
-    &en0_15,
-    &fw0_25,
-    &en0_30,
-    &en1_20,
-    &en0_50,
-    &en0_10,
-    NULL
+static const IPv6ServiceContents en0_10_a = {
+    en0_addr6, countof(en0_addr6),
+    "fe80::21f:f3ff:fe43:1abf", "en0", 10, NULL, NULL, 0, NULL, 0
 };
 
-struct ipv4_service_contents * test2[] = {
-    &en0_40,
-    &fw0_25,
-    &en0_30,
-    &en1_20,
-    &en0_50,
-    &en0_10,
-    NULL
+static const IPv6ServiceContents fw0_25 = {
+    fw0_addr1, countof(fw0_addr1),
+    "fe80::21f:f3ff:fe43:1abf", "fw0", 25, NULL, NULL, 0, NULL, 0
 };
 
-struct ipv4_service_contents * test3[] = {
-    &en0_40,
-    &en1_20,
-    &en0_50,
-    &en0_10,
-    &en0_110,
-    &en1_125,
-    &fw0_25,
-    &fw0_21,
-    &en0_40,
-    &en0_30,
-    NULL
+static const IPv6ServiceContents en1_20 = {
+    en1_addr, countof(en1_addr),
+    "fe80::21f:f3ff:fe43:1abf", "en1", 20, NULL, NULL, 0, NULL, 0
 };
 
-struct ipv4_service_contents * test4[] = {
-    &en0_1,
-    &en0_40,
-    &en0_30,
-    &en1_20,
-    &en1_2,
-    NULL
+static const IPv6ServiceContents en1_10_ext = {
+    en1_addr, countof(en1_addr),
+    "fe80::21f:f3ff:fe43:1abf", "en1", 10, NULL, NULL, 0,
+    en1_routelist_ext, countof(en1_routelist_ext)
 };
 
-struct ipv4_service_contents * test5[] = {
-    &ppp0_0_1,
-    &en0_1,
-    &en0_40,
-    &en0_30,
-    &en1_20,
-    &en1_2,
-    NULL
+static const IPv6ServiceContents en0_0_lladdr = {
+    en0_lladdr, countof(en0_lladdr),
+    "fe80::21f:f3ff:fe43:1abf", "en0", 20, NULL, NULL, 0, NULL, 0
 };
 
-struct ipv4_service_contents * test6[] = {
-    &en0_test6,
-    &en1_test6,
-    &en2_test6,
-    &fw0_test6_and_7,
-    NULL
+static const IPv6ServiceContents en0_loop = {
+    en0_addr1, countof(en0_addr1),
+    "fe80::21f:f3ff:fe43:1abf", "en0", 10, NULL,
+    loop_routelist, countof(loop_routelist), NULL, 0
 };
 
-struct ipv4_service_contents * test7[] = {
-    &en0_test7,
-    &en1_test7,
-    &en2_test7,
-    &fw0_test6_and_7,
-    NULL
+static const IPv6ServiceContents utun0 = {
+    utun0_addr, countof(utun0_addr),
+    "fe80::2d0:bcff:fe3d:8c00", "utun0", 40, NULL,
+    vpn_routelist, countof(vpn_routelist),
+    vpn_routelist_ext, countof(vpn_routelist_ext),
 };
 
-struct ipv4_service_contents * test8[] = {
-    &en0_10,
-    &en1_20,
-    NULL
-};
+typedef struct {
+    const char *               name;
+    IPv6ServiceContentsRef     test[];
+} IPv6RouteTest, * IPv6RouteTestRef;
 
-struct ipv4_service_contents * test9[] = {
-    &en0_10,
-    &en1_20_first,
-    &fw0_25,
-    NULL
+static IPv6RouteTest test1 = {
+    "test1",
+    {
+       &en0_40,
+       &en0_15,
+       &fw0_25,
+       &en0_30,
+       &en1_20,
+       &en0_50,
+       &en0_10,
+       NULL
+    }
 };
 
-struct ipv4_service_contents * test10[] = {
-    &en0_10_last,
-    &en1_20,
-    &fw0_25,
-    NULL
+static IPv6RouteTest test2 = {
+    "test2",
+    {
+       &en0_40,
+       &fw0_25,
+       &en0_30,
+       &en1_20,
+       &en0_50,
+       &en0_10,
+       NULL
+    }
 };
 
-struct ipv4_service_contents * test11[] = {
-    &en0_10_never,
-    &en1_20,
-    &fw0_25,
-    NULL
+static IPv6RouteTest test3 = {
+    "test3",
+    {
+       &en0_10_a,
+       &en1_10_ext,
+       NULL
+    }
 };
 
-struct ipv4_service_contents * test12[] = {
-    &en0_10,
-    &en1_20,
-    NULL
+static IPv6RouteTest test4 = {
+    "test4",
+    {
+       &en0_loop,
+       &en1_20,
+       NULL
+    }
 };
 
-struct ipv4_service_contents * test13[] = {
-    &en0_10,
-    &en1_20_never,
-    NULL
+static IPv6RouteTest test5 = {
+    "test5",
+    {
+       &en0_10,
+       &utun0,
+       &en0_0_lladdr,
+       &en1_20,
+       NULL
+    }
 };
 
-struct ipv4_service_contents * test14[] = {
-    &en1_20_never,
-    NULL
-};
 
-struct ipv4_service_contents * test15[] = {
-    &en0_linklocal,
+static IPv6RouteTestRef ipv6_tests[] = {
+    &test1,
+    &test2,
+    &test3,
+    &test4,
+    &test5,
     NULL
 };
 
+
 static void
 dict_add_string(CFMutableDictionaryRef dict, CFStringRef prop_name,
                const char * str)
@@ -6784,189 +9412,273 @@ dict_add_string(CFMutableDictionaryRef dict, CFStringRef prop_name,
 }
 
 static void
-dict_add_string_as_array(CFMutableDictionaryRef dict, CFStringRef prop_name,
-                        const char * str)
+dict_add_int(CFMutableDictionaryRef dict, CFStringRef prop_name,
+            int int_val)
 {
-    CFArrayRef         array;
-    CFStringRef                prop_val;
+    CFNumberRef                num;
 
-    if (str == NULL) {
+    num = CFNumberCreate(NULL, kCFNumberIntType, &int_val);
+    CFDictionarySetValue(dict, prop_name, num);
+    CFRelease(num);
+    return;
+}
+
+static void
+dict_insert_v6_routes(CFMutableDictionaryRef dict, CFStringRef prop_name,
+                     struct route * routes, int routes_count)
+{
+    int                        i;
+    CFMutableArrayRef  route_list;
+    struct route *     scan;
+
+    if (routes == NULL || routes_count == 0) {
        return;
     }
-    prop_val = CFStringCreateWithCString(NULL,
-                                        str,
-                                        kCFStringEncodingASCII);
-    array = CFArrayCreate(NULL,
-                         (const void **)&prop_val, 1,
-                         &kCFTypeArrayCallBacks);
-    CFRelease(prop_val);
-    CFDictionarySetValue(dict, prop_name, array);
-    CFRelease(array);
+    route_list = CFArrayCreateMutable(NULL, routes_count,
+                                     &kCFTypeArrayCallBacks);
+    for (i = 0, scan = routes; i < routes_count; i++, scan++) {
+       CFMutableDictionaryRef          route_dict;
+
+       route_dict = CFDictionaryCreateMutable(NULL, 0,
+                                              &kCFTypeDictionaryKeyCallBacks,
+                                              &kCFTypeDictionaryValueCallBacks);
+       dict_add_string(route_dict, kSCPropNetIPv6RouteDestinationAddress,
+                       scan->dest);
+       dict_add_int(route_dict, kSCPropNetIPv6PrefixLength,
+                    scan->prefix_length);
+       dict_add_string(route_dict, kSCPropNetIPv6RouteGatewayAddress,
+                       scan->gateway);
+       dict_add_string(route_dict, kSCPropNetIPv6RouteInterfaceName,
+                       scan->ifname);
+       CFArrayAppendValue(route_list, route_dict);
+       CFRelease(route_dict);
+    }
+    CFDictionarySetValue(dict, prop_name, route_list);
+    CFRelease(route_list);
+    return;
+}
+
+static void
+array_add_string(CFMutableArrayRef array, const char * c_str)
+{
+    CFStringRef                str;
+
+    str = CFStringCreateWithCString(NULL,
+                                   c_str,
+                                   kCFStringEncodingUTF8);
+    CFArrayAppendValue(array, str);
+    CFRelease(str);
+    return;
+}
+
+static void
+array_add_int(CFMutableArrayRef array, int int_val)
+{
+    CFNumberRef                num;
+
+    num = CFNumberCreate(NULL, kCFNumberIntType, &int_val);
+    CFArrayAppendValue(array, num);
+    CFRelease(num);
+    return;
+}
+
+static void
+dict_add_ipv6_addressing(CFMutableDictionaryRef dict,
+                        IPv6AddressRef list, int list_count)
+{
+    CFMutableArrayRef  addr = NULL;
+    CFMutableArrayRef  dest = NULL;
+    int                        i;
+    CFMutableArrayRef  prefix = NULL;
+    IPv6AddressRef     scan;
+
+    if (list == NULL || list_count == 0) {
+       return;
+    }
+    for (i = 0, scan = list; i < list_count; i++, scan++) {
+       if (scan->addr != NULL) {
+           if (addr == NULL) {
+               addr = CFArrayCreateMutable(NULL, list_count,
+                                           &kCFTypeArrayCallBacks);
+           }
+           array_add_string(addr, scan->addr);
+       }
+       if (scan->prefix_length >= 0) {
+           if (prefix == NULL) {
+               prefix = CFArrayCreateMutable(NULL, list_count,
+                                             &kCFTypeArrayCallBacks);
+           }
+           array_add_int(prefix, scan->prefix_length);
+       }
+       if (scan->dest != NULL) {
+           if (dest == NULL) {
+               dest = CFArrayCreateMutable(NULL, list_count,
+                                           &kCFTypeArrayCallBacks);
+           }
+           array_add_string(dest, scan->dest);
+       }
+    }
+    if (addr != NULL) {
+       CFDictionarySetValue(dict, kSCPropNetIPv6Addresses, addr);
+       CFRelease(addr);
+    }
+    if (dest != NULL) {
+       CFDictionarySetValue(dict, kSCPropNetIPv6DestAddresses, dest);
+       CFRelease(dest);
+    }
+    if (prefix != NULL) {
+       CFDictionarySetValue(dict, kSCPropNetIPv6PrefixLength, prefix);
+       CFRelease(prefix);
+    }
     return;
 }
 
 static CFDictionaryRef
-make_IPv4_dict(struct ipv4_service_contents * t)
+make_IPv6_dict(IPv6ServiceContentsRef t)
 {
     CFMutableDictionaryRef     dict;
 
     dict = CFDictionaryCreateMutable(NULL, 0,
                                     &kCFTypeDictionaryKeyCallBacks,
                                     &kCFTypeDictionaryValueCallBacks);
-    dict_add_string_as_array(dict, kSCPropNetIPv4Addresses, t->addr);
-    dict_add_string_as_array(dict, kSCPropNetIPv4SubnetMasks, t->mask);
-    dict_add_string_as_array(dict, kSCPropNetIPv4DestAddresses, t->dest);
-    dict_add_string(dict, kSCPropNetIPv4Router, t->router);
+    dict_add_ipv6_addressing(dict, t->addr, t->addr_count);
+    dict_add_string(dict, kSCPropNetIPv6Router, t->router);
     dict_add_string(dict, kSCPropInterfaceName, t->ifname);
+    dict_insert_v6_routes(dict, kSCPropNetIPv6AdditionalRoutes,
+                         t->additional_routes, t->additional_routes_count);
+    dict_insert_v6_routes(dict, kSCPropNetIPv6ExcludedRoutes,
+                         t->excluded_routes, t->excluded_routes_count);
     return (dict);
 }
 
-static IPv4RouteListRef
-make_IPv4RouteList(struct ipv4_service_contents * * this_test)
-{
-    IPv4RouteListRef           r;
-    IPv4RouteListRef           routes;
-    char                       routes_buf[IPv4RouteListComputeSize(R_STATIC)];
-    IPv4RouteListRef           ret = NULL;
-    struct ipv4_service_contents * *   scan_test;
+typedef enum {
+    kDirectionForwards = 0,
+    kDirectionBackwards = 1
+} Direction;
 
-    for (scan_test = this_test; *scan_test != NULL; scan_test++) {
-       CFDictionaryRef         dict;
+typedef enum {
+    kLogRouteDisabled = 0,
+    kLogRouteEnabled = 1
+} LogRoute;
 
-       dict = make_IPv4_dict(*scan_test);
-       if (dict == NULL) {
-           fprintf(stderr, "make_IPv4_dict failed\n");
-           exit(1);
-       }
-       routes = (IPv4RouteListRef)routes_buf;
-       routes->size = R_STATIC;
-       routes->count = 0;
-       r = IPv4RouteListCreateWithDictionary(routes, dict,
-               (*scan_test)->primaryRank ? *(*scan_test)->primaryRank : NULL);
-       if (r == NULL) {
-           fprintf(stderr, "IPv4RouteListCreateWithDictionary failed\n");
-           exit(1);
+static IPv6RouteListRef
+make_IPv6RouteList_for_test(IPv6RouteListRef list,
+                           IPv6ServiceContentsRef test,
+                           LogRoute log_it)
+{
+    CFDictionaryRef    dict;
+    IPv6RouteListRef   r;
+    Rank               rank;
+    Rank               rank_assertion = kRankAssertionDefault;
+    CFNumberRef                rank_assertion_cf = NULL;
+    Boolean            rank_assertion_is_set = FALSE;
+    IPv6RouteListRef   ret = NULL;
+    IPV6_ROUTES_BUF_DECL(routes);
+
+    dict = make_IPv6_dict(test);
+    if (dict == NULL) {
+       fprintf(stderr, "make_IPv6_dict failed\n");
+       exit(1);
+    }
+    if (test->primary_rank != NULL) {
+       rank_assertion
+           = PrimaryRankGetRankAssertion(*test->primary_rank,
+                                         &rank_assertion_is_set);
+       if (rank_assertion_is_set) {
+           rank_assertion_cf
+               = CFNumberCreate(NULL, kCFNumberSInt32Type, &rank_assertion);
        }
+    }
+    r = IPv6RouteListCreateWithDictionary(routes, dict,
+                                         rank_assertion_cf);
+    my_CFRelease(&rank_assertion_cf);
+    if (r == NULL) {
+       fprintf(stderr, "IPv6RouteListCreateWithDictionary failed\n");
+       exit(1);
+    }
 
-       (*scan_test)->rank = RankMake((*scan_test)->rank, kRankAssertionDefault);
+    if (rank_assertion == kRankAssertionScoped) {
+       rank_assertion = kRankAssertionNever;
+    }
+    rank = RankMake(test->rank, rank_assertion);
+    if (log_it == kLogRouteEnabled
+       && (S_IPMonitor_debug & kDebugFlag4) != 0) {
+       CFStringRef     descr;
 
-       if ((*scan_test)->primaryRank != NULL) {
-           (*scan_test)->rank = RankMake((*scan_test)->rank,
-                                            PrimaryRankGetRankAssertion(*(*scan_test)->primaryRank));
-       }
+       descr = IPv6RouteListCopyDescription(r);
+       SCLog(TRUE, LOG_NOTICE, CFSTR("Adding %@"), descr);
+       CFRelease(descr);
+    }
+    ret = IPv6RouteListAddRouteList(list, 1, r, rank);
+    if (r != routes) {
+       free(r);
+    }
+    CFRelease(dict);
+    return (ret);
+}
 
-       if ((*scan_test)->router == NULL) {
-           (*scan_test)->rank = RankMake((*scan_test)->rank,
-                                           PrimaryRankGetRankAssertion(kSCValNetServicePrimaryRankLast));
-       }
+static IPv6RouteListRef
+make_IPv6RouteList(IPv6ServiceContentsRef * test, Direction direction,
+                  LogRoute log_it)
+{
+    IPv6RouteListRef           ret = NULL;
+    IPv6ServiceContentsRef *   scan;
 
-       ret = IPv4RouteListAddRouteList(ret, 1, r, (*scan_test)->rank);
-       if (r != routes) {
-           free(r);
+    switch (direction) {
+    case kDirectionBackwards:
+       for (scan = test; *scan != NULL; scan++) {
+           /* find the end of the list */
        }
-       CFRelease(dict);
+       for (scan--; scan >= test; scan--) {
+           ret = make_IPv6RouteList_for_test(ret, *scan, log_it);
+       }
+       break;
+    default:
+    case kDirectionForwards:
+       for (scan = test; *scan != NULL; scan++) {
+           ret = make_IPv6RouteList_for_test(ret, *scan, log_it);
+       }
+       break;
     }
+    IPv6RouteListFinalize(ret);
     return (ret);
 }
 
+#define EMPHASIS_CHARS "================="
+
 /*
- * Function: run_test
+ * Function: routelist_build_test
  * Purpose:
  *   Runs through the given set of routes first in the forward direction,
  *   then again backwards.  We should end up with exactly the same set of
  *   routes at the end.
  */
 static boolean_t
-run_test(const char * name, struct ipv4_service_contents * * this_test)
+routelist_build_test(IPv6RouteTestRef test)
 {
     CFStringRef                        descr;
     boolean_t                  ret = FALSE;
-    IPv4RouteListRef           r;
-    IPv4RouteListRef           routes;
-    char                       routes_buf[IPv4RouteListComputeSize(R_STATIC)];
-    IPv4RouteListRef           routes1 = NULL, routes2 = NULL;
-    struct ipv4_service_contents * *   scan_test;
-
-    printf("\nStarting test %s\n", name);
-    for (scan_test = this_test; *scan_test != NULL; scan_test++) {
-       CFDictionaryRef         dict;
-
-       dict = make_IPv4_dict(*scan_test);
-       if (dict == NULL) {
-           fprintf(stderr, "make_IPv4_dict failed\n");
-           exit(1);
-       }
-       routes = (IPv4RouteListRef)routes_buf;
-       routes->size = R_STATIC;
-       routes->count = 0;
-       r = IPv4RouteListCreateWithDictionary(routes, dict,
-               (*scan_test)->primaryRank ? *(*scan_test)->primaryRank : NULL);
-       if (r == NULL) {
-           fprintf(stderr, "IPv4RouteListCreateWithDictionary failed\n");
-           exit(1);
-       }
-       if ((S_IPMonitor_debug & kDebugFlag4) != 0) {
-           descr = IPv4RouteListCopyDescription(r);
-           SCPrint(TRUE, stdout, CFSTR("test: Adding %@\n"), descr);
-           CFRelease(descr);
-       }
-
-       (*scan_test)->rank = RankMake((*scan_test)->rank, kRankAssertionDefault);
+    IPv6RouteListRef           routes1;
+    IPv6RouteListRef           routes2;
 
-       if ((*scan_test)->primaryRank != NULL) {
-           (*scan_test)->rank = RankMake((*scan_test)->rank,
-                                           PrimaryRankGetRankAssertion(*(*scan_test)->primaryRank));
-       }
+    printf("\n" EMPHASIS_CHARS  "> RouteList Build '%s' <"
+          EMPHASIS_CHARS "\n",
+          test->name);
 
-       routes1 = IPv4RouteListAddRouteList(routes1, 1, r, (*scan_test)->rank);
-       if (r != routes) {
-           free(r);
-       }
-       CFRelease(dict);
-    }
+    routes1 = make_IPv6RouteList(test->test, kDirectionForwards,
+                                kLogRouteEnabled);
     if ((S_IPMonitor_debug & kDebugFlag4) != 0) {
        if (routes1 != NULL) {
-           descr = IPv4RouteListCopyDescription(routes1);
+           descr = IPv6RouteListCopyDescription(routes1);
            SCPrint(TRUE, stdout, CFSTR("Routes are %@\n"), descr);
            CFRelease(descr);
        }
     }
-    for (scan_test--; scan_test >= this_test; scan_test--) {
-       CFDictionaryRef         dict;
-
-       dict = make_IPv4_dict(*scan_test);
-       if (dict == NULL) {
-           fprintf(stderr, "make_IPv4_dict failed\n");
-           exit(1);
-       }
-       routes = (IPv4RouteListRef)routes_buf;
-       routes->size = R_STATIC;
-       routes->count = 0;
-       r = IPv4RouteListCreateWithDictionary(routes, dict,
-               (*scan_test)->primaryRank ? *(*scan_test)->primaryRank : NULL);
-       if (r == NULL) {
-           fprintf(stderr, "IPv4RouteListCreateWithDictionary failed\n");
-           exit(1);
-       }
-       if ((S_IPMonitor_debug & kDebugFlag4) != 0) {
-           descr = IPv4RouteListCopyDescription(r);
-           SCPrint(TRUE, stdout, CFSTR("test: Adding %@\n"), descr);
-           CFRelease(descr);
-       }
-       if ((*scan_test)->primaryRank != NULL) {
-           (*scan_test)->rank = RankMake((*scan_test)->rank,
-                                           PrimaryRankGetRankAssertion(*(*scan_test)->primaryRank));
-       }
-
-       routes2 = IPv4RouteListAddRouteList(routes2, 1, r, (*scan_test)->rank);
-       if (r != routes) {
-           free(r);
-       }
-       CFRelease(dict);
-    }
+    routes2 = make_IPv6RouteList(test->test, kDirectionBackwards,
+                                kLogRouteEnabled);
     if ((S_IPMonitor_debug & kDebugFlag4) != 0) {
        if (routes2 != NULL) {
-           descr = IPv4RouteListCopyDescription(routes2);
+           descr = IPv6RouteListCopyDescription(routes2);
            SCPrint(TRUE, stdout, CFSTR("Routes are %@\n"), descr);
            CFRelease(descr);
        }
@@ -6984,7 +9696,7 @@ run_test(const char * name, struct ipv4_service_contents * * this_test)
                    routes1->count, routes2->count);
        }
        else if (bcmp(routes1, routes2,
-                     IPv4RouteListComputeSize(routes1->count)) != 0) {
+                     IPv6RouteListComputeSize(routes1->count)) != 0) {
            fprintf(stderr, "routes1 and routes2 are different\n");
        }
        else {
@@ -6998,166 +9710,96 @@ run_test(const char * name, struct ipv4_service_contents * * this_test)
     if (routes2 != NULL) {
        free(routes2);
     }
+    printf(EMPHASIS_CHARS  "> RouteList Build '%s': %s <"
+          EMPHASIS_CHARS "\n",
+          test->name, ret ? "PASSED" : "FAILED");
     return (ret);
 }
 
-typedef struct compare_context {
-    IPv4RouteListRef   old;
-    IPv4RouteListRef   new;
-} compare_context_t;
-
-static void
-compare_callback(IPv4RouteListApplyCommand cmd, IPv4RouteRef route, void * arg)
-{
-    compare_context_t *        context = (compare_context_t *)arg;
-
-    switch (cmd) {
-    case kIPv4RouteListAddRouteCommand:
-       printf("Add new[%ld] = ", route - context->new->list);
-       IPv4RoutePrint(route);
-       break;
-    case kIPv4RouteListRemoveRouteCommand:
-       printf("Remove old[%ld] = ", route - context->old->list);
-       IPv4RoutePrint(route);
-       break;
-    default:
-       break;
-    }
-    return;
-}
-
 static void
-compare_tests(struct ipv4_service_contents * * old_test,
-             struct ipv4_service_contents * * new_test)
+apply_test(IPv6RouteTestRef old_test, IPv6RouteTestRef new_test)
 {
-    IPv4RouteListRef   new_routes;
-    IPv4RouteListRef   old_routes;
-    compare_context_t  context;
+    IPv6RouteListRef   new_routes;
+    IPv6RouteListRef   old_routes;
 
-    old_routes = make_IPv4RouteList(old_test);
-    new_routes = make_IPv4RouteList(new_test);
+    printf("\n" EMPHASIS_CHARS  "> Apply '%s', '%s' Begin <"
+          EMPHASIS_CHARS "\n",
+          old_test->name, new_test->name);
 
+    old_routes = make_IPv6RouteList(old_test->test, kDirectionForwards,
+                                   kLogRouteDisabled);
+    new_routes = make_IPv6RouteList(new_test->test, kDirectionForwards,
+                                   kLogRouteDisabled);
     if (old_routes == NULL) {
        printf("No Old Routes\n");
     }
     else {
-       printf("Old Routes = ");
-       IPv4RouteListPrint(old_routes);
+       printf("Old routes ('%s') = ", old_test->name);
+       IPv6RouteListPrint(old_routes);
     }
+
+    /* apply the old routes */
+    IPv6RouteListApply(NULL, old_routes, -1);
     if (new_routes == NULL) {
        printf("No New Routes\n");
     }
     else {
-       printf("New Routes = ");
-       IPv4RouteListPrint(new_routes);
+       printf("New Routes ('%s') = ", new_test->name);
+       IPv6RouteListPrint(new_routes);
     }
-    context.old = old_routes;
-    context.new = new_routes;
-    IPv4RouteListApply(old_routes, new_routes, compare_callback, &context);
+
+    /* apply the new routes */
+    IPv6RouteListApply(old_routes, new_routes, -1);
     if (old_routes != NULL) {
        free(old_routes);
     }
     if (new_routes != NULL) {
        free(new_routes);
     }
-
+    printf(EMPHASIS_CHARS  "> Apply '%s', '%s' End <"
+          EMPHASIS_CHARS "\n",
+          old_test->name, new_test->name);
     return;
 }
 
 int
 main(int argc, char **argv)
 {
+    IPv6RouteTestRef * test;
+
     _sc_log     = FALSE;
     _sc_verbose = (argc > 1) ? TRUE : FALSE;
-
     S_IPMonitor_debug = kDebugFlag1 | kDebugFlag2 | kDebugFlag4;
     if (argc > 1) {
        S_IPMonitor_debug = strtoul(argv[1], NULL, 0);
     }
-
-    if (run_test("test1", test1) == FALSE) {
-       fprintf(stderr, "test1 failed\n");
-       exit(1);
-    }
-    if (run_test("test2", test2) == FALSE) {
-       fprintf(stderr, "test2 failed\n");
-       exit(1);
-    }
-    if (run_test("test3", test4) == FALSE) {
-       fprintf(stderr, "test3 failed\n");
-       exit(1);
-    }
-    if (run_test("test4", test4) == FALSE) {
-       fprintf(stderr, "test4 failed\n");
-       exit(1);
-    }
-    if (run_test("test5", test5) == FALSE) {
-       fprintf(stderr, "test5 failed\n");
-       exit(1);
-    }
-    if (run_test("test15", test15) == FALSE) {
-       fprintf(stderr, "test15 failed\n");
-       exit(1);
+    S_scopedroute_v6 = (argc < 3);
+    for (test = ipv6_tests; *test != NULL; test++) {
+       if (routelist_build_test(*test) == FALSE) {
+           fprintf(stderr, "%s failed\n", (*test)->name);
+           exit(1);
+       }
     }
+    for (test = ipv6_tests; *test != NULL; test++) {
+       IPv6RouteTestRef *      test2;
 
+       for (test2 = test + 1; *test2 != NULL; test2++) {
+           apply_test(*test, *test2);
+           apply_test(*test2, *test);
+       }
+    }
 
-    printf("\nCompare 1 to 2:\n");
-    compare_tests(test1, test2);
-
-    printf("\nCompare 2 to 1:\n");
-    compare_tests(test2, test1);
-
-    printf("\nCompare 1 to 1:\n");
-    compare_tests(test1, test1);
-
-    printf("\nCompare 1 to 3:\n");
-    compare_tests(test1, test3);
-
-    printf("\nCompare 3 to 1:\n");
-    compare_tests(test3, test1);
-
-    printf("\nCompare 2 to 3:\n");
-    compare_tests(test2, test3);
-
-    printf("\nCompare 3 to 2:\n");
-    compare_tests(test3, test2);
-
-    printf("\nCompare 3 to 4:\n");
-    compare_tests(test3, test4);
-
-    printf("\nCompare 5 to 4:\n");
-    compare_tests(test5, test4);
-
-    printf("\nCompare 6 to 7:\n");
-    compare_tests(test6, test7);
-
-    printf("\nCompare 7 to 6:\n");
-    compare_tests(test7, test6);
-
-    printf("\nCompare 8 to 9:\n");
-    compare_tests(test8, test9);
-
-    printf("\nCompare 8 to 10:\n");
-    compare_tests(test8, test10);
-
-    printf("\nCompare 8 to 11:\n");
-    compare_tests(test8, test11);
-
-    printf("\nCompare 12 to 13:\n");
-    compare_tests(test12, test13);
-
-    printf("\nCompare 13 to 14:\n");
-    compare_tests(test13, test14);
-
-    printf("\nChecking for leaks\n");
-    char    cmd[128];
-    sprintf(cmd, "leaks %d 2>&1", getpid());
-    fflush(stdout);
-    (void)system(cmd);
+    {
+       char    cmd[128];
 
+       printf("\nChecking for leaks\n");
+       sprintf(cmd, "leaks %d 2>&1", getpid());
+       fflush(stdout);
+       (void)system(cmd);
+    }
     exit(0);
     return (0);
 }
 
-#endif /* TEST_IPV4_ROUTELIST */
+#endif /* TEST_IPV6_ROUTELIST */