Consistently use peer label to ID peers when enforcing split horizon
authorJennifer Richards <jennifer@painless-security.com>
Sun, 29 Apr 2018 17:23:46 +0000 (13:23 -0400)
committerJennifer Richards <jennifer@painless-security.com>
Sun, 29 Apr 2018 17:23:46 +0000 (13:23 -0400)
We were incorrectly comparing the peer label (which is "hostname:port")
with the GSS name of our route's source (i.e., "credential@apc.x") when
checking whether we were about to advertise a route back to the trust
router that announced it to us. That broke split horizon enforcement.

trp/trps.c

index 83461d9..d6c86bc 100644 (file)
@@ -1324,26 +1324,62 @@ cleanup:
 }
 
 /* select the correct route to comm/realm to be announced to peer */
-static TRP_ROUTE *trps_select_realm_update(TRPS_INSTANCE *trps, TR_NAME *comm, TR_NAME *realm, TR_NAME *peer_gssname)
+static TRP_ROUTE *trps_select_realm_update(TRPS_INSTANCE *trps, TR_NAME *comm, TR_NAME *realm, TR_NAME *peer_label)
 {
-  TRP_ROUTE *route;
+  TRP_ROUTE *route = NULL;
+  TRP_PEER *route_peer = NULL;
+  TR_NAME *route_peer_label = NULL;
 
   /* Take the currently selected route unless it is through the peer we're sending the update to.
-   * I.e., enforce the split horizon rule. */
+   * I.e., enforce the split horizon rule. Start by looking up the currently selected route. */
   route=trp_rtable_get_selected_entry(trps->rtable, comm, realm);
   if (route==NULL) {
     /* No selected route, this should only happen if the only route has been retracted,
      * in which case we do not want to advertise it. */
     return NULL;
   }
-  tr_debug("trps_select_realm_update: %s vs %s", peer_gssname->buf,
-           trp_route_get_peer(route)->buf);
-  if (0==tr_name_cmp(peer_gssname, trp_route_get_peer(route))) {
-    tr_debug("trps_select_realm_update: matched, finding alternate route");
-    /* the selected entry goes through the peer we're reporting to, choose an alternate */
-    route=trps_find_best_route(trps, comm, realm, peer_gssname);
-    if ((route==NULL) || (!trp_metric_is_finite(trp_route_get_metric(route))))
-      return NULL; /* don't advertise a nonexistent or retracted route */
+
+  /* Check whether it's local. */
+  if (trp_route_is_local(route)) {
+    /* It is always ok to announce a local route */
+    tr_debug("trps_select_realm_update: selected route for %.*s/%.*s is local",
+             realm->len, realm->buf,
+             comm->len, comm->buf);
+  } else {
+    /* It's not local. Get the route's peer and check whether it's the same place we
+     * got the selected route from. Peer should always correspond to an entry in our
+     * peer table. */
+    tr_debug("trps_select_realm_update: selected route for %.*s/%.*s is not local",
+             realm->len, realm->buf,
+             comm->len, comm->buf);
+    route_peer = trp_ptable_find_gss_name(trps->ptable, trp_route_get_peer(route));
+    if (route_peer == NULL) {
+      tr_err("trps_select_realm_update: unknown peer GSS name (%.*s) for selected route for %.*s/%.*s",
+             trp_route_get_peer(route)->len, trp_route_get_peer(route)->buf,
+             realm->len, realm->buf,
+             comm->len, comm->buf);
+      return NULL;
+    }
+    route_peer_label = trp_peer_get_label(route_peer);
+    if (route_peer_label == NULL) {
+      tr_err("trps_select_realm_update: error retrieving peer label for selected route for %.*s/%.*s",
+             realm->len, realm->buf,
+             comm->len, comm->buf);
+      return NULL;
+    }
+
+    /* see if these match */
+    tr_debug("trps_select_realm_update: %.*s vs %.*s",
+             peer_label->len, peer_label->buf,
+             route_peer_label->len, route_peer_label->buf);
+
+    if (0==tr_name_cmp(peer_label, route_peer_label)) {
+      /* the selected entry goes through the peer we're reporting to, choose an alternate */
+      tr_debug("trps_select_realm_update: matched, finding alternate route");
+      route=trps_find_best_route(trps, comm, realm, peer_label);
+      if ((route==NULL) || (!trp_metric_is_finite(trp_route_get_metric(route))))
+        return NULL; /* don't advertise a nonexistent or retracted route */
+    }
   }
   return route;
 }
@@ -1352,7 +1388,7 @@ static TRP_ROUTE *trps_select_realm_update(TRPS_INSTANCE *trps, TR_NAME *comm, T
 static TRP_RC trps_select_route_updates_for_peer(TALLOC_CTX *mem_ctx,
                                                  GPtrArray *updates,
                                                  TRPS_INSTANCE *trps,
-                                                 TR_NAME *peer_gssname,
+                                                 TR_NAME *peer_label,
                                                  int triggered)
 {
   size_t n_comm=0;
@@ -1369,7 +1405,7 @@ static TRP_RC trps_select_route_updates_for_peer(TALLOC_CTX *mem_ctx,
   for (ii=0; ii<n_comm; ii++) {
     realm=trp_rtable_get_comm_realms(trps->rtable, comm[ii], &n_realm);
     for (jj=0; jj<n_realm; jj++) {
-      best=trps_select_realm_update(trps, comm[ii], realm[jj], peer_gssname);
+      best=trps_select_realm_update(trps, comm[ii], realm[jj], peer_label);
       /* If we found a route, add it to the list. If triggered!=0, then only
        * add triggered routes. */
       if ((best!=NULL) && ((!triggered) || trp_route_is_triggered(best))) {