From: Alberto Leiva Popper Date: Thu, 4 Apr 2019 18:45:23 +0000 (-0600) Subject: Merge remote-tracking branch 'rtrserver/master' X-Git-Tag: v0.0.2~52 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3469ffb09f3f6eb1b012da2d8b66c7123da89a63;p=thirdparty%2FFORT-validator.git Merge remote-tracking branch 'rtrserver/master' Checkpoint: console validation is stable, server is not. --- 3469ffb09f3f6eb1b012da2d8b66c7123da89a63 diff --cc .gitignore index b9aa85d0,61267617..b902dc8a --- a/.gitignore +++ b/.gitignore @@@ -35,7 -35,7 +35,7 @@@ *.i*86 *.x86_64 *.hex - rpki_validator -rtr_server ++src/fort # Debug files *.dSYM/ diff --cc Makefile.am index 7652996c,f2b73326..aece299a --- a/Makefile.am +++ b/Makefile.am @@@ -1,4 -1,14 +1,15 @@@ + # GNU wants us to include some files that we really don't want; therefore + # "foreign". The files are + # + # - AUTHORS: This should be inferred from the (automatic) git history, not some + # error prone, manually-maintained file! + # - ChangeLog: This is included in the main page of the site, which can be found + # in the gh-pages branch. Don't want to (nor should I) repeat myself. + # - NEWS: Same as ChangeLog. + # - README: We prefer the much gayer "README.md" version, so no thanks. + # + # Man, GNU conventions need a 21 century overhaul badly. AUTOMAKE_OPTIONS = foreign - SUBDIRS = src $(MAYBE_TESTS) - DIST_SUBDIRS = src -SUBDIRS = src man test ++SUBDIRS = src man $(MAYBE_TESTS) ++DIST_SUBDIRS = src man diff --cc README.md index c0379a5b,8cea4709..3b1eeb3e --- a/README.md +++ b/README.md @@@ -1,6 -1,8 +1,6 @@@ -# FORT RTR server +# FORT - An RPKI Validator. -An RTR server compliant to [RFC 6810](https://tools.ietf.org/html/rfc6810) (at least for now). - -More documentation about FORT at FORT's site [https://nicmx.github.io/FORT-validator/](https://nicmx.github.io/FORT-validator/). ++An RPKI Validator and RTR Server. **Still under development!** @@@ -8,11 -10,9 +8,13 @@@ Dependencies: +1. libcrypto ([LibreSSL](http://www.libressl.org/) or [OpenSSL](https://www.openssl.org/)) - 2. [tomlc99](https://github.com/cktan/tomlc99) + 1. [jansson](https://github.com/akheron/jansson) +3. [libcmscodec](https://github.com/ydahhrk/libcmscodec) +4. [rsync](http://rsync.samba.org/) + After all the dependencies are installed, run: ++ ``` ./autogen.sh ./configure @@@ -20,4 -20,32 +22,36 @@@ mak make install ``` -## Configuration +More documentation at [https://nicmx.github.io/FORT-validator/](https://nicmx.github.io/FORT-validator/). ++ ++## RTR Configuration ++ ++> TODO Move this + + The RTR server reads the configuration from a JSON file, learn about it at FORT's site [RTR Server arguments](https://nicmx.github.io/FORT-validator/doc/rtr-server.html). + + Here's an example of a valid configuration file (assuming that the CSV file returned by FORT's validator is located at `/tmp/fort/roas.csv`): + + ```javascript + { + "listen": { + "address": "127.0.0.1", + "port": "8323", + "queue": 10 + }, + "vrps": { + "location": "/tmp/fort/roas.csv", + "checkInterval": 60 + } + } + ``` + + ## Execution + + The executable needs only one argument: the location of the configuration file. So, assuming that the configuration file is located at `/home/fort/rtr.conf`, use the flag `-f` to indicate such location and run the server: + + ``` + $ rtr_server -f /home/fort/rtr.conf + ``` + -That's it! The server will be listening on the configured port for any RTR client that wishes to establish a connection and exchange for validated ROA payloads. ++That's it! The server will be listening on the configured port for any RTR client that wishes to establish a connection and exchange for validated ROA payloads. diff --cc configure.ac index da71f204,2a6aa36a..1846be3a --- a/configure.ac +++ b/configure.ac @@@ -3,7 -3,7 +3,7 @@@ AC_PREREQ([2.69]) # TODO change the bug report address - AC_INIT([rpki-validator], [0.0.1], [ydahhrk@gmail.com]) -AC_INIT([rtr-server], [0.0.1], [ydahhrk@gmail.com]) ++AC_INIT([fort], [0.0.1], [ydahhrk@gmail.com]) AC_CONFIG_SRCDIR([src/main.c]) AM_INIT_AUTOMAKE([subdir-objects]) @@@ -21,58 -23,12 +21,49 @@@ AC_CHECK_HEADER_STDBOO # Checks for library functions. AC_FUNC_MALLOC AC_CHECK_FUNCS([memset socket]) -AC_SEARCH_LIBS([pthread_create], [pthread]) +AC_SEARCH_LIBS([pthread_create], [pthread], [], + [AC_MSG_ERROR([unable to find the pthread() function])] +) +AC_SEARCH_LIBS([ber_decode], [cmscodec], [], + [AC_MSG_ERROR([unable to find the ber_decode() function])] +) +AC_SEARCH_LIBS([d2i_X509_bio], [crypto], [], + [AC_MSG_ERROR([unable to find the d2i_X509_bio() function])] +) - AC_SEARCH_LIBS([toml_parse], [toml], [], - [AC_MSG_ERROR([unable to find the toml_parse() function])] - ) +AC_SEARCH_LIBS([backtrace],[execinfo],[], + [AC_MSG_ERROR([unable to find backtrace() function])] +) + +# Dependencies managed by pkg-config -# Uhhh... this one starts with "PKG_" so it's probably different. -# No idea. -PKG_CHECK_MODULES([CHECK], [check]) + PKG_CHECK_MODULES([JANSSON], [jansson]) + +# By the way: Apparently PKG_CHECK_MODULES is poor practice now. I can't tell; +# it's always the same guy complaining about it in Stack Overflow. +# (Main one: https://stackoverflow.com/questions/10220946) +# But I couldn't make check work with AC_SEARCH_LIBS, and (probably due to +# typical obscure bullshit autotools reasoning) I have no idea why. + +AC_ARG_WITH( + [unit-tests], + AS_HELP_STRING( + [--with-unit-tests], - [Generate unit tests. (Requires pkg-config and check)] ++ [Generate unit tests. (Requires check)] + ) +) + +# https://www.gnu.org/software/automake/manual/html_node/Conditional-Subdirectories.html +# "Subdirectories with AM_CONDITIONAL" never worked for me. The problem might +# be that it puts `MAYBE_TESTS` in `.PRECIOUS`, which maybe is a bug in the +# autotools. (I honestly can't tell. They are so incredibly poorly designed.) +# The code below is implemented as "Subdirectories with AC_SUBST." - # - # https://autotools.io/pkgconfig/pkg_check_modules.html#pkgconfig.pkg_check_modules.optional - # Note: Since Check is the only current user of pkg-config, I don't want to - # force pkg-config as a dependency; it should only be needed by people who want - # to unit test. That's why I chose to use an `if` (which skips the - # `PKG_CHECK_MODULES` on false) instead of an `AS_IF` (which evaluates all paths - # apparently). In other words, if you ever want to add another pkg-config - # dependency, you will need the `AS_IF` version. +if test "x$with_unit_tests" = "xyes"; then + PKG_CHECK_MODULES([CHECK], [check]) + MAYBE_TESTS=test +else + MAYBE_TESTS= +fi +AC_SUBST([MAYBE_TESTS]) + # Spit out the makefiles. - AC_OUTPUT(Makefile src/Makefile test/Makefile) + AC_OUTPUT(Makefile src/Makefile man/Makefile test/Makefile) diff --cc man/rpki-validator.8 index 427a6eb9,00000000..c5c946b8 mode 100644,000000..100644 --- a/man/rpki-validator.8 +++ b/man/rpki-validator.8 @@@ -1,161 -1,0 +1,161 @@@ +.TH rpki-validator 8 2019-03-5 v0.0.1-beta "RPKI certificate path validator" + +.SH NAME +rpki-validator - Actually still unnamed officially. + +.SH OPTIONS + +--help +.RS 4 +Print long usage message. +.RE +.P + +--usage +.RS 4 +Print short usage message. +.RE +.P + +--version +.RS 4 +Print program version. +.RE +.P + +--configuration-file= +.RS 4 - Path to a TOML file from which additional configuration will be read. ++Path to a JSON file from which additional configuration will be read. +.RE +.P + +--local-repository= +.RS 4 +Path to a directory where the local cache of the repository will be stored +and/or read. +.RE +.P + +--sync-strategy=(off|strict|root|root-except-ta) +.RS 4 +RSYNC download strategy. +.P +off +.RS 4 +Skip all RSYNCs. (Validate the existing cache repository pointed by --local-repository.) +.RE +.P +strict +.RS 4 +RSYNC every repository publication point separately. Only skip publication +points that have already been downloaded during the current validation cycle. +(Assuming each synchronization is recursive.) +.P +For example, suppose the validator gets certificates whose caRepository access +methods (in their Subject Information Access extensions) point to the following +publication points: +.P +1. rsync://rpki.example.com/foo/bar/ +.br +2. rsync://rpki.example.com/foo/qux/ +.br +3. rsync://rpki.example.com/foo/bar/ +.br +4. rsync://rpki.example.com/foo/corge/grault/ +.br +5. rsync://rpki.example.com/foo/corge/ +.br +6. rsync://rpki.example.com/foo/corge/waldo/ +.P +A validator following the `strict` strategy would download `bar`, download +`qux`, skip `bar`, download `corge/grault`, download `corge` and skip +`corge/waldo`. +.P +This is the slowest, but also the strictly correct sync strategy. +.RE +.P +root +.RS 4 +For each publication point found, guess the root of its repository and RSYNC +that instead. Then skip any subsequent children of said root. +.P +(To guess the root of a repository, the validator counts four slashes, and +prunes the rest of the URL.) +.P +Reusing the caRepository URLs from the `strict` strategy (above) as example, a +validator following the `root` strategy would download +`rsync://rpki.example.com/foo`, and then skip everything else. +.P +Assuming that the repository is specifically structured to be found within as +few roots as possible, and they contain minimal RPKI-unrelated noise files, this +is the fastest synchronization strategy. At time of writing, this is true for +all the current official repositories. +.RE +.P +root-except-ta +.RS 4 +Synchronizes the root certificate (the one pointed by the TAL) in 'strict' mode, +and once it's validated, synchronizes the rest of the repository in 'root' mode. +.P +Useful if you want 'root', but the root certificate is separated from the rest +of the repository. Also useful if you don't want the validator to download the +entire repository without first confirming the integrity and legitimacy of the +root certificate. +.RE +.RE +.P + +--maximum-certificate-depth= +.RS 4 +Maximum allowable certificate chain length. +.P +(Required to prevent loops and "other degenerate forms of the logical RPKI +hierarchy." (RFC 6481)) +.RE +.P + +--tal= +.RS 4 +Path to the TAL file the validation will sprawl from. +.P +The TAL ("Trust Anchor Locator") is a text file that lists a few URLs which can +be used to access the "Trust Anchor" (the root of a particular RPKI tree) and +its public key. (See RFC 7730.) +.RE +.P + +--shuffle-uris +.RS 4 +Shuffle URIs in the TAL before accessing them. +.RE +.P + +--color-output +.RS 4 +Print ANSI color codes. +.RE +.P + +--output-file-name-format=(global-url|local-path|file-name) +.RS 4 +Decides which version of file names should be printed during most debug/error +messages. +.P +Suppose a certificate was downloaded from `rsync://rpki.example.com/foo/bar/baz.cer` into the local cache `repository/`: +.P +global-url +.RS 4 +will print the certificate's name as `rsync://rpki.example.com/foo/bar/baz.cer`. +.RE +.P +local-path +.RS 4 +will print the certificate's name as `repository/rpki.example.com/foo/bar/baz.cer`. +.RE +.P +file-name +.RS 4 +will print the certificate's name as `baz.cer`. +.RE +.P +.RE diff --cc src/Makefile.am index c4bb88b7,74a897ab..c6b56a88 --- a/src/Makefile.am +++ b/src/Makefile.am @@@ -1,80 -1,38 +1,97 @@@ -AM_CFLAGS = -pedantic -Wall -std=gnu11 -O3 -AM_LDFLAGS = - -bin_PROGRAMS = rtr_server - -rtr_server_SOURCES = main.c -rtr_server_SOURCES += address.c address.h -rtr_server_SOURCES += array_list.h -rtr_server_SOURCES += clients.c clients.h -rtr_server_SOURCES += common.c common.h -rtr_server_SOURCES += configuration.c configuration.h -rtr_server_SOURCES += csv.c csv.h -rtr_server_SOURCES += line_file.c line_file.h -rtr_server_SOURCES += notify.c notify.h -rtr_server_SOURCES += updates_daemon.c updates_daemon.h -rtr_server_SOURCES += vrps.c vrps.h - -rtr_server_SOURCES += rtr/err_pdu.c rtr/err_pdu.h -rtr_server_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h -rtr_server_SOURCES += rtr/pdu_sender.c rtr/pdu_sender.h -rtr_server_SOURCES += rtr/pdu_serializer.c rtr/pdu_serializer.h -rtr_server_SOURCES += rtr/pdu.c rtr/pdu.h -rtr_server_SOURCES += rtr/primitive_reader.c rtr/primitive_reader.h -rtr_server_SOURCES += rtr/primitive_writer.c rtr/primitive_writer.h -rtr_server_SOURCES += rtr/rtr.c rtr/rtr.h - -rtr_server_LDADD = ${JANSSON_LIBS} +# Comment these out during releases. +# Also increase optimization I guess (-O0 -> -On) +CFLAGS_DEBUG = -DDEBUG +LDFLAGS_DEBUG = -rdynamic + - bin_PROGRAMS = rpki_validator - - rpki_validator_SOURCES = main.c - - rpki_validator_SOURCES += address.h address.c - rpki_validator_SOURCES += algorithm.h algorithm.c - rpki_validator_SOURCES += array_list.h - rpki_validator_SOURCES += certificate_refs.h certificate_refs.c - rpki_validator_SOURCES += common.h - rpki_validator_SOURCES += config.h config.c - rpki_validator_SOURCES += debug.h debug.c - rpki_validator_SOURCES += extension.h extension.c - rpki_validator_SOURCES += file.h file.c - rpki_validator_SOURCES += line_file.h line_file.c - rpki_validator_SOURCES += log.h log.c - rpki_validator_SOURCES += nid.h nid.c - rpki_validator_SOURCES += random.h random.c - rpki_validator_SOURCES += resource.h resource.c - rpki_validator_SOURCES += rpp.h rpp.c - rpki_validator_SOURCES += sorted_array.h sorted_array.c - rpki_validator_SOURCES += state.h state.c - rpki_validator_SOURCES += str.h str.c - rpki_validator_SOURCES += thread_var.h thread_var.c - rpki_validator_SOURCES += uri.h uri.c - rpki_validator_SOURCES += toml_handler.h toml_handler.c - - rpki_validator_SOURCES += rsync/rsync.h rsync/rsync.c - - rpki_validator_SOURCES += asn1/content_info.h asn1/content_info.c - rpki_validator_SOURCES += asn1/decode.h asn1/decode.c - rpki_validator_SOURCES += asn1/oid.h asn1/oid.c - rpki_validator_SOURCES += asn1/signed_data.h asn1/signed_data.c - - rpki_validator_SOURCES += config/boolean.c config/boolean.h - rpki_validator_SOURCES += config/filename_format.h config/filename_format.c - rpki_validator_SOURCES += config/out_file.h config/out_file.c - rpki_validator_SOURCES += config/str.c config/str.h - rpki_validator_SOURCES += config/string_array.h config/string_array.c - rpki_validator_SOURCES += config/sync_strategy.h config/sync_strategy.c - rpki_validator_SOURCES += config/types.h - rpki_validator_SOURCES += config/uint.c config/uint.h - - rpki_validator_SOURCES += crypto/base64.h crypto/base64.c - rpki_validator_SOURCES += crypto/hash.h crypto/hash.c - - rpki_validator_SOURCES += object/certificate.h object/certificate.c - rpki_validator_SOURCES += object/crl.h object/crl.c - rpki_validator_SOURCES += object/ghostbusters.h object/ghostbusters.c - rpki_validator_SOURCES += object/manifest.h object/manifest.c - rpki_validator_SOURCES += object/name.h object/name.c - rpki_validator_SOURCES += object/roa.h object/roa.c - rpki_validator_SOURCES += object/signed_object.h object/signed_object.c - rpki_validator_SOURCES += object/tal.h object/tal.c - rpki_validator_SOURCES += object/vcard.h object/vcard.c - - rpki_validator_SOURCES += resource/ip4.h resource/ip4.c - rpki_validator_SOURCES += resource/ip6.h resource/ip6.c - rpki_validator_SOURCES += resource/asn.h resource/asn.c - - rpki_validator_CFLAGS = -Wall ++bin_PROGRAMS = fort ++ ++fort_SOURCES = main.c ++ ++fort_SOURCES += address.h address.c ++fort_SOURCES += algorithm.h algorithm.c ++fort_SOURCES += array_list.h ++fort_SOURCES += certificate_refs.h certificate_refs.c ++fort_SOURCES += common.h ++fort_SOURCES += config.h config.c ++fort_SOURCES += debug.h debug.c ++fort_SOURCES += extension.h extension.c ++fort_SOURCES += file.h file.c ++fort_SOURCES += line_file.h line_file.c ++fort_SOURCES += log.h log.c ++fort_SOURCES += nid.h nid.c ++fort_SOURCES += random.h random.c ++fort_SOURCES += resource.h resource.c ++fort_SOURCES += rpp.h rpp.c ++fort_SOURCES += sorted_array.h sorted_array.c ++fort_SOURCES += state.h state.c ++fort_SOURCES += str.h str.c ++fort_SOURCES += thread_var.h thread_var.c ++fort_SOURCES += uri.h uri.c ++fort_SOURCES += json_handler.h json_handler.c ++ ++fort_SOURCES += rsync/rsync.h rsync/rsync.c ++ ++fort_SOURCES += asn1/content_info.h asn1/content_info.c ++fort_SOURCES += asn1/decode.h asn1/decode.c ++fort_SOURCES += asn1/oid.h asn1/oid.c ++fort_SOURCES += asn1/signed_data.h asn1/signed_data.c ++ ++fort_SOURCES += config/boolean.c config/boolean.h ++fort_SOURCES += config/filename_format.h config/filename_format.c ++fort_SOURCES += config/str.c config/str.h ++fort_SOURCES += config/string_array.h config/string_array.c ++fort_SOURCES += config/sync_strategy.h config/sync_strategy.c ++fort_SOURCES += config/types.h ++fort_SOURCES += config/uint.c config/uint.h ++fort_SOURCES += config/uint32.c config/uint32.h ++ ++fort_SOURCES += crypto/base64.h crypto/base64.c ++fort_SOURCES += crypto/hash.h crypto/hash.c ++ ++fort_SOURCES += object/certificate.h object/certificate.c ++fort_SOURCES += object/crl.h object/crl.c ++fort_SOURCES += object/ghostbusters.h object/ghostbusters.c ++fort_SOURCES += object/manifest.h object/manifest.c ++fort_SOURCES += object/name.h object/name.c ++fort_SOURCES += object/roa.h object/roa.c ++fort_SOURCES += object/signed_object.h object/signed_object.c ++fort_SOURCES += object/tal.h object/tal.c ++fort_SOURCES += object/vcard.h object/vcard.c ++ ++fort_SOURCES += resource/ip4.h resource/ip4.c ++fort_SOURCES += resource/ip6.h resource/ip6.c ++fort_SOURCES += resource/asn.h resource/asn.c ++ ++fort_SOURCES += array_list.h ++fort_SOURCES += clients.c clients.h ++fort_SOURCES += common.c common.h ++fort_SOURCES += notify.c notify.h ++fort_SOURCES += updates_daemon.c updates_daemon.h ++fort_SOURCES += vrps.c vrps.h ++ ++fort_SOURCES += rtr/err_pdu.c rtr/err_pdu.h ++fort_SOURCES += rtr/pdu_handler.c rtr/pdu_handler.h ++fort_SOURCES += rtr/pdu_sender.c rtr/pdu_sender.h ++fort_SOURCES += rtr/pdu_serializer.c rtr/pdu_serializer.h ++fort_SOURCES += rtr/pdu.c rtr/pdu.h ++fort_SOURCES += rtr/primitive_reader.c rtr/primitive_reader.h ++fort_SOURCES += rtr/primitive_writer.c rtr/primitive_writer.h ++fort_SOURCES += rtr/rtr.c rtr/rtr.h ++ ++fort_CFLAGS = -Wall +# Feel free to temporarily remove this one if you're not using gcc 7.3.0. - rpki_validator_CFLAGS += $(GCC_WARNS) - rpki_validator_CFLAGS += -std=gnu99 -O0 -g $(CFLAGS_DEBUG) - rpki_validator_LDFLAGS = $(LDFLAGS_DEBUG) ++fort_CFLAGS += $(GCC_WARNS) ++fort_CFLAGS += -std=gnu99 -O0 -g $(CFLAGS_DEBUG) ++fort_LDFLAGS = $(LDFLAGS_DEBUG) ++fort_LDADD = ${JANSSON_LIBS} # I'm tired of scrolling up, but feel free to comment this out. --GCC_WARNS = -fmax-errors=1 ++#GCC_WARNS = -fmax-errors=1 # Please ready some arguments if you want to permanently remove some of these # flags. I gave a quick read to the documentation of all of these warnings, and # generally liked each of them. --GCC_WARNS += -Wpedantic -pedantic-errors -Waddress -Walloc-zero -Walloca ++GCC_WARNS = -Wpedantic -pedantic-errors -Waddress -Walloc-zero -Walloca GCC_WARNS += -Wno-aggressive-loop-optimizations -Warray-bounds=2 -Wbool-compare GCC_WARNS += -Wbool-operation -Wno-builtin-declaration-mismatch -Wcast-align GCC_WARNS += -Wcast-qual -Wchar-subscripts -Wchkp -Wclobbered -Wcomment @@@ -137,4 -95,4 +154,4 @@@ GCC_WARNS += -Wframe-larger-than=1024 - # Can't use because of dependencies: -Waggregate-return # Want to add, but needs work: -Wconversion, -Wsign-compare, -Wsign-conversion # Seem to require other compiler features: -Wchkp, -Wstack-protector, --# -Wstrict-aliasing, -Wstrict-overflow ++# -Wstrict-aliasing diff --cc src/array_list.h index 213e322a,ff00c934..0453c1f1 --- a/src/array_list.h +++ b/src/array_list.h @@@ -1,7 -1,9 +1,9 @@@ #ifndef SRC_ARRAY_LIST_H_ #define SRC_ARRAY_LIST_H_ -#include #include + #include ++#include "log.h" #define ARRAY_LIST(name, elem_type) \ struct name { \ @@@ -20,7 -22,7 +22,7 @@@ list->len = 0; \ list->array = malloc(list->capacity \ * sizeof(elem_type)); \ -- return (list->array != NULL) ? 0 : -ENOMEM; \ ++ return (list->array != NULL) ? 0 : pr_enomem(); \ } \ \ static void \ diff --cc src/clients.c index 00000000,0dcfb8f4..b3752af7 mode 000000,100644..100644 --- a/src/clients.c +++ b/src/clients.c @@@ -1,0 -1,171 +1,171 @@@ + #include "clients.h" + + #include "array_list.h" + #include "common.h" + + #define SADDR_IN(addr) ((struct sockaddr_in *)addr) + #define SADDR_IN6(addr) ((struct sockaddr_in6 *)addr) + + ARRAY_LIST(clientsdb, struct client) + + struct clientsdb clients_db; + + /* Read and Write locks */ + sem_t rlock, wlock; + + /* Readers counter */ + unsigned int rcounter; + + int + clients_db_init(void) + { + int error; + + error = clientsdb_init(&clients_db); + if (error) - warnx( "Clients DB couldn't be initialized"); ++ return error; + + sem_init(&rlock, 0, 1); + sem_init(&wlock, 0, 1); + rcounter = 0; + + return error; + } + + static struct client * + get_client(struct sockaddr_storage *addr) + { + struct client *ptr; + + read_lock(&rlock, &wlock, &rcounter); + ARRAYLIST_FOREACH(&clients_db, ptr) + if (ptr->sin_family == addr->ss_family) { + if (ptr->sin_family == AF_INET) { - if (ptr->sin_addr.s_addr == ++ if (ptr->addr.sin.s_addr == + SADDR_IN(addr)->sin_addr.s_addr && + ptr->sin_port == + SADDR_IN(addr)->sin_port) { + read_unlock(&rlock, &wlock, &rcounter); + return ptr; + } + } else if (ptr->sin_family == AF_INET6) + if (IN6_ARE_ADDR_EQUAL( - ptr->sin6_addr.s6_addr32, ++ ptr->addr.sin6.s6_addr32, + SADDR_IN6(addr)->sin6_addr.s6_addr32) && + ptr->sin_port == + SADDR_IN6(addr)->sin6_port) { + read_unlock(&rlock, &wlock, &rcounter); + return ptr; + } + } + read_unlock(&rlock, &wlock, &rcounter); + return NULL; + } + + static int + create_client(int fd, struct sockaddr_storage *addr, u_int8_t rtr_version) + { + struct client client; + int error; + + client.fd = fd; + client.sin_family = addr->ss_family; + if (addr->ss_family == AF_INET) { - client.sin_addr = SADDR_IN(addr)->sin_addr; ++ client.addr.sin = SADDR_IN(addr)->sin_addr; + client.sin_port = SADDR_IN(addr)->sin_port; + } else if (addr->ss_family == AF_INET6) { - client.sin6_addr = SADDR_IN6(addr)->sin6_addr; ++ client.addr.sin6 = SADDR_IN6(addr)->sin6_addr; + client.sin_port = SADDR_IN6(addr)->sin6_port; + } + client.rtr_version = rtr_version; + + sem_wait(&wlock); + error = clientsdb_add(&clients_db, &client); + sem_post(&wlock); + + return error; + } + + /* + * If the ADDR isn't already stored, store it; otherwise update its file + * descriptor. + * + * Return the creation/update result. + * + * Code error -EINVAL will be returned when a client exists but its RTR version + * isn't the same as in the DB. + */ + int + update_client(int fd, struct sockaddr_storage *addr, u_int8_t rtr_version) + { + struct client *client; + client = get_client(addr); + + if (client == NULL) + return create_client(fd, addr, rtr_version); + + /* + * Isn't ready to handle distinct version on clients reconnection, but + * for now there's no problem since only RTR v0 is supported. + */ + if (client->rtr_version != rtr_version) + return -EINVAL; + + client->fd = fd; + return 0; + } + + size_t + client_list(struct client **clients) + { + size_t len; + + read_lock(&rlock, &wlock, &rcounter); + *clients = clients_db.array; + len = clients_db.len; + read_unlock(&rlock, &wlock, &rcounter); + + return len; + } + + static void + client_destroy(struct client *client) + { + /* Didn't allocate something, so do nothing */ + } + + void + clients_forget(int fd) + { + struct clientsdb *new_db; + struct client *ptr; + + new_db = malloc(sizeof(struct clientsdb)); + if (new_db == NULL) { - warnx("Couldn't allocate new clients DB"); - return; ++ pr_err("Couldn't allocate new clients DB"); ++ return; /* TODO This is not acceptable... */ + } + clientsdb_init(new_db); + read_lock(&rlock, &wlock, &rcounter); + ARRAYLIST_FOREACH(&clients_db, ptr) + if (ptr->fd != fd) + clientsdb_add(new_db, ptr); + read_unlock(&rlock, &wlock, &rcounter); + + sem_wait(&wlock); + clientsdb_cleanup(&clients_db, client_destroy); + clients_db = *new_db; + sem_post(&wlock); + free(new_db); + } + + void + clients_db_destroy(void) + { + sem_wait(&wlock); + clientsdb_cleanup(&clients_db, client_destroy); + sem_post(&wlock); + + sem_destroy(&wlock); + sem_destroy(&rlock); + } diff --cc src/clients.h index 00000000,b80be4c3..571f15ca mode 000000,100644..100644 --- a/src/clients.h +++ b/src/clients.h @@@ -1,0 -1,24 +1,24 @@@ + #ifndef SRC_CLIENTS_H_ + #define SRC_CLIENTS_H_ + + #include + + struct client { + int fd; + sa_family_t sin_family; + union { - struct in_addr sin_addr; - struct in6_addr sin6_addr; - }; ++ struct in_addr sin; ++ struct in6_addr sin6; ++ } addr; + in_port_t sin_port; + u_int8_t rtr_version; + }; + + int clients_db_init(void); + int update_client(int, struct sockaddr_storage *, u_int8_t); + size_t client_list(struct client **); + + void clients_forget(int); + void clients_db_destroy(void); + + #endif /* SRC_CLIENTS_H_ */ diff --cc src/common.h index b56db53d,042b5bd8..b8ade4bc --- a/src/common.h +++ b/src/common.h @@@ -1,18 -1,19 +1,29 @@@ -#ifndef _SRC_COMMON_H_ -#define _SRC_COMMON_H_ +#ifndef SRC_RTR_COMMON_H_ +#define SRC_RTR_COMMON_H_ + #include + -#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) - -#define EUNIMPLEMENTED 566456 +/* "I think that this is not supposed to be implemented." */ +#define ENOTSUPPORTED 3172 +/* "I haven't implemented this yet." */ +#define ENOTIMPLEMENTED 3173 +/* + * "URI was not RSYNC; ignore it." + * Not really an error. The RFCs usually declare URI lists; usually only one of + * them is required to be RSYNC and the others should be skipped (until we + * start supporting them.) + */ +#define ENOTRSYNC 3174 - #define ARRAY_LEN(array) (sizeof(array) / sizeof(array[0])) + /* - * FYI: The error functions are warn() and warnx(). - * warn() automatically appends the errno string message (strerror()), warnx() - * does not. ++ * If you're wondering why I'm not using -abs(error), it's because abs(INT_MIN) ++ * overflows, so gcc complains sometimes. + */ ++#define ENSURE_NEGATIVE(error) (((error) < 0) ? (error) : -(error)) ++ ++#define ARRAY_LEN(array) (sizeof(array) / sizeof((array)[0])) + + void read_lock(sem_t *, sem_t *, unsigned int *); + void read_unlock(sem_t *, sem_t *, unsigned int *); -#endif /* _SRC_COMMON_H_ */ +#endif /* SRC_RTR_COMMON_H_ */ diff --cc src/config.c index 6c1c69fa,00000000..6677388f mode 100644,000000..100644 --- a/src/config.c +++ b/src/config.c @@@ -1,640 -1,0 +1,728 @@@ +#include "config.h" + +#include +#include +#include +#include +#include ++#include + +#include "common.h" ++#include "json_handler.h" +#include "log.h" - #include "toml_handler.h" +#include "config/boolean.h" - #include "config/out_file.h" +#include "config/str.h" +#include "config/uint.h" - - /** - * Please note that this is actually two `for`s stacked together, so don't use - * `break` nor `continue` to get out. - */ - #define FOREACH_OPTION(groups, grp, opt, type) \ - for (grp = groups; grp->name != NULL; grp++) \ - for (opt = grp->options; opt->id != 0; opt++) \ - if ((opt->availability == 0) || \ - (opt->availability & type)) ++#include "config/uint32.h" + +/** + * To add a member to this structure, + * + * 1. Add it. + * 2. Add its metadata somewhere in @groups. + * 3. Add default value to set_default_values(). + * 4. Create the getter. + * + * Assuming you don't need to create a data type, that should be all. + */ +struct rpki_config { - /** TAL file name/location. */ ++ /** TAL file name or directory. TODO support directories */ + char *tal; + /** Path of our local clone of the repository */ + char *local_repository; + /** Synchronization (currently only RSYNC) download strategy. */ + enum sync_strategy sync_strategy; + /** - * Shuffle uris in tal? - * (https://tools.ietf.org/html/rfc7730#section-3, last paragraphs) ++ * Handle TAL URIs in random order? ++ * (https://tools.ietf.org/html/rfc7730#section-3, last ++ * paragraphs) + */ - bool shuffle_uris; ++ bool shuffle_tal_uris; + /** + * rfc6487#section-7.2, last paragraph. + * Prevents arbitrarily long paths and loops. + */ + unsigned int maximum_certificate_depth; + ++ struct { ++ /** The listener address of the RTR server. */ ++ char *address; ++ /** TODO document */ ++ char *port; ++ /** Maximum accepted client connections */ ++ unsigned int queue; ++ ++ /** Interval used to look for updates at VRPs location */ ++ /* TODO rename */ ++ unsigned int vrps_check_interval; ++ ++ /** Intervals use at RTR v1 End of data PDU **/ ++ u_int32_t refresh_interval; ++ u_int32_t retry_interval; ++ u_int32_t expire_interval; ++ } server; ++ + struct { + char *program; + struct { + struct string_array flat; + struct string_array recursive; + } args; + } rsync; + + struct { + /** Print ANSI color codes? */ + bool color; + /** Format in which file names will be printed. */ + enum filename_format filename_format; - /** Output stream where the valid ROAs will be dumped. */ - struct config_out_file roa_output; - } output; ++ } log; +}; + +static void print_usage(FILE *, bool); + +#define DECLARE_HANDLE_FN(name) \ + static int name( \ + struct option_field const *, \ + char * \ + ) +DECLARE_HANDLE_FN(handle_help); +DECLARE_HANDLE_FN(handle_usage); +DECLARE_HANDLE_FN(handle_version); - DECLARE_HANDLE_FN(handle_toml); ++DECLARE_HANDLE_FN(handle_json); + +static char const *program_name; +static struct rpki_config rpki_config; + +/** + * An option that takes no arguments, is not correlated to any rpki_config + * fields, and is entirely managed by its handler function. + */ +static const struct global_type gt_callback = { + .has_arg = no_argument, +}; + - static const struct option_field global_fields[] = { ++static const struct option_field options[] = { ++ ++ /* ARGP-only, non-fields */ + { + .id = 'h', + .name = "help", + .type = >_callback, + .handler = handle_help, + .doc = "Give this help list", + .availability = AVAILABILITY_GETOPT, + }, { + .id = 1000, + .name = "usage", + .type = >_callback, + .handler = handle_usage, + .doc = "Give a short usage message", + .availability = AVAILABILITY_GETOPT, + }, { + .id = 'V', + .name = "version", + .type = >_callback, + .handler = handle_version, + .doc = "Print program version", + .availability = AVAILABILITY_GETOPT, + }, { + .id = 'f', + .name = "configuration-file", + .type = >_string, - .handler = handle_toml, - .doc = "TOML file additional configuration will be read from", ++ .handler = handle_json, ++ .doc = "JSON file additional configuration will be read from", + .arg_doc = "", + .availability = AVAILABILITY_GETOPT, ++ }, ++ ++ /* Root fields */ ++ { ++ .id = 't', ++ .name = "tal", ++ .type = >_string, ++ .offset = offsetof(struct rpki_config, tal), ++ .doc = "Path to the TAL file", ++ .arg_doc = "", + }, { + .id = 'r', + .name = "local-repository", + .type = >_string, + .offset = offsetof(struct rpki_config, local_repository), + .doc = "Directory where the repository local cache will be stored/read", + .arg_doc = "", + }, { + .id = 1001, + .name = "sync-strategy", + .type = >_sync_strategy, + .offset = offsetof(struct rpki_config, sync_strategy), + .doc = "RSYNC download strategy", ++ }, { ++ .id = 2000, ++ .name = "shuffle-uris", ++ .type = >_bool, ++ .offset = offsetof(struct rpki_config, shuffle_tal_uris), ++ .doc = "Shuffle URIs in the TAL before accessing them", + }, { + .id = 1002, + .name = "maximum-certificate-depth", + .type = >_u_int, + .offset = offsetof(struct rpki_config, + maximum_certificate_depth), + .doc = "Maximum allowable certificate chain length", + .min = 1, + /** + * It cannot be UINT_MAX, because then the actual number will + * overflow and will never be bigger than this. + */ + .max = UINT_MAX - 1, + }, - { 0 }, - }; + - static const struct option_field tal_fields[] = { ++ /* Server fields */ + { - .id = 't', - .name = "tal", ++ .id = 5000, ++ .name = "server.address", + .type = >_string, - .offset = offsetof(struct rpki_config, tal), - .doc = "Path to the TAL file", - .arg_doc = "", ++ .offset = offsetof(struct rpki_config, server.address), ++ .doc = "The listener address of the RTR server.", + }, { - .id = 2000, - .name = "shuffle-uris", - .type = >_bool, - .offset = offsetof(struct rpki_config, shuffle_uris), - .doc = "Shuffle URIs in the TAL before accessing them", ++ .id = 5001, ++ .name = "server.port", ++ .type = >_string, ++ .offset = offsetof(struct rpki_config, server.port), ++ .doc = "", /* TODO */ ++ }, { ++ .id = 5003, ++ .name = "server.queue", ++ .type = >_u_int, ++ .offset = offsetof(struct rpki_config, server.queue), ++ .doc = "Maximum accepted client connections", ++ .min = 1, ++ .max = SOMAXCONN, ++ }, { ++ .id = 5004, ++ .name = "server.vrps-check-interval", ++ .type = >_u_int, ++ .offset = offsetof(struct rpki_config, server.vrps_check_interval), ++ .doc = "Interval used to look for updates at VRPs location", ++ /* ++ * RFC 6810 and 8210: ++ * The cache MUST rate-limit Serial Notifies to no more frequently than ++ * one per minute. ++ */ ++ .min = 60, ++ .max = 7200, ++ }, { ++ .id = 5005, ++ .name = "server.rtr-interval.refresh", ++ .type = >_u_int32, ++ .offset = offsetof(struct rpki_config, server.refresh_interval), ++ .doc = "Intervals use at RTR v1 End of data PDU", ++ .min = 1, ++ .max = 86400, ++ }, { ++ .id = 5006, ++ .name = "server.rtr-interval.retry", ++ .type = >_u_int32, ++ .offset = offsetof(struct rpki_config, server.retry_interval), ++ .doc = "", ++ .min = 1, ++ .max = 7200, ++ }, { ++ .id = 5007, ++ .name = "server.rtr-interval.expire", ++ .type = >_u_int32, ++ .offset = offsetof(struct rpki_config, server.expire_interval), ++ .doc = "", ++ .min = 600, ++ .max = 172800, + }, - { 0 }, - }; + - static const struct option_field rsync_fields[] = { ++ /* RSYNC fields */ + { + .id = 3000, - .name = "program", ++ .name = "rsync.program", + .type = >_string, + .offset = offsetof(struct rpki_config, rsync.program), + .doc = "Name of the program needed to execute an RSYNC", + .arg_doc = "", - .availability = AVAILABILITY_TOML, ++ .availability = AVAILABILITY_JSON, + }, { + .id = 3001, - .name = "arguments-recursive", ++ .name = "rsync.arguments-recursive", + .type = >_string_array, + .offset = offsetof(struct rpki_config, rsync.args.recursive), + .doc = "RSYNC program arguments that will trigger a recursive RSYNC", - .availability = AVAILABILITY_TOML, ++ .availability = AVAILABILITY_JSON, + }, { + .id = 3002, - .name = "arguments-flat", ++ .name = "rsync.arguments-flat", + .type = >_string_array, + .offset = offsetof(struct rpki_config, rsync.args.flat), + .doc = "RSYNC program arguments that will trigger a non-recursive RSYNC", - .availability = AVAILABILITY_TOML, ++ .availability = AVAILABILITY_JSON, + }, - { 0 }, - }; + - static const struct option_field output_fields[] = { ++ /* Logging fields */ + { + .id = 'c', - .name = "color-output", ++ .name = "log.color-output", + .type = >_bool, - .offset = offsetof(struct rpki_config, output.color), ++ .offset = offsetof(struct rpki_config, log.color), + .doc = "Print ANSI color codes.", + }, { + .id = 4000, - .name = "output-file-name-format", ++ .name = "log.file-name-format", + .type = >_filename_format, - .offset = offsetof(struct rpki_config, output.filename_format), ++ .offset = offsetof(struct rpki_config, log.filename_format), + .doc = "File name variant to print during debug/error messages", - }, { - .id = 'o', - .name = "roa-output-file", - .type = >_out_file, - .offset = offsetof(struct rpki_config, output.roa_output), - .doc = "File where the valid ROAs will be dumped.", + }, - { 0 }, - }; + - static const struct group_fields groups[] = { - { - .name = "root", - .options = global_fields, - }, { - .name = "tal", - .options = tal_fields, - }, { - .name = "rsync", - .options = rsync_fields, - }, { - .name = "output", - .options = output_fields, - }, - { NULL }, ++ { 0 }, +}; + +/** + * Returns true if @field is the descriptor of one of the members of the + * struct rpki_config structure, false otherwise. + * (Alternatively: Returns true if @field->offset is valid, false otherwise.) + */ +static bool +is_rpki_config_field(struct option_field const *field) +{ + return field->handler == NULL; +} + +void * +get_rpki_config_field(struct option_field const *field) +{ + return ((unsigned char *) &rpki_config) + field->offset; +} + +static int +handle_help(struct option_field const *field, char *arg) +{ + print_usage(stdout, true); + exit(0); +} + +static int +handle_usage(struct option_field const *field, char *arg) +{ + print_usage(stdout, false); + exit(0); +} + +static int +handle_version(struct option_field const *field, char *arg) +{ + printf("0.0.1\n"); + exit(0); +} + +static int - handle_toml(struct option_field const *field, char *file_name) ++handle_json(struct option_field const *field, char *file_name) +{ + return set_config_from_file(file_name); +} + +static bool +is_alphanumeric(int chara) +{ + return ('a' <= chara && chara <= 'z') + || ('A' <= chara && chara <= 'Z') + || ('0' <= chara && chara <= '9'); +} + +/** + * "struct option" is the array that getopt expects. + * "struct args_flag" is our option metadata. + */ +static int +construct_getopt_options(struct option **_long_opts, char **_short_opts) +{ - struct group_fields const *group; + struct option_field const *opt; + struct option *long_opts; + char *short_opts; + unsigned int total_long_options; + unsigned int total_short_options; + + total_long_options = 0; + total_short_options = 0; - FOREACH_OPTION(groups, group, opt, AVAILABILITY_GETOPT) { ++ FOREACH_OPTION(options, opt, AVAILABILITY_GETOPT) { + total_long_options++; + if (is_alphanumeric(opt->id)) { + total_short_options++; + if (opt->type->has_arg != no_argument) + total_short_options++; /* ":" */ + } + } + + /* +1 NULL end, means end of array. */ + long_opts = calloc(total_long_options + 1, sizeof(struct option)); + if (long_opts == NULL) + return pr_enomem(); + short_opts = malloc(total_short_options + 1); + if (short_opts == NULL) { + free(long_opts); + return pr_enomem(); + } + + *_long_opts = long_opts; + *_short_opts = short_opts; + - FOREACH_OPTION(groups, group, opt, AVAILABILITY_GETOPT) { ++ FOREACH_OPTION(options, opt, AVAILABILITY_GETOPT) { + long_opts->name = opt->name; + long_opts->has_arg = opt->type->has_arg; + long_opts->flag = NULL; + long_opts->val = opt->id; + long_opts++; + + if (is_alphanumeric(opt->id)) { + *short_opts = opt->id; + short_opts++; + if (opt->type->has_arg != no_argument) { + *short_opts = ':'; + short_opts++; + } + } + } + + *short_opts = '\0'; + return 0; +} + +static void +print_config(void) +{ - struct group_fields const *grp; + struct option_field const *opt; + + pr_info("Configuration {"); + pr_indent_add(); + - FOREACH_OPTION(groups, grp, opt, 0xFFFF) ++ FOREACH_OPTION(options, opt, 0xFFFF) + if (is_rpki_config_field(opt) && opt->type->print != NULL) - opt->type->print(grp, opt, get_rpki_config_field(opt)); ++ opt->type->print(opt, get_rpki_config_field(opt)); + + pr_indent_rm(); + pr_info("}"); +} + +static int +set_default_values(void) +{ + static char const *default_rsync_args[] = { + "--recursive", + "--delete", + "--times", + "--contimeout=20", + "$REMOTE", + "$LOCAL", + }; + + int error; + + /* + * Values that might need to be freed WILL be freed, so use heap + * duplicates. + */ + ++ rpki_config.server.address = NULL; ++ rpki_config.server.port = strdup("323"); ++ if (rpki_config.server.port == NULL) ++ return pr_enomem(); ++ ++ rpki_config.server.queue = 10; ++ rpki_config.server.vrps_check_interval = 60; ++ rpki_config.server.refresh_interval = 3600; ++ rpki_config.server.retry_interval = 600; ++ rpki_config.server.expire_interval = 7200; ++ + rpki_config.tal = NULL; + + rpki_config.local_repository = strdup("repository/"); - if (rpki_config.local_repository == NULL) - return pr_enomem(); ++ if (rpki_config.local_repository == NULL) { ++ error = pr_enomem(); ++ goto revert_port; ++ } + + rpki_config.sync_strategy = SYNC_ROOT; - rpki_config.shuffle_uris = false; ++ rpki_config.shuffle_tal_uris = false; + rpki_config.maximum_certificate_depth = 32; + + rpki_config.rsync.program = strdup("rsync"); + if (rpki_config.rsync.program == NULL) { + error = pr_enomem(); + goto revert_repository; + } + + error = string_array_init(&rpki_config.rsync.args.recursive, + default_rsync_args, ARRAY_LEN(default_rsync_args)); + if (error) + goto revert_rsync_program; + /* Simply remove --recursive and --delete. */ + error = string_array_init(&rpki_config.rsync.args.flat, + default_rsync_args + 2, ARRAY_LEN(default_rsync_args) - 2); + if (error) + goto revert_recursive_array; + - rpki_config.output.color = false; - rpki_config.output.filename_format = FNF_GLOBAL; - rpki_config.output.roa_output.fd = NULL; - rpki_config.output.roa_output.file_name = NULL; ++ rpki_config.log.color = false; ++ rpki_config.log.filename_format = FNF_GLOBAL; + + return 0; + +revert_recursive_array: + string_array_cleanup(&rpki_config.rsync.args.recursive); +revert_rsync_program: + free(rpki_config.rsync.program); +revert_repository: + free(rpki_config.local_repository); ++revert_port: ++ free(rpki_config.server.port); + return error; +} + +static int +validate_config(void) +{ + return (rpki_config.tal != NULL) + ? 0 + : pr_err("The TAL file (--tal) is mandatory."); +} + +static void +print_usage(FILE *stream, bool print_doc) +{ - struct group_fields const *group; + struct option_field const *option; + char const *arg_doc; + + fprintf(stream, "Usage: %s\n", program_name); - FOREACH_OPTION(groups, group, option, AVAILABILITY_GETOPT) { ++ FOREACH_OPTION(options, option, AVAILABILITY_GETOPT) { + fprintf(stream, "\t["); + fprintf(stream, "--%s", option->name); + + if (option->arg_doc != NULL) + arg_doc = option->arg_doc; + else if (option->type->arg_doc != NULL) + arg_doc = option->type->arg_doc; + else + arg_doc = NULL; + + switch (option->type->has_arg) { + case no_argument: + break; + case optional_argument: + case required_argument: + if (arg_doc != NULL) + fprintf(stream, "=%s", arg_doc); + break; + } + + fprintf(stream, "]\n"); + + if (print_doc) + fprintf(stream, "\t (%s)\n", option->doc); + } +} + + +static int +handle_opt(int opt) +{ - struct group_fields const *group; + struct option_field const *option; + - FOREACH_OPTION(groups, group, option, AVAILABILITY_GETOPT) { ++ FOREACH_OPTION(options, option, AVAILABILITY_GETOPT) { + if (option->id == opt) { + return is_rpki_config_field(option) + ? option->type->parse.argv(option, optarg, + get_rpki_config_field(option)) + : option->handler(option, optarg); + } + } + + pr_err("Unrecognized option: %d", opt); + return -ESRCH; +} + +int +handle_flags_config(int argc, char **argv) +{ + struct option *long_opts; + char *short_opts; + int opt; + int error; + + program_name = argv[0]; + error = set_default_values(); + if (error) + return error; + + long_opts = NULL; + short_opts = NULL; + error = construct_getopt_options(&long_opts, &short_opts); + if (error) + goto end; /* Error msg already printed. */ + + while ((opt = getopt_long(argc, argv, short_opts, long_opts, NULL)) + != -1) { + error = handle_opt(opt); + if (error) + goto end; + } + + /* + * This triggers when the user runs something like + * `rpki-validator disable-rsync` instead of + * `rpki-validator --disable-rsync`. + * This program does not have unflagged payload. + */ + if (optind < argc) { + error = pr_err("I don't know what '%s' is.", argv[optind]); + goto end; + } + + error = validate_config(); + +end: + if (error) + free_rpki_config(); + else + print_config(); + + free(long_opts); + free(short_opts); + return error; + +} + - void - get_group_fields(struct group_fields const **group_fields) ++struct option_field const * ++get_option_metadatas(void) ++{ ++ return options; ++} ++ ++char const * ++config_get_server_address(void) +{ - *group_fields = groups; ++ return rpki_config.server.address; ++} ++ ++char const * ++config_get_server_port(void) ++{ ++ return rpki_config.server.port; ++} ++ ++int ++config_get_server_queue(void) ++{ ++ /* ++ * The range of this is 1-, so adding signedness is safe. ++ */ ++ return rpki_config.server.queue; ++} ++ ++unsigned int ++config_get_vrps_check_interval(void) ++{ ++ return rpki_config.server.vrps_check_interval; ++} ++ ++u_int32_t ++config_get_refresh_interval(void) ++{ ++ return rpki_config.server.refresh_interval; ++} ++ ++u_int32_t ++config_get_retry_interval(void) ++{ ++ return rpki_config.server.retry_interval; ++} ++ ++u_int32_t ++config_get_expire_interval(void) ++{ ++ return rpki_config.server.expire_interval; +} + +char const * +config_get_tal(void) +{ + return rpki_config.tal; +} + +char const * +config_get_local_repository(void) +{ + return rpki_config.local_repository; +} + +enum sync_strategy +config_get_sync_strategy(void) +{ + return rpki_config.sync_strategy; +} + +bool - config_get_shuffle_uris(void) ++config_get_shuffle_tal_uris(void) +{ - return rpki_config.shuffle_uris; ++ return rpki_config.shuffle_tal_uris; +} + +unsigned int +config_get_max_cert_depth(void) +{ + return rpki_config.maximum_certificate_depth; +} + +bool +config_get_color_output(void) +{ - return rpki_config.output.color; ++ return rpki_config.log.color; +} + +enum filename_format +config_get_filename_format(void) +{ - return rpki_config.output.filename_format; - } - - FILE * - config_get_roa_output(void) - { - return (rpki_config.output.roa_output.fd != NULL) - ? rpki_config.output.roa_output.fd - : stdout; ++ return rpki_config.log.filename_format; +} + +char * +config_get_rsync_program(void) +{ + return rpki_config.rsync.program; +} + +struct string_array const * +config_get_rsync_args(bool is_ta) +{ + switch (rpki_config.sync_strategy) { + case SYNC_ROOT: + return &rpki_config.rsync.args.recursive; + case SYNC_ROOT_EXCEPT_TA: + return is_ta + ? &rpki_config.rsync.args.flat + : &rpki_config.rsync.args.recursive; + case SYNC_STRICT: + return &rpki_config.rsync.args.flat; + case SYNC_OFF: + break; + } + - pr_crit("Invalid sync strategy: '%u'", rpki_config.sync_strategy); ++ pr_crit("Invalid sync strategy: '%u'", ++ rpki_config.sync_strategy); + /* + * Return something usable anyway; don't want to check NULL. + * This is supposed to be unreachable code anyway. + */ + return &rpki_config.rsync.args.recursive; +} + +void +free_rpki_config(void) +{ - struct group_fields const *group; + struct option_field const *option; + - FOREACH_OPTION(groups, group, option, 0xFFFF) ++ FOREACH_OPTION(options, option, 0xFFFF) + if (is_rpki_config_field(option) && option->type->free != NULL) + option->type->free(get_rpki_config_field(option)); +} diff --cc src/config.h index 341968c8,00000000..1eca1e09 mode 100644,000000..100644 --- a/src/config.h +++ b/src/config.h @@@ -1,31 -1,0 +1,39 @@@ +#ifndef SRC_CONFIG_H_ +#define SRC_CONFIG_H_ + +#include ++#include + +#include "config/filename_format.h" +#include "config/sync_strategy.h" +#include "config/string_array.h" +#include "config/types.h" + +/* Init/destroy */ +int handle_flags_config(int , char **); +void free_rpki_config(void); + +/* Getters */ ++char const *config_get_server_address(void); ++char const *config_get_server_port(void); ++int config_get_server_queue(void); ++unsigned int config_get_vrps_check_interval(void); ++uint32_t config_get_refresh_interval(void); ++uint32_t config_get_retry_interval(void); ++uint32_t config_get_expire_interval(void); ++ +char const *config_get_tal(void); +char const *config_get_local_repository(void); +enum sync_strategy config_get_sync_strategy(void); - bool config_get_shuffle_uris(void); ++bool config_get_shuffle_tal_uris(void); +unsigned int config_get_max_cert_depth(void); +bool config_get_color_output(void); +enum filename_format config_get_filename_format(void); - FILE *config_get_roa_output(void); +char *config_get_rsync_program(void); +struct string_array const *config_get_rsync_args(bool); + - /* Needed public by the TOML module */ ++/* Needed public by the JSON module */ +void *get_rpki_config_field(struct option_field const *); - void get_group_fields(struct group_fields const **); ++struct option_field const *get_option_metadatas(void); + +#endif /* SRC_CONFIG_H_ */ diff --cc src/config/boolean.c index 0a6ceb2f,00000000..2c909d23 mode 100644,000000..100644 --- a/src/config/boolean.c +++ b/src/config/boolean.c @@@ -1,66 -1,0 +1,57 @@@ +#include "config/boolean.h" + +#include +#include +#include +#include "log.h" + ++#define DEREFERENCE(void_value) (*((bool *) void_value)) ++ +static void - print_bool(struct group_fields const *group, struct option_field const *field, - void *_value) ++print_bool(struct option_field const *field, void *value) +{ - bool *value = _value; - pr_info("%s.%s: %s", group->name, field->name, - (*value) ? "true" : "false"); ++ pr_info("%s: %s", field->name, DEREFERENCE(value) ? "true" : "false"); +} + +static int +parse_argv_bool(struct option_field const *field, char const *str, void *result) +{ - bool *value = result; - + if (str == NULL) { - *value = true; ++ DEREFERENCE(result) = true; + return 0; + } + + if (strcmp(str, "true") == 0) { - *value = true; ++ DEREFERENCE(result) = true; + return 0; + } + + if (strcmp(str, "false") == 0) { - *value = false; ++ DEREFERENCE(result) = false; + return 0; + } + + return pr_err("Cannot parse '%s' as a bool (true|false).", str); +} + +static int - parse_toml_bool(struct option_field const *opt, struct toml_table_t *toml, - void *_result) ++parse_json_bool(struct option_field const *opt, struct json_t *json, ++ void *result) +{ - const char *raw; - int value; - bool *result; - - raw = toml_raw_in(toml, opt->name); - if (raw == NULL) - return 0; - if (toml_rtob(raw, &value) == -1) - return pr_err("Cannot parse '%s' as a boolean.", raw); ++ if (!json_is_boolean(json)) { ++ return pr_err("The '%s' element is not a JSON boolean.", ++ opt->name); ++ } + - result = _result; - *result = value; ++ DEREFERENCE(result) = json_boolean_value(json); + return 0; +} + +const struct global_type gt_bool = { + .has_arg = no_argument, + .size = sizeof(bool), + .print = print_bool, + .parse.argv = parse_argv_bool, - .parse.toml = parse_toml_bool, ++ .parse.json = parse_json_bool, + .arg_doc = "true|false", +}; diff --cc src/config/filename_format.c index 4bc805da,00000000..a9241f59 mode 100644,000000..100644 --- a/src/config/filename_format.c +++ b/src/config/filename_format.c @@@ -1,80 -1,0 +1,70 @@@ +#include "config/filename_format.h" + +#include +#include +#include + +#include "log.h" +#include "config/str.h" + +#define FNF_VALUE_GLOBAL "global-url" +#define FNF_VALUE_LOCAL "local-path" +#define FNF_VALUE_NAME "file-name" + ++#define DEREFERENCE(void_value) (*((enum filename_format *) void_value)) ++ +static void - print_filename_format(struct group_fields const *group, - struct option_field const *field, void *value) ++print_filename_format(struct option_field const *field, void *value) +{ - enum filename_format *format = value; + char const *str = ""; + - switch (*format) { ++ switch (DEREFERENCE(value)) { + case FNF_GLOBAL: + str = FNF_VALUE_GLOBAL; + break; + case FNF_LOCAL: + str = FNF_VALUE_LOCAL; + break; + case FNF_NAME: + str = FNF_VALUE_NAME; + break; + } + - pr_info("%s.%s: %s", group->name, field->name, str); ++ pr_info("%s: %s", field->name, str); +} + +static int +parse_argv_filename_format(struct option_field const *field, char const *str, - void *_result) ++ void *result) +{ - enum filename_format *result = _result; - + if (strcmp(str, FNF_VALUE_GLOBAL) == 0) - *result = FNF_GLOBAL; ++ DEREFERENCE(result) = FNF_GLOBAL; + else if (strcmp(str, FNF_VALUE_LOCAL) == 0) - *result = FNF_LOCAL; ++ DEREFERENCE(result) = FNF_LOCAL; + else if (strcmp(str, FNF_VALUE_NAME) == 0) - *result = FNF_NAME; ++ DEREFERENCE(result) = FNF_NAME; + else + return pr_err("Unknown file name format: '%s'", str); + + return 0; +} + +static int - parse_toml_filename_format(struct option_field const *opt, - struct toml_table_t *toml, void *_result) ++parse_json_filename_format(struct option_field const *opt, json_t *json, ++ void *result) +{ - char *string; ++ char const *string; + int error; + - error = parse_toml_string(toml, opt->name, &string); - if (error) - return error; - if (string == NULL) - return 0; - - error = parse_argv_filename_format(opt, string, _result); - - free(string); - return error; ++ error = parse_json_string(json, opt->name, &string); ++ return error ? error : parse_argv_filename_format(opt, string, result); +} + +const struct global_type gt_filename_format = { + .has_arg = required_argument, + .size = sizeof(enum filename_format), + .print = print_filename_format, + .parse.argv = parse_argv_filename_format, - .parse.toml = parse_toml_filename_format, ++ .parse.json = parse_json_filename_format, + .arg_doc = FNF_VALUE_GLOBAL "|" FNF_VALUE_LOCAL "|" FNF_VALUE_NAME, +}; diff --cc src/config/str.c index 4be654bd,00000000..83880562 mode 100644,000000..100644 --- a/src/config/str.c +++ b/src/config/str.c @@@ -1,93 -1,0 +1,76 @@@ +#include "config/str.h" + +#include +#include +#include +#include "log.h" + ++#define DEREFERENCE(void_value) (*((char **) void_value)) ++ +static void +__string_free(char **string) +{ + free(*string); + *string = NULL; +} + +static void - string_print(struct group_fields const *group, struct option_field const *field, - void *value) ++string_print(struct option_field const *field, void *value) +{ - pr_info("%s.%s: %s", group->name, field->name, *((char **) value)); ++ pr_info("%s: %s", field->name, DEREFERENCE(value)); +} + +static int +string_parse_argv(struct option_field const *field, char const *str, - void *_result) ++ void *result) +{ - char **result = _result; - + if (field->type->has_arg != required_argument || str == NULL) { + return pr_err("String options ('%s' in this case) require an argument.", + field->name); + } + + /* Remove the previous value (usually the default). */ + __string_free(result); + - /* tomlc99 frees @str early, so work with a copy. */ - *result = strdup(str); - return ((*result) != NULL) ? 0 : pr_enomem(); ++ DEREFERENCE(result) = strdup(str); ++ return (DEREFERENCE(result) != NULL) ? 0 : pr_enomem(); +} + +static int - string_parse_toml(struct option_field const *opt, struct toml_table_t *toml, - void *_result) ++string_parse_json(struct option_field const *opt, json_t *json, void *result) +{ - char *tmp; - char **result; ++ char const *string; + int error; + - error = parse_toml_string(toml, opt->name, &tmp); - if (error) - return error; - if (tmp == NULL) - return 0; - - result = _result; - __string_free(result); - *result = tmp; - return 0; ++ error = parse_json_string(json, opt->name, &string); ++ return error ? error : string_parse_argv(opt, string, result); +} + +static void +string_free(void *string) +{ + __string_free(string); +} + +const struct global_type gt_string = { + .has_arg = required_argument, + .size = sizeof(char *), + .print = string_print, + .parse.argv = string_parse_argv, - .parse.toml = string_parse_toml, ++ .parse.json = string_parse_json, + .free = string_free, + .arg_doc = "", +}; + ++/** ++ * *result must not be freed nor long-term stored. ++ */ +int - parse_toml_string(struct toml_table_t *toml, char const *name, char **result) ++parse_json_string(json_t *json, char const *name, char const **result) +{ - const char *raw; - char *value; - - raw = toml_raw_in(toml, name); - if (raw == NULL) { - *result = NULL; - return 0; - } - if (toml_rtos(raw, &value) == -1) - return pr_err("Cannot parse '%s' as a string.", raw); ++ if (!json_is_string(json)) ++ return pr_err("The '%s' element is not a JSON string.", name); + - *result = value; ++ *result = json_string_value(json); + return 0; +} diff --cc src/config/str.h index 7cb592ed,00000000..f78f7292 mode 100644,000000..100644 --- a/src/config/str.h +++ b/src/config/str.h @@@ -1,10 -1,0 +1,10 @@@ +#ifndef SRC_CONFIG_STR_H_ +#define SRC_CONFIG_STR_H_ + +#include "config/types.h" + +extern const struct global_type gt_string; + - int parse_toml_string(struct toml_table_t *, char const *, char **); ++int parse_json_string(json_t *, char const *, char const **); + +#endif /* SRC_CONFIG_STR_H_ */ diff --cc src/config/string_array.c index 69f38415,00000000..ccbb6803 mode 100644,000000..100644 --- a/src/config/string_array.c +++ b/src/config/string_array.c @@@ -1,124 -1,0 +1,140 @@@ +#include "config/string_array.h" + +#include +#include +#include +#include ++ +#include "log.h" ++#include "config/str.h" + +int +string_array_init(struct string_array *array, char const *const *values, + size_t len) +{ + size_t i; + + array->length = len; + + array->array = calloc(len, sizeof(char *)); + if (array->array == NULL) + return -ENOMEM; + + for (i = 0; i < len; i++) { + array->array[i] = strdup(values[i]); + if (array->array[i] == NULL) { + string_array_cleanup(array); + return -ENOMEM; + } + } + + return 0; +} + +void +string_array_cleanup(struct string_array *array) +{ + size_t i; + for (i = 0; i < array->length; i++) + free(array->array[i]); + free(array->array); +} + +static void +__string_array_free(struct string_array *array) +{ + string_array_cleanup(array); + array->array = NULL; + array->length = 0; +} + +static void - string_array_print(struct group_fields const *group, - struct option_field const *field, void *_value) ++string_array_print(struct option_field const *field, void *_value) +{ + struct string_array *value = _value; + size_t i; + - pr_info("%s.%s:", group->name, field->name); ++ pr_info("%s:", field->name); + pr_indent_add(); + + if (value->length == 0) + pr_info(""); + else for (i = 0; i < value->length; i++) + pr_info("%s", value->array[i]); + + pr_indent_rm(); +} + +static int - string_array_parse_toml(struct option_field const *opt, - struct toml_table_t *toml, void *_result) ++string_array_parse_json(struct option_field const *opt, json_t *json, ++ void *_result) +{ - toml_array_t *array; - int array_len; - int i; - const char *raw; - struct string_array *result = _result; ++ struct string_array *result; ++ json_t *child; ++ size_t i, len; ++ char const *tmp; + int error; + - array = toml_array_in(toml, opt->name); - if (array == NULL) ++ if (!json_is_array(json)) { ++ return pr_err("The '%s' element is not a JSON array.", ++ opt->name); ++ } ++ ++ len = json_array_size(json); ++ if (len == 0) { ++ __string_array_free(_result); + return 0; - array_len = toml_array_nelem(array); ++ } ++ ++ for (i = 0; i < len; i++) { ++ child = json_array_get(json, i); ++ if (!json_is_string(child)) { ++ return pr_err("'%s' array element #%zu is not a string.", ++ opt->name, i); ++ } ++ } ++ ++ result = _result; + + /* Remove the previous value (usually the default). */ + __string_array_free(result); + - result->array = malloc(array_len * sizeof(char *)); ++ result->array = calloc(len, sizeof(char *)); + if (result->array == NULL) + return pr_enomem(); - result->length = array_len; ++ result->length = len; + - for (i = 0; i < array_len; i++) { - raw = toml_raw_at(array, i); - if (raw == NULL) { - error = pr_crit("Array index %d is NULL.", i); ++ for (i = 0; i < len; i++) { ++ error = parse_json_string(json_array_get(json, i), ++ "array element", &tmp); ++ if (error) + goto fail; - } - if (toml_rtos(raw, &result->array[i]) == -1) { - error = pr_err("Cannot parse '%s' as a string.", raw); ++ ++ result->array[i] = strdup(tmp); ++ if (result->array[i] == NULL) { ++ error = pr_enomem(); + goto fail; + } + } + + return 0; + +fail: - free(result->array); - result->length = 0; ++ __string_array_free(result); + return error; +} + +static void +string_array_free(void *array) +{ + __string_array_free(array); +} + +const struct global_type gt_string_array = { + .has_arg = required_argument, + .size = sizeof(char *const *), + .print = string_array_print, - .parse.toml = string_array_parse_toml, ++ .parse.json = string_array_parse_json, + .free = string_array_free, + .arg_doc = "", +}; diff --cc src/config/string_array.h index 74989eaa,00000000..ce0a836f mode 100644,000000..100644 --- a/src/config/string_array.h +++ b/src/config/string_array.h @@@ -1,17 -1,0 +1,21 @@@ +#ifndef SRC_CONFIG_STRING_ARRAY_H_ +#define SRC_CONFIG_STRING_ARRAY_H_ + +#include +#include "config/types.h" + +struct string_array { ++ /* ++ * BTW: The array size can be zero, in which case this will be NULL. ++ * TODO Remember to handle properly. ++ */ + char **array; + size_t length; +}; + +extern const struct global_type gt_string_array; + +int string_array_init(struct string_array *, char const *const *, size_t); +void string_array_cleanup(struct string_array *); + +#endif /* SRC_CONFIG_STRING_ARRAY_H_ */ diff --cc src/config/sync_strategy.c index d2522e18,00000000..e6b13e48 mode 100644,000000..100644 --- a/src/config/sync_strategy.c +++ b/src/config/sync_strategy.c @@@ -1,89 -1,0 +1,79 @@@ +#include "config/sync_strategy.h" + +#include +#include +#include + +#include "log.h" +#include "config/str.h" + +#define SYNC_VALUE_OFF "off" +#define SYNC_VALUE_STRICT "strict" +#define SYNC_VALUE_ROOT "root" +#define SYNC_VALUE_ROOT_EXCEPT_TA "root-except-ta" + ++#define DEREFERENCE(void_value) (*((enum sync_strategy *) void_value)) ++ +static void - print_sync_strategy(struct group_fields const *group, - struct option_field const *field, void *value) ++print_sync_strategy(struct option_field const *field, void *value) +{ - enum sync_strategy *strategy = value; + char const *str = ""; + - switch (*strategy) { ++ switch (DEREFERENCE(value)) { + case SYNC_OFF: + str = SYNC_VALUE_OFF; + break; + case SYNC_STRICT: + str = SYNC_VALUE_STRICT; + break; + case SYNC_ROOT: + str = SYNC_VALUE_ROOT; + break; + case SYNC_ROOT_EXCEPT_TA: + str = SYNC_VALUE_ROOT_EXCEPT_TA; + break; + } + - pr_info("%s.%s: %s", group->name, field->name, str); ++ pr_info("%s: %s", field->name, str); +} + +static int +parse_argv_sync_strategy(struct option_field const *field, char const *str, - void *_result) ++ void *result) +{ - enum sync_strategy *result = _result; - + if (strcmp(str, SYNC_VALUE_OFF) == 0) - *result = SYNC_OFF; ++ DEREFERENCE(result) = SYNC_OFF; + else if (strcmp(str, SYNC_VALUE_STRICT) == 0) - *result = SYNC_STRICT; ++ DEREFERENCE(result) = SYNC_STRICT; + else if (strcmp(str, SYNC_VALUE_ROOT) == 0) - *result = SYNC_ROOT; ++ DEREFERENCE(result) = SYNC_ROOT; + else if (strcmp(str, SYNC_VALUE_ROOT_EXCEPT_TA) == 0) - *result = SYNC_ROOT_EXCEPT_TA; ++ DEREFERENCE(result) = SYNC_ROOT_EXCEPT_TA; + else + return pr_err("Unknown synchronization strategy: '%s'", str); + + return 0; +} + +static int - parse_toml_sync_strategy(struct option_field const *opt, - struct toml_table_t *toml, void *_result) ++parse_json_sync_strategy(struct option_field const *opt, struct json_t *json, ++ void *result) +{ ++ char const *string; + int error; - char *string; - - error = parse_toml_string(toml, opt->name, &string); - if (error) - return error; - if (string == NULL) - return 0; - - error = parse_argv_sync_strategy(opt, string, _result); + - free(string); - return error; ++ error = parse_json_string(json, opt->name, &string); ++ return error ? error : parse_argv_sync_strategy(opt, string, result); +} + +const struct global_type gt_sync_strategy = { + .has_arg = required_argument, + .size = sizeof(enum sync_strategy), + .print = print_sync_strategy, + .parse.argv = parse_argv_sync_strategy, - .parse.toml = parse_toml_sync_strategy, ++ .parse.json = parse_json_sync_strategy, + .arg_doc = SYNC_VALUE_OFF + "|" SYNC_VALUE_STRICT + "|" SYNC_VALUE_ROOT + "|" SYNC_VALUE_ROOT_EXCEPT_TA, +}; diff --cc src/config/types.h index bdb5d7bf,00000000..56abb8b8 mode 100644,000000..100644 --- a/src/config/types.h +++ b/src/config/types.h @@@ -1,139 -1,0 +1,135 @@@ +#ifndef SRC_CONFIG_TYPES_H_ +#define SRC_CONFIG_TYPES_H_ + ++#include +#include +#include - #include + +struct option_field; +struct group_fields; + +/** This option can be set from the command line. */ +#define AVAILABILITY_GETOPT (1 << 0) - /** This option can be set from the TOML file. */ - #define AVAILABILITY_TOML (1 << 1) ++/** This option can be set from the JSON file. */ ++#define AVAILABILITY_JSON (1 << 1) + +typedef void (*print_function)( - struct group_fields const *, + struct option_field const *, + void * +); +typedef int (*argv_parse_function)( + struct option_field const *, + char const *, + void * +); - typedef int (*toml_parse_function)( ++typedef int (*json_parse_function)( + struct option_field const *, - struct toml_table_t *, ++ struct json_t *, + void * +); +typedef int (*handler_function)( + struct option_field const *, + char * +); + +struct option_field { + /* + * Must be zero, alphanumeric or >= 1000. + * If zero, signals the end of the array. + * If alphanumeric, it's the short option name character. + * Otherwise it's just a non-printable identifier. + * Must be unique across all option fields. + * Mandatory. + */ + int id; + /** + * For example, if the option name is '--potato', then @name is + * "potato". + * Mandatory. + */ + char const *name; + + /** Data type. Mandatory. */ + struct global_type const *type; + /** + * Number of bytes between the beginning of the struct rpki_config + * and the position where this option is stored. + * Only relevant when @handler == NULL. + */ + size_t offset; + /** Overrides @type->parser and @offset. Optional. */ + handler_function handler; + + /** + * Explanation of the field, for user consumption during --help. + * Meant to be short; the bulk of it should be found in the manpage. + * Probably should not include punctuation at the end. + * Mandatory. + */ + const char *doc; + /** Overrides type->arg_doc. Optional. */ + char const *arg_doc; + /** + * AVAILABILITY_* flags above. + * Default availability is everywhere. + * Optional. + */ + int availability; + unsigned int min; + unsigned int max; +}; + - struct group_fields { - char const *name; - struct option_field const *options; - }; ++#define FOREACH_OPTION(opts, opt, type) \ ++ for (opt = opts; opt->id != 0; opt++) \ ++ if ((opt->availability == 0) || (opt->availability & type)) + +struct global_type { + /** Same as struct option.has_arg. Mandatory. */ + int has_arg; + /** + * Number of bytes this data type uses in the rpki_config structure. + * Optional. Defaults to zero, obviously. + */ + size_t size; + + /** + * Prints this data type during the print_config() function. + * Optional. + */ + print_function print; + + /** If the option's handler is not NULL, this is optional. */ + struct { + /** + * Convers from string to this data type. + * Optional if there are no fields of this type that are read + * from argv. + */ + argv_parse_function argv; + /** - * Converts from a TOML node to this data type. - * If the node is not present in the file, this function should - * do nothing. ++ * Converts from a JSON node to this data type. + * Optional if there are no fields of this type that are read - * from TOML files. ++ * from JSON files. + */ - toml_parse_function toml; ++ json_parse_function json; + } parse; + + /** + * Function that will release this data type. + * If the option's handler is not NULL, this is optional. + * + * IMPORTANT: This function might be called twice in succession. + * Therefore, make sure that it nullifies the value, and reacts properly + * when the input is NULL. + */ + void (*free)(void *); + + /** + * Descriptor of this type's payload. Printed in usage documentation. + * For example, in `--tal=`, @arg_doc is "". + * The type might have no payload, so this is optional. + */ + char const *arg_doc; +}; + +#endif /* SRC_CONFIG_TYPES_H_ */ diff --cc src/config/uint.c index 920bcf86,00000000..16c04374 mode 100644,000000..100644 --- a/src/config/uint.c +++ b/src/config/uint.c @@@ -1,74 -1,0 +1,67 @@@ +#include "config/uint.h" + +#include +#include +#include +#include "log.h" + +static void - print_u_int(struct group_fields const *group, struct option_field const *field, - void *value) ++print_u_int(struct option_field const *field, void *value) +{ - pr_info("%s.%s: %u", group->name, field->name, - *((unsigned int *) value)); ++ pr_info("%s: %u", field->name, *((unsigned int *) value)); +} + - static int ++int +parse_argv_u_int(struct option_field const *field, char const *str, - void *_result) ++ void *result) +{ + unsigned long parsed; - int *result; + + if (field->type->has_arg != required_argument || str == NULL) { + return pr_err("Integer options ('%s' in this case) require an argument.", + field->name); + } + + errno = 0; + parsed = strtoul(str, NULL, 10); + if (errno) + return pr_errno(errno, "'%s' is not an unsigned integer", str); + + if (parsed < field->min || field->max < parsed) { + return pr_err("'%lu' is out of bounds (%u-%u).", parsed, + field->min, field->max); + } + - result = _result; - *result = parsed; ++ *((unsigned int *) result) = parsed; + return 0; +} + - static int - parse_toml_u_int(struct option_field const *opt, struct toml_table_t *toml, - void *_result) ++int ++parse_json_u_int(struct option_field const *opt, json_t *json, void *result) +{ - const char *raw; - int64_t value; - unsigned int *result; ++ json_int_t value; + - raw = toml_raw_in(toml, opt->name); - if (raw == NULL) - return 0; - if (toml_rtoi(raw, &value) == -1) - return pr_err("Cannot parse '%s' as an integer.", raw); ++ if (!json_is_integer(json)) { ++ return pr_err("The '%s' element is not a JSON integer.", ++ opt->name); ++ } ++ ++ value = json_integer_value(json); + + if (value < opt->min || opt->max < value) { + return pr_err("Integer '%s' is out of range (%u-%u).", + opt->name, opt->min, opt->max); + } + - result = _result; - *result = value; ++ *((unsigned int *) result) = value; + return 0; +} + +const struct global_type gt_u_int = { + .has_arg = required_argument, + .size = sizeof(unsigned int), + .print = print_u_int, + .parse.argv = parse_argv_u_int, - .parse.toml = parse_toml_u_int, ++ .parse.json = parse_json_u_int, + .arg_doc = "", +}; diff --cc src/config/uint.h index 5623385d,00000000..4108cc7d mode 100644,000000..100644 --- a/src/config/uint.h +++ b/src/config/uint.h @@@ -1,8 -1,0 +1,11 @@@ +#ifndef SRC_CONFIG_UINT_H_ +#define SRC_CONFIG_UINT_H_ + +#include "config/types.h" + +extern const struct global_type gt_u_int; + ++int parse_argv_u_int(struct option_field const *, char const *, void *); ++int parse_json_u_int(struct option_field const *, struct json_t *, void *); ++ +#endif /* SRC_CONFIG_UINT_H_ */ diff --cc src/config/uint32.c index 00000000,00000000..05b704ff new file mode 100644 --- /dev/null +++ b/src/config/uint32.c @@@ -1,0 -1,0 +1,54 @@@ ++#include "config/uint.h" ++ ++#include ++#include ++#include ++ ++#include "log.h" ++#include "config/uint.h" ++ ++static void ++print_u_int32(struct option_field const *field, void *value) ++{ ++ pr_info("%s: %u", field->name, *((u_int32_t *) value)); ++} ++ ++static int ++parse_argv_u_int32(struct option_field const *field, char const *str, ++ void *result) ++{ ++ unsigned int tmp; ++ int error; ++ ++ error = parse_argv_u_int(field, str, &tmp); ++ if (error) ++ return error; ++ ++ /* Range already validated (from field->min and field->max). */ ++ *((u_int32_t *) result) = tmp; ++ return 0; ++} ++ ++static int ++parse_json_u_int32(struct option_field const *opt, json_t *json, void *result) ++{ ++ unsigned int tmp; ++ int error; ++ ++ error = parse_json_u_int(opt, json, &tmp); ++ if (error) ++ return error; ++ ++ /* Range already validated (from opt->min and opt->max). */ ++ *((u_int32_t *) result) = tmp; ++ return 0; ++} ++ ++const struct global_type gt_u_int32 = { ++ .has_arg = required_argument, ++ .size = sizeof(u_int32_t), ++ .print = print_u_int32, ++ .parse.argv = parse_argv_u_int32, ++ .parse.json = parse_json_u_int32, ++ .arg_doc = "<32-bit unsigned integer>", ++}; diff --cc src/config/uint32.h index 00000000,00000000..1689229a new file mode 100644 --- /dev/null +++ b/src/config/uint32.h @@@ -1,0 -1,0 +1,8 @@@ ++#ifndef SRC_CONFIG_UINT32_H_ ++#define SRC_CONFIG_UINT32_H_ ++ ++#include "config/types.h" ++ ++extern const struct global_type gt_u_int32; ++ ++#endif /* SRC_CONFIG_UINT32_H_ */ diff --cc src/json_handler.c index 00000000,00000000..6d7488c1 new file mode 100644 --- /dev/null +++ b/src/json_handler.c @@@ -1,0 -1,0 +1,79 @@@ ++#include "json_handler.h" ++ ++#include ++#include ++ ++#include "config.h" ++#include "log.h" ++#include "config/types.h" ++ ++int ++find_json(struct json_t *root, char const *full_name, json_t **result) ++{ ++ struct { ++ char *opt_name; /* full token sequence string */ ++ char *token; /* current token */ ++ char *saveptr; /* state needed by strtok_r */ ++ } strtok; ++ struct json_t *node; ++ ++ /* strtok_r() needs a non-const string */ ++ strtok.opt_name = strdup(full_name); ++ if (strtok.opt_name == NULL) ++ return pr_enomem(); ++ ++ node = root; ++ strtok.token = strtok_r(strtok.opt_name, ".", &strtok.saveptr); ++ ++ while (node != NULL && strtok.token != NULL) { ++ node = json_object_get(node, strtok.token); ++ strtok.token = strtok_r(NULL, ".", &strtok.saveptr); ++ } ++ ++ free(strtok.opt_name); ++ *result = node; ++ return 0; ++} ++ ++static int ++json_to_config(struct json_t *root) ++{ ++ struct option_field const *opt; ++ struct json_t *child; ++ int error; ++ ++ FOREACH_OPTION(get_option_metadatas(), opt, AVAILABILITY_JSON) { ++ error = find_json(root, opt->name, &child); ++ if (error) ++ return error; ++ if (child == NULL) ++ continue; ++ ++ error = opt->type->parse.json(opt, child, ++ get_rpki_config_field(opt)); ++ if (error) ++ return error; ++ } ++ ++ return 0; ++} ++ ++int ++set_config_from_file(char *file) ++{ ++ json_t *root; ++ json_error_t json_error; ++ int error; ++ ++ root = json_load_file(file, JSON_REJECT_DUPLICATES, &json_error); ++ if (root == NULL) { ++ pr_err("JSON error on line %d, column %d: %s", ++ json_error.line, json_error.column, json_error.text); ++ return -ENOENT; ++ } ++ ++ error = json_to_config(root); ++ ++ json_decref(root); ++ return error; ++} diff --cc src/json_handler.h index 00000000,00000000..931970de new file mode 100644 --- /dev/null +++ b/src/json_handler.h @@@ -1,0 -1,0 +1,6 @@@ ++#ifndef SRC_JSON_HANDLER_H_ ++#define SRC_JSON_HANDLER_H_ ++ ++int set_config_from_file(char *); ++ ++#endif /* SRC_JSON_HANDLER_H_ */ diff --cc src/main.c index d902db44,5496b585..d236e2bc --- a/src/main.c +++ b/src/main.c @@@ -1,129 -1,77 +1,70 @@@ - #include --#include - #include -#include -#include -#include -- - #include "common.h" -#include "rtr/rtr.h" + #include "clients.h" -#include "configuration.h" -#include "csv.h" +#include "config.h" +#include "debug.h" +#include "extension.h" - #include "log.h" +#include "nid.h" - #include "rpp.h" +#include "thread_var.h" - #include "object/certificate.h" - #include "object/manifest.h" - #include "object/tal.h" + #include "vrps.h" +#include "rsync/rsync.h" ++#include "rtr/rtr.h" - /** - * Performs the whole validation walkthrough on uri @uri, which is assumed to - * have been extracted from a TAL. -/* - * This program is an RTR server. - * - * RTR ("RPKI-to-Router") is a protocol (defined in RFCs 6810 and 8210) that - * reports the work of an RPKI validator (cryptographcally-verified - * attestations that define the ASN that owns a given routing prefix). It is - * normally served to routers who wish to verify BGP claims. -- */ -int -main(int argc, char *argv[]) +static int - handle_tal_uri(struct tal *tal, struct rpki_uri const *uri) ++start_rtr_server(void) { - /* - * Because of the way the foreach iterates, this function must return - * - * - 0 on soft errors. - * - `> 0` on URI handled successfully. - * - `< 0` on hard errors. - * - * A "soft error" is "the connection to the preferred URI fails, or the - * retrieved CA certificate public key does not match the TAL public - * key." (RFC 7730) - * - * A "hard error" is any other error. - */ - - struct validation *state; - int err; - char *json_file = NULL; - int c; + int error; - error = download_files(uri, true); - if (error) { - return pr_warn("TAL URI '%s' could not be RSYNC'd.", - uri->global); - while ((c = getopt(argc, argv, "f:")) != -1) { - switch (c) { - case 'f': - json_file = optarg; - break; - case '?': - fprintf(stdout, "usage: %s -f \n", argv[0]); - return 0; - } -- } - - error = validation_prepare(&state, tal); ++ error = deltas_db_init(); + if (error) - return -abs(error); - - pr_debug_add("TAL URI '%s' {", uri_get_printable(uri)); ++ goto end1; - if (!uri_is_certificate(uri)) { - pr_err("TAL file does not point to a certificate. (Expected .cer, got '%s')", - uri_get_printable(uri)); - error = -EINVAL; - goto end; - if (json_file == NULL) { - fprintf(stderr, "Missing flag '-f '\n"); - return -EINVAL; -- } ++ error = clients_db_init(); ++ if (error) ++ goto end2; - error = certificate_traverse(NULL, uri, NULL, true); - if (error) { - switch (validation_pubkey_state(state)) { - case PKS_INVALID: - error = 0; - break; - case PKS_VALID: - case PKS_UNTESTED: - error = -abs(error); - break; - } - } else { - error = 1; - err = config_init(json_file); - if (err) { - /* - * TODO Special scenario, if the VRPs location doesn't exists - * just send a warning (logged by the config_init function). - * - * This should be fixed later. - */ - err = (err == -ENOENT ? 0 : err); - goto end1; -- } ++ error = rtr_listen(); ++ rtr_cleanup(); /* TODO shouldn't this only happen on !error? */ - end: - validation_destroy(state); - pr_debug_rm("}"); - return error; - err = deltas_db_init(); - if (err) - goto end1; ++ clients_db_destroy(); ++end2: deltas_db_destroy(); ++end1: return error; +} - err = clients_db_init(); - if (err) - goto end2; +int +main(int argc, char **argv) +{ - struct tal *tal; + int error; - err = csv_parse_vrps_file(); - if (err) - goto end3; + print_stack_trace_on_segfault(); - thvar_init(); - fnstack_init(); - err = rtr_listen(); ++ error = thvar_init(); ++ if (error) ++ return error; - rtr_cleanup(); -end3: - clients_db_destroy(); -end2: - deltas_db_destroy(); -end1: - config_cleanup(); - return err; + error = handle_flags_config(argc, argv); + if (error) + return error; + + error = rsync_init(); + if (error) - goto end1; - ++ goto revert_config; + error = nid_init(); + if (error) - goto end2; ++ goto revert_rsync; + error = extension_init(); + if (error) - goto end2; - fnstack_push(config_get_tal()); ++ goto revert_rsync; + - error = tal_load(config_get_tal(), &tal); - if (!error) { - if (config_get_shuffle_uris()) - tal_shuffle_uris(tal); - - error = foreach_uri(tal, handle_tal_uri); - if (error > 0) - error = 0; - else if (error == 0) - error = pr_err("None of the URIs of the TAL yielded a successful traversal."); ++ error = perform_standalone_validation(NULL); ++ if (error) ++ goto revert_rsync; + - tal_destroy(tal); - } ++ if (config_get_server_address() != NULL) ++ error = start_rtr_server(); ++ /* Otherwise, no server requested. */ + - end2: ++revert_rsync: + rsync_destroy(); - end1: ++revert_config: + free_rpki_config(); - fnstack_cleanup(); + return error; } diff --cc src/object/ghostbusters.c index e39634fb,00000000..72c28515 mode 100644,000000..100644 --- a/src/object/ghostbusters.c +++ b/src/object/ghostbusters.c @@@ -1,44 -1,0 +1,44 @@@ +#include "object/ghostbusters.h" + +#include "log.h" +#include "thread_var.h" +#include "asn1/oid.h" +#include "asn1/signed_data.h" +#include "object/signed_object.h" +#include "vcard.h" + +static int +handle_vcard(OCTET_STRING_t *vcard, void *arg) +{ + return handle_ghostbusters_vcard(vcard); +} + +int - handle_ghostbusters(struct rpki_uri const *uri, struct rpp *pp, ++ghostbusters_traverse(struct rpki_uri const *uri, struct rpp *pp, + STACK_OF(X509_CRL) *crls) +{ + static OID oid = OID_GHOSTBUSTERS; + struct oid_arcs arcs = OID2ARCS("ghostbusters", oid); + struct signed_object_args sobj_args; + int error; + + pr_debug_add("Ghostbusters '%s' {", uri->global); + fnstack_push_uri(uri); + + error = signed_object_args_init(&sobj_args, uri, crls, true); + if (error) + goto end1; + + error = signed_object_decode(&sobj_args, &arcs, handle_vcard, NULL); + if (error) + goto end2; + + error = refs_validate_ee(&sobj_args.refs, pp, sobj_args.uri); + +end2: + signed_object_args_cleanup(&sobj_args); +end1: + pr_debug_rm("}"); + fnstack_pop(); + return error; +} diff --cc src/object/ghostbusters.h index d5610cb7,00000000..b4bbbd8c mode 100644,000000..100644 --- a/src/object/ghostbusters.h +++ b/src/object/ghostbusters.h @@@ -1,11 -1,0 +1,11 @@@ +#ifndef SRC_OBJECT_GHOSTBUSTERS_H_ +#define SRC_OBJECT_GHOSTBUSTERS_H_ + +#include +#include "uri.h" +#include "rpp.h" + - int handle_ghostbusters(struct rpki_uri const *, struct rpp *, ++int ghostbusters_traverse(struct rpki_uri const *, struct rpp *, + STACK_OF(X509_CRL) *); + +#endif /* SRC_OBJECT_GHOSTBUSTERS_H_ */ diff --cc src/object/roa.c index 7ad3e730,00000000..9fe42919 mode 100644,000000..100644 --- a/src/object/roa.c +++ b/src/object/roa.c @@@ -1,236 -1,0 +1,233 @@@ +#include "object/roa.h" + +#include +#include +#include + +#include "config.h" +#include "log.h" +#include "thread_var.h" +#include "asn1/decode.h" +#include "asn1/oid.h" +#include "object/signed_object.h" + +#include + +static int +roa_decode(OCTET_STRING_t *string, void *arg) +{ + return asn1_decode_octet_string(string, &asn_DEF_RouteOriginAttestation, + arg); +} + +static int +print_addr4(struct resources *parent, unsigned long asn, + struct ROAIPAddress *roa_addr) +{ + struct ipv4_prefix prefix; + unsigned long max_length; - char str[INET_ADDRSTRLEN]; - const char *str2; + int error; + + error = prefix4_decode(&roa_addr->address, &prefix); + if (error) + return error; + + if (roa_addr->maxLength != NULL) { + error = asn_INTEGER2ulong(roa_addr->maxLength, &max_length); + if (error) { + if (errno) + pr_errno(errno, "Error casting ROA's IPv4 maxLength"); + return pr_err("The ROA's IPv4 maxLength isn't a valid unsigned long"); + } + + if (max_length > 32) { + return pr_err("maxLength (%lu) is out of bounds (0-32).", + max_length); + } + + if (prefix.len > max_length) { + return pr_err("Prefix length (%u) > maxLength (%lu)", + prefix.len, max_length); + } + + } else { + max_length = prefix.len; + } + - str2 = inet_ntop(AF_INET, &prefix.addr, str, sizeof(str)); - if (str2 == NULL) - return pr_err("inet_ntop() returned NULL."); - + if (!resources_contains_ipv4(parent, &prefix)) { - return pr_err("ROA is not allowed to advertise %s/%u.", str2, - prefix.len); ++ return pr_err("ROA is not allowed to advertise %s/%u.", ++ v4addr2str(&prefix.addr), prefix.len); + } + - fprintf(config_get_roa_output(), "AS%lu,%s/%u,%lu\n", asn, str2, - prefix.len, max_length); - return 0; ++ /* TODO I think we're not validating asn boundaries */ ++ return roa_handle_v4(asn, &prefix, max_length); ++} ++ ++int ++roa_handle_v4(u_int32_t asn, struct ipv4_prefix *prefix, u_int8_t max_length) ++{ ++ return -ENOTIMPLEMENTED; +} + +static int +print_addr6(struct resources *parent, unsigned long asn, + struct ROAIPAddress *roa_addr) +{ + struct ipv6_prefix prefix; + unsigned long max_length; - char str[INET6_ADDRSTRLEN]; - const char *str2; + int error; + + error = prefix6_decode(&roa_addr->address, &prefix); + if (error) + return error; + + if (roa_addr->maxLength != NULL) { + error = asn_INTEGER2ulong(roa_addr->maxLength, &max_length); + if (error) { + if (errno) + pr_errno(errno, "Error casting ROA's IPv6 maxLength"); + return pr_err("The ROA's IPv6 maxLength isn't a valid unsigned long"); + } + + if (max_length > 128) { + return pr_err("maxLength (%lu) is out of bounds (0-128).", + max_length); + } + + if (prefix.len > max_length) { + return pr_err("Prefix length (%u) > maxLength (%lu)", + prefix.len, max_length); + } + + } else { + max_length = prefix.len; + } + - str2 = inet_ntop(AF_INET6, &prefix.addr, str, sizeof(str)); - if (str2 == NULL) - return pr_err("inet_ntop() returned NULL."); - + if (!resources_contains_ipv6(parent, &prefix)) { - return pr_err("ROA is not allowed to advertise %s/%u.", str2, - prefix.len); ++ return pr_err("ROA is not allowed to advertise %s/%u.", ++ v6addr2str(&prefix.addr), prefix.len); + } + - fprintf(config_get_roa_output(), "AS%lu,%s/%u,%lu\n", asn, str2, - prefix.len, max_length); - return 0; ++ return roa_handle_v6(asn, &prefix, max_length); ++} ++ ++int ++roa_handle_v6(u_int32_t asn, struct ipv6_prefix *prefix, u_int8_t max_length) ++{ ++ return -ENOTIMPLEMENTED; +} + +static int +print_addr(struct resources *parent, ASID_t *as_id, uint8_t family, + struct ROAIPAddress *roa_addr) +{ + unsigned long asn; + + if (asn_INTEGER2ulong(as_id, &asn) != 0) { + if (errno) + pr_errno(errno, "Error casting ROA's AS ID value"); + return pr_err("ROA's AS ID couldn't be parsed as unsigned long"); + } + + switch (family) { + case 1: /* IPv4 */ + return print_addr4(parent, asn, roa_addr); + case 2: /* IPv6 */ + return print_addr6(parent, asn, roa_addr); + } + + return pr_err("Unknown family value: %u", family); +} + +static int +__handle_roa(struct RouteOriginAttestation *roa, struct resources *parent) +{ + struct ROAIPAddressFamily *block; + unsigned long version; + int b; + int a; + int error; + + if (roa->version != NULL) { + error = asn_INTEGER2ulong(roa->version, &version); + if (error) { + if (errno) + pr_errno(errno, "Error casting ROA's version"); + return pr_err("The ROA's version isn't a valid long"); + } + /* rfc6482#section-3.1 */ + if (version != 0) + return pr_err("ROA's version (%lu) is nonzero.", version); + } + + /* rfc6482#section-3.3 */ + + if (roa->ipAddrBlocks.list.array == NULL) + return pr_crit("ipAddrBlocks array is NULL."); + + for (b = 0; b < roa->ipAddrBlocks.list.count; b++) { + block = roa->ipAddrBlocks.list.array[b]; + if (block == NULL) + return pr_err("Address block array element is NULL."); + + if (block->addressFamily.size != 2) + goto family_error; + if (block->addressFamily.buf[0] != 0) + goto family_error; + if (block->addressFamily.buf[1] != 1 + && block->addressFamily.buf[1] != 2) + goto family_error; + + if (block->addresses.list.array == NULL) + return pr_err("ROA's address list array is NULL."); + for (a = 0; a < block->addresses.list.count; a++) { + error = print_addr(parent, &roa->asID, + block->addressFamily.buf[1], + block->addresses.list.array[a]); + if (error) + return error; + } + } + + return 0; + +family_error: + return pr_err("ROA's IP family is not v4 or v6."); +} + +int - handle_roa(struct rpki_uri const *uri, struct rpp *pp, ++roa_traverse(struct rpki_uri const *uri, struct rpp *pp, + STACK_OF(X509_CRL) *crls) +{ + static OID oid = OID_ROA; + struct oid_arcs arcs = OID2ARCS("roa", oid); + struct signed_object_args sobj_args; + struct RouteOriginAttestation *roa; + int error; + + pr_debug_add("ROA '%s' {", uri_get_printable(uri)); + fnstack_push_uri(uri); + + error = signed_object_args_init(&sobj_args, uri, crls, false); + if (error) + goto end1; + + error = signed_object_decode(&sobj_args, &arcs, roa_decode, &roa); + if (error) + goto end2; + + error = __handle_roa(roa, sobj_args.res); + if (error) + goto end3; + + error = refs_validate_ee(&sobj_args.refs, pp, sobj_args.uri); + +end3: + ASN_STRUCT_FREE(asn_DEF_RouteOriginAttestation, roa); +end2: + signed_object_args_cleanup(&sobj_args); +end1: + pr_debug_rm("}"); + fnstack_pop(); + return error; +} diff --cc src/object/roa.h index c77b7da1,00000000..1ce475a9 mode 100644,000000..100644 --- a/src/object/roa.h +++ b/src/object/roa.h @@@ -1,10 -1,0 +1,15 @@@ +#ifndef SRC_OBJECT_ROA_H_ +#define SRC_OBJECT_ROA_H_ + +#include ++ ++#include "address.h" +#include "rpp.h" +#include "uri.h" + - int handle_roa(struct rpki_uri const *, struct rpp *, STACK_OF(X509_CRL) *); ++int roa_traverse(struct rpki_uri const *, struct rpp *, STACK_OF(X509_CRL) *); ++ ++int roa_handle_v4(u_int32_t, struct ipv4_prefix *, u_int8_t); ++int roa_handle_v6(u_int32_t, struct ipv6_prefix *, u_int8_t); + +#endif /* SRC_OBJECT_ROA_H_ */ diff --cc src/object/tal.c index 8e8f912a,00000000..11b799fc mode 100644,000000..100644 --- a/src/object/tal.c +++ b/src/object/tal.c @@@ -1,264 -1,0 +1,360 @@@ +#define _GNU_SOURCE + +#include "tal.h" + +#include +#include +#include +#include +#include +#include + +#include "common.h" ++#include "config.h" +#include "line_file.h" +#include "log.h" +#include "random.h" ++#include "state.h" ++#include "thread_var.h" +#include "crypto/base64.h" ++#include "object/certificate.h" ++#include "rsync/rsync.h" + +struct uris { + char **array; /* This is an array of string pointers. */ + unsigned int count; + unsigned int size; +}; + +struct tal { + char const *file_name; + struct uris uris; + unsigned char *spki; /* Decoded; not base64. */ + size_t spki_len; +}; + +static int +uris_init(struct uris *uris) +{ + uris->count = 0; + uris->size = 4; /* Most TALs only define one. */ + uris->array = malloc(uris->size * sizeof(char *)); + return (uris->array != NULL) ? 0 : -ENOMEM; +} + +static void +uris_destroy(struct uris *uris) +{ + unsigned int i; + for (i = 0; i < uris->count; i++) + free(uris->array[i]); + free(uris->array); +} + +static int +uris_add(struct uris *uris, char *uri) +{ + char **tmp; + + if (uris->count + 1 >= uris->size) { + uris->size *= 2; + tmp = realloc(uris->array, uris->size * sizeof(char *)); + if (tmp == NULL) + return pr_enomem(); + uris->array = tmp; + } + + uris->array[uris->count++] = uri; + return 0; +} + +static int +read_uris(struct line_file *lfile, struct uris *uris) +{ + char *uri; + int error; + + error = lfile_read(lfile, &uri); + if (error) + return error; + + if (uri == NULL) + return pr_err("TAL file is empty."); + if (strcmp(uri, "") == 0) { + free(uri); + return pr_err("There's no URI in the first line of the TAL."); + } + + error = uris_add(uris, uri); + if (error) + return error; + + do { + error = lfile_read(lfile, &uri); + if (error) + return error; + + if (uri == NULL) + return pr_err("TAL file ended prematurely. (Expected URI list, blank line and public key.)"); + if (strcmp(uri, "") == 0) { + free(uri); + return 0; /* Happy path */ + } + + error = uris_add(uris, uri); + if (error) + return error; + } while (true); +} + +/* + * Will usually allocate slightly more because of the newlines, but I'm fine + * with it. + */ +static size_t +get_spki_alloc_size(struct line_file *lfile) +{ + struct stat st; + size_t result; + + stat(lfile_name(lfile), &st); + result = st.st_size - lfile_offset(lfile); + + return EVP_DECODE_LENGTH(result); +} + +static int +read_spki(struct line_file *lfile, struct tal *tal) +{ + BIO *encoded; /* base64 encoded. */ + size_t alloc_size; + int error; + + alloc_size = get_spki_alloc_size(lfile); + tal->spki = malloc(alloc_size); + if (tal->spki == NULL) + return -ENOMEM; + + encoded = BIO_new_fp(lfile_fd(lfile), BIO_NOCLOSE); + if (encoded == NULL) { + free(tal->spki); + return crypto_err("BIO_new_fp() returned NULL"); + } + + error = base64_decode(encoded, tal->spki, alloc_size, &tal->spki_len); + if (error) + free(tal->spki); + + BIO_free(encoded); + return error; +} + +/** + * @file_name is expected to outlive @result. + */ +int +tal_load(char const *file_name, struct tal **result) +{ + struct line_file *lfile; + struct tal *tal; + int error; + + error = lfile_open(file_name, &lfile); + if (error) { + pr_errno(error, "Error opening file '%s'", file_name); + goto fail4; + } + + tal = malloc(sizeof(struct tal)); + if (tal == NULL) { + error = -ENOMEM; + goto fail3; + } + + tal->file_name = file_name; + + error = uris_init(&tal->uris); + if (error) + goto fail2; + + error = read_uris(lfile, &tal->uris); + if (error) + goto fail1; + + error = read_spki(lfile, tal); + if (error) + goto fail1; + + lfile_close(lfile); + *result = tal; + return 0; + +fail1: + uris_destroy(&tal->uris); +fail2: + free(tal); +fail3: + lfile_close(lfile); +fail4: + return error; +} + +void tal_destroy(struct tal *tal) +{ + if (tal == NULL) + return; + + uris_destroy(&tal->uris); + free(tal->spki); + free(tal); +} + +int +foreach_uri(struct tal *tal, foreach_uri_cb cb) +{ + struct rpki_uri uri; + unsigned int i; + int error; + + for (i = 0; i < tal->uris.count; i++) { + error = uri_init_str(&uri, tal->uris.array[i], + strlen(tal->uris.array[i])); + if (error == ENOTRSYNC) { + /* Log level should probably be INFO. */ + pr_debug("TAL has non-RSYNC URI; ignoring."); + continue; + } + if (error) + return error; + + error = cb(tal, &uri); + uri_cleanup(&uri); + if (error) + return error; + } + + return 0; +} + +void +tal_shuffle_uris(struct tal *tal) +{ + char **array = tal->uris.array; + unsigned int count = tal->uris.count; + char *tmp; + long random_index; + unsigned int i; + + random_init(); + + for (i = 0; i < count; i++) { + tmp = array[i]; + random_index = random_at_most(count - 1 - i) + i; + array[i] = array[random_index]; + array[random_index] = tmp; + } +} + +char const * +tal_get_file_name(struct tal *tal) +{ + return tal->file_name; +} + +void +tal_get_spki(struct tal *tal, unsigned char const **buffer, size_t *len) +{ + *buffer = tal->spki; + *len = tal->spki_len; +} ++ ++/** ++ * Performs the whole validation walkthrough on uri @uri, which is assumed to ++ * have been extracted from a TAL. ++ */ ++static int ++handle_tal_uri(struct tal *tal, struct rpki_uri const *uri) ++{ ++ /* ++ * Because of the way the foreach iterates, this function must return ++ * ++ * - 0 on soft errors. ++ * - `> 0` on URI handled successfully. ++ * - `< 0` on hard errors. ++ * ++ * A "soft error" is "the connection to the preferred URI fails, or the ++ * retrieved CA certificate public key does not match the TAL public ++ * key." (RFC 7730) ++ * ++ * A "hard error" is any other error. ++ */ ++ ++ struct validation *state; ++ int error; ++ ++ error = download_files(uri, true); ++ if (error) { ++ return pr_warn("TAL URI '%s' could not be RSYNC'd.", ++ uri->global); ++ } ++ ++ error = validation_prepare(&state, tal); ++ if (error) ++ return ENSURE_NEGATIVE(error); ++ ++ pr_debug_add("TAL URI '%s' {", uri_get_printable(uri)); ++ ++ if (!uri_is_certificate(uri)) { ++ pr_err("TAL file does not point to a certificate. (Expected .cer, got '%s')", ++ uri_get_printable(uri)); ++ error = -EINVAL; ++ goto end; ++ } ++ ++ error = certificate_traverse(NULL, uri, NULL, true); ++ if (error) { ++ switch (validation_pubkey_state(state)) { ++ case PKS_INVALID: ++ error = 0; ++ break; ++ case PKS_VALID: ++ case PKS_UNTESTED: ++ error = ENSURE_NEGATIVE(error); ++ break; ++ } ++ } else { ++ error = 1; ++ } ++ ++end: validation_destroy(state); ++ pr_debug_rm("}"); ++ return error; ++} ++ ++/* TODO set @updated */ ++int ++perform_standalone_validation(bool *updated) ++{ ++ struct tal *tal; ++ int error; ++ ++ fnstack_init(); ++ fnstack_push(config_get_tal()); ++ ++ error = tal_load(config_get_tal(), &tal); ++ if (error) ++ goto end; ++ ++ if (config_get_shuffle_tal_uris()) ++ tal_shuffle_uris(tal); ++ error = foreach_uri(tal, handle_tal_uri); ++ if (error > 0) ++ error = 0; ++ else if (error == 0) ++ error = pr_err("None of the URIs of the TAL yielded a successful traversal."); ++ ++end: tal_destroy(tal); ++ fnstack_pop(); ++ fnstack_cleanup(); ++ return error; ++} diff --cc src/object/tal.h index e092989f,00000000..a79ee43a mode 100644,000000..100644 --- a/src/object/tal.h +++ b/src/object/tal.h @@@ -1,21 -1,0 +1,23 @@@ +#ifndef TAL_OBJECT_H_ +#define TAL_OBJECT_H_ + +/* This is RFC 7730. */ + +#include +#include "uri.h" + +struct tal; + +int tal_load(char const *, struct tal **); +void tal_destroy(struct tal *); + +typedef int (*foreach_uri_cb)(struct tal *, struct rpki_uri const *); +int foreach_uri(struct tal *, foreach_uri_cb); +void tal_shuffle_uris(struct tal *); + +char const *tal_get_file_name(struct tal *); +void tal_get_spki(struct tal *, unsigned char const **, size_t *); + ++int perform_standalone_validation(bool *); ++ +#endif /* TAL_OBJECT_H_ */ diff --cc src/rpp.c index bcb7eda2,00000000..a546b456 mode 100644,000000..100644 --- a/src/rpp.c +++ b/src/rpp.c @@@ -1,161 -1,0 +1,161 @@@ +#include "rpp.h" + +#include +#include "array_list.h" +#include "log.h" +#include "thread_var.h" +#include "uri.h" +#include "object/certificate.h" +#include "object/crl.h" +#include "object/ghostbusters.h" +#include "object/roa.h" + +ARRAY_LIST(uris, struct rpki_uri) + +/** A Repository Publication Point (RFC 6481), as described by some manifest. */ +struct rpp { + struct uris certs; /* Certificates */ + + struct rpki_uri crl; /* Certificate Revocation List */ + bool crl_set; + + /* The Manifest is not needed for now. */ + + struct uris roas; /* Route Origin Attestations */ + + struct uris ghostbusters; +}; + +struct rpp * +rpp_create(void) +{ + struct rpp *result; + + result = malloc(sizeof(struct rpp)); + if (result == NULL) + goto fail1; + + if (uris_init(&result->certs) != 0) + goto fail2; + result->crl_set = false; + if (uris_init(&result->roas) != 0) + goto fail3; + if (uris_init(&result->ghostbusters) != 0) + goto fail4; + + return result; + +fail4: + uris_cleanup(&result->roas, uri_cleanup); +fail3: + uris_cleanup(&result->certs, uri_cleanup); +fail2: + free(result); +fail1: + return NULL; +} + +void +rpp_destroy(struct rpp *pp) +{ + uris_cleanup(&pp->certs, uri_cleanup); + uri_cleanup(&pp->crl); + uris_cleanup(&pp->roas, uri_cleanup); + uris_cleanup(&pp->ghostbusters, uri_cleanup); + free(pp); +} + +int +rpp_add_cert(struct rpp *pp, struct rpki_uri *uri) +{ + return uris_add(&pp->certs, uri); +} + +int +rpp_add_roa(struct rpp *pp, struct rpki_uri *uri) +{ + return uris_add(&pp->roas, uri); +} + +int +rpp_add_ghostbusters(struct rpp *pp, struct rpki_uri *uri) +{ + return uris_add(&pp->ghostbusters, uri); +} + +int +rpp_add_crl(struct rpp *pp, struct rpki_uri *uri) +{ + /* rfc6481#section-2.2 */ + if (pp->crl_set) + return pr_err("Repository Publication Point has more than one CRL."); + + pp->crl = *uri; + pp->crl_set = true; + return 0; +} + +static int +add_crl_to_stack(struct rpp *pp, STACK_OF(X509_CRL) *crls) +{ + X509_CRL *crl; + int error; + int idx; + + fnstack_push_uri(&pp->crl); + + error = crl_load(&pp->crl, &crl); + if (error) + goto end; + + idx = sk_X509_CRL_push(crls, crl); + if (idx <= 0) { + error = crypto_err("Could not add CRL to a CRL stack"); + X509_CRL_free(crl); + goto end; + } + +end: + fnstack_pop(); + return error; +} + +struct rpki_uri const * +rpp_get_crl(struct rpp const *pp) +{ + return pp->crl_set ? &pp->crl : NULL; +} + +int +rpp_traverse(struct rpp *pp) +{ + /* + * TODO is the stack supposed to have only the CRLs of this layer, + * or all of them? + */ + STACK_OF(X509_CRL) *crls; + struct rpki_uri *uri; + int error; + + crls = sk_X509_CRL_new_null(); + if (crls == NULL) + return pr_enomem(); + error = add_crl_to_stack(pp, crls); + if (error) + goto end; + + /* Use CRL stack to validate certificates, and also traverse them. */ + ARRAYLIST_FOREACH(&pp->certs, uri) + certificate_traverse(pp, uri, crls, false); + + /* Use valid address ranges to print ROAs that match them. */ + ARRAYLIST_FOREACH(&pp->roas, uri) - handle_roa(uri, pp, crls); ++ roa_traverse(uri, pp, crls); + + ARRAYLIST_FOREACH(&pp->ghostbusters, uri) - handle_ghostbusters(uri, pp, crls); ++ ghostbusters_traverse(uri, pp, crls); + +end: + sk_X509_CRL_pop_free(crls, X509_CRL_free); + return error; +} diff --cc src/rtr/err_pdu.c index 00000000,cbea8177..b8b4e3a8 mode 000000,100644..100644 --- a/src/rtr/err_pdu.c +++ b/src/rtr/err_pdu.c @@@ -1,0 -1,71 +1,72 @@@ + #include "err_pdu.h" + + #include + #include + #include "pdu_sender.h" ++#include "log.h" + + int + err_pdu_send(int fd, u_int8_t version, u_int16_t code, void *err_pdu_header, - char *message) ++ char const *message) + { + int error; + + error = send_error_report_pdu(fd, version, code, err_pdu_header, + message); + if (err_pdu_is_fatal(code)) { + warnx("Fatal error report PDU sent [code %u], closing socket.", + code); + close(fd); + } + + return error; + } + + bool + err_pdu_is_fatal(u_int16_t code) + { + /* Only NO_DATA_AVAILABLE error isn't fatal */ + return code != ERR_PDU_NO_DATA_AVAILABLE; + } + + void + err_pdu_log(u_int16_t code, char *message) + { - char *code_title; ++ char const *code_title; + + switch (code) { + case ERR_PDU_CORRUPT_DATA: + code_title = "Corrupt Data"; + break; + case ERR_PDU_INTERNAL_ERROR: + code_title = "Internal Error"; + break; + case ERR_PDU_NO_DATA_AVAILABLE: + code_title = "No Data Available"; + break; + case ERR_PDU_INVALID_REQUEST: + code_title = "Invalid Request"; + break; + case ERR_PDU_UNSUP_PROTO_VERSION: + code_title = "Unsupported Protocol Version"; + break; + case ERR_PDU_UNSUP_PDU_TYPE: + code_title = "Unsupported PDU Type"; + break; + case ERR_PDU_WITHDRAWAL_UNKNOWN: + code_title = "Withdrawal of Unknown Record"; + break; + case ERR_PDU_DUPLICATE_ANNOUNCE: + code_title = "Duplicate Announcement Received"; + break; + case ERR_PDU_UNEXPECTED_PROTO_VERSION: + code_title = "Unexpected Protocol Version"; + break; + default: + code_title = "Unknown error code"; + break; + } + - warnx("Error report PDU info: '%s', message '%s'.", ++ pr_err("Error report PDU info: '%s', message '%s'.", + code_title, message == NULL ? "[empty]" : message); + } diff --cc src/rtr/err_pdu.h index 00000000,bdb0c62d..fa6f4ba9 mode 000000,100644..100644 --- a/src/rtr/err_pdu.h +++ b/src/rtr/err_pdu.h @@@ -1,0 -1,22 +1,22 @@@ + #ifndef SRC_RTR_ERR_PDU_H_ + #define SRC_RTR_ERR_PDU_H_ + + #include + #include + + #define ERR_PDU_CORRUPT_DATA 0 + #define ERR_PDU_INTERNAL_ERROR 1 + #define ERR_PDU_NO_DATA_AVAILABLE 2 + #define ERR_PDU_INVALID_REQUEST 3 + #define ERR_PDU_UNSUP_PROTO_VERSION 4 + #define ERR_PDU_UNSUP_PDU_TYPE 5 + #define ERR_PDU_WITHDRAWAL_UNKNOWN 6 + #define ERR_PDU_DUPLICATE_ANNOUNCE 7 + #define ERR_PDU_UNEXPECTED_PROTO_VERSION 8 + + -int err_pdu_send(int, u_int8_t, u_int16_t, void *, char *); ++int err_pdu_send(int, u_int8_t, u_int16_t, void *, char const *); + bool err_pdu_is_fatal(u_int16_t); + void err_pdu_log(u_int16_t, char *); + + #endif /* SRC_RTR_ERR_PDU_H_ */ diff --cc src/rtr/pdu.c index 00000000,770e52a8..d4f9d51d mode 000000,100644..100644 --- a/src/rtr/pdu.c +++ b/src/rtr/pdu.c @@@ -1,0 -1,229 +1,227 @@@ + #include "pdu.h" + + #include + #include + #include + #include + + #include "../common.h" + #include "pdu_handler.h" + + static int pdu_header_from_stream(int, struct pdu_header *); + static int serial_notify_from_stream(struct pdu_header *, int, void *); + static int serial_query_from_stream(struct pdu_header *, int, void *); + static int reset_query_from_stream(struct pdu_header *, int, void *); + static int cache_response_from_stream(struct pdu_header *, int, void *); + static int ipv4_prefix_from_stream(struct pdu_header *, int, void *); + static int ipv6_prefix_from_stream(struct pdu_header *, int, void *); + static int end_of_data_from_stream(struct pdu_header *, int, void *); + static int cache_reset_from_stream(struct pdu_header *, int, void *); + static int error_report_from_stream(struct pdu_header *, int, void *); + static void error_report_destroy(void *); + + int + pdu_load(int fd, void **pdu, struct pdu_metadata const **metadata, + u_int8_t *rtr_version) + { + struct pdu_header header; + struct pdu_metadata const *meta; + int err; + + err = pdu_header_from_stream(fd, &header); + if (err) + return err; + + meta = pdu_get_metadata(header.pdu_type); + if (!meta) + return -ENOENT; /* TODO try to skip it anyway? */ + + *pdu = malloc(meta->length); + if (*pdu == NULL) + return -ENOMEM; + + err = meta->from_stream(&header, fd, *pdu); + if (err) { + free(*pdu); + return err; + } + *rtr_version = header.protocol_version; + + if (metadata) + *metadata = meta; + return 0; + } + + static int + pdu_header_from_stream(int fd, struct pdu_header *header) + { + /* If the first read yields no bytes, the connection was terminated. */ + return read_int8(fd, &header->protocol_version) + || read_int8(fd, &header->pdu_type) - || read_int16(fd, &header->session_id) ++ || read_int16(fd, &header->m.session_id) + || read_int32(fd, &header->length); + } + + static int + serial_notify_from_stream(struct pdu_header *header, int fd, void *pdu_void) + { + struct serial_notify_pdu *pdu = pdu_void; + memcpy(&pdu->header, header, sizeof(*header)); + return read_int32(fd, &pdu->serial_number); + } + + static int + serial_query_from_stream(struct pdu_header *header, int fd, void *pdu_void) + { + struct serial_query_pdu *pdu = pdu_void; + memcpy(&pdu->header, header, sizeof(*header)); + return read_int32(fd, &pdu->serial_number); + } + + static int + reset_query_from_stream(struct pdu_header *header, int fd, void *pdu_void) + { + struct reset_query_pdu *pdu = pdu_void; + memcpy(&pdu->header, header, sizeof(*header)); + return 0; + } + + static int + cache_response_from_stream(struct pdu_header *header, int fd, void *pdu_void) + { + struct cache_response_pdu *pdu = pdu_void; + memcpy(&pdu->header, header, sizeof(*header)); + return 0; + } + + static int + ipv4_prefix_from_stream(struct pdu_header *header, int fd, void *pdu_void) + { + struct ipv4_prefix_pdu *pdu = pdu_void; + memcpy(&pdu->header, header, sizeof(*header)); + return read_int8(fd, &pdu->flags) + || read_int8(fd, &pdu->prefix_length) + || read_int8(fd, &pdu->max_length) + || read_int8(fd, &pdu->zero) + || read_in_addr(fd, &pdu->ipv4_prefix) + || read_int32(fd, &pdu->asn); + } + + static int + ipv6_prefix_from_stream(struct pdu_header *header, int fd, void *pdu_void) + { + struct ipv6_prefix_pdu *pdu = pdu_void; + memcpy(&pdu->header, header, sizeof(*header)); + return read_int8(fd, &pdu->flags) + || read_int8(fd, &pdu->prefix_length) + || read_int8(fd, &pdu->max_length) + || read_int8(fd, &pdu->zero) + || read_in6_addr(fd, &pdu->ipv6_prefix) + || read_int32(fd, &pdu->asn); + } + + static int + end_of_data_from_stream(struct pdu_header *header, int fd, void *pdu_void) + { + struct end_of_data_pdu *pdu = pdu_void; + memcpy(&pdu->header, header, sizeof(*header)); + return read_int32(fd, &pdu->serial_number); + } + + static int + cache_reset_from_stream(struct pdu_header *header, int fd, void *pdu_void) + { + struct cache_reset_pdu *pdu = pdu_void; + memcpy(&pdu->header, header, sizeof(*header)); + return 0; + } + + static int + error_report_from_stream(struct pdu_header *header, int fd, void *pdu_void) + { + struct error_report_pdu *pdu = pdu_void; + u_int32_t sub_pdu_len; /* TODO use this for something */ + u_int8_t rtr_version; + int error; + + memcpy(&pdu->header, header, sizeof(*header)); + + error = read_int32(fd, &sub_pdu_len); + if (error) + return error; + + error = pdu_load(fd, &pdu->erroneous_pdu, NULL, &rtr_version); + if (error) + return -EINVAL; + + error = read_string(fd, &pdu->error_message); + if (error) { + free(pdu->erroneous_pdu); + return error; + } + + return 0; + } + + static void + error_report_destroy(void *pdu_void) + { + struct error_report_pdu *pdu = pdu_void; + struct pdu_header *sub_hdr; + struct pdu_metadata const *sub_meta; + + sub_hdr = pdu_get_header(pdu->erroneous_pdu); + sub_meta = pdu_get_metadata(sub_hdr->pdu_type); + if (sub_meta) + sub_meta->destructor(pdu->erroneous_pdu); + else + warnx("Unknown PDU type (%u).", sub_hdr->pdu_type); + + free(pdu->error_message); + free(pdu_void); + } + + #define DEFINE_METADATA(name, dtor) \ + static struct pdu_metadata const name ## _meta = { \ + .length = sizeof(struct name ## _pdu), \ + .from_stream = name ## _from_stream, \ + .handle = handle_ ## name ## _pdu, \ + .destructor = dtor, \ + } + + DEFINE_METADATA(serial_notify, free); + DEFINE_METADATA(serial_query, free); + DEFINE_METADATA(reset_query, free); + DEFINE_METADATA(cache_response, free); + DEFINE_METADATA(ipv4_prefix, free); + DEFINE_METADATA(ipv6_prefix, free); + DEFINE_METADATA(end_of_data, free); + DEFINE_METADATA(cache_reset, free); + DEFINE_METADATA(error_report, error_report_destroy); + + struct pdu_metadata const *const pdu_metadatas[] = { + /* 0 */ &serial_notify_meta, + /* 1 */ &serial_query_meta, + /* 2 */ &reset_query_meta, + /* 3 */ &cache_response_meta, + /* 4 */ &ipv4_prefix_meta, + /* 5 */ NULL, + /* 6 */ &ipv6_prefix_meta, + /* 7 */ &end_of_data_meta, + /* 8 */ &cache_reset_meta, + /* 9 */ NULL, + /* 10 */ &error_report_meta, + }; + + struct pdu_metadata const * + pdu_get_metadata(u_int8_t type) + { - return (type < 0 || ARRAY_SIZE(pdu_metadatas) <= type) - ? NULL - : pdu_metadatas[type]; ++ return (ARRAY_LEN(pdu_metadatas) <= type) ? NULL : pdu_metadatas[type]; + } + + struct pdu_header * + pdu_get_header(void *pdu) + { + /* The header is by definition the first field of every PDU. */ + return pdu; + } diff --cc src/rtr/pdu.h index 00000000,3fd76164..8b441e1c mode 000000,100644..100644 --- a/src/rtr/pdu.h +++ b/src/rtr/pdu.h @@@ -1,0 -1,102 +1,102 @@@ + #ifndef RTR_PDU_H_ + #define RTR_PDU_H_ + + #include + + #include "../common.h" + #include "primitive_reader.h" + + #define RTR_V0 0 + #define RTR_V1 1 + + #define PDU_TYPE_SERIAL_NOTIFY 0 + #define PDU_TYPE_CACHE_RESPONSE 3 + #define PDU_TYPE_IPV4_PREFIX 4 + #define PDU_TYPE_IPV6_PREFIX 6 + #define PDU_TYPE_END_OF_DATA 7 + #define PDU_TYPE_CACHE_RESET 8 + #define PDU_TYPE_ERROR_REPORT 10 + + struct pdu_header { + u_int8_t protocol_version; + u_int8_t pdu_type; + union { + u_int16_t session_id; + u_int16_t reserved; + u_int16_t error_code; - }; ++ } m; /* Note: "m" stands for "meh." I have no idea what to call this. */ + u_int32_t length; + }; + + struct serial_notify_pdu { + struct pdu_header header; + u_int32_t serial_number; + }; + + struct serial_query_pdu { + struct pdu_header header; + u_int32_t serial_number; + }; + + struct reset_query_pdu { + struct pdu_header header; + }; + + struct cache_response_pdu { + struct pdu_header header; + }; + + struct ipv4_prefix_pdu { + struct pdu_header header; + u_int8_t flags; + u_int8_t prefix_length; + u_int8_t max_length; + u_int8_t zero; + struct in_addr ipv4_prefix; + u_int32_t asn; + }; + + struct ipv6_prefix_pdu { + struct pdu_header header; + u_int8_t flags; + u_int8_t prefix_length; + u_int8_t max_length; + u_int8_t zero; + struct in6_addr ipv6_prefix; + u_int32_t asn; + }; + + struct end_of_data_pdu { + struct pdu_header header; + u_int32_t serial_number; + u_int32_t refresh_interval; + u_int32_t retry_interval; + u_int32_t expire_interval; + }; + + struct cache_reset_pdu { + struct pdu_header header; + }; + + struct error_report_pdu { + struct pdu_header header; + u_int32_t error_pdu_length; + void *erroneous_pdu; + u_int32_t error_message_length; + rtr_char *error_message; + }; + + struct pdu_metadata { + size_t length; + int (*from_stream)(struct pdu_header *, int, void *); + int (*handle)(int, void *); + void (*destructor)(void *); + }; + + __BEGIN_DECLS + int pdu_load(int, void **, struct pdu_metadata const **, u_int8_t *); + struct pdu_metadata const *pdu_get_metadata(u_int8_t); + struct pdu_header *pdu_get_header(void *); + __END_DECLS + + #endif /* RTR_PDU_H_ */ diff --cc src/rtr/pdu_handler.c index 00000000,0ec9c4bf..ff87250e mode 000000,100644..100644 --- a/src/rtr/pdu_handler.c +++ b/src/rtr/pdu_handler.c @@@ -1,0 -1,173 +1,173 @@@ + #include "pdu_handler.h" + + #include + #include + #include + #include + + #include "err_pdu.h" + #include "pdu.h" + #include "pdu_sender.h" + #include "vrps.h" + + static int -warn_unexpected_pdu(int fd, void *pdu, char *pdu_name) ++warn_unexpected_pdu(int fd, void *pdu, char const *pdu_name) + { + struct pdu_header *pdu_header = pdu; + warnx("Unexpected %s PDU received", pdu_name); + err_pdu_send(fd, pdu_header->protocol_version, ERR_PDU_UNSUP_PDU_TYPE, + pdu_header, "Unexpected PDU received"); + return -EINVAL; + } + + int + handle_serial_notify_pdu(int fd, void *pdu) + { + return warn_unexpected_pdu(fd, pdu, "Serial Notify"); + } + + static int + send_commmon_exchange(struct sender_common *common) + { + int error; + + /* Send Cache response PDU */ + error = send_cache_response_pdu(common); + if (error) + return error; + + /* Send Payload PDUs */ + error = send_payload_pdus(common); + if (error) + return error; + + /* Send End of data PDU */ + return send_end_of_data_pdu(common); + } + + int + handle_serial_query_pdu(int fd, void *pdu) + { + struct serial_query_pdu *received = pdu; + struct sender_common common; + int error, updates; + u_int32_t current_serial; + u_int16_t session_id; + u_int8_t version; + + /* + * RFC 6810 and 8210: + * "If [...] either the router or the cache finds that the value of the + * Session ID is not the same as the other's, the party which detects + * the mismatch MUST immediately terminate the session with an Error + * Report PDU with code 0 ("Corrupt Data")" + */ + version = received->header.protocol_version; + session_id = get_current_session_id(version); - if (received->header.session_id != session_id) ++ if (received->header.m.session_id != session_id) + return err_pdu_send(fd, version, ERR_PDU_CORRUPT_DATA, + &received->header, NULL); + + current_serial = get_last_serial_number(); + init_sender_common(&common, fd, version, &session_id, + &received->serial_number, ¤t_serial); + + updates = deltas_db_status(common.start_serial); + switch (updates) { + case NO_DATA_AVAILABLE: + /* https://tools.ietf.org/html/rfc8210#section-8.4 */ + return err_pdu_send(fd, version, ERR_PDU_NO_DATA_AVAILABLE, + NULL, NULL); + case DIFF_UNDETERMINED: + /* https://tools.ietf.org/html/rfc8210#section-8.3 */ + return send_cache_reset_pdu(&common); + case DIFF_AVAILABLE: + /* https://tools.ietf.org/html/rfc8210#section-8.2 */ + return send_commmon_exchange(&common); + case NO_DIFF: + /* Typical exchange with no Payloads */ + error = send_cache_response_pdu(&common); + if (error) + return error; + return send_end_of_data_pdu(&common); + default: + warnx("Reached 'unreachable' code"); + return -EINVAL; + } + } + + int + handle_reset_query_pdu(int fd, void *pdu) + { + struct reset_query_pdu *received = pdu; + struct sender_common common; + u_int32_t current_serial; + u_int16_t session_id; + u_int8_t version; + int updates; + + version = received->header.protocol_version; + session_id = get_current_session_id(version); + current_serial = get_last_serial_number(); + init_sender_common(&common, fd, version, &session_id, NULL, + ¤t_serial); + + updates = deltas_db_status(NULL); + switch (updates) { + case NO_DATA_AVAILABLE: + /* https://tools.ietf.org/html/rfc8210#section-8.4 */ + return err_pdu_send(fd, version, ERR_PDU_NO_DATA_AVAILABLE, + NULL, NULL); + case DIFF_AVAILABLE: + /* https://tools.ietf.org/html/rfc8210#section-8.1 */ + return send_commmon_exchange(&common); + default: + warnx("Reached 'unreachable' code"); + return -EINVAL; + } + } + + int + handle_cache_response_pdu(int fd, void *pdu) + { + return warn_unexpected_pdu(fd, pdu, "Cache Response"); + } + + int + handle_ipv4_prefix_pdu(int fd, void *pdu) + { + return warn_unexpected_pdu(fd, pdu, "IPv4 Prefix"); + } + + int + handle_ipv6_prefix_pdu(int fd, void *pdu) + { + return warn_unexpected_pdu(fd, pdu, "IPv6 Prefix"); + } + + int + handle_end_of_data_pdu(int fd, void *pdu) + { + return warn_unexpected_pdu(fd, pdu, "End of Data"); + } + + int + handle_cache_reset_pdu(int fd, void *pdu) + { + return warn_unexpected_pdu(fd, pdu, "Cache Reset"); + } + + int + handle_error_report_pdu(int fd, void *pdu) + { + struct error_report_pdu *received = pdu; + - if (err_pdu_is_fatal(received->header.error_code)) { ++ if (err_pdu_is_fatal(received->header.m.error_code)) { + warnx("Fatal error report PDU received [code %u], closing socket.", - received->header.error_code); ++ received->header.m.error_code); + close(fd); + } - err_pdu_log(received->header.error_code, received->error_message); ++ err_pdu_log(received->header.m.error_code, received->error_message); + + return 0; + } diff --cc src/rtr/pdu_sender.c index 00000000,a7bf2e1a..1148d851 mode 000000,100644..100644 --- a/src/rtr/pdu_sender.c +++ b/src/rtr/pdu_sender.c @@@ -1,0 -1,291 +1,291 @@@ + #include "pdu_sender.h" + + #include + #include + #include + #include + #include + #include + -#include "../configuration.h" -#include "../vrps.h" -#include "pdu_serializer.h" ++#include "config.h" ++#include "vrps.h" ++#include "rtr/pdu_serializer.h" + + /* Header length field is always 64 bits long */ + #define HEADER_LENGTH 8 + /* IPvN PDUs length without header */ + #define IPV4_PREFIX_LENGTH 12 + #define IPV6_PREFIX_LENGTH 24 + + void + init_sender_common(struct sender_common *common, int fd, u_int8_t version, + u_int16_t *session_id, u_int32_t *start_serial, u_int32_t *end_serial) + { + common->fd = fd; + common->version = version; + common->session_id = session_id == NULL ? 0 : session_id; + common->start_serial = start_serial; + common->end_serial = end_serial; + } + /* + * Set all the header values, EXCEPT length field. + */ + static void + set_header_values(struct pdu_header *header, u_int8_t version, u_int8_t type, + u_int16_t reserved) + { + header->protocol_version = version; + header->pdu_type = type; - header->reserved = reserved; ++ header->m.reserved = reserved; + } + + static u_int32_t + length_serial_notify_pdu(struct serial_notify_pdu *pdu) + { + return HEADER_LENGTH + sizeof(pdu->serial_number); + } + + static u_int32_t + length_ipvx_prefix_pdu(bool isv4) + { + return HEADER_LENGTH + + (isv4 ? IPV4_PREFIX_LENGTH : IPV6_PREFIX_LENGTH); + } + + static u_int32_t + length_end_of_data_pdu(struct end_of_data_pdu *pdu) + { + u_int32_t len; + + len = HEADER_LENGTH; + len += sizeof(pdu->serial_number); + if (pdu->header.protocol_version == RTR_V1) { + len += sizeof(pdu->refresh_interval); + len += sizeof(pdu->retry_interval); + len += sizeof(pdu->expire_interval); + } + + return len; + } + + static u_int32_t + length_error_report_pdu(struct error_report_pdu *pdu) + { + return HEADER_LENGTH + + pdu->error_pdu_length + sizeof(pdu->error_pdu_length) + + pdu->error_message_length + sizeof(pdu->error_message_length); + } + + static int + send_response(int fd, char *data, size_t data_len) + { + struct data_buffer buffer; + int error; + + init_buffer(&buffer); + /* Check for buffer overflow */ + if (data_len > buffer.capacity) { + warnx("Response buffer out of capacity"); + return -EINVAL; + } + memcpy(buffer.data, data, data_len); + buffer.len = data_len; + + error = write(fd, buffer.data, buffer.len); + free_buffer(&buffer); + if (error < 0) { + warnx("Error sending response"); + return -EINVAL; + } + + return 0; + } + + int + send_serial_notify_pdu(struct sender_common *common) + { + struct serial_notify_pdu pdu; + char data[BUFFER_SIZE]; + size_t len; + + set_header_values(&pdu.header, common->version, PDU_TYPE_SERIAL_NOTIFY, + *common->session_id); + + pdu.serial_number = *common->start_serial; + pdu.header.length = length_serial_notify_pdu(&pdu); + + len = serialize_serial_notify_pdu(&pdu, data); + + return send_response(common->fd, data, len); + } + + int + send_cache_reset_pdu(struct sender_common *common) + { + struct cache_reset_pdu pdu; + char data[BUFFER_SIZE]; + size_t len; + + /* This PDU has only the header */ + set_header_values(&pdu.header, common->version, PDU_TYPE_CACHE_RESET, + 0); + pdu.header.length = HEADER_LENGTH; + + len = serialize_cache_reset_pdu(&pdu, data); + return send_response(common->fd, data, len); + } + + int + send_cache_response_pdu(struct sender_common *common) + { + struct cache_response_pdu pdu; + char data[BUFFER_SIZE]; + size_t len; + + /* This PDU has only the header */ + set_header_values(&pdu.header, common->version, + PDU_TYPE_CACHE_RESPONSE, *common->session_id); + pdu.header.length = HEADER_LENGTH; + + len = serialize_cache_response_pdu(&pdu, data); + + return send_response(common->fd, data, len); + } + + static int + send_ipv4_prefix_pdu(struct sender_common *common, struct vrp *vrp) + { + struct ipv4_prefix_pdu pdu; + char data[BUFFER_SIZE]; + size_t len; + + set_header_values(&pdu.header, common->version, PDU_TYPE_IPV4_PREFIX, + 0); + + pdu.flags = vrp->flags; + pdu.prefix_length = vrp->prefix_length; + pdu.max_length = vrp->max_prefix_length; + pdu.zero = 0; - pdu.ipv4_prefix = vrp->ipv4_prefix; ++ pdu.ipv4_prefix = vrp->prefix.ipv4; + pdu.asn = vrp->asn; + pdu.header.length = length_ipvx_prefix_pdu(true); + + len = serialize_ipv4_prefix_pdu(&pdu, data); + + return send_response(common->fd, data, len); + } + + static int + send_ipv6_prefix_pdu(struct sender_common *common, struct vrp *vrp) + { + struct ipv6_prefix_pdu pdu; + char data[BUFFER_SIZE]; + size_t len; + + set_header_values(&pdu.header, common->version, PDU_TYPE_IPV6_PREFIX, + 0); + + pdu.flags = vrp->flags; + pdu.prefix_length = vrp->prefix_length; + pdu.max_length = vrp->max_prefix_length; + pdu.zero = 0; - pdu.ipv6_prefix = vrp->ipv6_prefix; ++ pdu.ipv6_prefix = vrp->prefix.ipv6; + pdu.asn = vrp->asn; + pdu.header.length = length_ipvx_prefix_pdu(false); + + len = serialize_ipv6_prefix_pdu(&pdu, data); + + return send_response(common->fd, data, len); + } + + int + send_payload_pdus(struct sender_common *common) + { + struct vrp *vrps, *ptr; + unsigned int len; + int error; + + vrps = malloc(sizeof(struct vrp)); + if (vrps == NULL) { + warn("Couldn't allocate VRPs to send PDUs"); + return -ENOMEM; + } + len = get_vrps_delta(common->start_serial, common->end_serial, &vrps); + if (len == 0) + goto end; + + for (ptr = vrps; (ptr - vrps) < len; ptr++) { + if (ptr->addr_fam == AF_INET) + error = send_ipv4_prefix_pdu(common, ptr); + else if (ptr->addr_fam == AF_INET6) + error = send_ipv6_prefix_pdu(common, ptr); + else + error = -EINVAL; + + if (error) { + free(vrps); + return error; + } + } + end: + free(vrps); + return 0; + } + + int + send_end_of_data_pdu(struct sender_common *common) + { + struct end_of_data_pdu pdu; + char data[BUFFER_SIZE]; + size_t len; + + set_header_values(&pdu.header, common->version, PDU_TYPE_END_OF_DATA, + *common->session_id); + pdu.serial_number = *common->end_serial; + if (common->version == RTR_V1) { + pdu.refresh_interval = config_get_refresh_interval(); + pdu.retry_interval = config_get_retry_interval(); + pdu.expire_interval = config_get_expire_interval(); + } + pdu.header.length = length_end_of_data_pdu(&pdu); + + len = serialize_end_of_data_pdu(&pdu, data); + + return send_response(common->fd, data, len); + } + + int + send_error_report_pdu(int fd, u_int8_t version, u_int16_t code, -struct pdu_header *err_pdu_header, char *message) ++struct pdu_header *err_pdu_header, char const *message) + { + struct error_report_pdu pdu; + char data[BUFFER_SIZE]; + size_t len; + + set_header_values(&pdu.header, version, PDU_TYPE_ERROR_REPORT, + code); + + pdu.error_pdu_length = 0; + pdu.erroneous_pdu = (void *)err_pdu_header; + if (err_pdu_header != NULL) + pdu.error_pdu_length = sizeof(err_pdu_header); + + pdu.error_message_length = 0; + pdu.error_message = NULL; + if (message != NULL) { + pdu.error_message = malloc(strlen(message) + 1); + if (pdu.error_message == NULL) + warn("Error message couldn't be allocated, removed from PDU"); + else { + pdu.error_message_length = strlen(message) + 1; + strcpy(pdu.error_message, message); + } + } + + /* Calculate lengths */ + pdu.header.length = length_error_report_pdu(&pdu); + + len = serialize_error_report_pdu(&pdu, data); + free(pdu.error_message); + return send_response(fd, data, len); + } diff --cc src/rtr/pdu_sender.h index 00000000,db228c8d..a4ff8f2c mode 000000,100644..100644 --- a/src/rtr/pdu_sender.h +++ b/src/rtr/pdu_sender.h @@@ -1,0 -1,27 +1,27 @@@ + #ifndef SRC_RTR_PDU_SENDER_H_ + #define SRC_RTR_PDU_SENDER_H_ + + #include + #include "pdu.h" + + struct sender_common { + int fd; + u_int8_t version; + u_int16_t *session_id; + u_int32_t *start_serial; + u_int32_t *end_serial; + }; + + void init_sender_common(struct sender_common *, int, u_int8_t, u_int16_t *, + u_int32_t *, u_int32_t *); + + int send_serial_notify_pdu(struct sender_common *); + int send_cache_reset_pdu(struct sender_common *); + int send_cache_response_pdu(struct sender_common *); + int send_payload_pdus(struct sender_common *); + int send_end_of_data_pdu(struct sender_common *); + int send_error_report_pdu(int, u_int8_t, u_int16_t, struct pdu_header *, - char *); ++ char const *); + + + #endif /* SRC_RTR_PDU_SENDER_H_ */ diff --cc src/rtr/pdu_serializer.c index 00000000,3e6220d4..4d794694 mode 000000,100644..100644 --- a/src/rtr/pdu_serializer.c +++ b/src/rtr/pdu_serializer.c @@@ -1,0 -1,152 +1,153 @@@ + #include "pdu_serializer.h" + + #include + #include + #include "primitive_writer.h" + + void + init_buffer(struct data_buffer *buffer) + { + buffer->capacity = BUFFER_SIZE; + buffer->data = malloc(BUFFER_SIZE); + } + + void + free_buffer(struct data_buffer *buffer) + { + free(buffer->data); + } + + static size_t + serialize_pdu_header(struct pdu_header *header, u_int16_t union_value, + char *buf) + { + char *ptr; + + ptr = buf; + ptr = write_int8(ptr, header->protocol_version); + ptr = write_int8(ptr, header->pdu_type); + ptr = write_int16(ptr, union_value); + ptr = write_int32(ptr, header->length); + + return ptr - buf; + } + + size_t + serialize_serial_notify_pdu(struct serial_notify_pdu *pdu, char *buf) + { + size_t head_size; + char *ptr; + - head_size = serialize_pdu_header(&pdu->header, pdu->header.session_id, ++ head_size = serialize_pdu_header(&pdu->header, pdu->header.m.session_id, + buf); + + ptr = buf + head_size; + ptr = write_int32(ptr, pdu->serial_number); + + return ptr - buf; + } + + size_t + serialize_cache_response_pdu(struct cache_response_pdu *pdu, char *buf) + { + /* No payload to serialize */ - return serialize_pdu_header(&pdu->header, pdu->header.session_id, buf); ++ return serialize_pdu_header(&pdu->header, pdu->header.m.session_id, ++ buf); + } + + size_t + serialize_ipv4_prefix_pdu(struct ipv4_prefix_pdu *pdu, char *buf) + { + size_t head_size; + char *ptr; + - head_size = serialize_pdu_header(&pdu->header, pdu->header.reserved, ++ head_size = serialize_pdu_header(&pdu->header, pdu->header.m.reserved, + buf); + + ptr = buf + head_size; + ptr = write_int8(ptr, pdu->flags); + ptr = write_int8(ptr, pdu->prefix_length); + ptr = write_int8(ptr, pdu->max_length); + ptr = write_int8(ptr, pdu->zero); + ptr = write_in_addr(ptr, pdu->ipv4_prefix); + ptr = write_int32(ptr, pdu->asn); + + return ptr - buf; + } + + size_t + serialize_ipv6_prefix_pdu(struct ipv6_prefix_pdu *pdu, char *buf) + { + size_t head_size; + char *ptr; + - head_size = serialize_pdu_header(&pdu->header, pdu->header.reserved, ++ head_size = serialize_pdu_header(&pdu->header, pdu->header.m.reserved, + buf); + + ptr = buf + head_size; + ptr = write_int8(ptr, pdu->flags); + ptr = write_int8(ptr, pdu->prefix_length); + ptr = write_int8(ptr, pdu->max_length); + ptr = write_int8(ptr, pdu->zero); + ptr = write_in6_addr(ptr, pdu->ipv6_prefix); + ptr = write_int32(ptr, pdu->asn); + + return ptr - buf; + } + + size_t + serialize_end_of_data_pdu(struct end_of_data_pdu *pdu, char *buf) + { + size_t head_size; + char *ptr; + - head_size = serialize_pdu_header(&pdu->header, pdu->header.session_id, ++ head_size = serialize_pdu_header(&pdu->header, pdu->header.m.session_id, + buf); + + ptr = buf + head_size; + ptr = write_int32(ptr, pdu->serial_number); + if (pdu->header.protocol_version == RTR_V1) { + ptr = write_int32(ptr, pdu->refresh_interval); + ptr = write_int32(ptr, pdu->retry_interval); + ptr = write_int32(ptr, pdu->expire_interval); + } + + return ptr - buf; + } + + size_t + serialize_cache_reset_pdu(struct cache_reset_pdu *pdu, char *buf) + { + /* No payload to serialize */ - return serialize_pdu_header(&pdu->header, pdu->header.reserved, buf); ++ return serialize_pdu_header(&pdu->header, pdu->header.m.reserved, buf); + } + + size_t + serialize_error_report_pdu(struct error_report_pdu *pdu, char *buf) + { + struct pdu_header *err_pdu_header; + size_t head_size; + char *ptr, *tmp_ptr; + int i; + - head_size = serialize_pdu_header(&pdu->header, pdu->header.error_code, ++ head_size = serialize_pdu_header(&pdu->header, pdu->header.m.error_code, + buf); + + ptr = buf + head_size; + + ptr = write_int32(ptr, pdu->error_pdu_length); + if (pdu->error_pdu_length > 0) { + /* Set only the header of err PDU */ + err_pdu_header = (struct pdu_header *)pdu->erroneous_pdu; + head_size = serialize_pdu_header(err_pdu_header, - err_pdu_header->reserved, ptr); ++ err_pdu_header->m.reserved, ptr); + ptr = ptr + head_size; + } + + ptr = write_int32(ptr, pdu->error_message_length); + tmp_ptr = pdu->error_message; + for (i = 0; i < pdu->error_message_length; i++) + ptr = write_int8(ptr, tmp_ptr[i]); + + return ptr - buf; + } diff --cc src/rtr/primitive_reader.c index 00000000,dd264d48..fac4f0ec mode 000000,100644..100644 --- a/src/rtr/primitive_reader.c +++ b/src/rtr/primitive_reader.c @@@ -1,0 -1,238 +1,250 @@@ + #include "primitive_reader.h" + + #include + #include + #include ++#include + #include + #include + ++#include "log.h" ++ + static int read_exact(int, unsigned char *, size_t); + static int read_and_waste(int, unsigned char *, size_t, u_int32_t); + static int get_octets(unsigned char); + static void place_null_character(rtr_char *, size_t); + + static int + read_exact(int fd, unsigned char *buffer, size_t buffer_len) + { + ssize_t read_result; + size_t offset; + + for (offset = 0; offset < buffer_len; offset += read_result) { + read_result = read(fd, &buffer[offset], buffer_len - offset); + if (read_result == -1) { + warn("Client socket read interrupted"); + return -errno; + } + if (read_result == 0) { + warnx("Stream ended mid-PDU."); + return -EPIPE; + } + } + + return 0; + } + + int + read_int8(int fd, u_int8_t *result) + { + return read_exact(fd, result, sizeof(u_int8_t)); + } + + /** Big Endian. */ + int + read_int16(int fd, u_int16_t *result) + { + unsigned char buffer[2]; + int err; + + err = read_exact(fd, buffer, sizeof(buffer)); + if (err) + return err; + + *result = (((u_int16_t)buffer[0]) << 8) | ((u_int16_t)buffer[1]); + return 0; + } + + /** Big Endian. */ + int + read_int32(int fd, u_int32_t *result) + { + unsigned char buffer[4]; + int err; + + err = read_exact(fd, buffer, sizeof(buffer)); + if (err) + return err; + + *result = (((u_int32_t)buffer[0]) << 24) + | (((u_int32_t)buffer[1]) << 16) + | (((u_int32_t)buffer[2]) << 8) + | (((u_int32_t)buffer[3]) ); + return 0; + } + + int + read_in_addr(int fd, struct in_addr *result) + { + return read_int32(fd, &result->s_addr); + } + + int + read_in6_addr(int fd, struct in6_addr *result) + { + return read_int32(fd, &result->s6_addr32[0]) + || read_int32(fd, &result->s6_addr32[1]) + || read_int32(fd, &result->s6_addr32[2]) + || read_int32(fd, &result->s6_addr32[3]); + } + + /* + * Consumes precisely @total_len bytes from @fd. + * The first @str_len bytes are stored in @str. + * + * It is required that @str_len <= @total_len. + */ + static int + read_and_waste(int fd, unsigned char *str, size_t str_len, u_int32_t total_len) + { + #define TLEN 1024 /* "Trash length" */ - unsigned char trash[TLEN]; ++ unsigned char *trash; + size_t offset; + int err; + + err = read_exact(fd, str, str_len); + if (err) + return err; + - for (offset = str_len; (offset + TLEN) < total_len; offset += TLEN) { - err = read_exact(fd, trash, TLEN); ++ if (str_len == total_len) ++ return 0; ++ ++ trash = malloc(TLEN); ++ if (trash == NULL) ++ return pr_enomem(); ++ ++ for (offset = str_len; offset < total_len; offset += TLEN) { ++ err = read_exact(fd, trash, ++ (total_len - offset >= TLEN) ? TLEN : (total_len - offset)); + if (err) - return err; ++ break; + } + - return read_exact(fd, trash, total_len - offset); ++ free(trash); ++ return err; + #undef TLEN + } + + #define EINVALID_UTF8 -0xFFFF + + /* + * Returns the length (in octets) of the UTF-8 code point that starts with + * octet @first_octet. + */ + static int + get_octets(unsigned char first_octet) + { + if ((first_octet & 0x80) == 0) + return 1; + if ((first_octet >> 5) == 6) /* 0b110 */ + return 2; + if ((first_octet >> 4) == 14) /* 0b1110 */ + return 3; + if ((first_octet >> 3) == 30) /* 0b11110 */ + return 4; + return EINVALID_UTF8; + } + + /* This is just a cast. The barebones version is too cluttered. */ + #define UCHAR(c) ((unsigned char *)c) + + /* + * This also sanitizes the string, BTW. + * (Because it overrides the first invalid character with the null chara. + * The rest is silently ignored.) + */ + static void + place_null_character(rtr_char *str, size_t len) + { + rtr_char *null_chara_pos; + rtr_char *cursor; + int octet; + int octets; + + /* + * This could be optimized by noticing that all byte continuations in + * UTF-8 start with 0b10. This means that we could start from the end + * of the string and move left until we find a valid character. + * But if we do that, we'd lose the sanitization. So this is better + * methinks. + */ + + null_chara_pos = str; + cursor = str; + + while (cursor < str + len - 1) { + octets = get_octets(*UCHAR(cursor)); + if (octets == EINVALID_UTF8) + break; + cursor++; + + for (octet = 1; octet < octets; octet++) { + /* Memory ends in the middle of this code point? */ + if (cursor >= str + len - 1) + goto end; + /* All continuation octets must begin with 0b10. */ + if ((*(UCHAR(cursor)) >> 6) != 2 /* 0b10 */) + goto end; + cursor++; + } + + null_chara_pos = cursor; + } + + end: + *null_chara_pos = '\0'; + } + + /* + * Reads an RTR string from the file descriptor @fd. Returns the string as a + * normal UTF-8 C string (NULL-terminated). + * + * Will consume the entire string from the stream, but @result can be + * truncated. This is because RTR strings are technically allowed to be 4 GBs + * long. + * + * The result is allocated in the heap. It will length 4096 characters at most. + * (Including the NULL chara.) + */ + int + read_string(int fd, rtr_char **result) + { + /* Actual string length claimed by the PDU, in octets. */ + u_int32_t full_length32; /* Excludes the null chara */ + u_int64_t full_length64; /* Includes the null chara */ + /* + * Actual length that we allocate. Octets. + * This exists because there might be value in truncating the string; + * full_length is a fucking 32-bit integer for some reason. + * Note that, because this is UTF-8 we're dealing with, this might not + * necessarily end up being the actual octet length of the final + * string; since our truncation can land in the middle of a code point, + * the null character might need to be shifted left slightly. + */ + size_t alloc_length; /* Includes the null chara */ + rtr_char *str; + int err; + + err = read_int32(fd, &full_length32); + if (err) + return err; + + full_length64 = ((u_int64_t) full_length32) + 1; + + alloc_length = (full_length64 > 4096) ? 4096 : full_length64; + str = malloc(alloc_length); + if (!str) + return -ENOMEM; + + err = read_and_waste(fd, UCHAR(str), alloc_length - 1, full_length32); + if (err) { + free(str); + return err; + } + + place_null_character(str, alloc_length); + + *result = str; + return 0; + } diff --cc src/rtr/rtr.c index 00000000,863e4272..ed49b86c mode 000000,100644..100644 --- a/src/rtr/rtr.c +++ b/src/rtr/rtr.c @@@ -1,0 -1,320 +1,359 @@@ + #include "rtr.h" + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + -#include "../updates_daemon.h" ++#include "config.h" + #include "clients.h" -#include "configuration.h" -#include "err_pdu.h" -#include "pdu.h" ++#include "updates_daemon.h" ++#include "rtr/err_pdu.h" ++#include "rtr/pdu.h" + + /* TODO Support both RTR v0 an v1 */ + #define RTR_VERSION_SUPPORTED RTR_V0 + + volatile bool loop; + + struct thread_node { + pthread_t tid; + SLIST_ENTRY(thread_node) next; + }; + + SLIST_HEAD(thread_list, thread_node) threads; + ++static int ++init_addrinfo(struct addrinfo **result) ++{ ++ char const *hostname; ++ char const *service; ++ struct addrinfo hints; ++ int error; ++ ++ memset(&hints, 0 , sizeof(hints)); ++ hints.ai_family = AF_UNSPEC; ++ /* hints.ai_socktype = SOCK_DGRAM; */ ++ hints.ai_flags |= AI_PASSIVE; ++ ++ hostname = config_get_server_address(); ++ service = config_get_server_port(); ++ ++ error = getaddrinfo(hostname, service, &hints, result); ++ if (error) { ++ warnx("Could not infer a bindable address out of address '%s' and port '%s': %s", ++ (hostname != NULL) ? hostname : "any", service, ++ gai_strerror(error)); ++ return error; ++ } ++ ++ return 0; ++} ++ ++ + /* + * Creates the socket that will stay put and wait for new connections started + * from the clients. + */ + static int -create_server_socket(void) ++create_server_socket(int *result) + { - struct addrinfo const *addr; ++ struct addrinfo *addrs; ++ struct addrinfo *addr; + int fd; /* "file descriptor" */ ++ int error; + - addr = config_get_server_addrinfo(); - for (; addr != NULL; addr = addr->ai_next) { ++ error = init_addrinfo(&addrs); ++ if (error) ++ return error; ++ ++ for (addr = addrs; addr != NULL; addr = addr->ai_next) { + printf( + "Attempting to bind socket to address '%s', port '%s'.\n", + (addr->ai_canonname != NULL) ? addr->ai_canonname : "any", + config_get_server_port()); + + fd = socket(addr->ai_family, SOCK_STREAM, 0); + if (fd < 0) { + warn("socket() failed"); + continue; + } + + if (bind(fd, addr->ai_addr, addr->ai_addrlen) < 0) { + warn("bind() failed"); + continue; + } + + printf("Success.\n"); - return fd; /* Happy path */ ++ freeaddrinfo(addrs); ++ *result = fd; ++ return 0; /* Happy path */ + } + + warnx("None of the addrinfo candidates could be bound."); ++ freeaddrinfo(addrs); + return -EINVAL; + } + + /* + * Arguments that the server socket thread will send to the client socket + * threads whenever it creates them. + */ + struct thread_param { + int client_fd; + struct sockaddr_storage client_addr; + }; + + enum verdict { + /* No errors; continue happily. */ + VERDICT_SUCCESS, + /* A temporal error just happened. Try again. */ + VERDICT_RETRY, + /* "Stop whatever you're doing and return." */ + VERDICT_EXIT, + }; + + /* + * Converts an error code to a verdict. + * The error code is assumed to have been spewed by the `accept()` function. + */ + static enum verdict + handle_accept_result(int client_fd, int err) + { + if (client_fd >= 0) + return VERDICT_SUCCESS; + + /* + * Note: I can't just use a single nice switch because EAGAIN and + * EWOULDBLOCK are the same value in at least one supported system + * (Linux). + */ + + /* + * TODO this `if` is a Linux quirk and should probably not exist in the + * BSDs. See `man 2 accept`. + */ + if (err == ENETDOWN || err == EPROTO || err == ENOPROTOOPT + || err == EHOSTDOWN || err == ENONET || err == EHOSTUNREACH + || err == EOPNOTSUPP || err == ENETUNREACH) + return VERDICT_RETRY; + ++#pragma GCC diagnostic push ++#pragma GCC diagnostic ignored "-Wlogical-op" + if (err == EAGAIN || err == EWOULDBLOCK) + return VERDICT_RETRY; ++#pragma GCC diagnostic pop + + errno = err; + warn("Connection acceptor thread interrupted"); + return VERDICT_EXIT; + } + + static void * + end_client(int client_fd, const struct pdu_metadata *meta, void *pdu) + { + if (meta != NULL && pdu != NULL) + meta->destructor(pdu); + clients_forget(client_fd); + return NULL; + } + + /* + * The client socket threads' entry routine. + * + * Please remember that this function needs to always release @param_void + * before returning. + */ + static void * + client_thread_cb(void *param_void) + { + struct thread_param param; + struct pdu_metadata const *meta; + void *pdu; + int err; + u_int8_t rtr_version; + + memcpy(¶m, param_void, sizeof(param)); + + while (loop) { /* For each PDU... */ + err = pdu_load(param.client_fd, &pdu, &meta, &rtr_version); + if (err) + return end_client(param.client_fd, NULL, NULL); + + /* Protocol Version Negotiation */ + if (rtr_version != RTR_VERSION_SUPPORTED) { + err_pdu_send(param.client_fd, RTR_VERSION_SUPPORTED, + ERR_PDU_UNSUP_PROTO_VERSION, + (struct pdu_header *) pdu, NULL); + return end_client(param.client_fd, meta, pdu); + } + /* RTR Version ready, now update client */ + err = update_client(param.client_fd, ¶m.client_addr, + rtr_version); + if (err) { + if (err == -EINVAL) { + err_pdu_send(param.client_fd, rtr_version, + (rtr_version == RTR_V0 + ? ERR_PDU_UNSUP_PROTO_VERSION + : ERR_PDU_UNEXPECTED_PROTO_VERSION), + (struct pdu_header *) pdu, NULL); + } + return end_client(param.client_fd, meta, pdu); + } + + err = meta->handle(param.client_fd, pdu); + meta->destructor(pdu); + if (err) + return end_client(param.client_fd, NULL, NULL); + } + + return NULL; /* Unreachable. */ + } + + /* + * Waits for client connections and spawns threads to handle them. + */ + static int + handle_client_connections(int server_fd) + { + struct sockaddr_storage client_addr; + struct thread_param arg; + struct thread_node *new_thread; + socklen_t sizeof_client_addr; + pthread_attr_t attr; + int client_fd; + + listen(server_fd, config_get_server_queue()); + + sizeof_client_addr = sizeof(client_addr); + + do { + client_fd = accept(server_fd, (struct sockaddr *)&client_addr, + &sizeof_client_addr); + switch (handle_accept_result(client_fd, errno)) { + case VERDICT_SUCCESS: + break; + case VERDICT_RETRY: + continue; + case VERDICT_EXIT: + return 0; + } + + /* + * Note: My gut says that errors from now on (even the unknown + * ones) should be treated as temporary; maybe the next + * accept() will work. + * So don't interrupt the thread when this happens. + */ + + new_thread = malloc(sizeof(struct thread_node)); + if (new_thread == NULL) { + warnx("Couldn't create thread struct"); + close(client_fd); + continue; + } + + arg.client_fd = client_fd; + arg.client_addr = client_addr; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + errno = pthread_create(&new_thread->tid, &attr, + client_thread_cb, &arg); + pthread_attr_destroy(&attr); + if (errno) { + warn("Could not spawn the client's thread"); + free(new_thread); + close(client_fd); + continue; + } + + SLIST_INSERT_HEAD(&threads, new_thread, next); + + } while (true); + + return 0; /* Unreachable. */ + } + + static void + signal_handler(int signal, siginfo_t *info, void *param) + { + /* Empty handler */ + } + + static int + init_signal_handler(void) + { + struct sigaction act; + int error; + + memset(&act, 0, sizeof act); + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = signal_handler; + + error = sigaction(SIGINT, &act, NULL); + if (error) { + warn("Error initializing signal handler"); + error = -errno; + } + return error; + } + + /* + * Starts the server, using the current thread to listen for RTR client + * requests. + * + * This function blocks. + */ + int + rtr_listen(void) + { + int server_fd; /* "file descriptor" */ + int error; + - server_fd = create_server_socket(); - if (server_fd < 0) - return server_fd; ++ error = create_server_socket(&server_fd); ++ if (error) ++ return error; + + /* Server ready, start updates thread */ + error = updates_daemon_start(); + if (error) + return error; + + /* Init global vars */ + loop = true; + SLIST_INIT(&threads); + + error = init_signal_handler(); + if (error) + return error; + + return handle_client_connections(server_fd); + } + + void + rtr_cleanup(void) + { + struct thread_node *ptr; + + updates_daemon_destroy(); + + /* Wait for threads to end gracefully */ + loop = false; + while (!SLIST_EMPTY(&threads)) { + ptr = SLIST_FIRST(&threads); + SLIST_REMOVE_HEAD(&threads, next); + pthread_kill(ptr->tid, SIGINT); + pthread_join(ptr->tid, NULL); + free(ptr); + } + } diff --cc src/str.h index ec1db1c6,00000000..b8d00a38 mode 100644,000000..100644 --- a/src/str.h +++ b/src/str.h @@@ -1,34 -1,0 +1,37 @@@ +#ifndef SRC_STR_H_ +#define SRC_STR_H_ + +#include +#include +#include +#include + +int ia5s2string(ASN1_IA5STRING *, char **); +int BN2string(BIGNUM *, char **); + +/* This file is named "str.h" because "string.h" collides with . */ + +/** + * Do not modify fields directly; this should be private. ++ * ++ * This is more or less like strtok(), except it doesn't modify the string at ++ * any point. + */ +struct string_tokenizer { + /** String we're tokenizing. */ + char const *str; + size_t str_len; + /** Token delimiter. */ + unsigned char separator; + /** Offset of the first character of the current token. */ + size_t start; + /** Offset of the last character of the current token + 1. */ + size_t end; +}; + +void string_tokenizer_init(struct string_tokenizer *, char const *, size_t, + unsigned char); +bool string_tokenizer_next(struct string_tokenizer *); +bool token_equals(struct string_tokenizer *, struct string_tokenizer *); + +#endif /* SRC_STR_H_ */ diff --cc src/thread_var.c index 1d3b9839,00000000..d907d85d mode 100644,000000..100644 --- a/src/thread_var.c +++ b/src/thread_var.c @@@ -1,258 -1,0 +1,260 @@@ +#include "thread_var.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +static pthread_key_t state_key; +static pthread_key_t filenames_key; + +struct filename_stack { + /* This can be NULL. Abort all operations if this is the case. */ + char const **filenames; + unsigned int len; + unsigned int size; +}; + +static void +fnstack_discard(void *arg) +{ + struct filename_stack *files = arg; + free(files->filenames); + free(files); +} + +/** Initializes this entire module. Call once per runtime lifetime. */ - void ++int +thvar_init(void) +{ + int error; + + error = pthread_key_create(&state_key, NULL); + if (error) { + fprintf(stderr, + "Fatal: Errcode %d while initializing the validation state thread variable.\n", + error); - exit(error); ++ return error; + } + + /* + * Hm. It's a little odd. + * fnstack_discard() is not being called on program termination. + * Not sure if this is an implementation quirk. + * We'll just have to delete it manually. + */ + error = pthread_key_create(&filenames_key, fnstack_discard); + if (error) { + fprintf(stderr, + "Fatal: Errcode %d while initializing the file name stack thread variable.\n", + error); - exit(error); ++ return error; + } ++ ++ return 0; +} + +/* Puts @state in the current thread's variable pool. Call once per thread. */ +int +state_store(struct validation *state) +{ + int error; + + error = pthread_setspecific(state_key, state); + if (error) + fprintf(stderr, "pthread_setspecific() returned %d.", error); + + return error; +} + +/* Returns the current thread's validation state. */ +struct validation * +state_retrieve(void) +{ + struct validation *state; + + state = pthread_getspecific(state_key); + if (state == NULL) + fprintf(stderr, "Programming error: This thread lacks a validation state.\n"); + + return state; +} + +/** Initializes the current thread's fnstack. Call once per thread. */ +void +fnstack_init(void) +{ + struct filename_stack *files; + int error; + + files = malloc(sizeof(struct filename_stack)); + if (files == NULL) + return; + + files->filenames = malloc(32 * sizeof(char *)); + if (files->filenames == NULL) { + free(files); + return; + } + + files->len = 0; + files->size = 32; + + error = pthread_setspecific(filenames_key, files); + if (error) + fprintf(stderr, "pthread_setspecific() returned %d.", error); +} + +void +fnstack_cleanup(void) +{ + struct filename_stack *files; + int error; + + files = pthread_getspecific(filenames_key); + if (files == NULL) + return; + + fnstack_discard(files); + + error = pthread_setspecific(filenames_key, NULL); + if (error) + fprintf(stderr, "pthread_setspecific() returned %d.", error); +} + +static struct filename_stack * +get_file_stack(void) +{ + struct filename_stack *files; + + files = pthread_getspecific(filenames_key); + if (files == NULL) + fprintf(stderr, "This thread lacks a files stack.\n"); + + return files; +} + +/** + * Call this function every time you're about to start processing a new file. + * Any pr_err()s and friends will now include the new file name. + * Use fnstack_pop() to revert back to the previously stacked file name. + * @file is not cloned; it's expected to outlive the push/pop operation. + */ +void +fnstack_push(char const *file) +{ + struct filename_stack *files; + char const **tmp; + + files = get_file_stack(); + if (files == NULL || files->filenames == NULL) + return; + + if (files->len >= files->size) { + tmp = realloc(files->filenames, 2 * files->size * sizeof(char *)); + if (tmp == NULL) { + /* Oh noes */ + free(files->filenames); + files->filenames = NULL; + return; + } + + files->filenames = tmp; + files->size *= 2; + } + + files->filenames[files->len++] = file; +} + +/** See fnstack_push(). */ +void +fnstack_push_uri(struct rpki_uri const *uri) +{ + fnstack_push(uri_get_printable(uri)); +} + +/* Returns the file name on the top of the file name stack. */ +char const * +fnstack_peek(void) +{ + struct filename_stack *files; + + files = get_file_stack(); + if (files == NULL || files->filenames == NULL || files->len == 0) + return NULL; + + return files->filenames[files->len - 1]; +} + +/* Reverts the last fnstack_push(). */ +void +fnstack_pop(void) +{ + struct filename_stack *files; + + files = get_file_stack(); + if (files == NULL || files->filenames == NULL || files->len == 0) + return; + + files->len--; +} + +static char const * +addr2str(int af, void *addr, char *(*buffer_cb)(struct validation *)) +{ + struct validation *state; + + state = state_retrieve(); + if (!state) + return NULL; + + return inet_ntop(af, addr, buffer_cb(state), INET6_ADDRSTRLEN); +} + +/** + * Returns @addr, converted to a printable string. Intended for minimal clutter + * address printing. + * + * The buffer the string is stored in was allocated in a thread variable, so it + * will be overridden the next time you call this function. Also, you should not + * free it. + * + * The buffer is the same as v6addr2str()'s, so don't mix them either. + */ +char const * +v4addr2str(struct in_addr *addr) +{ + return addr2str(AF_INET, addr, validation_get_ip_buffer1); +} + +/** + * Same as v4addr2str(), except a different buffer is used. + */ +char const * +v4addr2str2(struct in_addr *addr) +{ + return addr2str(AF_INET, addr, validation_get_ip_buffer2); +} + +/** + * See v4addr2str(). + */ +char const * +v6addr2str(struct in6_addr *addr) +{ + return addr2str(AF_INET6, addr, validation_get_ip_buffer1); +} + +/** + * See v4addr2str2(). + */ +char const * +v6addr2str2(struct in6_addr *addr) +{ + return addr2str(AF_INET6, addr, validation_get_ip_buffer2); +} diff --cc src/thread_var.h index 3d629533,00000000..7dbe18e0 mode 100644,000000..100644 --- a/src/thread_var.h +++ b/src/thread_var.h @@@ -1,24 -1,0 +1,24 @@@ +#ifndef SRC_THREAD_VAR_H_ +#define SRC_THREAD_VAR_H_ + +#include "state.h" + - void thvar_init(void); ++int thvar_init(void); /* This function does not need cleanup. */ + +int state_store(struct validation *); +struct validation *state_retrieve(void); + +void fnstack_init(void); +void fnstack_cleanup(void); + +void fnstack_push(char const *); +void fnstack_push_uri(struct rpki_uri const *); +char const *fnstack_peek(void); +void fnstack_pop(void); + +char const *v4addr2str(struct in_addr *addr); +char const *v4addr2str2(struct in_addr *addr); +char const *v6addr2str(struct in6_addr *addr); +char const *v6addr2str2(struct in6_addr *addr); + +#endif /* SRC_THREAD_VAR_H_ */ diff --cc src/updates_daemon.c index 00000000,3206bbaf..cd7d3c89 mode 000000,100644..100644 --- a/src/updates_daemon.c +++ b/src/updates_daemon.c @@@ -1,0 -1,58 +1,61 @@@ + #include "updates_daemon.h" + + #include + #include + #include + #include + #include + -#include "csv.h" -#include "configuration.h" ++#include "config.h" + #include "notify.h" ++#include "object/tal.h" + -pthread_t thread; ++static pthread_t thread; + + static void * -check_vrps_updates(void *param_void) { ++check_vrps_updates(void *param_void) ++{ + int error; + bool updated; ++ + do { + updated = false; - error = csv_check_vrps_file(&updated); ++ error = perform_standalone_validation(&updated); + if (error) { - warnx("Error while searching CSV updates, sleeping.."); ++ warnx("Error '%d' while searching CSV updates, sleeping...", ++ error); + goto sleep; + } + if (updated) + notify_clients(); + sleep: + sleep(config_get_vrps_check_interval()); + } while (true); + + return NULL; + } + + int + updates_daemon_start(void) + { + pthread_attr_t attr; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + errno = pthread_create(&thread, NULL, check_vrps_updates, NULL); + pthread_attr_destroy(&attr); + if (errno) { + warn("Could not spawn the update daemon thread"); + return -errno; + } + + return 0; + } + + void + updates_daemon_destroy(void) + { + void *ptr = NULL; + pthread_cancel(thread); + pthread_join(thread, &ptr); + } diff --cc src/vrps.c index 00000000,ff3ae7cc..2222431d mode 000000,100644..100644 --- a/src/vrps.c +++ b/src/vrps.c @@@ -1,0 -1,466 +1,461 @@@ + #include "vrps.h" + + #include + #include + #include "array_list.h" + #include "common.h" + + /* + * Storage of VRPs (term taken from RFC 6811 "Validated ROA Payload") and + * Serials that contain such VRPs + */ + + #define FLAG_WITHDRAWAL 0 + #define FLAG_ANNOUNCEMENT 1 + #define START_SERIAL 0 + + ARRAY_LIST(vrps, struct vrp) + + struct delta { + u_int32_t serial; + struct vrps vrps; + }; + + ARRAY_LIST(deltasdb, struct delta) + + struct state { + /** The current valid ROAs, freshly loaded from the file */ + struct delta base_db; + /** ROA changes over time */ + struct deltasdb deltas_db; + u_int32_t current_serial; + u_int16_t v0_session_id; + u_int16_t v1_session_id; + time_t last_modified_date; + } state; + + /* Read and Write locks */ + sem_t rlock, wlock; + + /* Readers counter */ + unsigned int rcounter; + + static int + delta_init(struct delta *delta) + { + return vrps_init(&delta->vrps); + } + + static void + vrp_destroy(struct vrp *vrp) + { + /* Didn't allocate something, so do nothing */ + } + + static void + delta_destroy(struct delta *delta) + { + vrps_cleanup(&delta->vrps, vrp_destroy); + } + + int + deltas_db_init(void) + { + int error, shift; + + error = delta_init(&state.base_db); - if (error){ - warnx("Delta base DB couldn't be initialized"); ++ if (error) + return error; - } + + error = deltasdb_init(&state.deltas_db); + if (error) { - warnx("Deltas DB couldn't be initialized"); + delta_destroy(&state.base_db); + return error; + } + + /* + * Use the same start serial, the session ID will avoid + * "desynchronization" (more at RFC 6810 'Glossary' and + * 'Fields of a PDU') + */ + state.current_serial = START_SERIAL; + + /* Get the bits that'll fit in session_id */ + shift = sizeof(time_t) - sizeof(state.v0_session_id); + state.v0_session_id = (u_int16_t)((time(NULL) << shift) >> shift); + /* Minus 1 to prevent same ID */ + state.v1_session_id = state.v0_session_id - 1; + + sem_init(&rlock, 0, 1); + sem_init(&wlock, 0, 1); + rcounter = 0; + + return 0; + } + + static void + init_vrp (struct vrp *vrp, u_int32_t asn, u_int8_t prefix_length, + u_int8_t max_prefix_length) + { + vrp->asn = asn; + vrp->prefix_length = prefix_length; + vrp->max_prefix_length = max_prefix_length; + /* Set as ANNOUNCEMENT by default */ + vrp->flags = FLAG_ANNOUNCEMENT; + } + + struct vrp + create_vrp4(u_int32_t asn, struct in_addr ipv4_prefix, u_int8_t prefix_length, + u_int8_t max_prefix_length) + { + struct vrp result; + + init_vrp(&result, asn, prefix_length, max_prefix_length); - result.ipv4_prefix = ipv4_prefix; ++ result.prefix.ipv4 = ipv4_prefix; + result.addr_fam = AF_INET; + + return result; + } + + struct vrp + create_vrp6(u_int32_t asn, struct in6_addr ipv6_prefix, u_int8_t prefix_length, + u_int8_t max_prefix_length) + { + struct vrp result; + + init_vrp(&result, asn, prefix_length, max_prefix_length); - result.ipv6_prefix = ipv6_prefix; ++ result.prefix.ipv6 = ipv6_prefix; + result.addr_fam = AF_INET6; + + return result; + } + + static bool + vrp_equal(struct vrp *left, struct vrp *right) + { + return left->asn == right->asn + && left->addr_fam == right->addr_fam + && left->prefix_length == right->prefix_length + && left->max_prefix_length == right->max_prefix_length + && ((left->addr_fam == AF_INET - && left->ipv4_prefix.s_addr == right->ipv4_prefix.s_addr) ++ && left->prefix.ipv4.s_addr == right->prefix.ipv4.s_addr) + || (left->addr_fam == AF_INET6 - && IN6_ARE_ADDR_EQUAL(left->ipv6_prefix.s6_addr32, - right->ipv6_prefix.s6_addr32))); ++ && IN6_ARE_ADDR_EQUAL(left->prefix.ipv6.s6_addr32, ++ right->prefix.ipv6.s6_addr32))); + } + + static struct vrp * + vrp_locate(struct vrps *base, struct vrp *vrp) + { + struct vrp *cursor; + + ARRAYLIST_FOREACH(base, cursor) + if (vrp_equal(cursor, vrp)) + return cursor; + + return NULL; + } + + static bool + vrp_is_new(struct vrps *base, struct vrp *vrp) + { + return vrp_locate(base, vrp) == NULL; + } + + static int + delta_add_vrp(struct delta *delta, struct vrp *vrp) + { + return vrps_add(&delta->vrps, vrp); + } + + static int + delta_summary(struct delta *base_delta, struct delta *result) + { + struct vrps *base, *search_list; + struct vrp *cursor; + int error; + + /* + * Note: Don't fix this function yet. + * I realize why you implemented it this way, and I'm trying to come up + * with a more efficient algorithm. + */ + + error = delta_init(result); + if (error) + return error; + + result->serial = base_delta->serial; + read_lock(&rlock, &wlock, &rcounter); + /* First check for announcements */ + base = &base_delta->vrps; + search_list = &state.base_db.vrps; + ARRAYLIST_FOREACH(base, cursor) + if (vrp_is_new(search_list, cursor)) { + cursor->flags = FLAG_ANNOUNCEMENT; + error = delta_add_vrp(result, cursor); + if (error) { - warnx("Couldn't add announcement to summary"); + read_unlock(&rlock, &wlock, &rcounter); + return error; + } + } + + /* Now for withdrawals */ + base = &state.base_db.vrps; + search_list = &base_delta->vrps; + ARRAYLIST_FOREACH(base, cursor) + if (vrp_is_new(search_list, cursor)) { + cursor->flags = FLAG_WITHDRAWAL; + error = delta_add_vrp(result, cursor); + if (error) { - warnx("Couldn't add withdrawal to summary"); + read_unlock(&rlock, &wlock, &rcounter); + return error; + } + } + + read_unlock(&rlock, &wlock, &rcounter); + return 0; + } + + static int + deltas_db_add_delta(struct delta delta) + { + struct delta summary; + int result; + + result = 0; + read_lock(&rlock, &wlock, &rcounter); + delta.serial = state.current_serial; + read_unlock(&rlock, &wlock, &rcounter); + /* Store only updates */ + if (delta.serial != START_SERIAL) { + result = delta_summary(&delta, &summary); + if (result != 0) { - warnx("Error summarizing new delta"); ++ pr_err("Error summarizing new delta"); + return result; + } + sem_wait(&wlock); + result = deltasdb_add(&state.deltas_db, &summary); + sem_post(&wlock); + } + /* Don't set the base in case of error */ + if (result != 0) { - warnx("Error persisting new delta"); ++ pr_err("Error persisting new delta"); + return result; + } + + sem_wait(&wlock); + free(state.base_db.vrps.array); + state.base_db = delta; + state.current_serial++; + sem_post(&wlock); + return result; + } + + static void + copy_vrps(struct vrp **dst, struct vrp *src, unsigned int len) + { + struct vrp *tmp; + tmp = realloc(*dst, len * sizeof(struct vrp)); + if (tmp == NULL) { - warn("Couldn't copy VRPs"); ++ pr_enomem(); + return; + } + *dst = tmp; + memcpy(*dst, src, len * sizeof(struct vrp)); + } + + int + deltas_db_create_delta(struct vrp *array, unsigned int len) + { + struct delta new_delta; + int error; + + error = delta_init(&new_delta); + if (error) { - warnx("New Delta couldn't be initialized"); ++ pr_err("New Delta couldn't be initialized"); + return error; + } + + copy_vrps(&new_delta.vrps.array, array, len); + new_delta.vrps.len = len; + new_delta.vrps.capacity = len * sizeof(struct vrp); + + error = deltas_db_add_delta(new_delta); + if (error) + return error; + + return 0; + } + + void + deltas_db_destroy(void) + { + sem_wait(&wlock); + delta_destroy(&state.base_db); + deltasdb_cleanup(&state.deltas_db, delta_destroy); + sem_post(&wlock); + + sem_destroy(&wlock); + sem_destroy(&rlock); + } + + /* + * Get a status to know the difference between the delta with serial SERIAL and + * the last delta at DB. + * + * If SERIAL is received as NULL, and there's data at DB then the status will + * be DIFF_AVAILABLE. + * + * The possible return values are: + * NO_DATA_AVAILABLE -> There's no data at the DB + * DIFF_UNDETERMINED -> The diff can't be determined + * NO_DIFF -> There's no difference + * DIFF_AVAILABLE -> There are diffs between SERIAL and the last DB serial + */ + int + deltas_db_status(u_int32_t *serial) + { + struct delta *delta; + int result; + + read_lock(&rlock, &wlock, &rcounter); + if (state.base_db.vrps.len == 0) { + result = NO_DATA_AVAILABLE; + goto end; + } + + /* No serial to match, and there's data at DB */ + if (serial == NULL) { + result = DIFF_AVAILABLE; + goto end; + } + + /* Is the last version? */ + if (*serial == state.base_db.serial) { + result = NO_DIFF; + goto end; + } + + /* Get the delta corresponding to the serial */ + ARRAYLIST_FOREACH(&state.deltas_db, delta) + if (delta->serial == *serial) { + result = DIFF_AVAILABLE; + goto end; + } + + /* No match yet, release lock */ + read_unlock(&rlock, &wlock, &rcounter); + + /* The first serial isn't at deltas */ + if (*serial == START_SERIAL) + return DIFF_AVAILABLE; + + /* Reached end, diff can't be determined */ + return DIFF_UNDETERMINED; + end: + read_unlock(&rlock, &wlock, &rcounter); + return result; + } + + static void + add_vrps_filtered(struct vrps *dst, struct vrps *src) + { + struct vrp *ptr; + for (ptr = src->array; (ptr - src->array) < src->len; ptr++) + if (vrp_is_new(dst, ptr)) + vrps_add(dst, ptr); + } + + /* + * Get the number of updates from serial START_SERIAL to END_SERIAL, set them + * at RESULT. + * + * Return 0 if no updates are available or couldn't be calculated with the + * received values. + */ + unsigned int + get_vrps_delta(u_int32_t *start_serial, u_int32_t *end_serial, + struct vrp **result) + { + struct delta *delta1; + struct vrps summary; + unsigned int vrps_len; + + read_lock(&rlock, &wlock, &rcounter); + /* No data */ + if (state.base_db.vrps.len == 0) { + read_unlock(&rlock, &wlock, &rcounter); + return 0; + } + + /* NULL start? Send the last version, there's no need to iterate DB */ + if (start_serial == NULL) { + copy_vrps(result, state.base_db.vrps.array, + state.base_db.vrps.len); + vrps_len = state.base_db.vrps.len; + read_unlock(&rlock, &wlock, &rcounter); + return vrps_len; + } + + /* Apparently nothing to return */ + if (*start_serial >= *end_serial) { + read_unlock(&rlock, &wlock, &rcounter); + return 0; + } + + /* Get the delta corresponding to the serials */ + vrps_init(&summary); + ARRAYLIST_FOREACH(&state.deltas_db, delta1) { + if (delta1->serial > *start_serial) + add_vrps_filtered(&summary, &delta1->vrps); + if (delta1->serial == *end_serial) + break; + } + read_unlock(&rlock, &wlock, &rcounter); + + copy_vrps(result, summary.array, summary.len); + vrps_cleanup(&summary, vrp_destroy); + return summary.len; + } + + void + set_vrps_last_modified_date(time_t new_date) + { + sem_wait(&wlock); + state.last_modified_date = new_date; + sem_post(&wlock); + } + + u_int32_t + get_last_serial_number(void) + { + u_int32_t serial; + + read_lock(&rlock, &wlock, &rcounter); + serial = state.current_serial - 1; + read_unlock(&rlock, &wlock, &rcounter); + + return serial; + } + + u_int16_t + get_current_session_id(u_int8_t rtr_version) + { + /* Semaphore isn't needed since this value is set at initialization */ + if (rtr_version == 1) + return state.v1_session_id; + return state.v0_session_id; + } + + time_t + get_vrps_last_modified_date(void) + { + time_t date; + + read_lock(&rlock, &wlock, &rcounter); + date = state.last_modified_date; + read_unlock(&rlock, &wlock, &rcounter); + + return date; + } diff --cc src/vrps.h index 00000000,f6d4c208..6939e08e mode 000000,100644..100644 --- a/src/vrps.h +++ b/src/vrps.h @@@ -1,0 -1,41 +1,41 @@@ + #ifndef SRC_VRPS_H_ + #define SRC_VRPS_H_ + + #include + #include + + #define NO_DATA_AVAILABLE -2 + #define DIFF_UNDETERMINED -1 + #define NO_DIFF 0 + #define DIFF_AVAILABLE 1 + + struct vrp { + u_int32_t asn; + union { - struct in_addr ipv4_prefix; - struct in6_addr ipv6_prefix; - }; ++ struct in_addr ipv4; ++ struct in6_addr ipv6; ++ } prefix; + u_int8_t prefix_length; + u_int8_t max_prefix_length; + u_int8_t addr_fam; + u_int8_t flags; + }; + + int deltas_db_init(void); + + struct vrp create_vrp4(u_int32_t, struct in_addr, u_int8_t, u_int8_t); + struct vrp create_vrp6(u_int32_t, struct in6_addr, u_int8_t, u_int8_t); + + int deltas_db_create_delta(struct vrp *, unsigned int); + int deltas_db_status(u_int32_t *); + + unsigned int get_vrps_delta(u_int32_t *, u_int32_t *, struct vrp **); + + void deltas_db_destroy(void); + void set_vrps_last_modified_date(time_t); + + u_int32_t get_last_serial_number(void); + u_int16_t get_current_session_id(u_int8_t); + time_t get_vrps_last_modified_date(void); + + #endif /* SRC_VRPS_H_ */ diff --cc test/Makefile.am index 75168b9e,fdacf089..b4d45663 --- a/test/Makefile.am +++ b/test/Makefile.am @@@ -1,51 -1,30 +1,73 @@@ +# Reminder: `make check` + +# If you want to only run one test, do `make check TESTS=`. +# Example: `make check TESTS=address` + +# Once compiled, you can also just run the test directly: `./address.test` +# You can easily see standard output and error this way. + + # Reminder: Automake will automatically add this to any targets where + # _CFLAGS is not defined. + # Otherwise it must be included manually: -# mumble_mumble_CFLAGS = $(AM_CFLAGS) flag1 flag2 flag3 ... -AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src @CHECK_CFLAGS@ - ++# mumble_mumble_CFLAGS = ${AM_CFLAGS} flag1 flag2 flag3 ... +AM_CFLAGS = -pedantic -Wall -std=gnu11 -I../src -DUNIT_TESTING ${CHECK_CFLAGS} + # Reminder: As opposed to AM_CFLAGS, "AM_LDADD" is not idiomatic automake, and + # autotools will even reprehend us if we declare it. Therefore, I came up with + # "my" own "ldadd". Unlike AM_CFLAGS, it needs to be manually added to every + # target. -MY_LDADD = $(CHECK_LIBS) +MY_LDADD = ${CHECK_LIBS} +BASIC_MODULES = ../src/log.c ../src/log.h +BASIC_MODULES += impersonator.c -check_PROGRAMS = rtr/primitive_reader.test rtr/pdu.test address.test -TESTS = $(check_PROGRAMS) +check_PROGRAMS = address.test +check_PROGRAMS += vcard.test +check_PROGRAMS += line_file.test +check_PROGRAMS += rsync.test +check_PROGRAMS += tal.test ++check_PROGRAMS += rtr/primitive_reader.test ++check_PROGRAMS += rtr/pdu.test +TESTS = ${check_PROGRAMS} -rtr_primitive_reader_test_SOURCES = \ - rtr/primitive_reader_test.c \ - rtr/stream.c -rtr_primitive_reader_test_LDADD = $(MY_LDADD) +address_test_SOURCES = ${BASIC_MODULES} +address_test_SOURCES += ../src/address.h +address_test_SOURCES += address_test.c +address_test_LDADD = ${MY_LDADD} - vcard_test_SOURCES = ${BASIC_MODULES} - vcard_test_SOURCES += vcard_test.c - vcard_test_LDADD = ${MY_LDADD} - -rtr_pdu_test_SOURCES = \ - rtr/pdu_test.c \ - rtr/stream.c \ - $(top_builddir)/src/rtr/primitive_reader.c \ - $(top_builddir)/src/rtr/pdu_handler.c -rtr_pdu_test_LDADD = $(MY_LDADD) +line_file_test_SOURCES = ${BASIC_MODULES} +line_file_test_SOURCES += ../src/file.c ../src/file.h +line_file_test_SOURCES += ../src/line_file.c ../src/line_file.h +line_file_test_SOURCES += line_file_test.c +line_file_test_LDADD = ${MY_LDADD} + +rsync_test_SOURCES = ${BASIC_MODULES} +rsync_test_SOURCES += ../src/str.c ../src/str.h +rsync_test_SOURCES += ../src/uri.c ../src/uri.h +rsync_test_SOURCES += rsync_test.c +rsync_test_LDADD = ${MY_LDADD} + ++rtr_primitive_reader_test_SOURCES = ${BASIC_MODULES} ++rtr_primitive_reader_test_SOURCES += rtr/primitive_reader_test.c ++rtr_primitive_reader_test_SOURCES += rtr/stream.c ++rtr_primitive_reader_test_LDADD = ${MY_LDADD} ++ ++rtr_pdu_test_SOURCES = ${BASIC_MODULES} ++rtr_pdu_test_SOURCES += rtr/pdu_test.c ++rtr_pdu_test_SOURCES += rtr/stream.c ++rtr_pdu_test_SOURCES += $(top_builddir)/src/rtr/primitive_reader.c ++rtr_pdu_test_SOURCES += $(top_builddir)/src/rtr/pdu_handler.c ++rtr_pdu_test_LDADD = ${MY_LDADD} ++ +tal_test_SOURCES = ${BASIC_MODULES} +tal_test_SOURCES += ../src/file.c ../src/file.h +tal_test_SOURCES += ../src/crypto/base64.c ../src/crypto/base64.h +tal_test_SOURCES += ../src/random.c ../src/random.h +tal_test_SOURCES += ../src/str.c ../src/str.h +tal_test_SOURCES += ../src/uri.c ../src/uri.h +tal_test_SOURCES += ../src/line_file.c ../src/line_file.h +tal_test_SOURCES += tal_test.c - tal_test_CFLAGS = ${AM_CFLAGS} +tal_test_LDADD = ${MY_LDADD} ++ ++vcard_test_SOURCES = ${BASIC_MODULES} ++vcard_test_SOURCES += vcard_test.c ++vcard_test_LDADD = ${MY_LDADD} + -address_test_SOURCES = ../src/address.h -address_test_SOURCES += address_test.c -address_test_LDADD = $(MY_LDADD) diff --cc test/address_test.c index c31e8d7c,bb23a855..d829b08a --- a/test/address_test.c +++ b/test/address_test.c @@@ -28,145 -11,48 +28,176 @@@ START_TEST(check_encoding4_test } END_TEST +static void +test_range6(uint32_t a1a, uint32_t a1b, uint32_t a1c, uint32_t a1d, + uint32_t a2a, uint32_t a2b, uint32_t a2c, uint32_t a2d, + bool valid) +{ + struct ipv6_range range; + + addr6_set_quadrant(&range.min, 0, a1a); + addr6_set_quadrant(&range.min, 1, a1b); + addr6_set_quadrant(&range.min, 2, a1c); + addr6_set_quadrant(&range.min, 3, a1d); + addr6_set_quadrant(&range.max, 0, a2a); + addr6_set_quadrant(&range.max, 1, a2b); + addr6_set_quadrant(&range.max, 2, a2c); + addr6_set_quadrant(&range.max, 3, a2d); + + ck_assert_int_eq(valid ? 0 : -EINVAL, check_encoding6(&range)); +} + +START_TEST(check_encoding6_test) +{ + test_range6(0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, + false); + test_range6(0x12345678, 0x12345678, 0x12345678, 0x12345678, + 0x12345678, 0x12345678, 0x12345678, 0x12345678, + false); + test_range6(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + false); + + test_range6(0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + false); + test_range6(0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000001, + false); + + /* Matching most significant bits stop on the first quadrant */ + test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000, + 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + false); + + test_range6(0x00001010, 0x00000000, 0x00000000, 0x00000000, + 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000, + 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00000000, 0x00001000, 0x00000000, + 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00000000, 0x00000000, 0x00001000, + 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + true); + + test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000, + 0x00001F0F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000, + 0x00001FFF, 0xFF0FFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000, + 0x00001FFF, 0xFFFFFFFF, 0xFFFFF0FF, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00000000, 0x00000000, 0x00000000, + 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF0FF, + true); + + /* Matching most significant bits stop on the second quadrant */ + test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000, + 0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, + false); + + test_range6(0x00001000, 0x00001010, 0x00000000, 0x00000000, + 0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000, + 0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00001000, 0x00000000, 0x00001000, + 0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFFFFF, + true); + + test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000, + 0x00001000, 0x00001F0F, 0xFFFFFFFF, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000, + 0x00001000, 0x00001FFF, 0xFFFFF0FF, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00001000, 0x00000000, 0x00000000, + 0x00001000, 0x00001FFF, 0xFFFFFFFF, 0xFFFFF0FF, + true); + + /* Matching most significant bits stop on the third quadrant */ + test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000, + 0x00001000, 0x00001000, 0x00001FFF, 0xFFFFFFFF, + false); + + test_range6(0x00001000, 0x00001000, 0x00001010, 0x00000000, + 0x00001000, 0x00001000, 0x00001FFF, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001000, + 0x00001000, 0x00001000, 0x00001FFF, 0xFFFFFFFF, + true); + + test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000, + 0x00001000, 0x00001000, 0x00001F0F, 0xFFFFFFFF, + true); + test_range6(0x00001000, 0x00001000, 0x00001000, 0x00000000, + 0x00001000, 0x00001000, 0x00001FFF, 0xFFFFF0FF, + true); + + /* Matching most significant bits stop on the fourth quadrant */ + test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001000, + 0x00001000, 0x00001000, 0x00001000, 0x00001FFF, + false); + + test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001010, + 0x00001000, 0x00001000, 0x00001000, 0x00001FFF, + true); + + test_range6(0x00001000, 0x00001000, 0x00001000, 0x00001000, + 0x00001000, 0x00001000, 0x00001000, 0x00001F0F, + true); +} +END_TEST + + static void + test_get_address_from_string(char *text_prefix) + { + struct ipv4_prefix prefix; + const char *result; + int error; + char buffer[INET_ADDRSTRLEN]; + + error = prefix4_decode(text_prefix, &prefix); + if (error) + return; + + result = addr2str4(&prefix.addr, buffer); + + ck_assert_str_eq(text_prefix, result); + } + + START_TEST(address_test_get_addr) + { + char *text; + text = "198.248.146.0"; + + test_get_address_from_string(text); + + } + END_TEST + Suite *address_load_suite(void) { Suite *suite; - TCase *core, *test_get_address; + TCase *core; ++ TCase *string; core = tcase_create("Core"); - tcase_add_test(core, load_normal); + tcase_add_test(core, check_encoding4_test); + tcase_add_test(core, check_encoding6_test); - test_get_address = tcase_create("test_get_address"); - tcase_add_test(test_get_address, address_test_get_addr); ++ string = tcase_create("Strings"); ++ tcase_add_test(string, address_test_get_addr); + - suite = suite_create("address_test()"); + suite = suite_create("Encoding checking"); suite_add_tcase(suite, core); - suite_add_tcase(suite, test_get_address); - return suite; } diff --cc test/rtr/stream.c index 00000000,0902dc62..7526aa88 mode 000000,100644..100644 --- a/test/rtr/stream.c +++ b/test/rtr/stream.c @@@ -1,0 -1,62 +1,62 @@@ + #include "stream.h" + + #include + #include + #include + #include + + #include "common.h" + + /* + * Writes exactly @length bytes from @buffer to the file descriptor @fd. + * All or nothing. + * + * The result is zero on success, nonzero on failure. + */ + int + write_exact(int fd, unsigned char *buffer, size_t length) + { + size_t written; + int written_now; + + for (written = 0; written < length; written += written_now) { + written_now = write(fd, buffer + written, length - written); + if (written_now == -1) + return errno; + } + + return 0; + } + + /* + * "Converts" the buffer @buffer (sized @size) to a file descriptor (FD). + * You will get @buffer if you `read()` the FD. + * + * If the result is not negative, then you're receiving the resulting FD. + * If the result is negative, it's an error code. + * + * Note that you need to close the FD when you're done reading it. + */ + int + buffer2fd(unsigned char *buffer, size_t size) + { + int fd[2]; + int err; + + if (pipe(fd) == -1) { + err = errno; + warn("Pipe creation failed"); - return -abs(err); ++ return ENSURE_NEGATIVE(err); + } + + err = write_exact(fd[1], buffer, size); + close(fd[1]); + if (err) { + errno = err; + warn("Pipe write failed"); + close(fd[0]); - return -abs(err); ++ return ENSURE_NEGATIVE(err); + } + + return fd[0]; + }