diff options
Diffstat (limited to 'nhrp/nhrp_server.c')
-rw-r--r-- | nhrp/nhrp_server.c | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/nhrp/nhrp_server.c b/nhrp/nhrp_server.c new file mode 100644 index 0000000..b41e4b8 --- /dev/null +++ b/nhrp/nhrp_server.c @@ -0,0 +1,566 @@ +/* nhrp_server.c - NHRP request handling + * + * Copyright (C) 2007-2009 Timo Teräs <timo.teras@iki.fi> + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 or later as + * published by the Free Software Foundation. + * + * See http://www.gnu.org/ for details. + */ + +#include <string.h> +#include <netinet/in.h> + +#include "nhrp_common.h" +#include "nhrp_packet.h" +#include "nhrp_interface.h" +#include "nhrp_peer.h" + +#define NHRP_MAX_PENDING_REQUESTS 16 + +struct nhrp_pending_request { + struct list_head request_list_entry; + int natted; + int num_ok, num_error; + struct nhrp_packet *packet; + struct nhrp_cie *cie; + struct nhrp_payload *payload; + struct nhrp_peer *peer, *rpeer; + ev_tstamp now; +}; + +static struct list_head request_list = LIST_INITIALIZER(request_list); +static int num_pending_requests = 0; + +static void nhrp_server_start_cie_reg(struct nhrp_pending_request *pr); + +static struct nhrp_pending_request * +nhrp_server_record_request(struct nhrp_packet *packet) +{ + struct nhrp_pending_request *pr; + + pr = calloc(1, sizeof(struct nhrp_pending_request)); + list_init(&pr->request_list_entry); + if (pr != NULL) { + num_pending_requests++; + list_add(&pr->request_list_entry, &request_list); + pr->packet = nhrp_packet_get(packet); + pr->now = ev_now(); + } + return pr; +} + +void nhrp_server_finish_request(struct nhrp_pending_request *pr) +{ + list_del(&pr->request_list_entry); + if (pr->rpeer != NULL) { + struct nhrp_peer *peer = pr->rpeer; + if (peer->flags & NHRP_PEER_FLAG_REPLACED) { + /* The route peer entry was not accepted. We still + * send the replies here, and cancel anything pending + * so it'll get deleted cleanly on next put(). */ + nhrp_peer_send_packet_queue(peer); + nhrp_peer_cancel_async(peer); + } + nhrp_peer_put(pr->rpeer); + } + if (pr->peer != NULL) + nhrp_peer_put(pr->peer); + if (pr->packet != NULL) + nhrp_packet_put(pr->packet); + free(pr); + num_pending_requests--; +} + +static int nhrp_server_request_pending(struct nhrp_packet *packet) +{ + struct nhrp_pending_request *r; + + list_for_each_entry(r, &request_list, request_list_entry) { + if (nhrp_address_cmp(&packet->src_nbma_address, + &r->packet->src_nbma_address) != 0) + continue; + if (nhrp_address_cmp(&packet->src_protocol_address, + &r->packet->src_protocol_address) != 0) + continue; + if (nhrp_address_cmp(&packet->dst_protocol_address, + &r->packet->dst_protocol_address) != 0) + continue; + + /* Request from the same address being already processed */ + return TRUE; + } + + return FALSE; +} + +static int nhrp_handle_resolution_request(struct nhrp_packet *packet) +{ + char tmp[64], tmp2[64]; + struct nhrp_payload *payload; + struct nhrp_peer *peer = packet->dst_peer; + struct nhrp_peer_selector sel; + struct nhrp_cie *cie; + + nhrp_info("Received Resolution Request from proto src %s to %s", + nhrp_address_format(&packet->src_protocol_address, + sizeof(tmp), tmp), + nhrp_address_format(&packet->dst_protocol_address, + sizeof(tmp2), tmp2)); + + /* As first thing, flush all negative entries for the + * requestor */ + memset(&sel, 0, sizeof(sel)); + sel.flags = NHRP_PEER_FIND_EXACT; + sel.type_mask = BIT(NHRP_PEER_TYPE_NEGATIVE); + sel.interface = packet->src_iface; + sel.protocol_address = packet->src_protocol_address; + nhrp_peer_foreach(nhrp_peer_remove_matching, NULL, &sel); + + /* Send reply */ + packet->hdr.type = NHRP_PACKET_RESOLUTION_REPLY; + packet->hdr.hop_count = NHRP_PACKET_DEFAULT_HOP_COUNT; + packet->hdr.flags &= NHRP_FLAG_RESOLUTION_SOURCE_IS_ROUTER | + NHRP_FLAG_RESOLUTION_SOURCE_STABLE | + NHRP_FLAG_RESOLUTION_UNIQUE | + NHRP_FLAG_RESOLUTION_NAT; + packet->hdr.flags |= NHRP_FLAG_RESOLUTION_DESTINATION_STABLE | + NHRP_FLAG_RESOLUTION_AUTHORATIVE; + + cie = nhrp_cie_alloc(); + if (cie == NULL) + return FALSE; + + cie->hdr = (struct nhrp_cie_header) { + .code = NHRP_CODE_SUCCESS, + .prefix_length = peer->prefix_length, + }; + if (peer->holding_time) + cie->hdr.holding_time = htons(peer->holding_time); + else if (peer->interface != NULL) + cie->hdr.holding_time = htons(peer->interface->holding_time); + else + cie->hdr.holding_time = NHRP_DEFAULT_HOLDING_TIME; + + payload = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_ANY); + nhrp_payload_free(payload); + nhrp_payload_set_type(payload, NHRP_PAYLOAD_TYPE_CIE_LIST); + nhrp_payload_add_cie(payload, cie); + + if (!nhrp_packet_reroute(packet, NULL)) + return FALSE; + + peer = packet->dst_peer; + cie->hdr.mtu = htons(peer->my_nbma_mtu); + cie->nbma_address = peer->my_nbma_address; + cie->protocol_address = packet->dst_iface->protocol_address; + + nhrp_info("Sending Resolution Reply %s/%d is-at %s (holdtime %d)", + nhrp_address_format(&packet->dst_protocol_address, + sizeof(tmp), tmp), + cie->hdr.prefix_length, + nhrp_address_format(&cie->nbma_address, + sizeof(tmp2), tmp2), + ntohs(cie->hdr.holding_time)); + + /* Reset NAT header to regenerate it for reply */ + payload = nhrp_packet_extension(packet, + NHRP_EXTENSION_NAT_ADDRESS | + NHRP_EXTENSION_FLAG_NOCREATE, + NHRP_PAYLOAD_TYPE_ANY); + if (payload != NULL) { + nhrp_payload_free(payload); + nhrp_payload_set_type(payload, NHRP_PAYLOAD_TYPE_CIE_LIST); + } + + return nhrp_packet_send(packet); +} + +static int find_one(void *ctx, struct nhrp_peer *p) +{ + return 1; +} + +static int remove_old_registrations(void *ctx, struct nhrp_peer *p) +{ + struct nhrp_peer *peer = (struct nhrp_peer *) ctx; + + /* If re-registration, mark the new connection up */ + if (nhrp_address_cmp(&peer->protocol_address, + &p->protocol_address) == 0 && + nhrp_address_cmp(&peer->next_hop_address, + &p->next_hop_address) == 0 && + peer->prefix_length == p->prefix_length) + peer->flags |= p->flags & (NHRP_PEER_FLAG_UP | + NHRP_PEER_FLAG_LOWER_UP); + + p->flags |= NHRP_PEER_FLAG_REPLACED; + nhrp_peer_remove(p); + return 0; +} + +static void nhrp_server_finish_reg(struct nhrp_pending_request *pr) +{ + char tmp[64], tmp2[64]; + struct nhrp_packet *packet = pr->packet; + + if (pr->rpeer != NULL && + nhrp_packet_reroute(packet, pr->rpeer)) { + nhrp_info("Sending Registration Reply from proto src %s to %s (%d bindings accepted, %d rejected)", + nhrp_address_format(&packet->dst_protocol_address, + sizeof(tmp), tmp), + nhrp_address_format(&packet->src_protocol_address, + sizeof(tmp2), tmp2), + pr->num_ok, pr->num_error); + + nhrp_packet_send(packet); + } else { + /* We could not create route peer entry (likely out of memory), + * so we can't do much more here. */ + nhrp_info("Dropping Registration Reply from proto src %s to %s", + nhrp_address_format(&packet->dst_protocol_address, + sizeof(tmp), tmp), + nhrp_address_format(&packet->src_protocol_address, + sizeof(tmp2), tmp2)); + } + + nhrp_server_finish_request(pr); +} + +static void nhrp_server_finish_cie_reg_cb(union nhrp_peer_event e, int revents) +{ + struct nhrp_peer *peer; + struct nhrp_pending_request *pr; + struct nhrp_packet *packet; + struct nhrp_cie *cie; + struct nhrp_peer_selector sel; + char tmp[64], reason[32]; + + peer = nhrp_peer_from_event(e, revents); + pr = peer->request; + packet = pr->packet; + cie = pr->cie; + + peer->request = NULL; + nhrp_address_format(&peer->protocol_address, sizeof(tmp), tmp); + if (revents != 0 && nhrp_peer_event_ok(e, revents)) { + nhrp_debug("[%s] Peer registration authorized", tmp); + + /* Remove all old stuff and accept registration */ + memset(&sel, 0, sizeof(sel)); + sel.flags = NHRP_PEER_FIND_EXACT; + sel.type_mask = NHRP_PEER_TYPEMASK_REMOVABLE; + sel.interface = packet->src_iface; + sel.protocol_address = peer->protocol_address; + sel.prefix_length = peer->prefix_length; + nhrp_peer_foreach(remove_old_registrations, peer, &sel); + + pr->num_ok++; + cie->hdr.code = NHRP_CODE_SUCCESS; + nhrp_peer_insert(peer); + } else { + if (revents == 0) + nhrp_error("[%s] Peer registration failed: " + "static entry exists", tmp); + else + nhrp_error("[%s] Peer registration failed: %s", + tmp, + nhrp_peer_event_reason(e, revents, + sizeof(reason), + reason)); + pr->num_error++; + cie->hdr.code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; + peer->flags |= NHRP_PEER_FLAG_REPLACED; + } + if (pr->rpeer == NULL) + pr->rpeer = nhrp_peer_get(peer); + + nhrp_peer_put(peer); + pr->peer = NULL; + + /* Process next CIE or finish registration handling */ + if (cie->cie_list_entry.next != &pr->payload->u.cie_list) { + pr->cie = list_next(&cie->cie_list_entry, struct nhrp_cie, cie_list_entry); + nhrp_server_start_cie_reg(pr); + } else { + nhrp_server_finish_reg(pr); + } + +} + +static void nhrp_server_start_cie_reg(struct nhrp_pending_request *pr) +{ + struct nhrp_cie *cie = pr->cie; + struct nhrp_packet *packet = pr->packet; + struct nhrp_peer *peer; + struct nhrp_peer_selector sel; + + peer = nhrp_peer_alloc(packet->src_iface); + if (peer == NULL) { + /* Mark all remaining registration requests as failed + * due to lack of memory, and send reply */ + for (; cie->cie_list_entry.next != &pr->payload->u.cie_list; + cie = list_next(&cie->cie_list_entry, struct nhrp_cie, cie_list_entry)) { + pr->num_error++; + cie->hdr.code = NHRP_CODE_INSUFFICIENT_RESOURCES; + } + pr->num_error++; + cie->hdr.code = NHRP_CODE_INSUFFICIENT_RESOURCES; + nhrp_server_finish_reg(pr); + return; + } + + peer->type = NHRP_PEER_TYPE_DYNAMIC; + peer->afnum = packet->hdr.afnum; + peer->protocol_type = packet->hdr.protocol_type; + peer->expire_time = pr->now + ntohs(cie->hdr.holding_time); + peer->mtu = ntohs(cie->hdr.mtu); + if (cie->nbma_address.addr_len != 0) + peer->next_hop_address = cie->nbma_address; + else + peer->next_hop_address = packet->src_nbma_address; + + if (pr->natted) { + peer->next_hop_nat_oa = peer->next_hop_address; + peer->next_hop_address = packet->src_linklayer_address; + } + + if (cie->protocol_address.addr_len != 0) + peer->protocol_address = cie->protocol_address; + else + peer->protocol_address = packet->src_protocol_address; + + peer->prefix_length = cie->hdr.prefix_length; + if (peer->prefix_length == 0xff) + peer->prefix_length = peer->protocol_address.addr_len * 8; + + memset(&sel, 0, sizeof(sel)); + sel.flags = NHRP_PEER_FIND_EXACT; + sel.type_mask = ~NHRP_PEER_TYPEMASK_REMOVABLE; + sel.interface = packet->src_iface; + sel.protocol_address = peer->protocol_address; + sel.prefix_length = peer->prefix_length; + + /* Link the created peer and pending request structures */ + pr->peer = peer; + peer->request = pr; + + /* Check that there is no conflicting peers */ + if (nhrp_peer_foreach(find_one, peer, &sel) != 0) { + cie->hdr.code = NHRP_CODE_ADMINISTRATIVELY_PROHIBITED; + peer->flags |= NHRP_PEER_FLAG_REPLACED; + nhrp_server_finish_cie_reg_cb(&peer->child, 0); + } else { + nhrp_peer_run_script(peer, "peer-register", + nhrp_server_finish_cie_reg_cb); + } +} + +static int nhrp_handle_registration_request(struct nhrp_packet *packet) +{ + char tmp[64], tmp2[64]; + struct nhrp_payload *payload; + struct nhrp_cie *cie; + struct nhrp_pending_request *pr; + int natted = 0; + + nhrp_info("Received Registration Request from proto src %s to %s", + nhrp_address_format(&packet->src_protocol_address, + sizeof(tmp), tmp), + nhrp_address_format(&packet->dst_protocol_address, + sizeof(tmp2), tmp2)); + + if (nhrp_server_request_pending(packet)) { + nhrp_info("Already processing: resent packet ignored."); + return TRUE; + } + + if (num_pending_requests >= NHRP_MAX_PENDING_REQUESTS) { + /* We should probably send Registration Reply with CIE + * error NHRP_CODE_INSUFFICIENT_RESOURCES, or an Error + * Indication. However, we do not have a direct peer entry + * nor can we make sure that the lower layer is up, so + * we just lamely drop the packet for now. */ + nhrp_info("Too many pending requests: dropping this one"); + return TRUE; + } + + /* Cisco NAT extension, CIE added IF all of the following is true: + * 1. We are the first hop registration server + * (=no entries in forward transit CIE list) + * 2. NAT is detected (link layer address != announced address) + * 3. NAT extension is requested */ + payload = nhrp_packet_extension(packet, + NHRP_EXTENSION_FORWARD_TRANSIT_NHS | + NHRP_EXTENSION_FLAG_NOCREATE, + NHRP_PAYLOAD_TYPE_CIE_LIST); + if (payload != NULL && list_empty(&payload->u.cie_list) && + packet->src_linklayer_address.type != PF_UNSPEC && + nhrp_address_cmp(&packet->src_nbma_address, + &packet->src_linklayer_address) != 0) { + natted = 1; + payload = nhrp_packet_extension(packet, + NHRP_EXTENSION_NAT_ADDRESS | + NHRP_EXTENSION_FLAG_NOCREATE, + NHRP_PAYLOAD_TYPE_CIE_LIST); + if (payload != NULL) { + cie = nhrp_cie_alloc(); + if (cie != NULL) { + cie->nbma_address = packet->src_linklayer_address; + cie->protocol_address = packet->src_protocol_address; + nhrp_payload_add_cie(payload, cie); + } + } + } + + packet->hdr.type = NHRP_PACKET_REGISTRATION_REPLY; + packet->hdr.hop_count = NHRP_PACKET_DEFAULT_HOP_COUNT; + packet->hdr.flags &= NHRP_FLAG_REGISTRATION_UNIQUE | + NHRP_FLAG_REGISTRATION_NAT; + + payload = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_CIE_LIST); + if (list_empty(&payload->u.cie_list)) { + nhrp_error("Received registration request has no CIEs"); + return TRUE; + } + + /* Start processing the CIEs */ + pr = nhrp_server_record_request(packet); + pr->natted = natted; + pr->payload = payload; + + pr->cie = nhrp_payload_get_cie(payload, 1); + nhrp_server_start_cie_reg(pr); + + return TRUE; +} + +static int remove_peer_by_nbma(void *ctx, struct nhrp_peer *peer) +{ + struct nhrp_address *nbma = ctx; + struct nhrp_address *peer_nbma = NULL; + + if (!nhrp_address_is_any_addr(nbma)) { + if (peer->type == NHRP_PEER_TYPE_SHORTCUT_ROUTE) { + struct nhrp_peer *nexthop; + + nexthop = nhrp_peer_route(peer->interface, + &peer->next_hop_address, + NHRP_PEER_FIND_EXACT, + NHRP_PEER_TYPEMASK_ADJACENT); + if (nexthop != NULL) + peer_nbma = &nexthop->next_hop_address; + } else { + peer_nbma = &peer->next_hop_address; + } + } else { + peer_nbma = nbma; + } + + if (peer_nbma != NULL && + nhrp_address_cmp(peer_nbma, nbma) == 0) + nhrp_peer_remove(peer); + + return 0; +} + +static int nhrp_handle_purge_request(struct nhrp_packet *packet) +{ + char tmp[64], tmp2[64]; + struct nhrp_peer_selector sel; + struct nhrp_payload *payload; + struct nhrp_cie *cie; + int flags, ret = TRUE; + + nhrp_info("Received Purge Request from proto src %s to %s", + nhrp_address_format(&packet->src_protocol_address, + sizeof(tmp), tmp), + nhrp_address_format(&packet->dst_protocol_address, + sizeof(tmp2), tmp2)); + + flags = packet->hdr.flags; + packet->hdr.type = NHRP_PACKET_PURGE_REPLY; + packet->hdr.hop_count = NHRP_PACKET_DEFAULT_HOP_COUNT; + packet->hdr.flags = 0; + + if (!(flags & NHRP_FLAG_PURGE_NO_REPLY)) { + if (nhrp_packet_reroute(packet, NULL)) + ret = nhrp_packet_send(packet); + else + ret = FALSE; + } + + payload = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_CIE_LIST); + list_for_each_entry(cie, &payload->u.cie_list, cie_list_entry) { + nhrp_info("Purge proto %s/%d nbma %s", + nhrp_address_format(&cie->protocol_address, + sizeof(tmp), tmp), + cie->hdr.prefix_length, + nhrp_address_format(&cie->nbma_address, + sizeof(tmp2), tmp2)); + + memset(&sel, 0, sizeof(sel)); + sel.flags = NHRP_PEER_FIND_EXACT; + sel.type_mask = NHRP_PEER_TYPEMASK_REMOVABLE; + sel.interface = packet->src_iface; + sel.protocol_address = cie->protocol_address; + sel.prefix_length = cie->hdr.prefix_length; + nhrp_peer_foreach(remove_peer_by_nbma, + &cie->nbma_address, &sel); + nhrp_rate_limit_clear(&cie->protocol_address, + cie->hdr.prefix_length); + } + + return ret; +} + +static int nhrp_handle_traffic_indication(struct nhrp_packet *packet) +{ + char tmp[64], tmp2[64]; + struct nhrp_address dst; + struct nhrp_payload *pl; + + pl = nhrp_packet_payload(packet, NHRP_PAYLOAD_TYPE_RAW); + if (pl == NULL) + return FALSE; + + if (!nhrp_address_parse_packet(packet->hdr.protocol_type, + pl->u.raw->length, pl->u.raw->data, + NULL, &dst)) + return FALSE; + + /* Shortcuts enabled? */ + if (packet->src_iface->flags & NHRP_INTERFACE_FLAG_SHORTCUT) { + nhrp_info("Traffic Indication from proto src %s; " + "about packet to %s", + nhrp_address_format(&packet->src_protocol_address, + sizeof(tmp), tmp), + nhrp_address_format(&dst, sizeof(tmp2), tmp2)); + + nhrp_peer_traffic_indication(packet->src_iface, + packet->hdr.afnum, + &dst); + } else { + nhrp_info("Traffic Indication ignored from proto src %s; " + "about packet to %s", + nhrp_address_format(&packet->src_protocol_address, + sizeof(tmp), tmp), + nhrp_address_format(&dst, sizeof(tmp2), tmp2)); + } + + return TRUE; +} + +void server_init(void) +{ + nhrp_packet_hook_request(NHRP_PACKET_RESOLUTION_REQUEST, + nhrp_handle_resolution_request); + nhrp_packet_hook_request(NHRP_PACKET_REGISTRATION_REQUEST, + nhrp_handle_registration_request); + nhrp_packet_hook_request(NHRP_PACKET_PURGE_REQUEST, + nhrp_handle_purge_request); + nhrp_packet_hook_request(NHRP_PACKET_TRAFFIC_INDICATION, + nhrp_handle_traffic_indication); +} |