Update route table when a TRP update is received. Not tested.
[trust_router.git] / trp / trps.c
1 #include <fcntl.h>
2 #include <talloc.h>
3 #include <errno.h>
4 #include <unistd.h>
5
6 #include <gsscon.h>
7 #include <tr_rp.h>
8 #include <tr_debug.h>
9 #include <trp_rtable.h>
10 #include <trp_internal.h>
11
12
13 TRPS_INSTANCE *trps_new (TALLOC_CTX *mem_ctx)
14 {
15   TRPS_INSTANCE *trps=talloc(mem_ctx, TRPS_INSTANCE);
16   if (trps!=NULL)  {
17     trps->hostname=NULL;
18     trps->port=0;
19     trps->cookie=NULL;
20     trps->conn=NULL;
21     trps->trpc=NULL;
22     trps->mq=tr_mq_new(trps);
23     if (trps->mq==NULL) {
24       /* failed to allocate mq */
25       talloc_free(trps);
26       trps=NULL;
27     }
28   }
29   return trps;
30 }
31
32 void trps_free (TRPS_INSTANCE *trps)
33 {
34   if (trps!=NULL)
35     talloc_free(trps);
36 }
37
38 TR_MQ_MSG *trps_mq_pop(TRPS_INSTANCE *trps)
39 {
40   return tr_mq_pop(trps->mq);
41 }
42
43 void trps_mq_append(TRPS_INSTANCE *trps, TR_MQ_MSG *msg)
44 {
45   tr_mq_append(trps->mq, msg);
46 }
47
48 /* stand-in for a function that finds the connection for a particular peer */
49 #if 0
50 static TRP_CONNECTION *trps_find_connection(TRPS_INSTANCE *trps)
51 {
52   return trps->conn;
53 }
54 #endif
55
56 void trps_add_connection(TRPS_INSTANCE *trps, TRP_CONNECTION *new)
57 {
58   if (trps->conn==NULL)
59     trps->conn=new;
60   else
61     trp_connection_append(trps->conn, new);
62
63   talloc_steal(trps, new);
64 }
65
66 /* ok to call more than once; guarantees connection no longer in the list.
67  * Caller is responsible for freeing the removed element afterwards.  */
68 void trps_remove_connection(TRPS_INSTANCE *trps, TRP_CONNECTION *remove)
69 {
70   trps->conn=trp_connection_remove(trps->conn, remove);
71 }
72
73 void trps_add_trpc(TRPS_INSTANCE *trps, TRPC_INSTANCE *trpc)
74 {
75   if (trps->trpc==NULL)
76     trps->trpc=trpc;
77   else
78     trpc_append(trps->trpc, trpc);
79
80   talloc_steal(trps, trpc);
81 }
82
83 /* ok to call more than once; guarantees trpc no longer in the list.
84  * Caller is responsible for freeing the removed element afterwards.  */
85 void trps_remove_trpc(TRPS_INSTANCE *trps, TRPC_INSTANCE *remove)
86 {
87   trps->trpc=trpc_remove(trps->trpc, remove);
88 }
89
90 TRP_RC trps_send_msg (TRPS_INSTANCE *trps, void *peer, const char *msg)
91 {
92   TALLOC_CTX *tmp_ctx=talloc_new(NULL);
93   TR_MQ_MSG *mq_msg=NULL;
94   char *msg_dup=NULL;
95   TRP_RC rc=TRP_ERROR;
96
97   /* Currently ignore peer and just send to an open connection.
98    * In reality, need to identify the correct peer and send via that
99    * one.  */
100   if (trps->trpc != NULL) {
101     if (trpc_get_status(trps->trpc)!=TRP_CONNECTION_UP)
102       tr_debug("trps_send_msg: skipping message sent while TRPC connection not up.");
103     else {
104       mq_msg=tr_mq_msg_new(tmp_ctx, "trpc_send");
105       msg_dup=talloc_strdup(mq_msg, msg); /* get local copy in mq_msg context */
106       tr_mq_msg_set_payload(mq_msg, msg_dup, NULL); /* no need for a free() func */
107       trpc_mq_append(trps->trpc, mq_msg);
108       rc=TRP_SUCCESS;
109     }
110   }
111   talloc_free(tmp_ctx);
112   return rc;
113 }
114
115 static int trps_listen (TRPS_INSTANCE *trps, int port) 
116 {
117     int rc = 0;
118     int conn = -1;
119     int optval = 1;
120
121     union {
122       struct sockaddr_storage storage;
123       struct sockaddr_in in4;
124     } addr;
125
126     struct sockaddr_in *saddr = (struct sockaddr_in *) &addr.in4;
127
128     saddr->sin_port = htons (port);
129     saddr->sin_family = AF_INET;
130     saddr->sin_addr.s_addr = INADDR_ANY;
131
132     if (0 > (conn = socket (AF_INET, SOCK_STREAM, 0)))
133       return conn;
134
135     setsockopt(conn, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
136
137     if (0 > (rc = bind (conn, (struct sockaddr *) saddr, sizeof(struct sockaddr_in))))
138       return rc;
139
140     if (0 > (rc = listen(conn, 512)))
141       return rc;
142
143     tr_debug("trps_listen: TRP Server listening on port %d", port);
144     return conn;
145 }
146
147 /* returns EACCES if authorization is denied */
148 int trps_auth_cb(gss_name_t clientName, gss_buffer_t displayName, void *data)
149 {
150   TRPS_INSTANCE *inst = (TRPS_INSTANCE *)data;
151   int result=0;
152
153   if (0!=inst->auth_handler(clientName, displayName, inst->cookie)) {
154     tr_debug("trps_auth_cb: client '%.*s' denied authorization.", displayName->length, displayName->value);
155     result=EACCES; /* denied */
156   }
157
158   return result;
159 }
160
161 /* get the currently selected route if available */
162 TRP_RENTRY *trps_get_route(TRPS_INSTANCE *trps, TR_NAME *comm, TR_NAME *realm)
163 {
164   return trp_rtable_get_selected_entry(trps->rtable, comm, realm);
165 }
166
167 /* mark a route as retracted */
168 static TRP_RC trps_retract_route(TRPS_INSTANCE *trps, TRP_RENTRY *entry)
169 {
170   trp_rentry_set_metric(entry, TRP_METRIC_INFINITY);
171 }
172
173 /* is this route retracted? */
174 static int trps_route_retracted(TRPS_INSTANCE *trps, TRP_RENTRY *entry)
175 {
176   return (trp_rentry_get_metric(entry)==TRP_METRIC_INFINITY);
177 }
178
179 static TRP_RC trps_read_message(TRPS_INSTANCE *trps, TRP_CONNECTION *conn, TR_MSG **msg)
180 {
181   int err=0;
182   char *buf=NULL;
183   size_t buflen = 0;
184   TR_NAME *peer=NULL
185
186   tr_debug("trps_read_message: started");
187   if (err = gsscon_read_encrypted_token(trp_connection_get_fd(conn),
188                                        *(trp_connection_get_gssctx(conn)), 
189                                         &buf,
190                                         &buflen)) {
191     tr_debug("trps_read_message: error");
192     if (buf)
193       free(buf);
194     return TRP_ERROR;
195   }
196
197   tr_debug("trps_read_message(): Request Received, %u bytes.", (unsigned) buflen);
198   tr_debug("trps_read_message(): %.*s", buflen, buf);
199
200   *msg=tr_msg_decode(buf, buflen);
201   free(buf);
202   if (*msg==NULL)
203     return TRP_NOPARSE;
204
205   peer=trp_connection_get_peer(conn);
206   /* verify we received a message we support, otherwise drop it now */
207   switch (tr_msg_get_msg_type(*msg)) {
208   case TRP_UPDATE:
209     trp_upd_set_peer(tr_msg_get_trp_upd(msg), tr_name_dup(peer));
210     break;
211
212   case TRP_REQUEST:
213     trp_req_set_peer(tr_msg_get_trp_req(msg), tr_name_dup(peer));
214     break;
215
216   default:
217     tr_debug("trps_read_message: received unsupported message from %.*s", peer->len, peer->buf);
218     tr_msg_free_decoded(*msg);
219     *msg=NULL;
220     return TRP_UNSUPPORTED;
221   }
222   
223   return TRP_SUCCESS;
224 }
225
226 int trps_get_listener(TRPS_INSTANCE *trps,
227                       TRPS_MSG_FUNC msg_handler,
228                       TRP_AUTH_FUNC auth_handler,
229                       const char *hostname,
230                       unsigned int port,
231                       void *cookie)
232 {
233   int listen = -1;
234
235   if (0 > (listen = trps_listen(trps, port))) {
236     char errbuf[256];
237     if (0 == strerror_r(errno, errbuf, 256)) {
238       tr_debug("trps_get_listener: Error opening port %d: %s.", port, errbuf);
239     } else {
240       tr_debug("trps_get_listener: Unknown error openining port %d.", port);
241     }
242   } 
243
244   if (listen > 0) {
245     /* opening port succeeded */
246     tr_debug("trps_get_listener: Opened port %d.", port);
247     
248     /* make this socket non-blocking */
249     if (0 != fcntl(listen, F_SETFL, O_NONBLOCK)) {
250       tr_debug("trps_get_listener: Error setting O_NONBLOCK.");
251       close(listen);
252       listen=-1;
253     }
254   }
255
256   if (listen > 0) {
257     /* store the caller's request handler & cookie */
258     trps->msg_handler = msg_handler;
259     trps->auth_handler = auth_handler;
260     trps->hostname = talloc_strdup(trps, hostname);
261     trps->port = port;
262     trps->cookie = cookie;
263   }
264
265   return listen;
266 }
267
268 void trps_handle_connection(TRPS_INSTANCE *trps, TRP_CONNECTION *conn)
269 {
270   TALLOC_CTX *tmp_ctx=talloc_new(NULL);
271   TR_MSG *msg=NULL;
272   TRP_RC rc=TRP_ERROR;
273
274   /* try to establish a GSS context */
275   if (0!=trp_connection_auth(conn, trps->auth_handler, trps->cookie)) {
276     tr_notice("tr_trps_conn_thread: failed to authorize connection");
277     pthread_exit(NULL);
278   }
279   tr_notice("trps_handle_connection: authorized connection");
280   
281   /* loop as long as the connection exists */
282   while (trp_connection_get_status(conn)==TRP_CONNECTION_UP) {
283     rc=trps_read_message(trps, conn, &msg);
284     switch(rc) {
285     case TRP_SUCCESS:
286       trps->msg_handler(trps, conn, msg); /* send the TR_MSG off to the callback */
287       break;
288
289     case TRP_ERROR:
290       trp_connection_close(conn);
291       break;
292
293     default:
294       tr_debug("trps_handle_connection: trps_read_message failed (%d)", rc);
295     }
296   }
297
298   tr_debug("trps_handle_connection: connection closed.");
299   talloc_free(tmp_ctx);
300 }
301
302 /* ensure that the update could be accepted if feasible */
303 static TRP_RC trps_validate_inforec(TRPS_INSTANCE *trps, TRP_INFOREC *rec)
304 {
305   switch(trp_inforec_get_type(rec)) {
306   case TRP_INFOREC_TYPE_ROUTE:
307     if ((trp_inforec_get_comm(rec)==NULL)
308        || (trp_inforec_get_realm(rec)==NULL)
309        || (trp_inforec_get_trust_router(rec)==NULL)
310        || (trp_inforec_get_next_hop(rec)==NULL))
311       tr_debug("trps_validate_inforec: missing record info.");
312       return TRP_ERROR;
313
314     /* check for valid metric */
315     if ((trp_inforec_get_metric(rec)==TRP_METRIC_INVALID)
316        || (trp_inforec_get_metric(rec)>TRP_METRIC_INFINITY)) {
317       tr_debug("trps_validate_inforec: invalid metric.");
318       return TRP_ERROR;
319     }
320
321     /* check for valid interval */
322     if (trp_inforec_get_interval(rec)==TRP_INTERVAL_INVALID) {
323       tr_debug("trps_validate_inforec: invalid interval.");
324       return TRP_ERROR;
325     }
326     break;
327
328   default:
329     return TRP_UNSUPPORTED;
330   }
331
332   return TRP_SUCCESS;
333 }
334
335 /* link cost to a peer */
336 static unsigned int trps_cost(TRPS_INSTANCE *trps, TR_NAME *peer)
337 {
338   return 1;
339 }
340
341 static unsigned int trps_advertised_metric(TRPS_INSTANCE *trps, TR_NAME *comm, TR_NAME *realm, TR_NAME *peer)
342 {
343   TRP_RENTRY *entry=trp_rtable_get_entry(trps->rtable, comm, realm, peer);
344   if (entry==NULL)
345     return TRP_METRIC_INFINITY;
346   return trp_rentry_get_metric(entry) + trps_cost(trps, peer);
347 }
348
349 /* returns TRP_SUCCESS if route is feasible, TRP_ERROR otherwise */
350 static TRP_RC trps_check_feasibility(TRPS_INSTANCE *trps, TRP_INFOREC *rec)
351 {
352   unsigned int rec_metric=trp_inforec_get_metric(rec);
353   unsigned int new_metric=0;
354   unsigned int current_metric=0;
355
356   /* we check these in the validation stage, but just in case... */
357   if ((rec_metric==TRP_METRIC_INVALID) || (rec_metric>TRP_METRIC_INFINITY))
358     return TRP_ERROR;
359
360   /* retractions (aka infinite metrics) are always feasible */
361   if (rec_metric==TRP_METRIC_INFINITY)
362     return TRP_SUCCESS;
363
364   /* updates from our current next hop are always feasible*/
365   if (0==tr_name_cmp(trp_inforec_get_next_hop(rec),
366                      trps_next_hop(trps,
367                                    trp_inforec_get_comm(rec),
368                                    trp_inforec_get_realm(rec)))) {
369     return TRP_SUCCESS;
370   }
371     
372
373   /* compare the existing metric we advertise to what we would advertise
374    * if we accept this update */
375   current_metric=trps_advertised_metric(trps,
376                                         trp_inforec_get_comm(rec),
377                                         trp_inforec_get_realm(rec),
378                                         trp_inforec_get_next_hop(rec));
379   new_metric=rec_metric + trps_cost(trps, trp_inforec_get_next_hop(rec));
380   if (new_metric <= current_metric)
381     return TRP_SUCCESS;
382   else
383     return TRP_ERROR;
384 }
385
386 /* uses memory pointed to by *ts, also returns that value */
387 static void trps_compute_expiry(unsigned int interval, struct timespec *ts)
388 {
389   const unsigned int small_factor=3; /* how many intervals we wait before expiring */
390   if (0!=clock_gettime(CLOCK_REALTIME, ts)) {
391     tr_error("trps_compute_expiry: could not read realtime clock.");
392     ts->tv_sec=0;
393     ts->tv_nsec=0;
394     return NULL;
395   }
396   ts->tv_sec += small_factor*interval;
397   return ts;
398 }
399
400 static TRP_RC trps_accept_update(TRPS_INSTANCE *trps, TRP_INFOREC *rec)
401 {
402   TRP_RENTRY *entry=NULL;
403
404   entry=trp_rtable_get_entry(trps->rtable,
405                              trp_inforec_get_comm(rec),
406                              trp_inforec_get_realm(rec),
407                              trp_inforec_get_next_hop(rec));
408   if (entry==NULL) {
409     entry=trp_rentry_new(NULL);
410     if (entry==NULL) {
411       tr_error("trps_accept_update: unable to allocate new entry.");
412       return TRP_NOMEM;
413     }
414
415     trp_rentry_set_apc(entry, tr_dup_name(trp_inforec_get_comm(rec)));
416     trp_rentry_set_realm(entry, tr_dup_name(trp_inforec_get_realm(rec)));
417     trp_rentry_set_peer(entry, tr_dup_name(trp_inforec_get_next_hop(rec)));
418     trp_rentry_set_trust_router(entry, tr_dup_name(trp_inforec_get_trust_router(rec)));
419     trp_rentry_set_next_hop(entry, tr_dup_name(trp_inforec_get_next_hop(rec)));
420     if ((trp_rentry_get_apc(entry)==NULL)
421        ||(trp_rentry_get_realm(entry)==NULL)
422        ||(trp_rentry_get_peer(entry)==NULL)
423        ||(trp_rentry_get_trust_router(entry)==NULL)
424        ||(trp_rentry_next_hop(entry)==NULL)) {
425       /* at least one field could not be allocated */
426       tr_error("trps_accept_update: unable to allocate all fields for entry.");
427       trp_rentry_free(entry);
428       return TRP_NOMEM;
429     }
430     trp_rtable_add(trps->rtable, entry);
431   }
432
433   /* We now have an entry in the table, whether it's new or not. Update metric and expiry, unless
434    * the metric is infinity. An infinite metric can only occur here if we just retracted an existing
435    * route (we never accept retractions as new routes), so there is no risk of leaving the expiry
436    * time unset on a new route entry. */
437   trp_rentry_set_metric(entry, trp_inforec_get_metric(rec));
438   if (!trps_route_retracted(trps, entry))
439     trp_rentry_set_expiry(entry, trps_compute_expiry(trps, trp_inforec_get_interval(rec)));
440   return TRP_SUCCESS;
441 }
442
443 /* TODO: handle community updates */
444 static TRP_RC trps_handle_update(TRPS_INSTANCE *trps, TRP_UPD *upd)
445 {
446   unsigned int feas=0;
447   TRP_INFOREC *rec=trp_upd_get_inforec(upd);
448   TRP_RENTRY *route=NULL;
449
450   if (rec==NULL) {
451     tr_notice("trps_handle_update: received TRP update with no info records.");
452     return TRP_ERROR;
453   }
454
455   for (;rec!=NULL; rec=trp_inforec_get_next(rec)) {
456     /* validate/sanity check the update */
457     if (trps_validate_inforec(trps, upd) != TRP_SUCCESS) {
458       tr_notice("trps_handle_update: invalid record in TRP update.");
459       continue;
460     }
461
462     /* determine feasibility */
463     feas=trps_check_feasibility(trps, rec);
464
465     /* do we have an existing route? */
466     route=trps_get_route(trps, trp_inforec_get_comm(rec), trp_inforec_get_realm(rec));
467     if (route!=NULL) {
468       /* there was a route table entry already */
469       if (feas) {
470         /* Update is feasible. Accept it. */
471         trps_accept_update(trps, rec);
472       } else {
473         /* Update is infeasible. Ignore it unless the trust router has changed. */
474         if (0!=tr_name_cmp(trp_rentry_get_trust_router(route),
475                            trp_inforec_get_trust_router(rec))) {
476           /* the trust router associated with the route has changed, treat update as a retraction */
477           trps_retract_route(trps, route);
478         }
479       }
480     } else {
481       /* No existing route table entry. Ignore it unless it is feasible and not a retraction. */
482       if (feas && (trp_inforec_get_metric != TRP_METRIC_INFINITY))
483         trps_accept_update(trps, rec);
484     }
485   }
486   return TRP_SUCCESS;
487 }
488
489 TRP_RC trps_handle_tr_msg(TRPS_INSTANCE *trps, TR_MSG *tr_msg)
490 {
491   switch (tr_msg_get_msg_type(tr_msg)) {
492   case TRP_UPDATE:
493     return trps_handle_update(TRPS_INSTANCE *trps, tr_msg_get_trp_upd(tr_msg));
494
495   case TRP_REQUEST:
496     return TRP_UNSUPPORTED;
497
498   default:
499     /* unknown error or one we don't care about (e.g., TID messages) */
500     return TRP_ERROR;
501   }
502 }