/* information about connections between hosts and clients * Copyright (C) 1998, 1999 D. Hugh Redelmeier. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * RCSID $Id: connections.c,v 1.66 2000/06/21 18:24:31 dhr Exp $ */ #include #include #include #include #include #include #include #include #include #include "constants.h" #include "defs.h" #include "id.h" #include "connections.h" /* needs id.h */ #include "packet.h" #include "demux.h" /* needs packet.h */ #include "state.h" #include "ipsec_doi.h" /* needs demux.h and state.h */ #include "server.h" #include "kernel.h" /* for no_klips */ #include "log.h" #include "preshared.h" #include "whack.h" static struct connection *connections = NULL; /* host_pair: a nexus of information about a pair of hosts. * A host is an IP address, UDP port pair. This is a debatable choice: * - should port be considered (no choice of port in standard)? * - should ID be considered (hard because not always known)? * - should IP address matter on our end (we don't know our end)? * Since connections are not all oriented, the host identifiers * (struct in_addr) are placed in a canonical order. */ struct host_pair { struct { struct in_addr addr; /* network order */ u_int16_t port; /* host order */ } a, b; bool initial_connection_sent; struct connection *connections; /* connections with this pair */ struct host_pair *next; }; static struct host_pair *host_pairs = NULL; /* check to see that Ids of peers match */ bool same_peer_ids(const struct connection *c, const struct connection *d , const struct id *his_id) { return id_same(&c->this.id, &d->this.id) && id_same(his_id == NULL? &c->that.id : his_id, &d->that.id); } /* Arbitrary canonicalization of host pair. * Because it is abitrary, we don't need to compare in host order. */ static void canonical_host_pair(struct host_pair *hp , struct in_addr xaddr, u_int16_t xport , struct in_addr yaddr, u_int16_t yport) { if (xaddr.s_addr < yaddr.s_addr || (xaddr.s_addr == yaddr.s_addr && xport <= yport)) { hp->a.addr = xaddr; hp->a.port = xport; hp->b.addr = yaddr; hp->b.port = yport; } else { hp->a.addr = yaddr; hp->a.port = yport; hp->b.addr = xaddr; hp->b.port = xport; } } static struct host_pair * find_host_pair(struct in_addr xaddr, u_int16_t xport , struct in_addr yaddr, u_int16_t yport) { struct host_pair hp; struct host_pair *p, *prev; canonical_host_pair(&hp, xaddr, xport, yaddr, yport); for (prev = NULL, p = host_pairs; p != NULL; prev = p, p = p->next) { if (same_ip(p->a.addr, hp.a.addr) && p->a.port == hp.a.port && same_ip(p->b.addr, hp.b.addr) && p->b.port == hp.b.port) { if (prev != NULL) { prev->next = p->next; /* remove p from list */ p->next = host_pairs; /* and stick it on front */ host_pairs = p; } break; } } return p; } static struct connection * find_host_pair_connections(struct in_addr xaddr, u_int16_t xport , struct in_addr yaddr, u_int16_t yport) { struct host_pair *hp = find_host_pair(xaddr, xport, yaddr, yport); return hp == NULL? NULL : hp->connections; } static void connect_to_host_pair(struct connection *c) { struct host_pair *hp = find_host_pair(c->this.host_addr, c->this.host_port , c->that.host_addr, c->that.host_port); if (hp == NULL) { hp = alloc_thing(struct host_pair, "host_pair"); canonical_host_pair(hp, c->this.host_addr, c->this.host_port , c->that.host_addr, c->that.host_port); hp->initial_connection_sent = FALSE; hp->connections = NULL; hp->next = host_pairs; host_pairs = hp; } c->host_pair = hp; c->hp_next = hp->connections; hp->connections = c; } /* find a connection by name. * move the winner (if any) to the front. * If none is found, and wfd is not NULL_FD, a diagnostic is logged to whack. */ struct connection * con_by_name(const char *nm, bool strict) { struct connection *p, *prev; for (prev = NULL, p = connections; ; prev = p, p = p->next) { if (p == NULL) { if (strict) whack_log(RC_UNKNOWN_NAME, "no connection with that name"); break; } if (strcmp(p->name, nm) == 0) { if (prev != NULL) { prev->next = p->next; /* remove p from list */ p->next = connections; /* and stick it on front */ connections = p; } break; } } return p; } void unorient_connection(struct connection *c) { if (c->rw_state == rwcs_instance) { /* This does everything we need. * Note that we will be called by delete_connection, * but rw_state will be rwcs_going_away. */ delete_connection(c); } else { delete_states_by_connection(c); unroute_connection(c); c->interface = NULL; } } /* Delete a connection */ #define list_rm(etype, enext, e, ehead) { \ etype **ep; \ for (ep = &(ehead); *ep != (e); ep = &(*ep)->enext) \ passert(*ep != NULL); /* we must not come up empty-handed */ \ *ep = (e)->enext; \ } void delete_connection(struct connection *c) { struct connection *old_cur_connection = cur_connection == c? NULL : cur_connection; #ifdef DEBUG unsigned int old_cur_debugging = cur_debugging; #endif SET_CUR_CONNECTION(c); /* Must be careful to avoid circularity: * we mark c as going away so it won't get deleted recursively. */ passert(c->rw_state != rwcs_going_away); if (c->rw_state == rwcs_instance) { char that_host[ADDRTOA_BUF]; addrtoa(c->that.host_addr, 0, that_host, sizeof(that_host)); log("deleting connection \"%s\" with Road Warrior %s" , c->name, that_host); c->rw_state = rwcs_going_away; } else { log("deleting connection"); } unorient_connection(c); /* won't delete c */ /* find and delete c from connections list */ list_rm(struct connection, next, c, connections); cur_connection = old_cur_connection; /* find and delete c from the host pair list */ { struct host_pair *hp = c->host_pair; list_rm(struct connection, hp_next, c, hp->connections); c->host_pair = NULL; /* redundant, but safe */ /* if there are no more connections with this host_pair * and we haven't even made an initial contact, let's delete * this guy in case we were created by an attempted DOS attack. */ if (hp->connections == NULL && !hp->initial_connection_sent) { list_rm(struct host_pair, next, hp, host_pairs); pfree(hp); } } #ifdef DEBUG cur_debugging = old_cur_debugging; #endif pfreeany(c->name); free_id_content(&c->this.id); pfreeany(c->this.updown); free_id_content(&c->that.id); pfreeany(c->that.updown); pfree(c); } void delete_every_connection(void) { while (connections != NULL) delete_connection(connections); } void release_interface(struct iface *i) { struct connection *c, *n; passert(i != NULL); for (c = connections; c != NULL; c = n) { n = c->next; if (c->interface == i) unorient_connection(c); /* may delete c */ } } static void default_end(struct end *e, struct in_addr dflt_nexthop) { /* default ID to IP (but only if not NO_IP -- WildCard) */ if (e->id.kind == ID_NONE && !is_NO_IP(e->host_addr)) { e->id.kind = ID_IPV4_ADDR; e->id.ip_addr = e->host_addr; } /* default nexthop to other side */ if (is_NO_IP(e->host_nexthop)) e->host_nexthop = dflt_nexthop; /* default client to subnet containing only self */ if (!e->has_client) { e->client_net = e->host_addr; e->client_mask = mask32.sin_addr; } } /* format the topology of an end, leaving out defaults * Note: if that==NULL, skip nexthop */ size_t format_end(char *buf, size_t buf_len, struct end *this, struct end *that, bool is_left) { char client[SUBNETTOA_BUF]; const char *client_sep = ""; char host[ADDRTOA_BUF]; char host_port[10]; char host_id[30]; char hop[ADDRTOA_BUF]; const char *hop_sep = ""; /* [client===] */ client[0] = '\0'; if (this->has_client) { subnettoa(this->client_net, this->client_mask, 0 , client, sizeof(client)); client_sep = "==="; } /* host */ addrtoa(this->host_addr, 0, host, sizeof(host)); host_port[0] = '\0'; if (this->host_port != IKE_UDP_PORT) snprintf(host_port, sizeof(host_port), ":%u" , this->host_port); /* id, if different from host */ host_id[0] = '\0'; if (!(this->id.kind == ID_NONE || (this->id.kind == ID_IPV4_ADDR && same_ip(this->id.ip_addr, this->host_addr)))) { int len = idtoa(&this->id, host_id+1, sizeof(host_id)-2); host_id[0] = '['; strcpy(&host_id[len < 0? sizeof(host_id)-2 : 1 + len], "]"); } /* [---hop] */ hop[0] = '\0'; hop_sep = ""; if (that != NULL && !same_ip(this->host_nexthop, that->host_addr)) { addrtoa(this->host_nexthop, 0, hop, sizeof(hop)); hop_sep = "---"; } if (is_left) snprintf(buf, buf_len, "%s%s%s%s%s%s%s" , client, client_sep , host, host_port, host_id , hop_sep, hop); else snprintf(buf, buf_len, "%s%s%s%s%s%s%s" , hop, hop_sep , host, host_port, host_id , client_sep, client); return strlen(buf); } static void unshare_connection_strings(struct connection *c) { c->name = clone_str(c->name, "connection name"); clone_id_content(&c->this.id); c->this.updown = clone_str(c->this.updown, "updown"); clone_id_content(&c->that.id); c->that.updown = clone_str(c->that.updown, "updown"); } static void extract_end(struct end *dst, const struct whack_end *src, const char *which) { /* decode id, if any */ if (src->id == NULL) { dst->id.kind = ID_NONE; } else { complaint_t ugh = atoid(src->id, &dst->id); if (ugh != NULL) { loglog(RC_BADID, "bad %s --id: %s (ignored)", which, ugh); dst->id = empty_id; /* ignore bad one */ } } /* the rest is simple copying of corresponding fields */ dst->host_addr = src->host_addr; dst->host_nexthop = src->host_nexthop; dst->client_net = src->client_net; dst->client_mask = src->client_mask; dst->has_client = src->has_client; dst->updown = src->updown; dst->host_port = src->host_port; } static bool rw_check(const struct whack_end *this, const struct whack_end *that , const struct whack_message *wm) { if (is_NO_IP(this->host_addr)) { loglog(RC_ORIENT, "connection must specify host IP address for our side"); return FALSE; } else { /* check that all RW IKE policies agree because we must implement * them before the correct connection is known. * We cannot enforce this for other non-RW connections because * differentiation is possible when a command specifies which * to initiate. */ const struct connection *c = find_host_pair_connections(this->host_addr , this->host_port, mask0.sin_addr, that->host_port); for (; c != NULL; c = c->hp_next) { if ((c->policy ^ wm->policy) & (POLICY_PSK | POLICY_RSASIG)) { loglog(RC_CLASH , "authentication method disagrees with \"%s\", which is also for an unspecified peer" , c->name); return FALSE; } } } return TRUE; /* happy */ } void add_connection(const struct whack_message *wm) { if (con_by_name(wm->name, FALSE) != NULL) { loglog(RC_DUPNAME, "attempt to redefine connection \"%s\"", wm->name); } else if ((is_NO_IP(wm->left.host_addr) && !rw_check(&wm->right, &wm->left, wm)) || (is_NO_IP(wm->right.host_addr) && !rw_check(&wm->left, &wm->right, wm))) { /* game over -- give up */ } else { struct connection *c = alloc_thing(struct connection, "struct connection"); c->name = wm->name; c->policy = wm->policy; c->sa_ike_life_seconds = wm->sa_ike_life_seconds; c->sa_ipsec_life_seconds = wm->sa_ipsec_life_seconds; c->sa_rekey_margin = wm->sa_rekey_margin; c->sa_rekey_fuzz = wm->sa_rekey_fuzz; c->sa_keying_tries = wm->sa_keying_tries; extract_end(&c->this, &wm->left, "left"); extract_end(&c->that, &wm->right, "right"); default_end(&c->this, c->that.host_addr); default_end(&c->that, c->this.host_addr); /* force any wildcard host IP address to that end */ if (is_NO_IP(wm->left.host_addr)) { struct end t = c->this; c->this = c->that; c->that = t; } /* set internal fields */ c->next = connections; connections = c; c->interface = NULL; c->routed = FALSE; c->newest_isakmp_sa = SOS_NOBODY; c->newest_ipsec_sa = SOS_NOBODY; c->eroute_owner = SOS_NOBODY; c->rw_state = rwcs_permanent; #ifdef DEBUG c->extra_debugging = wm->debugging; #endif unshare_connection_strings(c); connect_to_host_pair(c); /* log all about this connection */ log("added connection description \"%s\"", c->name); DBG(DBG_CONTROL, char lhs[100]; char rhs[100]; (void) format_end(lhs, sizeof(lhs), &c->this, &c->that, TRUE); (void) format_end(rhs, sizeof(rhs), &c->that, &c->this, FALSE); DBG_log("%s...%s", lhs, rhs); DBG_log("ike_life: %lus; ipsec_life: %lus; rekey_margin: %lus;" " rekey_fuzz: %lu%%; keyingtries: %lu; policy: %s" , (unsigned long) c->sa_ike_life_seconds , (unsigned long) c->sa_ipsec_life_seconds , (unsigned long) c->sa_rekey_margin , (unsigned long) c->sa_rekey_fuzz , (unsigned long) c->sa_keying_tries , bitnamesof(sa_policy_bit_names, c->policy)); ); } } struct connection * rw_connection(const struct connection *c, struct in_addr him) { struct connection *d = clone_thing(*c, "temporary connection"); passert(c->rw_state == rwcs_permanent); unshare_connection_strings(d); DBG(DBG_CONTROL, DBG_log("instantiating \"%s\" for %s" , d->name, inet_ntoa(him))); d->rw_state = rwcs_instance; passert(oriented(*d)); passert(HasWildcardIP(*d)); d->that.host_addr = him; default_end(&d->that, d->this.host_addr); /* We cannot guess what our next_hop should be, but if it was * explicitly specified as 0.0.0.0, we set it to be him. * (whack will not allow nexthop to be elided in RW case.) */ default_end(&d->this, d->that.host_addr); /* set internal fields */ d->next = connections; connections = d; d->routed = FALSE; d->newest_isakmp_sa = SOS_NOBODY; d->newest_ipsec_sa = SOS_NOBODY; d->eroute_owner = SOS_NOBODY; connect_to_host_pair(d); return d; } bool orient(struct connection *c, bool must) { if (!oriented(*c)) { struct iface *p; /* Note: this loop does not stop when it finds a match: * it continues checking to catch any ambiguity. */ for (p = interfaces; p != NULL; p = p->next) { for (;;) { /* check if this interface matches this end */ if (same_ip(c->this.host_addr, p->addr) && (!no_klips || c->this.host_port == pluto_port)) { if (oriented(*c)) { if (c->interface == p) loglog(RC_LOG_SERIOUS , "both sides of \"%s\" are our interfaces!" , c->name); else loglog(RC_LOG_SERIOUS, "two interfaces match \"%s\"", c->name); c->interface = NULL; /* withdraw orientation */ return FALSE; } c->interface = p; } /* done with this interface if it doesn't match that end */ if (!(same_ip(c->that.host_addr, p->addr) && (!no_klips || c->that.host_port == pluto_port))) break; /* swap ends and try again */ { struct end t = c->this; c->this = c->that; c->that = t; } } } if (!oriented(*c)) { if (must) loglog(RC_LOG_SERIOUS, "cannot find interface for connection \"%s\"", c->name); return FALSE; } } return TRUE; } void initiate_connection(const char *name, int whackfd) { struct connection *c = con_by_name(name, TRUE); if (c != NULL) { SET_CUR_CONNECTION(c); if (!orient(c, TRUE)) { loglog(RC_ORIENT, "could not orient connection"); } else if (HasWildcardIP(*c)) { loglog(RC_NOPEERIP, "cannot initiate connection without peer IP"); } else { /* We will only request an IPsec SA if policy isn't empty * (ignoring Main Mode items). * This is a fudge, but not yet important. * If we are to proceed asynchronously, whackfd will be NULL_FD. */ ipsecdoi_initiate(whackfd , c , (c->policy>>POLICY_IPSEC_SHIFT) != 0 , c->policy , 1); } UNSET_CUR_CONNECTION(); } } void terminate_connection(const char *nm) { /* Loop because more than one may match (master and instances) * But at least one is required (enforced by con_by_name). */ struct connection *c, *n; for (c = con_by_name(nm, TRUE); c != NULL; c = n) { n = c->next; /* grab this before c might disappear */ if (strcmp(c->name, nm) == 0) { SET_CUR_CONNECTION(c); log("terminating SAs using connection"); delete_states_by_connection(c); UNSET_CUR_CONNECTION(); } } } /* check nexthop safety * Our nexthop must not be within a routed client subnet, and vice versa. * Note: we don't think this is true. We think that KLIPS will * not process a packet output by an eroute. */ #ifdef NEVER bool check_nexthop(const struct connection *c) { struct connection *d; if (inside_client(c->this.host_nexthop, c->that)) { loglog(RC_LOG_SERIOUS, "cannot perform routing for connection \"%s\"" " because nexthop is within peer's client network", c->name); return FALSE; } for (d = connections; d != NULL; d = d->next) { if (d->routed) { if (inside_client(c->this.host_nexthop, d->that)) { loglog(RC_LOG_SERIOUS, "cannot do routing for connection \"%s\" " because nexthop is contained in" " existing routing for connection \"%s\"", c->name, d->name); return FALSE; } if (inside_client(d->this.host_nexthop, c->that)) { loglog(RC_LOG_SERIOUS, "cannot do routing for connection \"%s\" " because it contains nexthop of" " existing routing for connection \"%s\"", c->name, d->name); return FALSE; } } } return TRUE; } #endif /* NEVER */ /* Find a connection that owns the route to a connection's peer's client. * Preference is given to one that has an eroute too. * If eroute is true, our client must match too (as for eroutes). */ struct connection * route_owner(struct connection *c, bool eroute) { struct connection *d, *best = NULL; passert(oriented(*c)); if (c->routed && c->eroute_owner != SOS_NOBODY) return c; for (d = connections; d != NULL; d = d->next) { if (d->routed) { passert(oriented(*d)); if (same_client(c->that, d->that) && (!eroute || same_client(c->this, d->this))) { if (d->eroute_owner != SOS_NOBODY) return d; /* definite winner */ best = d; /* winner if no better comes along */ } } } return best; } struct connection * find_host_connection(struct in_addr me, u_int16_t my_port , struct in_addr him, u_int16_t his_port) { struct connection *c = find_host_pair_connections(me, my_port, him, his_port); if (c != NULL && !oriented(*c)) orient(c, FALSE); return c; } /* given an up-until-now satisfactory connection, find the best * connection now that we've got the Id Payload from the peer. * We can only accept connections with compatible authentication. */ struct connection * refine_host_connection(const struct state *st, const struct id *peer_id , bool initiator) { struct connection *c = st->st_connection; u_int16_t auth = st->st_oakley.auth; struct connection *d; lset_t auth_policy; const chunk_t *psk; const struct RSA_public_key *his_RSA_pub; const struct RSA_private_key *my_RSA_pri; bool rw; if (id_same(&c->that.id, peer_id)) return c; switch (auth) { case OAKLEY_PRESHARED_KEY: auth_policy = POLICY_PSK; psk = get_preshared_secret(c); if (psk == NULL) return NULL; /* cannot determine PSK! */ break; case OAKLEY_RSA_SIG: auth_policy = POLICY_RSASIG; my_RSA_pri = get_RSA_private_key(c); if (my_RSA_pri == NULL) return NULL; /* cannot determine my RSA private key! */ if (initiator) { his_RSA_pub = get_RSA_public_key(&c->that.id); if (his_RSA_pub == NULL) return NULL; /* cannot determine his RSA public key! */ } break; default: passert(FALSE); } /* The current connection won't do: search for one that will. * First search for one with the same pair of hosts. * If that fails, search for one with a Road Warrior peer. */ d = c->host_pair->connections; for (rw = FALSE; ; rw = TRUE) { for (; d != NULL; d = d->hp_next) { if (!oriented(*d)) (void)orient(d, FALSE); if (same_peer_ids(c, d, peer_id)) { /* check whether authentication would work the same */ if (!((d->policy & auth_policy) != 0 || (d->policy & POLICY_ISAKMP_MASK) == 0)) continue; /* our auth isn't OK for this connection */ switch (auth) { case OAKLEY_PRESHARED_KEY: if (psk != get_preshared_secret(d)) continue; /* different secret */ break; case OAKLEY_RSA_SIG: if (my_RSA_pri != get_RSA_private_key(d)) continue; /* different private key */ if (initiator && his_RSA_pub != get_RSA_public_key(&d->that.id)) continue; /* different public key */ break; default: passert(FALSE); } return d; /* passed all the tests */ } } if (rw) break; /* been around twice already */ /* for second time around: figure out RW host pair's connections */ d = find_host_pair_connections(c->this.host_addr, c->this.host_port , mask0.sin_addr, c->that.host_port); } return NULL; } /* Given a connection suitable for ISAKMP (i.e. the hosts match), * find a one suitable for IPSEC (i.e. with matching clients). * - c is the starting point * - hp is a connection with the correct pair of hosts. This cannot * be the same as c iff we are searching for a wildcarded connection. * * If we don't find an exact match (not even our current connection), * we try for one that still needs instantiation. * This requires inverse instantiation: abstraction. * (1) The peer is abstracted to 0.0.0.0. * (2) If the peer's client subnet just contains the peer * (peer/32), constrain the peer's client to be 0.0.0.0/32. * (3) If the Phase 1 ID of the peer is simply its IP address, * and we cannot find a suitable wild-carded connection * with that ID, allow the peer's ID to be none * Interestingly, we allow a wild card match even if the old * connection was not an instantiation. * Note: whatever ID we happened to choose for our end is * required to stick. After all, this is the one we used * during authentication. */ struct connection * find_client_connection( struct connection *c, struct in_addr our_net, struct in_addr our_mask, struct in_addr peer_net, struct in_addr peer_mask) { struct connection *hp = c->host_pair->connections; struct id peer_id = c->that.id; struct connection *unrouted = NULL; bool rw; /* give priority to current connection * but even greater priority to a routed connection */ if (client_is(c->this, our_net, our_mask) && client_is(c->that, peer_net, peer_mask)) { passert(oriented(*c)); if (c->routed) return c; unrouted = c; } for (rw = FALSE; ; rw = TRUE) { struct connection *d; for (d = hp; d != NULL; d = d->hp_next) { if (!oriented(*d)) (void)orient(d, FALSE); if (same_peer_ids(c, d, &peer_id) && client_is(d->this, our_net, our_mask) && client_is(d->that, peer_net, peer_mask)) { if (d->routed) return d; if (unrouted == NULL) unrouted = d; } } if (unrouted != NULL) break; /* good enough to preclude more abstract search */ /* this search failed; try to set up another */ if (!rw) { /* try for a road warrior with identical mobile ID */ hp = find_host_connection(c->this.host_addr , c->this.host_port, mask0.sin_addr, c->that.host_port); /* abstract peer_net, if subnet only contains peer. * peer_mask need not be adjusted. */ if (same_subnet(peer_net, peer_mask , c->that.host_addr, mask32.sin_addr)) peer_net = mask0.sin_addr; } else if (peer_id.kind == ID_IPV4_ADDR && same_ip(peer_id.ip_addr, c->that.host_addr)) { /* try again for road warrior, but without ID */ peer_id.kind = ID_NONE; } else { break; } } return unrouted; } void show_connections_status(void) { struct connection *c; for (c = connections; c != NULL; c = c->next) { const char *routed = c->routed? "; routed" : ""; const char *ifn = oriented(*c)? c->interface->rname : ""; /* show topology */ { char lhs[100]; size_t ll = format_end(lhs, sizeof(lhs), &c->this, &c->that, TRUE); char rhs[100]; size_t rl = format_end(rhs, sizeof(rhs), &c->that, &c->this, FALSE); if (ll + rl < 70) { /* display on one line */ whack_log(RC_COMMENT, "\"%s\": %s...%s", c->name, lhs, rhs); } else { /* split over two lines */ whack_log(RC_COMMENT, "\"%s\": %s...", c->name, lhs); whack_log(RC_COMMENT, "\"%s\": ...%s", c->name, rhs); } } whack_log(RC_COMMENT , "\"%s\": ike_life: %lus; ipsec_life: %lus; rekey_margin: %lus;" " rekey_fuzz: %lu%%; keyingtries: %lu" , c->name , (unsigned long) c->sa_ike_life_seconds , (unsigned long) c->sa_ipsec_life_seconds , (unsigned long) c->sa_rekey_margin , (unsigned long) c->sa_rekey_fuzz , (unsigned long) c->sa_keying_tries); whack_log(RC_COMMENT , "\"%s\": policy: %s; interface: %s%s" , c->name , bitnamesof(sa_policy_bit_names, c->policy) , ifn , routed); whack_log(RC_COMMENT , "\"%s\": newest ISAKMP SA: #%ld; newest IPsec SA: #%ld; eroute owner: #%lu" , c->name , c->newest_isakmp_sa , c->newest_ipsec_sa , c->eroute_owner); } }