diff options
author | Jan Moskyto Matejka <mq@ucw.cz> | 2016-12-07 15:30:46 +0100 |
---|---|---|
committer | Jan Moskyto Matejka <mq@ucw.cz> | 2016-12-07 15:30:46 +0100 |
commit | ad88b94bca78e010357a6c7806e1d5e01701d4a7 (patch) | |
tree | 9c06e9c1b0c87f372dcf27cc832d5692db112e80 /proto/rpki/packets.c | |
parent | d15b0b0a1b494c14b139d2d28706d82cd6e2f139 (diff) | |
parent | af62c0f9f1f6382fe88c8ae5e514f70c0b5b6d05 (diff) |
Merge branch 'int-new-rpki-squashed' (early part) into int-new
Diffstat (limited to 'proto/rpki/packets.c')
-rw-r--r-- | proto/rpki/packets.c | 1077 |
1 files changed, 1077 insertions, 0 deletions
diff --git a/proto/rpki/packets.c b/proto/rpki/packets.c new file mode 100644 index 00000000..22b0b54f --- /dev/null +++ b/proto/rpki/packets.c @@ -0,0 +1,1077 @@ +/* + * BIRD -- The Resource Public Key Infrastructure (RPKI) to Router Protocol + * + * (c) 2015 CZ.NIC + * (c) 2015 Pavel Tvrdik <pawel.tvrdik@gmail.com> + * + * This file was a part of RTRlib: http://rpki.realmv6.org/ + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#undef LOCAL_DEBUG + +#include "rpki.h" +#include "transport.h" +#include "packets.h" + +#define RPKI_ADD_FLAG 0b00000001 + +enum rpki_transmit_type { + RPKI_RECV = 0, + RPKI_SEND = 1, +}; + +enum pdu_error_type { + CORRUPT_DATA = 0, + INTERNAL_ERROR = 1, + NO_DATA_AVAIL = 2, + INVALID_REQUEST = 3, + UNSUPPORTED_PROTOCOL_VER = 4, + UNSUPPORTED_PDU_TYPE = 5, + WITHDRAWAL_OF_UNKNOWN_RECORD = 6, + DUPLICATE_ANNOUNCEMENT = 7, + PDU_TOO_BIG = 32 +}; + +static const char *str_pdu_error_type[] = { + [CORRUPT_DATA] = "Corrupt-Data", + [INTERNAL_ERROR] = "Internal-Error", + [NO_DATA_AVAIL] = "No-Data-Available", + [INVALID_REQUEST] = "Invalid-Request", + [UNSUPPORTED_PROTOCOL_VER] = "Unsupported-Protocol-Version", + [UNSUPPORTED_PDU_TYPE] = "Unsupported-PDU-Type", + [WITHDRAWAL_OF_UNKNOWN_RECORD]= "Withdrawal-Of-Unknown-Record", + [DUPLICATE_ANNOUNCEMENT] = "Duplicate-Announcement", + [PDU_TOO_BIG] = "PDU-Too-Big", +}; + +enum pdu_type { + SERIAL_NOTIFY = 0, + SERIAL_QUERY = 1, + RESET_QUERY = 2, + CACHE_RESPONSE = 3, + IPV4_PREFIX = 4, + RESERVED = 5, + IPV6_PREFIX = 6, + END_OF_DATA = 7, + CACHE_RESET = 8, + ROUTER_KEY = 9, + ERROR = 10, + PDU_TYPE_MAX +}; + +static const char *str_pdu_type_[] = { + [SERIAL_NOTIFY] = "Serial Notify", + [SERIAL_QUERY] = "Serial Query", + [RESET_QUERY] = "Reset Query", + [CACHE_RESPONSE] = "Cache Response", + [IPV4_PREFIX] = "IPv4 Prefix", + [RESERVED] = "Reserved", + [IPV6_PREFIX] = "IPv6 Prefix", + [END_OF_DATA] = "End of Data", + [CACHE_RESET] = "Cache Reset", + [ROUTER_KEY] = "Router Key", + [ERROR] = "Error" +}; + +static const char * const str_pdu_type(uint type) { + if (type < PDU_TYPE_MAX) + return str_pdu_type_[type]; + else + return "Undefined packet type"; +} + +/* + * 0 8 16 24 31 + * .-------------------------------------------. + * | Protocol | PDU | | + * | Version | Type | reserved = zero | + * | 0 or 1 | 0 - 10 | | + * +-------------------------------------------+ + * | | + * | Length >= 8 | + * | | + * `-------------------------------------------' */ +struct pdu_header { + u8 ver; + u8 type; + u16 reserved; + u32 len; +} PACKED; + +struct pdu_cache_response { + u8 ver; + u8 type; + u16 session_id; + u32 len; +} PACKED; + +struct pdu_serial_notify { + u8 ver; + u8 type; + u16 session_id; + u32 len; + u32 serial_num; +} PACKED; + +struct pdu_serial_query { + u8 ver; + u8 type; + u16 session_id; + u32 len; + u32 serial_num; +} PACKED; + +struct pdu_ipv4 { + u8 ver; + u8 type; + u16 reserved; + u32 len; + u8 flags; + u8 prefix_len; + u8 max_prefix_len; + u8 zero; + ip4_addr prefix; + u32 asn; +} PACKED; + +struct pdu_ipv6 { + u8 ver; + u8 type; + u16 reserved; + u32 len; + u8 flags; + u8 prefix_len; + u8 max_prefix_len; + u8 zero; + ip6_addr prefix; + u32 asn; +} PACKED; + +/* + * 0 8 16 24 31 + * .-------------------------------------------. + * | Protocol | PDU | | + * | Version | Type | Error Code | + * | 1 | 10 | | + * +-------------------------------------------+ + * | | + * | Length | + * | | + * +-------------------------------------------+ + * | | + * | Length of Encapsulated PDU | + * | | + * +-------------------------------------------+ + * | | + * ~ Copy of Erroneous PDU ~ + * | | + * +-------------------------------------------+ + * | | + * | Length of Error Text | + * | | + * +-------------------------------------------+ + * | | + * | Arbitrary Text | + * | of | + * ~ Error Diagnostic Message ~ + * | | + * `-------------------------------------------' */ +struct pdu_error { + u8 ver; + u8 type; + u16 error_code; + u32 len; + u32 len_enc_pdu; /* Length of Encapsulated PDU */ + byte rest[]; /* Copy of Erroneous PDU + * Length of Error Text + * Error Diagnostic Message */ +} PACKED; + +struct pdu_reset_query { + u8 ver; + u8 type; + u16 flags; + u32 len; +} PACKED; + +struct pdu_end_of_data_v0 { + u8 ver; + u8 type; + u16 session_id; + u32 len; + u32 serial_num; +} PACKED; + +struct pdu_end_of_data_v1 { + u8 ver; + u8 type; + u16 session_id; + u32 len; + u32 serial_num; + u32 refresh_interval; + u32 retry_interval; + u32 expire_interval; +} PACKED; + +static const size_t min_pdu_size[] = { + [SERIAL_NOTIFY] = sizeof(struct pdu_serial_notify), + [SERIAL_QUERY] = sizeof(struct pdu_serial_query), + [RESET_QUERY] = sizeof(struct pdu_reset_query), + [CACHE_RESPONSE] = sizeof(struct pdu_cache_response), + [IPV4_PREFIX] = sizeof(struct pdu_ipv4), + [RESERVED] = sizeof(struct pdu_header), + [IPV6_PREFIX] = sizeof(struct pdu_ipv6), + [END_OF_DATA] = sizeof(struct pdu_end_of_data_v0), + [CACHE_RESET] = sizeof(struct pdu_cache_response), + [ROUTER_KEY] = sizeof(struct pdu_header), /* FIXME */ + [ERROR] = 16, +}; + +static int rpki_send_error_pdu(struct rpki_cache *cache, const enum pdu_error_type error_code, const u32 err_pdu_len, const struct pdu_header *erroneous_pdu, const char *fmt, ...); + +static void +rpki_pdu_to_network_byte_order(struct pdu_header *pdu) +{ + pdu->reserved = htons(pdu->reserved); + pdu->len = htonl(pdu->len); + + switch (pdu->type) + { + case SERIAL_QUERY: + { + /* Note that a session_id is converted using converting header->reserved */ + struct pdu_serial_query *sq_pdu = (void *) pdu; + sq_pdu->serial_num = htonl(sq_pdu->serial_num); + break; + } + + case ERROR: + { + struct pdu_error *err = (void *) pdu; + u32 *err_text_len = (u32 *)(err->rest + err->len_enc_pdu); + *err_text_len = htonl(*err_text_len); + err->len_enc_pdu = htonl(err->len_enc_pdu); + break; + } + + case RESET_QUERY: + break; + + default: + bug("PDU type %s should not be sent by us", str_pdu_type(pdu->type)); + } +} + +static void +rpki_pdu_to_host_byte_order(struct pdu_header *pdu) +{ + /* The Router Key PDU has two one-byte fields instead of one two-bytes field. */ + if (pdu->type != ROUTER_KEY) + pdu->reserved = ntohs(pdu->reserved); + + pdu->len = ntohl(pdu->len); + + switch (pdu->type) + { + case SERIAL_NOTIFY: + { + /* Note that a session_id is converted using converting header->reserved */ + struct pdu_serial_notify *sn_pdu = (void *) pdu; + sn_pdu->serial_num = ntohl(sn_pdu->serial_num); + break; + } + + case END_OF_DATA: + { + /* Note that a session_id is converted using converting header->reserved */ + struct pdu_end_of_data_v0 *eod0 = (void *) pdu; + eod0->serial_num = ntohl(eod0->serial_num); /* Same either for version 1 */ + + if (pdu->ver == RPKI_VERSION_1) + { + struct pdu_end_of_data_v1 *eod1 = (void *) pdu; + eod1->expire_interval = ntohl(eod1->expire_interval); + eod1->refresh_interval = ntohl(eod1->refresh_interval); + eod1->retry_interval = ntohl(eod1->retry_interval); + } + break; + } + + case IPV4_PREFIX: + { + struct pdu_ipv4 *ipv4 = (void *) pdu; + ipv4->prefix = ip4_ntoh(ipv4->prefix); + ipv4->asn = ntohl(ipv4->asn); + break; + } + + case IPV6_PREFIX: + { + struct pdu_ipv6 *ipv6 = (void *) pdu; + ipv6->prefix = ip6_ntoh(ipv6->prefix); + ipv6->asn = ntohl(ipv6->asn); + break; + } + + case ERROR: + { + /* Note that a error_code is converted using converting header->reserved */ + struct pdu_error *err = (void *) pdu; + err->len_enc_pdu = ntohl(err->len_enc_pdu); + u32 *err_text_len = (u32 *)(err->rest + err->len_enc_pdu); + *err_text_len = htonl(*err_text_len); + break; + } + + case ROUTER_KEY: + /* Router Key PDU is not supported yet */ + + case SERIAL_QUERY: + case RESET_QUERY: + /* Serial/Reset Query are sent only in direction router to cache. + * We don't care here. */ + + case CACHE_RESPONSE: + case CACHE_RESET: + /* Converted with pdu->reserved */ + break; + } +} + +/** + * rpki_convert_pdu_back_to_network_byte_order - convert host-byte order PDU back to network-byte order + * @out: allocated memory for writing a converted PDU of size @in->len + * @in: host-byte order PDU + * + * Assumed: |A == ntoh(ntoh(A))| + */ +static struct pdu_header * +rpki_pdu_back_to_network_byte_order(struct pdu_header *out, const struct pdu_header *in) +{ + memcpy(out, in, in->len); + rpki_pdu_to_host_byte_order(out); + return out; +} + +static void +rpki_log_packet(struct rpki_cache *cache, const struct pdu_header *pdu, const enum rpki_transmit_type action) +{ + if (!(cache->p->p.debug & D_PACKETS)) + return; + + const char *str_type = str_pdu_type(pdu->type); + char detail[256]; + +#define SAVE(fn) \ + do { \ + if (fn < 0) \ + { \ + bsnprintf(detail + sizeof(detail) - 16, 16, "... <too long>)"); \ + goto detail_finished; \ + } \ + } while(0) \ + + switch (pdu->type) + { + case SERIAL_NOTIFY: + case SERIAL_QUERY: + SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u)", pdu->reserved, ((struct pdu_serial_notify *) pdu)->serial_num)); + break; + + case END_OF_DATA: + { + const struct pdu_end_of_data_v1 *eod = (void *) pdu; + if (eod->ver == RPKI_VERSION_1) + SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u, refresh: %us, retry: %us, expire: %us)", eod->session_id, eod->serial_num, eod->refresh_interval, eod->retry_interval, eod->expire_interval)); + else + SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u, serial number: %u)", eod->session_id, eod->serial_num)); + break; + } + + case CACHE_RESPONSE: + SAVE(bsnprintf(detail, sizeof(detail), "(session id: %u)", pdu->reserved)); + break; + + case IPV4_PREFIX: + { + const struct pdu_ipv4 *ipv4 = (void *) pdu; + SAVE(bsnprintf(detail, sizeof(detail), "(%I4/%u-%u AS%u)", ipv4->prefix, ipv4->prefix_len, ipv4->max_prefix_len, ipv4->asn)); + break; + } + + case IPV6_PREFIX: + { + const struct pdu_ipv6 *ipv6 = (void *) pdu; + SAVE(bsnprintf(detail, sizeof(detail), "(%I6/%u-%u AS%u)", ipv6->prefix, ipv6->prefix_len, ipv6->max_prefix_len, ipv6->asn)); + break; + } + + case ROUTER_KEY: + /* We don't support saving Router Key PDUs yet */ + SAVE(bsnprintf(detail, sizeof(detail), "(ignored)")); + break; + + case ERROR: + { + const struct pdu_error *err = (void *) pdu; + SAVE(bsnprintf(detail, sizeof(detail), "(%s", str_pdu_error_type[err->error_code])); + + /* Optional description of error */ + const u32 len_err_txt = *((u32 *) (err->rest + err->len_enc_pdu)); + if (len_err_txt > 0) + { + size_t expected_len = err->len_enc_pdu + len_err_txt + 16; + if (expected_len == err->len) + { + char txt[len_err_txt + 1]; + char *pdu_txt = (char *) err->rest + err->len_enc_pdu + 4; + bsnprintf(txt, sizeof(txt), "%s", pdu_txt); /* it's ensured that txt is ended with a null byte */ + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ": '%s'", txt)); + } + else + { + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ", malformed size")); + } + } + + /* Optional encapsulated erroneous packet */ + if (err->len_enc_pdu) + { + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ", %s packet:", str_pdu_type(((struct pdu_header *) err->rest)->type))); + if (err->rest + err->len_enc_pdu <= (byte *)err + err->len) + { + for (const byte *c = err->rest; c != err->rest + err->len_enc_pdu; c++) + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), " %02X", *c)); + } + } + + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), ")")); + break; + } + + default: + *detail = '\0'; + } +#undef SAVE + + detail_finished: + + if (action == RPKI_RECV) + { + CACHE_TRACE(D_PACKETS, cache, "Received %s packet %s", str_type, detail); + } + else + { + CACHE_TRACE(D_PACKETS, cache, "Sending %s packet %s", str_type, detail); + } + +#if defined(LOCAL_DEBUG) || defined(GLOBAL_DEBUG) + int seq = 0; + for(const byte *c = pdu; c != pdu + pdu->len; c++) + { + if ((seq % 4) == 0) + DBG("%2d: ", seq); + + DBG(" 0x%02X %-3u", *c, *c); + + if ((++seq % 4) == 0) + DBG("\n"); + } + if ((seq % 4) != 0) + DBG("\n"); +#endif +} + +static int +rpki_send_pdu(struct rpki_cache *cache, const void *pdu, const uint len) +{ + struct rpki_proto *p = cache->p; + sock *sk = cache->tr_sock->sk; + + rpki_log_packet(cache, pdu, RPKI_SEND); + + if (sk->tbuf != sk->tpos) + { + RPKI_WARN(p, "Old packet overwritten in TX buffer"); + } + + if (len > sk->tbsize) + { + RPKI_WARN(p, "%u bytes is too much for send", len); + ASSERT(0); + return RPKI_ERROR; + } + + memcpy(sk->tbuf, pdu, len); + rpki_pdu_to_network_byte_order((void *) sk->tbuf); + + if (!sk_send(sk, len)) + { + DBG("Cannot send just the whole data. It will be sent using a call of tx_hook()"); + } + + return RPKI_SUCCESS; +} + +/** + * rpki_check_receive_packet - make a basic validation of received RPKI PDU header + * @cache: cache connection instance + * @pdu: RPKI PDU in network byte order + * + * This function checks protocol version, PDU type, version and size. If all is all right then + * function returns |RPKI_SUCCESS| otherwise sends Error PDU and returns + * |RPKI_ERROR|. + */ +static int +rpki_check_receive_packet(struct rpki_cache *cache, const struct pdu_header *pdu) +{ + struct rpki_proto *p = cache->p; + int error = RPKI_SUCCESS; + u32 pdu_len = ntohl(pdu->len); + + /* + * Minimal and maximal allowed PDU size is treated in rpki_rx_hook() function. + * @header.len corresponds to number of bytes of @pdu and + * it is in range from RPKI_PDU_HEADER_LEN to RPKI_PDU_MAX_LEN bytes. + */ + + /* Do not handle error PDUs here, leave this task to rpki_handle_error_pdu() */ + if (pdu->ver != cache->version && pdu->type != ERROR) + { + /* If this is the first PDU we have received */ + if (cache->request_session_id) + { + if (pdu->type == SERIAL_NOTIFY) + { + /* + * The router MUST ignore any Serial Notify PDUs it might receive from + * the cache during this initial start-up period, regardless of the + * Protocol Version field in the Serial Notify PDU. + * (https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis-07#section-7) + */ + } + else if (cache->last_update == 0 + && pdu->ver >= RPKI_MIN_VERSION + && pdu->ver <= RPKI_MAX_VERSION + && pdu->ver < cache->version) + { + CACHE_TRACE(D_EVENTS, cache, "Downgrade session to %s from %u to %u version", rpki_get_cache_ident(cache), cache->version, pdu->ver); + cache->version = pdu->ver; + } + else + { + /* If this is not the first PDU we have received, something is wrong with + * the server implementation -> Error */ + rpki_send_error_pdu(cache, UNSUPPORTED_PROTOCOL_VER, pdu_len, pdu, "PDU with unsupported Protocol version received"); + return RPKI_ERROR; + } + } + } + + if ((pdu->type >= PDU_TYPE_MAX) || (pdu->ver == RPKI_VERSION_0 && pdu->type == ROUTER_KEY)) + { + rpki_send_error_pdu(cache, UNSUPPORTED_PDU_TYPE, pdu_len, pdu, "Unsupported PDU type %u received", pdu->type); + return RPKI_ERROR; + } + + if (pdu_len < min_pdu_size[pdu->type]) + { + rpki_send_error_pdu(cache, CORRUPT_DATA, pdu_len, pdu, "Received %s packet with %d bytes, but expected at least %d bytes", str_pdu_type(pdu->type), pdu_len, min_pdu_size[pdu->type]); + return RPKI_ERROR; + } + + return RPKI_SUCCESS; +} + +static int +rpki_handle_error_pdu(struct rpki_cache *cache, const struct pdu_error *pdu) +{ + switch (pdu->error_code) + { + case CORRUPT_DATA: + case INTERNAL_ERROR: + case INVALID_REQUEST: + case UNSUPPORTED_PDU_TYPE: + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + break; + + case NO_DATA_AVAIL: + rpki_cache_change_state(cache, RPKI_CS_ERROR_NO_DATA_AVAIL); + break; + + case UNSUPPORTED_PROTOCOL_VER: + CACHE_TRACE(D_PACKETS, cache, "Client uses unsupported protocol version"); + if (pdu->ver <= RPKI_MAX_VERSION && + pdu->ver >= RPKI_MIN_VERSION && + pdu->ver < cache->version) + { + CACHE_TRACE(D_EVENTS, cache, "Downgrading from protocol version %d to version %d", cache->version, pdu->ver); + cache->version = pdu->ver; + rpki_cache_change_state(cache, RPKI_CS_FAST_RECONNECT); + } + else + { + CACHE_TRACE(D_PACKETS, cache, "Got UNSUPPORTED_PROTOCOL_VER error PDU with invalid values, " \ + "current version: %d, PDU version: %d", cache->version, pdu->ver); + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + } + break; + + default: + CACHE_TRACE(D_PACKETS, cache, "Error unknown, server sent unsupported error code %u", pdu->error_code); + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + break; + } + + return RPKI_SUCCESS; +} + +static void +rpki_handle_serial_notify_pdu(struct rpki_cache *cache, const struct pdu_serial_notify *pdu) +{ + /* The router MUST ignore any Serial Notify PDUs it might receive from + * the cache during this initial start-up period, regardless of the + * Protocol Version field in the Serial Notify PDU. + * (https://tools.ietf.org/html/draft-ietf-sidr-rpki-rtr-rfc6810-bis-07#section-7) + */ + if (cache->request_session_id) + { + CACHE_TRACE(D_PACKETS, cache, "Ignore a Serial Notify packet during initial start-up period"); + return; + } + + /* XXX Serial number should be compared using method RFC 1982 (3.2) */ + if (cache->serial_num != pdu->serial_num) + rpki_cache_change_state(cache, RPKI_CS_SYNC_START); +} + +static int +rpki_handle_cache_response_pdu(struct rpki_cache *cache, const struct pdu_cache_response *pdu) +{ + if (cache->request_session_id) + { + if (cache->last_update != 0) + { + /* + * This isn't the first sync and we already received records. This point + * is after Reset Query and before importing new records from cache + * server. We need to load new ones and kick out missing ones. So start + * a refresh cycle. + */ + if (cache->p->roa4_channel) + rt_refresh_begin(cache->p->roa4_channel->table, cache->p->roa4_channel); + if (cache->p->roa6_channel) + rt_refresh_begin(cache->p->roa6_channel->table, cache->p->roa6_channel); + + cache->p->refresh_channels = 1; + } + cache->session_id = pdu->session_id; + cache->request_session_id = 0; + } + else + { + if (cache->session_id != pdu->session_id) + { + byte tmp[pdu->len]; + const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu); + rpki_send_error_pdu(cache, CORRUPT_DATA, pdu->len, hton_pdu, "Wrong session_id %u in Cache Response PDU", pdu->session_id); + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + return RPKI_ERROR; + } + } + + rpki_cache_change_state(cache, RPKI_CS_SYNC_RUNNING); + return RPKI_SUCCESS; +} + +/** + * rpki_prefix_pdu_2_net_addr - convert IPv4/IPv6 Prefix PDU into net_addr_union + * @pdu: host byte order IPv4/IPv6 Prefix PDU + * @n: allocated net_addr_union for save ROA + * + * This function reads ROA data from IPv4/IPv6 Prefix PDU and + * write them into net_addr_roa4 or net_addr_roa6 data structure. + */ +static net_addr_union * +rpki_prefix_pdu_2_net_addr(const struct pdu_header *pdu, net_addr_union *n) +{ + /* + * Note that sizeof(net_addr_roa6) > sizeof(net_addr) + * and thence we must use net_addr_union and not only net_addr + */ + + if (pdu->type == IPV4_PREFIX) + { + const struct pdu_ipv4 *ipv4 = (void *) pdu; + n->roa4.type = NET_ROA4; + n->roa4.length = sizeof(net_addr_roa4); + n->roa4.prefix = ipv4->prefix; + n->roa4.asn = ipv4->asn; + n->roa4.pxlen = ipv4->prefix_len; + n->roa4.max_pxlen = ipv4->max_prefix_len; + } + else + { + const struct pdu_ipv6 *ipv6 = (void *) pdu; + n->roa6.type = NET_ROA6; + n->roa6.length = sizeof(net_addr_roa6); + n->roa6.prefix = ipv6->prefix; + n->roa6.asn = ipv6->asn; + n->roa6.pxlen = ipv6->prefix_len; + n->roa6.max_pxlen = ipv6->max_prefix_len; + } + + return n; +} + +static int +rpki_handle_prefix_pdu(struct rpki_cache *cache, const struct pdu_header *pdu) +{ + const enum pdu_type type = pdu->type; + ASSERT(type == IPV4_PREFIX || type == IPV6_PREFIX); + + net_addr_union addr = {}; + rpki_prefix_pdu_2_net_addr(pdu, &addr); + + struct channel *channel = NULL; + + if (type == IPV4_PREFIX) + channel = cache->p->roa4_channel; + if (type == IPV6_PREFIX) + channel = cache->p->roa6_channel; + + if (!channel) + { + CACHE_TRACE(D_ROUTES, cache, "Skip %N, missing %s channel", &addr, (type == IPV4_PREFIX ? "roa4" : "roa6"), addr); + return RPKI_ERROR; + } + + cache->last_rx_prefix = now; + + /* A place for 'flags' is same for both data structures pdu_ipv4 or pdu_ipv6 */ + struct pdu_ipv4 *pfx = (void *) pdu; + if (pfx->flags & RPKI_ADD_FLAG) + rpki_table_add_roa(cache, channel, &addr); + else + rpki_table_remove_roa(cache, channel, &addr); + + return RPKI_SUCCESS; +} + +static uint +rpki_check_interval(struct rpki_cache *cache, const char *(check_fn)(uint), uint interval) +{ + if (check_fn(interval)) + { + RPKI_WARN(cache->p, "%s, received %u seconds", check_fn(interval), interval); + return 0; + } + return 1; +} + +static void +rpki_handle_end_of_data_pdu(struct rpki_cache *cache, const struct pdu_end_of_data_v1 *pdu) +{ + const struct rpki_config *cf = (void *) cache->p->p.cf; + + if (pdu->session_id != cache->session_id) + { + byte tmp[pdu->len]; + const struct pdu_header *hton_pdu = rpki_pdu_back_to_network_byte_order((void *) tmp, (const void *) pdu); + rpki_send_error_pdu(cache, CORRUPT_DATA, pdu->len, hton_pdu, "Received Session ID %u, but expected %u", pdu->session_id, cache->session_id); + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + return; + } + + if (pdu->ver == RPKI_VERSION_1) + { + if (!cf->keep_refresh_interval && rpki_check_interval(cache, rpki_check_refresh_interval, pdu->refresh_interval)) + cache->refresh_interval = pdu->refresh_interval; + + if (!cf->keep_retry_interval && rpki_check_interval(cache, rpki_check_retry_interval, pdu->retry_interval)) + cache->retry_interval = pdu->retry_interval; + + if (!cf->keep_expire_interval && rpki_check_interval(cache, rpki_check_expire_interval, pdu->expire_interval)) + cache->expire_interval = pdu->expire_interval; + + CACHE_TRACE(D_EVENTS, cache, "New interval values: " + "refresh: %s%us, " + "retry: %s%us, " + "expire: %s%us", + (cf->keep_refresh_interval ? "keeps " : ""), cache->refresh_interval, + (cf->keep_retry_interval ? "keeps " : ""), cache->retry_interval, + (cf->keep_expire_interval ? "keeps " : ""), cache->expire_interval); + } + + if (cache->p->refresh_channels) + { + cache->p->refresh_channels = 0; + if (cache->p->roa4_channel) + rt_refresh_end(cache->p->roa4_channel->table, cache->p->roa4_channel); + if (cache->p->roa6_channel) + rt_refresh_end(cache->p->roa6_channel->table, cache->p->roa6_channel); + } + + cache->last_update = now; + cache->serial_num = pdu->serial_num; + rpki_cache_change_state(cache, RPKI_CS_ESTABLISHED); +} + +/** + * rpki_rx_packet - process a received RPKI PDU + * @cache: RPKI connection instance + * @pdu: a RPKI PDU in network byte order + */ +static void +rpki_rx_packet(struct rpki_cache *cache, struct pdu_header *pdu) +{ + struct rpki_proto *p = cache->p; + + if (rpki_check_receive_packet(cache, pdu) == RPKI_ERROR) + { + rpki_cache_change_state(cache, RPKI_CS_ERROR_FATAL); + return; + } + + rpki_pdu_to_host_byte_order(pdu); + rpki_log_packet(cache, pdu, RPKI_RECV); + + switch (pdu->type) + { + case RESET_QUERY: + case SERIAL_QUERY: + RPKI_WARN(p, "Received a %s packet that is destined for cache server", str_pdu_type(pdu->type)); + break; + + case SERIAL_NOTIFY: + /* This is a signal to synchronize with the cache server just now */ + rpki_handle_serial_notify_pdu(cache, (void *) pdu); + break; + + case CACHE_RESPONSE: + rpki_handle_cache_response_pdu(cache, (void *) pdu); + break; + + case IPV4_PREFIX: + case IPV6_PREFIX: + rpki_handle_prefix_pdu(cache, pdu); + break; + + case END_OF_DATA: + rpki_handle_end_of_data_pdu(cache, (void *) pdu); + break; + + case CACHE_RESET: + /* Cache cannot provide an incremental update. */ + rpki_cache_change_state(cache, RPKI_CS_NO_INCR_UPDATE_AVAIL); + break; + + case ERROR: + rpki_handle_error_pdu(cache, (void *) pdu); + break; + + case ROUTER_KEY: + /* TODO: Implement Router Key PDU handling */ + break; + + default: + CACHE_TRACE(D_PACKETS, cache, "Received unsupported type (%u)", pdu->type); + }; +} + +int +rpki_rx_hook(struct birdsock *sk, int size) +{ + struct rpki_cache *cache = sk->data; + struct rpki_proto *p = cache->p; + + byte *pkt_start = sk->rbuf; + byte *end = pkt_start + size; + + DBG("rx hook got %d bytes \n", size); + + while (end >= pkt_start + RPKI_PDU_HEADER_LEN) + { + struct pdu_header *pdu = (void *) pkt_start; + u32 pdu_size = ntohl(pdu->len); + + if (pdu_size < RPKI_PDU_HEADER_LEN || pdu_size > RPKI_PDU_MAX_LEN) + { + RPKI_WARN(p, "Received invalid packet length %u, purge the whole receiving buffer", pdu_size); + return 1; /* Purge recv buffer */ + } + + if (end < pkt_start + pdu_size) + break; + + rpki_rx_packet(cache, pdu); + + /* It is possible that bird socket was freed/closed */ + if (p->p.proto_state == PS_DOWN || sk != cache->tr_sock->sk) + return 0; + + pkt_start += pdu_size; + } + + if (pkt_start != sk->rbuf) + { + CACHE_DBG(cache, "Move %u bytes of a memory at the start of buffer", end - pkt_start); + memmove(sk->rbuf, pkt_start, end - pkt_start); + sk->rpos = sk->rbuf + (end - pkt_start); + } + + return 0; /* Not purge sk->rbuf */ +} + +void +rpki_err_hook(struct birdsock *sk, int error_num) +{ + struct rpki_cache *cache = sk->data; + + if (error_num) + { + /* sk->err may contains a SSH error description */ + if (sk->err) + CACHE_TRACE(D_EVENTS, cache, "Lost connection: %s", sk->err); + else + CACHE_TRACE(D_EVENTS, cache, "Lost connection: %M", error_num); + } + else + { + CACHE_TRACE(D_EVENTS, cache, "The other side closed a connection"); + } + + + rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT); +} + +static int +rpki_fire_tx(struct rpki_cache *cache) +{ + sock *sk = cache->tr_sock->sk; + + uint bytes_to_send = sk->tpos - sk->tbuf; + DBG("Sending %u bytes", bytes_to_send); + return sk_send(sk, bytes_to_send); +} + +void +rpki_tx_hook(sock *sk) +{ + struct rpki_cache *cache = sk->data; + + while (rpki_fire_tx(cache) > 0) + ; +} + +void +rpki_connected_hook(sock *sk) +{ + struct rpki_cache *cache = sk->data; + + CACHE_TRACE(D_EVENTS, cache, "Connected"); + proto_notify_state(&cache->p->p, PS_UP); + + sk->rx_hook = rpki_rx_hook; + sk->tx_hook = rpki_tx_hook; + + rpki_cache_change_state(cache, RPKI_CS_SYNC_START); +} + +/** + * rpki_send_error_pdu - send RPKI Error PDU + * @cache: RPKI connection instance + * @error_code: PDU Error type + * @err_pdu_len: length of @erroneous_pdu + * @erroneous_pdu: optional network byte-order PDU that invokes Error by us or NULL + * @fmt: optional description text of error or NULL + * @args: optional arguments for @fmt + * + * This function prepares Error PDU and sends it to a cache server. + */ +static int +rpki_send_error_pdu(struct rpki_cache *cache, const enum pdu_error_type error_code, const u32 err_pdu_len, const struct pdu_header *erroneous_pdu, const char *fmt, ...) +{ + va_list args; + char msg[128]; + + /* Size including the terminating null byte ('\0') */ + int msg_len = 0; + + /* Don't send errors for erroneous error PDUs */ + if (err_pdu_len >= 2) + { + if (erroneous_pdu->type == ERROR) + return RPKI_SUCCESS; + } + + if (fmt) + { + va_start(args, fmt); + msg_len = bvsnprintf(msg, sizeof(msg), fmt, args) + 1; + } + + u32 pdu_size = 16 + err_pdu_len + msg_len; + byte pdu[pdu_size]; + memset(pdu, 0, sizeof(pdu)); + + struct pdu_error *e = (void *) pdu; + e->ver = cache->version; + e->type = ERROR; + e->error_code = error_code; + e->len = pdu_size; + + e->len_enc_pdu = err_pdu_len; + if (err_pdu_len > 0) + memcpy(e->rest, erroneous_pdu, err_pdu_len); + + *((u32 *)(e->rest + err_pdu_len)) = msg_len; + if (msg_len > 0) + memcpy(e->rest + err_pdu_len + 4, msg, msg_len); + + return rpki_send_pdu(cache, pdu, pdu_size); +} + +int +rpki_send_serial_query(struct rpki_cache *cache) +{ + struct pdu_serial_query pdu = { + .ver = cache->version, + .type = SERIAL_QUERY, + .session_id = cache->session_id, + .len = sizeof(pdu), + .serial_num = cache->serial_num + }; + + if (rpki_send_pdu(cache, &pdu, sizeof(pdu)) != RPKI_SUCCESS) + { + rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT); + return RPKI_ERROR; + } + + return RPKI_SUCCESS; +} + +int +rpki_send_reset_query(struct rpki_cache *cache) +{ + struct pdu_reset_query pdu = { + .ver = cache->version, + .type = RESET_QUERY, + .len = sizeof(pdu), + }; + + if (rpki_send_pdu(cache, &pdu, sizeof(pdu)) != RPKI_SUCCESS) + { + rpki_cache_change_state(cache, RPKI_CS_ERROR_TRANSPORT); + return RPKI_ERROR; + } + + return RPKI_SUCCESS; +} |