OSDN Git Service

Apply the dns and route diff which we get a change
authorRobert Greenwalt <rgreenwalt@google.com>
Thu, 14 Jul 2011 21:28:05 +0000 (14:28 -0700)
committerRobert Greenwalt <rgreenwalt@google.com>
Fri, 15 Jul 2011 01:12:14 +0000 (18:12 -0700)
bug:5008973
Change-Id: Ib99e43d9d852452cc1f2aea6bef07bbd194f28cc

core/java/android/net/LinkProperties.java
core/java/android/net/RouteInfo.java
services/java/com/android/server/ConnectivityService.java
telephony/java/com/android/internal/telephony/DataConnection.java
telephony/java/com/android/internal/telephony/DataConnectionAc.java
telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java

index f2f0e82..9826bec 100644 (file)
@@ -57,16 +57,16 @@ public class LinkProperties implements Parcelable {
     private Collection<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
     private ProxyProperties mHttpProxy;
 
-    public static class CompareAddressesResult {
-        public ArrayList<LinkAddress> removed = new ArrayList<LinkAddress>();
-        public ArrayList<LinkAddress> added = new ArrayList<LinkAddress>();
+    public static class CompareResult<T> {
+        public ArrayList<T> removed = new ArrayList<T>();
+        public ArrayList<T> added = new ArrayList<T>();
 
         @Override
         public String toString() {
-            String retVal = "removedAddresses=[";
-            for (LinkAddress addr : removed) retVal += addr.toString() + ",";
-            retVal += "] addedAddresses=[";
-            for (LinkAddress addr : added) retVal += addr.toString() + ",";
+            String retVal = "removed=[";
+            for (T addr : removed) retVal += addr.toString() + ",";
+            retVal += "] added=[";
+            for (T addr : added) retVal += addr.toString() + ",";
             retVal += "]";
             return retVal;
         }
@@ -263,10 +263,10 @@ public class LinkProperties implements Parcelable {
      * mLinkAddress which would then result in target and mLinkAddresses
      * being the same list.
      *
-     * @param target is a new list of addresses
+     * @param target is a LinkProperties with the new list of addresses
      * @return the removed and added lists.
      */
-    public CompareAddressesResult compareAddresses(LinkProperties target) {
+    public CompareResult<LinkAddress> compareAddresses(LinkProperties target) {
         /*
          * Duplicate the LinkAddresses into removed, we will be removing
          * address which are common between mLinkAddresses and target
@@ -274,17 +274,81 @@ public class LinkProperties implements Parcelable {
          * are in target but not in mLinkAddresses are placed in the
          * addedAddresses.
          */
-        CompareAddressesResult result = new CompareAddressesResult();
+        CompareResult<LinkAddress> result = new CompareResult<LinkAddress>();
         result.removed = new ArrayList<LinkAddress>(mLinkAddresses);
         result.added.clear();
-        for (LinkAddress newAddress : target.getLinkAddresses()) {
-            if (! result.removed.remove(newAddress)) {
-                result.added.add(newAddress);
+        if (target != null) {
+            for (LinkAddress newAddress : target.getLinkAddresses()) {
+                if (! result.removed.remove(newAddress)) {
+                    result.added.add(newAddress);
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Return two lists, a list of dns addresses that would be removed from
+     * mDnses and a list of addresses that would be added to
+     * mDnses which would then result in target and mDnses
+     * being the same list.
+     *
+     * @param target is a LinkProperties with the new list of dns addresses
+     * @return the removed and added lists.
+     */
+    public CompareResult<InetAddress> compareDnses(LinkProperties target) {
+        /*
+         * Duplicate the InetAddresses into removed, we will be removing
+         * dns address which are common between mDnses and target
+         * leaving the addresses that are different. And dns address which
+         * are in target but not in mDnses are placed in the
+         * addedAddresses.
+         */
+        CompareResult<InetAddress> result = new CompareResult<InetAddress>();
+
+        result.removed = new ArrayList<InetAddress>(mDnses);
+        result.added.clear();
+        if (target != null) {
+            for (InetAddress newAddress : target.getDnses()) {
+                if (! result.removed.remove(newAddress)) {
+                    result.added.add(newAddress);
+                }
             }
         }
         return result;
     }
 
+    /**
+     * Return two lists, a list of routes that would be removed from
+     * mRoutes and a list of routes that would be added to
+     * mRoutes which would then result in target and mRoutes
+     * being the same list.
+     *
+     * @param target is a LinkProperties with the new list of routes
+     * @return the removed and added lists.
+     */
+    public CompareResult<RouteInfo> compareRoutes(LinkProperties target) {
+        /*
+         * Duplicate the RouteInfos into removed, we will be removing
+         * routes which are common between mDnses and target
+         * leaving the routes that are different. And route address which
+         * are in target but not in mRoutes are placed in added.
+         */
+        CompareResult<RouteInfo> result = new CompareResult<RouteInfo>();
+
+        result.removed = new ArrayList<RouteInfo>(mRoutes);
+        result.added.clear();
+        if (target != null) {
+            for (RouteInfo r : target.getRoutes()) {
+                if (! result.removed.remove(r)) {
+                    result.added.add(r);
+                }
+            }
+        }
+        return result;
+    }
+
+
     @Override
     /**
      * generate hashcode based on significant fields
index 8e5ddda..275f32a 100644 (file)
@@ -43,6 +43,7 @@ public class RouteInfo implements Parcelable {
     private final InetAddress mGateway;
 
     private final boolean mIsDefault;
+    private final boolean mIsHost;
 
     public RouteInfo(LinkAddress destination, InetAddress gateway) {
         if (destination == null) {
@@ -68,6 +69,7 @@ public class RouteInfo implements Parcelable {
                 destination.getNetworkPrefixLength()), destination.getNetworkPrefixLength());
         mGateway = gateway;
         mIsDefault = isDefault();
+        mIsHost = isHost();
     }
 
     public RouteInfo(InetAddress gateway) {
@@ -88,6 +90,10 @@ public class RouteInfo implements Parcelable {
         }
     }
 
+    private boolean isHost() {
+        return (mGateway.equals(Inet4Address.ANY) || mGateway.equals(Inet6Address.ANY));
+    }
+
     private boolean isDefault() {
         boolean val = false;
         if (mGateway != null) {
@@ -100,6 +106,7 @@ public class RouteInfo implements Parcelable {
         return val;
     }
 
+
     public LinkAddress getDestination() {
         return mDestination;
     }
@@ -112,6 +119,10 @@ public class RouteInfo implements Parcelable {
         return mIsDefault;
     }
 
+    public boolean isHostRoute() {
+        return mIsHost;
+    }
+
     public String toString() {
         String val = "";
         if (mDestination != null) val = mDestination.toString();
index 812c1eb..edf1907 100644 (file)
@@ -28,7 +28,7 @@ import android.net.EthernetDataTracker;
 import android.net.IConnectivityManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
-import android.net.LinkProperties.CompareAddressesResult;
+import android.net.LinkProperties.CompareResult;
 import android.net.MobileDataStateTracker;
 import android.net.NetworkConfig;
 import android.net.NetworkInfo;
@@ -232,6 +232,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
 
     private InetAddress mDefaultDns;
 
+    // this collection is used to refcount the added routes - if there are none left
+    // it's time to remove the route from the route table
+    private Collection<RouteInfo> mAddedRoutes = new ArrayList<RouteInfo>();
+
     // used in DBG mode to track inet condition reports
     private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
     private ArrayList mInetLog;
@@ -451,7 +455,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                         mNetConfigs[netType].radio);
                 continue;
             }
-            mCurrentLinkProperties[netType] = mNetTrackers[netType].getLinkProperties();
+            mCurrentLinkProperties[netType] = null;
         }
 
         mTethering = new Tethering(mContext, mHandler.getLooper());
@@ -936,62 +940,68 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         }
         try {
             InetAddress addr = InetAddress.getByAddress(hostAddress);
-            return addHostRoute(tracker, addr, 0);
+            LinkProperties lp = tracker.getLinkProperties();
+            return addRoute(lp, RouteInfo.makeHostRoute(addr));
         } catch (UnknownHostException e) {}
         return false;
     }
 
-    /**
-     * Ensure that a network route exists to deliver traffic to the specified
-     * host via the mobile data network.
-     * @param hostAddress the IP address of the host to which the route is desired,
-     * in network byte order.
-     * TODO - deprecate
-     * @return {@code true} on success, {@code false} on failure
-     */
-    private boolean addHostRoute(NetworkStateTracker nt, InetAddress hostAddress, int cycleCount) {
-        LinkProperties lp = nt.getLinkProperties();
-        if ((lp == null) || (hostAddress == null)) return false;
+    private boolean addRoute(LinkProperties p, RouteInfo r) {
+        return modifyRoute(p.getInterfaceName(), p, r, 0, true);
+    }
 
-        String interfaceName = lp.getInterfaceName();
-        if (DBG) {
-            log("Requested host route to " + hostAddress + "(" + interfaceName + "), cycleCount=" +
-                    cycleCount);
-        }
-        if (interfaceName == null) {
-            if (DBG) loge("addHostRoute failed due to null interface name");
+    private boolean removeRoute(LinkProperties p, RouteInfo r) {
+        return modifyRoute(p.getInterfaceName(), p, r, 0, false);
+    }
+
+    private boolean modifyRoute(String ifaceName, LinkProperties lp, RouteInfo r, int cycleCount,
+            boolean doAdd) {
+        if ((ifaceName == null) || (lp == null) || (r == null)) return false;
+
+        if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) {
+            loge("Error adding route - too much recursion");
             return false;
         }
 
-        RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), hostAddress);
-        InetAddress gatewayAddress = null;
-        if (bestRoute != null) {
-            gatewayAddress = bestRoute.getGateway();
-            // if the best route is ourself, don't relf-reference, just add the host route
-            if (hostAddress.equals(gatewayAddress)) gatewayAddress = null;
+        if (r.isHostRoute() == false) {
+            RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), r.getGateway());
+            if (bestRoute != null) {
+                if (bestRoute.getGateway().equals(r.getGateway()) == false) {
+                    bestRoute = RouteInfo.makeHostRoute(r.getGateway(), bestRoute.getGateway());
+                } else {
+                    bestRoute = RouteInfo.makeHostRoute(r.getGateway());
+                }
+                if (!modifyRoute(ifaceName, lp, bestRoute, cycleCount+1, doAdd)) return false;
+            }
         }
-        if (gatewayAddress != null) {
-            if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) {
-                loge("Error adding hostroute - too much recursion");
+        if (doAdd) {
+            if (DBG) log("Adding " + r + " for interface " + ifaceName);
+            mAddedRoutes.add(r);
+            try {
+                mNetd.addRoute(ifaceName, r);
+            } catch (Exception e) {
+                // never crash - catch them all
+                loge("Exception trying to add a route: " + e);
                 return false;
             }
-            if (!addHostRoute(nt, gatewayAddress, cycleCount+1)) return false;
-        }
-
-        RouteInfo route = RouteInfo.makeHostRoute(hostAddress, gatewayAddress);
-
-        try {
-            mNetd.addRoute(interfaceName, route);
-            return true;
-        } catch (Exception ex) {
-            return false;
+        } else {
+            // if we remove this one and there are no more like it, then refcount==0 and
+            // we can remove it from the table
+            mAddedRoutes.remove(r);
+            if (mAddedRoutes.contains(r) == false) {
+                if (DBG) log("Removing " + r + " for interface " + ifaceName);
+                try {
+                    mNetd.removeRoute(ifaceName, r);
+                } catch (Exception e) {
+                    // never crash - catch them all
+                    loge("Exception trying to remove a route: " + e);
+                    return false;
+                }
+            } else {
+                if (DBG) log("not removing " + r + " as it's still in use");
+            }
         }
-    }
-
-    // TODO support the removal of single host routes.  Keep a ref count of them so we
-    // aren't over-zealous
-    private boolean removeHostRoute(NetworkStateTracker nt, InetAddress hostAddress) {
-        return false;
+        return true;
     }
 
     /**
@@ -1427,10 +1437,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
          */
         handleDnsConfigurationChange(netType);
 
+        LinkProperties curLp = mCurrentLinkProperties[netType];
+        LinkProperties newLp = null;
+
         if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
-            LinkProperties newLp = mNetTrackers[netType].getLinkProperties();
-            LinkProperties curLp = mCurrentLinkProperties[netType];
-            mCurrentLinkProperties[netType] = newLp;
+            newLp = mNetTrackers[netType].getLinkProperties();
             if (VDBG) {
                 log("handleConnectivityChange: changed linkProperty[" + netType + "]:" +
                         " doReset=" + doReset + " resetMask=" + resetMask +
@@ -1438,48 +1449,50 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                         "\n   newLp=" + newLp);
             }
 
-            if (curLp.isIdenticalInterfaceName(newLp)) {
-                CompareAddressesResult car = curLp.compareAddresses(newLp);
-                if ((car.removed.size() != 0) || (car.added.size() != 0)) {
-                    for (LinkAddress linkAddr : car.removed) {
-                        if (linkAddr.getAddress() instanceof Inet4Address) {
-                            resetMask |= NetworkUtils.RESET_IPV4_ADDRESSES;
+            if (curLp != null) {
+                if (curLp.isIdenticalInterfaceName(newLp)) {
+                    CompareResult<LinkAddress> car = curLp.compareAddresses(newLp);
+                    if ((car.removed.size() != 0) || (car.added.size() != 0)) {
+                        for (LinkAddress linkAddr : car.removed) {
+                            if (linkAddr.getAddress() instanceof Inet4Address) {
+                                resetMask |= NetworkUtils.RESET_IPV4_ADDRESSES;
+                            }
+                            if (linkAddr.getAddress() instanceof Inet6Address) {
+                                resetMask |= NetworkUtils.RESET_IPV6_ADDRESSES;
+                            }
                         }
-                        if (linkAddr.getAddress() instanceof Inet6Address) {
-                            resetMask |= NetworkUtils.RESET_IPV6_ADDRESSES;
+                        if (DBG) {
+                            log("handleConnectivityChange: addresses changed" +
+                                    " linkProperty[" + netType + "]:" + " resetMask=" + resetMask +
+                                    "\n   car=" + car);
+                        }
+                    } else {
+                        if (DBG) {
+                            log("handleConnectivityChange: address are the same reset per doReset" +
+                                   " linkProperty[" + netType + "]:" +
+                                   " resetMask=" + resetMask);
                         }
-                    }
-                    if (DBG) {
-                        log("handleConnectivityChange: addresses changed" +
-                                " linkProperty[" + netType + "]:" + " resetMask=" + resetMask +
-                                "\n   car=" + car);
                     }
                 } else {
-                    if (DBG) {
-                        log("handleConnectivityChange: address are the same reset per doReset" +
-                               " linkProperty[" + netType + "]:" +
-                               " resetMask=" + resetMask);
-                    }
+                    resetMask = NetworkUtils.RESET_ALL_ADDRESSES;
+                    log("handleConnectivityChange: interface not not equivalent reset both" +
+                            " linkProperty[" + netType + "]:" +
+                            " resetMask=" + resetMask);
                 }
-            } else {
-                resetMask = NetworkUtils.RESET_ALL_ADDRESSES;
-                log("handleConnectivityChange: interface not not equivalent reset both" +
-                        " linkProperty[" + netType + "]:" +
-                        " resetMask=" + resetMask);
             }
             if (mNetConfigs[netType].isDefault()) {
                 handleApplyDefaultProxy(netType);
-                addDefaultRoute(mNetTrackers[netType]);
-            } else {
-                addPrivateDnsRoutes(mNetTrackers[netType]);
             }
         } else {
-            if (mNetConfigs[netType].isDefault()) {
-                removeDefaultRoute(mNetTrackers[netType]);
-            } else {
-                removePrivateDnsRoutes(mNetTrackers[netType]);
+            if (VDBG) {
+                log("handleConnectivityChange: changed linkProperty[" + netType + "]:" +
+                        " doReset=" + doReset + " resetMask=" + resetMask +
+                        "\n  curLp=" + curLp +
+                        "\n  newLp= null");
             }
         }
+        mCurrentLinkProperties[netType] = newLp;
+        updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault());
 
         if (doReset || resetMask != 0) {
             LinkProperties linkProperties = mNetTrackers[netType].getLinkProperties();
@@ -1503,108 +1516,64 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         }
     }
 
-    private void addPrivateDnsRoutes(NetworkStateTracker nt) {
-        boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet();
-        LinkProperties p = nt.getLinkProperties();
-        if (p == null) return;
-        String interfaceName = p.getInterfaceName();
-
-        if (DBG) {
-            log("addPrivateDnsRoutes for " + nt +
-                    "(" + interfaceName + ") - mPrivateDnsRouteSet = " + privateDnsRouteSet);
-        }
-        if (interfaceName != null && !privateDnsRouteSet) {
-            Collection<InetAddress> dnsList = p.getDnses();
-            for (InetAddress dns : dnsList) {
-                addHostRoute(nt, dns, 0);
+    /**
+     * Add and remove routes using the old properties (null if not previously connected),
+     * new properties (null if becoming disconnected).  May even be double null, which
+     * is a noop.
+     * Uses isLinkDefault to determine if default routes should be set or conversely if
+     * host routes should be set to the dns servers
+     */
+    private void updateRoutes(LinkProperties newLp, LinkProperties curLp, boolean isLinkDefault) {
+        Collection<RouteInfo> routesToAdd = null;
+        CompareResult<InetAddress> dnsDiff = null;
+
+        if (curLp != null) {
+            // check for the delta between the current set and the new
+            CompareResult<RouteInfo> routeDiff = curLp.compareRoutes(newLp);
+            dnsDiff = curLp.compareDnses(newLp);
+
+            for (RouteInfo r : routeDiff.removed) {
+                if (isLinkDefault || ! r.isDefaultRoute()) {
+                    removeRoute(curLp, r);
+                }
             }
-            nt.privateDnsRouteSet(true);
+            routesToAdd = routeDiff.added;
         }
-    }
 
-    private void removePrivateDnsRoutes(NetworkStateTracker nt) {
-        LinkProperties p = nt.getLinkProperties();
-        if (p == null) return;
-        String interfaceName = p.getInterfaceName();
-        boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet();
-        if (interfaceName != null && privateDnsRouteSet) {
-            if (DBG) {
-                log("removePrivateDnsRoutes for " + nt.getNetworkInfo().getTypeName() +
-                        " (" + interfaceName + ")");
+        if (newLp != null) {
+            // if we didn't get a diff from cur -> new, then just use the new
+            if (routesToAdd == null) {
+                routesToAdd = newLp.getRoutes();
             }
 
-            Collection<InetAddress> dnsList = p.getDnses();
-            for (InetAddress dns : dnsList) {
-                if (DBG) log("  removing " + dns);
-                RouteInfo route = RouteInfo.makeHostRoute(dns);
-                try {
-                    mNetd.removeRoute(interfaceName, route);
-                } catch (Exception ex) {
-                    loge("error (" + ex + ") removing dns route " + route);
+            for (RouteInfo r :  routesToAdd) {
+                if (isLinkDefault || ! r.isDefaultRoute()) {
+                    addRoute(newLp, r);
                 }
             }
-            nt.privateDnsRouteSet(false);
         }
-    }
-
-
-    private void addDefaultRoute(NetworkStateTracker nt) {
-        LinkProperties p = nt.getLinkProperties();
-        if (p == null) return;
-        String interfaceName = p.getInterfaceName();
-        if (TextUtils.isEmpty(interfaceName)) return;
 
-        for (RouteInfo route : p.getRoutes()) {
-            //TODO - handle non-default routes
-            if (route.isDefaultRoute()) {
-                if (DBG) log("adding default route " + route);
-                InetAddress gateway = route.getGateway();
-                if (addHostRoute(nt, gateway, 0)) {
-                    try {
-                        mNetd.addRoute(interfaceName, route);
-                    } catch (Exception e) {
-                        loge("error adding default route " + route);
-                        continue;
-                    }
-                    if (DBG) {
-                        NetworkInfo networkInfo = nt.getNetworkInfo();
-                        log("addDefaultRoute for " + networkInfo.getTypeName() +
-                                " (" + interfaceName + "), GatewayAddr=" +
-                                gateway.getHostAddress());
-                    }
-                } else {
-                    loge("error adding host route for default route " + route);
+        if (!isLinkDefault) {
+            // handle DNS routes
+            Collection<InetAddress> dnsToAdd = null;
+            if (dnsDiff != null) {
+                dnsToAdd = dnsDiff.added;
+                for (InetAddress dnsAddress : dnsDiff.removed) {
+                    removeRoute(curLp, RouteInfo.makeHostRoute(dnsAddress));
                 }
             }
-        }
-    }
-
-
-    public void removeDefaultRoute(NetworkStateTracker nt) {
-        LinkProperties p = nt.getLinkProperties();
-        if (p == null) return;
-        String interfaceName = p.getInterfaceName();
-
-        if (interfaceName == null) return;
-
-        for (RouteInfo route : p.getRoutes()) {
-            //TODO - handle non-default routes
-            if (route.isDefaultRoute()) {
-                try {
-                    mNetd.removeRoute(interfaceName, route);
-                } catch (Exception ex) {
-                    loge("error (" + ex + ") removing default route " + route);
-                    continue;
+            if (newLp != null) {
+                if (dnsToAdd == null) {
+                    dnsToAdd = newLp.getDnses();
                 }
-                if (DBG) {
-                    NetworkInfo networkInfo = nt.getNetworkInfo();
-                    log("removeDefaultRoute for " + networkInfo.getTypeName() + " (" +
-                            interfaceName + ")");
+                for(InetAddress dnsAddress : dnsToAdd) {
+                    addRoute(newLp, RouteInfo.makeHostRoute(dnsAddress));
                 }
             }
         }
     }
 
+
    /**
      * Reads the network specific TCP buffer sizes from SystemProperties
      * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
index 1bba8e3..8978f1d 100644 (file)
@@ -26,7 +26,6 @@ import com.android.internal.util.StateMachine;
 import android.app.PendingIntent;
 import android.net.LinkCapabilities;
 import android.net.LinkProperties;
-import android.net.LinkProperties.CompareAddressesResult;
 import android.net.ProxyProperties;
 import android.os.AsyncResult;
 import android.os.Message;
index 9e185e5..a9f2cd1 100644 (file)
@@ -23,7 +23,6 @@ import com.android.internal.util.Protocol;
 import android.app.PendingIntent;
 import android.net.LinkCapabilities;
 import android.net.LinkProperties;
-import android.net.LinkProperties.CompareAddressesResult;
 import android.net.ProxyProperties;
 import android.os.Message;
 
index bf964b7..ccdb0bf 100644 (file)
@@ -27,7 +27,7 @@ import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.ConnectivityManager;
 import android.net.LinkAddress;
-import android.net.LinkProperties.CompareAddressesResult;
+import android.net.LinkProperties.CompareResult;
 import android.net.NetworkUtils;
 import android.net.ProxyProperties;
 import android.net.TrafficStats;
@@ -1152,7 +1152,7 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker {
                                     ! result.oldLp.isIdenticalHttpProxy(result.newLp) ||
                                     ! result.oldLp.isIdenticalAddresses(result.newLp)) {
                                 // If the same address type was removed and added we need to cleanup
-                                CompareAddressesResult car =
+                                CompareResult<LinkAddress> car =
                                     result.oldLp.compareAddresses(result.newLp);
                                 boolean needToClean = false;
                                 for (LinkAddress added : car.added) {