+ if (trps_name_in_provenance(our_peer_label, trp_inforec_get_provenance(rec)))
+ tr_debug("trps_handle_inforec_comm: rejecting community inforec to avoid provenance loop.");
+ else {
+ /* no loop occurring, accept the update */
+ comm=tr_comm_table_find_comm(trps->ctable, comm_id);
+ if (comm==NULL) {
+ tr_debug("trps_handle_inforec_comm: unknown community %.*s in inforec, creating it.",
+ comm_id->len, comm_id->buf);
+ comm=trps_create_new_comm(tmp_ctx, comm_id, rec);
+ if (comm==NULL) {
+ tr_debug("trps_handle_inforec_comm: unable to create new community.");
+ goto cleanup;
+ }
+ if (tr_comm_table_add_comm(trps->ctable, comm) != 0)
+ {
+ tr_debug("trps_handle_inforec_comm: unable to add community to community table.");
+ goto cleanup;
+ }
+ }
+ /* TODO: see if other comm data match the new inforec and update or complain */
+
+ trps_compute_expiry(trps, trp_inforec_get_interval(rec), &expiry);
+ if ((expiry.tv_sec==0)&&(expiry.tv_nsec==0))
+ goto cleanup;
+
+ switch (trp_inforec_get_role(rec)) {
+ case TR_ROLE_RP:
+ rp_realm=tr_rp_realm_lookup(trps->ctable->rp_realms, realm_id);
+ if (rp_realm==NULL) {
+ tr_debug("trps_handle_inforec_comm: unknown RP realm %.*s in inforec, creating it.",
+ realm_id->len, realm_id->buf);
+ rp_realm= trps_create_new_rp_realm(tmp_ctx, tr_comm_get_id(comm), realm_id, rec);
+ if (rp_realm==NULL) {
+ tr_debug("trps_handle_inforec_comm: unable to create new RP realm.");
+ /* we may leave an unused community in the table, but it will only last until
+ * the next table sweep if it does not get any realms before that happens */
+ goto cleanup;
+ }
+ tr_comm_table_add_rp_realm(trps->ctable, rp_realm);
+ }
+ /* TODO: if realm existed, see if data match the new inforec and update or complain */
+ tr_comm_add_rp_realm(trps->ctable, comm, rp_realm, trp_inforec_get_interval(rec), trp_inforec_get_provenance(rec), &expiry);
+ tr_debug("trps_handle_inforec_comm: added RP realm %.*s to comm %.*s (origin %.*s).",
+ realm_id->len, realm_id->buf,
+ comm_id->len, comm_id->buf,
+ origin_id->len, origin_id->buf);
+ break;
+ case TR_ROLE_IDP:
+ idp_realm=tr_idp_realm_lookup(trps->ctable->idp_realms, realm_id);
+ if (idp_realm==NULL) {
+ tr_debug("trps_handle_inforec_comm: unknown IDP realm %.*s in inforec, creating it.",
+ realm_id->len, realm_id->buf);
+ idp_realm= trps_create_new_idp_realm(tmp_ctx, tr_comm_get_id(comm), realm_id, rec);
+ if (idp_realm==NULL) {
+ tr_debug("trps_handle_inforec_comm: unable to create new IDP realm.");
+ /* we may leave an unused community in the table, but it will only last until
+ * the next table sweep if it does not get any realms before that happens */
+ goto cleanup;
+ }
+ tr_comm_table_add_idp_realm(trps->ctable, idp_realm);
+ }
+ /* TODO: if realm existed, see if data match the new inforec and update or complain */
+ tr_comm_add_idp_realm(trps->ctable, comm, idp_realm, trp_inforec_get_interval(rec), trp_inforec_get_provenance(rec), &expiry);
+ tr_debug("trps_handle_inforec_comm: added IDP realm %.*s to comm %.*s (origin %.*s).",
+ realm_id->len, realm_id->buf,
+ comm_id->len, comm_id->buf,
+ origin_id->len, origin_id->buf);
+ break;
+ default:
+ tr_debug("trps_handle_inforec_comm: unable to add realm.");
+ goto cleanup;
+ }
+ }
+
+ rc=TRP_SUCCESS;
+
+cleanup:
+ if (our_peer_label!=NULL)
+ tr_free_name(our_peer_label);
+ if (origin_id!=NULL)
+ tr_free_name(origin_id);
+ talloc_free(tmp_ctx);
+ return rc;
+}
+
+/**
+ * Apply applicable TRP_INBOUND filters to an inforec. Rejects everything if peer has no filters.
+ *
+ * @param trps Active TRPS instance
+ * @param upd TRP_UPD that contains the inforec to filter
+ * @param rec Inforec to filter
+ * @return 1 if accepted by the filter, 0 otherwise
+ */
+static int trps_filter_inbound_inforec(TRPS_INSTANCE *trps, TRP_UPD *upd, TRP_INFOREC *rec)
+{
+ TRP_PEER *peer=NULL;
+ TR_NAME *peer_name=NULL;
+ TR_FILTER_ACTION action=TR_FILTER_ACTION_REJECT;
+ TR_FILTER_TARGET *target=NULL;
+ int retval=0;
+
+ /* Look up the peer. For inbound messages, the peer is identified by its GSS name */
+ peer_name=trp_upd_get_peer(upd);
+ peer=trps_get_peer_by_gssname(trps, peer_name);
+ if (peer==NULL) {
+ tr_err("trps_filter_inbound_inforec: received inforec from unknown peer (%.*s), rejecting.",
+ peer_name->len,
+ peer_name->buf);
+ return 0;
+ }
+
+ /* tr_filter_apply() and tr_filter_set_get() handle null filter sets/filters by rejecting */
+ target= tr_filter_target_trp_inforec(NULL, upd, rec);
+ if (target==NULL) {
+ /* TODO: signal that filtering failed. Until then, just filter everything and give an error message. */
+ tr_crit("trps_filter_inbound_inforec: Unable to allocate filter target, cannot apply filter!");
+ }
+ if ((target==NULL)
+ || (TR_FILTER_NO_MATCH==tr_filter_apply(target,
+ tr_filter_set_get(peer->filters, TR_FILTER_TYPE_TRP_INBOUND),
+ NULL,
+ &action))
+ || (action!=TR_FILTER_ACTION_ACCEPT)) {
+ /* either the filter did not match or it matched a reject rule or allocating the target failed */
+ retval=0;
+ } else
+ retval=1;
+ if (target!=NULL)
+ tr_filter_target_free(target);
+
+ /* filter matched an accept rule */
+ return retval;
+}
+
+
+static TRP_RC trps_handle_update(TRPS_INSTANCE *trps, TRP_UPD *upd)
+{
+ TRP_INFOREC *rec=NULL;
+
+ if (trps_validate_update(trps, upd) != TRP_SUCCESS) {
+ tr_notice("trps_handle_update: received invalid TRP update.");
+ return TRP_ERROR;
+ }
+
+ for (rec=trp_upd_get_inforec(upd); rec!=NULL; rec=trp_inforec_get_next(rec)) {
+ /* validate/sanity check the record update */
+ if (trps_validate_inforec(trps, rec) != TRP_SUCCESS) {
+ tr_notice("trps_handle_update: invalid inforec in TRP update, discarding entire update.");
+ return TRP_ERROR;
+ }
+ }
+
+ for (rec=trp_upd_get_inforec(upd); rec!=NULL; rec=trp_inforec_get_next(rec)) {
+ if (!trps_filter_inbound_inforec(trps, upd, rec)) {
+ tr_debug("trps_handle_update: inforec rejected by filter.");
+ continue; /* just go on to the next record */
+ }
+
+ switch (trp_inforec_get_type(rec)) {
+ case TRP_INFOREC_TYPE_ROUTE:
+ tr_debug("trps_handle_update: handling route inforec.");
+ if (TRP_SUCCESS!=trps_handle_inforec_route(trps, upd, rec))
+ tr_notice("trps_handle_update: error handling route inforec.");
+ break;
+ case TRP_INFOREC_TYPE_COMMUNITY:
+ tr_debug("trps_handle_update: handling community inforec.");
+ if (TRP_SUCCESS!=trps_handle_inforec_comm(trps, upd, rec))
+ tr_notice("trps_handle_update: error handling community inforec.");
+
+ break;
+ default:
+ tr_notice("trps_handle_update: unsupported inforec in TRP update.");
+ break;
+ }
+ }
+ return TRP_SUCCESS;
+}
+
+static TRP_RC trps_validate_request(TRPS_INSTANCE *trps, TRP_REQ *req)
+{
+ if (req==NULL) {
+ tr_notice("trps_validate_request: null TRP request.");
+ return TRP_BADARG;
+ }
+
+ if (trp_req_get_comm(req)==NULL) {
+ tr_notice("trps_validate_request: received TRP request with null community.");
+ return TRP_ERROR;
+ }
+
+ if (trp_req_get_realm(req)==NULL) {
+ tr_notice("trps_validate_request: received TRP request with null realm.");
+ return TRP_ERROR;
+ }
+
+ if (trp_req_get_peer(req)==NULL) {
+ tr_notice("trps_validate_request: received TRP request without origin peer information.");
+ return TRP_ERROR;
+ }
+
+ return TRP_SUCCESS;
+}
+
+/* choose the best route to comm/realm, optionally excluding routes to a particular peer */
+static TRP_ROUTE *trps_find_best_route(TRPS_INSTANCE *trps,
+ TR_NAME *comm,
+ TR_NAME *realm,
+ TR_NAME *exclude_peer_label)
+{
+ TRP_ROUTE **entry=NULL;
+ TRP_ROUTE *best=NULL;
+ TRP_PEER *route_peer = NULL;
+ size_t n_entry=0;
+ unsigned int kk=0;
+ unsigned int kk_min=0;
+ unsigned int min_metric=TRP_METRIC_INFINITY;
+
+ entry=trp_rtable_get_realm_entries(trps->rtable, comm, realm, &n_entry);
+ for (kk=0; kk<n_entry; kk++) {
+ if (trp_route_get_metric(entry[kk]) < min_metric) {
+ if (exclude_peer_label != NULL) {
+ if (!trp_route_is_local(entry[kk])) {
+ /* route is not local, check the peer label */
+ route_peer = trp_ptable_find_gss_name(trps->ptable,
+ trp_route_get_peer(entry[kk]));
+ if (route_peer == NULL) {
+ tr_err("trps_find_best_route: unknown peer GSS name (%.*s) for route %d to %.*s/%.*s",
+ trp_route_get_peer(entry[kk])->len, trp_route_get_peer(entry[kk])->buf,
+ kk,
+ realm->len, realm->buf,
+ comm->len, comm->buf);
+ continue; /* unknown peer, skip the route */
+ }
+ if (0 == tr_name_cmp(exclude_peer_label, trp_peer_get_label(route_peer))) {
+ /* we're excluding this peer - skip the route */
+ continue;
+ }
+ }
+ }
+ /* if we get here, we're not excluding the route */
+ kk_min = kk;
+ min_metric = trp_route_get_metric(entry[kk]);
+ }
+ }
+
+ if (trp_metric_is_finite(min_metric))
+ best=entry[kk_min];
+
+ talloc_free(entry);
+ return best;
+}
+
+/* TODO: think this through more carefully. At least ought to add hysteresis
+ * to avoid flapping between routers or routes. */
+TRP_RC trps_update_active_routes(TRPS_INSTANCE *trps)
+{
+ size_t n_comm=0, ii=0;
+ TR_NAME **comm=trp_rtable_get_comms(trps->rtable, &n_comm);
+ size_t n_realm=0, jj=0;
+ TR_NAME **realm=NULL;
+ TRP_ROUTE *best_route=NULL, *cur_route=NULL;
+ unsigned int best_metric=0, cur_metric=0;
+
+ 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_route=trps_find_best_route(trps, comm[ii], realm[jj], NULL);
+ if (best_route==NULL)
+ best_metric=TRP_METRIC_INFINITY;
+ else
+ best_metric=trp_route_get_metric(best_route);
+
+ cur_route=trps_get_selected_route(trps, comm[ii], realm[jj]);
+ if (cur_route!=NULL) {
+ cur_metric=trp_route_get_metric(cur_route);
+ if ((best_metric < cur_metric) && (trp_metric_is_finite(best_metric))) {
+ /* The new route has a lower metric than the previous, and is finite. Accept. */
+ trp_route_set_selected(cur_route, 0);
+ trp_route_set_selected(best_route, 1);
+ } else if (!trp_metric_is_finite(cur_metric)) /* rejects infinite or invalid metrics */
+ trp_route_set_selected(cur_route, 0);
+ } else if (trp_metric_is_finite(best_metric)) {
+ trp_route_set_selected(best_route, 1);
+ }
+ }
+ if (realm!=NULL)
+ talloc_free(realm);
+ realm=NULL; n_realm=0;
+ }
+ if (comm!=NULL)
+ talloc_free(comm);
+ comm=NULL; n_comm=0;
+
+ return TRP_SUCCESS;
+}
+
+/* true if curtime >= expiry */
+static int trps_expired(struct timespec *expiry, struct timespec *curtime)
+{
+ return (tr_cmp_timespec(curtime, expiry) >= 0);
+}
+
+/* Sweep for expired routes. For each expired route, if its metric is infinite, the route is flushed.
+ * If its metric is finite, the metric is set to infinite and the route's expiration time is updated. */
+TRP_RC trps_sweep_routes(TRPS_INSTANCE *trps)
+{
+ struct timespec sweep_time={0,0};
+ TRP_ROUTE **entry=NULL;
+ size_t n_entry=0;
+ size_t ii=0;
+
+ /* use a single time for the entire sweep */
+ if (0!=clock_gettime(TRP_CLOCK, &sweep_time)) {
+ tr_err("trps_sweep_routes: could not read realtime clock.");
+ sweep_time.tv_sec=0;
+ sweep_time.tv_nsec=0;
+ return TRP_ERROR;
+ }
+
+ entry= trp_rtable_get_entries(NULL, trps->rtable, &n_entry); /* must talloc_free *entry */
+
+ /* loop over the entries */
+ for (ii=0; ii<n_entry; ii++) {
+ if (!trp_route_is_local(entry[ii]) && trps_expired(trp_route_get_expiry(entry[ii]), &sweep_time)) {
+ tr_debug("trps_sweep_routes: route expired.");
+ if (!trp_metric_is_finite(trp_route_get_metric(entry[ii]))) {
+ /* flush route */
+ tr_debug("trps_sweep_routes: metric was infinity, flushing route.");
+ trp_rtable_remove(trps->rtable, entry[ii]); /* entry[ii] is no longer valid */
+ entry[ii]=NULL;
+ } else {
+ /* set metric to infinity and reset timer */
+ tr_debug("trps_sweep_routes: setting metric to infinity and resetting expiry.");
+ trp_route_set_metric(entry[ii], TRP_METRIC_INFINITY);
+ trp_route_set_expiry(entry[ii], trps_compute_expiry(trps,
+ trp_route_get_interval(entry[ii]),
+ trp_route_get_expiry(entry[ii])));
+ }
+ }
+ }
+
+ talloc_free(entry);
+ return TRP_SUCCESS;
+}
+
+
+/* Sweep for expired communities/realms/memberships. */
+TRP_RC trps_sweep_ctable(TRPS_INSTANCE *trps)
+{
+ TALLOC_CTX *tmp_ctx=talloc_new(NULL);
+ struct timespec sweep_time={0,0};
+ struct timespec tmp = {0};
+ TR_COMM_MEMB *memb=NULL;
+ TR_COMM_ITER *iter=NULL;
+ TRP_RC rc=TRP_ERROR;
+
+ /* use a single time for the entire sweep */
+ if (0!=clock_gettime(TRP_CLOCK, &sweep_time)) {
+ tr_err("trps_sweep_ctable: could not read realtime clock.");
+ sweep_time.tv_sec=0;
+ sweep_time.tv_nsec=0;
+ goto cleanup;
+ }
+
+ /* iterate all memberships */
+ iter=tr_comm_iter_new(tmp_ctx);
+ if (iter==NULL) {
+ tr_err("trps_sweep_ctable: unable to allocate iterator.");
+ rc=TRP_NOMEM;
+ goto cleanup;
+ }
+ for (memb=tr_comm_memb_iter_all_first(iter, trps->ctable);
+ memb!=NULL;
+ memb=tr_comm_memb_iter_all_next(iter)) {
+ if (tr_comm_memb_get_origin(memb)==NULL)
+ continue; /* do not expire local entries */
+
+ if (tr_comm_memb_is_expired(memb, &sweep_time)) {
+ if (tr_comm_memb_get_times_expired(memb)>0) {
+ /* Already expired once; flush. */
+ tr_debug("trps_sweep_ctable: flushing expired community membership (%.*s in %.*s, origin %.*s, expired %s).",
+ tr_comm_memb_get_realm_id(memb)->len, tr_comm_memb_get_realm_id(memb)->buf,
+ tr_comm_get_id(tr_comm_memb_get_comm(memb))->len, tr_comm_get_id(tr_comm_memb_get_comm(memb))->buf,
+ tr_comm_memb_get_origin(memb)->len, tr_comm_memb_get_origin(memb)->buf,
+ timespec_to_str(tr_comm_memb_get_expiry_realtime(memb, &tmp)));
+ tr_comm_table_remove_memb(trps->ctable, memb);
+ tr_comm_memb_free(memb);
+ } else {
+ /* This is the first expiration. Note this and reset the expiry time. */
+ tr_comm_memb_expire(memb);
+ trps_compute_expiry(trps, tr_comm_memb_get_interval(memb), tr_comm_memb_get_expiry(memb));
+ tr_debug("trps_sweep_ctable: community membership expired at %s, resetting expiry to %s (%.*s in %.*s, origin %.*s).",
+ timespec_to_str(tr_clock_convert(TRP_CLOCK, &sweep_time, CLOCK_REALTIME, &tmp)),
+ timespec_to_str(tr_comm_memb_get_expiry_realtime(memb, &tmp)),
+ tr_comm_memb_get_realm_id(memb)->len, tr_comm_memb_get_realm_id(memb)->buf,
+ tr_comm_get_id(tr_comm_memb_get_comm(memb))->len, tr_comm_get_id(tr_comm_memb_get_comm(memb))->buf,
+ tr_comm_memb_get_origin(memb)->len, tr_comm_memb_get_origin(memb)->buf);
+ }
+ }
+ }
+
+ /* get rid of any unreferenced realms, etc */
+ tr_comm_table_sweep(trps->ctable);
+
+cleanup:
+ talloc_free(tmp_ctx);
+ return rc;
+}
+
+/* add metrics */
+static unsigned int trps_metric_add(unsigned int m1, unsigned int m2)
+{
+ if (trp_metric_is_invalid(m1) || trp_metric_is_invalid(m2))
+ return TRP_METRIC_INVALID;
+
+ if (trp_metric_is_infinite(m1) || trp_metric_is_infinite(m2))
+ return TRP_METRIC_INFINITY;
+
+ if (trp_metric_is_finite(m1+m2))
+ return m1+m2;
+ else
+ return TRP_METRIC_INFINITY;
+}