diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000..01f7262a --- /dev/null +++ b/.mailmap @@ -0,0 +1,2 @@ +Maria Matejka +Maria Matejka diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..8651b145 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,109 @@ +# Contributing to BIRD + +We welcome a broad range of contributions to BIRD with some limitations and +caveats. + +BIRD is highly optimized for performance in both memory and computation time. +We generally don't accept obviously inefficient code and even though the +quality of the existing codebase quite varies, there should be good reasons +why to commit something slow or greedy. + +There are several basic rules for contributing: + +- your branch must have understandable commit messages +- your branch must be either: + - rooted in the current thread-next, aiming for inclusion in BIRD 3 + - or rooted in the master branch; in this case, we may refuse your patch + if it's completely unmergeable with thread-next +- when incorporating proposed fixes, you may have to rebase your branch +- please add automatic tests (see below) +- upfront and continuous consultation with the development team gives you a + fast track for merging +- don't forget to update documentation + +## Security issues + +Please contact us on bird-support@network.cz for private disclosure of any +security issues. This includes any crash in or related to filters, induced by +CLI or by receiving a malformed message by a protocol. + +## How to contribute + +You can either send a patch (prepared by git format-patch) to our mailing-list +bird-users@network.cz, or you can send just a link to your repository and the +commit hash you're contributing. + +## What if your contribution isn't mergable + +If your code needs minor updates to align with our standards / taste, we'll +just do these modifications ourselves and either add these as a separate commit +or just update your commit noting this fact in the commit message. + +If your code has some major flaws, misses the point or introduces another +problem (e.g. performance issues), we'll refuse your patch. Then we'll either +try to tell you how we prefer to reach the goal, or we may reimplement your +ideas ourselves. We'll mention your original contribution in the commit message. + +## Specific kinds of contributions + +### Substantial updates + +If you feel like the BIRD internals need some major changes and you wish to +implement it, please contact the development team first. We're (as of May 2024) +developing two versions at once and we have some raw thoughts about BIRD's future +which we haven't published yet. + +Beware that BIRD is more convoluted inside than it looks like on the surface, +and in many places the learning curve is _very_ steep. + +### New protocol implementations + +We generally welcome broadening of BIRD capabilities. Upfront consultation is +very much appreciated to align all parties on the development principles, +internal APIs, coding style and more. + +### Refactoring and reformatting + +Please don't send us _any_ refactoring proposals without previous explicit approval. + +### Programmer's documentation, user documentation or tutorials + +We welcome updates to enhance the documentation, including the algorithmic +principles, internal libraries and API. We keep our right to reject low quality +contributions altogether. + +### Minor changes + +Feel free to propose minor fixes in any part of BIRD. + +## Testing + +There is another repository, https://gitlab.nic.cz/labs/bird-tools.git, where +we store our automatic tests in the netlab/ directory. This repository is quite +messy and you may need some help with it. We're planning to move the Netlab +suite into the main git repository; after we do that, we'll require every +contribution to add tests (if applicable, of course). + +## Crediting policy + +The credits are scattered over all the source code files; in the commentary +section, you may find typically the original authors of these files or some +major contributors who felt like adding their names there. Overall, if you feel +like your name should be there, include this change in your commits please. + +If your name should be changed, please do that change in the source code files. +If your name should be changed in the displayed git commit author / commiter +logs, please submit a patch on the `.mailmap` file. + +We are planning to centralize the credits one day; we'll then update this file +accordingly. + +## Meta + +If some of these rules are breached, you may complain either at the mailing +list, or directly to CZ.NIC who is currently BIRD's maintainer. + +If we don't reply within 3 weeks, please ping us. We don't intend to ghost you, +we are just overloaded. + +This contributing policy also applies to itself. diff --git a/README b/README index 6ff691e8..32e60e5e 100644 --- a/README +++ b/README @@ -68,12 +68,23 @@ User support If you want to help us debugging, enhancing and porting BIRD or just lurk around to see what's going to develop, feel free to subscribe to the BIRD users mailing list bird-users@network.cz, just send `subscribe' to -bird-request@network.cz. Bug reports, suggestions, feature requests and -code are welcome! We don't use gitlab issues for reporting, sorry. +bird-request@network.cz. Subscribe: http://bird.network.cz/mailman/listinfo/bird-users/ Archive: http://bird.network.cz/pipermail/bird-users/ +Please don't send security issues to the mailing-list, contact us instead at +bird-support@network.cz which is a private e-mail address where you also can +get commercial support for your BIRD deployment. + +We don't use our gitlab issues for reporting but we're partially tracking +the core developent team's work there publicly. + +Contributing +============ + +Please see the CONTRIBUTING.md file to find how to contribute to BIRD. + Licence ======= diff --git a/bird-gdb.py b/bird-gdb.py index b277cb0a..663ad687 100644 --- a/bird-gdb.py +++ b/bird-gdb.py @@ -28,7 +28,7 @@ class BIRDFValPrinter(BIRDPrinter): "T_ENUM_SCOPE": "i", "T_ENUM_RTD": "i", "T_ENUM_ROA": "i", - "T_ENUM_NETTYPE": "i", + "T_ENUM_NET_TYPE": "i", "T_ENUM_RA_PREFERENCE": "i", "T_ENUM_AF": "i", "T_IP": "ip", diff --git a/conf/cf-lex.l b/conf/cf-lex.l index a1d62d3a..d192adf4 100644 --- a/conf/cf-lex.l +++ b/conf/cf-lex.l @@ -715,7 +715,7 @@ cf_lex_symbol(const char *data) int val = sym->keyword->value; if (val > 0) return val; cf_lval.i = -val; - return ENUM; + return ENUM_TOKEN; } case SYM_METHOD: return (sym->method->arg_num > 1) ? CF_SYM_METHOD_ARGS : CF_SYM_METHOD_BARE; diff --git a/conf/conf.c b/conf/conf.c index 9f259691..cfe484cb 100644 --- a/conf/conf.c +++ b/conf/conf.c @@ -589,6 +589,7 @@ order_shutdown(int gr) init_list(&c->mpls_domains); init_list(&c->symbols); obstacle_target_init(&c->obstacles, &c->obstacles_cleared, c->pool, "Config"); + c->cli = (struct cli_config_list) {}; memset(c->def_tables, 0, sizeof(c->def_tables)); c->shutdown = 1; c->gr_down = gr; diff --git a/conf/conf.h b/conf/conf.h index 9a06ad4d..03ef4ef8 100644 --- a/conf/conf.h +++ b/conf/conf.h @@ -15,6 +15,7 @@ #include "lib/resource.h" #include "lib/obstacle.h" #include "lib/timer.h" +#include "lib/tlists.h" /* Configuration structure */ struct config { @@ -26,6 +27,7 @@ struct config { list logfiles; /* Configured log files (sysdep) */ list tests; /* Configured unit tests (f_bt_test_suite) */ list symbols; /* Configured symbols in config order */ + TLIST_STRUCT_DEF(cli_config, struct cli_config) cli; /* Configured CLI sockets */ struct rfile *mrtdump_file; /* Configured MRTDump file */ const char *syslog_name; /* Name used for syslog (NULL -> no syslog) */ diff --git a/conf/confbase.Y b/conf/confbase.Y index 56dd15e7..0eafdb14 100644 --- a/conf/confbase.Y +++ b/conf/confbase.Y @@ -100,7 +100,8 @@ CF_DECLS struct f_prefix px; struct proto_spec ps; struct channel_limit cl; - struct timeformat *tf; + struct timeformat tf; + struct timeformat *tfp; struct settle_config settle; struct adata *ad; const struct adata *bs; @@ -110,7 +111,7 @@ CF_DECLS %token END CLI_MARKER INVALID_TOKEN ELSECOL DDOT %token GEQ LEQ NEQ AND OR IMP %token PO PC -%token NUM ENUM +%token NUM ENUM_TOKEN %token IP4 %token IP6 %token VPN_RD @@ -124,7 +125,7 @@ CF_DECLS %type settle %type ipa net_ip6_slash %type net_ip4_ net_ip4 net_ip6_ net_ip6 net_ip_ net_ip net_or_ipa -%type net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ net_ip6_sadr_ net_mpls_ +%type net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ net_ip6_sadr_ net_mpls_ net_aspa_ %type label_stack_start label_stack %type text opttext @@ -325,6 +326,12 @@ net_mpls_: MPLS NUM net_fill_mpls($$, $2); } +net_aspa_: ASPA NUM +{ + $$ = cfg_alloc(sizeof(net_addr_aspa)); + net_fill_aspa($$, $2); +} + net_ip_: net_ip4_ | net_ip6_ ; net_vpn_: net_vpn4_ | net_vpn6_ ; net_roa_: net_roa4_ | net_roa6_ ; @@ -336,6 +343,7 @@ net_: | net_flow_ | net_ip6_sadr_ | net_mpls_ + | net_aspa_ ; diff --git a/conf/gen_keywords.m4 b/conf/gen_keywords.m4 index 5667ceaa..1d1c0c91 100644 --- a/conf/gen_keywords.m4 +++ b/conf/gen_keywords.m4 @@ -34,10 +34,11 @@ m4_define(CF_CLI, `CF_KEYWORDS(m4_translit($1, [[ ]], [[,]])) # Enums are translated to C initializers: use CF_ENUM(typename, prefix, values) # For different prefix: CF_ENUM_PX(typename, external prefix, C prefix, values) +# The typename is converted to a keyword by removing T_ENUM_ prefix m4_define(CF_enum, `m4_divert(1){ "CF_enum_prefix_ext[[]]$1", -((CF_enum_type<<16) | CF_enum_prefix_int[[]]$1) }, m4_divert(-1)') -m4_define(CF_ENUM, `m4_define([[CF_enum_type]],$1)m4_define([[CF_enum_prefix_ext]],$2)m4_define([[CF_enum_prefix_int]],$2)CF_iterate([[CF_enum]], [[m4_shift(m4_shift($@))]])DNL') -m4_define(CF_ENUM_PX, `m4_define([[CF_enum_type]],$1)m4_define([[CF_enum_prefix_ext]],$2)m4_define([[CF_enum_prefix_int]],$3)CF_iterate([[CF_enum]], [[m4_shift(m4_shift(m4_shift($@)))]])DNL') +m4_define(CF_ENUM, `CF_keywd(m4_substr($1, 7))m4_define([[CF_enum_type]],$1)m4_define([[CF_enum_prefix_ext]],$2)m4_define([[CF_enum_prefix_int]],$2)CF_iterate([[CF_enum]], [[m4_shift(m4_shift($@))]])DNL') +m4_define(CF_ENUM_PX, `CF_keywd(m4_substr($1, 7))m4_define([[CF_enum_type]],$1)m4_define([[CF_enum_prefix_ext]],$2)m4_define([[CF_enum_prefix_int]],$3)CF_iterate([[CF_enum]], [[m4_shift(m4_shift(m4_shift($@)))]])DNL') # After all configuration templates end, we generate the keyword list m4_m4wrap(` diff --git a/conf/gen_parser.m4 b/conf/gen_parser.m4 index e64394d1..49c8c71c 100644 --- a/conf/gen_parser.m4 +++ b/conf/gen_parser.m4 @@ -29,6 +29,8 @@ m4_define(CF_END, `m4_divert(-1)') m4_define(CF_itera, `m4_ifelse($#, 1, [[CF_iter($1)]], [[CF_iter($1)[[]]CF_itera(m4_shift($@))]])') m4_define(CF_iterate, `m4_define([[CF_iter]], m4_defn([[$1]]))CF_itera($2)') +m4_define(CF_append, `m4_define([[$1]], m4_ifdef([[$1]], m4_defn([[$1]])[[$3]])[[$2]])') + # Keywords act as untyped %token m4_define(CF_keywd, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)m4_define([[CF_toks]],CF_toks $1)]])') m4_define(CF_KEYWORDS, `m4_define([[CF_toks]],[[]])CF_iterate([[CF_keywd]], [[$@]])m4_ifelse(CF_toks,,,%token[[]]CF_toks @@ -46,8 +48,10 @@ m4_define(CF_CLI_OPT, `') m4_define(CF_CLI_HELP, `') # ENUM declarations are ignored -m4_define(CF_ENUM, `') -m4_define(CF_ENUM_PX, `') +m4_define(CF_token, `m4_ifdef([[CF_tok_$1]],,[[m4_define([[CF_tok_$1]],1)%token $1]])') +m4_define(CF_enum, `CF_append([[CF_enum_type]],[[$1 { $$ = $2; }]],[[ | ]])CF_token($1)') +m4_define(CF_ENUM, `CF_enum(m4_substr($1, 7), $1)') +m4_define(CF_ENUM_PX, `CF_enum(m4_substr($1, 7), $1)') # After all configuration templates end, we finally generate the grammar file. m4_m4wrap(` @@ -58,7 +62,11 @@ m4_undivert(1)DNL m4_undivert(2)DNL +%type enum_type + %% +enum_type: CF_enum_type; + m4_undivert(3)DNL %% diff --git a/configure.ac b/configure.ac index 25e1b159..e086019f 100644 --- a/configure.ac +++ b/configure.ac @@ -114,7 +114,7 @@ AC_SEARCH_LIBS([clock_gettime], [rt posix4], AC_CANONICAL_HOST # Store this value because ac_test_CFLAGS is overwritten by AC_PROG_CC -if test "$ac_test_CFLAGS" != set ; then +if ! test "$ac_test_CFLAGS" ; then bird_cflags_default=yes fi diff --git a/doc/bird.sgml b/doc/bird.sgml index 3e7ae9dd..d20d4309 100644 --- a/doc/bird.sgml +++ b/doc/bird.sgml @@ -125,11 +125,11 @@ Anyway, it will probably work well also on older systems. and Perl, installing BIRD should be as easy as: - ./configure - make - make install - vi /usr/local/etc/bird.conf - bird +./configure +make +make install +vi /usr/local/etc/bird.conf +bird

You can use ./configure --help to get a list of configure @@ -992,7 +992,7 @@ agreement").

You can use the command-line client birdc to talk with a running -BIRD. Communication is done using a Many commands have the Configuration +

Here is a brief list of supported functions: +

By default, BIRD opens It's also possible to configure additional remote control sockets in the +configuration file by The remote control socket can be also set as restricted by +Usage +

Here is a brief list of supported functions. + +

Note: Many commands have the @@ -1465,6 +1490,13 @@ This argument can be omitted if there exists only a single instance. pipe protocol, both directions are always reloaded together (

The filter language supports common integer operators (+,-,*,/), -parentheses -

There is one operator related to ROA infrastructure - route origin validation for a -given network prefix. The basic usage is roa_check(, which -checks the current route (which should be from BGP to have AS_PATH argument) in -the specified ROA table and returns ROA_UNKNOWN if there is no relevant ROA, -ROA_VALID if there is a matching ROA, or ROA_INVALID if there are some relevant -ROAs but none of them match. There is also an extended variant -roa_check(, which allows to specify a -prefix and an ASN as arguments. +

Logical operations include unary not ( +

Special operators include ( + element and set of elements of the same type (returning true if + element is contained in the given set) + two strings (returning true if the first string matches a shell-like + pattern stored in the second string) + IP and prefix (returning true if IP is within the range defined by + that prefix) + prefix and prefix (returning true if the first prefix is more specific + than the second one) + bgppath and bgpmask (returning true if the path matches the mask) + number and bgppath (returning true if the number is in the path) + bgppath and int (number) set (returning true if any ASN from the + path is in the set) + pair/quad and clist (returning true if the pair/quad is element of + the clist) + clist and pair/quad set (returning true if there is an element of the + clist that is also a member of the pair/quad set). + + +

There are also operators related to RPKI infrastructure used to run + route origin validation and (draft) AS path validation. + + + roa_check( checks the current route in the specified + ROA table and returns ROA_UNKNOWN, ROA_INVALID or ROA_VALID, + if the validation result is unknown, invalid, or valid, respectively. The result is + valid if there is a matching ROA, it is invalid if there is either matching ROA + with a different ASN, or any covering ROA with shorter maximal prefix length. + + roa_check( is an explicit version + of the ROA check if the user for whatever reason needs to check a different prefix + or different ASN than the default one. The equivalent call of the short variant + is roa_check( and it is faster + to call the short variant. + + aspa_check_downstream( checks the current route + in the specified ASPA table and returns ASPA_UNKNOWN, ASPA_INVALID, + or ASPA_VALID if the validation result is unknown, invalid, or valid, + respectively. The result is valid if there is a full coverage of matching + ASPA records according to the Algorithm for Downstream Paths by the (draft). + This operator is not present if BGP is not compiled in. + + aspa_check_upstream( checks the current route + in the specified ASPA table as the former operator, but it applies the + (stricter) Algorithm for Upstream Paths by the (draft). + This operator is not present if BGP is not compiled in. + + aspa_check( is + an explicit version of the former two ASPA check operators. The equivalent + of aspa_check_downstream is aspa_check( + and for aspa_check_upstream it is + aspa_check(. + Note: the ASPA check does not include the local ASN in the AS path. + Also, ASPA_INVALID is returned for an empty AS path + or for AS path containing CONFED_SET or CONFED_SEQUENCE blocks, + as the (draft) stipulates. + + +

The following example checks for ROA and ASPA on routes from a customer: + + +roa6 table r6; +aspa table at; +attribute int valid_roa; +attribute int valid_aspa; + +filter customer_check { + case roa_check(r6) { + ROA_INVALID: reject "Invalid ROA"; + ROA_VALID: valid_roa = 1; + } + + case aspa_check_upstream(at) { + ASPA_INVALID: reject "Invalid ASPA"; + ASPA_VALID: valid_aspa = 1; + } + + accept; +} + Control structures

The case is similar to case from Pascal. Syntax is case -. The expression after case can be of any type which can be -on the left side of the ˜ operator and anything that could be a member of -a set is allowed before matches one of the matches -neither of the . +The expression after case can be of any type that could be a member of +a set, while the matches one +of the matches neither of the Here is example that uses BIRD implements basic BFD behavior as defined in (some -advanced features like the echo mode or authentication are not implemented), IP -transport for BFD as defined in and and -interaction with client protocols as defined in . +advanced features like the echo mode are not implemented), IP transport for BFD +as defined in and and interaction with client +protocols as defined in .

BFD packets are sent with a dynamic source port number. Linux systems use by default a bit different dynamic port range than the IANA approved one @@ -2534,6 +2628,8 @@ milliseconds. protocol bfd [<name>] { accept [ipv4|ipv6] [direct|multihop]; + strict bind <switch>; + zero udp6 checksum rx <switch>; interface <interface pattern> { interval <time>; min rx interval <time>; @@ -2583,6 +2679,14 @@ protocol bfd [<name>] { in cases like running multiple BIRD instances on a machine, each handling a different set of interfaces. Default: disabled. +

The same protocol, since version 2, also receives and maintains a set +of ASPAs. You can then validate AS paths using function Supported transports

@@ -5690,22 +5797,28 @@ define more RPKI protocols generally. protocol rpki [<name>] { roa4 { table <tab>; }; roa6 { table <tab>; }; + aspa { table <tab>; }; remote <ip> | "<domain>" [port <num>]; port <num>; local address <ip>; refresh [keep] <num>; retry [keep] <num>; expire [keep] <num>; - transport tcp; + transport tcp { + authentication none|md5; + password "<text>"; + }; transport ssh { bird private key "</path/to/id_rsa>"; remote public key "</path/to/known_host>"; user "<name>"; }; + max version 2; + min version 2; } -

Alse note that you have to specify the ROA channel. If you want to import +

Alse note that you have to specify the ROA and ASPA channels. If you want to import only IPv4 prefixes you have to specify only roa4 channel. Similarly with IPv6 prefixes only. If you want to fetch both IPv4 and even IPv6 ROAs you have to specify both channels. @@ -5752,15 +5865,37 @@ specify both channels. instead. This may be useful for implementing loose RPKI check for blackholes. Default: disabled. - transport tcp Unprotected transport over TCP. It's a default - transport. Should be used only on secure private networks. - Default: tcp + min version + Minimal allowed version of the RTR protocol. BIRD will refuse to + downgrade a connection below this version and drop the session instead. + Default: 0 + + max version + Maximal allowed version of the RTR protocol. BIRD will start with this + version. Use this option if sending version 2 to your cache causes + problems. Default: 2 + + transport tcp { Transport over + TCP, it's the default transport. Cannot be combined with a SSH transport. + Default: TCP, no authentication. transport ssh { It enables a SSHv2 transport encryption. Cannot be combined with a TCP transport. Default: off +TCP transport options +

+ + authentication none|md5 + Select authentication method to be used. ). + Default: no authentication. + + password "text" + Use this password for TCP-MD5 authentication of the RPKI-To-Router session. + + SSH transport options

@@ -5966,6 +6101,13 @@ options (The ROA config is just route with no nexthop. +Autonomous System Provider Authorization + +

The ASPA config is route aspa with no nexthop. + The first ASN is client and the following are a list of providers. + For a transit, you can also write route aspa to get + the no-provider ASPA. + Flowspec bfd_neigh_local @@ -53,6 +53,7 @@ bfd_proto_item: | MULTIHOP bfd_multihop | NEIGHBOR bfd_neighbor | STRICT BIND bool { BFD_CFG->strict_bind = $3; } + | ZERO UDP6 CHECKSUM RX bool { BFD_CFG->zero_udp6_checksum_rx = $5; } ; bfd_proto_opts: @@ -185,6 +186,52 @@ bfd_neighbor: ipa bfd_neigh_iface bfd_neigh_local bfd_neigh_multihop }; +/* BFD options */ + +bfd_item: + INTERVAL expr_us { this_bfd_opts->min_rx_int = this_bfd_opts->min_tx_int = $2; } + | MIN RX INTERVAL expr_us { this_bfd_opts->min_rx_int = $4; } + | MIN TX INTERVAL expr_us { this_bfd_opts->min_tx_int = $4; } + | IDLE TX INTERVAL expr_us { this_bfd_opts->idle_tx_int = $4; } + | MULTIPLIER expr { this_bfd_opts->multiplier = $2; } + | PASSIVE bool { this_bfd_opts->passive = $2; this_bfd_opts->passive_set = 1; } + | GRACEFUL { this_bfd_opts->mode = BGP_BFD_GRACEFUL; } + | AUTHENTICATION bfd_auth_type { this_bfd_opts->auth_type = $2; } + | password_list {} + ; + +bfd_items: + /* empty */ + | bfd_items bfd_item ';' + ; + +bfd_opts_start: +{ reset_passwords(); } ; + +bfd_opts_end: +{ + this_bfd_opts->passwords = get_passwords(); + + if (!this_bfd_opts->auth_type != !this_bfd_opts->passwords) + cf_warn("Authentication and password options should be used together"); + + if (this_bfd_opts->passwords) + { + struct password_item *pass; + WALK_LIST(pass, *this_bfd_opts->passwords) + { + if (pass->alg) + cf_error("Password algorithm option not available in BFD protocol"); + + pass->alg = bfd_auth_type_to_hash_alg[this_bfd_opts->auth_type]; + } + } +}; + +bfd_opts: + '{' bfd_opts_start bfd_items '}' bfd_opts_end; + + CF_CLI_HELP(SHOW BFD, ..., [[Show information about BFD protocol]]); CF_CLI_HELP(SHOW BFD SESSIONS, ..., [[Show information about BFD sessions]]); diff --git a/proto/bfd/packets.c b/proto/bfd/packets.c index 64e0c556..1ceb470c 100644 --- a/proto/bfd/packets.c +++ b/proto/bfd/packets.c @@ -109,7 +109,7 @@ const u8 bfd_auth_type_to_hash_alg[] = { static void bfd_fill_authentication(struct bfd_proto *p, struct bfd_session *s, struct bfd_ctl_packet *pkt) { - struct bfd_iface_config *cf = s->ifa->cf; + struct bfd_session_config *cf = &s->cf; struct password_item *pass = password_find(cf->passwords, 0); uint meticulous = 0; @@ -179,7 +179,7 @@ bfd_fill_authentication(struct bfd_proto *p, struct bfd_session *s, struct bfd_c static int bfd_check_authentication(struct bfd_proto *p, struct bfd_session *s, struct bfd_ctl_packet *pkt) { - struct bfd_iface_config *cf = s->ifa->cf; + struct bfd_session_config *cf = &s->cf; const char *err_dsc = NULL; uint err_val = 0; uint auth_type = 0; @@ -306,7 +306,7 @@ bfd_send_ctl(struct bfd_proto *p, struct bfd_session *s, int final) else if (s->poll_active) pkt->flags |= BFD_FLAG_POLL; - if (s->ifa->cf->auth_type) + if (s->cf.auth_type) bfd_fill_authentication(p, s, pkt); if (sk->tbuf != sk->tpos) @@ -416,6 +416,8 @@ bfd_err_hook(sock *sk, int err) sock * bfd_open_rx_sk(struct bfd_proto *p, int multihop, int af) { + struct bfd_config *cf = (struct bfd_config *) (p->p.cf); + sock *sk = sk_new(p->p.pool); sk->type = SK_UDP; sk->subtype = af; @@ -432,6 +434,9 @@ bfd_open_rx_sk(struct bfd_proto *p, int multihop, int af) sk->priority = sk_priority_control; sk->flags = SKF_LADDR_RX | (!multihop ? SKF_TTL_RX : 0); + if (cf->zero_udp6_checksum_rx) + sk->flags |= SKF_UDP6_NO_CSUM_RX; + if (sk_open(sk, p->p.loop) < 0) goto err; @@ -446,6 +451,8 @@ err: sock * bfd_open_rx_sk_bound(struct bfd_proto *p, ip_addr local, struct iface *ifa) { + struct bfd_config *cf = (struct bfd_config *) (p->p.cf); + sock *sk = sk_new(p->tpool); sk->type = SK_UDP; sk->saddr = local; @@ -463,6 +470,9 @@ bfd_open_rx_sk_bound(struct bfd_proto *p, ip_addr local, struct iface *ifa) sk->priority = sk_priority_control; sk->flags = SKF_BIND | (ifa ? SKF_TTL_RX : 0); + if (cf->zero_udp6_checksum_rx) + sk->flags |= SKF_UDP6_NO_CSUM_RX; + if (sk_open(sk, p->p.loop) < 0) goto err; diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c index 597ada4d..0e843b50 100644 --- a/proto/bgp/attrs.c +++ b/proto/bgp/attrs.c @@ -729,7 +729,7 @@ static void bgp_format_cluster_list(const eattr *a, byte *buf, uint size) { /* Truncates cluster lists larger than buflen, probably not a problem */ - int_set_format(a->u.ptr, 0, -1, buf, size); + int_set_format(a->u.ptr, ISF_ROUTER_ID, -1, buf, size); } diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c index 408053d5..db5eb6c5 100644 --- a/proto/bgp/bgp.c +++ b/proto/bgp/bgp.c @@ -104,6 +104,7 @@ * RFC 9072 - Extended Optional Parameters Length for BGP OPEN Message * RFC 9117 - Revised Validation Procedure for BGP Flow Specifications * RFC 9234 - Route Leak Prevention and Detection Using Roles + * RFC 9687 - Send Hold Timer * draft-uttaro-idr-bgp-persistence-04 * draft-walton-bgp-hostname-capability-02 */ diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y index b656dd96..90dfc905 100644 --- a/proto/bgp/config.Y +++ b/proto/bgp/config.Y @@ -33,12 +33,14 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY, KEEPALIVE, FIRST, FREE, VALIDATE, BASE, ROLE, ROLES, PEER, PROVIDER, CUSTOMER, RS_SERVER, RS_CLIENT, REQUIRE, BGP_OTC, GLOBAL, SEND) +CF_ENUM(T_ENUM_BGP_ORIGIN, ORIGIN_, IGP, EGP, INCOMPLETE) + %type bgp_nh %type bgp_afi CF_KEYWORDS(CEASE, PREFIX, LIMIT, HIT, ADMINISTRATIVE, SHUTDOWN, RESET, PEER, CONFIGURATION, CHANGE, DECONFIGURED, CONNECTION, REJECTED, COLLISION, - OUT, OF, RESOURCES) + OUT, OF, RESOURCES, ASPA_CHECK_UPSTREAM, ASPA_CHECK_DOWNSTREAM) %type bgp_cease_mask bgp_cease_list bgp_cease_flag bgp_role_name @@ -365,8 +367,6 @@ custom_attr: ATTRIBUTE BGP NUM type symbol ';' { cf_define_symbol(new_config, $5, SYM_ATTRIBUTE, attribute, ac); }; -CF_ENUM(T_ENUM_BGP_ORIGIN, ORIGIN_, IGP, EGP, INCOMPLETE) - CF_CLI(RELOAD BGP, proto_patt, [], [[Send and request route refresh to/from neighbor]]) { proto_apply_cmd($3, bgp_reload_in, 1, 0); @@ -383,6 +383,29 @@ CF_CLI(RELOAD BGP OUT, proto_patt, [], [[Refresh routes to neighbor]]) proto_apply_cmd($4, bgp_reload_out, 1, 0); } +/* ASPA shortcuts */ +term: ASPA_CHECK_DOWNSTREAM '(' rtable ')' { $$ = + f_new_inst(FI_ASPA_CHECK_EXPLICIT, + f_new_inst(FI_EA_GET, + f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_ROUTE, .val.rte = NULL, }), + ea_class_find_by_name("bgp_path") + ), + f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 0, }), + $3 + ); +} + +term: ASPA_CHECK_UPSTREAM '(' rtable ')' { $$ = + f_new_inst(FI_ASPA_CHECK_EXPLICIT, + f_new_inst(FI_EA_GET, + f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_ROUTE, .val.rte = NULL, }), + ea_class_find_by_name("bgp_path") + ), + f_new_inst(FI_CONSTANT, (struct f_val) { .type = T_BOOL, .val.i = 1, }), + $3 + ); +} + CF_CODE CF_END diff --git a/proto/bgp/packets.c b/proto/bgp/packets.c index 8a61a1c5..372b775d 100644 --- a/proto/bgp/packets.c +++ b/proto/bgp/packets.c @@ -3342,7 +3342,7 @@ static struct { { 6, 8, "Out of Resources" }, { 7, 0, "Invalid ROUTE-REFRESH message" }, /* [RFC7313] */ { 7, 1, "Invalid ROUTE-REFRESH message length" }, /* [RFC7313] */ - { 8, 0, "Send hold timer expired" }, /* [draft-ietf-idr-bgp-sendholdtimer] */ + { 8, 0, "Send hold timer expired" }, /* [RFC9687] */ }; /** diff --git a/proto/ospf/ospf.h b/proto/ospf/ospf.h index 91204667..75552953 100644 --- a/proto/ospf/ospf.h +++ b/proto/ospf/ospf.h @@ -252,6 +252,7 @@ struct ospf_proto u32 last_vlink_id; /* Interface IDs for vlinks (starts at 0x80000000) */ struct tbf log_pkt_tbf; /* TBF for packet messages */ struct tbf log_lsa_tbf; /* TBF for LSA messages */ + ip_addr loopback_addr; /* IP address used as common next hop (in OSPFv3-IPv4) */ }; struct ospf_area @@ -331,6 +332,7 @@ struct ospf_iface struct top_hash_entry **flood_queue; /* LSAs queued for LSUPD */ u8 update_link_lsa; u8 update_net_lsa; + u8 loopback_addr_used; /* The Link-LSA depends on p->loopback_addr */ u16 flood_queue_used; /* The current number of LSAs in flood_queue */ u16 flood_queue_size; /* The maximum number of LSAs in flood_queue */ int fadj; /* Number of fully adjacent neighbors */ diff --git a/proto/ospf/rt.c b/proto/ospf/rt.c index 40c30b81..b9b992a5 100644 --- a/proto/ospf/rt.c +++ b/proto/ospf/rt.c @@ -2029,6 +2029,14 @@ again1: { neighbor *nbr = neigh_find(&p->p, nh->gw, nh->iface, (nh->flags & RNF_ONLINK) ? NEF_ONLINK : 0); + + /* According to RFC 5838 2.5 Direct Interface Address */ + if (ospf_is_v3(p) && !nbr && ipa_is_ip4(nh->gw)) + { + nh->flags |= RNF_ONLINK; + nbr = neigh_find(&p->p, nh->gw, nh->iface, NEF_ONLINK); + } + if (!nbr || (nbr->scope == SCOPE_HOST)) { reset_ri(nf); break; } } diff --git a/proto/ospf/topology.c b/proto/ospf/topology.c index fadd6b58..402afcd5 100644 --- a/proto/ospf/topology.c +++ b/proto/ospf/topology.c @@ -1381,6 +1381,46 @@ lsab_put_prefix(struct ospf_proto *p, net_addr *n, u32 cost) ospf3_put_prefix(buf, n, flags, cost); } +static inline void +update_loopback_addr(struct ospf_proto *p) +{ + ip_addr old_addr = p->loopback_addr; + ip_addr best_addr = IPA_NONE; + int best_pref = 0; + + struct ospf_iface *ifa; + WALK_LIST(ifa, p->iface_list) + { + if (ifa->type == OSPF_IT_VLINK) + continue; + + struct ifa *a; + WALK_LIST(a, ifa->iface->addrs) + { + if ((a->prefix.type != ospf_get_af(p)) || + (a->flags & IA_SECONDARY) || + (a->scope <= SCOPE_LINK)) + continue; + + int pref = (a->flags & IA_HOST) ? 3 : (ifa->stub ? 2 : 1); + if ((pref > best_pref) || ((pref == best_pref) && ipa_equal(a->ip, old_addr))) + { + best_addr = a->ip; + best_pref = pref; + } + } + } + + if (ipa_equal(best_addr, old_addr)) + return; + + p->loopback_addr = best_addr; + + WALK_LIST(ifa, p->iface_list) + if (ifa->loopback_addr_used) + ospf_notify_link_lsa(ifa); +} + static void prepare_link_lsa_body(struct ospf_proto *p, struct ospf_iface *ifa) { @@ -1406,6 +1446,12 @@ prepare_link_lsa_body(struct ospf_proto *p, struct ospf_iface *ifa) i++; } + if (ospf_is_ip4(p) && ipa_zero(nh)) + { + nh = p->loopback_addr; + ifa->loopback_addr_used = 1; + } + /* Filling the preallocated header */ struct ospf_lsa_link *ll = p->lsab; ll->options = ifa->oa->options | (ifa->priority << 24); @@ -1832,6 +1878,9 @@ ospf_update_topology(struct ospf_proto *p) } } + if (ospf_is_v3(p) && ospf_is_ip4(p)) + update_loopback_addr(p); + WALK_LIST(ifa, p->iface_list) { if (ifa->type == OSPF_IT_VLINK) @@ -1839,6 +1888,8 @@ ospf_update_topology(struct ospf_proto *p) if (ifa->update_link_lsa) { + ifa->loopback_addr_used = 0; + if ((ifa->state > OSPF_IS_LOOP) && !ifa->link_lsa_suppression) ospf_originate_link_lsa(p, ifa); else diff --git a/proto/rpki/config.Y b/proto/rpki/config.Y index dc5df048..16797206 100644 --- a/proto/rpki/config.Y +++ b/proto/rpki/config.Y @@ -13,6 +13,7 @@ CF_HDR CF_DEFINES #define RPKI_CFG ((struct rpki_config *) this_proto) +#define RPKI_TR_TCP_CFG ((struct rpki_tr_tcp_config *) RPKI_CFG->tr_config.spec) #define RPKI_TR_SSH_CFG ((struct rpki_tr_ssh_config *) RPKI_CFG->tr_config.spec) static void @@ -32,7 +33,8 @@ rpki_check_unused_transport(void) CF_DECLS CF_KEYWORDS(RPKI, REMOTE, BIRD, PRIVATE, PUBLIC, KEY, TCP, SSH, TRANSPORT, USER, - RETRY, REFRESH, EXPIRE, KEEP, IGNORE, MAX, LENGTH, LOCAL, ADDRESS) + RETRY, REFRESH, EXPIRE, KEEP, IGNORE, MAX, MIN, LENGTH, LOCAL, ADDRESS, + AUTHENTICATION, NONE, MD5, PASSWORD, VERSION) %type rpki_keep_interval @@ -46,6 +48,8 @@ rpki_proto_start: proto_start RPKI { RPKI_CFG->retry_interval = RPKI_RETRY_INTERVAL; RPKI_CFG->refresh_interval = RPKI_REFRESH_INTERVAL; RPKI_CFG->expire_interval = RPKI_EXPIRE_INTERVAL; + RPKI_CFG->min_version = 0; + RPKI_CFG->max_version = RPKI_MAX_VERSION; }; rpki_proto: rpki_proto_start proto_name '{' rpki_proto_opts '}' { rpki_check_config(RPKI_CFG); }; @@ -82,6 +86,14 @@ rpki_proto_item: RPKI_CFG->keep_expire_interval = $2; } | IGNORE MAX LENGTH bool { RPKI_CFG->ignore_max_length = $4; } + | MIN VERSION expr { + if ($3 > RPKI_MAX_VERSION) cf_error("RPKI version %u unsupported, min version must be in range 0-%u", $3, RPKI_MAX_VERSION); + RPKI_CFG->min_version = $3; + } + | MAX VERSION expr { + if ($3 > RPKI_MAX_VERSION) cf_error("RPKI version %u unsupported, max version must be in range 0-%u", $3, RPKI_MAX_VERSION); + RPKI_CFG->max_version = $3; + } ; rpki_keep_interval: @@ -109,7 +121,7 @@ rpki_cache_addr: text_or_ipa }; rpki_transport: - TCP rpki_transport_tcp_init + TCP rpki_transport_tcp_init rpki_transport_tcp_opts_list rpki_transport_tcp_check | SSH rpki_transport_ssh_init '{' rpki_transport_ssh_opts '}' rpki_transport_ssh_check ; @@ -120,6 +132,28 @@ rpki_transport_tcp_init: RPKI_CFG->tr_config.type = RPKI_TR_TCP; }; +rpki_transport_tcp_opts_list: + /* empty */ + | '{' rpki_transport_tcp_opts '}' + ; + +rpki_transport_tcp_opts: + /* empty */ + | rpki_transport_tcp_opts rpki_transport_tcp_item ';' + ; + +rpki_transport_tcp_item: + AUTHENTICATION NONE { RPKI_TR_TCP_CFG->auth_type = RPKI_TCP_AUTH_NONE; } + | AUTHENTICATION MD5 { RPKI_TR_TCP_CFG->auth_type = RPKI_TCP_AUTH_MD5; } + | PASSWORD text { RPKI_TR_TCP_CFG->password = $2; } + ; + +rpki_transport_tcp_check: +{ + if (!RPKI_TR_TCP_CFG->auth_type != !RPKI_TR_TCP_CFG->password) + cf_error("Authentication and password options should be used together"); +}; + rpki_transport_ssh_init: { #if HAVE_LIBSSH diff --git a/proto/rpki/packets.c b/proto/rpki/packets.c index 07847db0..4cd10773 100644 --- a/proto/rpki/packets.c +++ b/proto/rpki/packets.c @@ -62,6 +62,7 @@ enum pdu_type { CACHE_RESET = 8, ROUTER_KEY = 9, ERROR = 10, + ASPA = 11, PDU_TYPE_MAX }; @@ -76,7 +77,8 @@ static const char *str_pdu_type_[] = { [END_OF_DATA] = "End of Data", [CACHE_RESET] = "Cache Reset", [ROUTER_KEY] = "Router Key", - [ERROR] = "Error" + [ERROR] = "Error", + [ASPA] = "ASPA", }; static const char *str_pdu_type(uint type) { @@ -193,6 +195,35 @@ struct pdu_error { * Error Diagnostic Message */ } PACKED; +/* + *0 8 16 24 31 + * .-------------------------------------------. + * | Protocol | PDU | | | + * | Version | Type | Flags | zero | + * | 2 | 11 | | | + * +-------------------------------------------+ + * | | + * | Length | + * | | + * +-------------------------------------------+ + * | | + * | Customer Autonomous System Number | + * | | + * +-------------------------------------------+ + * | | + * ~ Provider Autonomous System Numbers ~ + * | | + * ~-------------------------------------------~ */ +struct pdu_aspa { + u8 ver; + u8 type; + u8 flags; + u8 zero; + u32 len; + u32 customer_as_num; + u32 provider_as_nums[0]; +} PACKED; + struct pdu_reset_query { u8 ver; u8 type; @@ -230,9 +261,13 @@ static const size_t min_pdu_size[] = { [END_OF_DATA] = sizeof(struct pdu_end_of_data_v0), [CACHE_RESET] = sizeof(struct pdu_cache_response), [ROUTER_KEY] = sizeof(struct pdu_header), /* FIXME */ + [ASPA] = sizeof(struct pdu_aspa), [ERROR] = 16, }; +static inline int rpki_pdu_aspa_provider_asn_count(const struct pdu_aspa *pdu) +{ return (pdu->len - sizeof(struct pdu_aspa)) / (sizeof(u32)); } + 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, ...); #define rpki_send_error_pdu(cache, error_code, err_pdu_len, erroneous_pdu, fmt...) ({ \ @@ -276,29 +311,25 @@ rpki_pdu_to_network_byte_order(struct pdu_header *pdu) 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->session_id = ntohs(sn_pdu->session_id); 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->session_id = ntohs(eod0->session_id); eod0->serial_num = ntohl(eod0->serial_num); /* Same either for version 1 */ - if (pdu->ver == RPKI_VERSION_1) + if (pdu->ver > RPKI_VERSION_0) { struct pdu_end_of_data_v1 *eod1 = (void *) pdu; eod1->expire_interval = ntohl(eod1->expire_interval); @@ -326,14 +357,29 @@ rpki_pdu_to_host_byte_order(struct pdu_header *pdu) case ERROR: { - /* Note that a error_code is converted using converting header->reserved */ struct pdu_error *err = (void *) pdu; + err->error_code = ntohs(err->error_code); 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 ASPA: + { + struct pdu_aspa *aspa = (void *) pdu; + int provider_asn_count = rpki_pdu_aspa_provider_asn_count(aspa); + + /* Convert customer ASN */ + aspa->customer_as_num = ntohl(aspa->customer_as_num); + + /* Convert provider ASNs */ + for (int i = 0; i < provider_asn_count ; i++) + aspa->provider_as_nums[i] = ntohl(aspa->provider_as_nums[i]); + + break; + } + case ROUTER_KEY: /* Router Key PDU is not supported yet */ @@ -343,8 +389,14 @@ rpki_pdu_to_host_byte_order(struct pdu_header *pdu) * We don't care here. */ case CACHE_RESPONSE: + { + struct pdu_cache_response *cr = (void *) pdu; + cr->session_id = ntohs(cr->session_id); + break; + } + case CACHE_RESET: - /* Converted with pdu->reserved */ + /* Nothing to convert */ break; } } @@ -359,6 +411,9 @@ rpki_pdu_to_host_byte_order(struct pdu_header *pdu) static struct pdu_header * rpki_pdu_back_to_network_byte_order(struct pdu_header *out, const struct pdu_header *in) { + /* Only valid for fixed-length PDUs */ + ASSERT_DIE(in->type != ERROR && in->type != ASPA); + memcpy(out, in, in->len); rpki_pdu_to_host_byte_order(out); return out; @@ -392,7 +447,7 @@ rpki_log_packet(struct rpki_cache *cache, const struct pdu_header *pdu, const en case END_OF_DATA: { const struct pdu_end_of_data_v1 *eod = (void *) pdu; - if (eod->ver == RPKI_VERSION_1) + if (eod->ver > RPKI_VERSION_0) 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)); @@ -460,6 +515,25 @@ rpki_log_packet(struct rpki_cache *cache, const struct pdu_header *pdu, const en break; } + case ASPA: + { + const struct pdu_aspa *aspa = (void *) pdu; + int provider_asn_count = rpki_pdu_aspa_provider_asn_count(aspa); + + if (provider_asn_count <= 0) + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), + "%u transit", aspa->customer_as_num)); + else + { + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), + "%u (providers", aspa->customer_as_num)); + for (int i = 0; i < provider_asn_count; i++) + SAVE(bsnprintf(detail + strlen(detail), sizeof(detail) - strlen(detail), + " %u%c", aspa->provider_as_nums[i], (i == provider_asn_count-1) ? ')' : ',')); + } + break; + } + default: *detail = '\0'; } @@ -561,7 +635,9 @@ rpki_check_receive_packet(struct rpki_cache *cache, const struct pdu_header *pdu } else if (!cache->last_update && (pdu->ver <= RPKI_MAX_VERSION) && - (pdu->ver < cache->version)) + (pdu->ver < cache->version) && + (pdu->ver >= cache->min_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; @@ -576,15 +652,21 @@ rpki_check_receive_packet(struct rpki_cache *cache, const struct pdu_header *pdu } } - if ((pdu->type >= PDU_TYPE_MAX) || (pdu->ver == RPKI_VERSION_0 && pdu->type == ROUTER_KEY)) + if ((pdu->type >= PDU_TYPE_MAX) || + (pdu->ver < RPKI_VERSION_1 && pdu->type == ROUTER_KEY) || + (pdu->ver < RPKI_VERSION_2 && pdu->type == ASPA)) { 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]) + uint min_pdu_length = min_pdu_size[pdu->type]; + if (pdu->type == END_OF_DATA && pdu->ver >= RPKI_VERSION_1) + min_pdu_length = sizeof(struct pdu_end_of_data_v1); + + if (pdu_len < min_pdu_length) { - 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]); + rpki_send_error_pdu(cache, CORRUPT_DATA, pdu_len, pdu, "Received %s packet with %u bytes, but expected at least %u bytes", str_pdu_type(pdu->type), pdu_len, min_pdu_length); return RPKI_ERROR; } @@ -611,7 +693,8 @@ rpki_handle_error_pdu(struct rpki_cache *cache, const struct pdu_error *pdu) case UNSUPPORTED_PROTOCOL_VER: CACHE_TRACE(D_PACKETS, cache, "Client uses unsupported protocol version"); if (pdu->ver <= RPKI_MAX_VERSION && - pdu->ver < cache->version) + pdu->ver < cache->version && + pdu->ver >= cache->min_version) { CACHE_TRACE(D_EVENTS, cache, "Downgrading from protocol version %d to version %d", cache->version, pdu->ver); cache->version = pdu->ver; @@ -789,6 +872,29 @@ rpki_handle_prefix_pdu(struct rpki_cache *cache, const struct pdu_header *pdu) return RPKI_SUCCESS; } +static int +rpki_handle_aspa_pdu(struct rpki_cache *cache, const struct pdu_header *pdu) +{ + struct pdu_aspa *aspa = (void *) pdu; + struct channel *channel = cache->p->aspa_channel; + uint providers_length = aspa->len - sizeof(struct pdu_aspa); + + if (!channel) + { + CACHE_TRACE(D_ROUTES, cache, "Skip AS%u, missing aspa channel", aspa->customer_as_num); + return RPKI_ERROR; + } + + cache->last_rx_prefix = current_time(); + + if (aspa->flags & RPKI_ADD_FLAG) + rpki_table_add_aspa(cache, channel, aspa->customer_as_num, aspa->provider_as_nums, providers_length); + else + rpki_table_remove_aspa(cache, channel, aspa->customer_as_num); + + return RPKI_SUCCESS; +} + static uint rpki_check_interval(struct rpki_cache *cache, const char *(check_fn)(uint), uint interval) { @@ -814,7 +920,7 @@ rpki_handle_end_of_data_pdu(struct rpki_cache *cache, const struct pdu_end_of_da return; } - if (pdu->ver == RPKI_VERSION_1) + if (pdu->ver > RPKI_VERSION_0) { if (!cf->keep_refresh_interval && rpki_check_interval(cache, rpki_check_refresh_interval, pdu->refresh_interval)) cache->refresh_interval = pdu->refresh_interval; @@ -898,6 +1004,10 @@ rpki_rx_packet(struct rpki_cache *cache, struct pdu_header *pdu) /* TODO: Implement Router Key PDU handling */ break; + case ASPA: + rpki_handle_aspa_pdu(cache, (void *) pdu); + break; + default: CACHE_TRACE(D_PACKETS, cache, "Received unsupported type (%u)", pdu->type); }; diff --git a/proto/rpki/packets.h b/proto/rpki/packets.h index d6f8a249..f7897d9a 100644 --- a/proto/rpki/packets.h +++ b/proto/rpki/packets.h @@ -23,8 +23,10 @@ * +4 bytes (Length of inserted text) * +800 bytes (UTF-8 text 400*2 bytes) * ------------ - * = 848 bytes (Maximal expected PDU size) */ -#define RPKI_PDU_MAX_LEN 848 + * = 848 bytes (Maximal expected PDU size) + * + * Received ASPA PDU can have any size, so let's start with 4k */ +#define RPKI_PDU_MAX_LEN 4096 /* RX buffer size has a great impact to scheduler granularity */ #define RPKI_RX_BUFFER_SIZE 4096 diff --git a/proto/rpki/rpki.c b/proto/rpki/rpki.c index 4c19321f..adb073b2 100644 --- a/proto/rpki/rpki.c +++ b/proto/rpki/rpki.c @@ -137,6 +137,33 @@ rpki_table_remove_roa(struct rpki_cache *cache, struct channel *channel, const n rte_update(channel, &pfxr->n, NULL, p->p.main_source); } +void +rpki_table_add_aspa(struct rpki_cache *cache, struct channel *channel, + u32 customer, void *providers, uint providers_length) +{ + struct rpki_proto *p = cache->p; + + net_addr_union n = { .aspa = NET_ADDR_ASPA(customer) }; + + ea_list *ea = NULL; + ea_set_attr_u32(&ea, &ea_gen_preference, 0, channel->preference); + ea_set_attr_u32(&ea, &ea_gen_source, 0, RTS_RPKI); + + ea_set_attr_data(&ea, &ea_gen_aspa_providers, 0, providers, providers_length); + + rte e0 = { .attrs = ea, .src = p->p.main_source, }; + + rte_update(channel, &n.n, &e0, p->p.main_source); +} + +void +rpki_table_remove_aspa(struct rpki_cache *cache, struct channel *channel, u32 customer) +{ + struct rpki_proto *p = cache->p; + net_addr_union n = { .aspa = NET_ADDR_ASPA(customer) }; + rte_update(channel, &n.n, NULL, p->p.main_source); +} + void rpki_start_refresh(struct rpki_proto *p) { @@ -144,6 +171,8 @@ rpki_start_refresh(struct rpki_proto *p) rt_refresh_begin(&p->roa4_channel->in_req); if (p->roa6_channel) rt_refresh_begin(&p->roa6_channel->in_req); + if (p->aspa_channel) + rt_refresh_begin(&p->aspa_channel->in_req); p->refresh_channels = 1; } @@ -160,6 +189,8 @@ rpki_stop_refresh(struct rpki_proto *p) rt_refresh_end(&p->roa4_channel->in_req); if (p->roa6_channel) rt_refresh_end(&p->roa6_channel->in_req); + if (p->aspa_channel) + rt_refresh_end(&p->aspa_channel->in_req); } /* @@ -608,7 +639,8 @@ rpki_init_cache(struct rpki_proto *p, struct rpki_config *cf) cache->state = RPKI_CS_SHUTDOWN; cache->request_session_id = 1; - cache->version = RPKI_MAX_VERSION; + cache->version = cf->max_version; + cache->min_version = cf->min_version; cache->refresh_interval = cf->refresh_interval; cache->retry_interval = cf->retry_interval; @@ -714,6 +746,23 @@ rpki_reconfigure_cache(struct rpki_proto *p UNUSED, struct rpki_cache *cache, st return NEED_RESTART; } + if (new->min_version > cache->version) + { + CACHE_TRACE(D_EVENTS, cache, "Protocol min version %u higher than current version %u", + new->min_version, cache->version); + return NEED_RESTART; + } + else + cache->min_version = new->min_version; + + if (new->max_version < cache->version) + { + CACHE_TRACE(D_EVENTS, cache, "Protocol max version %u lower than current version %u", + new->max_version, cache->version); + cache->version = new->max_version; + try_reset = 1; + } + if (old->tr_config.type != new->tr_config.type) { CACHE_TRACE(D_EVENTS, cache, "Transport type changed"); @@ -726,6 +775,24 @@ rpki_reconfigure_cache(struct rpki_proto *p UNUSED, struct rpki_cache *cache, st try_reset = 1; } + if (new->tr_config.type == RPKI_TR_TCP) + { + struct rpki_tr_tcp_config *tcp_old = (void *) old->tr_config.spec; + struct rpki_tr_tcp_config *tcp_new = (void *) new->tr_config.spec; + + if (tcp_old->auth_type != tcp_new->auth_type) + { + CACHE_TRACE(D_EVENTS, cache, "Authentication type changed"); + return NEED_RESTART; + } + + if (bstrcmp(tcp_old->password, tcp_new->password)) + { + CACHE_TRACE(D_EVENTS, cache, "MD5 password changed"); + return NEED_RESTART; + } + } + #if HAVE_LIBSSH else if (new->tr_config.type == RPKI_TR_SSH) { @@ -796,7 +863,8 @@ rpki_reconfigure(struct proto *P, struct proto_config *CF) struct rpki_cache *cache = p->cache; if (!proto_configure_channel(&p->p, &p->roa4_channel, proto_cf_find_channel(CF, NET_ROA4)) || - !proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6))) + !proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6)) || + !proto_configure_channel(&p->p, &p->aspa_channel, proto_cf_find_channel(CF, NET_ASPA))) return NEED_RESTART; if (rpki_reconfigure_cache(p, cache, new, old) != SUCCESSFUL_RECONF) @@ -818,6 +886,7 @@ rpki_init(struct proto_config *CF) proto_configure_channel(&p->p, &p->roa4_channel, proto_cf_find_channel(CF, NET_ROA4)); proto_configure_channel(&p->p, &p->roa6_channel, proto_cf_find_channel(CF, NET_ROA6)); + proto_configure_channel(&p->p, &p->aspa_channel, proto_cf_find_channel(CF, NET_ASPA)); return P; } @@ -883,8 +952,12 @@ rpki_show_proto_info(struct proto *P) default_port = RPKI_SSH_PORT; break; #endif - case RPKI_TR_TCP: - transport_name = "Unprotected over TCP"; + case RPKI_TR_TCP:; + struct rpki_tr_tcp_config *tcp_cf = (void *) cf->tr_config.spec; + if (tcp_cf->auth_type == RPKI_TCP_AUTH_MD5) + transport_name = "TCP-MD5"; + else + transport_name = "Unprotected over TCP"; default_port = RPKI_TCP_PORT; break; }; @@ -927,6 +1000,11 @@ rpki_show_proto_info(struct proto *P) channel_show_info(p->roa6_channel); else cli_msg(-1006, " No roa6 channel"); + + if (p->aspa_channel) + channel_show_info(p->aspa_channel); + else + cli_msg(-1006, " No aspa channel"); } } @@ -944,6 +1022,9 @@ rpki_show_proto_info(struct proto *P) void rpki_check_config(struct rpki_config *cf) { + if (cf->min_version > cf->max_version) + cf_error("Impossible min/max version for RPKI: %u/%u", cf->min_version, cf->max_version); + /* Do not check templates at all */ if (cf->c.class == SYM_TEMPLATE) return; @@ -998,7 +1079,7 @@ struct protocol proto_rpki = { .init = rpki_init, .start = rpki_start, .postconfig = rpki_postconfig, - .channel_mask = (NB_ROA4 | NB_ROA6), + .channel_mask = (NB_ROA4 | NB_ROA6 | NB_ASPA), .show_proto_info = rpki_show_proto_info, .shutdown = rpki_shutdown, .copy_config = rpki_copy_config, diff --git a/proto/rpki/rpki.h b/proto/rpki/rpki.h index 31278860..d09644b6 100644 --- a/proto/rpki/rpki.h +++ b/proto/rpki/rpki.h @@ -29,7 +29,8 @@ #define RPKI_VERSION_0 0 #define RPKI_VERSION_1 1 -#define RPKI_MAX_VERSION RPKI_VERSION_1 +#define RPKI_VERSION_2 2 +#define RPKI_MAX_VERSION RPKI_VERSION_2 /* @@ -60,6 +61,7 @@ struct rpki_cache { u8 request_session_id; /* 1: have to request new session id; 0: we have already received session id */ u32 serial_num; /* Serial number denotes the logical version of data from cache server */ u8 version; /* Protocol version */ + u8 min_version; /* Minimum allowed protocol version */ btime last_update; /* Last successful synchronization with cache server */ btime last_rx_prefix; /* Last received prefix PDU */ @@ -83,6 +85,9 @@ const char *rpki_cache_state_to_str(enum rpki_cache_state state); void rpki_table_add_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr); void rpki_table_remove_roa(struct rpki_cache *cache, struct channel *channel, const net_addr_union *pfxr); +void rpki_table_add_aspa(struct rpki_cache *cache, struct channel *channel, u32 customer, void *providers, uint providers_length); +void rpki_table_remove_aspa(struct rpki_cache *cache, struct channel *channel, u32 customer); + void rpki_start_refresh(struct rpki_proto *p); void rpki_stop_refresh(struct rpki_proto *p); @@ -112,6 +117,7 @@ struct rpki_proto { struct channel *roa4_channel; struct channel *roa6_channel; + struct channel *aspa_channel; u8 refresh_channels; /* For non-incremental updates using rt_refresh_begin(), rt_refresh_end() */ }; @@ -129,6 +135,8 @@ struct rpki_config { u8 keep_retry_interval:1; /* Do not overwrite retry interval by cache server update */ u8 keep_expire_interval:1; /* Do not overwrite expire interval by cache server update */ u8 ignore_max_length:1; /* Ignore received max length and use MAX_PREFIX_LENGTH instead */ + u8 min_version; /* Minimum version allowed */ + u8 max_version; /* Maximum version allowed (to start with) */ }; void rpki_check_config(struct rpki_config *cf); diff --git a/proto/rpki/tcp_transport.c b/proto/rpki/tcp_transport.c index ebb8030f..da0a5927 100644 --- a/proto/rpki/tcp_transport.c +++ b/proto/rpki/tcp_transport.c @@ -24,10 +24,16 @@ static int rpki_tr_tcp_open(struct rpki_tr_sock *tr) { + struct rpki_cache *cache = tr->cache; + struct rpki_config *cf = (void *) cache->p->p.cf; + struct rpki_tr_tcp_config *tcp_cf = (void *) cf->tr_config.spec; sock *sk = tr->sk; sk->type = SK_TCP_ACTIVE; + if (tcp_cf->auth_type == RPKI_TCP_AUTH_MD5) + sk->password = tcp_cf->password; + if (sk_open(sk, tr->cache->p->p.loop) != 0) return RPKI_TR_ERROR; diff --git a/proto/rpki/transport.h b/proto/rpki/transport.h index bb8d41eb..10e9acb7 100644 --- a/proto/rpki/transport.h +++ b/proto/rpki/transport.h @@ -56,6 +56,12 @@ enum rpki_tr_type { #endif }; +/* TCP authentication types */ +enum rpki_tcp_auth { + RPKI_TCP_AUTH_NONE, + RPKI_TCP_AUTH_MD5 +}; + /* Common configure structure for transports */ struct rpki_tr_config { enum rpki_tr_type type; /* RPKI_TR_TCP or RPKI_TR_SSH */ @@ -63,7 +69,8 @@ struct rpki_tr_config { }; struct rpki_tr_tcp_config { - /* No internal configuration data */ + enum rpki_tcp_auth auth_type; /* Authentication type */ + const char *password; /* Password used for authentication */ }; struct rpki_tr_ssh_config { diff --git a/proto/static/config.Y b/proto/static/config.Y index 87560100..ef9612f0 100644 --- a/proto/static/config.Y +++ b/proto/static/config.Y @@ -15,6 +15,7 @@ CF_DEFINES #define STATIC_CFG ((struct static_config *) this_proto) static struct static_route *this_srt, *this_snh; static struct f_inst *this_srt_cmds, *this_srt_last_cmd; +static uint this_srt_aspa_max; static struct static_route * static_nexthop_new(void) @@ -47,6 +48,7 @@ CF_DECLS CF_KEYWORDS(STATIC, ROUTE, VIA, DROP, REJECT, PROHIBIT, PREFERENCE, CHECK, LINK, DEV) CF_KEYWORDS(ONLINK, WEIGHT, RECURSIVE, IGP, TABLE, BLACKHOLE, UNREACHABLE, BFD, MPLS) +CF_KEYWORDS(TRANSIT, PROVIDERS) CF_GRAMMAR @@ -148,8 +150,35 @@ stat_route: | stat_route0 BLACKHOLE { this_srt->dest = RTD_BLACKHOLE; } | stat_route0 UNREACHABLE { this_srt->dest = RTD_UNREACHABLE; } | stat_route0 PROHIBIT { this_srt->dest = RTD_PROHIBIT; } + | stat_route0 PROVIDER { + if (this_srt->net->type != NET_ASPA) cf_error("Provider settings available only for ASPA"); + this_srt->aspa = cfg_alloc(sizeof (adata) + (this_srt_aspa_max = 8) * sizeof (u32)); + this_srt->aspa->length = 0; + } stat_aspa_providers + | stat_route0 TRANSIT { + if (this_srt->net->type != NET_ASPA) cf_error("Transit settings available only for ASPA"); + /* Allocate an explicit zero */ + this_srt->aspa = cfg_alloc(sizeof (adata) + sizeof (u32)); + this_srt->aspa->length = sizeof(u32); + ((u32 *) this_srt->aspa->data)[0] = 0; + } ; +stat_aspa_provider: NUM { + if (this_srt->aspa->length == this_srt_aspa_max * sizeof(u32)) + { + adata *new = cfg_alloc(sizeof (adata) + (this_srt_aspa_max * 2) * sizeof (u32)); + memcpy(new, this_srt->aspa, this_srt->aspa->length + sizeof(adata)); + this_srt->aspa = new; + this_srt_aspa_max *= 2; + } + + ((u32 *) this_srt->aspa->data)[this_srt->aspa->length / sizeof(u32)] = $1; + this_srt->aspa->length += sizeof(u32); +} + +stat_aspa_providers: stat_aspa_provider | stat_aspa_providers ',' stat_aspa_provider ; + stat_route_item: cmd { if (this_srt_last_cmd) diff --git a/proto/static/static.c b/proto/static/static.c index cd393dd7..2bfeb8cc 100644 --- a/proto/static/static.c +++ b/proto/static/static.c @@ -112,6 +112,26 @@ static_announce_rte(struct static_proto *p, struct static_route *r) else if (r->dest) ea_set_dest(&ea, 0, r->dest); + if (r->net->type == NET_ASPA) + { + if (r->dest != RTD_NONE) + { + log(L_WARN "%s: ASPA %u configured with nexthop, ignoring the nexthop", + p->p.name, ((struct net_addr_aspa *) r->net)->asn); + r->dest = RTD_NONE; + } + + if (!r->aspa) + { + log(L_WARN "%s: ASPA %u configured with no provider list, ignoring the whole rule", + p->p.name, ((struct net_addr_aspa *) r->net)->asn); + goto withdraw; + } + + ea_set_attr(&ea, EA_LITERAL_DIRECT_ADATA( + &ea_gen_aspa_providers, 0, r->aspa)); + } + if (p->p.mpls_channel) { struct mpls_channel *mc = (void *) p->p.mpls_channel; diff --git a/proto/static/static.h b/proto/static/static.h index b7feb649..22810a5e 100644 --- a/proto/static/static.h +++ b/proto/static/static.h @@ -50,7 +50,10 @@ struct static_route { byte use_bfd; /* Configured to use BFD */ uint mpls_label; /* Local MPLS label, -1 if unused */ struct bfd_request *bfd_req; /* BFD request, if BFD is used */ - struct adata *mls; /* MPLS label stack; may be NULL */ + union { + adata *mls; /* MPLS label stack; may be NULL */ + adata *aspa; /* ASPA provider list; may be NULL */ + }; }; /* diff --git a/sysdep/bsd/sysio.h b/sysdep/bsd/sysio.h index b6b42b1e..c20c7f99 100644 --- a/sysdep/bsd/sysio.h +++ b/sysdep/bsd/sysio.h @@ -309,3 +309,9 @@ sk_set_freebind(sock *s) { ERR_MSG("Freebind is not supported"); } + +static inline int +sk_set_udp6_no_csum_rx(sock *s) +{ + ERR_MSG("UDPv6 zero checksum is not supported"); +} diff --git a/sysdep/linux/netlink.c b/sysdep/linux/netlink.c index 6e10f543..9e275152 100644 --- a/sysdep/linux/netlink.c +++ b/sysdep/linux/netlink.c @@ -51,6 +51,8 @@ struct nl_parse_state #define KRT_FEATURES_MAX 4 static void krt_bitfield_format(const eattr *e, byte *buf, uint buflen); +static struct f_val krt_bitfield_empty(const struct ea_class *cls UNUSED) +{ return (struct f_val) { .type = T_INT }; } static struct ea_class ea_krt_prefsrc = { @@ -71,11 +73,13 @@ static struct ea_class ea_krt_metrics[] = { .name = "krt_lock", .type = T_INT, .format = krt_bitfield_format, + .empty = krt_bitfield_empty, }, [RTAX_FEATURES] = { .name = "krt_features", .type = T_INT, .format = krt_bitfield_format, + .empty = krt_bitfield_empty, }, [RTAX_CC_ALGO] = { .name = "krt_congctl", @@ -1589,8 +1593,24 @@ done: } static inline int -nl_allow_replace(struct krt_proto *p, rte *new) +nl_allow_replace(struct krt_proto *p, const rte *new, const rte *old) { + /* + * In kernel routing tables, (net, metric) is the primary key. Therefore, we + * can use NL_OP_REPLACE only if the new and and the old route have the same + * kernel metric, otherwise the replace would just add the new route while + * keep the old one. + */ + + if ((p->af != AF_MPLS) && (KRT_CF->sys.metric == 0)) + { + uint new_metric = ea_get_int(new->attrs, &ea_krt_metric, 0); + uint old_metric = ea_get_int(old->attrs, &ea_krt_metric, 0); + + if (new_metric != old_metric) + return 0; + } + /* * We use NL_OP_REPLACE for IPv4, it has an issue with not checking for * matching rtm_protocol, but that is OK when dedicated priority is used. @@ -1622,7 +1642,7 @@ krt_replace_rte(struct krt_proto *p, const net_addr *n UNUSED, rte *new, const r { int err = 0; - if (old && new && nl_allow_replace(p, new)) + if (old && new && nl_allow_replace(p, new, old)) { err = nl_send_route(p, new, NL_OP_REPLACE); } diff --git a/sysdep/linux/sysio.h b/sysdep/linux/sysio.h index f13eda7c..49495e13 100644 --- a/sysdep/linux/sysio.h +++ b/sysdep/linux/sysio.h @@ -285,3 +285,14 @@ sk_set_freebind(sock *s) return 0; } + +static inline int +sk_set_udp6_no_csum_rx(sock *s) +{ + int y = 1; + + if (setsockopt(s->fd, SOL_UDP, UDP_NO_CHECK6_RX, &y, sizeof(y)) < 0) + ERR("UDP_NO_CHECK6_RX"); + + return 0; +} diff --git a/sysdep/unix/config.Y b/sysdep/unix/config.Y index 46365c85..42d78c6f 100644 --- a/sysdep/unix/config.Y +++ b/sysdep/unix/config.Y @@ -14,17 +14,17 @@ CF_HDR CF_DEFINES static struct log_config *this_log; +static struct cli_config *this_cli_config; CF_DECLS -CF_KEYWORDS(LOG, SYSLOG, ALL, DEBUG, TRACE, INFO, REMOTE, WARNING, ERROR, AUTH, FATAL, BUG, STDERR, SOFT, UDP, PORT) +CF_KEYWORDS(LOG, SYSLOG, ALL, DEBUG, TRACE, INFO, REMOTE, WARNING, ERROR, AUTH, FATAL, BUG, STDERR, SOFT, UDP, PORT, CLI) CF_KEYWORDS(NAME, CONFIRM, UNDO, CHECK, TIMEOUT, DEBUG, LATENCY, LIMIT, WATCHDOG, WARNING, STATUS) CF_KEYWORDS(PING, WAKEUP, SOCKETS, SCHEDULING, EVENTS, TIMERS, ALLOCATOR) CF_KEYWORDS(GRACEFUL, RESTART, FIXED) %type log_mask log_mask_list log_cat cfg_timeout debug_unix latency_debug_mask latency_debug_flag latency_debug_list %type cfg_name -%type timeformat_which %type syslog_name CF_GRAMMAR @@ -122,6 +122,28 @@ mrtdump_base: } ; +conf: cli ; + +cli: CLI text cli_opts { + this_cli_config->name = $2; + cli_config_add_tail(&new_config->cli, this_cli_config); + this_cli_config = NULL; +} ; + +cli_opts: cli_opts_begin '{' cli_opts_block '}' ';' | cli_opts_begin ';' ; + +cli_opts_begin: { + this_cli_config = cfg_alloc(sizeof *this_cli_config); + *this_cli_config = (typeof (*this_cli_config)) { + .config = new_config, + .mode = 0660, + }; +}; + +cli_opts_block: + /* EMPTY */ | + cli_opts_block RESTRICT { this_cli_config->restricted = 1; } +; conf: THREADS expr { if ($2 < 1) cf_error("Number of threads must be at least one."); diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c index 51de4a78..b247ccdb 100644 --- a/sysdep/unix/io.c +++ b/sysdep/unix/io.c @@ -668,6 +668,40 @@ sk_set_high_port(sock *s UNUSED) return 0; } +static inline int +sk_set_min_rcvbuf_(sock *s, int bufsize) +{ + int oldsize = 0, oldsize_s = sizeof(oldsize); + + if (getsockopt(s->fd, SOL_SOCKET, SO_RCVBUF, &oldsize, &oldsize_s) < 0) + ERR("SO_RCVBUF"); + + if (oldsize >= bufsize) + return 0; + + bufsize = BIRD_ALIGN(bufsize, 64); + if (setsockopt(s->fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) < 0) + ERR("SO_RCVBUF"); + + /* + int newsize = 0, newsize_s = sizeof(newsize); + if (getsockopt(s->fd, SOL_SOCKET, SO_RCVBUF, &newsize, &newsize_s) < 0) + ERR("SO_RCVBUF"); + + log(L_INFO "Setting rcvbuf on %s from %d to %d", + s->iface ? s->iface->name : "*", oldsize, newsize); + */ + + return 0; +} + +static void +sk_set_min_rcvbuf(sock *s, int bufsize) +{ + if (sk_set_min_rcvbuf_(s, bufsize) < 0) + log(L_WARN "Socket error: %s%#m", s->err); +} + static inline byte * sk_skip_ip_header(byte *pkt, int *len) { @@ -999,6 +1033,9 @@ sk_set_rbsize(sock *s, uint val) xfree(s->rbuf_alloc); s->rbuf_alloc = xmalloc(val); s->rpos = s->rbuf = s->rbuf_alloc; + + if ((s->type == SK_UDP) || (s->type == SK_IP)) + sk_set_min_rcvbuf(s, s->rbsize); } void @@ -1108,10 +1145,11 @@ sk_setup(sock *s) } #endif - if (s->vrf && (s->vrf != &default_vrf) && !s->iface) + if (s->vrf && (s->vrf != &default_vrf) && !s->iface && (s->type != SK_TCP)) { /* Bind socket to associated VRF interface. - This is Linux-specific, but so is SO_BINDTODEVICE. */ + This is Linux-specific, but so is SO_BINDTODEVICE. + For accepted TCP sockets it is inherited from the listening one. */ #ifdef SO_BINDTODEVICE struct ifreq ifr = {}; strcpy(ifr.ifr_name, s->vrf->name); @@ -1183,6 +1221,10 @@ sk_setup(sock *s) if (s->tos >= 0) if (sk_set_tos6(s, s->tos) < 0) return -1; + + if ((s->flags & SKF_UDP6_NO_CSUM_RX) && (s->type == SK_UDP)) + if (sk_set_udp6_no_csum_rx(s) < 0) + return -1; } /* Must be after sk_set_tos4() as setting ToS on Linux also mangles priority */ @@ -1190,6 +1232,9 @@ sk_setup(sock *s) if (sk_set_priority(s, s->priority) < 0) return -1; + if ((s->type == SK_UDP) || (s->type == SK_IP)) + sk_set_min_rcvbuf(s, s->rbsize); + return 0; } @@ -1680,7 +1725,7 @@ err: } int -sk_open_unix(sock *s, struct birdloop *loop, char *name) +sk_open_unix(sock *s, struct birdloop *loop, const char *name) { struct sockaddr_un sa; int fd; @@ -2572,7 +2617,7 @@ io_loop(void) } void -test_old_bird(char *path) +test_old_bird(const char *path) { int fd; struct sockaddr_un sa; diff --git a/sysdep/unix/log.c b/sysdep/unix/log.c index 98642e9c..9d77211b 100644 --- a/sysdep/unix/log.c +++ b/sysdep/unix/log.c @@ -51,7 +51,7 @@ static struct log_channel * _Atomic global_logs; /* Logging flags to validly prepare logging messages */ static _Atomic uint logging_flags; -static _Atomic uint logging_mask; +static _Atomic uint logging_mask = ~0U; #ifdef HAVE_SYSLOG_H #include @@ -189,9 +189,20 @@ log_commit(log_buffer *buf) memcpy(buf->buf.end - sizeof TOO_LONG, TOO_LONG, sizeof TOO_LONG); #undef TOO_LONG + struct log_channel *glogs = atomic_load_explicit(&global_logs, memory_order_acquire); + if (!glogs) + { + static struct log_channel initial_stderr_log = { + .rf = &rf_stderr, + .mask = ~0, + .prepare = BIT32_ALL(LBPP_TERMINAL, LBP_CLASS, LBP_MSG), + }; + + glogs = &initial_stderr_log; + } + for ( - struct log_channel *l = atomic_load_explicit(&global_logs, memory_order_acquire); - l; + struct log_channel *l = glogs; l; l = atomic_load_explicit(&l->next, memory_order_acquire) ) { diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c index 254690b1..8f26c639 100644 --- a/sysdep/unix/main.c +++ b/sysdep/unix/main.c @@ -30,6 +30,7 @@ #include "lib/event.h" #include "lib/locking.h" #include "lib/timer.h" +#include "lib/tlists.h" #include "lib/string.h" #include "nest/route.h" #include "nest/protocol.h" @@ -186,6 +187,8 @@ cf_read(byte *dest, uint len, int fd) return l; } +static void cli_preconfig(struct config *c); + void sysdep_preconfig(struct config *c) { @@ -200,14 +203,19 @@ sysdep_preconfig(struct config *c) read_iproute_table(c, PATH_IPROUTE_DIR "/rt_scopes", "ips_", 255); read_iproute_table(c, PATH_IPROUTE_DIR "/rt_tables", "ipt_", 0xffffffff); #endif + + cli_preconfig(c); } +static void cli_commit(struct config *new, struct config *old); + void sysdep_commit(struct config *new, struct config *old) { if (!new->shutdown) log_switch(0, &new->logfiles, new->syslog_name); + cli_commit(new, old); bird_thread_commit(new, old); } @@ -400,9 +408,28 @@ cmd_reconfig_status(void) * Command-Line Interface */ -static sock *cli_sk; -static char *path_control_socket = PATH_CONTROL_SOCKET; +static struct cli_config initial_control_socket_config = { + .name = PATH_CONTROL_SOCKET, + .mode = 0660, +}; +#define path_control_socket initial_control_socket_config.name +static struct cli_config *main_control_socket_config = NULL; + +#define TLIST_PREFIX cli_listener +#define TLIST_TYPE struct cli_listener +#define TLIST_ITEM n +#define TLIST_WANT_ADD_TAIL +#define TLIST_WANT_WALK +static struct cli_listener { + TLIST_DEFAULT_NODE; + sock *s; + struct cli_config *config; +} *main_control_socket = NULL; + +#include "lib/tlists.h" + +static TLIST_LIST(cli_listener) cli_listeners; static void cli_write(cli *c) @@ -523,7 +550,7 @@ cli_connect(sock *s, uint size UNUSED) s->rx_hook = cli_rx; s->tx_hook = cli_tx; s->err_hook = cli_err; - s->data = c = cli_new(s); + s->data = c = cli_new(s, ((struct cli_listener *) s->data)->config); s->pool = c->pool; /* We need to have all the socket buffers allocated in the cli pool */ s->fast_rx = 1; c->rx_pos = c->rx_buf; @@ -531,33 +558,116 @@ cli_connect(sock *s, uint size UNUSED) return 1; } -static void -cli_init_unix(uid_t use_uid, gid_t use_gid) +static struct cli_listener * +cli_listen(struct cli_config *cf) { - sock *s; - - cli_init(); - s = cli_sk = sk_new(cli_pool); + struct cli_listener *l = mb_allocz(cli_pool, sizeof *l); + l->config = cf; + sock *s = l->s = sk_new(cli_pool); s->type = SK_UNIX_PASSIVE; s->rx_hook = cli_connect; s->err_hook = cli_connect_err; + s->data = l; s->rbsize = 1024; s->fast_rx = 1; /* Return value intentionally ignored */ - unlink(path_control_socket); + unlink(cf->name); - if (sk_open_unix(s, &main_birdloop, path_control_socket) < 0) - die("Cannot create control socket %s: %m", path_control_socket); + if (sk_open_unix(s, &main_birdloop, cf->name) < 0) + { + log(L_ERR "Cannot create control socket %s: %m", cf->name); + return NULL; + } - if (use_uid || use_gid) - if (chown(path_control_socket, use_uid, use_gid) < 0) - die("chown: %m"); + if (cf->uid || cf->gid) + if (chown(cf->name, cf->uid, cf->gid) < 0) + { + log(L_ERR "Cannot chown control socket %s: %m", cf->name); + return NULL; + } - if (chmod(path_control_socket, 0660) < 0) - die("chmod: %m"); + if (chmod(cf->name, cf->mode) < 0) + { + log(L_ERR "Cannot chmod control socket %s: %m", cf->name); + return NULL; + } + + cli_listener_add_tail(&cli_listeners, l); + + return l; } +static void +cli_deafen(struct cli_listener *l) +{ + rfree(l->s); + unlink(l->config->name); + cli_listener_rem_node(&cli_listeners, l); + mb_free(l); +} + +static void +cli_init_unix(uid_t use_uid, gid_t use_gid) +{ + ASSERT_DIE(main_control_socket_config == NULL); + + main_control_socket_config = &initial_control_socket_config; + main_control_socket_config->uid = use_uid; + main_control_socket_config->gid = use_gid; + + ASSERT_DIE(main_control_socket == NULL); + main_control_socket = cli_listen(main_control_socket_config); + if (!main_control_socket) + die("Won't run without control socket"); +} + +static void +cli_preconfig(struct config *c) +{ + if (!main_control_socket_config) + return; + + struct cli_config *ccf = mb_alloc(cli_pool, sizeof *ccf); + memcpy(ccf, main_control_socket_config, sizeof *ccf); + ccf->n = (struct cli_config_node) {}; + ccf->config = c; + cli_config_add_tail(&c->cli, ccf); +} + +static void +cli_commit(struct config *new, struct config *old) +{ + if (new->shutdown) + { + /* Keep the main CLI throughout the shutdown */ + initial_control_socket_config.config = new; + main_control_socket->config = &initial_control_socket_config; + } + + WALK_TLIST(cli_config, c, &new->cli) + { + _Bool seen = 0; + WALK_TLIST(cli_listener, l, &cli_listeners) + if (l->config->config != new) + if (!strcmp(l->config->name, c->name)) + { + ASSERT_DIE(l->config->config == old); + l->config = c; + seen = 1; + break; + } + + if (!seen) + cli_listen(c); + } + + WALK_TLIST_DELSAFE(cli_listener, l, &cli_listeners) + if (l->config->config != new) + cli_deafen(l); +} + + /* * PID file */ @@ -635,7 +745,7 @@ void sysdep_shutdown_done(void) { unlink_pid_file(); - unlink(path_control_socket); + cli_deafen(main_control_socket); log_msg(L_FATAL "Shutdown completed"); exit(0); } @@ -920,6 +1030,8 @@ main(int argc, char **argv) uid_t use_uid = get_uid(use_user); gid_t use_gid = get_gid(use_group); + cli_init(); + if (!parse_and_exit) { test_old_bird(path_control_socket); diff --git a/sysdep/unix/unix.h b/sysdep/unix/unix.h index e7c4c143..3f0f2a9b 100644 --- a/sysdep/unix/unix.h +++ b/sysdep/unix/unix.h @@ -114,7 +114,7 @@ extern volatile sig_atomic_t async_shutdown_flag; void io_init(void); void io_loop(void); void io_log_dump(void); -int sk_open_unix(struct birdsock *s, struct birdloop *, char *name); +int sk_open_unix(struct birdsock *s, struct birdloop *, const char *name); enum rf_mode { RF_APPEND = 1, @@ -130,7 +130,7 @@ int rf_fileno(struct rfile *f); extern struct rfile rf_stderr; -void test_old_bird(char *path); +void test_old_bird(const char *path); ip_addr resolve_hostname(const char *host, int type, const char **err_msg); /* krt.c bits */