fort(8)

πŸ¦‹ fort(8) β€” the FORT validator, bestie edition

okay so. fort(8). it's the FORT validator, and it's literally the moment. grab a snack πŸ“ and let's get into it.


πŸ’– NAME πŸ’–

fort β€” RPKI validator and RTR server. no cap, that's the whole vibe.


✨ SYNOPSIS ✨

fort [--mode=server] [OPTIONS]

fort --mode=standalone [OPTIONS]

fort --init-tals --tal=PATH

fort [-h|--help|--V|--version|--usage]

periodt. the way that's the entire synopsis πŸ’…


🌸 DESCRIPTION 🌸

so Fort is an RPKI "Relying Party" (RP for short, iykyk). what's that mean? it's the artifact that validates Route Origin Attestations (ROAs) via a Public Key Infrastructure (PKI). and routers use those ROAs to verify BGP routing attestations. iconic behavior honestly 🫢

its main input is one or more Trust Anchor Locator (TAL) files (--tal), and those point to the RPKI Trust Anchors β€” which are, like, the root certificates. then Fort downloads all the resources governed by the trust anchors into a local cache (--local-repository), and goes on to validate their entirety. and the output of all of that validation? a set of VRPs (Validated ROA Payloads), which get served to routers (--mode=server, --server.address, --server.port) through the RTR protocol (RFC 6810). serving looks good on you, fr fr πŸ‘‘


πŸŽ€ OPTIONS πŸŽ€

  • -h, --help β€” prints the long usage message, then dips out. exit.
  • --usage β€” prints the short usage message, then exits. tiny version, still cute.
  • -V, --version β€” prints the program version, then exits. flexing the version number, as one does ⭐
  • --init-tals β€” downloads the currently known core TALs into the existent local directory --tal, then exits.

the "currently known core TALs" are:

https://rpki.afrinic.net/tal/afrinic.tal
https://tal.apnic.net/apnic.tal
https://www.arin.net/resources/manage/rpki/arin.tal
https://www.lacnic.net/innovaportal/file/4983/1/lacnic.tal
https://tal.rpki.ripe.net/ripe-ncc.tal
  • --init-as0-tals β€” downloads the currently known AS0 TALs into the existent local directory --tal, then exits.

the "currently known AS0 TALs" are:

https://tal.apnic.net/apnic-as0.tal
https://www.lacnic.net/innovaportal/file/4983/1/lacnic-as0.tal
  • -f, --configuration-file=FILE β€” path to a JSON file where extra configuration gets read from.

the configuration options are mostly the same as the ones in this manual. every property is mapped as a member of the main JSON object, and any member containing a dot . has to be set as an object. so like, --server.port=8323 gets set as { "server": { "port": "8323" } }. it's giving nesting πŸ’…

there's an example configuration file in the EXAMPLES section of this manual, go look.

some configuration options can't be set in the JSON configuration file: --version, --help, --version, and --configuration-file. (yes, --version is listed twice, that's exactly how it is in the original, don't come for me πŸ™ˆ)

and some other configuration options can only be configured at the JSON configuration file:

  • rsync.program β€” name of the program needed to invoke an rsync file transfer. default value is "rsync".
  • rsync.arguments-recursive β€” arguments needed by rsync.program to perform a recursive rsync. specified as a JSON string array, and its default value is: [ "-rtz", "--delete", "--omit-dir-times", "--contimeout=20", "--max-size=20MB", "--timeout=15", "--exclude=.*", "--include=*/", "--include=*.cer", "--include=*.roa", "--include=*.asa", "--include=*.mft", "--include=*.crl", "--include=*.gbr", "--exclude=*", "$REMOTE", "$LOCAL" ]

FORT will replace "$REMOTE" with the remote URL it needs to download, and "$LOCAL" with the target local directory where the file is supposed to be dropped. cute lil templating moment.

see rsync(1) for a description of each argument.

  • incidences β€” a listing of actions to be performed by validation upon encountering certain common error conditions. this config is how you modify FORT's validator behavior when it runs into profile violations that, from experience, are often overlooked. πŸ‘€

incidences are configured inside the member "incidences" as a JSON array of objects, each with two members: "name" and "action", like:

"incidences": [
  {
    "name": "incid-hashalg-has-params",
    "action": "warn"
  }, {
    "name": "incid-obj-not-der-encoded",
    "action": "error"
  }
]

an incidence contains a name and an action. the name is just an identifier defined by FORT that states a particular error condition. the action is an enumeration that states the outcome of a violation of the corresponding incidence.

the action can have one of three values:

  1. error: print error message in error log level, fail validation of the offending object (and all of its children).
  2. warn: print error message in warning log level, continue validation as if nothing happened.
  3. ignore: do not print error message, continue validation as if nothing happened.

currently these are the registered incidences, each with a distinct action configured by default:

  • incid-hashalg-has-params (Signed Object's hash algorithm has NULL object as parameters). [Default action: ignore]
  • incid-obj-not-der-encoded (Object isn't DER encoded). [Default action: ignore]
  • incid-file-at-mft-not-found (File listed at manifest doesn't exist). [Default action: error]
  • incid-file-at-mft-hash-not-match (File hash listed at manifest doesn't match the actual file hash). [Default action: error]
  • incid-mft-stale (Manifest is stale). [Default action: error]
  • incid-crl-stale (CRL is stale). [Default action: error]

more info about incidences can be consulted at FORT's web docs.

  • -t, --tal=(FILE|DIRECTORY) β€” path to a .tal, or a directory containing .tal files. Fort will validate the trees pointed by them.

the TAL ("Trust Anchor Locator") is a text file that lists a few URLs you can use to access the "Trust Anchor" (the root of a particular RPKI tree) and its public key. (see RFC 8630.) πŸ”₯

  • -r, --local-repository=DIRECTORY β€” path to a directory where the local cache of the repository will be stored and/or read.

FORT accesses RPKI repositories either with rsync or RRDP (see RFC 8182). during each validation cycle, and depending on the preferred access methods defined by the CAs, Fort can do two things:

  • literally invoke an rsync command (see rsync.program and rsync.arguments-recursive), which will download the files into --local-repository.
  • fetch the RRDP Update Notification file (which implies an HTTP request) and fetch the files from there on (can be obtained from a Snapshot file or Delta files). the files will be downloaed into --local-repository. (that's "downloaded", the typo is canon, we keep it πŸ’€)

Fort's entire validation process operates on the resulting copy of the files β€” doesn't matter if the files where fetched by rsync of https. same energy either way.

because rsync uses delta encoding, you're advised to keep this cache around. it significantly speeds up subsequent validation cycles. it's literally free speed, bestie ✨

by default, the path is /var/lib/fort.

  • --daemon β€” if this flag is activated, Fort will run as a daemon. the process is detached from the calling terminal and sent to the background. bye bestie, go be mysterious πŸŒ™

all the enabled logs will be sent to syslog, so the configured values of --log.output and --validation-log.output will be ignored. (they simply do not matter in daemon mode, no cap.)

  • --work-offline β€” if this flag is activated, Fort will disable all outgoing requests (currently done with: rsync and https β€” RRDP protocol uses HTTPS to fetch data). all repository files (certificates, ROAs, etc.) are expected to exist at configured --local-repository.

otherwise, Fort will perform outgoing requests whenever this is needed. if a specific protocol needs to be deactivated, use --rsync.enabled or --http.enabled.

  • --maximum-certificate-depth=UNSIGNED_INTEGER β€” maximum allowable certificate chain length. it's meant to protect FORT from iterating infinitely due to certificate chain loops. very much a safety rail πŸ›‘οΈ

by default, it has a value of 32. the minimum allowed value is 5.

(required to prevent loops and "other degenerate forms of the logical RPKI hierarchy." (RFC 6481))

  • --slurm=(FILE|DIRECTORY) β€” path to the SLURM FILE or SLURMs DIRECTORY.

the SLURM definition is from RFC 8416. SLURM stands for "Simplified Local Internet Number Resource Management with the RPKI" β€” basically it's a document that can override (either as a filter or by adding assertions) the global RPKI repository data fetched by FORT. potentially very useful for network operators. main character energy for the netops girlies πŸ‘‘

if a DIRECTORY is set, the files with extension .slurm are the ones considered as SLURM files and FORT will use them.

the configured SLURM path (either FILE or DIRECTORY) will be read each time a new validation cycle begins. if there's a syntax or content error, the last valid version of the SLURM will be applied (if there's one) and a message will be logged to indicate this action. note that all this will happen only if --mode=server and --slurm is configured.

there's a basic example of a SLURM file in the EXAMPLES section of this manual (it's almost the same as the one in RFC 8416).

see more about SLURM configuration at FORT's web docs.

  • --mode=(server|standalone|print) β€” operation mode. pick your fighter:
    • server (Default) β€” perform validation cycles in perpetuity. (see --server.interval.validation). also, serve resulting VRPs through RTR. (see --server.*.)
    • standalone β€” perform a single validation cycle, then exit. one and done, love that for us πŸ’…
    • print β€” print a Certificate, CRL or Signed Object (ROA, Manifest or Ghostbusters) in standard output.
  • --server.address=NODE(S) β€” list of hostnames or numeric host addresses the RTR server will be bound to. must resolve to (or be) bindable IP addresses. IPv4 and IPv6 are both supported. inclusive king 🌈

the address list must be comma-separated, and each address must have the following format: <address>[#<port>]. the port defaults to --server.port.

here are some examples:

  • --server.address="localhost": bind to localhost, port --server.port.
  • --server.address="localhost, ::1#8324": same as above, and also bind to [::1]:8324.
  • --server.address="localhost#8323, ::1#8324": bind to localhost on port 8323, and to [::1]:8324. (--server.port is ignored.)

use wildcards to bind to all available addresses. note that, for historical reasons, Linux is a bit strange:

BSDs:

  • 0.0.0.0 β€” bind to all available IPv4 addresses ✨
    • :: β€” bind to all available IPv6 addresses 🌐
    • 0.0.0.0, :: β€” bind to all available IPv4 and IPv6 addresses 🫢

Now on Linux bestie it's built different:

  • 0.0.0.0 β€” bind to all available IPv4 addresses
  • :: β€” bind to all available IPv4 and IPv6 addresses
  • 0.0.0.0, :: β€” Error πŸ’€

  • --server.port=SERVICE β€” the TCP port (or service) the server address(es) get bound to by default, whenever you don't set a port yourself (peep --server.address).

It's a string bc a service alias is a whole valid value, ok?? Those aliases usually live at /etc/services. Also go look at getaddrinfo(3) and services(5) if you want the lore.

The default port (323) is privileged. For security vibes, either change it or jail it. πŸ”’

The stock fort-validator.service config sets the CAP_NET_BIND_SERVICE capability so the unprivileged daemon can still bind port 323 anyway. iconic.

  • --server.backlog=UNSIGNED_INTEGER β€” the RTR server's listen queue length. It's literally the second argument of the function listen (more at man listen(2)). It drops a hint to the implementation that the implementation shall use to cap the number of outstanding connections chilling in the socket's listen queue.

Default value? SOMAXCONN. slay.

  • --server.interval.validation=UNSIGNED_INTEGER β€” the number of seconds FORT sleeps between validation cycles 😴. The timer starts counting every time a validation finishes, not every time it begins. So lowkey the actual validation loop is longer than this number. math is mathing.

Default value: 3600.

  • --server.interval.refresh=UNSIGNED_INTEGER β€” number of seconds a router should wait before its next attempt to poll FORT, using either a Serial Query PDU or a Reset Query PDU. The countdown starts the moment an End Of Data PDU shows up (this is the client's job to administer, bestie).

This value is only used on RTR version 1 sessions (more info at RFC 8210 section 6).

Default value: 3600. Minimum allowed value: 1, maximum allowed value: 86400.

  • --server.interval.retry=UNSIGNED_INTEGER β€” number of seconds a router should wait before retrying a failed Serial Query PDU or Reset Query PDU. Countdown kicks off when the query fails, and restarts after every subsequent failure until a query finally succeeds (again, the client administers this).

Only used on RTR version 1 sessions (more info at RFC 8210 section 6).

Default value: 600. Minimum allowed value: 1, maximum allowed value: 7200.

  • --server.interval.expire=UNSIGNED_INTEGER β€” number of seconds a router can keep the current version of data while it's unable to pull off a successful subsequent query. Countdown starts upon receipt of an End Of Data PDU (client's job to administer, as always).

Only used on RTR version 1 sessions (more info at RFC 8210 section 6).

Default value: 7200. Minimum allowed value: 600, maximum allowed value: 172800. And it must be larger than server.interval.refresh and server.interval.retry. periodt.

  • --server.deltas.lifetime=UNSIGNED_INTEGER β€” okay so when routers first connect to Fort, they request a snapshot of the validation results. (ROAs and Router Keys.) Because they gotta keep their validated objects updated, and snapshots tend to be relatively big amounts of information, they request deltas afterwards over configurable intervals. ("Deltas" = the differences between snapshots, iykyk.)

During each validation cycle, Fort generates a new snapshot, plus the deltas needed to build the new snapshot from the previous one. All of this is stored in RAM. --server.deltas.lifetime is how many iterations a set of deltas gets kept before being deallocated. (Recall that every iteration lasts --server.interval.validation seconds, plus however long the validation takes. ⏳)

If a router lags behind β€” to the point Fort has already deleted the deltas it needs to update the router's snapshot β€” Fort will have to fall back to fetching the entire latest snapshot instead. 😬

  • --server.max-rtr-version=UNSIGNED_INTEGER β€” the maximum RTR version the server will be willing to negotiate with RTR clients.

RTRv0 is RFC6810, RTRv1 is RFC8210, and at time of writing, RTRv2 is draft-ietf-sidrops-8210bis-26.

RTRv0 servers Prefix VRPs. RTRv1 adds Router Keys, and RTRv2 adds ASPAs.

At present, this value defaults to v0 because Fort's validation currently yields no router keys (issue #58), and the ASPA specification is still receiving updates. Fort 1.7.0.experimental's RTRv2 implementation is draft-ietf-sidrops-8210bis-25, not 26. πŸ‘€

The default value is expected to grow as the implementation matures.

  • --prometheus.port=UNSIGNED_INTEGER β€” the TCP port the Prometheus server will be bound to.

The Prometheus server requires --prometheus.port to be defined, and --mode to be server. Otherwise it will not start. no cap.

Stats are served in openmetrics format. πŸ“Š

  • --log.enabled=true|false β€” turns the operation logs on. ✨

Default value: true (these logs are enabled).

Read more about logs at FORT validator's web docs, module 'Logging'.

  • --log.level=(error|warning|info|debug) β€” decides which operation log messages get logged based on priority, e.g. a value of info will log messages of equal or higher level (info, warning, and error).

The priority levels, from higher to lowest, are:

  • error
  • warning
  • info
  • debug

Default value: warning.

Read more about logs at FORT validator's web docs, module 'Logging'.

  • --log.output=(syslog|console) β€” the desired output where the operation logs get printed.

The value console logs messages at standard output and standard error; syslog logs to syslog.

Depending on the OS, distinct implementations of syslog could be installed (syslog, rsyslog, or syslog-ng are the most common ones). Syslog usage and configuration is out of this man scope. πŸ™ˆ

Default value: console.

Read more about logs at FORT validator's web docs, module 'Logging'.

  • -c, --log.color-output=true|false β€” if enabled, the operation logs output gets ANSI color codes. Meant for human consumption, obvi. 🌈

Meaningful only if --log.output value is console.

Default value: false (it's disabled).

Read more about logs at FORT validator's web docs, module 'Logging'.

  • --log.file-name-format=(global-url | local-path | file-name) β€” decides which version of file names should be printed during most debug/error messages at the operation logs.

Suppose a certificate was downloaded from rsync://rpki.example.com/foo/bar/baz.cer into the local cache repository/:

global-url Will print the certificate's name as rsync://rpki.example.com/foo/bar/baz.cer.

local-path Will print the certificate's name as repository/rpki.example.com/foo/bar/baz.cer.

file-name Will print the certificate's name as baz.cer.

Default value: global-url.

Read more about logs at FORT validator's web docs, module 'Logging'.

  • --log.facility=(auth | authpriv | cron | daemon | ftp | lpr | mail | news | user | uucp | from local0 to local7) β€” syslog facility used for operation logs (meaningful only if --log.output is syslog).

Default value: daemon.

Read more about logs at FORT validator's web docs, module 'Logging'.

  • --log.tag=STRING β€” text tag that gets added to the operation log messages (it shows up inside square brackets, cute).

Default value: a NULL value.

Read more about logs at FORT validator's web docs, module 'Logging'.

  • --validation-log.enabled=true|false β€” turns the validation logs on.

Default value: false (these logs are disabled).

Read more about logs at FORT validator's web docs, module 'Logging'.

  • --validation-log.level=(error|warning|info|debug) β€” decides which validation log messages get logged based on priority, e.g. a value of info will log messages of equal or higher level (info, warning, and error).

The priority levels, from higher to lowest, are:

  • error
  • warning
  • info
  • debug

Default value: warning.

Read more about logs at FORT validator's web docs, module 'Logging'.

  • --validation-log.output=(syslog|console) β€” the desired output where the validation logs get printed.

The value console logs messages at standard output and standard error; syslog logs to syslog.

Depending on the OS, distinct implementations of syslog could be installed (syslog, rsyslog, or syslog-ng are the most common ones). Syslog usage and configuration is out of this man scope.

Default value: console.

Read more about logs at FORT validator's web docs, module 'Logging'.

  • -c, --validation-log.color-output=true|false β€” if enabled, the validation logs output gets ANSI color codes. Meant for human consumption.

Meaningful only if --validation-log.output value is console.

Default value: false (it's disabled).

Read more about logs at FORT validator's web docs, module 'Logging'.

  • --validation-log.file-name-format=(global-url | local-path | file-name) β€” decides which version of file names should be printed during most debug/error messages at the validation logs.

Suppose a certificate was downloaded from rsync://rpki.example.com/foo/bar/baz.cer into the local cache repository/:

global-url Will print the certificate's name as rsync://rpki.example.com/foo/bar/baz.cer.

local-path Will print the certificate's name as repository/rpki.example.com/foo/bar/baz.cer.

file-name Will print the certificate's name as baz.cer.

Default value: global-url.

Read more about logs at FORT validator's web docs, module 'Logging'.

  • --validation-log.facility=(auth | authpriv | cron | daemon | ftp | lpr | mail | news | user | uucp | from local0 to local7) β€” syslog facility used for validation logs (meaningful only if --validation-log.output is syslog).

Default value: daemon.

Read more about logs at FORT validator's web docs, module 'Logging'. πŸ“–


  • --validation-log.tag=STRING β€” a text tag that gets tacked onto the validation log messages (it shows up wrapped in square brackets, like [whatever] πŸ’…).

Default vibe: the value is Validation.

Read more about logs at FORT validator's web docs, module 'Logging'. 🧚

  • --http.enabled=true|false β€” turns outgoing HTTP requests on or off, bestie 🌐.

If you disable it (like --http.enabled=false), FORT validator straight up won't request HTTP URIs, and it'll expect to find every single one of the corresponding repository files over at --local-repository. no cap.

  • --http.priority=UNSIGNED_INTEGER β€” assigns the priority for using HTTP to fetch repository files. higher value = higher priority, periodt πŸ”₯

This one works hand in hand with --rsync.priority, because whichever of the two values is bigger decides the first protocol it reaches for when fetching repository files. That said, it also depends on certificates info or the TAL URIs, since right now HTTP URIs are optional and not every RIR repository uses them. lowkey important.

Whenever a certificate or a TAL has both RSYNC and HTTP URIs, here's the criteria it follows to prioritize which one goes first:

  • --rsync.priority equals --http.priority: use the order specified at the certificate or the TAL to fetch the corresponding URI.
  • --rsync.priority greater than --http.priority: use RSYNC repository/TAL URI first; if there's an error fetching data, fallback to fetch HTTP repository/TAL URI.
  • --rsync.priority less than --http.priority: use HTTP repository/TAL URI first; if there's an error fetching data, fallback to use RSYNC repository/TAL URI.

By default, the value is 60, so HTTP requests are preferred over rsync requests. it's giving http supremacy πŸ™ˆ

  • --http.retry.count=UNSIGNED_INTEGER β€” the maximum number of retries whenever there's an error requesting an HTTP URI. πŸ”

A value of 0 means no retries, bestie. zero. nada.

Whenever it's necessary to request an HTTP URI, the validator will try the request at least once. If there was an error requesting the URI, the validator will retry at most --http.retry.count times to fetch the file, waiting --http.retry.interval seconds between each retry.

By default, the value is 4.

  • --http.retry.interval=UNSIGNED_INTEGER β€” the period (in seconds) to wait between retries after an error ocurred requestin HTTP URIs. ⏳

By default, the value is 5.

  • --http.user-agent=STRING β€” the User-Agent to send on HTTP requests πŸ•΅οΈ

Whatever value you specify (whether from the argument or the default value) gets utilized in libcurl’s option CURLOPT_USERAGENT.

By default, the value is fort/<current-version>.

  • --http.connect-timeout=UNSIGNED_INTEGER β€” timeout (in seconds) for the connect phase, iykyk ⏰

Whenever an HTTP connection is about to be established, the validator will wait a maximum of http.connect-timeout seconds for the peer to respond to the connection request; if the timeout is reached, the connection attempt will be ceased. bye πŸ‘‹

The value specified (either by the argument or the default value) is utilized in libcurl’s option CURLOPT_CONNECTTIMEOUT.

By default, it has a value of 30. The minimum allowed value is 1.

  • --http.transfer-timeout=UNSIGNED_INTEGER β€” the maximum time in seconds (once the connection is established) that the request can last.

Once the connection is established with the server, the request will last a maximum of http.transfer-timeout seconds. A value of 0 means unlimited time (default value). πŸ•°οΈ

The value specified (either by the argument or the default value) is utilized in libcurl’s option CURLOPT_TIMEOUT.

By default, it has a value of 900.

  • --low-speed-limit=UNSIGNED_INTEGER β€” the value Fort employs as CURLOPT_LOW_SPEED_LIMIT during every HTTP transfer. 🐌

"Abort connection if slower than LIMIT bytes/sec during TIME seconds."

(See --low-speed-time.)

Default: 100000 (100 KB/s)

  • --low-speed-time=UNSIGNED_INTEGER β€” the value Fort employs as CURLOPT_LOW_SPEED_TIME during every HTTP transfer. ⏱️

"Abort connection if slower than LIMIT bytes/sec during TIME seconds."

(See --low-speed-limit.)

Default: 10

  • --http.max-file-size=UNSIGNED_INTEGER β€” the maximum amount of bytes files are allowed to length during HTTP transfers. πŸ“

Default: 2000000000 (2 GB)

  • --http.ca-path=DIRECTORY β€” the local path where the CA’s utilized to verify the peers are located. πŸŽ€

Super useful when the CA from the peer isn't located at the default OS certificate bundle. If specified, the peer certificate will be verified using the CAs at the path. The directory MUST be prepared using the rehash utility from the SSL library:

  • OpenSSL command (with help): $ openssl rehash -h
  • LibreSSL command (with help): $ openssl certhash -h

The value specified is utilized in libcurl’s option CURLOPT_CAPATH.

By default, the path has a NULL value.

  • --http.proxy=URL β€” set a proxy to use for HTTP transfers. πŸ›°οΈ

It can be a hostname, a dotted numerical IPv4 address or a numerical IPv6 address enclosed in brackets. The port defaults to 1080; append :<port> to override.

  • --rsync.enabled=true|false β€” enables RSYNC requests. πŸ“‘

If disabled (eg. --rsync.enabled=false), FORT validator won't download files nor directories via RSYNC, and will expect to find all repository files at --local-repository.

  • --rsync.priority=UNSIGNED_INTEGER β€” assigns the priority for using RSYNC to fetch repository files. higher value = higher priority, same energy as before πŸ’«

This argument works along with --http.priority, since the higher value of the two arguments will result in the first protocol to utilize when fetching repositories files. Of course, this depends also on certificates information or the TAL URIs, since currently HTTP URIs are optional and not every RIR repository makes use of them.

Whenever a certificate has both RSYNC and HTTP URIs, the following criteria is followed to prioritize which one to use first:

  • --rsync.priority equals --http.priority: use the order specified at the certificate or the TAL to fetch the corresponding URI.
  • --rsync.priority greater than --http.priority: use RSYNC repository/TAL URI first; if there's an error fetching data, fallback to use fetch HTTP repository/TAL URI.
  • --rsync.priority less than --http.priority: use HTTP repository/TAL URI first; if there's an error fetching data, fallback to use RSYNC repository/TAL URI.

By default, the value is 50, so HTTP requests are preferred over rsync requests. πŸ¦‹

  • --rsync.retry.count=UNSIGNED_INTEGER β€” the maximum number of retries whenever there's an error executing RSYNC. πŸ”„

A value of 0 means no retries.

Whenever it's necessary to execute an RSYNC, the validator will try the execution at least once. If there was an error executing the RSYNC, the validator will retry it at most --rsync.retry.count times, waiting --rsync.retry.interval seconds between each retry.

By default, the value is 4.

  • --rsync.retry.interval=UNSIGNED_INTEGER β€” the period (in seconds) to wait between retries after an RSYNC error ocurred. βŒ›

By default, the value is 5.

  • --rsync.transfer-timeout=UNSIGNED_INTEGER β€” the maximum time in seconds that the rsync process can last. πŸƒβ™€οΈ

Once the connection is established with the server, the request will last a maximum of rsync.transfer-timeout seconds. A value of 0 means unlimited time (default value).

By default, it has a value of 900.

  • --aspa.max-providers=UNSIGNED_INTEGER β€” the maximum number of providers each customerASID is allowed to declare across all RPKI trees during each validation cycle. πŸ‘‘

Customers that exceed this provider count will be invalidated. no mercy πŸ’€

  • --output.roa=FILE β€” assigns a file where Fort will print the valid VRPs found after each validation run. πŸ“„

If the file already exists, it will be overwritten. If it doesn't exist, it will be created. To print to standard output, assign a hyphen.

Sample output when --output.format=csv:

ASN,Prefix,Max prefix length
AS1000,192.0.2.0/24,24
AS10001,203.0.113.0/28,30
AS24047,2001:db8::/64,96

Sample output when --output.format=json:

{ "roas": [
     {
          "asn": "AS1000",
          "prefix": "192.0.2.0/24",
          "maxLength": 30
     }, {
          "asn": "AS10001",
          "prefix": "203.0.113.0/28",
          "maxLength": 30
     }, {
          "asn": "AS24047",
          "prefix": "2001:db8::/64",
          "maxLength": 96
     }
] }

If absent, VRPs are not printed. πŸ™ˆ

  • --output.bgpsec=FILE β€” the file where the BGPsec Router Keys will be printed in the configured format (see --output.format). πŸ”‘

Since most of the data is binary (Subject Key Identifier and Subject Public Key Info), such data is base64url encoded without trailing pads.

When the FILE is specified, its content will be overwritten by the resulting Router Keys of the validation (if FILE doesn't exists, it'll be created).

When --output.format=csv (which is the default value), then each line of the result is printed in the following order: AS, Subject Key Identifier, Subject Public Key Info; the first line contains those column descriptors.

When --output.format=json, then each element is printed inside an object array of "router-keys"; ie:

{
  "router-keys": [
    {
      "asn": "AS64496",
      "ski": "<Base64 Encoded SKI>",
      "spki": "<Base64 Encoded SPKI>"
    },
    {
      "asn": "AS64496",
      "ski": "<Base64 Encoded SKI>",
      "spki": "<Base64 Encoded SPKI>"
    }
  ]
}

In order to print the Router Keys at console, use a hyphen as the FILE value, eg. --output.bgpsec=- πŸ’…

By default, it has no value set.

  • --output.aspa=FILE β€” assigns a file where Fort will print the valid ASPAs found after each validation run. ASPAs are always printed in JSON format. ✨

If the file already exists, it will be overwritten. If it doesn't exist, it will be created. To print to standard output, assign a hyphen.

Oh and btw, each element in that ASPA object is identifier by its customerASID, and its value is an array of provider ASIDs:

{
     "aspa" : {
          "2": [ 10000, 10001, 10002 ],
          "1513": [ 10000 ],
          "65500": [ 4221, 61971, 87645 ],
          "16777217": [ 39, 410, 503, 8018622 ]
     }
}

If absent, ASPAs are not printed. Simple as that πŸ’…

  • --output.format=csv|json β€” the output format for --output.roa and --output.bgpsec. By default it's csv. No cap.
  • --thread-pool.server.max=UNSIGNED_INTEGER β€” how many threads the RTR server reserves for handling RTR client (router) requests. The server can handle at most --thread-pool.server.max requests at once β€” anything extra just queues up and waits its turn bestie, no cutting the line πŸ™ˆ
    • Minimum: 1
    • Maximum: UINT_MAX
    • Default: 20
  • --asn1-decode-max-stack=UNSIGNED_INTEGER β€” the ASN1 decoder's max allowed stack size, in bytes, and the whole reason it exists is to avoid a stack overflow when some big gnarly nested ASN1 object gets parsed 😭 nobody wants that.
    • By default it's 4096 (that's 4 kB).

✨ EXAMPLES ✨

Obsessed with a good copy-paste moment, so here you go 🫢

fort --init-tals --tal=/tmp/tal

Download the 5 RIR TALs into the /tmp/tal directory. Boom, TALs secured ✨

fort -t /etc/tals -r /var/lib/fort --server.port=9323

Run FORT with all the default values, using a custom TALs directory, a custom repository directory as well, and binding the RTR server to port 9323.

fort -t /etc/tals -r /var/lib/fort --mode=standalone --output.roa=-

Run FORT as standalone and output ROAs CSV straight to the console. Live footage. Main character behaviour.

fort -t /etc/tals -r /var/lib/fort \
     --mode=standalone \
     --slurm=/etc/fort/slurm/

Run FORT as standalone and using a SLURM file.

fort --configuration-file=conf.json

Run FORT using the JSON configuration file conf.json.

fort -t /etc/tals -r /var/lib/fort \
     --server.address=::1 --server.port=9323 \
     --server.interval.validation=1800 \
     --output.roa=/var/lib/fort/roas.csv

Run FORT with the RTR server listening on IPv6 address ::1, port 9323, validating every 30 minutes, and printing the ROAs CSV into a file. She's serving, she's validating, she's filing πŸ’…


πŸ’– Complete configuration file πŸ’–

This is an example of a valid JSON configuration file with all its members set to a specific value. Every single one. Sit with that.

{
  "tal": "/etc/tals/",
  "local-repository": "/var/lib/fort/",
  "maximum-certificate-depth": 32,
  "slurm": "/etc/fort/slurm/",
  "mode": "server",
  "work-offline": false,
  "daemon": false,

  "server": {
    "address": [
      "192.0.2.1",
      "2001:db8::1"
    ],
    "port": 8323,
    "backlog": 4096,
    "interval": {
      "validation": 3600,
      "refresh": 3600,
      "retry": 600,
      "expire": 7200
    },
    "deltas": {
      "lifetime": 2
    }
  },

  "prometheus": {
    "port": 0
  },

  "rsync": {
    "enabled": true,
    "priority": 50,
    "retry": {
      "count": 1,
      "interval": 4
    },
    "transfer-timeout": 900,
    "program": "rsync",
    "arguments-recursive": [
      "-rtz",
      "--delete",
      "--omit-dir-times",
      "--contimeout=20",
      "--max-size=20MB",
      "--timeout=15",
      "--include=*/",
      "--include=*.cer",
      "--include=*.crl",
      "--include=*.gbr",
      "--include=*.mft",
      "--include=*.roa",
      "--exclude=*",
      "$REMOTE",
      "$LOCAL"
    ]
  },

  "http": {
    "enabled": true,
    "priority": 60,
    "retry": {
      "count": 1,
      "interval": 4
    },
    "user-agent": "fort/1.6.7",
    "max-redirs": 10,
    "connect-timeout": 30,
    "transfer-timeout": 900,
    "low-speed-limit": 100000,
    "low-speed-time": 10,
    "max-file-size": 2000000000,
    "ca-path": "/etc/ssl/certs",
    "proxy": "https://example.com:1234"
  },

  "log": {
    "enabled": true,
    "output": "console",
    "level": "warning",
    "tag": "Op",
    "facility": "daemon",
    "file-name-format": "global-url",
    "color-output": false
  },

  "validation-log": {
    "enabled": false,
    "output": "console",
    "level": "warning",
    "tag": "Validation",
    "facility": "daemon",
    "file-name-format": "global-url",
    "color-output": false
  },

  "incidences": [
    {
      "name": "incid-hashalg-has-params",
      "action": "ignore"
    }, {
      "name": "incid-obj-not-der-encoded",
      "action": "ignore"
    }, {
      "name": "incid-file-at-mft-not-found",
      "action": "error"
    }, {
      "name": "incid-file-at-mft-hash-not-match",
      "action": "error"
    }, {
      "name": "incid-mft-stale",
      "action": "error"
    }, {
      "name": "incid-crl-stale",
      "action": "error"
    }
  ],

  "output": {
    "roa": "/var/lib/fort/roas.csv",
    "bgpsec": "/var/lib/fort/bgpsec.csv",
    "format": "csv"
  },

  "asn1-decode-max-stack": 4096,

  "thread-pool": {
    "server": {
      "max": 20
    }
  }
}

🌸 Dummy SLURM file 🌸

And here's an example of a SLURM file with some prefix filters and assertions, plus some dummy Router Keys (BGPsec) info. It's giving "fully loaded example" energy πŸŽ€

{
  "slurmVersion": 1,
  "validationOutputFilters": {
    "prefixFilters": [
      {
        "prefix": "192.0.2.0/24",
        "comment": "All VRPs encompassed by prefix"
      },
      {
        "asn": 64496,
        "comment": "All VRPs matching ASN"
      },
      {
        "prefix": "198.51.100.0/24",
        "asn": 64497,
        "comment": "All VRPs encompassed by prefix, matching ASN"
      }
    ],
    "bgpsecFilters": [
      {
        "asn": 64496,
        "comment": "All keys for ASN"
      },
      {
        "SKI": "Q8KMeBsCto1PJ6EuhowleIGNL7A",
        "comment": "Key matching Router SKI"
      },
      {
        "asn": 64497,
        "SKI": "g5RQYCnkMpDqEbt9WazTeB19nZs",
        "comment": "Key for ASN 64497 matching Router SKI"
      }
    ]
  },
  "locallyAddedAssertions": {
    "prefixAssertions": [
      {
        "asn": 64496,
        "prefix": "198.51.100.0/24",
        "comment": "My other important route"
      },
      {
        "asn": 64496,
        "prefix": "2001:DB8::/32",
        "maxPrefixLength": 48,
        "comment": "My other important de-aggregated routes"
      }
    ],
    "bgpsecAssertions": [
      {
        "asn": 64496,
        "SKI": "Dulqji-sUM5sX5M-3mqngKaFDjE",
        "routerPublicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE-rkSLXlPpL_m-L7CfCfKrv1FHrM55FsIc8fMlnjHE6Y5nTuCn3UgWfCV6sYuGUZzPZ0Ey6AvezmfcELUB87eBA"
      }
    ]
  }
}

πŸ‘‘ SEE ALSO πŸ‘‘


And that's the whole page bestie, you made it to the end πŸ¦‹ this one's v1.7.0.experimental and it's dated 2026-07-14 β€” go read the section that matters to you and slay, you got this πŸ’–βœ¨ periodt.

πŸ“¦ where this page comes from