mirror of
https://codeberg.org/1414codeforge/ubgpsuite.git
synced 2025-06-05 21:29:11 +02:00
[*] Initial commit
This commit is contained in:
919
tools/bgpgrep/bgpgrep.1.in
Executable file
919
tools/bgpgrep/bgpgrep.1.in
Executable 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
441
tools/bgpgrep/bgpgrep.c
Executable 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
214
tools/bgpgrep/bgpgrep_asmatch.c
Executable 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;
|
||||
}
|
237
tools/bgpgrep/bgpgrep_communities.c
Normal file
237
tools/bgpgrep/bgpgrep_communities.c
Normal 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
744
tools/bgpgrep/bgpgrep_compile.c
Executable 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
275
tools/bgpgrep/bgpgrep_dump.c
Executable 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
182
tools/bgpgrep/bgpgrep_local.h
Executable 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
|
225
tools/bgpgrep/bgpgrep_peer.c
Normal file
225
tools/bgpgrep/bgpgrep_peer.c
Normal 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;
|
||||
}
|
190
tools/bgpgrep/bgpgrep_prefixlist.c
Executable file
190
tools/bgpgrep/bgpgrep_prefixlist.c
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
262
tools/bgpgrep/bgpgrep_timestamp.c
Normal file
262
tools/bgpgrep/bgpgrep_timestamp.c
Normal 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
215
tools/bgpgrep/bgpgrep_vmfunc.c
Executable 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);
|
||||
}
|
Reference in New Issue
Block a user