Update route table when a TRP update is received. Not tested.
authorJennifer Richards <jennifer@painless-security.com>
Fri, 1 Jul 2016 15:02:12 +0000 (11:02 -0400)
committerJennifer Richards <jennifer@painless-security.com>
Fri, 1 Jul 2016 15:02:12 +0000 (11:02 -0400)
include/trp_internal.h
include/trp_rtable.h
include/trust_router/trp.h
tr/tr_trp.c
trp/trp_req.c
trp/trp_rtable.c
trp/trp_upd.c
trp/trps.c

index 3f7fa0e..faa8582 100644 (file)
@@ -36,11 +36,13 @@ struct trp_inforec {
 
 struct trp_update {
   TRP_INFOREC *records;
+  TR_NAME *peer; /* who did this update come from? */
 };
 
 struct trp_req {
   TR_NAME *comm;
   TR_NAME *realm;
+  TR_NAME *peer; /* who did this req come from? */
 };
 
 
@@ -89,6 +91,7 @@ struct trps_instance {
   TRP_CONNECTION *conn; /* connections from peers */
   TRPC_INSTANCE *trpc; /* connections to peers */
   TR_MQ *mq;
+  TRP_RTABLE *rtable; /* route table */
 };
 
 
@@ -154,4 +157,5 @@ int trps_auth_cb(gss_name_t clientName, gss_buffer_t displayName, void *data);
 TR_MQ_MSG *trps_mq_pop(TRPS_INSTANCE *trps);
 void trps_mq_append(TRPS_INSTANCE *trps, TR_MQ_MSG *msg);
 void trps_handle_connection(TRPS_INSTANCE *trps, TRP_CONNECTION *conn);
+TRP_RC trps_handle_tr_msg(TRPS_INSTANCE *trps, TR_MSG *tr_msg);
 #endif /* TRP_INTERNAL_H */
index ff94414..ec3d778 100644 (file)
@@ -53,6 +53,7 @@ TR_NAME *trp_rentry_get_next_hop(TRP_RENTRY *entry);
 void trp_rentry_set_selected(TRP_RENTRY *entry, int sel);
 int trp_rentry_get_selected(TRP_RENTRY *entry);
 void trp_rentry_set_expiry(TRP_RENTRY *entry, struct timespec *exp);
+TRP_RENTRY *trp_rtable_get_selected_entry(TRP_RTABLE *rtbl, TR_NAME *apc, TR_NAME *realm);
 struct timespec *trp_rentry_get_expiry(TRP_RENTRY *entry);
 char *trp_rentry_to_str(TALLOC_CTX *mem_ctx, TRP_RENTRY *entry, const char *sep);
 
index 5f54d2f..5cda8ad 100644 (file)
@@ -34,6 +34,8 @@ TR_EXPORT TRP_UPD *trp_upd_new(TALLOC_CTX *mem_ctx);
 void trp_upd_free(TRP_UPD *update);
 TR_EXPORT TRP_INFOREC *trp_upd_get_inforec(TRP_UPD *upd);
 void trp_upd_set_inforec(TRP_UPD *upd, TRP_INFOREC *rec);
+TR_EXPORT TR_NAME *trp_upd_get_peer(TRP_UPD *upd);
+TRP_RC trp_upd_set_peer(TRP_UPD *upd, TR_NAME *peer);
 TR_EXPORT TRP_INFOREC *trp_inforec_new(TALLOC_CTX *mem_ctx, TRP_INFOREC_TYPE type);
 void trp_inforec_free(TRP_INFOREC *rec);
 TR_EXPORT TRP_INFOREC *trp_inforec_get_next(TRP_INFOREC *rec);
@@ -46,6 +48,8 @@ TR_EXPORT TR_NAME *trp_inforec_get_realm(TRP_INFOREC *rec);
 TRP_RC trp_inforec_set_realm(TRP_INFOREC *rec, TR_NAME *realm);
 TR_EXPORT TR_NAME *trp_inforec_get_trust_router(TRP_INFOREC *rec);
 TRP_RC trp_inforec_set_trust_router(TRP_INFOREC *rec, TR_NAME *trust_router);
+TR_EXPORT TR_NAME *trp_inforec_get_next_hop(TRP_INFOREC *rec);
+TRP_RC trp_inforec_set_next_hop(TRP_INFOREC *rec, TR_NAME *next_hop);
 TR_EXPORT unsigned int trp_inforec_get_metric(TRP_INFOREC *rec);
 TRP_RC trp_inforec_set_metric(TRP_INFOREC *rec, unsigned int metric);
 TR_EXPORT unsigned int trp_inforec_get_interval(TRP_INFOREC *rec);
@@ -60,5 +64,7 @@ TR_EXPORT TR_NAME *trp_req_get_comm(TRP_REQ *req);
 void trp_req_set_comm(TRP_REQ *req, TR_NAME *comm);
 TR_EXPORT TR_NAME *trp_req_get_realm(TRP_REQ *req);
 void trp_req_set_realm(TRP_REQ *req, TR_NAME *realm);
+TR_EXPORT TR_NAME *trp_req_get_peer(TRP_REQ *req);
+TRP_RC trp_req_set_peer(TRP_REQ *req, TR_NAME *peer);
 
 #endif /* TRP_H */
index 41c5690..f085f7d 100644 (file)
@@ -171,9 +171,7 @@ static void tr_trps_process_mq(int socket, short event, void *arg)
                                                    TRP_CONNECTION));
     }
     else if (0==strcmp(s, "tr_msg")) {
-      tmp=tr_msg_encode(tr_mq_msg_get_payload(msg));
-      tr_debug("tr_msg: %s", tmp);
-      tr_msg_free_encoded(tmp);
+      trps_handle_tr_msg(trps, msg);
     }
     else
       tr_notice("tr_trps_process_mq: unknown message '%s' received.", tr_mq_msg_get_message(msg));
index d740266..0b809b0 100644 (file)
@@ -10,16 +10,15 @@ static int trp_req_destructor(void *object)
   TRP_REQ *req=talloc_get_type_abort(object, TRP_REQ);
   
   /* clean up TR_NAME data, which are not managed by talloc */
-  if (req->comm != NULL) {
+  if (req->comm != NULL)
     tr_free_name(req->comm);
-    req->comm=NULL;
-    tr_debug("trp_req_destructor: freed community");
-  }
-  if (req->realm != NULL) {
+
+  if (req->realm != NULL)
     tr_free_name(req->realm);
-    req->realm=NULL;
-    tr_debug("trp_req_destructor: freed realm");
-  }
+
+  if (req->peer != NULL)
+    tr_free_name(req->peer);
+
   return 0;
 }
 
@@ -30,6 +29,7 @@ TRP_REQ *trp_req_new(TALLOC_CTX *mem_ctx)
   if (new_req != NULL) {
     new_req->comm=NULL;
     new_req->realm=NULL;
+    new_req->peer=NULL;
   }
 
   talloc_set_destructor((void *)new_req, trp_req_destructor);
@@ -70,3 +70,18 @@ void trp_req_set_realm(TRP_REQ *req, TR_NAME *realm)
   if (req)
     req->realm=realm;
 }
+
+TR_NAME *trp_req_get_peer(TRP_REQ *req)
+{
+  if (req!=NULL)
+    return req->peer;
+  else
+    return NULL;
+}
+
+
+void trp_req_set_peer(TRP_REQ *req, TR_NAME *peer)
+{
+  if (req)
+    req->peer=peer;
+}
index b20e788..97bf923 100644 (file)
@@ -500,13 +500,9 @@ TR_NAME **trp_rtable_get_apc_realm_peers(TRP_RTABLE *rtbl, TR_NAME *apc, TR_NAME
 /* Gets a single entry. Do not free it. */
 TRP_RENTRY *trp_rtable_get_entry(TRP_RTABLE *rtbl, TR_NAME *apc, TR_NAME *realm, TR_NAME *peer)
 {
-  GHashTable *apc_tbl=NULL;
   GHashTable *realm_tbl=NULL;
   
-  apc_tbl=g_hash_table_lookup(rtbl, apc);
-  if (apc_tbl==NULL)
-    return NULL;
-  realm_tbl=g_hash_table_lookup(apc_tbl, realm);
+  realm_tbl=trp_rtable_get_realm_table(rtbl, apc, realm);
   if (realm_tbl==NULL)
     return NULL;
   return g_hash_table_lookup(realm_tbl, peer); /* does not copy or increment ref count */
@@ -531,6 +527,19 @@ static char *timespec_to_str(struct timespec *ts)
   return s;
 }
 
+TRP_RENTRY *trp_rtable_get_selected_entry(TRP_RTABLE *rtbl, TR_NAME *apc, TR_NAME *realm)
+{
+  size_t n_entries=0;
+  TRP_RENTRY *entry=trp_rtable_get_realm_entries(rtbl, apc, realm, &n_entries);
+  if (n_entries==0)
+    return NULL;
+
+  while((entry!=NULL) && (!trp_rentry_get_selected(entry)))
+    entry=trp_rentry_get_next(entry);
+
+  return entry;
+}
+
 /* Pretty print a route table entry to a newly allocated string. If sep is NULL,
  * returns comma+space separated string. */
 char *trp_rentry_to_str(TALLOC_CTX *mem_ctx, TRP_RENTRY *entry, const char *sep)
index d8ba9fa..68ccacf 100644 (file)
@@ -338,12 +338,22 @@ void trp_inforec_free(TRP_INFOREC *rec)
     talloc_free(rec);
 }
 
+static int trp_upd_destructor(void *object)
+{
+  TRP_UPD *upd=talloc_get_type_abort(object, TRP_UPD);
+  if (upd->peer!=NULL)
+    tr_free_name(upd->peer);
+  return 0;
+}
+
 TRP_UPD *trp_upd_new(TALLOC_CTX *mem_ctx)
 {
   TRP_UPD *new_body=talloc(mem_ctx, TRP_UPD);
 
   if (new_body!=NULL) {
     new_body->records=NULL;
+    new_body->peer=NULL;
+    talloc_set_destructor(new_body, trp_upd_destructor);
   }
   return new_body;
 }
@@ -368,6 +378,23 @@ void trp_upd_set_inforec(TRP_UPD *upd, TRP_INFOREC *rec)
     upd->records=rec;
 }
 
+TR_NAME *trp_upd_get_peer(TRP_UPD *upd)
+{
+  return upd->peer;
+}
+
+void trp_upd_set_peer(TRP_UPD *upd, TR_NAME *peer)
+{
+  TRP_INFOREC *rec=NULL;
+  TR_NAME *cpy=NULL;
+
+  upd->peer=peer;
+  for (rec=trp_upd_get_inforec(upd); rec!=NULL; rec=trp_inforec_get_next(rec)) {
+    if (trp_inforec_set_next_hop(rec, cpy=tr_dup_name(peer)) != TRP_SUCCESS)
+      tr_free_name(cpy);
+  }
+}
+
 /* pretty print */
 static void trp_inforec_route_print(TRP_INFOREC_DATA data)
 {
index 7a27840..c96495d 100644 (file)
@@ -6,6 +6,7 @@
 #include <gsscon.h>
 #include <tr_rp.h>
 #include <tr_debug.h>
+#include <trp_rtable.h>
 #include <trp_internal.h>
 
 
@@ -157,11 +158,30 @@ int trps_auth_cb(gss_name_t clientName, gss_buffer_t displayName, void *data)
   return result;
 }
 
+/* get the currently selected route if available */
+TRP_RENTRY *trps_get_route(TRPS_INSTANCE *trps, TR_NAME *comm, TR_NAME *realm)
+{
+  return trp_rtable_get_selected_entry(trps->rtable, comm, realm);
+}
+
+/* mark a route as retracted */
+static TRP_RC trps_retract_route(TRPS_INSTANCE *trps, TRP_RENTRY *entry)
+{
+  trp_rentry_set_metric(entry, TRP_METRIC_INFINITY);
+}
+
+/* is this route retracted? */
+static int trps_route_retracted(TRPS_INSTANCE *trps, TRP_RENTRY *entry)
+{
+  return (trp_rentry_get_metric(entry)==TRP_METRIC_INFINITY);
+}
+
 static TRP_RC trps_read_message(TRPS_INSTANCE *trps, TRP_CONNECTION *conn, TR_MSG **msg)
 {
   int err=0;
   char *buf=NULL;
   size_t buflen = 0;
+  TR_NAME *peer=NULL
 
   tr_debug("trps_read_message: started");
   if (err = gsscon_read_encrypted_token(trp_connection_get_fd(conn),
@@ -182,8 +202,24 @@ static TRP_RC trps_read_message(TRPS_INSTANCE *trps, TRP_CONNECTION *conn, TR_MS
   if (*msg==NULL)
     return TRP_NOPARSE;
 
-  /* fill in the next hop as the peer who just sent this to us */
-  trp_inforec_set_next_hop(*msg, trp_connection_get_peer(conn));
+  peer=trp_connection_get_peer(conn);
+  /* verify we received a message we support, otherwise drop it now */
+  switch (tr_msg_get_msg_type(*msg)) {
+  case TRP_UPDATE:
+    trp_upd_set_peer(tr_msg_get_trp_upd(msg), tr_name_dup(peer));
+    break;
+
+  case TRP_REQUEST:
+    trp_req_set_peer(tr_msg_get_trp_req(msg), tr_name_dup(peer));
+    break;
+
+  default:
+    tr_debug("trps_read_message: received unsupported message from %.*s", peer->len, peer->buf);
+    tr_msg_free_decoded(*msg);
+    *msg=NULL;
+    return TRP_UNSUPPORTED;
+  }
+  
   return TRP_SUCCESS;
 }
 
@@ -262,3 +298,205 @@ void trps_handle_connection(TRPS_INSTANCE *trps, TRP_CONNECTION *conn)
   tr_debug("trps_handle_connection: connection closed.");
   talloc_free(tmp_ctx);
 }
+
+/* ensure that the update could be accepted if feasible */
+static TRP_RC trps_validate_inforec(TRPS_INSTANCE *trps, TRP_INFOREC *rec)
+{
+  switch(trp_inforec_get_type(rec)) {
+  case TRP_INFOREC_TYPE_ROUTE:
+    if ((trp_inforec_get_comm(rec)==NULL)
+       || (trp_inforec_get_realm(rec)==NULL)
+       || (trp_inforec_get_trust_router(rec)==NULL)
+       || (trp_inforec_get_next_hop(rec)==NULL))
+      tr_debug("trps_validate_inforec: missing record info.");
+      return TRP_ERROR;
+
+    /* check for valid metric */
+    if ((trp_inforec_get_metric(rec)==TRP_METRIC_INVALID)
+       || (trp_inforec_get_metric(rec)>TRP_METRIC_INFINITY)) {
+      tr_debug("trps_validate_inforec: invalid metric.");
+      return TRP_ERROR;
+    }
+
+    /* check for valid interval */
+    if (trp_inforec_get_interval(rec)==TRP_INTERVAL_INVALID) {
+      tr_debug("trps_validate_inforec: invalid interval.");
+      return TRP_ERROR;
+    }
+    break;
+
+  default:
+    return TRP_UNSUPPORTED;
+  }
+
+  return TRP_SUCCESS;
+}
+
+/* link cost to a peer */
+static unsigned int trps_cost(TRPS_INSTANCE *trps, TR_NAME *peer)
+{
+  return 1;
+}
+
+static unsigned int trps_advertised_metric(TRPS_INSTANCE *trps, TR_NAME *comm, TR_NAME *realm, TR_NAME *peer)
+{
+  TRP_RENTRY *entry=trp_rtable_get_entry(trps->rtable, comm, realm, peer);
+  if (entry==NULL)
+    return TRP_METRIC_INFINITY;
+  return trp_rentry_get_metric(entry) + trps_cost(trps, peer);
+}
+
+/* returns TRP_SUCCESS if route is feasible, TRP_ERROR otherwise */
+static TRP_RC trps_check_feasibility(TRPS_INSTANCE *trps, TRP_INFOREC *rec)
+{
+  unsigned int rec_metric=trp_inforec_get_metric(rec);
+  unsigned int new_metric=0;
+  unsigned int current_metric=0;
+
+  /* we check these in the validation stage, but just in case... */
+  if ((rec_metric==TRP_METRIC_INVALID) || (rec_metric>TRP_METRIC_INFINITY))
+    return TRP_ERROR;
+
+  /* retractions (aka infinite metrics) are always feasible */
+  if (rec_metric==TRP_METRIC_INFINITY)
+    return TRP_SUCCESS;
+
+  /* updates from our current next hop are always feasible*/
+  if (0==tr_name_cmp(trp_inforec_get_next_hop(rec),
+                     trps_next_hop(trps,
+                                   trp_inforec_get_comm(rec),
+                                   trp_inforec_get_realm(rec)))) {
+    return TRP_SUCCESS;
+  }
+    
+
+  /* compare the existing metric we advertise to what we would advertise
+   * if we accept this update */
+  current_metric=trps_advertised_metric(trps,
+                                        trp_inforec_get_comm(rec),
+                                        trp_inforec_get_realm(rec),
+                                        trp_inforec_get_next_hop(rec));
+  new_metric=rec_metric + trps_cost(trps, trp_inforec_get_next_hop(rec));
+  if (new_metric <= current_metric)
+    return TRP_SUCCESS;
+  else
+    return TRP_ERROR;
+}
+
+/* uses memory pointed to by *ts, also returns that value */
+static void trps_compute_expiry(unsigned int interval, struct timespec *ts)
+{
+  const unsigned int small_factor=3; /* how many intervals we wait before expiring */
+  if (0!=clock_gettime(CLOCK_REALTIME, ts)) {
+    tr_error("trps_compute_expiry: could not read realtime clock.");
+    ts->tv_sec=0;
+    ts->tv_nsec=0;
+    return NULL;
+  }
+  ts->tv_sec += small_factor*interval;
+  return ts;
+}
+
+static TRP_RC trps_accept_update(TRPS_INSTANCE *trps, TRP_INFOREC *rec)
+{
+  TRP_RENTRY *entry=NULL;
+
+  entry=trp_rtable_get_entry(trps->rtable,
+                             trp_inforec_get_comm(rec),
+                             trp_inforec_get_realm(rec),
+                             trp_inforec_get_next_hop(rec));
+  if (entry==NULL) {
+    entry=trp_rentry_new(NULL);
+    if (entry==NULL) {
+      tr_error("trps_accept_update: unable to allocate new entry.");
+      return TRP_NOMEM;
+    }
+
+    trp_rentry_set_apc(entry, tr_dup_name(trp_inforec_get_comm(rec)));
+    trp_rentry_set_realm(entry, tr_dup_name(trp_inforec_get_realm(rec)));
+    trp_rentry_set_peer(entry, tr_dup_name(trp_inforec_get_next_hop(rec)));
+    trp_rentry_set_trust_router(entry, tr_dup_name(trp_inforec_get_trust_router(rec)));
+    trp_rentry_set_next_hop(entry, tr_dup_name(trp_inforec_get_next_hop(rec)));
+    if ((trp_rentry_get_apc(entry)==NULL)
+       ||(trp_rentry_get_realm(entry)==NULL)
+       ||(trp_rentry_get_peer(entry)==NULL)
+       ||(trp_rentry_get_trust_router(entry)==NULL)
+       ||(trp_rentry_next_hop(entry)==NULL)) {
+      /* at least one field could not be allocated */
+      tr_error("trps_accept_update: unable to allocate all fields for entry.");
+      trp_rentry_free(entry);
+      return TRP_NOMEM;
+    }
+    trp_rtable_add(trps->rtable, entry);
+  }
+
+  /* We now have an entry in the table, whether it's new or not. Update metric and expiry, unless
+   * the metric is infinity. An infinite metric can only occur here if we just retracted an existing
+   * route (we never accept retractions as new routes), so there is no risk of leaving the expiry
+   * time unset on a new route entry. */
+  trp_rentry_set_metric(entry, trp_inforec_get_metric(rec));
+  if (!trps_route_retracted(trps, entry))
+    trp_rentry_set_expiry(entry, trps_compute_expiry(trps, trp_inforec_get_interval(rec)));
+  return TRP_SUCCESS;
+}
+
+/* TODO: handle community updates */
+static TRP_RC trps_handle_update(TRPS_INSTANCE *trps, TRP_UPD *upd)
+{
+  unsigned int feas=0;
+  TRP_INFOREC *rec=trp_upd_get_inforec(upd);
+  TRP_RENTRY *route=NULL;
+
+  if (rec==NULL) {
+    tr_notice("trps_handle_update: received TRP update with no info records.");
+    return TRP_ERROR;
+  }
+
+  for (;rec!=NULL; rec=trp_inforec_get_next(rec)) {
+    /* validate/sanity check the update */
+    if (trps_validate_inforec(trps, upd) != TRP_SUCCESS) {
+      tr_notice("trps_handle_update: invalid record in TRP update.");
+      continue;
+    }
+
+    /* determine feasibility */
+    feas=trps_check_feasibility(trps, rec);
+
+    /* do we have an existing route? */
+    route=trps_get_route(trps, trp_inforec_get_comm(rec), trp_inforec_get_realm(rec));
+    if (route!=NULL) {
+      /* there was a route table entry already */
+      if (feas) {
+        /* Update is feasible. Accept it. */
+        trps_accept_update(trps, rec);
+      } else {
+        /* Update is infeasible. Ignore it unless the trust router has changed. */
+        if (0!=tr_name_cmp(trp_rentry_get_trust_router(route),
+                           trp_inforec_get_trust_router(rec))) {
+          /* the trust router associated with the route has changed, treat update as a retraction */
+          trps_retract_route(trps, route);
+        }
+      }
+    } else {
+      /* No existing route table entry. Ignore it unless it is feasible and not a retraction. */
+      if (feas && (trp_inforec_get_metric != TRP_METRIC_INFINITY))
+        trps_accept_update(trps, rec);
+    }
+  }
+  return TRP_SUCCESS;
+}
+
+TRP_RC trps_handle_tr_msg(TRPS_INSTANCE *trps, TR_MSG *tr_msg)
+{
+  switch (tr_msg_get_msg_type(tr_msg)) {
+  case TRP_UPDATE:
+    return trps_handle_update(TRPS_INSTANCE *trps, tr_msg_get_trp_upd(tr_msg));
+
+  case TRP_REQUEST:
+    return TRP_UNSUPPORTED;
+
+  default:
+    /* unknown error or one we don't care about (e.g., TID messages) */
+    return TRP_ERROR;
+  }
+}