[*] Initial commit

This commit is contained in:
Lorenzo Cogotti
2021-06-07 16:55:13 +02:00
commit b0ef4dd774
117 changed files with 29737 additions and 0 deletions

919
tools/bgpgrep/bgpgrep.1.in Executable file
View File

@ -0,0 +1,919 @@
'\" et
.TH BGPGREP 1 @VERSION@ BGPGREP "User Commands"
.\"
.SH NAME
@UTILITY@
\(em filter and print BGP data within MRT dumps
.SH SYNOPSIS
.LP
.nf
@UTILITY@ \fB[\fIOPTIONS\fB]\fR... \fB[\fIFILES\fB]\fR... \fB[\fIEXPRESSION\fB]
.fi
.SH DESCRIPTION
The
.IR @UTILITY@
utility reads each possibly compressed Multithreaded Routing Toolkit
(MRT)
dump file specified by
.IR FILES
and formats its contents to standard output.
.IR @UTILITY@
may optionally evaluate a predicate defined by
.IR EXPRESSION
over every MRT record containing a BGP message.
Whenever such predicate evaluates as false the relevant BGP message is
discarded from output (see the
.IR EXAMPLES
section below).
.P
.IR EXPRESSION
predicates only affect BGP messages output, other kind of information, such as
state changes, is always printed by
.IR @UTILITY@
regardless of any filtering rule.
.P
.IR @UTILITY@
prints diagnostics to standard error,
it detects and tolerates data corruption as much as possible.
Corruption within a BGP message causes only the affected message to be
dropped. Unrecoverable corruption within the entire MRT dump file causes
the whole file to be dropped,
.IR @UTILITY@
will then move to the next file in
.IR FILES ,
if any.
.P
Such events are always reported with reasonable diagnostic errors.
Parsed data up to the corruption point may still be printed to
standard output.
.SH OPTIONS
.IR @UTILITY@
expects its options before the
.IR FILES
list. This is due to the fact that
.IR @UTILITY@
must distinguish between options and its expression predicate (see the
.IR OPERANDS
section below, for details on how
.IR @UTILITY@
makes such distinction).
.P
The following options are supported:
.IP "\fB\-\-dump\-bytecode\fP" 10
Debug option, causes
.IR @UTILITY@
to dump its filtering engine bytecode to standard error before starting
MRT dump files processing.
.IP "\fB\-\-no\-color\fP" 10
.IR @UTILITY@
may ease visualization by surrounding some output with color escape sequences,
on terminals that support this feature. This option forces colored text
output off.
.IP "\fB\-h or \-\-help\fP" 10
Prints a short help message, summarizing
.IR @UTILITY@
functionality.
.IP "\fB\-?\fP" 10
Equivalent to
.BR \-h .
.SH OPERANDS
.IR @UTILITY@
interprets all its operands up to but not including the first operand
that starts with a `\-' , or is a `(' or a `!', as the
.IR FILES
operand.
Each of these operands are pathnames to a MRT dump file to be
processed. An actual `\-' is a placemarker to read uncompressed MRT data
from standard input at that point during processing (see
.IR STDIN
section below).
.P
The first operand that starts with a `\-' , or is a `(' or a `!',
and any subsequent arguments, are interpreted as an
.IR EXPRESSION
predicate.
Legal predicates are composed of the following terms:
.IP "\fB\-type\ \fImsg\-type\fR" 10
Evaluates as true if the BGP message type matches
.IR `msg\-type' .
Message types may be provided by a human readable type name, as defined by
.IR "RFC 4271" ", " "Section 4"
(e.g. OPEN, UPDATE), or any other relevant RFC defining the message type
(e.g. ROUTE_REFRESH).
An explicit numeric code may also be provided (e.g. 1 as an equivalent to OPEN).
.IP "\fB\-attr\ \fIattribute\-type\fR" 10
Evaluates as true if the BGP message is an UPDATE containing a path attribute of
type
.IR `attribute\-type' .
The attribute type may be specified by a human readable name, as defined by
.IR "RFC 4271" ", " "Section 5.1"
(e.g. AS_PATH, ATOMIC_AGGREGATE), or any other relevant RFCs defining the
interesting path attribute (e.g. COMMUNITY).
An explicit numeric code may also be provided (e.g. 8 as an
equivalent to COMMUNITY), which is especially useful to specify
non-standard path attributes.
.IP "\fB\-aspath\ \fIpattern\fR" 10
Evaluates as true if the BGP message is an UPDATE with an AS_PATH that matches
the regular expression specified by
.IR `pattern' .
See the
.IR "AS_PATH REGULAR EXPRESSIONS"
section below for details.
.IP "\fB\-peer\ \fIpeer\-expression\fR" 10
Evaluates as true if
.IR `peer\-expression'
matches the peer that provided
the BGP data. This term matches the PEER field as displayed by the
.IR "LINE ORIENTED OUTPUT"
format (see section below for details).
Supported peer matching expressions are documented in the
.IR "PEER MATCHING EXPRESSIONS"
section below.
.IP "\fB\-loops\fR" 10
Evaluates as true if BGP message is an UPDATE whose AS_PATH contains loops.
.IP "\fB\-exact\ \fIprefix\-expression\fR" 10
Evaluates as true if BGP message is an UPDATE and contains at least one of the
relevant networks of interest specified in
.IR `prefix\-expression' .
See
.IR "PREFIX MATCHING EXPRESSIONS"
section for details.
.IP "\fB\-supernet\ \fIprefix\-expression\fR" 10
Similar to
.BR \-exact ,
but evaluates as true if BGP message contains supernets of the relevant networks
of interest, or the actual networks themselves.
.IP "\fB\-subnet\ \fIprefix\-expression\fR" 10
Similar to
.BR \-exact,
but evaluates as true if BGP message contains subnets of the relevant networks
of interest.
.IP "\fB\-related\ \fIprefix\-expression\fR" 10
Similar to
.BR \-exact
but evaluates as true if BGP message contains prefixes related to the relevant
networks of interest. A related network is defined to be either a subnet
or a supernet of the specified prefix.
.IP "\fB\-timestamp\ \fItime\-expression\fP" 10
Evaluates as true if the timestamp at which the BGP data was originated or
collected matches the specified
.IR 'time\-expression' .
Accepted expressions are described in the
.IR "TIMESTAMP MATCHING EXPRESSIONS"
section.
.IP "\fB\-communities\ \fIcommunities\-expression\fP" 10
Evaluates as true if BGP message is an UPDATE whose path attributes
contains at least one community specified within
.IR 'communities\-expression' ,
see the
.IR "COMMUNITY MATCHING EXPRESSIONS"
section below for details.
.IP "\fB\-all\-communities\ \fIcommunities\-expression\fP" 10
Similar to
.BR \-communities ,
but requires all communities inside
.IR `communities\-expression'
to be present inside UPDATE message path attributes.
.P
Terms can be combined with the following operators (in order of
decreasing precedence):
.IP "(\ \fIexpression\fR\ )" 10
Evaluates as true if expression is true, may be used to explicitly control
precedence.
.IP "\fB!\ \fIexpression\fR\ or\ \fB-not\ \fIexpression\fR" 10
Negation of expression; the unary NOT operator.
.IP "\fIexpression\ \fB[\-a]\ \fIexpression\fR\ or\ \fIexpression \fB[\-and]\ \fIexpression\fR" 10
Conjunction of expressions; the AND operator is implicit if no other operator is
provided inbetween two consecutive expressions, but can be made explicit
by explicitly inserting the
.BR \-a
or
.BR \-and
operators.
The second expression is not evaluated if the first one is false.
.IP "\fIexpression\ \fB\-o\ \fIexpression\fR\ or\ \fIexpression\ \fB\-or\ \fIexpression\fR" 10
Alternation of expressions; the OR operator. The second expression is not
evaluated if the first one is true.
.SH "AS_PATH REGULAR EXPRESSIONS"
.IR @UTILITY@
uses a specialized regular expression (regexp) style pattern matching approach
for AS_PATH.
.P
AS_PATH regular expressions support most features found in string
pattern matching, except backreferences, classes and counted repetition.
.P
ASN are specified by a numeric value, for example 19819 represents AS19819.
In their simplest form, AS_PATH expressions match an ASN sequence against
the merged BGP data AS_PATH (as specified by
.IR "RFC 4893" ),
indipendently by its starting position. In the same way
a regexp would match a string of characters. For example `19819 172' matches
AS_PATH `AS121
.BR AS19819
.BR AS172
AS1111'.
.P
The following features, commonly found in regular expressions, are supported by
.IR @UTILITY@ :
.IP "\fIComplements" 10
The prefix `!' operator can be used to match any but the specified ASN,
for example `!871' matches any ASN but AS871.
.IP "\fIAnchors" 10
`^' and `$' assume a special meaning, they match with the
beginning and the end, respectively, of the AS_PATH. This allows to assert
a particular position within the AS_PATH at which an ASN sequence
is supposed to appear.
.IP "\fIGrouping and alternation" 10
Groups can be defined inside regexp by enclosing them inside parentheses, for
example `( 202 397 )' defines a group with the single ASN sequence
`AS202 AS397'. The alternation operator `|' provides additional flexibility,
allowing multiple sequences inside groups, like
`( 202 397 | 1111 5439 )', which would match both
`AS1921
.BR AS202
.BR AS397 '
and `AS2431
.BR AS1111
.BR AS5439
AS79'. Alternation can be used even outside groups and alternatives may very
well be more than two. Groups may be nested.
.IP "\fIMetacharacters" 10
The `.' metacharacter can be used to match any ASN in its position.
The metacharacters `*', `?' and `+' are repetition operators, they can be used
to match the preceding ASN or group multiple times in different ways. `191*'
matches AS191 zero or more times, `191?' matches AS191 zero or one time,
while `191+' matches AS191 one or more times.
.P
These features may be combined at will to provide powerful expressions,
for example `^ !432' matches any AS_PATH that does not start with AS432.
.P
Extensive usage examples can be found in the
.IR EXAMPLES
section.
.SH "PEER MATCHING EXPRESSIONS"
Peer matching expressions specify a set of relevant peers, either by
providing their IP address, their ASN, or both.
The constructed set is then matched against the peer providing the BGP data
inside the MRT input files.
.P
Allowed constructs are:
.IP "\fIpeer\-asn\fR" 10
Only peer ASN is matched for equality.
.IP "\fIpeer\-address\fR" 10
Only peer IP address is matched for equality.
.IP "\(dq\fIpeer\-address\ \fIpeer\-asn\fR\(dq"
Both peer IP address and ASN are tested for equality.
.P
When both IP address and ASN are provided, the match should be quoted
so that it is understood to be a single match as opposed to one match by
peer address followed by another match by peer ASN.
.P
Multiple peer matches can be provided at the same time by enclosing them in
parentheses, for example `( \(aq199036\(aq \(aq173.2.2.1 7566\(aq )'
matches both peer AS199036 and peer AS7566 with IP address 173.2.2.1.
.P
Whenever a peer matching expression is expected, a filepath to a text file
may be specified in its place. In this case
.IR @UTILITY@
will read the peer matches directly from file. Matches inside file may be
separated by either spaces or newlines. No parentheses are necessary, though
quoting may still be necessary for matches specifying both peer address and
ASN. Typical C and C++ style comments are supported within the file.
.P
See the
.IR EXAMPLES
section for usage examples.
.SH "COMMUNITY MATCHING EXPRESSIONS"
COMMUNITY matching expressions define a set of interesting communities.
Communities may be specified in any of the following ways:
.IP \[bu]
A well-known COMMUNITY name (e.g. BLACKHOLE for COMMUNITY 0xFFFF029A).
.IP \[bu]
A hexadecimal numeric COMMUNITY code (e.g. 0xFFFFFF01 for NO_EXPORT).
.IP \[bu]
The canonical representation of a COMMUNITY as two fields separated by `:',
such as `65535:65282' for NO_ADVERTISE. In this form either one of the two
field, but not both, may be left unspecified by marking it with `*'. In this
case, communities will be matched only against the specified portion.
For example `65535:*' matches any COMMUNITY whose first two octets match 65535.
.P
Multiple communities may be listed by enclosing them in parentheses,
for example `( \(aq65535:*\(aq \(aq0:*\(aq )' matches any reserved COMMUNITY
as per
.IR "RFC 1997" .
.P
Whenever a community matching expression is expected, a filepath to a text file
may be provided in its place. In this case
.IR @UTILITY@
will parse the communities from the file itself. Each COMMUNITY inside file
may be separated by either spaces or newlines. No parentheses are required.
Typical C and C++ style comments are supported within the file.
.P
See the
.IR EXAMPLES
section for usage examples.
.SH "PREFIX MATCHING EXPRESSIONS"
Prefix matching expressions define a set of interesting networks.
Networks are specified as prefixes in their CIDR notation, for example
193.0.0.0/16 or 2001:67c::/32.
If prefix length is not defined explicitly, it is taken to be the full IP
address length, that is 32 for IPv4 addresses and 64 for IPv6 addresses.
.P
Prefix matching can be restricted to announcements or withdrawals.
Syntax is:
.IP "\fB+\fIprefix\fR" 10
Restrict matching to announcements only.
.IP "\fB-\fIprefix\fR" 10
Restrict matching to withdrawals only.
.P
If none of `+' or `-' is prepended, then matching takes place on
both announcements and withdrawals.
.P
Multiple prefixes can be specified at the same time by enclosing them in
parentheses, for example: `( \(aq+193.0.0.0/16\(aq \(aq2001:67c::/32\(aq )'.
.P
Whenever a prefix matching expression is expected, a filepath to a text file
may be specified in its place. In this case
.IR @UTILITY@
will parse the relevant prefixes from the file itself. Inside file, prefixes
may be separated by either spaces or newlines. No parentheses are required.
Typical C and C++ style comments are supported within the file.
.P
See the
.IR EXAMPLES
section for usage examples.
.SH "TIMESTAMP MATCHING EXPRESSIONS"
Timestamp matching expressions define a time point and a matching direction.
Expressions are matched either to the MRT header timestamp, in case of
BGP4MP and ZEBRA records (commonly referred to as updates), or to the
ORIGINATED field in case of TABLE_DUMPV2 or TABLE_DUMP snapshots (commonly
referred to as RIB snapshots).
Timestamps may be specified in either of the two following formats:
.IP \[bu]
A
.IR "Unix timestamp"
in its explicit numeric representation, such as `1622725323', which is
equivalent to `2021\-06\-03 13:02:03 GMT'.
Microsecond resolution may be added appending a
<dot>
followed by the subsecond part, such as `1622725323.000030'.
.IP \[bu]
A human readable
.IR "RFC 3339"
UTC timestamp. This format is commonly found in JSON. For example
`1985\-04\-12T23:20:50.52Z' .
.P
Matching direction may be any of the following:
.IP "\fB>=\fItimestamp\fR" 10
Matches if BGP data was originated after or exactly at the relevant timestamp.
.IP "\fB>\fItimestamp\fR" 10
Matches if BGP data was originated after the relevant timestamp.
.IP "\fB=\fItimestamp\fR" 10
Matches if BGP data was originated exactly at the relevant timestamp.
.IP "\fB<\fItimestamp\fR" 10
Matches if BGP data was originated before the relevant timestamp.
.IP "\fB<=\fItimestamp\fR" 10
Matches if BGP data was originated before or exactly at the relevant timestamp.
.P
If no matching direction is provided, `=' is implicitly assumed. See the
.IR EXAMPLES
section for usage examples.
.SH "LINE ORIENTED OUTPUT"
.IR @UTILITY@
prints each MRT record into multiple lines, each one representing either
.BR "ROUTE INFORMATION"
or
.BR "BGP SESSION STATUS" .
.P
.BR "ROUTE INFORMATION"
can be either an announcement, a route withdrawn or a routing table (RIB)
snapshot.
Each
.BR "ROUTE INFORMATION"
line is a sequence of the following `|' separated fields:
.RS 4
.sp
.RS 4
.nf
TYPE|PREFIXES|PATH ATTRIBUTES|PEER|TIMESTAMP|ASN32BIT
.fi
.P
.RE
.P
Fields have the following meaning:
.IP "\fBTYPE\fR" 4
Single character describing the route information type, may be `='
(RIB snapshot entry), `+' (announcement), or `-' (withdrawn).
.IP "\fBPREFIXES\fR" 4
The list of prefixes carried into the message. If the information is an
announcement, then this enumerates the prefixes within NLRI and
MP_REACH_NLRI fields. If the information is a withdrawn, then this
enumerates the prefixes within WITHDRAWN_ROUTES and MP_UNREACH_NLRI fields.
If the information is a RIB snapshot entry, then this is the prefix
related to the current RIB entry.
Multiple prefixes are separated by a single space.
.IP "\fBPATH ATTRIBUTES\fR" 4
This is a `|' separated list of the most common BGP path attributes
characterizing a route. Each field is left empty if the corresponding path
attribute is not present in the collected BGP data (e.g. route announcements
without optional attributes, or route withdrawals).
The currently displayed path attributes are:
.RS 4
.sp
.RS 4
.nf
AS_PATH|NEXT_HOP|ORIGIN|ATOMIC_AGGREGATE|AGGREGATOR|COMMUNITIES
.fi
.P
.RE
.P
If the BGP peer does not support ASN32BIT capability, then the AS_PATH
field contains the result of the merging procedure between AS_PATH and AS4_PATH
attributes, according to
.IR "RFC 4893" ,
and the AGGREGATOR field contains the AS4_AGGREGATOR attribute (if present).
Otherwise, AS_PATH and AGGREGATOR fields contain the respective attribute.
.P
NEXT_HOP field contains either the NEXT_HOP attribute (IPv4) or the next hop
address(es) listed into the MP_REACH_NLRI attribute (IPv6), as described in
.IR "RFC 4760" .
.P
ORIGIN contains the corresponding attribute encoded as a single character,
`i' (IGP), `e' (EGP), `?' (INCOMPLETE).
.P
ATOMIC_AGGREGATE field contains
.BR "AT"
if the attribute is set, it is left empty otherwise.
.P
COMMUNITIES field contains both COMMUNITY (
.IR "RFC 1997"
) and LARGE_COMMUNITY (
.IR "RFC 8092"
) displayed in their canonical representation. Well\-known communities are
displayed according to their IANA assigned names (e.g. NO_EXPORT instead of
`65535:65281').
.P
.RE
.IP "\fBPEER\fP" 4
The BGP peer that provided the BGP message.
If the peer uses the ADD\-PATH extension (
.IR "RFC 7911"
) to announce BGP data, then it is displayed as `peer\-address peer\-asn
path\-id', otherwise as `peer\-address peer\-asn'.
.IP "\fBTIMESTAMP\fP" 4
Displays the Unix epoch time at which the information was collected.
If extended timestamp information is available, the Unix Epoch time is followed
by a `.' and the microsecond precision is appended right after it. Timestamp is
displayed as a raw numerical value.
.IP "\fBASN32BIT\fP" 4
May be either 1, if BGP data has ASN32BIT capability, or 0.
.P
.RE
The
.BR "BGP SESSION STATUS"
is encoded as a BGP session state change according to
.IR "RFC 6936" ", " "Section 4.4.1" .
The format of a line representing a state change is the following:
.RS 4
.sp
.RS 4
.nf
#|OLD_STATE-NEW_STATE|||||||PEER|TIMESTAMP|ASN32BIT
.fi
.P
.RE
.P
Each field has the following format:
.IP "\fBOLD_STATE\-NEW_STATE\fP" 4
Represents the old and new state of the BGP session respectively,
according to the BGP Finite State Machine states numerical codes.
.IP "\fBPEER, TIMESTAMP, ASN32BIT\fP" 4
Have identical format and meaning with regards to the
.BR "ROUTING INFORMATION"
case.
.P
.RE
Each line produced always has the same `|' character count, in both
.BR "ROUTING INFORMATION" 's
and
.BR "BGP SESSION STATUS" 's
case. This facilitates the task of writing simple scripts that manipulate
.IR @UTILITY@ 's
output text.
.SH "EXIT STATUS"
The following exit values are returned:
.IP "\00" 6
All input data was scanned successfully,
and data was written to output correctly.
.IP >0 6
Errors were detected in input data, write error occurred,
or an unrecoverable error occurred (such as out of memory errors).
.SH STDIN
The standard input is used only if no
.IR FILES
arguments are provided, or when any of the specified
.IR FILES
arguments is `\-' , in which case MRT data is read from standard input at that
point, up to an <end\-of\-file>.
.P
Whenever
.IR @UTILITY@
reads from standard input, MRT data is assumed to be uncompressed.
.SH "INPUT FILES"
.IR @UTILITY@
supports most MRT dump formats as written by the majority of Route Collecting
projects (see the
.IR STANDARDS
section below for additional references).
MRT dumps may be provided either in their plain uncompressed form, or
as files compressed by
.IR gzip (1),
.IR bzip2 (1),
or
.IR xz (1).
.IR @UTILITY@
performs appropriate decompression on the fly.
File extension is used, in a case insensitive way, to discriminate among
supported compression formats. If the file extension is not recognized,
or there is no extension, then it is assumed to be uncompressed.
.SH STDOUT
The standard output is used to print a human readable text representation of
BGP message data, nothing else shall be written to the standard output.
.IR @UTILITY@
may detect and treat as error whenever the standard output is a regular file,
and is the same file as any of the
.IR FILES
arguments.
The default output format used by
.IR @UTILITY@
is documented in the
.IR "LINE ORIENTED OUTPUT"
section.
.SH STDERR
The standard error is used only for diagnostic messages and error reporting.
Any BGP message output is exclusive to standard output.
.SH EXAMPLES
This section contains some useful examples, starting from trivial ones,
demonstrating basic
.IR @UTILITY@
usage, to more complex ones employing sophisticated filtering predicates.
Examples in this section use paranoid quoting, since this a worthwhile habit
that eliminates potential pitfalls introduced by shell expansion.
.IP \[bu]
The following is the simplest way to invoke
.IR @UTILITY@ :
.nf
\&
.in +2m
@UTILITY@
.in
\&
.fi
It formats and prints all the BGP data it inside the uncompressed MRT
input data it receives from standard input.
.IP \[bu]
The following command:
.nf
\&
.in +2m
@UTILITY@ -- -peer \(aq199036\(aq
.in
\&
.fi
finds all BGP data announced by peer AS199036, taking MRT input data
implicitly from standard input. Notice how an explicit `\-\-' is necessary
for
.IR @UTILITY@
to interpret
.BR \-peer
as an actual
.IR EXPRESSION
operand, rather than incorrectly mistaking it for
.IR OPTIONS.
.IP \[bu]
The following is equivalent to the previous example:
.nf
\&
.in +2m
@UTILITY@ ./rib.1.bz2 -peer \(aq199036\(aq
.in
\&
.fi
but takes MRT input data from a
.IR bzip (1),
compressed file. The file argument removes the necessity of an explicit `\-\-'
to separate
.IR FILES
and
.IR EXPRESSION
operands.
.IP \[bu]
The following command:
.nf
\&
.in +2m
@UTILITY@ ./updates.*.gz -aspath \(aq^199036\(aq
.in
\&
.fi
finds every message whose first ASN in AS_PATH is AS199036, inside all
.IR gzip (1)
compressed files resulting from the glob expansion.
.IP \[bu]
The following command:
.nf
\&
.in +2m
@UTILITY@ ./updates.*.gz -aspath \(aq3333$\(aq
.in
\&
.fi
is similar to the previous example, but uses
.IR "AS_PATH REGULAR EXPRESSIONS"
to find every BGP message whose last ASN in AS_PATH is AS3333.
.IP \[bu]
The following command:
.nf
\&
.in +2m
@UTILITY@ ./updates.*.gz -aspath \(aq174 3356\(aq
.in
\&
.fi
demonstrates yet another basic use of
.IR "AS_PATH REGULAR EXPRESSIONS"
to find every BGP message whose AS_PATH crosses link AS174 AS3356.
Notice how the argument of
.BR \-aspath
needs quoting.
.IP \[bu]
The following command demonstrates a more advanced use of
.IR "AS_PATH REGULAR EXPRESSIONS":
.nf
\&
.in +2m
@UTILITY@ ./updates.*.gz -aspath \(aq174 (2603+|202+|303+) 3356\(aq
.in
\&
.fi
It finds every BGP message whose AS_PATH crosses AS174 and AS3356, through one
intermediate ASN among AS2603, AS202, or AS303. It also takes care of possible
prepending for the inbetween ASN.
.IP \[bu]
The following commands are equivalent:
.nf
\&
.in +2m
@UTILITY@ ./updates.*.gz -aspath \(aq^7854 .* 5032$\(aq -or -aspath \(aq109 .* 9081$\(aq
@UTILITY@ ./updates.*.gz -aspath \(aq^7854 .* 5032$ | ^109 .* 9081$\(aq
.in
\&
.fi
They both find every BGP message whose AS_PATH starts at AS7854 and terminates
at AS5032, or starts at AS109 and terminates at AS9081.
The second being the most efficient.
This example illustrates the use of alternation inside
.IR "AS_PATH REGULAR EXPRESSIONS"
to test multiple patterns at the same time.
.IP \[bu]
The following example:
.nf
\&
.in +2m
@UTILITY@ ./rib.*.xz -subnet \(aq192.65.0.0/16\(aq -aspath \(aq174 137\(aq
.in
\&
.fi
finds all subnets of 192.65.0.0/16 crossing link AS174 AS137.
It combines two
.IR EXPRESSION
terms with an implicit AND operator, since no explicit
.BR \-and
and no
.BR \-or
was provided, as detailed by the
.IR OPERANDS
section.
.IP \[bu]
The following commands are equivalent:
.nf
\&
.in +2m
@UTILITY@ ./rib.*.gz \e( -subnet \(aq193.0.0.0/16\(aq -or -subnet \(aq2001:67c::/32\(aq \e) -aspath \(aq3333$\(aq
@UTILITY@ ./rib.*.gz -subnet \e( \(aq193.0.0.0/16\(aq \(aq2001:67c::/32\(aq \e) -aspath \(aq3333$\(aq
.in
\&
.fi
They both print every message containing subnets of 193.0.0.0/16 or
2001:67c::/32 destinated to AS3333, the second being a more efficient
alternative. In the latter, notice the use of `(' and `)' inside
.BR \-subnet
to provide multiple arguments.
This behavior is further explained in the
.IR "PREFIX MATCHING EXPRESSIONS"
section, and is common to most matching expressions provided by
.IR @UTILITY@ .
.IP \[bu]
The following command:
.nf
\&
.in +2m
@UTILITY@ ./rib.*.bz2 -peer \(aq202\(aq -timestamp \(aq>=2012-10-01\(aq -timestamp \(aq<2012-11-01\(aq -loops
.in
\&
.fi
is another example combining multiple
.IR EXPRESSION
terms to achieve complex filtering. It scans all
.IR bzip2 (1)
compressed MRT input files resulting from glob expansion,
and prints every BGP message provided by peer AS202 during the month of
October, 2012 containing loops in its AS_PATH.
Notice how multiple
.BR \-timestamp
terms can be combined to effectively define bounded time ranges.
.IP \[bu]
The following command:
.nf
\&
.in +2m
@UTILITY@ ./rib.*.bz2 -communities \e( \(aq0:*\(aq \(aq65535:*\(aq \e) -peer \(aq185.169.236.135 201\(aq
.in
\&
.fi
prints all the BGP messages containing reserved communities, provided by peer
AS201 with IP address 185.169.236.135.
.IP \[bu]
The following command:
.nf
\&
.in +2m
@UTILITY@ ./rib.*.bz2 -all-communities ./commlist.tpl -subnet ./netlist.tpl
.in
\&
.fi
demonstrates the use of filepath arguments for
.BR \-all\-communities
and
.BR \-subnet
.IR EXPRESSION
terms.
.IR bgpgrep
will parse the two text files and use their contents as arguments.
This is especially useful to create templates containing relevant networks,
communities, or peers and reuse them across various runs.
.SH "APPLICATION USAGE"
The
.IR @UTILITY@
utility and its filtering engine have been designed for performance.
Providing predicates has the double role of cleaning the output of irrelevant
data, without resorting to complex scripting, and avoid wasting time to
decode useless data. Therefore
.IR @UTILITY@
can gain a lot performance\-wise when provided with restrictive predicates,
that cut away significant amounts of BGP data from its input files.
.P
.IR @UTILITY@
deliberately mimics the
.IR find (1)
utility operands convention, in an attempt to feel familiar to the experienced
shell user and provide a powerful
.IR EXPRESSION
syntax that feels both expressive and readable.
Though, many of
.IR find (1)'s
subtleties also apply to
.IR bgpgrep .
When writing
.IR EXPRESSION ,
it should be noted that `!', `(', `<' and `>' have special meaning to the shell,
and should be quoted accordingly.
.IR @UTILITY@
provides the alternative
.BR \-not
syntax for the unary NOT
.BR !
operator that avoids the problem. Still, care should be used with other
.IR EXPRESSION
terms arguments. When in doubt use explicit quotes, as demonstrated in the
.IR EXAMPLES
section.
.P
.IR @UTILITY@
attempts to provide descriptive output for syntax errors that should help
with most of these problems.
.P
Another common source of errors is the distinction between
.IR FILES
and
.IR EXPRESSION .
.IR bgpgrep
treats any operand starting with `\-' and followed by at least one character
as the beginning of an
.IR EXPRESSION ,
and an actual `\-' as a placeholder for standard input (see
.IR STDIN
and
.IR OPERANDS
sections for details). In the unlikely event of having to deal with files
that may generate ambiguity (e.g. a file named `\-'), make the file reference
explicit by prepending `./' (e.g. `./\-' to reference a file named `\-' in the
current directory).
If the
.IR FILES
list should be left empty, but an
.IR EXPRESSION
should still be applied, then provide an explicit `\-\-' to mark the empty file
list, as shown in the
.IR EXAMPLES
section.
.SH SEE ALSO
.IR awk (1),
.IR grep (1)
.SH STANDARDS
The
.IR @UTILITY@
utility conforms to:
.IP \[bu] 2m
.IR "RFC 6396" " \-" "Multi\-Threaded Routing Toolkit (MRT) Routing Information Export Format"
.IP \[bu] 2m
.IR "RFC 8050" " \- " "Multi\-Threaded Routing Toolkit (MRT) Routing Information Export Format with BGP Additional Path Extensions"
.IP \[bu] 2m
.IR "IANA Border Gateway Protocol (BGP) Well\-known Communities" ". Updated list of well\-known communities as of 2021\-05\-07."
.SH AUTHOR
.IR @UTILITY@
was written by
.UR lcg@\:inventati.\:org
Lorenzo Cogotti
.UE .
.IR @UTILITY@
is an evolution over
.IR bgpscanner
originally developed by the same author at the Institute of Informatics and
Telematics of the Italian National Research Council (IIT\-CNR),
with significant contributions by the Isolario project development team at
the time.

441
tools/bgpgrep/bgpgrep.c Executable file
View File

@ -0,0 +1,441 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* \file bgpgrep.c
*
* `bgpgrep` main entry point and general command line parsing.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgpgrep_local.h"
#include "bgp/bgp.h"
#include "bgp/bytebuf.h"
#include "bgp/patricia.h"
#include "cpr/flate.h"
#include "cpr/bzip2.h"
#include "cpr/xz.h"
#include "sys/fs.h"
#include "sys/con.h"
#include "sys/sys.h"
#include "sys/vt100.h"
#include "argv.h"
#include "strlib.h"
#include <assert.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#define BYTEBUFSIZ (256 * 1024)
static BGP_FIXBYTEBUF(BYTEBUFSIZ) bgp_msgBuf = { BYTEBUFSIZ };
BgpgrepState S;
typedef enum {
NO_COLOR_FLAG,
DUMP_BYTECODE_FLAG,
NUM_FLAGS
} BgpgrepOpt;
static Optflag options[] = {
[NO_COLOR_FLAG] = {
'-', "no-color", NULL, "Disable color output", ARG_NONE
},
[DUMP_BYTECODE_FLAG] = {
'-', "dump-bytecode", NULL, "Dump BGP VM bytecode to stderr (debug)", ARG_NONE
},
[NUM_FLAGS] = { '\0' }
};
static void Bgpgrep_SetupCommandLine(char *argv0)
{
Sys_StripPath(argv0, NULL);
Sys_StripFileExtension(argv0, NULL);
com_progName = argv0;
com_synopsis = "[OPTIONS...] [FILES...]";
com_shortDescr = "Filter and print BGP data within MRT dumps";
com_longDescr =
"Reads MRT dumps in various formats, applying filtering rules\n"
"to each BGP message therein, then outputs every passing message to stdout.\n"
"If - is found inside FILES, then input is read from stdin.\n"
"If no FILES arguments are provided, then input\n"
"is implicitly expected from stdin.\n"
"Any diagnostic message is logged to stderr.";
}
static void Bgpgrep_ApplyProgramOptions(void)
{
S.noColor = options[NO_COLOR_FLAG].flagged;
S.dumpBytecode = options[DUMP_BYTECODE_FLAG].flagged;
}
static int CountFileArguments(int argc, char **argv)
{
int i;
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
if (arg[0] == '-' && arg[1] != '\0')
break;
if (strcmp(arg, "(") == 0 || strcmp(arg, "!") == 0)
break;
}
return i;
}
void Bgpgrep_Fatal(const char *fmt, ...)
{
va_list va;
Sys_Print(STDERR, com_progName);
Sys_Print(STDERR, ": ERROR: ");
va_start(va, fmt);
Sys_VPrintf(STDERR, fmt, va);
va_end(va);
Sys_Print(STDERR, "\n");
exit(EXIT_FAILURE);
}
void Bgpgrep_Warning(const char *fmt, ...)
{
va_list va;
Sys_Print(STDERR, com_progName);
Sys_Print(STDERR, ": ");
if (S.filename) {
Sys_Print(STDERR, S.filename);
Sys_Print(STDERR, ": ");
}
Sys_Print(STDERR, "WARNING: ");
va_start(va, fmt);
Sys_VPrintf(STDERR, fmt, va);
va_end(va);
Sys_Print(STDERR, "\n");
}
void Bgpgrep_DropMessage(const char *fmt, ...)
{
va_list va;
Sys_Print(STDERR, com_progName);
Sys_Print(STDERR, ": ");
assert(S.filename);
Sys_Print(STDERR, S.filename);
Sys_Print(STDERR, ": ");
va_start(va, fmt);
Sys_VPrintf(STDERR, fmt, va);
va_end(va);
Sys_Print(STDERR, "\n");
Bgp_ClearMsg(&S.msg);
S.nerrors++;
longjmp_fast(S.dropMsgFrame);
}
void Bgpgrep_DropRecord(const char *fmt, ...)
{
va_list va;
Sys_Print(STDERR, com_progName);
Sys_Print(STDERR, ": ");
assert(S.filename);
Sys_Print(STDERR, S.filename);
Sys_Print(STDERR, ": ");
va_start(va, fmt);
Sys_VPrintf(STDERR, fmt, va);
va_end(va);
Sys_Print(STDERR, "\n");
S.nerrors++;
S.lenientBgpErrors = FALSE; // reset for future files
// If we're dropping a record we have to kill S.rec and S.msg
Bgp_ClearMsg(&S.msg);
Bgp_ClearMrt(&S.rec);
// ...but don't drop PEER_INDEX_TABLE
longjmp_fast(S.dropRecordFrame);
}
void Bgpgrep_DropFile(const char *fmt, ...)
{
va_list va;
Sys_Print(STDERR, com_progName);
Sys_Print(STDERR, ": ");
assert(S.filename);
Sys_Print(STDERR, S.filename);
Sys_Print(STDERR, ": ");
va_start(va, fmt);
Sys_VPrintf(STDERR, fmt, va);
va_end(va);
Sys_Print(STDERR, "\n");
S.nerrors++;
S.lenientBgpErrors = FALSE; // reset for future files
// Must clear PEER_INDEX_TABLE also, along with S.rec and S.msg...
Bgp_ClearMsg(&S.msg);
Bgp_ClearMrt(&S.rec);
Bgp_ClearMrt(&S.peerIndex);
S.hasPeerIndex = FALSE;
longjmp_fast(S.dropFileFrame);
}
// Common error management for BGP layer
NOINLINE static void Bgpgrep_HandleBgpError(BgpRet err, Srcloc *loc, void *obj)
{
USED(obj);
if (err == BGPEVMMSGERR)
err = Bgp_GetErrStat(NULL); // retrieve the actual BGP error
// Hopefully we are not dealing with a programming error...
assert(err != BGPEBADMRTTYPE);
assert(err != BGPENOADDPATH);
assert(err != BGPEBADTYPE && err != BGPEBADATTRTYPE);
// On out of memory we die with an appropriate backtrace
if (err == BGPENOMEM) {
loc->call_depth++; // include this function itself
_Sys_OutOfMemory(loc->filename, loc->func, loc->line, loc->call_depth);
}
// BGP filter errors should never occur, but are theoretically
// possible when filters are excessively complex,
// we don't proceed any further since they may cause incomplete or
// misleading output data
if (BGP_ISVMERR(err))
Bgpgrep_Fatal("Unexpected BGP filter error: %s", Bgp_ErrorString(err));
// On I/O error we drop the entire file
if (err == BGPEIO)
Bgpgrep_DropFile("I/O error while reading MRT dump"); // TODO: better diagnostics
// Deal with critical MRT errors that cause a record drop
if (err == BGPETRUNCMRT || err == BGPETRUNCPEERV2 || err == BGPETRUNCRIBV2)
Bgpgrep_DropRecord("SKIPPING MRT RECORD: %s", Bgp_ErrorString(err));
if (BGP_ISMSGERR(err)) {
if (S.lenientBgpErrors) {
// Get away with just a warning
Bgpgrep_Warning("CORRUPT BGP MESSAGE: %s", Bgp_ErrorString(err));
S.nerrors++;
return;
}
// Drop the affected entry
Bgpgrep_DropMessage("SKIPPING BGP MESSAGE: %s", Bgp_ErrorString(err));
}
assert(BGP_ISMRTERR(err));
if (err == BGPEBADPEERIDXCNT || err == BGPEBADRIBV2CNT) {
// Only warrant a warning
Bgpgrep_Warning("CORRUPT MRT RECORD: %s", Bgp_ErrorString(err));
S.nerrors++;
return;
}
// Anything else causes a BGP drop
Bgpgrep_DropMessage("SKIPPING BGP MESSAGE: %s", Bgp_ErrorString(err));
}
static void Bgpgrep_Init(void)
{
if (!S.noColor && Sys_IsVt100Console(STDOUT))
S.outFmt = Bgp_IsolarioFmtWc; // console supports colors
else
S.outFmt = Bgp_IsolarioFmt;
Mrtrecord *rec = &S.rec;
Bgpmsg *msg = &S.msg;
rec->allocp = msg->allocp = &bgp_msgBuf;
rec->memOps = msg->memOps = Mem_BgpBufOps;
Bgp_InitVm(&S.vm, /*heapSize=*/0);
}
static const StmOps *Bgpgrep_OpenMrtDump(const char *filename, void **phn)
{
Fildes fh = Sys_Fopen(filename, FM_READ, FH_SEQ);
if (fh == FILDES_BAD)
Bgpgrep_DropFile("Can't open file");
const char *ext = Sys_GetFileExtension(filename);
void *hn;
const StmOps *ops;
if (Df_stricmp(ext, ".bz2") == 0) {
hn = Bzip2_OpenDecompress(STM_FILDES(fh), Stm_FildesOps, /*opts=*/NULL);
ops = Bzip2_StmOps;
Bzip2Ret err = Bzip2_GetErrStat();
if (err)
Bgpgrep_DropFile("Can't read Bz2 archive: %s", Bzip2_ErrorString(err));
} else if (Df_stricmp(ext, ".gz") == 0 || Df_stricmp(ext, ".z") == 0) {
hn = Zlib_InflateOpen(STM_FILDES(fh), Stm_FildesOps, /*opts=*/NULL);
ops = Zlib_StmOps;
ZlibRet err = Zlib_GetErrStat();
if (err)
Bgpgrep_DropFile("Can't read Zlib archive: %s", Zlib_ErrorString(err));
} else if (Df_stricmp(ext, ".xz") == 0) {
hn = Xz_OpenDecompress(STM_FILDES(fh), Stm_FildesOps, /*opts=*/NULL);
ops = Xz_StmOps;
XzRet err = Xz_GetErrStat();
if (err)
Bgpgrep_DropFile("Can't read LZMA archive: %s", Xz_ErrorString(err));
} else {
// Assume uncompressed file
hn = STM_FILDES(fh);
ops = Stm_FildesOps;
}
*phn = hn;
return ops;
}
static void Bgpgrep_ProcessMrtDump(const char *filename)
{
void *hn;
const StmOps *ops;
S.filename = filename; // NOTE: Only function that manipulates this
if (strcmp(filename, "-") == 0) {
hn = STM_FILDES(CON_FILDES(STDIN));
ops = Stm_NcFildesOps;
} else
ops = Bgpgrep_OpenMrtDump(filename, &hn);
setjmp_fast(S.dropRecordFrame); // NOTE: The ONLY place where this is set
setjmp_fast(S.dropMsgFrame); // NOTE: May be set again by specific BgpgrepD_*()
while (Bgp_ReadMrt(&S.rec, hn, ops) == OK) {
const Mrthdr *hdr = MRT_HDR(&S.rec);
switch (hdr->type) {
case MRT_TABLE_DUMPV2: BgpgrepD_TableDumpv2(); break;
case MRT_TABLE_DUMP: BgpgrepD_TableDump(); break;
case MRT_BGP: BgpgrepD_Zebra(); break;
default:
if (MRT_ISBGP4MP(hdr->type))
BgpgrepD_Bgp4mp();
break;
}
}
if (ops->Close)
ops->Close(hn);
S.filename = NULL;
}
static int Bgpgrep_CleanupAndExit(void)
{
assert(!S.hasPeerIndex); // should have been cleared
Bgp_ClearVm(&S.vm);
Trielist *t = S.trieList;
while (t) {
Trielist *tn = t->next;
Pat_Clear(&t->trie);
free(t);
t = tn;
}
return (S.nerrors > 0) ? EXIT_FAILURE : EXIT_SUCCESS;
}
int main(int argc, char **argv)
{
#ifndef NDEBUG
Sys_SetErrFunc(SYS_ERR_ABORT, NULL);
#else
Sys_SetErrFunc(SYS_ERR_QUIT, NULL);
#endif
Bgp_SetErrFunc(Bgpgrep_HandleBgpError, NULL);
// Initial program setup
Bgpgrep_SetupCommandLine(argv[0]);
int optind = Com_ArgParse(argc, argv, options, /*flags=*/ARG_NOREORD);
if (optind == OPT_HELP)
return EXIT_SUCCESS; // only called to get help...
if (optind < 0)
return EXIT_FAILURE; // can't parse command line
Bgpgrep_ApplyProgramOptions();
Bgpgrep_Init(); // initialize according to command line
// Done with options
argc -= optind;
argv += optind;
// Extrapolate files arguments from argv
char **files = argv;
int nfiles = CountFileArguments(argc, argv);
// Move to filtering rules and compile them
argv += nfiles;
argc -= nfiles;
Bgpgrep_CompileVmProgram(argc, argv);
if (nfiles == 0) {
// If no FILES are provided, read from stdin
static const char *const stdinFile[] = { "-" };
files = (char **) stdinFile;
nfiles = ARRAY_SIZE(stdinFile);
}
// Files processing
volatile int i = 0;
setjmp_fast(S.dropFileFrame); // NOTE: The ONLY place where this is set
while (i < nfiles) {
Bgpgrep_ProcessMrtDump(files[i++]);
// Don't need PEER_INDEX_TABLE anymore
Bgp_ClearMrt(&S.peerIndex);
S.hasPeerIndex = FALSE;
}
return Bgpgrep_CleanupAndExit();
}

214
tools/bgpgrep/bgpgrep_asmatch.c Executable file
View File

@ -0,0 +1,214 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* \file bgpgrep_asmatch.c
*
* AS_PATH regular expressions compilation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgpgrep_local.h"
#include "sys/con.h"
#include "sys/endian.h"
#include "lexer.h"
#include "strlib.h"
#include <stdlib.h>
#include <string.h>
#define MAXEXCERPTLEN 32
#define GROWSTEP 128
typedef struct {
unsigned cap, len;
Asn expr[FLEX_ARRAY];
} Asnbuf;
// Return TRUE if `asn` may be followed by '+', '?' or '*' wildcards.
static Boolean WildcardAllowed(Asn asn)
{
return asn != ASN_START && asn != ASN_END && asn != ASN_ALT && asn != ASN_NEWGRP;
}
static void AddAsnToList(Asnbuf **pbuf, Asn asn)
{
Asnbuf *buf = *pbuf;
if (!buf || buf->len == buf->cap) {
size_t oldcap = buf ? buf->cap : 0;
size_t newcap = oldcap + GROWSTEP;
buf = (Asnbuf *) realloc(buf, offsetof(Asnbuf, expr[newcap]));
if (!buf)
Bgpgrep_Fatal("Memory allocation failure");
buf->cap = newcap;
buf->len = oldcap;
*pbuf = buf;
}
buf->expr[buf->len++] = asn;
}
Sint32 BgpgrepC_BakeAsRegexp(void)
{
// Generate expression name
char expnam[1 + 16 + MAXEXCERPTLEN + 1 + 1], *ptr = expnam;
const char *match = BgpgrepC_ExpectAnyToken();
*ptr++ = '<';
strcpy(ptr, "AS PATH regexp: "); ptr += 16;
size_t n = Df_strncpyz(ptr, match, MAXEXCERPTLEN);
if (n > MAXEXCERPTLEN) strcpy(ptr + MAXEXCERPTLEN-3, "...");
ptr += n;
*ptr++ = '>';
*ptr = '\0';
// Setup lexer
Lex p;
Tok tok;
memset(&p, 0, sizeof(p));
if (!S.noColor)
SetLexerFlags(&p, L_COLORED);
BeginLexerSession(&p, expnam, /*line=*/1);
SetLexerTextn(&p, match, n);
SetLexerErrorFunc(&p, LEX_QUIT, LEX_WARN, NULL);
// Compile expression
Asnbuf *buf = NULL;
int notcount = 0;
int nparens = 0;
Boolean seenAsn = FALSE, seenBol = FALSE, seenEol = FALSE;
Boolean wUnmatchable = TRUE;
Asn asn;
while (Lex_ReadToken(&p, &tok)) {
// Special handling for negation (don't produce any ASN)
if (strcmp(tok.text, "!") == 0) {
notcount++;
continue;
}
// Any other case
if (strcmp(tok.text, "(") == 0) {
if (notcount > 0)
LexerError(&p, "Illegal '(' after '!' ASN modifier");
AddAsnToList(&buf, ASN_NEWGRP);
seenBol = seenAsn = seenEol = FALSE;
wUnmatchable = TRUE;
nparens++;
} else if (strcmp(tok.text, ")") == 0) {
if (notcount > 0)
LexerError(&p, "Dangling '!' at end of group");
if (nparens == 0)
LexerError(&p, "Stray ')' in match expression");
AddAsnToList(&buf, ASN_ENDGRP);
seenBol = seenAsn = seenEol = FALSE;
wUnmatchable = TRUE;
nparens--;
} else if (strcmp(tok.text, "^") == 0) {
if (notcount > 0)
LexerError(&p, "Illegal '^' after '!' ASN modifier");
if (seenBol)
LexerWarning(&p, "Duplicate '^'");
if (seenAsn && wUnmatchable) {
LexerWarning(&p, "'^' following ASN term never matches");
wUnmatchable = FALSE;
}
if (seenEol && wUnmatchable) {
LexerWarning(&p, "'^' following '$' never matches");
wUnmatchable = FALSE;
}
AddAsnToList(&buf, ASN_START);
seenBol = TRUE;
} else if (strcmp(tok.text, "$") == 0) {
if (notcount > 0)
LexerError(&p, "Illegal '$' after '!' ASN modifier");
if (seenEol)
LexerWarning(&p, "Duplicate '$'");
AddAsnToList(&buf, ASN_END);
seenEol = TRUE;
} else if (strcmp(tok.text, "|") == 0) {
if (notcount > 0)
LexerError(&p, "Alternative not allowed after '!'");
AddAsnToList(&buf, ASN_ALT);
seenBol = seenAsn = seenEol = FALSE;
wUnmatchable = TRUE;
} else {
if (strcmp(tok.text, ".") == 0) {
// Any match, make sure ! wasn't used
if (notcount > 0)
LexerError(&p, "Illegal use of '!' combined with '.' operator");
asn = ASN_ANY;
} else {
// Read actual numeric ASN
Lex_UngetToken(&p, &tok);
long long num = Lex_ParseInt(&p, /*optionalSign=*/FALSE);
if (num > 0xffffffff)
LexerError(&p, "%lld: ASN is out of range", num);
asn = ASN32BIT(beswap32(num));
}
if (notcount & 1)
asn = ASNNOT(asn); // negate match
if (seenEol && wUnmatchable) {
LexerWarning(&p, "ASN term following '$' never matches");
wUnmatchable = FALSE;
}
AddAsnToList(&buf, asn);
notcount = 0;
seenAsn = TRUE;
}
// Allow *, ? and + repetition operators
if (WildcardAllowed(buf->expr[buf->len-1])) {
if (Lex_CheckToken(&p, "*"))
AddAsnToList(&buf, ASN_STAR);
else if (Lex_CheckToken(&p, "+"))
AddAsnToList(&buf, ASN_PLUS);
else if (Lex_CheckToken(&p, "?"))
AddAsnToList(&buf, ASN_QUEST);
}
}
if (nparens != 0)
LexerError(&p, "Missing ')' at end of match expression");
if (notcount != 0)
LexerError(&p, "Dangling '!' at end of match expression");
void *regexp = Bgp_VmCompileAsMatch(&S.vm, buf->expr, buf->len);
if (!regexp)
Bgpgrep_Fatal("AS PATH Regexp compilation failed");
Sint32 kidx = BGP_VMSETKA(&S.vm, Bgp_VmNewk(&S.vm), regexp);
if (kidx == -1)
Bgpgrep_Fatal("Maximum BGP VM variables limit hit: please try to simplify the filtering expression");
free(buf);
return kidx;
}

View File

@ -0,0 +1,237 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* \file bgpgrep_communities.c
*
* COMMUNITY matching expressions parsing.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgpgrep_local.h"
#include "sys/endian.h"
#include "sys/fs.h"
#include "sys/sys.h"
#include "lexer.h"
#include "numlib.h"
#include "strlib.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define FATALF(fmt, ...) ((void) ((lexp) ? \
LexerError(lexp, fmt, __VA_ARGS__) : \
Bgpgrep_Fatal("%s: " fmt, BgpgrepC_CurTerm(), __VA_ARGS__)))
typedef struct {
size_t cap, len;
Bgpmatchcomm c[FLEX_ARRAY];
} Matchcommlist;
#define MINLISTSIZ 256
#define LISTGROWSIZ 128
static Boolean ParseWellKnownCommunity(Bgpmatchcomm *dest, const char *s)
{
if (Df_stricmp(s, "PLANNED_SHUT") == 0)
dest->c.code = BGP_COMMUNITY_PLANNED_SHUT;
else if (Df_stricmp(s, "ACCEPT_OWN") == 0)
dest->c.code = BGP_COMMUNITY_ACCEPT_OWN;
else if (Df_stricmp(s, "ROUTE_FILTER_TRANSLATED_V4") == 0)
dest->c.code = BGP_COMMUNITY_ROUTE_FILTER_TRANSLATED_V4;
else if (Df_stricmp(s, "ROUTE_FILTER_V4") == 0)
dest->c.code = BGP_COMMUNITY_ROUTE_FILTER_V4;
else if (Df_stricmp(s, "ROUTE_FILTER_TRANSLATED_V6") == 0)
dest->c.code = BGP_COMMUNITY_ROUTE_FILTER_TRANSLATED_V6;
else if (Df_stricmp(s, "ROUTE_FILTER_V6") == 0)
dest->c.code = BGP_COMMUNITY_ROUTE_FILTER_V6;
else if (Df_stricmp(s, "LLGR_STALE") == 0)
dest->c.code = BGP_COMMUNITY_LLGR_STALE;
else if (Df_stricmp(s, "NO_LLGR") == 0)
dest->c.code = BGP_COMMUNITY_NO_LLGR;
else if (Df_stricmp(s, "ACCEPT_OWN_NEXTHOP") == 0)
dest->c.code = BGP_COMMUNITY_ACCEPT_OWN_NEXTHOP;
else if (Df_stricmp(s, "STANDBY_PE") == 0)
dest->c.code = BGP_COMMUNITY_STANDBY_PE;
else if (Df_stricmp(s, "BLACKHOLE") == 0)
dest->c.code = BGP_COMMUNITY_BLACKHOLE;
else if (Df_stricmp(s, "NO_EXPORT") == 0)
dest->c.code = BGP_COMMUNITY_NO_EXPORT;
else if (Df_stricmp(s, "NO_ADVERTISE") == 0)
dest->c.code = BGP_COMMUNITY_NO_ADVERTISE;
else if (Df_stricmp(s, "NO_EXPORT_SUBCONFED") == 0)
dest->c.code = BGP_COMMUNITY_NO_EXPORT_SUBCONFED;
else if (Df_stricmp(s, "NO_PEER") == 0)
dest->c.code = BGP_COMMUNITY_NO_PEER;
else return FALSE;
return TRUE;
}
static Bgpmatchcomm *AppendMatch(Matchcommlist **dest)
{
Matchcommlist *list = *dest;
if (list->len == list->cap) {
list->cap += LISTGROWSIZ;
list = (Matchcommlist *) realloc(list, offsetof(Matchcommlist, c[list->cap]));
if (!list)
Sys_OutOfMemory();
*dest = list;
}
return &list->c[list->len++];
}
static void ParseCommunity(Matchcommlist **dest, const char *s, Lex *lexp)
{
char *p = (char *) s;
unsigned long long n;
NumConvRet res;
Bgpmatchcomm *m = AppendMatch(dest);
memset(m, 0, sizeof(*m));
if (ParseWellKnownCommunity(m, s))
return; // got a well known community
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
// Hexadecimal representation
n = Atoull(p, &p, 16, &res);
if (res != NCVENOERR || n > 0xffffffffuLL || *p != '\0')
FATALF("%s: Hexadecimal COMMUNITY code is out of range", s);
m->c.code = beswap32(n);
return;
}
// HIVALUE:LOVALUE (either one may be *)
if (*p == '*') {
m->maskHi = TRUE;
p++;
} else {
n = Atoull(p, &p, 10, &res);
if (res != NCVENOERR || n > 0xffffuLL)
FATALF("%s: Expected numeric value or '*' for COMMUNITY high order bytes", s);
m->c.hi = beswap16(n);
}
if (*p != ':')
FATALF("%s: Expecting ':' after COMMUNITY high order bytes", s);
p++;
if (*p == '*') {
m->maskLo = TRUE;
p++;
} else {
n = Atoull(p, &p, 10, &res);
if (res != NCVENOERR || n > 0xffffuLL)
FATALF("%s: Expected numeric value or '*' for COMMUNITY low order bytes", s);
m->c.lo = beswap16(n);
}
if (*p != '\0')
FATALF("%s: Bad COMMUNITY, should be an hexadecimal code or 'hivalue:lowvalue'", s);
if (m->maskHi && m->maskLo)
FATALF("%s: At least one between high order or low order bytes must be defined", s);
}
static Judgement ParseCommunityFile(Matchcommlist **dest, const char *filename)
{
Lex lex;
Tok tok;
// Map file in memory
Fildes fd = Sys_Fopen(filename, FM_READ, FH_SEQ);
if (fd == FILDES_BAD)
return NG;
Sint64 fsiz = Sys_FileSize(fd); // NOTE: aborts on failure
assert(fsiz >= 0);
char *text = (char *) malloc(fsiz);
if (!text)
Sys_OutOfMemory();
Sint64 n = Sys_Fread(fd, text, fsiz);
if (n != fsiz)
Bgpgrep_Fatal("Short read from file '%s'", filename);
Sys_Fclose(fd);
// Setup Lex and start reading strings
memset(&lex, 0, sizeof(lex));
BeginLexerSession(&lex, filename, /*line=*/1);
SetLexerTextn(&lex, text, fsiz);
if (!S.noColor)
SetLexerFlags(&lex, L_COLORED);
SetLexerErrorFunc(&lex, LEX_QUIT, LEX_WARN, NULL);
char buf[MAXTOKLEN + 1 + MAXTOKLEN + 1];
while (Lex_ReadToken(&lex, &tok)) {
strcpy(buf, tok.text);
Lex_MatchTokenType(&lex, &tok, TT_PUNCT, P_COLON);
strcat(buf, tok.text);
Lex_MatchAnyToken(&lex, &tok);
strcat(buf, tok.text);
ParseCommunity(dest, buf, &lex);
}
free(text);
return OK;
}
Sint32 BgpgrepC_ParseCommunity(BgpVmOpt opt)
{
const char *tok = BgpgrepC_ExpectAnyToken();
if (strcmp(tok, "()") == 0)
return -1; // empty list always fails
Matchcommlist *list = (Matchcommlist *) malloc(offsetof(Matchcommlist,
c[MINLISTSIZ]));
if (!list)
Sys_OutOfMemory();
list->cap = MINLISTSIZ;
list->len = 0;
if (strcmp(tok, "(") == 0) {
// Explicit inline prefix list, directly inside command line
while (TRUE) {
tok = BgpgrepC_ExpectAnyToken();
if (strcmp(tok, ")") == 0)
break;
ParseCommunity(&list, tok, /*lexp=*/NULL);
}
} else {
// Anything else is interpreted as either:
// - A file argument, containing a space-separated COMMUNITY match list
// - A single match, if the file is not found
// (shorthand for "( <community> )" )
if (ParseCommunityFile(&list, tok) != OK)
ParseCommunity(&list, tok, /*lexp=*/NULL);
}
void *match = Bgp_VmCompileCommunityMatch(&S.vm, list->c, list->len, opt);
if (!match)
Bgpgrep_Fatal("%s: %s", BgpgrepC_CurTerm(), Bgp_ErrorString(S.vm.errCode));
free(list);
Sint32 kidx = BGP_VMSETKA(&S.vm, Bgp_VmNewk(&S.vm), match);
if (kidx == -1)
Bgpgrep_Fatal("Too many community match expressions");
return kidx;
}

744
tools/bgpgrep/bgpgrep_compile.c Executable file
View File

@ -0,0 +1,744 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* \file bgpgrep_compile.c
*
* Filtering expression predicate compilation to VM bytecode.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgpgrep_local.h"
#include "sys/con.h"
#include "sys/endian.h"
#include "sys/fs.h"
#include "sys/sys.h"
#include "lexer.h"
#include "numlib.h"
#include "strlib.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#define MAXCODE 8192
#define MAXLEAFLEN 8
// Expression types, in order of ascending precedence
typedef enum {
OP_NONE, // no operation
OP_OR, // -or, -o
OP_AND, // -and, -a (implicit when 2 consecutive expressions are found)
OP_NOT, // -not, !
OP_LEAF // instruction block, leaf node
} Expropc;
typedef Uint16 Expridx;
typedef struct {
Expropc opc;
union {
// Expression node
struct {
Expridx l, r; // left-right subnodes
} n;
// Expression leaf
struct {
Uint8 nc;
Bgpvmbytec c[MAXLEAFLEN];
} leaf;
};
} Exprop;
typedef struct {
// Argument list
int argc;
int argidx;
char **argv;
char *curterm;
Sint32 loopsFn;
Sint32 peerMatchFn;
Sint32 timestampCmpFn;
Uint16 ncode;
Boolean8 wasImplicitAnd;
Exprop code[MAXCODE];
} BgpgrepC;
static BgpgrepC C;
static Expridx BgpgrepC_ParseExpression(void); // Forward decl
static char *BgpgrepC_LastToken(void)
{
assert(C.argidx > 0);
return C.argv[C.argidx - 1];
}
static void BgpgrepC_UngetToken(void)
{
assert(C.argidx > 0 || C.wasImplicitAnd);
if (C.wasImplicitAnd)
C.wasImplicitAnd = FALSE;
else
C.argidx--;
}
static Boolean IsEndOfParse(void)
{
return C.argidx == C.argc && !C.wasImplicitAnd;
}
char *BgpgrepC_GetToken(void)
{
C.wasImplicitAnd = FALSE;
return (C.argidx < C.argc) ? C.argv[C.argidx++] : NULL;
}
char *BgpgrepC_CurTerm(void)
{
return C.curterm;
}
char *BgpgrepC_ExpectAnyToken(void)
{
if (C.argidx >= C.argc)
Bgpgrep_Fatal("Unexpected match expression end after '%s'", BgpgrepC_LastToken());
return C.argv[C.argidx++];
}
char *BgpgrepC_ExpectToken(const char *what)
{
if (C.argidx >= C.argc)
Bgpgrep_Fatal("Unexpected match expression end after '%s', while expecting '%s'", BgpgrepC_LastToken(), what);
char *tok = C.argv[C.argidx++];
if (strcmp(tok, what) != 0)
Bgpgrep_Fatal("Unexpected token '%s' while expecting '%s'", tok, what);
return tok;
}
static Expridx PushOp(Expridx l, Expropc opc, Expridx r)
{
if (C.ncode >= MAXCODE)
Bgpgrep_Fatal("At '%s': Expression operations limit reached, please simplify the input expression", BgpgrepC_LastToken());
Expridx idx = C.ncode++;
Exprop *op = &C.code[idx];
op->opc = opc;
op->n.l = l;
op->n.r = r;
return idx;
}
static Expridx PushLeaf(const Bgpvmbytec *c, size_t n)
{
assert(n <= MAXLEAFLEN);
if (C.ncode >= MAXCODE)
Bgpgrep_Fatal("At '%s': Expression operations limit reached, please simplify the input expression", BgpgrepC_LastToken());
Expridx idx = C.ncode++;
Exprop *op = &C.code[idx];
op->opc = OP_LEAF;
op->leaf.nc = n;
memcpy(op->leaf.c, c, n * sizeof(*op->leaf.c));
return idx;
}
static BgpType ParseBgpType(void)
{
const char *tok = BgpgrepC_ExpectAnyToken();
if (Df_stricmp(tok, "OPEN") == 0)
return BGP_OPEN;
if (Df_stricmp(tok, "UPDATE") == 0)
return BGP_UPDATE;
if (Df_stricmp(tok, "KEEPALIVE") == 0)
return BGP_KEEPALIVE;
if (Df_stricmp(tok, "NOTIFICATION") == 0)
return BGP_NOTIFICATION;
if (Df_stricmp(tok, "ROUTE_REFRESH") == 0)
return BGP_ROUTE_REFRESH;
if (Df_stricmp(tok, "CLOSE") == 0)
return BGP_CLOSE;
Bgpgrep_Fatal("-type: Unknown BGP message type: %s", tok);
}
static BgpAttrCode ParseBgpAttr(void)
{
const char *tok = BgpgrepC_ExpectAnyToken();
if (Df_stricmp(tok, "ORIGIN") == 0) return BGP_ATTR_ORIGIN;
if (Df_stricmp(tok, "AS_PATH") == 0) return BGP_ATTR_AS_PATH;
if (Df_stricmp(tok, "NEXT_HOP") == 0) return BGP_ATTR_NEXT_HOP;
if (Df_stricmp(tok, "MULTI_EXIT_DISC") == 0) return BGP_ATTR_MULTI_EXIT_DISC;
if (Df_stricmp(tok, "LOCAL_PREFI") == 0) return BGP_ATTR_LOCAL_PREF;
if (Df_stricmp(tok, "ATOMIC_AGGREGATE") == 0) return BGP_ATTR_ATOMIC_AGGREGATE;
if (Df_stricmp(tok, "AGGREGATOR") == 0) return BGP_ATTR_AGGREGATOR;
if (Df_stricmp(tok, "COMMUNITY") == 0) return BGP_ATTR_COMMUNITY;
if (Df_stricmp(tok, "ORIGINATOR_ID") == 0) return BGP_ATTR_ORIGINATOR_ID;
if (Df_stricmp(tok, "CLUSTER_LIST") == 0) return BGP_ATTR_CLUSTER_LIST;
if (Df_stricmp(tok, "DPA") == 0) return BGP_ATTR_DPA;
if (Df_stricmp(tok, "ADVERTISER") == 0) return BGP_ATTR_ADVERTISER;
if (Df_stricmp(tok, "RCID_PATH_CLUSTER_ID") == 0) return BGP_ATTR_RCID_PATH_CLUSTER_ID;
if (Df_stricmp(tok, "MP_REACH_NLRI") == 0) return BGP_ATTR_MP_REACH_NLRI;
if (Df_stricmp(tok, "MP_UNREACH_NLRI") == 0) return BGP_ATTR_MP_UNREACH_NLRI;
if (Df_stricmp(tok, "EXTENDED_COMMUNITY") == 0) return BGP_ATTR_EXTENDED_COMMUNITY;
if (Df_stricmp(tok, "AS4_PATH") == 0) return BGP_ATTR_AS4_PATH;
if (Df_stricmp(tok, "AS4_AGGREGATOR") == 0) return BGP_ATTR_AS4_AGGREGATOR;
if (Df_stricmp(tok, "SAFI_SSA") == 0) return BGP_ATTR_SAFI_SSA;
if (Df_stricmp(tok, "CONNECTOR") == 0) return BGP_ATTR_CONNECTOR;
if (Df_stricmp(tok, "AS_PATHLIMIT") == 0) return BGP_ATTR_AS_PATHLIMIT;
if (Df_stricmp(tok, "PMSI_TUNNEL") == 0) return BGP_ATTR_PMSI_TUNNEL;
if (Df_stricmp(tok, "TUNNEL_ENCAPSULATION") == 0) return BGP_ATTR_TUNNEL_ENCAPSULATION;
if (Df_stricmp(tok, "TRAFFIC_ENGINEERING") == 0) return BGP_ATTR_TRAFFIC_ENGINEERING;
if (Df_stricmp(tok, "IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITY") == 0) return BGP_ATTR_IPV6_ADDRESS_SPECIFIC_EXTENDED_COMMUNITY;
if (Df_stricmp(tok, "AIGP") == 0) return BGP_ATTR_AIGP;
if (Df_stricmp(tok, "PE_DISTINGUISHER_LABELS") == 0) return BGP_ATTR_PE_DISTINGUISHER_LABELS;
if (Df_stricmp(tok, "ENTROPY_LEVEL_CAPABILITY") == 0) return BGP_ATTR_ENTROPY_LEVEL_CAPABILITY;
if (Df_stricmp(tok, "LS") == 0) return BGP_ATTR_LS;
if (Df_stricmp(tok, "LARGE_COMMUNITY") == 0) return BGP_ATTR_LARGE_COMMUNITY;
if (Df_stricmp(tok, "BGPSEC_PATH") == 0) return BGP_ATTR_BGPSEC_PATH;
if (Df_stricmp(tok, "COMMUNITY_CONTAINER") == 0) return BGP_ATTR_COMMUNITY_CONTAINER;
if (Df_stricmp(tok, "PREFIX_SID") == 0) return BGP_ATTR_PREFIX_SID;
if (Df_stricmp(tok, "SET") == 0) return BGP_ATTR_SET;
char *p;
NumConvRet ret;
unsigned code = Atou(tok, &p, /*base=*/0, &ret);
if (ret != NCVENOERR || code > 0xff || *p != '\0')
Bgpgrep_Fatal("-attr: Bad attribute '%s'", tok);
return code;
}
static void AddPrefixListToTrie(Triepair *dest,
const Pfxlist *list,
NetpfxType type)
{
memset(dest, 0, sizeof(*dest));
for (const Pfxnode *n = list->head[type]; n; n = n->next[type]) {
switch (n->pfx.afi) {
case AFI_IP:
if (!dest->v4) dest->v4 = BgpgrepC_NewTrie(AFI_IP);
if (!Pat_Insert(&dest->v4->trie, PLAINPFX(&n->pfx)))
Sys_OutOfMemory();
break;
case AFI_IP6:
if (!dest->v6) dest->v6 = BgpgrepC_NewTrie(AFI_IP6);
if (!Pat_Insert(&dest->v6->trie, PLAINPFX(&n->pfx)))
Sys_OutOfMemory();
break;
default: UNREACHABLE; break;
}
}
}
static Expridx ParsePrefixOp(Bgpvmopc opc)
{
Pfxlist list;
Bgpvmbytec c[MAXLEAFLEN];
size_t nc = 0;
Triepair t;
BgpgrepC_ParsePrefixList(&list);
if (list.isEmpty)
c[nc++] = BGP_VMOP(BGP_VMOP_LOADU, 0); // empty list = always FAIL
if (list.head[WITHDRAWN]) {
AddPrefixListToTrie(&t, &list, WITHDRAWN);
c[nc++] = t.v4 ? BGP_VMOP(BGP_VMOP_LOADK, t.v4->kidx) : BGP_VMOP_LOADN;
c[nc++] = t.v6 ? BGP_VMOP(BGP_VMOP_LOADK, t.v6->kidx) : BGP_VMOP_LOADN;
c[nc++] = BGP_VMOP(opc, BGP_VMOPA_ALL_WITHDRAWN);
}
if (list.head[ANNOUNCE]) {
if (!list.areListsMatching) AddPrefixListToTrie(&t, &list, ANNOUNCE);
if (list.head[WITHDRAWN]) c[nc++] = BGP_VMOP(BGP_VMOP_JNZ, 3);
c[nc++] = t.v4 ? BGP_VMOP(BGP_VMOP_LOADK, t.v4->kidx) : BGP_VMOP_LOADN;
c[nc++] = t.v6 ? BGP_VMOP(BGP_VMOP_LOADK, t.v6->kidx) : BGP_VMOP_LOADN;
c[nc++] = BGP_VMOP(opc, BGP_VMOPA_ALL_NLRI);
}
Expridx idx = PushLeaf(c, nc);
BgpgrepC_FreePrefixList(&list);
return idx;
}
static Expridx GetTerm(void)
{
Bgpvmbytec c[MAXLEAFLEN];
size_t n = 0;
C.curterm = BgpgrepC_ExpectAnyToken();
if (strcmp(C.curterm, "(") == 0) {
Expridx e = BgpgrepC_ParseExpression();
BgpgrepC_ExpectToken(")");
return e;
} else if (strcmp(C.curterm, "!") == 0 || strcmp(C.curterm, "-not") == 0) {
return PushOp(0, OP_NOT, GetTerm());
} else if (strcmp(C.curterm, "-type") == 0) {
BgpType type = ParseBgpType();
c[n++] = BGP_VMOP(BGP_VMOP_CHKT, type);
return PushLeaf(c, n);
} else if (strcmp(C.curterm, "-attr") == 0) {
BgpAttrCode code = ParseBgpAttr();
c[n++] = BGP_VMOP(BGP_VMOP_CHKA, code);
return PushLeaf(c, n);
} else if (strcmp(C.curterm, "-aspath") == 0) {
Sint32 kidx = BgpgrepC_BakeAsRegexp();
c[n++] = BGP_VMOP(BGP_VMOP_LOADK, kidx);
c[n++] = BGP_VMOP_FASMTC;
return PushLeaf(c, n);
} else if (strcmp(C.curterm, "-peer") == 0) {
Sint32 kidx = BgpgrepC_ParsePeerExpression();
if (kidx >= 0) {
// Non-empty list, compile to a call
c[n++] = BGP_VMOP(BGP_VMOP_LOADK, kidx);
c[n++] = BGP_VMOP(BGP_VMOP_CALL, C.peerMatchFn);
} else {
// Empty list, always fails
c[n++] = BGP_VMOP(BGP_VMOP_LOADU, FALSE);
}
return PushLeaf(c, n);
} else if (strcmp(C.curterm, "-loops") == 0) {
c[n++] = BGP_VMOP(BGP_VMOP_CALL, C.loopsFn);
return PushLeaf(c, n);
} else if (strcmp(C.curterm, "-exact") == 0) {
return ParsePrefixOp(BGP_VMOP_EXCT);
} else if (strcmp(C.curterm, "-supernet") == 0) {
return ParsePrefixOp(BGP_VMOP_SUPN);
} else if (strcmp(C.curterm, "-subnet") == 0) {
return ParsePrefixOp(BGP_VMOP_SUBN);
} else if (strcmp(C.curterm, "-related") == 0) {
return ParsePrefixOp(BGP_VMOP_RELT);
} else if (strcmp(C.curterm, "-timestamp") == 0) {
Sint32 kidx = BgpgrepC_ParseTimestampExpression();
c[n++] = BGP_VMOP(BGP_VMOP_LOADK, kidx);
c[n++] = BGP_VMOP(BGP_VMOP_CALL, C.timestampCmpFn);
return PushLeaf(c, n);
} else if (strcmp(C.curterm, "-communities") == 0) {
Sint32 kidx = BgpgrepC_ParseCommunity(BGP_VMOPT_ASSUME_COMTCH);
if (kidx >= 0) {
// Non-empty list, emit COMTCH
c[n++] = BGP_VMOP(BGP_VMOP_LOADK, kidx);
c[n++] = BGP_VMOP_COMTCH;
} else {
// Empty community always fails
c[n++] = BGP_VMOP(BGP_VMOP_LOADU, FALSE);
}
return PushLeaf(c, n);
} else if (strcmp(C.curterm, "-all-communities") == 0) {
Sint32 kidx = BgpgrepC_ParseCommunity(BGP_VMOPT_ASSUME_ACOMTC);
if (kidx >= 0) {
// Non-empt list, emit ACOMTC
c[n++] = BGP_VMOP(BGP_VMOP_LOADK, kidx);
c[n++] = BGP_VMOP_ACOMTC;
} else {
// Empty list always succeeds
c[n++] = BGP_VMOP(BGP_VMOP_LOADU, TRUE);
}
return PushLeaf(c, n);
} else {
Bgpgrep_Fatal("Invalid expression term '%s'", C.curterm);
return 0;
}
}
static Expropc GetOp(void)
{
const char *tok = BgpgrepC_GetToken();
Expropc opc = OP_NONE;
if (tok) {
if (strcmp(tok, "-and") == 0 || strcmp(tok, "-a") == 0) {
opc = OP_AND;
} else if (strcmp(tok, "-or") == 0 || strcmp(tok, "-o") == 0) {
opc = OP_OR;
} else {
// Implicitly assume AND with anything else that follows
BgpgrepC_UngetToken();
// Closing ) appear due to expression grouping
// e.g. ( -type ORIGIN -or -type AGGREGATOR )
//
// In this case we should return OP_NONE to signal
// expression and, propagating out of ParseExpression()
if (strcmp(tok, ")") != 0) {
C.wasImplicitAnd = TRUE;
opc = OP_AND;
}
}
}
return opc;
}
static Expridx BgpgrepC_ParseExpressionRecurse(Expridx l, int prio)
{
while (TRUE) {
Expropc opc = GetOp();
if (opc == OP_NONE)
break;
if ((int) opc < prio) {
BgpgrepC_UngetToken();
break;
}
Expridx r = GetTerm();
while (TRUE) {
Expropc nextOpc = GetOp();
if (nextOpc == OP_NONE)
break;
BgpgrepC_UngetToken();
if (nextOpc <= opc)
break;
r = BgpgrepC_ParseExpressionRecurse(r, nextOpc);
}
l = PushOp(l, opc, r);
}
return l;
}
static Expridx BgpgrepC_ParseExpression(void)
{
Expridx l = GetTerm();
return BgpgrepC_ParseExpressionRecurse(l, OP_NONE);
}
static Boolean IsNestedBlock(Expropc outer, Expropc inner)
{
assert(inner == OP_AND || inner == OP_OR);
return outer != OP_NONE && inner != outer;
}
static Expropc BgpgrepC_CompileRecurse(Expridx idx, Expropc opc)
{
Boolean nestedBlock;
const Exprop *op = &C.code[idx];
switch (op->opc) {
case OP_AND:
nestedBlock = IsNestedBlock(opc, op->opc);
if (nestedBlock)
Bgp_VmEmit(&S.vm, BGP_VMOP_BLK);
BgpgrepC_CompileRecurse(op->n.l, op->opc);
if (C.code[op->n.l].opc != OP_AND) {
Bgp_VmEmit(&S.vm, BGP_VMOP_NOT);
Bgp_VmEmit(&S.vm, BGP_VMOP_CFAIL);
}
BgpgrepC_CompileRecurse(op->n.r, op->opc);
if (C.code[op->n.r].opc != OP_AND) {
Bgp_VmEmit(&S.vm, BGP_VMOP_NOT);
Bgp_VmEmit(&S.vm, BGP_VMOP_CFAIL);
}
if (nestedBlock) {
Bgp_VmEmit(&S.vm, BGP_VMOP(BGP_VMOP_LOADU, TRUE));
Bgp_VmEmit(&S.vm, BGP_VMOP_CPASS);
Bgp_VmEmit(&S.vm, BGP_VMOP_ENDBLK);
}
break;
case OP_OR:
nestedBlock = IsNestedBlock(opc, op->opc);
if (nestedBlock)
Bgp_VmEmit(&S.vm, BGP_VMOP_BLK);
BgpgrepC_CompileRecurse(op->n.l, op->opc);
if (C.code[op->n.l].opc != OP_OR)
Bgp_VmEmit(&S.vm, BGP_VMOP_CPASS);
BgpgrepC_CompileRecurse(op->n.r, op->opc);
if (C.code[op->n.r].opc != OP_OR)
Bgp_VmEmit(&S.vm, BGP_VMOP_CPASS);
if (nestedBlock) {
Bgp_VmEmit(&S.vm, BGP_VMOP(BGP_VMOP_LOADU, TRUE));
Bgp_VmEmit(&S.vm, BGP_VMOP_CFAIL);
Bgp_VmEmit(&S.vm, BGP_VMOP_ENDBLK);
}
break;
case OP_NOT:
BgpgrepC_CompileRecurse(op->n.r, OP_NOT);
Bgp_VmEmit(&S.vm, BGP_VMOP_NOT);
break;
case OP_LEAF:
for (unsigned i = 0; i < op->leaf.nc; i++)
Bgp_VmEmit(&S.vm, op->leaf.c[i]);
break;
default: UNREACHABLE; break;
}
return op->opc;
}
static void BgpgrepC_Compile(Expridx startIdx)
{
Expropc opc = BgpgrepC_CompileRecurse(startIdx, OP_NONE);
// Compile the very last result
switch (opc) {
case OP_OR:
Bgp_VmEmit(&S.vm, BGP_VMOP(BGP_VMOP_LOADU, TRUE));
Bgp_VmEmit(&S.vm, BGP_VMOP_CFAIL);
break;
case OP_AND:
Bgp_VmEmit(&S.vm, BGP_VMOP(BGP_VMOP_LOADU, TRUE));
Bgp_VmEmit(&S.vm, BGP_VMOP_CPASS);
break;
case OP_NOT:
case OP_LEAF:
Bgp_VmEmit(&S.vm, BGP_VMOP_CPASS);
Bgp_VmEmit(&S.vm, BGP_VMOP(BGP_VMOP_LOADU, TRUE));
Bgp_VmEmit(&S.vm, BGP_VMOP_CFAIL);
break;
default: UNREACHABLE; break;
}
}
static Boolean IsLoadNz(Bgpvmbytec bytec)
{
switch (BGP_VMOPC(bytec)) {
case BGP_VMOP_LOAD:
case BGP_VMOP_LOADU: return BGP_VMOPARG(bytec) != 0;
default: return FALSE;
}
}
static Boolean IsLoadZ(Bgpvmbytec bytec)
{
switch (BGP_VMOPC(bytec)) {
case BGP_VMOP_LOAD:
case BGP_VMOP_LOADU: return BGP_VMOPARG(bytec) == 0;
default: return FALSE;
}
}
static void BgpgrepC_Optimize(void)
{
Uint32 i, j, n;
// Perform trivial peephole optimization
Uint32 wi[4];
Bgpvmbytec w[4];
Boolean changed;
i = 0;
while (i < S.vm.progLen) { // NOTE: don't care for END
if (S.vm.prog[i] == BGP_VMOP_NOP) {
// Skip initial NOPs
i++;
continue;
}
changed = FALSE;
// Fill peephole window
for (j = 0, n = 0; n < ARRAY_SIZE(w); j++) {
if (i + j >= S.vm.progLen) // NOTE: don't care for END
break;
if (S.vm.prog[i+j] == BGP_VMOP_NOP)
continue; // do not place any NOP inside window
wi[n] = i+j;
w[n] = S.vm.prog[i+j];
n++;
}
// Trivial redundant operation elimination
for (j = 1; j < n; j++) {
// NOT-NOT = NOP
if (w[j] == BGP_VMOP_NOT && w[j-1] == BGP_VMOP_NOT) {
w[j-1] = w[j] = BGP_VMOP_NOP;
changed = TRUE;
continue;
}
// LOADU 0-NOT = LOADU 1
if (IsLoadZ(w[j-1]) && w[j] == BGP_VMOP_NOT) {
w[j-1] = BGP_VMOP_NOP;
w[j] = BGP_VMOP(BGP_VMOP_LOADU, 1);
changed = TRUE;
continue;
}
// LOADU 1-NOT = LOADU 0
if (IsLoadNz(w[j-1]) && w[j] == BGP_VMOP_NOT) {
w[j-1] = BGP_VMOP_NOP;
w[j] = BGP_VMOP(BGP_VMOP_LOADU, 0);
changed = TRUE;
continue;
}
}
if (n == 4) {
// Simplify common CFAIL and CPASS chains introduced by AND/OR blocks
static const Bgpvmbytec ncfncp[] = {
BGP_VMOP_NOT,
BGP_VMOP_CFAIL,
BGP_VMOP(BGP_VMOP_LOADU, TRUE),
BGP_VMOP_CPASS
};
static const Bgpvmbytec ncpncf[] = {
BGP_VMOP_NOT,
BGP_VMOP_CPASS,
BGP_VMOP(BGP_VMOP_LOADU, TRUE),
BGP_VMOP_CFAIL
};
if (memcmp(w, ncfncp, sizeof(ncfncp)) == 0) {
// Move CPASS up
w[0] = BGP_VMOP_NOP;
w[1] = BGP_VMOP_CPASS;
w[2] = BGP_VMOP(BGP_VMOP_LOADU, TRUE);
w[3] = BGP_VMOP_CFAIL;
changed = TRUE;
} else if (memcmp(w, ncpncf, sizeof(ncpncf)) == 0) {
// Move CFAIL up
w[0] = BGP_VMOP_NOP;
w[1] = BGP_VMOP_CFAIL;
w[2] = BGP_VMOP(BGP_VMOP_LOADU, TRUE);
w[3] = BGP_VMOP_CPASS;
changed = TRUE;
}
}
if (changed) {
// Update VM bytecode
for (j = 0; j < n; j++)
S.vm.prog[wi[j]] = w[j];
continue; // another round of peephole optimization
}
// No optimization, slide window
i++;
}
// Eliminate NOPs
for (i = 0, j = 0; i <= S.vm.progLen; i++) {
if (S.vm.prog[i] != BGP_VMOP_NOP)
S.vm.prog[j++] = S.vm.prog[i];
}
S.vm.progLen = j-1;
assert(S.vm.prog[S.vm.progLen] == BGP_VMOP_END);
}
Trielist *BgpgrepC_NewTrie(Afi afi)
{
Trielist *t = (Trielist *) malloc(sizeof(*t));
if (!t)
Sys_OutOfMemory();
memset(&t->trie, 0, sizeof(t->trie));
t->trie.afi = afi;
t->kidx = BGP_VMSETKA(&S.vm, Bgp_VmNewk(&S.vm), &t->trie);
if (t->kidx == -1)
Bgpgrep_Fatal("BGP filter variables limit hit");
t->next = S.trieList;
S.trieList = t;
return t;
}
void Bgpgrep_CompileVmProgram(int argc, char **argv)
{
// Initial compiler setup
C.argc = argc;
C.argidx = 0;
C.argv = argv;
C.ncode = 0;
C.wasImplicitAnd = FALSE;
C.loopsFn = BGP_VMSETFN(&S.vm,
Bgp_VmNewFn(&S.vm),
BgpgrepF_FindAsLoops);
C.peerMatchFn = BGP_VMSETFN(&S.vm,
Bgp_VmNewFn(&S.vm),
BgpgrepF_PeerAddrMatch);
C.timestampCmpFn = BGP_VMSETFN(&S.vm,
Bgp_VmNewFn(&S.vm),
BgpgrepF_TimestampCompare);
assert(C.loopsFn >= 0);
assert(C.peerMatchFn >= 0);
assert(C.timestampCmpFn >= 0);
// Actual compilation
if (C.argc > 0) {
Expridx r = BgpgrepC_ParseExpression();
// Make sure we've consumed the whole expression
if (!IsEndOfParse()) {
const char *last = BgpgrepC_LastToken();
const char *tok = BgpgrepC_GetToken();
if (tok)
Bgpgrep_Fatal("Unexpected '%s' after '%s'", tok, last);
else // should never happen but still...
Bgpgrep_Fatal("Unexpected match expression end after '%s'", last);
}
// Convert to bytecode
BgpgrepC_Compile(r);
BgpgrepC_Optimize();
} else {
// Trivial filter
Bgp_VmEmit(&S.vm, BGP_VMOP(BGP_VMOP_LOADU, TRUE));
Bgp_VmEmit(&S.vm, BGP_VMOP_CPASS);
S.isTrivialFilter = TRUE;
}
if (S.dumpBytecode)
Bgp_VmDumpProgram(&S.vm, STM_CONHN(STDERR), Stm_ConOps);
}

275
tools/bgpgrep/bgpgrep_dump.c Executable file
View File

@ -0,0 +1,275 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* \file bgpgrep_dump.c
*
* BGP message dump logic.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgpgrep_local.h"
#include "sys/con.h"
#include "sys/endian.h"
#include <assert.h>
#include <string.h>
// NOTE: Each dump function is *RESPONSIBLE* to set S.dropEntryFrame
static void FixBgpAttributeTableForRib(Bgpattrtab tab, Boolean isRibv2)
{
// HACK ALERT:
//
// This is an innocent hack to speed up RIBv2 dumps on BGP messages
// that were already rebuilt (e.g. non-trivial filtering was necessary).
//
// The optimization is based on the fact that we know all offsets
// we've calculated inside the rebuilt BGP attribute list are valid up to
// the occurrence of MP_REACH_NLRI or MP_UNREACH_NLRI, whichever comes
// first. Moreover what we haven't found inside the BGP message attribute
// list isn't in the RIB either.
//
// NOTE: A LOT of RIBs also include the MP_UNREACH attribute,
// which is unfortunate. We clear the MP_UNREACH_NLRI attribute during
// filtering (to avoid printing false positives), but it still messes
// with the offsets of the subsequent attributes.
//
// NOTE: There is no chance to get BGP_ATTR_UNKNOWN inside the table,
// when BGP messages are rebuilt their offset table is filled up entirely.
// We don't need thread safety over Bgpattrtab, as bgpgrep is single-threaded.
Sint16 off = tab[bgp_attrTabIdx[BGP_ATTR_MP_UNREACH_NLRI]];
Uint16 maxoff = 0xffffu;
if (off != BGP_ATTR_NOTFOUND)
maxoff = (Uint16) off;
if (isRibv2) {
off = tab[bgp_attrTabIdx[BGP_ATTR_MP_REACH_NLRI]];
if (off != BGP_ATTR_NOTFOUND && maxoff > (Uint16) off)
maxoff = (Uint16) off;
}
if (maxoff == 0xffffu) // no MP_REACH or MP_UNREACH, table is perfectly ok
return;
// Reset any offset after maxoff
for (int i = 0; i < BGP_ATTRTAB_LEN; i++) {
off = tab[i];
if (off == BGP_ATTR_NOTFOUND)
continue;
if ((Uint16) off > maxoff)
tab[i] = BGP_ATTR_UNKNOWN;
}
}
static void OutputBgp4mp(const Mrthdr *hdr, Bgpattrtab tab)
{
S.lenientBgpErrors = TRUE;
S.outFmt->DumpBgp4mp(hdr, STM_CONHN(STDOUT), Stm_ConOps, tab);
S.lenientBgpErrors = FALSE;
}
void BgpgrepD_Bgp4mp(void)
{
const Mrthdr *hdr = MRT_HDR(&S.rec);
const Bgp4mphdr *bgp4mp = Bgp_GetBgp4mpHdr(&S.rec, NULL);
if (BGP4MP_ISSTATECHANGE(hdr->subtype)) {
OutputBgp4mp(hdr, NULL);
goto done;
}
if (!BGP4MP_ISMESSAGE(hdr->subtype))
goto done; // don't care for anything else
// NOTE: Optimizing BGP4MP to avoid message rebuild isn't worth the effort
// Setup for BGP4MP
S.peerAs = BGP4MP_GETPEERADDR(hdr->subtype, &S.peerAddr, bgp4mp);
S.timestampSecs = beswap32(hdr->timestamp);
S.timestampMicrosecs = 0;
if (hdr->subtype == MRT_BGP4MP_ET)
S.timestampMicrosecs = beswap32(((const Mrthdrex *) hdr)->microsecs);
// Dump MRT data
Bgp_UnwrapBgp4mp(&S.rec, &S.msg, /*flags=*/BGPF_UNOWNED);
if (Bgp_VmExec(&S.vm, &S.msg))
OutputBgp4mp(hdr, S.msg.table);
Bgp_ClearMsg(&S.msg);
done:
Bgp_ClearMrt(&S.rec);
}
static void OutputZebra(const Mrthdr *hdr, Bgpattrtab tab)
{
S.lenientBgpErrors = TRUE;
S.outFmt->DumpZebra(hdr, STM_CONHN(STDOUT), Stm_ConOps, tab);
S.lenientBgpErrors = FALSE;
}
void BgpgrepD_Zebra(void)
{
const Mrthdr *hdr = MRT_HDR(&S.rec);
const Zebrahdr *zebra = Bgp_GetZebraHdr(&S.rec, NULL);
if (hdr->subtype == ZEBRA_STATE_CHANGE) {
OutputZebra(hdr, NULL);
goto done;
}
if (!ZEBRA_ISMESSAGE(hdr->subtype))
goto done; // don't care for anything else
if (S.isTrivialFilter) {
// FAST PATH - avoid rebuilding original message
BGP_CLRATTRTAB(S.msg.table);
OutputZebra(hdr, S.msg.table);
goto done;
}
// FILTERING PATH - Setup filter
S.peerAs = ASN16BIT(zebra->peerAs);
S.peerAddr.family = IP4;
S.peerAddr.v4 = zebra->peerAddr;
S.timestampSecs = beswap32(hdr->timestamp);
S.timestampMicrosecs = 0;
// Filter and dump BGP data
Bgp_UnwrapZebra(&S.rec, &S.msg, /*flags=*/0);
if (Bgp_VmExec(&S.vm, &S.msg))
OutputZebra(hdr, S.msg.table);
Bgp_ClearMsg(&S.msg);
done:
Bgp_ClearMrt(&S.rec);
}
static void OutputRibv2(const Mrthdr *hdr,
const Mrtpeerentv2 *peerent,
const Mrtribentv2 *ent,
Bgpattrtab tab)
{
S.lenientBgpErrors = TRUE;
S.outFmt->DumpRibv2(hdr, peerent, ent, STM_CONHN(STDOUT), Stm_ConOps, tab);
S.lenientBgpErrors = FALSE;
}
void BgpgrepD_TableDumpv2(void)
{
const Mrthdr *hdr = MRT_HDR(&S.rec);
if (hdr->subtype == TABLE_DUMPV2_PEER_INDEX_TABLE) {
// Store record as PEER_INDEX_TABLE
Bgp_ClearMrt(&S.peerIndex);
MRT_MOVEREC(&S.peerIndex, &S.rec);
S.hasPeerIndex = TRUE;
return;
}
if (!TABLE_DUMPV2_ISRIB(hdr->subtype))
goto done; // don't care for anything but RIBs
// We may only dump record if we've got a PEER_INDEX_TABLE
if (!S.hasPeerIndex)
Bgpgrep_DropRecord("SKIPPING TABLE_DUMPV2 RECORD - No PEER_INDEX_TABLE found yet");
// Scan every entry inside RIBv2
const Mrtribentv2 *ent;
const Mrtribhdrv2 *ribhdr = Bgp_GetMrtRibHdrv2(&S.rec, NULL);
Mrtribiterv2 ribents;
Bgp_StartMrtRibEntriesv2(&ribents, &S.rec);
while ((ent = Bgp_NextRibEntryv2(&ribents)) != NULL) {
// If we get a corrupted entry, we must still scan what's next
if (setjmp_fast(S.dropMsgFrame))
continue;
// Fetch Peer entry
Uint16 idx = beswap16(ent->peerIndex);
const Mrtpeerentv2 *peerent = Bgp_GetMrtPeerByIndex(&S.peerIndex, idx);
if (S.isTrivialFilter) {
// FAST PATH - avoid BGP message rebuild
BGP_CLRATTRTAB(S.msg.table);
OutputRibv2(hdr, peerent, ent, S.msg.table);
continue;
}
// FILTERING PATH
Prefix pfx;
RIBV2_GETNLRI(hdr->subtype, &pfx, ribhdr, ent);
// Setup filter
S.peerAs = MRT_GETPEERADDR(&S.peerAddr, peerent);
S.timestampSecs = beswap32(ent->originatedTime);
S.timestampMicrosecs = 0;
// Execute filter and dump RIBv2s
const Bgpattrseg *tpa = RIBV2_GETATTRIBS(hdr->subtype, ent);
Bgp_RebuildMsgFromRib(&pfx, tpa, &S.msg, /*flags=*/BGPF_RIBV2|BGPF_CLEARUNREACH);
if (Bgp_VmExec(&S.vm, &S.msg)) {
FixBgpAttributeTableForRib(S.msg.table, /*isRibv2=*/TRUE);
OutputRibv2(hdr, peerent, ent, S.msg.table);
}
Bgp_ClearMsg(&S.msg);
}
done:
Bgp_ClearMrt(&S.rec);
}
static void OutputRib(const Mrthdr *hdr, const Mrtribent *ent, Bgpattrtab tab)
{
S.lenientBgpErrors = TRUE;
S.outFmt->DumpRib(hdr, ent, STM_CONHN(STDOUT), Stm_ConOps, tab);
S.lenientBgpErrors = FALSE;
}
void BgpgrepD_TableDump(void)
{
const Mrthdr *hdr = MRT_HDR(&S.rec);
const Mrtribent *ent = Bgp_GetMrtRibHdr(&S.rec);
if (S.isTrivialFilter) {
// FAST PATH - No need to rebuild BGP
BGP_CLRATTRTAB(S.msg.table);
OutputRib(hdr, ent, S.msg.table);
goto done;
}
// FILTERING PATH - Setup VM for TABLE_DUMP
S.peerAs = RIB_GETPEERADDR(hdr->subtype, &S.peerAddr, ent);
S.timestampSecs = beswap32(RIB_GETORIGINATED(hdr->subtype, ent));
S.timestampMicrosecs = 0;
// Rebuild message and execute filter
Prefix pfx;
pfx.afi = hdr->subtype;
pfx.safi = SAFI_UNICAST;
pfx.isAddPath = FALSE;
pfx.pathId = 0; // unimportant
RIB_GETPFX(hdr->subtype, PLAINPFX(&pfx), ent);
const Bgpattrseg *tpa = RIB_GETATTRIBS(hdr->subtype, ent);
Bgp_RebuildMsgFromRib(&pfx, tpa, &S.msg, /*flags=*/BGPF_CLEARUNREACH);
if (Bgp_VmExec(&S.vm, &S.msg)) {
FixBgpAttributeTableForRib(S.msg.table, /*isRibv2=*/FALSE);
OutputRib(hdr, ent, S.msg.table);
}
Bgp_ClearMsg(&S.msg);
done:
Bgp_ClearMrt(&S.rec);
}

182
tools/bgpgrep/bgpgrep_local.h Executable file
View File

@ -0,0 +1,182 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* \file bgpgrep_local.h
*
* `bgpgrep` private header.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#ifndef DF_BGPGREP_LOCAL_H_
#define DF_BGPGREP_LOCAL_H_
#include "bgp/dump.h"
#include "bgp/mrt.h"
#include "bgp/patricia.h"
#include "bgp/vm.h"
#include <setjmp.h>
// =======================================
// Data structures used during compilation
typedef struct Trielist Trielist;
struct Trielist {
Sint32 kidx;
Patricia trie;
Trielist *next;
};
typedef enum {
ANNOUNCE, WITHDRAWN, NUM_NETPFX_TYPES
} NetpfxType;
typedef struct Pfxnode Pfxnode;
struct Pfxnode {
Prefix pfx;
Uint8 refCount;
Pfxnode *next[NUM_NETPFX_TYPES];
};
typedef struct Pfxlist Pfxlist;
struct Pfxlist {
Boolean8 areListsMatching; // TRUE if head[ANNOUNCE] and head[WITHDRAWN] lists are equal
Boolean8 isEmpty; // TRUE if both lists are NULL
Pfxnode *head[NUM_NETPFX_TYPES];
};
typedef struct Triepair Triepair;
struct Triepair {
Trielist *v4, *v6;
};
typedef enum {
PEER_ADDR_NOP, // operate only on AS
PEER_ADDR_EQ, // test for equality
PEER_ADDR_NE // test for inequality
} Peeraddropc;
typedef struct Peerlistop Peerlistop;
struct Peerlistop {
Peeraddropc opc;
Asn asn;
Ipadr addr;
Peerlistop *next;
};
typedef enum {
TIMESTAMP_LE,
TIMESTAMP_LT,
TIMESTAMP_NE,
TIMESTAMP_EQ,
TIMESTAMP_GT,
TIMESTAMP_GE
} Timestampopc;
typedef struct Timestampop Timestampop;
struct Timestampop {
Sint64 secs;
Uint32 microsecs;
Timestampopc opc;
};
// ==================================
// Non Local Jumps - error management
#ifdef __GNUC__
// Faster alternative, less taxing than standard setjmp(), but comes with
// a few gotchas: https://gcc.gnu.org/onlinedocs/gcc/Nonlocal-Gotos.html
typedef Sintptr frame_buf[5];
#define setjmp_fast(frame) __builtin_setjmp(frame)
#define longjmp_fast(frame) __builtin_longjmp(frame, 1)
#else
typedef jmp_buf frame_buf;
#define setjmp_fast(frame) setjmp(frame)
#define longjmp_fast(frame) longjmp(frame, 1)
#endif
// ====================
// Global bgpgrep state
typedef struct BgpgrepState BgpgrepState;
struct BgpgrepState {
const char *filename; // current file being processed
const BgpDumpfmt *outFmt;
// Miscellaneous global flags and data
Boolean8 noColor;
Boolean8 hasPeerIndex;
Boolean8 dumpBytecode;
Boolean8 isTrivialFilter; // TRUE when `vm` program = "return TRUE"
Boolean8 lenientBgpErrors; // If TRUE bgpgrep won't drop a message on BGP error
// (allows dumping BGP data with corrupted markers)
Mrtrecord peerIndex;
Mrtrecord rec;
Bgpmsg msg;
Bgpvm vm;
Trielist *trieList;
// MRT extensions for Bgpvm (used by bgpgrep VM intrinsice: BgpgrepF_*)
Ipadr peerAddr;
Asn peerAs;
Sint64 timestampSecs;
Uint32 timestampMicrosecs;
// Error tracking and management
frame_buf dropMsgFrame; // used to skip single RIB entry or BGP message
frame_buf dropRecordFrame; // used by `Bgpgrep_DropRecord()`
frame_buf dropFileFrame; // used by `Bgpgrep_DropFile()`
int nerrors; // for `exit()` status
};
extern BgpgrepState S;
// ================
// Error management
CHECK_PRINTF(1, 2) void Bgpgrep_Warning(const char *, ...);
CHECK_PRINTF(1, 2) NORETURN void Bgpgrep_Fatal(const char *, ...);
CHECK_PRINTF(1, 2) NORETURN void Bgpgrep_DropMessage(const char *, ...);
CHECK_PRINTF(1, 2) NORETURN void Bgpgrep_DropRecord(const char *, ...);
CHECK_PRINTF(1, 2) NORETURN void Bgpgrep_DropFile(const char *, ...);
// ===============
// Rules compiler
// -> Entry point
void Bgpgrep_CompileVmProgram(int, char **);
// -> Called by `Bgpgrep_CompileVmProgram()` during compilation stage
char *BgpgrepC_GetToken(void);
char *BgpgrepC_CurTerm(void);
char *BgpgrepC_ExpectAnyToken(void);
char *BgpgrepC_ExpectToken(const char *);
Trielist *BgpgrepC_NewTrie(Afi);
Sint32 BgpgrepC_BakeAsRegexp(void);
void BgpgrepC_ParsePrefixList(Pfxlist *);
void BgpgrepC_FreePrefixList(Pfxlist *);
Sint32 BgpgrepC_ParsePeerExpression(void);
Sint32 BgpgrepC_ParseTimestampExpression(void);
Sint32 BgpgrepC_ParseCommunity(BgpVmOpt);
// -> Specific BGP VM intrinsics for bgpgrep operations (CALL targets)
void BgpgrepF_FindAsLoops(Bgpvm *);
void BgpgrepF_PeerAddrMatch(Bgpvm *);
void BgpgrepF_TimestampCompare(Bgpvm *);
// ==================
// MRT Dump functions
void BgpgrepD_Bgp4mp(void);
void BgpgrepD_Zebra(void);
void BgpgrepD_TableDumpv2(void);
void BgpgrepD_TableDump(void);
#endif

View File

@ -0,0 +1,225 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* \file bgpgrep_peer.c
*
* Peer matching expressions compilation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgpgrep_local.h"
#include "sys/fs.h"
#include "sys/endian.h"
#include "sys/sys.h"
#include "lexer.h"
#include "numlib.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
Peerlistop *head;
Peerlistop **lastp;
} Peermatch;
// Diagnostics used inside ParsePeerMatch()
#define WARN(fmt, ...) ((void) ((lexp) ? \
LexerWarning(lexp, fmt, __VA_ARGS__) : \
Bgpgrep_Warning("%s: " fmt, BgpgrepC_CurTerm(), __VA_ARGS__)))
#define FATAL(fmt, ...) ((void) ((lexp) ? \
LexerError(lexp, fmt, __VA_ARGS__) : \
Bgpgrep_Fatal("%s: " fmt, BgpgrepC_CurTerm(), __VA_ARGS__)))
static void ParsePeerMatch(Peermatch *dest, const char *tok, Lex *lexp)
{
Peeraddropc opc;
Ipadr addr;
Asn asn;
char *ep, *addrp, *asnp;
unsigned long long n;
NumConvRet res;
Boolean notFlag;
asn = ASN_ANY;
// Peer matches are in the form: "[[!]ip address] [[!]ASN]"
//
// Quotes may be omitted when matching only [ip address] or [ASN]
//
// At least one between [ip address] and [ASN] must be specified for
// each expression
ep = strchr(tok, ' ');
if (ep) {
// Both peer address and ASN specified
n = ep - tok;
addrp = (char *) alloca(n + 1);
memcpy(addrp, tok, n);
addrp[n] = '\0';
opc = PEER_ADDR_EQ;
if (*addrp == '!') {
opc = PEER_ADDR_NE;
addrp++;
}
if (Ip_StringToAdr(addrp, &addr) != OK)
FATAL("Got '%s' while expecting IP address", addrp);
asnp = ep + 1;
while (*asnp == ' ') asnp++;
notFlag = FALSE;
if (*asnp == '!') {
notFlag = TRUE;
asnp++;
}
n = Atoull(asnp, &ep, 10, &res);
if (res != NCVENOERR || *ep != '\0' || n > 0xffffffffuLL)
FATAL("Bad ASN '%s'", asnp);
asn = ASN32BIT(beswap32(n));
if (notFlag)
asn = ASNNOT(asn);
} else {
notFlag = FALSE;
if (*tok == '!') {
notFlag = TRUE;
tok++;
}
if (Ip_StringToAdr(tok, &addr) == OK) {
opc = notFlag ? PEER_ADDR_NE : PEER_ADDR_EQ;
} else {
// attempt ASN
n = Atoull(tok, &ep, 10, &res);
if (res != NCVENOERR || *ep != '\0' || n > 0xffffffffuLL)
FATAL("Got '%s' while expecting IP or ASN", tok);
opc = PEER_ADDR_NOP;
memset(&addr, 0, sizeof(addr));
asn = ASN32BIT(beswap32(n));
if (notFlag)
asn = ASNNOT(asn);
}
}
if (ISASTRANS(asn))
WARN("'%s': AS_TRANS in peer match expression", tok);
Peerlistop *p = Bgp_VmPermAlloc(&S.vm, sizeof(*p));
if (!p)
Bgpgrep_Fatal("Too many peer matches");
// Store match
p->opc = opc;
p->addr = addr;
p->asn = asn;
// Append to match list
p->next = *dest->lastp;
*dest->lastp = p;
dest->lastp = &p->next;
}
static Judgement ParsePeerMatchFile(Peermatch *dest, const char *filename)
{
Lex p;
Tok tok;
// Map file in memory
Fildes fd = Sys_Fopen(filename, FM_READ, FH_SEQ);
if (fd == FILDES_BAD)
return NG;
Sint64 fsiz = Sys_FileSize(fd); // NOTE: aborts on failure
assert(fsiz >= 0);
char *text = (char *) malloc(fsiz);
if (!text)
Sys_OutOfMemory();
Sint64 n = Sys_Fread(fd, text, fsiz);
if (n != fsiz)
Bgpgrep_Fatal("Short read from file '%s'", filename);
Sys_Fclose(fd);
// Setup Lex and start reading strings
unsigned flags = L_NOSTRCAT | L_ALLOWIPADDR | L_PLAINIPADDRONLY | L_COLORED;
if (S.noColor)
flags &= ~L_COLORED;
memset(&p, 0, sizeof(p));
BeginLexerSession(&p, filename, /*line=*/1);
SetLexerTextn(&p, text, fsiz);
SetLexerFlags(&p, flags);
SetLexerErrorFunc(&p, LEX_QUIT, LEX_WARN, NULL);
char buf[1 + MAXTOKLEN + 1];
while (Lex_ReadToken(&p, &tok)) {
strcpy(buf, tok.text);
if (tok.type == TT_PUNCT && tok.subtype == P_LOGIC_NOT) {
// Special case for straight "[[!]ip address]/[[!]ASN" with no quotes
if (tok.spacesBeforeToken == 0)
LexerError(&p, "%s: '!' following previous peer match expression requires space", BgpgrepC_CurTerm());
Lex_MatchAnyToken(&p, &tok);
strcat(buf, tok.text);
}
ParsePeerMatch(dest, buf, &p);
}
free(text);
return OK;
}
Sint32 BgpgrepC_ParsePeerExpression(void)
{
const char *tok = BgpgrepC_ExpectAnyToken();
if (strcmp(tok, "()") == 0)
return -1; // so the user doesn't have to split cmd line like \( \)
Peermatch matches;
memset(&matches, 0, sizeof(matches));
matches.lastp = &matches.head;
if (strcmp(tok, "(") == 0) {
// Explicit inline peer match list
while (TRUE) {
tok = BgpgrepC_ExpectAnyToken();
if (strcmp(tok, ")") == 0)
break;
ParsePeerMatch(&matches, tok, NULL);
}
} else {
// Anything else is interpreted as either:
// - A file argument, containing a space-separated prefix list
// - A single match, if the file is not found
// (shorthand for "( <peer match> )" )
if (ParsePeerMatchFile(&matches, tok) != OK)
ParsePeerMatch(&matches, tok, NULL);
}
if (!matches.head)
return -1; // empty list
Sint32 kidx = BGP_VMSETKA(&S.vm, Bgp_VmNewk(&S.vm), matches.head);
if (kidx < 0)
Bgpgrep_Fatal("Too many peer match expressions");
return kidx;
}

View File

@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* \file bgpgrep_prefixlist.c
*
* Prefix matching expressions compilation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgpgrep_local.h"
#include "sys/fs.h"
#include "sys/sys.h"
#include "lexer.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
static Pfxnode *AddPrefixToList(Pfxlist *dest,
const Prefix *pfx,
Boolean isAnnounce,
Boolean isWithdrawn)
{
Pfxnode *n = (Pfxnode *) calloc(1, sizeof(*n));
if (!n)
Sys_OutOfMemory();
memcpy(&n->pfx, pfx, sizeof(n->pfx));
if (isAnnounce) {
n->next[ANNOUNCE] = dest->head[ANNOUNCE];
dest->head[ANNOUNCE] = n;
n->refCount++;
}
if (isWithdrawn) {
n->next[WITHDRAWN] = dest->head[WITHDRAWN];
dest->head[WITHDRAWN] = n;
n->refCount++;
}
dest->areListsMatching &= (isAnnounce && isWithdrawn);
dest->isEmpty = FALSE;
return n;
}
static Pfxnode *AddPrefixStringToList(Pfxlist *dest, const char *s)
{
Prefix pfx;
Boolean isAnnounce = TRUE;
Boolean isWithdrawn = TRUE;
if (*s == '+') {
// announce only
s++;
isWithdrawn = FALSE;
} else if (*s == '-') {
// withdrawn only
s++;
isAnnounce = FALSE;
}
if (Bgp_StringToPrefix(s, &pfx) != OK)
Bgpgrep_Fatal("%s: Bad prefix '%s'", BgpgrepC_CurTerm(), s);
return AddPrefixToList(dest, &pfx, isAnnounce, isWithdrawn);
}
static Judgement ParsePrefixListFile(Pfxlist *dest, const char *filename)
{
Lex lex;
Tok tok;
// Map file in memory
Fildes fd = Sys_Fopen(filename, FM_READ, FH_SEQ);
if (fd == FILDES_BAD)
return NG;
Sint64 fsiz = Sys_FileSize(fd); // NOTE: aborts on failure
assert(fsiz >= 0);
char *text = (char *) malloc(fsiz);
if (!text)
Sys_OutOfMemory();
Sint64 n = Sys_Fread(fd, text, fsiz);
if (n != fsiz)
Bgpgrep_Fatal("%s: Short read from file '%s'", BgpgrepC_CurTerm(), filename);
Sys_Fclose(fd);
// Setup Lex and start reading tokens in form: [+-]network[/len]
unsigned flags = L_ALLOWIPADDR | L_PLAINIPADDRONLY | L_COLORED;
if (S.noColor)
flags &= ~L_COLORED;
memset(&lex, 0, sizeof(lex));
BeginLexerSession(&lex, filename, /*line=*/1);
SetLexerTextn(&lex, text, fsiz);
SetLexerFlags(&lex, flags);
SetLexerErrorFunc(&lex, LEX_QUIT, LEX_WARN, NULL);
char buf[MAXTOKLEN + 1 + MAXTOKLEN + 1]; // wild upperbound
while (Lex_ReadToken(&lex, &tok)) {
Boolean isAnnounce = TRUE;
Boolean isWithdrawn = TRUE;
Prefix pfx;
if (tok.type == TT_PUNCT) {
// Match limited to ANNOUNCE/WITHDRAWN
switch (tok.subtype) {
case P_ADD: isWithdrawn = FALSE; break;
case P_SUB: isAnnounce = FALSE; break;
default:
LexerError(&lex, "Read '%s' while expecting '+' or '-'", tok.text);
break;
}
Lex_MatchAnyToken(&lex, &tok);
}
if (tok.type != TT_NUMBER || (tok.subtype & TT_IPADDR) == 0)
LexerError(&lex, "Read '%s' while expecting internet address value", tok.text);
strcpy(buf, tok.text);
if (Lex_CheckTokenType(&lex, &tok, TT_PUNCT, P_DIV)) {
strcat(buf, tok.text);
Lex_MatchTokenType(&lex, &tok, TT_NUMBER, TT_INT);
strcat(buf, tok.text);
}
if (Bgp_StringToPrefix(buf, &pfx) != OK)
LexerError(&lex, "Bad network prefix '%s'", buf);
AddPrefixToList(dest, &pfx, isAnnounce, isWithdrawn);
}
free(text);
return OK;
}
void BgpgrepC_ParsePrefixList(Pfxlist *dest)
{
memset(dest, 0, sizeof(*dest));
dest->areListsMatching = TRUE;
dest->isEmpty = TRUE;
const char *tok = BgpgrepC_ExpectAnyToken();
if (strcmp(tok, "()") == 0)
return; // so the user doesn't have to split cmd line like \( \)
if (strcmp(tok, "(") == 0) {
// Explicit inline prefix list, directly inside command line
while (TRUE) {
tok = BgpgrepC_ExpectAnyToken();
if (strcmp(tok, ")") == 0)
break;
AddPrefixStringToList(dest, tok);
}
} else {
// Anything else is interpreted as either:
// - A file argument, containing a space-separated prefix list
// - A single prefix, if the file is not found
// (shorthand for "( <pfx> )" )
if (ParsePrefixListFile(dest, tok) == OK)
return;
AddPrefixStringToList(dest, tok);
}
}
void BgpgrepC_FreePrefixList(Pfxlist *list)
{
for (int i = 0; i < NUM_NETPFX_TYPES; i++) {
Pfxnode *n = list->head[i];
while (n) {
Pfxnode *t = n;
n = n->next[i];
if (--t->refCount == 0)
free(t);
}
}
}

View File

@ -0,0 +1,262 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* \file bgpgrep_timestamp.c
*
* Timestamp expressions parsing and compilation.
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgpgrep_local.h"
#include "numlib.h"
#include "strlib.h"
#include <assert.h>
#include <math.h>
#include <string.h>
#include <time.h>
#define SECSPERMIN (60)
#define SECSPERHOUR (60 * SECSPERMIN)
#define FATAL(msg) Bgpgrep_Fatal("%s: " msg, BgpgrepC_CurTerm())
#define FATALF(fmt, ...) Bgpgrep_Fatal("%s: " fmt, BgpgrepC_CurTerm(), __VA_ARGS__)
static time_t UtcTime(struct tm *t)
{
#ifdef _WIN32
return _mkgmtime(t);
#else
return timegm(t);
#endif
}
static Uint32 ParseFrac(const char *s, const char *p, char **ep)
{
char *endp;
NumConvRet res;
unsigned long long n = Atoull(p, &endp, 10, &res);
if (res != NCVENOERR)
FATALF("%s: Expecting fractional portion of a second after '.'", s);
int ndigs = endp - p;
double frac = pow(10, ndigs);
if (ep)
*ep = endp;
return (Uint32) trunc((n / frac) * 100000.0);
}
static Boolean ParseRfc3339(const char *s, Timestampop *dest)
{
NumConvRet res;
char *p, *ep;
int yyyy, mm, dd, h, min, secs, microsecs;
int timeOff;
Boolean expectTimeOfDay;
struct tm t;
memset(&t, 0, sizeof(t));
p = (char *) s;
yyyy = Atou(p, &ep, 10, &res);
if (res != NCVENOERR || (*ep != '-' && *ep != ':'))
return FALSE; // Invalid RFC-3339 format
h = min = secs = microsecs = 0;
timeOff = 0;
expectTimeOfDay = FALSE;
if (*ep == '-') {
// YYYY-MM-DD
if (ep - p != 4)
FATALF("%s: Expecting 4-digit year number", s);
p = ++ep;
mm = Atou(p, &ep, 10, &res);
if (res != NCVENOERR || ep - p != 2)
FATALF("%s: Expecting 2-digit month field", s);
if (*ep != '-')
FATALF("%s: Missing day of month field", s);
p = ++ep;
dd = Atou(p, &ep, 10, &res);
if (res != NCVENOERR || ep - p != 2)
FATALF("%s: Expecting 2-digit day of month field", s);
// Rescale values for struct tm
yyyy -= 1900;
mm -= 1;
if (*ep == 'T' || *ep == 't') {
++ep;
expectTimeOfDay = TRUE;
}
p = ep;
} else {
// What we've parsed is not YYYY, but actually HH,
// Reset parser and expect to get time of day
p = (char *) s;
expectTimeOfDay = TRUE;
// Setup YYYY-MM-DD from current date, in UTC
const time_t now = time(NULL);
const struct tm *cur = gmtime(&now);
if (!cur) // PARANOID
FATAL("gmtime() failed: Could not retrieve current date");
yyyy = cur->tm_year;
mm = cur->tm_mon;
dd = cur->tm_mday;
}
if (expectTimeOfDay) {
// HOUR:MIN[:SECS[.FRAC]]
h = Atou(p, &ep, 10, &res);
if (res != NCVENOERR || *ep != ':' || ep - p != 2)
FATALF("%s: Expected 2-digits for hour field", s);
p = ++ep;
min = Atou(p, &ep, 10, &res);
if (res != NCVENOERR || ep - p != 2)
FATALF("%s: Expected 2-digits for minutes field", s);
if (*ep == ':') {
// SECS[.FRAC]
p = ++ep;
secs = Atou(p, &ep, 10, &res);
if (res != NCVENOERR || ep - p != 2)
FATALF("%s: Expected 2-digits for seconds field", s);
if (*ep == '.') {
// Fraction of a second
p = ++ep;
microsecs = ParseFrac(s, p, &ep);
}
}
}
if (*ep == '+' || *ep == '-') {
// Time offset +/- HH:MM
int hoursOff, minOff;
int sign = (*ep == '+') ? 1 : -1;
p = ++ep;
hoursOff = Atou(p, &ep, 10, &res);
if (res != NCVENOERR || *ep != ':' || ep - p != 2 || hoursOff < 0 || hoursOff > 23)
FATALF("%s: Expected valid 2-digit hours field for timezone offset", s);
p = ++ep;
minOff = Atou(p, &ep, 10, &res);
if (res != NCVENOERR || ep - p != 2 || minOff < 0 || minOff > 59)
FATALF("%s: Expected valid 2-digit minute field for timezone offset", s);
timeOff = (hoursOff*SECSPERHOUR + minOff*SECSPERMIN);
timeOff *= sign;
} else if (*ep == 'Z' || *ep == 'z')
++ep; // explicit UTC marker
if (*ep != '\0')
FATALF("%s: Bad timestamp format", s);
if (h == 24 && min == 0 && secs == 0 && microsecs == 0) {
// 24:00:00 -> 00:00:00
// we adjust time offset to add one day to epoch
h = 0;
timeOff -= 24*SECSPERHOUR;
}
// Convert to UTC time_t
t.tm_year = yyyy;
t.tm_mon = mm;
t.tm_mday = dd;
t.tm_hour = h;
t.tm_min = min;
t.tm_sec = secs;
time_t epoch = UtcTime(&t);
// Do not accept renormalized dates
if (t.tm_year != yyyy || t.tm_mon != mm || t.tm_mday != dd ||
t.tm_hour != h || t.tm_min != min || t.tm_sec != secs)
FATALF("%s: Invalid timestamp", s);
// Adjust for time offset
dest->secs = epoch - timeOff; // assume POSIX time_t semantics
dest->microsecs = microsecs;
return TRUE;
}
static Boolean ParseEpoch(const char *s, Timestampop *dest)
{
char *p;
NumConvRet res;
dest->secs = Atoll(s, &p, 10, &res);
if (res != NCVENOERR)
return FALSE;
if (*p == '.') {
++p;
dest->microsecs = ParseFrac(s, p, &p);
} else
dest->microsecs = 0;
return *p == '\0';
}
Sint32 BgpgrepC_ParseTimestampExpression(void)
{
const char *tok = BgpgrepC_ExpectAnyToken();
Timestampop *stamp = (Timestampop *) Bgp_VmPermAlloc(&S.vm, sizeof(*stamp));
if (!stamp)
Bgpgrep_Fatal("Memory allocation failed, too many timestamp operations");
// Optional operator
stamp->opc = TIMESTAMP_EQ;
if (strncmp(tok, "<=", 2) == 0) {
stamp->opc = TIMESTAMP_LE;
tok += 2;
} else if (*tok == '<') {
stamp->opc = TIMESTAMP_LT;
tok++;
} else if (*tok == '!') {
stamp->opc = TIMESTAMP_NE;
tok++;
} else if (*tok == '=') {
stamp->opc = TIMESTAMP_EQ;
tok++;
} else if (strncmp(tok, ">=", 2) == 0) {
stamp->opc = TIMESTAMP_GE;
tok += 2;
} else if (*tok == '>') {
stamp->opc = TIMESTAMP_GT;
tok++;
}
// Timestamp value
if (!ParseRfc3339(tok, stamp) && !ParseEpoch(tok, stamp))
FATALF("Unrecognized timestamp format '%s'", tok);
Sint32 kidx = BGP_VMSETKA(&S.vm, Bgp_VmNewk(&S.vm), stamp);
if (kidx < 0)
Bgpgrep_Fatal("Expression has too many timestamp operations");
return kidx;
}

215
tools/bgpgrep/bgpgrep_vmfunc.c Executable file
View File

@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-3.0-or-later
/**
* \file bgpgrep_vmfunc.c
*
* `bgpgrep`-specific BGP VM extended functions
*
* \copyright The DoubleFourteen Code Forge (C) All Rights Reserved
* \author Lorenzo Cogotti
*/
#include "bgpgrep_local.h"
#include "bgp/vmintrin.h"
#include <assert.h>
#include <string.h>
typedef struct Asnnode Asnnode;
struct Asnnode {
Asn32 asn;
Sint32 pos;
Asnnode *children[2];
};
typedef struct Asntree Asntree;
struct Asntree {
Uint32 n;
Asnnode *root;
};
// NOTE: Need this macro so we can safely Bgp_VmTempFree() a bunch of
// nodes with a single call, instead of using a loop
#define ALIGNEDNODESIZ ALIGN(sizeof(Asnnode), ALIGNMENT)
Asnnode *GetAsnTreeNode(Bgpvm *vm, Asntree *t, Asn32 asn)
{
Asnnode **p, *i;
// Search suitable spot inside tree
p = &t->root;
while ((i = *p) != NULL && asn != i->asn)
p = &i->children[asn > i->asn];
if (!i) {
// No node with this ASN
i = (Asnnode *) Bgp_VmTempAlloc(vm, ALIGNEDNODESIZ);
if (!i)
return NULL;
i->asn = asn;
i->pos = -1;
i->children[0] = i->children[1] = NULL;
*p = i;
t->n++;
}
return i;
}
void BgpgrepF_PeerAddrMatch(Bgpvm *vm)
{
/*
* POPS:
* -1 - Peerlistop *
*
* PUSHES:
* TRUE if S.peerAddr and S.peerAs match peer list, FALSE otherwise
*/
if (!BGP_VMCHKSTKSIZ(vm, 1))
return;
Boolean foundPeer = FALSE; // unless found otherwise
for (Peerlistop *i = (Peerlistop *) BGP_VMPOPA(vm); i; i = i->next) {
switch (i->opc) {
default:
case PEER_ADDR_NOP:
break;
case PEER_ADDR_EQ:
if (!Ip_Equal(&i->addr, &S.peerAddr)) continue;
break;
case PEER_ADDR_NE:
if (Ip_Equal(&i->addr, &S.peerAddr)) continue;
break;
}
if (i->asn == ASN_ANY)
foundPeer = TRUE;
else if (ISASNNOT(i->asn))
foundPeer = ASN(i->asn) != ASN(S.peerAs);
else
foundPeer = ASN(i->asn) == ASN(S.peerAs);
if (foundPeer)
break;
}
// XXX: include match info
BGP_VMPUSH(vm, foundPeer);
}
FORCE_INLINE int TIMESTAMP_CMP(const Timestampop *stamp)
{
int res = (S.timestampSecs > stamp->secs) - (S.timestampSecs < stamp->secs);
if (res == 0)
res = (S.timestampMicrosecs > stamp->microsecs) -
(S.timestampMicrosecs < stamp->microsecs);
return res;
}
void BgpgrepF_TimestampCompare(Bgpvm *vm)
{
/*
* POPS:
* -1 - Timestampop *
*
* PUSHES:
* TRUE if S.timestampSecs and S.timestampMicrosecs matches argument,
* FALSE otherwise
*/
if (!BGP_VMCHKSTKSIZ(vm, 1))
return;
Boolean res = FALSE;
Timestampop *stamp = (Timestampop *) BGP_VMPOPA(vm);
if (!stamp) // should never happen
goto nomatch;
switch (stamp->opc) {
case TIMESTAMP_LE:
res = (TIMESTAMP_CMP(stamp) <= 0);
break;
case TIMESTAMP_LT:
res = (TIMESTAMP_CMP(stamp) < 0);
break;
case TIMESTAMP_NE:
res = (TIMESTAMP_CMP(stamp) != 0);
break;
case TIMESTAMP_EQ:
res = (TIMESTAMP_CMP(stamp) == 0);
break;
case TIMESTAMP_GT:
res = (TIMESTAMP_CMP(stamp) > 0);
break;
case TIMESTAMP_GE:
res = (TIMESTAMP_CMP(stamp) >= 0);
break;
default:
UNREACHABLE;
return;
}
nomatch:
// XXX: include match info
BGP_VMPUSH(vm, res);
}
void BgpgrepF_FindAsLoops(Bgpvm *vm)
{
/* POPS:
* NONE
*
* PUSHES:
* TRUE if loops are found inside AS PATH, FALSE otherwise
*/
Aspathiter it;
Asntree t;
Asn asn;
Sint32 pos = 0;
Boolean foundLoop = FALSE;
if (Bgp_StartMsgRealAsPath(&it, vm->msg) != OK)
goto nomatch;
memset(&t, 0, sizeof(t));
while ((asn = Bgp_NextAsPath(&it)) != -1) {
Asn32 as32 = ASN(asn);
if (as32 == AS4_TRANS) {
// AS_TRANS are irrelevant for loops
pos++;
continue;
}
Asnnode *n = GetAsnTreeNode(vm, &t, as32);
if (!n)
return; // out of temporary memory
if (n->pos != -1 && n->pos < pos - 1) {
foundLoop = TRUE;
break;
}
n->pos = pos++; // record last position of this ASN and move on
}
if (Bgp_GetErrStat(NULL)) {
vm->errCode = BGPEVMMSGERR;
return;
}
Bgp_VmTempFree(vm, t.n * ALIGNEDNODESIZ);
nomatch:
// XXX: include match info
if (BGP_VMCHKSTK(vm, 1))
BGP_VMPUSH(vm, foundLoop);
}