gobgp-1.29/000077500000000000000000000000001324612745600125555ustar00rootroot00000000000000gobgp-1.29/.goreleaser.yml000066400000000000000000000005041324612745600155050ustar00rootroot00000000000000# # .goreleaser.yml # Build customization builds: - main: ./gobgp/ binary: gobgp goos: - linux goarch: - amd64 - 386 - arm - main: ./gobgpd/ binary: gobgpd goos: - linux goarch: - amd64 - 386 - arm archive: files: - LICENSE - README.md gobgp-1.29/.pep8000066400000000000000000000001141324612745600134260ustar00rootroot00000000000000[pep8] # E501: Limit all lines to a maximum of 79 characters. ignore = E501 gobgp-1.29/.pylintrc000066400000000000000000000327571324612745600144400ustar00rootroot00000000000000[MASTER] # Specify a configuration file. #rcfile= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. ignore-patterns= # Pickle collected data for later comparisons. persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= # Use multiple processes to speed up Pylint. jobs=1 # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code extension-pkg-whitelist= # Allow optimization of some AST trees. This will activate a peephole AST # optimizer, which will apply various small optimizations. For instance, it can # be used to obtain the result of joining multiple strings with the addition # operator. Joining a lot of strings can lead to a maximum recursion error in # Pylint and this flag can prevent that. It has one side effect, the resulting # AST will be different than the one from reality. This option is deprecated # and it will be removed in Pylint 2.0. optimize-ast=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED confidence= # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. #enable= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once).You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating,invalid-name,missing-docstring,line-too-long [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html. You can also give a reporter class, eg # mypackage.mymodule.MyReporterClass. output-format=text # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". This option is deprecated # and it will be removed in Pylint 2.0. files-output=no # Tells whether to display a full report or only the messages reports=yes # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details #msg-template= [FORMAT] # Maximum number of characters on a single line. max-line-length=100 # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. no-space-check=trailing-comma,dict-separator # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= [LOGGING] # Logging modules to check that the string format arguments are in logging # function parameter format logging-modules=logging [SPELLING] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words=no [BASIC] # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Include a hint for the correct naming format with invalid-name include-naming-hint=no # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. property-classes=abc.abstractproperty # Regular expression matching correct function names function-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for function names function-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct variable names variable-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for variable names variable-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Naming hint for constant names const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Regular expression matching correct attribute names attr-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for attribute names attr-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct argument names argument-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for argument names argument-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Naming hint for class attribute names class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming hint for inline iteration names inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Naming hint for class names class-name-hint=[A-Z_][a-zA-Z0-9]+$ # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Naming hint for module names module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct method names method-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for method names method-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 [ELIF] # Maximum number of nested blocks for function / method body max-nested-blocks=5 [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=no # A regular expression matching the name of dummy variables (i.e. expectedly # not used). dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_,_cb # List of qualified module names which can have objects that can redefine # builtins. redefining-builtins-modules=six.moves,future.builtins [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no [TYPECHECK] # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. ignored-classes=optparse.Values,thread._local,_thread._local # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that # produce valid context managers. contextmanager-decorators=contextlib.contextmanager [DESIGN] # Maximum number of arguments for function / method max-args=5 # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.* # Maximum number of locals for function / method body max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branches=12 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # Maximum number of boolean expressions in a if statement max-bool-expr=5 [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict,_fields,_replace,_source,_make [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists # only in one or another interpreter, leading to false positives when analysed. analyse-fallback-blocks=no [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=Exception gobgp-1.29/.travis.yml000066400000000000000000000120701324612745600146660ustar00rootroot00000000000000language: go _dep_ensure: &_dep_ensure go: "1.10" before_install: go get -u github.com/golang/dep/cmd/dep install: $GOPATH/bin/dep ensure _unittest: &_unittest <<: *_dep_ensure script: - go test $(go list ./... | grep -v '/vendor/') - go build -o ./gobgp/gobgp ./gobgp/ - go build -o ./gobgpd/gobgpd ./gobgpd/ _build: &_build <<: *_dep_ensure script: - go build -o ./gobgp/gobgp ./gobgp/ - go build -o ./gobgpd/gobgpd ./gobgpd/ _python: &_python language: python python: "2.7" _docker: &_docker <<: *_python sudo: required dist: trusty group: deprecated-2017Q4 before_install: - test $TRAVIS_OS_NAME == "linux" && sudo sysctl -w net.ipv6.conf.all.disable_ipv6=0 - test $TRAVIS_OS_NAME == "linux" && sudo sysctl -w net.ipv6.conf.default.disable_ipv6=0 - test $TRAVIS_OS_NAME == "linux" && sudo sysctl -w net.ipv6.conf.docker0.disable_ipv6=1 install: - pip install -r test/pip-requires.txt - fab -f test/lib/base.py make_gobgp_ctn:tag=$DOCKER_IMAGE,from_image=$FROM_IMAGE script: - PYTHONPATH=test python test/scenario_test/$TEST --gobgp-image $DOCKER_IMAGE -x -s services: - docker env: global: - DOCKER_IMAGE=gobgp - FROM_IMAGE=osrg/quagga matrix: allow_failures: - go: tip include: # # Unit Tests # - <<: *_unittest env: - DESCRIPTION="Unit Tests" go: tip - <<: *_unittest env: - DESCRIPTION="Unit Tests" go: 1.9 - <<: *_unittest env: - DESCRIPTION="Unit Tests + goreleaser" go: "1.10" after_success: - test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash - <<: *_unittest env: - DESCRIPTION="Unit Tests on i386" before_script: - export GOARCH="386" - go env # # Cross-compile # # Note: We use "before_script" to enable "go env" settings. The following keeps # "env" sections as just markers of Travis-CI Web UI. # See https://github.com/travis-ci/travis-ci/issues/6126 - <<: *_build env: - GOOS="windows" before_script: - export GOOS="windows" - go env - <<: *_build env: - GOOS="freebsd" before_script: - export GOOS="freebsd" - go env - <<: *_build env: - GOOS="darwin" before_script: - export GOOS="darwin" - go env # # Misc # - <<: *_dep_ensure env: - DESCRIPTION="go fmt" script: test -z "$(go fmt $(go list ./... | grep -v '/vendor/'))" - <<: *_dep_ensure env: - DESCRIPTION="go vet" script: test -z "$(go vet $(go list ./... | grep -v '/vendor/'))" - <<: *_dep_ensure env: - DESCRIPTION="build_embeded_go.py" script: python test/scenario_test/ci-scripts/build_embeded_go.py docs/sources/lib.md # # Docker # - <<: *_docker env: - TEST=bgp_router_test.py - <<: *_docker env: - TEST=bgp_zebra_test.py - <<: *_docker env: - TEST=bgp_zebra_nht_test.py FROM_IMAGE=osrg/quagga:v1.0 - <<: *_docker env: - TEST=evpn_test.py - <<: *_docker env: - TEST=flow_spec_test.py - <<: *_docker env: - TEST=global_policy_test.py - <<: *_docker env: - TEST=graceful_restart_test.py - <<: *_docker env: - TEST=ibgp_router_test.py - <<: *_docker env: - TEST=route_reflector_test.py - <<: *_docker env: - TEST=route_server_as2_test.py - <<: *_docker env: - TEST=route_server_ipv4_v6_test.py - <<: *_docker env: - TEST=route_server_malformed_test.py - <<: *_docker env: - TEST=route_server_policy_grpc_test.py - <<: *_docker env: - TEST=route_server_policy_test.py - <<: *_docker env: - TEST=route_server_softreset_test.py - <<: *_docker env: - TEST=route_server_test.py - <<: *_docker env: - TEST=route_server_test2.py - <<: *_docker env: - TEST=zapi_v3_test.py FROM_IMAGE=osrg/quagga:v1.0 - <<: *_docker env: - TEST=long_lived_graceful_restart_test.py - <<: *_docker env: - TEST=vrf_neighbor_test.py - <<: *_docker env: - TEST=vrf_neighbor_test2.py - <<: *_docker env: - TEST=bgp_unnumbered_test.py - <<: *_docker env: - TEST=aspath_test.py - <<: *_docker env: - TEST=addpath_test.py - <<: *_docker env: - TEST=bgp_malformed_msg_handling_test.py - <<: *_docker env: - TEST=bgp_confederation_test.py # # Tools # - <<: *_python env: - DESCRIPTION="Tools" install: pip install scspell3k script: - bash tools/spell-check/scspell.sh - bash tools/grep_avoided_functions.sh cache: pip: true go_import_path: github.com/osrg/gobgp notifications: slack: secure: KzuC9kytzS7wTlfj4MqSg8EpqXfJRMbo59dAVTA3w587achnVCS3vhUenWuhMCiWk7+6DVWwNpwzn2+A0S6RwMFMpKAU6Ij6K9sxEOgqBGuMN8w1//w+uJTryzsnebEIhInGt2kKfqz4Wx3QQqW5gVhI+8s+c5M0iXxFtN4soVk= gobgp-1.29/Gopkg.lock000066400000000000000000000146221324612745600145030ustar00rootroot00000000000000# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. [[projects]] name = "github.com/BurntSushi/toml" packages = ["."] revision = "b26d9c308763d68093482582cea63d69be07a0f0" version = "v0.3.0" [[projects]] branch = "master" name = "github.com/armon/go-radix" packages = ["."] revision = "1fca145dffbcaa8fe914309b1ec0cfc67500fe61" [[projects]] name = "github.com/davecgh/go-spew" packages = ["spew"] revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" [[projects]] branch = "master" name = "github.com/dgryski/go-farm" packages = ["."] revision = "ac7624ea8da311f2fbbd94401d8c1cf66089f9fb" [[projects]] name = "github.com/eapache/channels" packages = ["."] revision = "47238d5aae8c0fefd518ef2bee46290909cf8263" version = "v1.1.0" [[projects]] name = "github.com/eapache/queue" packages = ["."] revision = "ded5959c0d4e360646dc9e9908cff48666781367" version = "v1.0.2" [[projects]] name = "github.com/fsnotify/fsnotify" packages = ["."] revision = "629574ca2a5df945712d3079857300b5e4da0236" version = "v1.4.2" [[projects]] branch = "master" name = "github.com/golang/protobuf" packages = [ "proto", "ptypes/any" ] revision = "748d386b5c1ea99658fd69fe9f03991ce86a90c1" [[projects]] branch = "master" name = "github.com/hashicorp/hcl" packages = [ ".", "hcl/ast", "hcl/parser", "hcl/scanner", "hcl/strconv", "hcl/token", "json/parser", "json/scanner", "json/token" ] revision = "392dba7d905ed5d04a5794ba89f558b27e2ba1ca" [[projects]] name = "github.com/inconshreveable/mousetrap" packages = ["."] revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" version = "v1.0" [[projects]] name = "github.com/influxdata/influxdb" packages = [ "client/v2", "models", "pkg/escape" ] revision = "5887e92e8950435ac7e496ff8dada784051284b7" version = "v1.3.1" [[projects]] name = "github.com/jessevdk/go-flags" packages = ["."] revision = "96dc06278ce32a0e9d957d590bb987c81ee66407" version = "v1.3.0" [[projects]] branch = "master" name = "github.com/kr/pretty" packages = ["."] revision = "cfb55aafdaf3ec08f0db22699ab822c50091b1c4" [[projects]] branch = "master" name = "github.com/kr/text" packages = ["."] revision = "7cafcd837844e784b526369c9bce262804aebc60" [[projects]] name = "github.com/magiconair/properties" packages = ["."] revision = "be5ece7dd465ab0765a9682137865547526d1dfb" version = "v1.7.3" [[projects]] branch = "master" name = "github.com/mitchellh/mapstructure" packages = ["."] revision = "d0303fe809921458f417bcf828397a65db30a7e4" [[projects]] name = "github.com/pelletier/go-buffruneio" packages = ["."] revision = "c37440a7cf42ac63b919c752ca73a85067e05992" version = "v0.2.0" [[projects]] name = "github.com/pelletier/go-toml" packages = ["."] revision = "5ccdfb18c776b740aecaf085c4d9a2779199c279" version = "v1.0.0" [[projects]] name = "github.com/pmezard/go-difflib" packages = ["difflib"] revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" [[projects]] branch = "master" name = "github.com/satori/go.uuid" packages = ["."] revision = "36e9d2ebbde5e3f13ab2e25625fd453271d6522e" [[projects]] name = "github.com/sirupsen/logrus" packages = [ ".", "hooks/syslog" ] revision = "a3f95b5c423586578a4e099b11a46c2479628cac" version = "1.0.2" [[projects]] branch = "master" name = "github.com/spf13/afero" packages = [ ".", "mem" ] revision = "9be650865eab0c12963d8753212f4f9c66cdcf12" [[projects]] name = "github.com/spf13/cast" packages = ["."] revision = "acbeb36b902d72a7a4c18e8f3241075e7ab763e4" version = "v1.1.0" [[projects]] branch = "master" name = "github.com/spf13/cobra" packages = ["."] revision = "b26b538f693051ac6518e65672de3144ce3fbedc" [[projects]] branch = "master" name = "github.com/spf13/jwalterweatherman" packages = ["."] revision = "0efa5202c04663c757d84f90f5219c1250baf94f" [[projects]] name = "github.com/spf13/pflag" packages = ["."] revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" version = "v1.0.0" [[projects]] branch = "master" name = "github.com/spf13/viper" packages = ["."] revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7" [[projects]] name = "github.com/stretchr/testify" packages = ["assert"] revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" version = "v1.1.4" [[projects]] branch = "master" name = "github.com/vishvananda/netlink" packages = [ ".", "nl" ] revision = "a95659537721550a65cfc3638b664380696e38e1" [[projects]] branch = "master" name = "github.com/vishvananda/netns" packages = ["."] revision = "86bef332bfc3b59b7624a600bd53009ce91a9829" [[projects]] branch = "master" name = "golang.org/x/net" packages = [ "context", "http2", "http2/hpack", "idna", "internal/timeseries", "lex/httplex", "trace" ] revision = "f5079bd7f6f74e23c4d65efa0f4ce14cbd6a3c0f" [[projects]] branch = "master" name = "golang.org/x/sys" packages = ["unix"] revision = "e312636bdaa2fac4f0acde9d17ab9fbad2b4ad10" [[projects]] branch = "master" name = "golang.org/x/text" packages = [ "internal/gen", "internal/triegen", "internal/ucd", "secure/bidirule", "transform", "unicode/bidi", "unicode/cldr", "unicode/norm", "unicode/rangetable" ] revision = "3bd178b88a8180be2df394a1fbb81313916f0e7b" [[projects]] branch = "master" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] revision = "09f6ed296fc66555a25fe4ce95173148778dfa85" [[projects]] name = "google.golang.org/grpc" packages = [ ".", "codes", "credentials", "grpclb/grpc_lb_v1", "grpclog", "internal", "keepalive", "metadata", "naming", "peer", "stats", "status", "tap", "transport" ] revision = "b8669c35455183da6d5c474ea6e72fbf55183274" version = "v1.5.1" [[projects]] branch = "v2" name = "gopkg.in/tomb.v2" packages = ["."] revision = "d5d1b5820637886def9eef33e03a27a9f166942c" [[projects]] branch = "v2" name = "gopkg.in/yaml.v2" packages = ["."] revision = "25c4ec802a7d637f88d584ab26798e94ad14c13b" [solve-meta] analyzer-name = "dep" analyzer-version = 1 inputs-digest = "6100eb99bf349d89739e85e9549f95a8351710d0e70e63057b82c213fbab07fd" solver-name = "gps-cdcl" solver-version = 1 gobgp-1.29/Gopkg.toml000066400000000000000000000032271324612745600145250ustar00rootroot00000000000000 # Gopkg.toml example # # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # for detailed Gopkg.toml documentation. # # required = ["github.com/user/thing/cmd/thing"] # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] # # [[constraint]] # name = "github.com/user/project" # version = "1.0.0" # # [[constraint]] # name = "github.com/user/project2" # branch = "dev" # source = "github.com/myfork/project2" # # [[override]] # name = "github.com/x/y" # version = "2.4.0" [[constraint]] name = "github.com/BurntSushi/toml" version = "0.3.0" [[constraint]] branch = "master" name = "github.com/armon/go-radix" [[constraint]] name = "github.com/eapache/channels" version = "1.1.0" [[constraint]] branch = "master" name = "github.com/golang/protobuf" [[constraint]] name = "github.com/influxdata/influxdb" version = "1.3.1" [[constraint]] name = "github.com/jessevdk/go-flags" version = "1.3.0" [[constraint]] branch = "master" name = "github.com/kr/pretty" [[constraint]] branch = "master" name = "github.com/satori/go.uuid" [[constraint]] name = "github.com/sirupsen/logrus" version = "1.0.2" [[constraint]] branch = "master" name = "github.com/spf13/cobra" [[constraint]] branch = "master" name = "github.com/spf13/viper" [[constraint]] name = "github.com/stretchr/testify" version = "1.1.4" [[constraint]] branch = "master" name = "github.com/vishvananda/netlink" [[constraint]] branch = "master" name = "golang.org/x/net" [[constraint]] name = "google.golang.org/grpc" version = "1.5.1" [[constraint]] branch = "v2" name = "gopkg.in/tomb.v2" gobgp-1.29/LICENSE000066400000000000000000000260741324612745600135730ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gobgp-1.29/README.md000066400000000000000000000056611324612745600140440ustar00rootroot00000000000000# GoBGP: BGP implementation in Go [![Build Status](https://travis-ci.org/osrg/gobgp.svg?branch=master)](https://travis-ci.org/osrg/gobgp/builds) [![Slack Status](https://slackin-gobgp.mybluemix.net/badge.svg)](https://slackin-gobgp.mybluemix.net/) GoBGP is an open source BGP implementation designed from scratch for modern environment and implemented in a modern programming language, [the Go Programming Language](http://golang.org/). ---- ## To start using GoBGP Try [a binary release](https://github.com/osrg/gobgp/releases/latest). ## To start developing GoBGP You need a working [Go environment](https://golang.org/doc/install) (1.9 or newer). ```bash $ go get -u github.com/golang/dep/cmd/dep $ go get github.com/osrg/gobgp $ cd $GOPATH/src/github.com/osrg/gobgp && dep ensure ``` ## Documentation ### Using GoBGP * [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md) * CLI * [Typical operation examples](https://github.com/osrg/gobgp/blob/master/docs/sources/cli-operations.md) * [Complete syntax](https://github.com/osrg/gobgp/blob/master/docs/sources/cli-command-syntax.md) * [Route Server](https://github.com/osrg/gobgp/blob/master/docs/sources/route-server.md) * [Route Reflector](https://github.com/osrg/gobgp/blob/master/docs/sources/route-reflector.md) * [Policy](https://github.com/osrg/gobgp/blob/master/docs/sources/policy.md) * [FIB manipulation](https://github.com/osrg/gobgp/blob/master/docs/sources/zebra.md) * [MRT](https://github.com/osrg/gobgp/blob/master/docs/sources/mrt.md) * [BMP](https://github.com/osrg/gobgp/blob/master/docs/sources/bmp.md) * [EVPN](https://github.com/osrg/gobgp/blob/master/docs/sources/evpn.md) * [Flowspec](https://github.com/osrg/gobgp/blob/master/docs/sources/flowspec.md) * [RPKI](https://github.com/osrg/gobgp/blob/master/docs/sources/rpki.md) * [Managing GoBGP with your favorite language with GRPC](https://github.com/osrg/gobgp/blob/master/docs/sources/grpc-client.md) * [Using GoBGP as a Go Native BGP library](https://github.com/osrg/gobgp/blob/master/docs/sources/lib.md) * [Graceful Restart](https://github.com/osrg/gobgp/blob/master/docs/sources/graceful-restart.md) * Data Center Networking * [Unnumbered BGP](https://github.com/osrg/gobgp/blob/master/docs/sources/unnumbered-bgp.md) ### Externals * [Tutorial: Using GoBGP as an IXP connecting router](http://www.slideshare.net/shusugimoto1986/tutorial-using-gobgp-as-an-ixp-connecting-router) ## Community, discussion and support We have the [Slack](https://slackin-gobgp.mybluemix.net/) and [mailing list](https://lists.sourceforge.net/lists/listinfo/gobgp-devel) for questions, discussion, suggestions, etc. You have code or documentation for GoBGP? Awesome! Send a pull request. No CLA, board members, governance, or other mess. ## Licensing GoBGP is licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/osrg/gobgp/blob/master/LICENSE) for the full license text. gobgp-1.29/VERSION000066400000000000000000000000051324612745600136200ustar00rootroot000000000000001.29 gobgp-1.29/api/000077500000000000000000000000001324612745600133265ustar00rootroot00000000000000gobgp-1.29/api/gobgp.pb.go000066400000000000000000010670061324612745600153650ustar00rootroot00000000000000// Code generated by protoc-gen-go. DO NOT EDIT. // source: gobgp.proto /* Package gobgpapi is a generated protocol buffer package. It is generated from these files: gobgp.proto It has these top-level messages: GetNeighborRequest GetNeighborResponse Arguments AddPathRequest AddPathResponse DeletePathRequest DeletePathResponse AddNeighborRequest AddNeighborResponse DeleteNeighborRequest DeleteNeighborResponse ResetNeighborRequest ResetNeighborResponse SoftResetNeighborRequest SoftResetNeighborResponse ShutdownNeighborRequest ShutdownNeighborResponse EnableNeighborRequest EnableNeighborResponse DisableNeighborRequest DisableNeighborResponse EnableMrtRequest EnableMrtResponse DisableMrtRequest DisableMrtResponse InjectMrtRequest InjectMrtResponse AddBmpRequest AddBmpResponse DeleteBmpRequest DeleteBmpResponse MonitorRibRequest RPKIConf RPKIState Rpki GetRpkiRequest GetRpkiResponse AddRpkiRequest AddRpkiResponse DeleteRpkiRequest DeleteRpkiResponse EnableRpkiRequest EnableRpkiResponse DisableRpkiRequest DisableRpkiResponse ResetRpkiRequest ResetRpkiResponse SoftResetRpkiRequest SoftResetRpkiResponse EnableZebraRequest EnableZebraResponse GetVrfRequest GetVrfResponse AddVrfRequest AddVrfResponse DeleteVrfRequest DeleteVrfResponse GetDefinedSetRequest GetDefinedSetResponse AddDefinedSetRequest AddDefinedSetResponse DeleteDefinedSetRequest DeleteDefinedSetResponse ReplaceDefinedSetRequest ReplaceDefinedSetResponse GetStatementRequest GetStatementResponse AddStatementRequest AddStatementResponse DeleteStatementRequest DeleteStatementResponse ReplaceStatementRequest ReplaceStatementResponse GetPolicyRequest GetPolicyResponse AddPolicyRequest AddPolicyResponse DeletePolicyRequest DeletePolicyResponse ReplacePolicyRequest ReplacePolicyResponse GetPolicyAssignmentRequest GetPolicyAssignmentResponse AddPolicyAssignmentRequest AddPolicyAssignmentResponse DeletePolicyAssignmentRequest DeletePolicyAssignmentResponse ReplacePolicyAssignmentRequest ReplacePolicyAssignmentResponse GetServerRequest GetServerResponse StartServerRequest StartServerResponse StopServerRequest StopServerResponse RPKIValidation Path Destination Table GetRibRequest GetRibResponse TableLookupPrefix GetPathRequest ValidateRibRequest ValidateRibResponse Peer ApplyPolicy PrefixLimit PeerConf EbgpMultihop RouteReflector PeerState Messages Message Queues Timers TimersConfig TimersState Transport RouteServer GracefulRestart MpGracefulRestartConfig MpGracefulRestartState MpGracefulRestart AfiSafiConfig AfiSafiState RouteSelectionOptionsConfig RouteSelectionOptionsState RouteSelectionOptions UseMultiplePathsConfig UseMultiplePathsState EbgpConfig EbgpState Ebgp IbgpConfig IbgpState Ibgp UseMultiplePaths RouteTargetMembershipConfig RouteTargetMembershipState RouteTargetMembership LongLivedGracefulRestartConfig LongLivedGracefulRestartState LongLivedGracefulRestart AfiSafi AddPathsConfig AddPathsState AddPaths Prefix DefinedSet MatchSet AsPathLength Conditions CommunityAction MedAction AsPrependAction NexthopAction LocalPrefAction Actions Statement Policy PolicyAssignment Roa GetRoaRequest GetRoaResponse Vrf Global TableInfo GetRibInfoRequest GetRibInfoResponse */ package gobgpapi import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" import ( context "golang.org/x/net/context" grpc "google.golang.org/grpc" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package // Constants for address families type Family int32 const ( Family__ Family = 0 Family_IPv4 Family = 65537 Family_IPv6 Family = 131073 Family_IPv4_MC Family = 65538 Family_IPv6_MC Family = 131074 Family_IPv4_MPLS Family = 65540 Family_IPv6_MPLS Family = 131076 Family_IPv4_VPN Family = 65664 Family_IPv6_VPN Family = 131200 Family_IPv4_VPN_MC Family = 65665 Family_IPv6_VPN_MC Family = 131201 Family_VPLS Family = 1638465 Family_EVPN Family = 1638470 Family_RTC Family = 65668 Family_IPv4_ENCAP Family = 65543 Family_IPv6_ENCAP Family = 131079 Family_FLOW_SPEC_IPv4 Family = 65669 Family_FLOW_SPEC_IPv6 Family = 131205 Family_FLOW_SPEC_IPv4_VPN Family = 65670 Family_FLOW_SPEC_IPv6_VPN Family = 131206 Family_FLOW_SPEC_L2_VPN Family = 1638534 Family_OPAQUE Family = 1074594033 ) var Family_name = map[int32]string{ 0: "_", 65537: "IPv4", 131073: "IPv6", 65538: "IPv4_MC", 131074: "IPv6_MC", 65540: "IPv4_MPLS", 131076: "IPv6_MPLS", 65664: "IPv4_VPN", 131200: "IPv6_VPN", 65665: "IPv4_VPN_MC", 131201: "IPv6_VPN_MC", 1638465: "VPLS", 1638470: "EVPN", 65668: "RTC", 65543: "IPv4_ENCAP", 131079: "IPv6_ENCAP", 65669: "FLOW_SPEC_IPv4", 131205: "FLOW_SPEC_IPv6", 65670: "FLOW_SPEC_IPv4_VPN", 131206: "FLOW_SPEC_IPv6_VPN", 1638534: "FLOW_SPEC_L2_VPN", 1074594033: "OPAQUE", } var Family_value = map[string]int32{ "_": 0, "IPv4": 65537, "IPv6": 131073, "IPv4_MC": 65538, "IPv6_MC": 131074, "IPv4_MPLS": 65540, "IPv6_MPLS": 131076, "IPv4_VPN": 65664, "IPv6_VPN": 131200, "IPv4_VPN_MC": 65665, "IPv6_VPN_MC": 131201, "VPLS": 1638465, "EVPN": 1638470, "RTC": 65668, "IPv4_ENCAP": 65543, "IPv6_ENCAP": 131079, "FLOW_SPEC_IPv4": 65669, "FLOW_SPEC_IPv6": 131205, "FLOW_SPEC_IPv4_VPN": 65670, "FLOW_SPEC_IPv6_VPN": 131206, "FLOW_SPEC_L2_VPN": 1638534, "OPAQUE": 1074594033, } func (x Family) String() string { return proto.EnumName(Family_name, int32(x)) } func (Family) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } type Resource int32 const ( Resource_GLOBAL Resource = 0 Resource_LOCAL Resource = 1 Resource_ADJ_IN Resource = 2 Resource_ADJ_OUT Resource = 3 Resource_VRF Resource = 4 ) var Resource_name = map[int32]string{ 0: "GLOBAL", 1: "LOCAL", 2: "ADJ_IN", 3: "ADJ_OUT", 4: "VRF", } var Resource_value = map[string]int32{ "GLOBAL": 0, "LOCAL": 1, "ADJ_IN": 2, "ADJ_OUT": 3, "VRF": 4, } func (x Resource) String() string { return proto.EnumName(Resource_name, int32(x)) } func (Resource) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } // API representation of table.LookupOption type TableLookupOption int32 const ( TableLookupOption_LOOKUP_EXACT TableLookupOption = 0 TableLookupOption_LOOKUP_LONGER TableLookupOption = 1 TableLookupOption_LOOKUP_SHORTER TableLookupOption = 2 ) var TableLookupOption_name = map[int32]string{ 0: "LOOKUP_EXACT", 1: "LOOKUP_LONGER", 2: "LOOKUP_SHORTER", } var TableLookupOption_value = map[string]int32{ "LOOKUP_EXACT": 0, "LOOKUP_LONGER": 1, "LOOKUP_SHORTER": 2, } func (x TableLookupOption) String() string { return proto.EnumName(TableLookupOption_name, int32(x)) } func (TableLookupOption) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } type DefinedType int32 const ( DefinedType_PREFIX DefinedType = 0 DefinedType_NEIGHBOR DefinedType = 1 DefinedType_TAG DefinedType = 2 DefinedType_AS_PATH DefinedType = 3 DefinedType_COMMUNITY DefinedType = 4 DefinedType_EXT_COMMUNITY DefinedType = 5 DefinedType_LARGE_COMMUNITY DefinedType = 6 ) var DefinedType_name = map[int32]string{ 0: "PREFIX", 1: "NEIGHBOR", 2: "TAG", 3: "AS_PATH", 4: "COMMUNITY", 5: "EXT_COMMUNITY", 6: "LARGE_COMMUNITY", } var DefinedType_value = map[string]int32{ "PREFIX": 0, "NEIGHBOR": 1, "TAG": 2, "AS_PATH": 3, "COMMUNITY": 4, "EXT_COMMUNITY": 5, "LARGE_COMMUNITY": 6, } func (x DefinedType) String() string { return proto.EnumName(DefinedType_name, int32(x)) } func (DefinedType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } type MatchType int32 const ( MatchType_ANY MatchType = 0 MatchType_ALL MatchType = 1 MatchType_INVERT MatchType = 2 ) var MatchType_name = map[int32]string{ 0: "ANY", 1: "ALL", 2: "INVERT", } var MatchType_value = map[string]int32{ "ANY": 0, "ALL": 1, "INVERT": 2, } func (x MatchType) String() string { return proto.EnumName(MatchType_name, int32(x)) } func (MatchType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } type AsPathLengthType int32 const ( AsPathLengthType_EQ AsPathLengthType = 0 AsPathLengthType_GE AsPathLengthType = 1 AsPathLengthType_LE AsPathLengthType = 2 ) var AsPathLengthType_name = map[int32]string{ 0: "EQ", 1: "GE", 2: "LE", } var AsPathLengthType_value = map[string]int32{ "EQ": 0, "GE": 1, "LE": 2, } func (x AsPathLengthType) String() string { return proto.EnumName(AsPathLengthType_name, int32(x)) } func (AsPathLengthType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } type RouteAction int32 const ( RouteAction_NONE RouteAction = 0 RouteAction_ACCEPT RouteAction = 1 RouteAction_REJECT RouteAction = 2 ) var RouteAction_name = map[int32]string{ 0: "NONE", 1: "ACCEPT", 2: "REJECT", } var RouteAction_value = map[string]int32{ "NONE": 0, "ACCEPT": 1, "REJECT": 2, } func (x RouteAction) String() string { return proto.EnumName(RouteAction_name, int32(x)) } func (RouteAction) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } type CommunityActionType int32 const ( CommunityActionType_COMMUNITY_ADD CommunityActionType = 0 CommunityActionType_COMMUNITY_REMOVE CommunityActionType = 1 CommunityActionType_COMMUNITY_REPLACE CommunityActionType = 2 ) var CommunityActionType_name = map[int32]string{ 0: "COMMUNITY_ADD", 1: "COMMUNITY_REMOVE", 2: "COMMUNITY_REPLACE", } var CommunityActionType_value = map[string]int32{ "COMMUNITY_ADD": 0, "COMMUNITY_REMOVE": 1, "COMMUNITY_REPLACE": 2, } func (x CommunityActionType) String() string { return proto.EnumName(CommunityActionType_name, int32(x)) } func (CommunityActionType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } type MedActionType int32 const ( MedActionType_MED_MOD MedActionType = 0 MedActionType_MED_REPLACE MedActionType = 1 ) var MedActionType_name = map[int32]string{ 0: "MED_MOD", 1: "MED_REPLACE", } var MedActionType_value = map[string]int32{ "MED_MOD": 0, "MED_REPLACE": 1, } func (x MedActionType) String() string { return proto.EnumName(MedActionType_name, int32(x)) } func (MedActionType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } type PolicyType int32 const ( PolicyType_IN PolicyType = 0 PolicyType_IMPORT PolicyType = 1 PolicyType_EXPORT PolicyType = 2 ) var PolicyType_name = map[int32]string{ 0: "IN", 1: "IMPORT", 2: "EXPORT", } var PolicyType_value = map[string]int32{ "IN": 0, "IMPORT": 1, "EXPORT": 2, } func (x PolicyType) String() string { return proto.EnumName(PolicyType_name, int32(x)) } func (PolicyType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } type SoftResetNeighborRequest_SoftResetDirection int32 const ( SoftResetNeighborRequest_IN SoftResetNeighborRequest_SoftResetDirection = 0 SoftResetNeighborRequest_OUT SoftResetNeighborRequest_SoftResetDirection = 1 SoftResetNeighborRequest_BOTH SoftResetNeighborRequest_SoftResetDirection = 2 ) var SoftResetNeighborRequest_SoftResetDirection_name = map[int32]string{ 0: "IN", 1: "OUT", 2: "BOTH", } var SoftResetNeighborRequest_SoftResetDirection_value = map[string]int32{ "IN": 0, "OUT": 1, "BOTH": 2, } func (x SoftResetNeighborRequest_SoftResetDirection) String() string { return proto.EnumName(SoftResetNeighborRequest_SoftResetDirection_name, int32(x)) } func (SoftResetNeighborRequest_SoftResetDirection) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{13, 0} } type AddBmpRequest_MonitoringPolicy int32 const ( AddBmpRequest_PRE AddBmpRequest_MonitoringPolicy = 0 AddBmpRequest_POST AddBmpRequest_MonitoringPolicy = 1 AddBmpRequest_BOTH AddBmpRequest_MonitoringPolicy = 2 AddBmpRequest_LOCAL AddBmpRequest_MonitoringPolicy = 3 AddBmpRequest_ALL AddBmpRequest_MonitoringPolicy = 4 ) var AddBmpRequest_MonitoringPolicy_name = map[int32]string{ 0: "PRE", 1: "POST", 2: "BOTH", 3: "LOCAL", 4: "ALL", } var AddBmpRequest_MonitoringPolicy_value = map[string]int32{ "PRE": 0, "POST": 1, "BOTH": 2, "LOCAL": 3, "ALL": 4, } func (x AddBmpRequest_MonitoringPolicy) String() string { return proto.EnumName(AddBmpRequest_MonitoringPolicy_name, int32(x)) } func (AddBmpRequest_MonitoringPolicy) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{27, 0} } type RPKIValidation_State int32 const ( RPKIValidation_STATE_NONE RPKIValidation_State = 0 RPKIValidation_STATE_NOT_FOUND RPKIValidation_State = 1 RPKIValidation_STATE_VALID RPKIValidation_State = 2 RPKIValidation_STATE_INVALID RPKIValidation_State = 3 ) var RPKIValidation_State_name = map[int32]string{ 0: "STATE_NONE", 1: "STATE_NOT_FOUND", 2: "STATE_VALID", 3: "STATE_INVALID", } var RPKIValidation_State_value = map[string]int32{ "STATE_NONE": 0, "STATE_NOT_FOUND": 1, "STATE_VALID": 2, "STATE_INVALID": 3, } func (x RPKIValidation_State) String() string { return proto.EnumName(RPKIValidation_State_name, int32(x)) } func (RPKIValidation_State) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{95, 0} } type RPKIValidation_Reason int32 const ( RPKIValidation_REASOT_NONE RPKIValidation_Reason = 0 RPKIValidation_REASON_AS RPKIValidation_Reason = 1 RPKIValidation_REASON_LENGTH RPKIValidation_Reason = 2 ) var RPKIValidation_Reason_name = map[int32]string{ 0: "REASOT_NONE", 1: "REASON_AS", 2: "REASON_LENGTH", } var RPKIValidation_Reason_value = map[string]int32{ "REASOT_NONE": 0, "REASON_AS": 1, "REASON_LENGTH": 2, } func (x RPKIValidation_Reason) String() string { return proto.EnumName(RPKIValidation_Reason_name, int32(x)) } func (RPKIValidation_Reason) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{95, 1} } type PeerConf_RemovePrivateAs int32 const ( PeerConf_NONE PeerConf_RemovePrivateAs = 0 PeerConf_ALL PeerConf_RemovePrivateAs = 1 PeerConf_REPLACE PeerConf_RemovePrivateAs = 2 ) var PeerConf_RemovePrivateAs_name = map[int32]string{ 0: "NONE", 1: "ALL", 2: "REPLACE", } var PeerConf_RemovePrivateAs_value = map[string]int32{ "NONE": 0, "ALL": 1, "REPLACE": 2, } func (x PeerConf_RemovePrivateAs) String() string { return proto.EnumName(PeerConf_RemovePrivateAs_name, int32(x)) } func (PeerConf_RemovePrivateAs) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{108, 0} } type PeerState_AdminState int32 const ( PeerState_UP PeerState_AdminState = 0 PeerState_DOWN PeerState_AdminState = 1 PeerState_PFX_CT PeerState_AdminState = 2 ) var PeerState_AdminState_name = map[int32]string{ 0: "UP", 1: "DOWN", 2: "PFX_CT", } var PeerState_AdminState_value = map[string]int32{ "UP": 0, "DOWN": 1, "PFX_CT": 2, } func (x PeerState_AdminState) String() string { return proto.EnumName(PeerState_AdminState_name, int32(x)) } func (PeerState_AdminState) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{111, 0} } type Conditions_RouteType int32 const ( Conditions_ROUTE_TYPE_NONE Conditions_RouteType = 0 Conditions_ROUTE_TYPE_INTERNAL Conditions_RouteType = 1 Conditions_ROUTE_TYPE_EXTERNAL Conditions_RouteType = 2 Conditions_ROUTE_TYPE_LOCAL Conditions_RouteType = 3 ) var Conditions_RouteType_name = map[int32]string{ 0: "ROUTE_TYPE_NONE", 1: "ROUTE_TYPE_INTERNAL", 2: "ROUTE_TYPE_EXTERNAL", 3: "ROUTE_TYPE_LOCAL", } var Conditions_RouteType_value = map[string]int32{ "ROUTE_TYPE_NONE": 0, "ROUTE_TYPE_INTERNAL": 1, "ROUTE_TYPE_EXTERNAL": 2, "ROUTE_TYPE_LOCAL": 3, } func (x Conditions_RouteType) String() string { return proto.EnumName(Conditions_RouteType_name, int32(x)) } func (Conditions_RouteType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{152, 0} } type GetNeighborRequest struct { EnableAdvertised bool `protobuf:"varint,1,opt,name=enableAdvertised" json:"enableAdvertised,omitempty"` Address string `protobuf:"bytes,2,opt,name=address" json:"address,omitempty"` } func (m *GetNeighborRequest) Reset() { *m = GetNeighborRequest{} } func (m *GetNeighborRequest) String() string { return proto.CompactTextString(m) } func (*GetNeighborRequest) ProtoMessage() {} func (*GetNeighborRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } func (m *GetNeighborRequest) GetEnableAdvertised() bool { if m != nil { return m.EnableAdvertised } return false } func (m *GetNeighborRequest) GetAddress() string { if m != nil { return m.Address } return "" } type GetNeighborResponse struct { Peers []*Peer `protobuf:"bytes,1,rep,name=peers" json:"peers,omitempty"` } func (m *GetNeighborResponse) Reset() { *m = GetNeighborResponse{} } func (m *GetNeighborResponse) String() string { return proto.CompactTextString(m) } func (*GetNeighborResponse) ProtoMessage() {} func (*GetNeighborResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } func (m *GetNeighborResponse) GetPeers() []*Peer { if m != nil { return m.Peers } return nil } type Arguments struct { Resource Resource `protobuf:"varint,1,opt,name=resource,enum=gobgpapi.Resource" json:"resource,omitempty"` Family uint32 `protobuf:"varint,2,opt,name=family" json:"family,omitempty"` Name string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"` Current bool `protobuf:"varint,4,opt,name=current" json:"current,omitempty"` } func (m *Arguments) Reset() { *m = Arguments{} } func (m *Arguments) String() string { return proto.CompactTextString(m) } func (*Arguments) ProtoMessage() {} func (*Arguments) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } func (m *Arguments) GetResource() Resource { if m != nil { return m.Resource } return Resource_GLOBAL } func (m *Arguments) GetFamily() uint32 { if m != nil { return m.Family } return 0 } func (m *Arguments) GetName() string { if m != nil { return m.Name } return "" } func (m *Arguments) GetCurrent() bool { if m != nil { return m.Current } return false } type AddPathRequest struct { Resource Resource `protobuf:"varint,1,opt,name=resource,enum=gobgpapi.Resource" json:"resource,omitempty"` VrfId string `protobuf:"bytes,2,opt,name=vrf_id,json=vrfId" json:"vrf_id,omitempty"` Path *Path `protobuf:"bytes,3,opt,name=path" json:"path,omitempty"` } func (m *AddPathRequest) Reset() { *m = AddPathRequest{} } func (m *AddPathRequest) String() string { return proto.CompactTextString(m) } func (*AddPathRequest) ProtoMessage() {} func (*AddPathRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } func (m *AddPathRequest) GetResource() Resource { if m != nil { return m.Resource } return Resource_GLOBAL } func (m *AddPathRequest) GetVrfId() string { if m != nil { return m.VrfId } return "" } func (m *AddPathRequest) GetPath() *Path { if m != nil { return m.Path } return nil } type AddPathResponse struct { Uuid []byte `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` } func (m *AddPathResponse) Reset() { *m = AddPathResponse{} } func (m *AddPathResponse) String() string { return proto.CompactTextString(m) } func (*AddPathResponse) ProtoMessage() {} func (*AddPathResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } func (m *AddPathResponse) GetUuid() []byte { if m != nil { return m.Uuid } return nil } type DeletePathRequest struct { Resource Resource `protobuf:"varint,1,opt,name=resource,enum=gobgpapi.Resource" json:"resource,omitempty"` VrfId string `protobuf:"bytes,2,opt,name=vrf_id,json=vrfId" json:"vrf_id,omitempty"` Family uint32 `protobuf:"varint,3,opt,name=family" json:"family,omitempty"` Path *Path `protobuf:"bytes,4,opt,name=path" json:"path,omitempty"` Uuid []byte `protobuf:"bytes,5,opt,name=uuid,proto3" json:"uuid,omitempty"` } func (m *DeletePathRequest) Reset() { *m = DeletePathRequest{} } func (m *DeletePathRequest) String() string { return proto.CompactTextString(m) } func (*DeletePathRequest) ProtoMessage() {} func (*DeletePathRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } func (m *DeletePathRequest) GetResource() Resource { if m != nil { return m.Resource } return Resource_GLOBAL } func (m *DeletePathRequest) GetVrfId() string { if m != nil { return m.VrfId } return "" } func (m *DeletePathRequest) GetFamily() uint32 { if m != nil { return m.Family } return 0 } func (m *DeletePathRequest) GetPath() *Path { if m != nil { return m.Path } return nil } func (m *DeletePathRequest) GetUuid() []byte { if m != nil { return m.Uuid } return nil } type DeletePathResponse struct { } func (m *DeletePathResponse) Reset() { *m = DeletePathResponse{} } func (m *DeletePathResponse) String() string { return proto.CompactTextString(m) } func (*DeletePathResponse) ProtoMessage() {} func (*DeletePathResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } type AddNeighborRequest struct { Peer *Peer `protobuf:"bytes,1,opt,name=peer" json:"peer,omitempty"` } func (m *AddNeighborRequest) Reset() { *m = AddNeighborRequest{} } func (m *AddNeighborRequest) String() string { return proto.CompactTextString(m) } func (*AddNeighborRequest) ProtoMessage() {} func (*AddNeighborRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } func (m *AddNeighborRequest) GetPeer() *Peer { if m != nil { return m.Peer } return nil } type AddNeighborResponse struct { } func (m *AddNeighborResponse) Reset() { *m = AddNeighborResponse{} } func (m *AddNeighborResponse) String() string { return proto.CompactTextString(m) } func (*AddNeighborResponse) ProtoMessage() {} func (*AddNeighborResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } type DeleteNeighborRequest struct { Peer *Peer `protobuf:"bytes,1,opt,name=peer" json:"peer,omitempty"` } func (m *DeleteNeighborRequest) Reset() { *m = DeleteNeighborRequest{} } func (m *DeleteNeighborRequest) String() string { return proto.CompactTextString(m) } func (*DeleteNeighborRequest) ProtoMessage() {} func (*DeleteNeighborRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } func (m *DeleteNeighborRequest) GetPeer() *Peer { if m != nil { return m.Peer } return nil } type DeleteNeighborResponse struct { } func (m *DeleteNeighborResponse) Reset() { *m = DeleteNeighborResponse{} } func (m *DeleteNeighborResponse) String() string { return proto.CompactTextString(m) } func (*DeleteNeighborResponse) ProtoMessage() {} func (*DeleteNeighborResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } type ResetNeighborRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` Communication string `protobuf:"bytes,2,opt,name=communication" json:"communication,omitempty"` } func (m *ResetNeighborRequest) Reset() { *m = ResetNeighborRequest{} } func (m *ResetNeighborRequest) String() string { return proto.CompactTextString(m) } func (*ResetNeighborRequest) ProtoMessage() {} func (*ResetNeighborRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } func (m *ResetNeighborRequest) GetAddress() string { if m != nil { return m.Address } return "" } func (m *ResetNeighborRequest) GetCommunication() string { if m != nil { return m.Communication } return "" } type ResetNeighborResponse struct { } func (m *ResetNeighborResponse) Reset() { *m = ResetNeighborResponse{} } func (m *ResetNeighborResponse) String() string { return proto.CompactTextString(m) } func (*ResetNeighborResponse) ProtoMessage() {} func (*ResetNeighborResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } type SoftResetNeighborRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` Direction SoftResetNeighborRequest_SoftResetDirection `protobuf:"varint,2,opt,name=direction,enum=gobgpapi.SoftResetNeighborRequest_SoftResetDirection" json:"direction,omitempty"` } func (m *SoftResetNeighborRequest) Reset() { *m = SoftResetNeighborRequest{} } func (m *SoftResetNeighborRequest) String() string { return proto.CompactTextString(m) } func (*SoftResetNeighborRequest) ProtoMessage() {} func (*SoftResetNeighborRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} } func (m *SoftResetNeighborRequest) GetAddress() string { if m != nil { return m.Address } return "" } func (m *SoftResetNeighborRequest) GetDirection() SoftResetNeighborRequest_SoftResetDirection { if m != nil { return m.Direction } return SoftResetNeighborRequest_IN } type SoftResetNeighborResponse struct { } func (m *SoftResetNeighborResponse) Reset() { *m = SoftResetNeighborResponse{} } func (m *SoftResetNeighborResponse) String() string { return proto.CompactTextString(m) } func (*SoftResetNeighborResponse) ProtoMessage() {} func (*SoftResetNeighborResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } type ShutdownNeighborRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` Communication string `protobuf:"bytes,2,opt,name=communication" json:"communication,omitempty"` } func (m *ShutdownNeighborRequest) Reset() { *m = ShutdownNeighborRequest{} } func (m *ShutdownNeighborRequest) String() string { return proto.CompactTextString(m) } func (*ShutdownNeighborRequest) ProtoMessage() {} func (*ShutdownNeighborRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} } func (m *ShutdownNeighborRequest) GetAddress() string { if m != nil { return m.Address } return "" } func (m *ShutdownNeighborRequest) GetCommunication() string { if m != nil { return m.Communication } return "" } type ShutdownNeighborResponse struct { } func (m *ShutdownNeighborResponse) Reset() { *m = ShutdownNeighborResponse{} } func (m *ShutdownNeighborResponse) String() string { return proto.CompactTextString(m) } func (*ShutdownNeighborResponse) ProtoMessage() {} func (*ShutdownNeighborResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} } type EnableNeighborRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` } func (m *EnableNeighborRequest) Reset() { *m = EnableNeighborRequest{} } func (m *EnableNeighborRequest) String() string { return proto.CompactTextString(m) } func (*EnableNeighborRequest) ProtoMessage() {} func (*EnableNeighborRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} } func (m *EnableNeighborRequest) GetAddress() string { if m != nil { return m.Address } return "" } type EnableNeighborResponse struct { } func (m *EnableNeighborResponse) Reset() { *m = EnableNeighborResponse{} } func (m *EnableNeighborResponse) String() string { return proto.CompactTextString(m) } func (*EnableNeighborResponse) ProtoMessage() {} func (*EnableNeighborResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} } type DisableNeighborRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` Communication string `protobuf:"bytes,2,opt,name=communication" json:"communication,omitempty"` } func (m *DisableNeighborRequest) Reset() { *m = DisableNeighborRequest{} } func (m *DisableNeighborRequest) String() string { return proto.CompactTextString(m) } func (*DisableNeighborRequest) ProtoMessage() {} func (*DisableNeighborRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} } func (m *DisableNeighborRequest) GetAddress() string { if m != nil { return m.Address } return "" } func (m *DisableNeighborRequest) GetCommunication() string { if m != nil { return m.Communication } return "" } type DisableNeighborResponse struct { } func (m *DisableNeighborResponse) Reset() { *m = DisableNeighborResponse{} } func (m *DisableNeighborResponse) String() string { return proto.CompactTextString(m) } func (*DisableNeighborResponse) ProtoMessage() {} func (*DisableNeighborResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} } type EnableMrtRequest struct { DumpType int32 `protobuf:"varint,1,opt,name=dump_type,json=dumpType" json:"dump_type,omitempty"` Filename string `protobuf:"bytes,2,opt,name=filename" json:"filename,omitempty"` Interval uint64 `protobuf:"varint,3,opt,name=interval" json:"interval,omitempty"` } func (m *EnableMrtRequest) Reset() { *m = EnableMrtRequest{} } func (m *EnableMrtRequest) String() string { return proto.CompactTextString(m) } func (*EnableMrtRequest) ProtoMessage() {} func (*EnableMrtRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} } func (m *EnableMrtRequest) GetDumpType() int32 { if m != nil { return m.DumpType } return 0 } func (m *EnableMrtRequest) GetFilename() string { if m != nil { return m.Filename } return "" } func (m *EnableMrtRequest) GetInterval() uint64 { if m != nil { return m.Interval } return 0 } type EnableMrtResponse struct { } func (m *EnableMrtResponse) Reset() { *m = EnableMrtResponse{} } func (m *EnableMrtResponse) String() string { return proto.CompactTextString(m) } func (*EnableMrtResponse) ProtoMessage() {} func (*EnableMrtResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{22} } type DisableMrtRequest struct { } func (m *DisableMrtRequest) Reset() { *m = DisableMrtRequest{} } func (m *DisableMrtRequest) String() string { return proto.CompactTextString(m) } func (*DisableMrtRequest) ProtoMessage() {} func (*DisableMrtRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{23} } type DisableMrtResponse struct { } func (m *DisableMrtResponse) Reset() { *m = DisableMrtResponse{} } func (m *DisableMrtResponse) String() string { return proto.CompactTextString(m) } func (*DisableMrtResponse) ProtoMessage() {} func (*DisableMrtResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{24} } type InjectMrtRequest struct { Resource Resource `protobuf:"varint,1,opt,name=resource,enum=gobgpapi.Resource" json:"resource,omitempty"` VrfId string `protobuf:"bytes,2,opt,name=vrf_id,json=vrfId" json:"vrf_id,omitempty"` Paths []*Path `protobuf:"bytes,3,rep,name=paths" json:"paths,omitempty"` } func (m *InjectMrtRequest) Reset() { *m = InjectMrtRequest{} } func (m *InjectMrtRequest) String() string { return proto.CompactTextString(m) } func (*InjectMrtRequest) ProtoMessage() {} func (*InjectMrtRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{25} } func (m *InjectMrtRequest) GetResource() Resource { if m != nil { return m.Resource } return Resource_GLOBAL } func (m *InjectMrtRequest) GetVrfId() string { if m != nil { return m.VrfId } return "" } func (m *InjectMrtRequest) GetPaths() []*Path { if m != nil { return m.Paths } return nil } type InjectMrtResponse struct { } func (m *InjectMrtResponse) Reset() { *m = InjectMrtResponse{} } func (m *InjectMrtResponse) String() string { return proto.CompactTextString(m) } func (*InjectMrtResponse) ProtoMessage() {} func (*InjectMrtResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{26} } type AddBmpRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` Port uint32 `protobuf:"varint,2,opt,name=port" json:"port,omitempty"` Type AddBmpRequest_MonitoringPolicy `protobuf:"varint,3,opt,name=type,enum=gobgpapi.AddBmpRequest_MonitoringPolicy" json:"type,omitempty"` } func (m *AddBmpRequest) Reset() { *m = AddBmpRequest{} } func (m *AddBmpRequest) String() string { return proto.CompactTextString(m) } func (*AddBmpRequest) ProtoMessage() {} func (*AddBmpRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{27} } func (m *AddBmpRequest) GetAddress() string { if m != nil { return m.Address } return "" } func (m *AddBmpRequest) GetPort() uint32 { if m != nil { return m.Port } return 0 } func (m *AddBmpRequest) GetType() AddBmpRequest_MonitoringPolicy { if m != nil { return m.Type } return AddBmpRequest_PRE } type AddBmpResponse struct { } func (m *AddBmpResponse) Reset() { *m = AddBmpResponse{} } func (m *AddBmpResponse) String() string { return proto.CompactTextString(m) } func (*AddBmpResponse) ProtoMessage() {} func (*AddBmpResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{28} } type DeleteBmpRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` Port uint32 `protobuf:"varint,2,opt,name=port" json:"port,omitempty"` } func (m *DeleteBmpRequest) Reset() { *m = DeleteBmpRequest{} } func (m *DeleteBmpRequest) String() string { return proto.CompactTextString(m) } func (*DeleteBmpRequest) ProtoMessage() {} func (*DeleteBmpRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{29} } func (m *DeleteBmpRequest) GetAddress() string { if m != nil { return m.Address } return "" } func (m *DeleteBmpRequest) GetPort() uint32 { if m != nil { return m.Port } return 0 } type DeleteBmpResponse struct { } func (m *DeleteBmpResponse) Reset() { *m = DeleteBmpResponse{} } func (m *DeleteBmpResponse) String() string { return proto.CompactTextString(m) } func (*DeleteBmpResponse) ProtoMessage() {} func (*DeleteBmpResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{30} } type MonitorRibRequest struct { Table *Table `protobuf:"bytes,1,opt,name=table" json:"table,omitempty"` Current bool `protobuf:"varint,2,opt,name=current" json:"current,omitempty"` } func (m *MonitorRibRequest) Reset() { *m = MonitorRibRequest{} } func (m *MonitorRibRequest) String() string { return proto.CompactTextString(m) } func (*MonitorRibRequest) ProtoMessage() {} func (*MonitorRibRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{31} } func (m *MonitorRibRequest) GetTable() *Table { if m != nil { return m.Table } return nil } func (m *MonitorRibRequest) GetCurrent() bool { if m != nil { return m.Current } return false } type RPKIConf struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` RemotePort string `protobuf:"bytes,2,opt,name=remote_port,json=remotePort" json:"remote_port,omitempty"` } func (m *RPKIConf) Reset() { *m = RPKIConf{} } func (m *RPKIConf) String() string { return proto.CompactTextString(m) } func (*RPKIConf) ProtoMessage() {} func (*RPKIConf) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{32} } func (m *RPKIConf) GetAddress() string { if m != nil { return m.Address } return "" } func (m *RPKIConf) GetRemotePort() string { if m != nil { return m.RemotePort } return "" } type RPKIState struct { Uptime int64 `protobuf:"varint,1,opt,name=uptime" json:"uptime,omitempty"` Downtime int64 `protobuf:"varint,2,opt,name=downtime" json:"downtime,omitempty"` Up bool `protobuf:"varint,3,opt,name=up" json:"up,omitempty"` RecordIpv4 uint32 `protobuf:"varint,4,opt,name=record_ipv4,json=recordIpv4" json:"record_ipv4,omitempty"` RecordIpv6 uint32 `protobuf:"varint,5,opt,name=record_ipv6,json=recordIpv6" json:"record_ipv6,omitempty"` PrefixIpv4 uint32 `protobuf:"varint,6,opt,name=prefix_ipv4,json=prefixIpv4" json:"prefix_ipv4,omitempty"` PrefixIpv6 uint32 `protobuf:"varint,7,opt,name=prefix_ipv6,json=prefixIpv6" json:"prefix_ipv6,omitempty"` Serial uint32 `protobuf:"varint,8,opt,name=serial" json:"serial,omitempty"` ReceivedIpv4 int64 `protobuf:"varint,9,opt,name=received_ipv4,json=receivedIpv4" json:"received_ipv4,omitempty"` ReceivedIpv6 int64 `protobuf:"varint,10,opt,name=received_ipv6,json=receivedIpv6" json:"received_ipv6,omitempty"` SerialNotify int64 `protobuf:"varint,11,opt,name=serial_notify,json=serialNotify" json:"serial_notify,omitempty"` CacheReset int64 `protobuf:"varint,12,opt,name=cache_reset,json=cacheReset" json:"cache_reset,omitempty"` CacheResponse int64 `protobuf:"varint,13,opt,name=cache_response,json=cacheResponse" json:"cache_response,omitempty"` EndOfData int64 `protobuf:"varint,14,opt,name=end_of_data,json=endOfData" json:"end_of_data,omitempty"` Error int64 `protobuf:"varint,15,opt,name=error" json:"error,omitempty"` SerialQuery int64 `protobuf:"varint,16,opt,name=serial_query,json=serialQuery" json:"serial_query,omitempty"` ResetQuery int64 `protobuf:"varint,17,opt,name=reset_query,json=resetQuery" json:"reset_query,omitempty"` } func (m *RPKIState) Reset() { *m = RPKIState{} } func (m *RPKIState) String() string { return proto.CompactTextString(m) } func (*RPKIState) ProtoMessage() {} func (*RPKIState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{33} } func (m *RPKIState) GetUptime() int64 { if m != nil { return m.Uptime } return 0 } func (m *RPKIState) GetDowntime() int64 { if m != nil { return m.Downtime } return 0 } func (m *RPKIState) GetUp() bool { if m != nil { return m.Up } return false } func (m *RPKIState) GetRecordIpv4() uint32 { if m != nil { return m.RecordIpv4 } return 0 } func (m *RPKIState) GetRecordIpv6() uint32 { if m != nil { return m.RecordIpv6 } return 0 } func (m *RPKIState) GetPrefixIpv4() uint32 { if m != nil { return m.PrefixIpv4 } return 0 } func (m *RPKIState) GetPrefixIpv6() uint32 { if m != nil { return m.PrefixIpv6 } return 0 } func (m *RPKIState) GetSerial() uint32 { if m != nil { return m.Serial } return 0 } func (m *RPKIState) GetReceivedIpv4() int64 { if m != nil { return m.ReceivedIpv4 } return 0 } func (m *RPKIState) GetReceivedIpv6() int64 { if m != nil { return m.ReceivedIpv6 } return 0 } func (m *RPKIState) GetSerialNotify() int64 { if m != nil { return m.SerialNotify } return 0 } func (m *RPKIState) GetCacheReset() int64 { if m != nil { return m.CacheReset } return 0 } func (m *RPKIState) GetCacheResponse() int64 { if m != nil { return m.CacheResponse } return 0 } func (m *RPKIState) GetEndOfData() int64 { if m != nil { return m.EndOfData } return 0 } func (m *RPKIState) GetError() int64 { if m != nil { return m.Error } return 0 } func (m *RPKIState) GetSerialQuery() int64 { if m != nil { return m.SerialQuery } return 0 } func (m *RPKIState) GetResetQuery() int64 { if m != nil { return m.ResetQuery } return 0 } type Rpki struct { Conf *RPKIConf `protobuf:"bytes,1,opt,name=conf" json:"conf,omitempty"` State *RPKIState `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` } func (m *Rpki) Reset() { *m = Rpki{} } func (m *Rpki) String() string { return proto.CompactTextString(m) } func (*Rpki) ProtoMessage() {} func (*Rpki) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{34} } func (m *Rpki) GetConf() *RPKIConf { if m != nil { return m.Conf } return nil } func (m *Rpki) GetState() *RPKIState { if m != nil { return m.State } return nil } type GetRpkiRequest struct { Family uint32 `protobuf:"varint,1,opt,name=family" json:"family,omitempty"` } func (m *GetRpkiRequest) Reset() { *m = GetRpkiRequest{} } func (m *GetRpkiRequest) String() string { return proto.CompactTextString(m) } func (*GetRpkiRequest) ProtoMessage() {} func (*GetRpkiRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{35} } func (m *GetRpkiRequest) GetFamily() uint32 { if m != nil { return m.Family } return 0 } type GetRpkiResponse struct { Servers []*Rpki `protobuf:"bytes,1,rep,name=servers" json:"servers,omitempty"` } func (m *GetRpkiResponse) Reset() { *m = GetRpkiResponse{} } func (m *GetRpkiResponse) String() string { return proto.CompactTextString(m) } func (*GetRpkiResponse) ProtoMessage() {} func (*GetRpkiResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{36} } func (m *GetRpkiResponse) GetServers() []*Rpki { if m != nil { return m.Servers } return nil } type AddRpkiRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` Port uint32 `protobuf:"varint,2,opt,name=port" json:"port,omitempty"` Lifetime int64 `protobuf:"varint,3,opt,name=lifetime" json:"lifetime,omitempty"` } func (m *AddRpkiRequest) Reset() { *m = AddRpkiRequest{} } func (m *AddRpkiRequest) String() string { return proto.CompactTextString(m) } func (*AddRpkiRequest) ProtoMessage() {} func (*AddRpkiRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{37} } func (m *AddRpkiRequest) GetAddress() string { if m != nil { return m.Address } return "" } func (m *AddRpkiRequest) GetPort() uint32 { if m != nil { return m.Port } return 0 } func (m *AddRpkiRequest) GetLifetime() int64 { if m != nil { return m.Lifetime } return 0 } type AddRpkiResponse struct { } func (m *AddRpkiResponse) Reset() { *m = AddRpkiResponse{} } func (m *AddRpkiResponse) String() string { return proto.CompactTextString(m) } func (*AddRpkiResponse) ProtoMessage() {} func (*AddRpkiResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{38} } type DeleteRpkiRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` Port uint32 `protobuf:"varint,2,opt,name=port" json:"port,omitempty"` } func (m *DeleteRpkiRequest) Reset() { *m = DeleteRpkiRequest{} } func (m *DeleteRpkiRequest) String() string { return proto.CompactTextString(m) } func (*DeleteRpkiRequest) ProtoMessage() {} func (*DeleteRpkiRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{39} } func (m *DeleteRpkiRequest) GetAddress() string { if m != nil { return m.Address } return "" } func (m *DeleteRpkiRequest) GetPort() uint32 { if m != nil { return m.Port } return 0 } type DeleteRpkiResponse struct { } func (m *DeleteRpkiResponse) Reset() { *m = DeleteRpkiResponse{} } func (m *DeleteRpkiResponse) String() string { return proto.CompactTextString(m) } func (*DeleteRpkiResponse) ProtoMessage() {} func (*DeleteRpkiResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{40} } type EnableRpkiRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` } func (m *EnableRpkiRequest) Reset() { *m = EnableRpkiRequest{} } func (m *EnableRpkiRequest) String() string { return proto.CompactTextString(m) } func (*EnableRpkiRequest) ProtoMessage() {} func (*EnableRpkiRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{41} } func (m *EnableRpkiRequest) GetAddress() string { if m != nil { return m.Address } return "" } type EnableRpkiResponse struct { } func (m *EnableRpkiResponse) Reset() { *m = EnableRpkiResponse{} } func (m *EnableRpkiResponse) String() string { return proto.CompactTextString(m) } func (*EnableRpkiResponse) ProtoMessage() {} func (*EnableRpkiResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{42} } type DisableRpkiRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` } func (m *DisableRpkiRequest) Reset() { *m = DisableRpkiRequest{} } func (m *DisableRpkiRequest) String() string { return proto.CompactTextString(m) } func (*DisableRpkiRequest) ProtoMessage() {} func (*DisableRpkiRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{43} } func (m *DisableRpkiRequest) GetAddress() string { if m != nil { return m.Address } return "" } type DisableRpkiResponse struct { } func (m *DisableRpkiResponse) Reset() { *m = DisableRpkiResponse{} } func (m *DisableRpkiResponse) String() string { return proto.CompactTextString(m) } func (*DisableRpkiResponse) ProtoMessage() {} func (*DisableRpkiResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{44} } type ResetRpkiRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` } func (m *ResetRpkiRequest) Reset() { *m = ResetRpkiRequest{} } func (m *ResetRpkiRequest) String() string { return proto.CompactTextString(m) } func (*ResetRpkiRequest) ProtoMessage() {} func (*ResetRpkiRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{45} } func (m *ResetRpkiRequest) GetAddress() string { if m != nil { return m.Address } return "" } type ResetRpkiResponse struct { } func (m *ResetRpkiResponse) Reset() { *m = ResetRpkiResponse{} } func (m *ResetRpkiResponse) String() string { return proto.CompactTextString(m) } func (*ResetRpkiResponse) ProtoMessage() {} func (*ResetRpkiResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{46} } type SoftResetRpkiRequest struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` } func (m *SoftResetRpkiRequest) Reset() { *m = SoftResetRpkiRequest{} } func (m *SoftResetRpkiRequest) String() string { return proto.CompactTextString(m) } func (*SoftResetRpkiRequest) ProtoMessage() {} func (*SoftResetRpkiRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{47} } func (m *SoftResetRpkiRequest) GetAddress() string { if m != nil { return m.Address } return "" } type SoftResetRpkiResponse struct { } func (m *SoftResetRpkiResponse) Reset() { *m = SoftResetRpkiResponse{} } func (m *SoftResetRpkiResponse) String() string { return proto.CompactTextString(m) } func (*SoftResetRpkiResponse) ProtoMessage() {} func (*SoftResetRpkiResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{48} } type EnableZebraRequest struct { Url string `protobuf:"bytes,1,opt,name=url" json:"url,omitempty"` RouteTypes []string `protobuf:"bytes,2,rep,name=route_types,json=routeTypes" json:"route_types,omitempty"` Version uint32 `protobuf:"varint,3,opt,name=version" json:"version,omitempty"` NexthopTriggerEnable bool `protobuf:"varint,4,opt,name=nexthop_trigger_enable,json=nexthopTriggerEnable" json:"nexthop_trigger_enable,omitempty"` NexthopTriggerDelay uint32 `protobuf:"varint,5,opt,name=nexthop_trigger_delay,json=nexthopTriggerDelay" json:"nexthop_trigger_delay,omitempty"` } func (m *EnableZebraRequest) Reset() { *m = EnableZebraRequest{} } func (m *EnableZebraRequest) String() string { return proto.CompactTextString(m) } func (*EnableZebraRequest) ProtoMessage() {} func (*EnableZebraRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{49} } func (m *EnableZebraRequest) GetUrl() string { if m != nil { return m.Url } return "" } func (m *EnableZebraRequest) GetRouteTypes() []string { if m != nil { return m.RouteTypes } return nil } func (m *EnableZebraRequest) GetVersion() uint32 { if m != nil { return m.Version } return 0 } func (m *EnableZebraRequest) GetNexthopTriggerEnable() bool { if m != nil { return m.NexthopTriggerEnable } return false } func (m *EnableZebraRequest) GetNexthopTriggerDelay() uint32 { if m != nil { return m.NexthopTriggerDelay } return 0 } type EnableZebraResponse struct { } func (m *EnableZebraResponse) Reset() { *m = EnableZebraResponse{} } func (m *EnableZebraResponse) String() string { return proto.CompactTextString(m) } func (*EnableZebraResponse) ProtoMessage() {} func (*EnableZebraResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{50} } type GetVrfRequest struct { } func (m *GetVrfRequest) Reset() { *m = GetVrfRequest{} } func (m *GetVrfRequest) String() string { return proto.CompactTextString(m) } func (*GetVrfRequest) ProtoMessage() {} func (*GetVrfRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{51} } type GetVrfResponse struct { Vrfs []*Vrf `protobuf:"bytes,1,rep,name=vrfs" json:"vrfs,omitempty"` } func (m *GetVrfResponse) Reset() { *m = GetVrfResponse{} } func (m *GetVrfResponse) String() string { return proto.CompactTextString(m) } func (*GetVrfResponse) ProtoMessage() {} func (*GetVrfResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{52} } func (m *GetVrfResponse) GetVrfs() []*Vrf { if m != nil { return m.Vrfs } return nil } type AddVrfRequest struct { Vrf *Vrf `protobuf:"bytes,1,opt,name=vrf" json:"vrf,omitempty"` } func (m *AddVrfRequest) Reset() { *m = AddVrfRequest{} } func (m *AddVrfRequest) String() string { return proto.CompactTextString(m) } func (*AddVrfRequest) ProtoMessage() {} func (*AddVrfRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{53} } func (m *AddVrfRequest) GetVrf() *Vrf { if m != nil { return m.Vrf } return nil } type AddVrfResponse struct { } func (m *AddVrfResponse) Reset() { *m = AddVrfResponse{} } func (m *AddVrfResponse) String() string { return proto.CompactTextString(m) } func (*AddVrfResponse) ProtoMessage() {} func (*AddVrfResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{54} } type DeleteVrfRequest struct { Vrf *Vrf `protobuf:"bytes,1,opt,name=vrf" json:"vrf,omitempty"` } func (m *DeleteVrfRequest) Reset() { *m = DeleteVrfRequest{} } func (m *DeleteVrfRequest) String() string { return proto.CompactTextString(m) } func (*DeleteVrfRequest) ProtoMessage() {} func (*DeleteVrfRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{55} } func (m *DeleteVrfRequest) GetVrf() *Vrf { if m != nil { return m.Vrf } return nil } type DeleteVrfResponse struct { } func (m *DeleteVrfResponse) Reset() { *m = DeleteVrfResponse{} } func (m *DeleteVrfResponse) String() string { return proto.CompactTextString(m) } func (*DeleteVrfResponse) ProtoMessage() {} func (*DeleteVrfResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{56} } type GetDefinedSetRequest struct { Type DefinedType `protobuf:"varint,1,opt,name=type,enum=gobgpapi.DefinedType" json:"type,omitempty"` Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` } func (m *GetDefinedSetRequest) Reset() { *m = GetDefinedSetRequest{} } func (m *GetDefinedSetRequest) String() string { return proto.CompactTextString(m) } func (*GetDefinedSetRequest) ProtoMessage() {} func (*GetDefinedSetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{57} } func (m *GetDefinedSetRequest) GetType() DefinedType { if m != nil { return m.Type } return DefinedType_PREFIX } func (m *GetDefinedSetRequest) GetName() string { if m != nil { return m.Name } return "" } type GetDefinedSetResponse struct { Sets []*DefinedSet `protobuf:"bytes,1,rep,name=sets" json:"sets,omitempty"` } func (m *GetDefinedSetResponse) Reset() { *m = GetDefinedSetResponse{} } func (m *GetDefinedSetResponse) String() string { return proto.CompactTextString(m) } func (*GetDefinedSetResponse) ProtoMessage() {} func (*GetDefinedSetResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{58} } func (m *GetDefinedSetResponse) GetSets() []*DefinedSet { if m != nil { return m.Sets } return nil } type AddDefinedSetRequest struct { Set *DefinedSet `protobuf:"bytes,1,opt,name=set" json:"set,omitempty"` } func (m *AddDefinedSetRequest) Reset() { *m = AddDefinedSetRequest{} } func (m *AddDefinedSetRequest) String() string { return proto.CompactTextString(m) } func (*AddDefinedSetRequest) ProtoMessage() {} func (*AddDefinedSetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{59} } func (m *AddDefinedSetRequest) GetSet() *DefinedSet { if m != nil { return m.Set } return nil } type AddDefinedSetResponse struct { } func (m *AddDefinedSetResponse) Reset() { *m = AddDefinedSetResponse{} } func (m *AddDefinedSetResponse) String() string { return proto.CompactTextString(m) } func (*AddDefinedSetResponse) ProtoMessage() {} func (*AddDefinedSetResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{60} } type DeleteDefinedSetRequest struct { Set *DefinedSet `protobuf:"bytes,1,opt,name=set" json:"set,omitempty"` All bool `protobuf:"varint,2,opt,name=all" json:"all,omitempty"` } func (m *DeleteDefinedSetRequest) Reset() { *m = DeleteDefinedSetRequest{} } func (m *DeleteDefinedSetRequest) String() string { return proto.CompactTextString(m) } func (*DeleteDefinedSetRequest) ProtoMessage() {} func (*DeleteDefinedSetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{61} } func (m *DeleteDefinedSetRequest) GetSet() *DefinedSet { if m != nil { return m.Set } return nil } func (m *DeleteDefinedSetRequest) GetAll() bool { if m != nil { return m.All } return false } type DeleteDefinedSetResponse struct { } func (m *DeleteDefinedSetResponse) Reset() { *m = DeleteDefinedSetResponse{} } func (m *DeleteDefinedSetResponse) String() string { return proto.CompactTextString(m) } func (*DeleteDefinedSetResponse) ProtoMessage() {} func (*DeleteDefinedSetResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{62} } type ReplaceDefinedSetRequest struct { Set *DefinedSet `protobuf:"bytes,1,opt,name=set" json:"set,omitempty"` } func (m *ReplaceDefinedSetRequest) Reset() { *m = ReplaceDefinedSetRequest{} } func (m *ReplaceDefinedSetRequest) String() string { return proto.CompactTextString(m) } func (*ReplaceDefinedSetRequest) ProtoMessage() {} func (*ReplaceDefinedSetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{63} } func (m *ReplaceDefinedSetRequest) GetSet() *DefinedSet { if m != nil { return m.Set } return nil } type ReplaceDefinedSetResponse struct { } func (m *ReplaceDefinedSetResponse) Reset() { *m = ReplaceDefinedSetResponse{} } func (m *ReplaceDefinedSetResponse) String() string { return proto.CompactTextString(m) } func (*ReplaceDefinedSetResponse) ProtoMessage() {} func (*ReplaceDefinedSetResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{64} } type GetStatementRequest struct { } func (m *GetStatementRequest) Reset() { *m = GetStatementRequest{} } func (m *GetStatementRequest) String() string { return proto.CompactTextString(m) } func (*GetStatementRequest) ProtoMessage() {} func (*GetStatementRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{65} } type GetStatementResponse struct { Statements []*Statement `protobuf:"bytes,1,rep,name=statements" json:"statements,omitempty"` } func (m *GetStatementResponse) Reset() { *m = GetStatementResponse{} } func (m *GetStatementResponse) String() string { return proto.CompactTextString(m) } func (*GetStatementResponse) ProtoMessage() {} func (*GetStatementResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{66} } func (m *GetStatementResponse) GetStatements() []*Statement { if m != nil { return m.Statements } return nil } type AddStatementRequest struct { Statement *Statement `protobuf:"bytes,1,opt,name=statement" json:"statement,omitempty"` } func (m *AddStatementRequest) Reset() { *m = AddStatementRequest{} } func (m *AddStatementRequest) String() string { return proto.CompactTextString(m) } func (*AddStatementRequest) ProtoMessage() {} func (*AddStatementRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{67} } func (m *AddStatementRequest) GetStatement() *Statement { if m != nil { return m.Statement } return nil } type AddStatementResponse struct { } func (m *AddStatementResponse) Reset() { *m = AddStatementResponse{} } func (m *AddStatementResponse) String() string { return proto.CompactTextString(m) } func (*AddStatementResponse) ProtoMessage() {} func (*AddStatementResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{68} } type DeleteStatementRequest struct { Statement *Statement `protobuf:"bytes,1,opt,name=statement" json:"statement,omitempty"` All bool `protobuf:"varint,2,opt,name=all" json:"all,omitempty"` } func (m *DeleteStatementRequest) Reset() { *m = DeleteStatementRequest{} } func (m *DeleteStatementRequest) String() string { return proto.CompactTextString(m) } func (*DeleteStatementRequest) ProtoMessage() {} func (*DeleteStatementRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{69} } func (m *DeleteStatementRequest) GetStatement() *Statement { if m != nil { return m.Statement } return nil } func (m *DeleteStatementRequest) GetAll() bool { if m != nil { return m.All } return false } type DeleteStatementResponse struct { } func (m *DeleteStatementResponse) Reset() { *m = DeleteStatementResponse{} } func (m *DeleteStatementResponse) String() string { return proto.CompactTextString(m) } func (*DeleteStatementResponse) ProtoMessage() {} func (*DeleteStatementResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{70} } type ReplaceStatementRequest struct { Statement *Statement `protobuf:"bytes,1,opt,name=statement" json:"statement,omitempty"` } func (m *ReplaceStatementRequest) Reset() { *m = ReplaceStatementRequest{} } func (m *ReplaceStatementRequest) String() string { return proto.CompactTextString(m) } func (*ReplaceStatementRequest) ProtoMessage() {} func (*ReplaceStatementRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{71} } func (m *ReplaceStatementRequest) GetStatement() *Statement { if m != nil { return m.Statement } return nil } type ReplaceStatementResponse struct { } func (m *ReplaceStatementResponse) Reset() { *m = ReplaceStatementResponse{} } func (m *ReplaceStatementResponse) String() string { return proto.CompactTextString(m) } func (*ReplaceStatementResponse) ProtoMessage() {} func (*ReplaceStatementResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{72} } type GetPolicyRequest struct { } func (m *GetPolicyRequest) Reset() { *m = GetPolicyRequest{} } func (m *GetPolicyRequest) String() string { return proto.CompactTextString(m) } func (*GetPolicyRequest) ProtoMessage() {} func (*GetPolicyRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{73} } type GetPolicyResponse struct { Policies []*Policy `protobuf:"bytes,1,rep,name=policies" json:"policies,omitempty"` } func (m *GetPolicyResponse) Reset() { *m = GetPolicyResponse{} } func (m *GetPolicyResponse) String() string { return proto.CompactTextString(m) } func (*GetPolicyResponse) ProtoMessage() {} func (*GetPolicyResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{74} } func (m *GetPolicyResponse) GetPolicies() []*Policy { if m != nil { return m.Policies } return nil } type AddPolicyRequest struct { Policy *Policy `protobuf:"bytes,1,opt,name=policy" json:"policy,omitempty"` // if this flag is set, gobgpd won't define new statements // but refer existing statements using statement's names in this arguments. ReferExistingStatements bool `protobuf:"varint,2,opt,name=refer_existing_statements,json=referExistingStatements" json:"refer_existing_statements,omitempty"` } func (m *AddPolicyRequest) Reset() { *m = AddPolicyRequest{} } func (m *AddPolicyRequest) String() string { return proto.CompactTextString(m) } func (*AddPolicyRequest) ProtoMessage() {} func (*AddPolicyRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{75} } func (m *AddPolicyRequest) GetPolicy() *Policy { if m != nil { return m.Policy } return nil } func (m *AddPolicyRequest) GetReferExistingStatements() bool { if m != nil { return m.ReferExistingStatements } return false } type AddPolicyResponse struct { } func (m *AddPolicyResponse) Reset() { *m = AddPolicyResponse{} } func (m *AddPolicyResponse) String() string { return proto.CompactTextString(m) } func (*AddPolicyResponse) ProtoMessage() {} func (*AddPolicyResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{76} } type DeletePolicyRequest struct { Policy *Policy `protobuf:"bytes,1,opt,name=policy" json:"policy,omitempty"` // if this flag is set, gobgpd won't delete any statements // even if some statements get not used by any policy by this operation. PreserveStatements bool `protobuf:"varint,2,opt,name=preserve_statements,json=preserveStatements" json:"preserve_statements,omitempty"` All bool `protobuf:"varint,3,opt,name=all" json:"all,omitempty"` } func (m *DeletePolicyRequest) Reset() { *m = DeletePolicyRequest{} } func (m *DeletePolicyRequest) String() string { return proto.CompactTextString(m) } func (*DeletePolicyRequest) ProtoMessage() {} func (*DeletePolicyRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{77} } func (m *DeletePolicyRequest) GetPolicy() *Policy { if m != nil { return m.Policy } return nil } func (m *DeletePolicyRequest) GetPreserveStatements() bool { if m != nil { return m.PreserveStatements } return false } func (m *DeletePolicyRequest) GetAll() bool { if m != nil { return m.All } return false } type DeletePolicyResponse struct { } func (m *DeletePolicyResponse) Reset() { *m = DeletePolicyResponse{} } func (m *DeletePolicyResponse) String() string { return proto.CompactTextString(m) } func (*DeletePolicyResponse) ProtoMessage() {} func (*DeletePolicyResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{78} } type ReplacePolicyRequest struct { Policy *Policy `protobuf:"bytes,1,opt,name=policy" json:"policy,omitempty"` // if this flag is set, gobgpd won't define new statements // but refer existing statements using statement's names in this arguments. ReferExistingStatements bool `protobuf:"varint,2,opt,name=refer_existing_statements,json=referExistingStatements" json:"refer_existing_statements,omitempty"` // if this flag is set, gobgpd won't delete any statements // even if some statements get not used by any policy by this operation. PreserveStatements bool `protobuf:"varint,3,opt,name=preserve_statements,json=preserveStatements" json:"preserve_statements,omitempty"` } func (m *ReplacePolicyRequest) Reset() { *m = ReplacePolicyRequest{} } func (m *ReplacePolicyRequest) String() string { return proto.CompactTextString(m) } func (*ReplacePolicyRequest) ProtoMessage() {} func (*ReplacePolicyRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{79} } func (m *ReplacePolicyRequest) GetPolicy() *Policy { if m != nil { return m.Policy } return nil } func (m *ReplacePolicyRequest) GetReferExistingStatements() bool { if m != nil { return m.ReferExistingStatements } return false } func (m *ReplacePolicyRequest) GetPreserveStatements() bool { if m != nil { return m.PreserveStatements } return false } type ReplacePolicyResponse struct { } func (m *ReplacePolicyResponse) Reset() { *m = ReplacePolicyResponse{} } func (m *ReplacePolicyResponse) String() string { return proto.CompactTextString(m) } func (*ReplacePolicyResponse) ProtoMessage() {} func (*ReplacePolicyResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{80} } type GetPolicyAssignmentRequest struct { Assignment *PolicyAssignment `protobuf:"bytes,1,opt,name=assignment" json:"assignment,omitempty"` } func (m *GetPolicyAssignmentRequest) Reset() { *m = GetPolicyAssignmentRequest{} } func (m *GetPolicyAssignmentRequest) String() string { return proto.CompactTextString(m) } func (*GetPolicyAssignmentRequest) ProtoMessage() {} func (*GetPolicyAssignmentRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{81} } func (m *GetPolicyAssignmentRequest) GetAssignment() *PolicyAssignment { if m != nil { return m.Assignment } return nil } type GetPolicyAssignmentResponse struct { Assignment *PolicyAssignment `protobuf:"bytes,1,opt,name=assignment" json:"assignment,omitempty"` } func (m *GetPolicyAssignmentResponse) Reset() { *m = GetPolicyAssignmentResponse{} } func (m *GetPolicyAssignmentResponse) String() string { return proto.CompactTextString(m) } func (*GetPolicyAssignmentResponse) ProtoMessage() {} func (*GetPolicyAssignmentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{82} } func (m *GetPolicyAssignmentResponse) GetAssignment() *PolicyAssignment { if m != nil { return m.Assignment } return nil } type AddPolicyAssignmentRequest struct { Assignment *PolicyAssignment `protobuf:"bytes,1,opt,name=assignment" json:"assignment,omitempty"` } func (m *AddPolicyAssignmentRequest) Reset() { *m = AddPolicyAssignmentRequest{} } func (m *AddPolicyAssignmentRequest) String() string { return proto.CompactTextString(m) } func (*AddPolicyAssignmentRequest) ProtoMessage() {} func (*AddPolicyAssignmentRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{83} } func (m *AddPolicyAssignmentRequest) GetAssignment() *PolicyAssignment { if m != nil { return m.Assignment } return nil } type AddPolicyAssignmentResponse struct { } func (m *AddPolicyAssignmentResponse) Reset() { *m = AddPolicyAssignmentResponse{} } func (m *AddPolicyAssignmentResponse) String() string { return proto.CompactTextString(m) } func (*AddPolicyAssignmentResponse) ProtoMessage() {} func (*AddPolicyAssignmentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{84} } type DeletePolicyAssignmentRequest struct { Assignment *PolicyAssignment `protobuf:"bytes,1,opt,name=assignment" json:"assignment,omitempty"` All bool `protobuf:"varint,2,opt,name=all" json:"all,omitempty"` } func (m *DeletePolicyAssignmentRequest) Reset() { *m = DeletePolicyAssignmentRequest{} } func (m *DeletePolicyAssignmentRequest) String() string { return proto.CompactTextString(m) } func (*DeletePolicyAssignmentRequest) ProtoMessage() {} func (*DeletePolicyAssignmentRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{85} } func (m *DeletePolicyAssignmentRequest) GetAssignment() *PolicyAssignment { if m != nil { return m.Assignment } return nil } func (m *DeletePolicyAssignmentRequest) GetAll() bool { if m != nil { return m.All } return false } type DeletePolicyAssignmentResponse struct { } func (m *DeletePolicyAssignmentResponse) Reset() { *m = DeletePolicyAssignmentResponse{} } func (m *DeletePolicyAssignmentResponse) String() string { return proto.CompactTextString(m) } func (*DeletePolicyAssignmentResponse) ProtoMessage() {} func (*DeletePolicyAssignmentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{86} } type ReplacePolicyAssignmentRequest struct { Assignment *PolicyAssignment `protobuf:"bytes,1,opt,name=assignment" json:"assignment,omitempty"` } func (m *ReplacePolicyAssignmentRequest) Reset() { *m = ReplacePolicyAssignmentRequest{} } func (m *ReplacePolicyAssignmentRequest) String() string { return proto.CompactTextString(m) } func (*ReplacePolicyAssignmentRequest) ProtoMessage() {} func (*ReplacePolicyAssignmentRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{87} } func (m *ReplacePolicyAssignmentRequest) GetAssignment() *PolicyAssignment { if m != nil { return m.Assignment } return nil } type ReplacePolicyAssignmentResponse struct { } func (m *ReplacePolicyAssignmentResponse) Reset() { *m = ReplacePolicyAssignmentResponse{} } func (m *ReplacePolicyAssignmentResponse) String() string { return proto.CompactTextString(m) } func (*ReplacePolicyAssignmentResponse) ProtoMessage() {} func (*ReplacePolicyAssignmentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{88} } type GetServerRequest struct { } func (m *GetServerRequest) Reset() { *m = GetServerRequest{} } func (m *GetServerRequest) String() string { return proto.CompactTextString(m) } func (*GetServerRequest) ProtoMessage() {} func (*GetServerRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{89} } type GetServerResponse struct { Global *Global `protobuf:"bytes,1,opt,name=global" json:"global,omitempty"` } func (m *GetServerResponse) Reset() { *m = GetServerResponse{} } func (m *GetServerResponse) String() string { return proto.CompactTextString(m) } func (*GetServerResponse) ProtoMessage() {} func (*GetServerResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{90} } func (m *GetServerResponse) GetGlobal() *Global { if m != nil { return m.Global } return nil } type StartServerRequest struct { Global *Global `protobuf:"bytes,1,opt,name=global" json:"global,omitempty"` } func (m *StartServerRequest) Reset() { *m = StartServerRequest{} } func (m *StartServerRequest) String() string { return proto.CompactTextString(m) } func (*StartServerRequest) ProtoMessage() {} func (*StartServerRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{91} } func (m *StartServerRequest) GetGlobal() *Global { if m != nil { return m.Global } return nil } type StartServerResponse struct { } func (m *StartServerResponse) Reset() { *m = StartServerResponse{} } func (m *StartServerResponse) String() string { return proto.CompactTextString(m) } func (*StartServerResponse) ProtoMessage() {} func (*StartServerResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{92} } type StopServerRequest struct { } func (m *StopServerRequest) Reset() { *m = StopServerRequest{} } func (m *StopServerRequest) String() string { return proto.CompactTextString(m) } func (*StopServerRequest) ProtoMessage() {} func (*StopServerRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{93} } type StopServerResponse struct { } func (m *StopServerResponse) Reset() { *m = StopServerResponse{} } func (m *StopServerResponse) String() string { return proto.CompactTextString(m) } func (*StopServerResponse) ProtoMessage() {} func (*StopServerResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{94} } type RPKIValidation struct { State RPKIValidation_State `protobuf:"varint,1,opt,name=state,enum=gobgpapi.RPKIValidation_State" json:"state,omitempty"` Reason RPKIValidation_Reason `protobuf:"varint,2,opt,name=reason,enum=gobgpapi.RPKIValidation_Reason" json:"reason,omitempty"` Matched []*Roa `protobuf:"bytes,3,rep,name=matched" json:"matched,omitempty"` UnmatchedAs []*Roa `protobuf:"bytes,4,rep,name=unmatched_as,json=unmatchedAs" json:"unmatched_as,omitempty"` UnmatchedLength []*Roa `protobuf:"bytes,5,rep,name=unmatched_length,json=unmatchedLength" json:"unmatched_length,omitempty"` } func (m *RPKIValidation) Reset() { *m = RPKIValidation{} } func (m *RPKIValidation) String() string { return proto.CompactTextString(m) } func (*RPKIValidation) ProtoMessage() {} func (*RPKIValidation) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{95} } func (m *RPKIValidation) GetState() RPKIValidation_State { if m != nil { return m.State } return RPKIValidation_STATE_NONE } func (m *RPKIValidation) GetReason() RPKIValidation_Reason { if m != nil { return m.Reason } return RPKIValidation_REASOT_NONE } func (m *RPKIValidation) GetMatched() []*Roa { if m != nil { return m.Matched } return nil } func (m *RPKIValidation) GetUnmatchedAs() []*Roa { if m != nil { return m.UnmatchedAs } return nil } func (m *RPKIValidation) GetUnmatchedLength() []*Roa { if m != nil { return m.UnmatchedLength } return nil } type Path struct { Nlri []byte `protobuf:"bytes,1,opt,name=nlri,proto3" json:"nlri,omitempty"` Pattrs [][]byte `protobuf:"bytes,2,rep,name=pattrs,proto3" json:"pattrs,omitempty"` Age int64 `protobuf:"varint,3,opt,name=age" json:"age,omitempty"` Best bool `protobuf:"varint,4,opt,name=best" json:"best,omitempty"` IsWithdraw bool `protobuf:"varint,5,opt,name=is_withdraw,json=isWithdraw" json:"is_withdraw,omitempty"` Validation int32 `protobuf:"varint,6,opt,name=validation" json:"validation,omitempty"` ValidationDetail *RPKIValidation `protobuf:"bytes,7,opt,name=validation_detail,json=validationDetail" json:"validation_detail,omitempty"` NoImplicitWithdraw bool `protobuf:"varint,8,opt,name=no_implicit_withdraw,json=noImplicitWithdraw" json:"no_implicit_withdraw,omitempty"` Family uint32 `protobuf:"varint,9,opt,name=family" json:"family,omitempty"` SourceAsn uint32 `protobuf:"varint,10,opt,name=source_asn,json=sourceAsn" json:"source_asn,omitempty"` SourceId string `protobuf:"bytes,11,opt,name=source_id,json=sourceId" json:"source_id,omitempty"` Filtered bool `protobuf:"varint,12,opt,name=filtered" json:"filtered,omitempty"` Stale bool `protobuf:"varint,13,opt,name=stale" json:"stale,omitempty"` IsFromExternal bool `protobuf:"varint,14,opt,name=is_from_external,json=isFromExternal" json:"is_from_external,omitempty"` NeighborIp string `protobuf:"bytes,15,opt,name=neighbor_ip,json=neighborIp" json:"neighbor_ip,omitempty"` Uuid []byte `protobuf:"bytes,16,opt,name=uuid,proto3" json:"uuid,omitempty"` IsNexthopInvalid bool `protobuf:"varint,17,opt,name=is_nexthop_invalid,json=isNexthopInvalid" json:"is_nexthop_invalid,omitempty"` Identifier uint32 `protobuf:"varint,18,opt,name=identifier" json:"identifier,omitempty"` LocalIdentifier uint32 `protobuf:"varint,19,opt,name=local_identifier,json=localIdentifier" json:"local_identifier,omitempty"` } func (m *Path) Reset() { *m = Path{} } func (m *Path) String() string { return proto.CompactTextString(m) } func (*Path) ProtoMessage() {} func (*Path) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{96} } func (m *Path) GetNlri() []byte { if m != nil { return m.Nlri } return nil } func (m *Path) GetPattrs() [][]byte { if m != nil { return m.Pattrs } return nil } func (m *Path) GetAge() int64 { if m != nil { return m.Age } return 0 } func (m *Path) GetBest() bool { if m != nil { return m.Best } return false } func (m *Path) GetIsWithdraw() bool { if m != nil { return m.IsWithdraw } return false } func (m *Path) GetValidation() int32 { if m != nil { return m.Validation } return 0 } func (m *Path) GetValidationDetail() *RPKIValidation { if m != nil { return m.ValidationDetail } return nil } func (m *Path) GetNoImplicitWithdraw() bool { if m != nil { return m.NoImplicitWithdraw } return false } func (m *Path) GetFamily() uint32 { if m != nil { return m.Family } return 0 } func (m *Path) GetSourceAsn() uint32 { if m != nil { return m.SourceAsn } return 0 } func (m *Path) GetSourceId() string { if m != nil { return m.SourceId } return "" } func (m *Path) GetFiltered() bool { if m != nil { return m.Filtered } return false } func (m *Path) GetStale() bool { if m != nil { return m.Stale } return false } func (m *Path) GetIsFromExternal() bool { if m != nil { return m.IsFromExternal } return false } func (m *Path) GetNeighborIp() string { if m != nil { return m.NeighborIp } return "" } func (m *Path) GetUuid() []byte { if m != nil { return m.Uuid } return nil } func (m *Path) GetIsNexthopInvalid() bool { if m != nil { return m.IsNexthopInvalid } return false } func (m *Path) GetIdentifier() uint32 { if m != nil { return m.Identifier } return 0 } func (m *Path) GetLocalIdentifier() uint32 { if m != nil { return m.LocalIdentifier } return 0 } type Destination struct { Prefix string `protobuf:"bytes,1,opt,name=prefix" json:"prefix,omitempty"` Paths []*Path `protobuf:"bytes,2,rep,name=paths" json:"paths,omitempty"` LongerPrefixes bool `protobuf:"varint,3,opt,name=longer_prefixes,json=longerPrefixes" json:"longer_prefixes,omitempty"` ShorterPrefixes bool `protobuf:"varint,4,opt,name=shorter_prefixes,json=shorterPrefixes" json:"shorter_prefixes,omitempty"` } func (m *Destination) Reset() { *m = Destination{} } func (m *Destination) String() string { return proto.CompactTextString(m) } func (*Destination) ProtoMessage() {} func (*Destination) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{97} } func (m *Destination) GetPrefix() string { if m != nil { return m.Prefix } return "" } func (m *Destination) GetPaths() []*Path { if m != nil { return m.Paths } return nil } func (m *Destination) GetLongerPrefixes() bool { if m != nil { return m.LongerPrefixes } return false } func (m *Destination) GetShorterPrefixes() bool { if m != nil { return m.ShorterPrefixes } return false } type Table struct { Type Resource `protobuf:"varint,1,opt,name=type,enum=gobgpapi.Resource" json:"type,omitempty"` Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` Family uint32 `protobuf:"varint,3,opt,name=family" json:"family,omitempty"` Destinations []*Destination `protobuf:"bytes,4,rep,name=destinations" json:"destinations,omitempty"` PostPolicy bool `protobuf:"varint,5,opt,name=post_policy,json=postPolicy" json:"post_policy,omitempty"` } func (m *Table) Reset() { *m = Table{} } func (m *Table) String() string { return proto.CompactTextString(m) } func (*Table) ProtoMessage() {} func (*Table) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{98} } func (m *Table) GetType() Resource { if m != nil { return m.Type } return Resource_GLOBAL } func (m *Table) GetName() string { if m != nil { return m.Name } return "" } func (m *Table) GetFamily() uint32 { if m != nil { return m.Family } return 0 } func (m *Table) GetDestinations() []*Destination { if m != nil { return m.Destinations } return nil } func (m *Table) GetPostPolicy() bool { if m != nil { return m.PostPolicy } return false } type GetRibRequest struct { Table *Table `protobuf:"bytes,1,opt,name=table" json:"table,omitempty"` } func (m *GetRibRequest) Reset() { *m = GetRibRequest{} } func (m *GetRibRequest) String() string { return proto.CompactTextString(m) } func (*GetRibRequest) ProtoMessage() {} func (*GetRibRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{99} } func (m *GetRibRequest) GetTable() *Table { if m != nil { return m.Table } return nil } type GetRibResponse struct { Table *Table `protobuf:"bytes,1,opt,name=table" json:"table,omitempty"` } func (m *GetRibResponse) Reset() { *m = GetRibResponse{} } func (m *GetRibResponse) String() string { return proto.CompactTextString(m) } func (*GetRibResponse) ProtoMessage() {} func (*GetRibResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{100} } func (m *GetRibResponse) GetTable() *Table { if m != nil { return m.Table } return nil } // API representation of table.LookupPrefix type TableLookupPrefix struct { Prefix string `protobuf:"bytes,1,opt,name=prefix" json:"prefix,omitempty"` LookupOption TableLookupOption `protobuf:"varint,2,opt,name=lookup_option,json=lookupOption,enum=gobgpapi.TableLookupOption" json:"lookup_option,omitempty"` } func (m *TableLookupPrefix) Reset() { *m = TableLookupPrefix{} } func (m *TableLookupPrefix) String() string { return proto.CompactTextString(m) } func (*TableLookupPrefix) ProtoMessage() {} func (*TableLookupPrefix) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{101} } func (m *TableLookupPrefix) GetPrefix() string { if m != nil { return m.Prefix } return "" } func (m *TableLookupPrefix) GetLookupOption() TableLookupOption { if m != nil { return m.LookupOption } return TableLookupOption_LOOKUP_EXACT } type GetPathRequest struct { Type Resource `protobuf:"varint,1,opt,name=type,enum=gobgpapi.Resource" json:"type,omitempty"` Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` Family uint32 `protobuf:"varint,3,opt,name=family" json:"family,omitempty"` Prefixes []*TableLookupPrefix `protobuf:"bytes,4,rep,name=prefixes" json:"prefixes,omitempty"` } func (m *GetPathRequest) Reset() { *m = GetPathRequest{} } func (m *GetPathRequest) String() string { return proto.CompactTextString(m) } func (*GetPathRequest) ProtoMessage() {} func (*GetPathRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{102} } func (m *GetPathRequest) GetType() Resource { if m != nil { return m.Type } return Resource_GLOBAL } func (m *GetPathRequest) GetName() string { if m != nil { return m.Name } return "" } func (m *GetPathRequest) GetFamily() uint32 { if m != nil { return m.Family } return 0 } func (m *GetPathRequest) GetPrefixes() []*TableLookupPrefix { if m != nil { return m.Prefixes } return nil } type ValidateRibRequest struct { Type Resource `protobuf:"varint,1,opt,name=type,enum=gobgpapi.Resource" json:"type,omitempty"` Family uint32 `protobuf:"varint,2,opt,name=family" json:"family,omitempty"` Prefix string `protobuf:"bytes,3,opt,name=prefix" json:"prefix,omitempty"` } func (m *ValidateRibRequest) Reset() { *m = ValidateRibRequest{} } func (m *ValidateRibRequest) String() string { return proto.CompactTextString(m) } func (*ValidateRibRequest) ProtoMessage() {} func (*ValidateRibRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{103} } func (m *ValidateRibRequest) GetType() Resource { if m != nil { return m.Type } return Resource_GLOBAL } func (m *ValidateRibRequest) GetFamily() uint32 { if m != nil { return m.Family } return 0 } func (m *ValidateRibRequest) GetPrefix() string { if m != nil { return m.Prefix } return "" } type ValidateRibResponse struct { } func (m *ValidateRibResponse) Reset() { *m = ValidateRibResponse{} } func (m *ValidateRibResponse) String() string { return proto.CompactTextString(m) } func (*ValidateRibResponse) ProtoMessage() {} func (*ValidateRibResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{104} } type Peer struct { // Note: Regarding to the consistency with OpenConfig model, a list of // address family should be removed from here, and should be configured with // the list of AfiSafi instead. Families []uint32 `protobuf:"varint,1,rep,packed,name=families" json:"families,omitempty"` ApplyPolicy *ApplyPolicy `protobuf:"bytes,2,opt,name=apply_policy,json=applyPolicy" json:"apply_policy,omitempty"` Conf *PeerConf `protobuf:"bytes,3,opt,name=conf" json:"conf,omitempty"` EbgpMultihop *EbgpMultihop `protobuf:"bytes,4,opt,name=ebgp_multihop,json=ebgpMultihop" json:"ebgp_multihop,omitempty"` RouteReflector *RouteReflector `protobuf:"bytes,5,opt,name=route_reflector,json=routeReflector" json:"route_reflector,omitempty"` Info *PeerState `protobuf:"bytes,6,opt,name=info" json:"info,omitempty"` Timers *Timers `protobuf:"bytes,7,opt,name=timers" json:"timers,omitempty"` Transport *Transport `protobuf:"bytes,8,opt,name=transport" json:"transport,omitempty"` RouteServer *RouteServer `protobuf:"bytes,9,opt,name=route_server,json=routeServer" json:"route_server,omitempty"` GracefulRestart *GracefulRestart `protobuf:"bytes,10,opt,name=graceful_restart,json=gracefulRestart" json:"graceful_restart,omitempty"` AfiSafis []*AfiSafi `protobuf:"bytes,11,rep,name=afi_safis,json=afiSafis" json:"afi_safis,omitempty"` AddPaths *AddPaths `protobuf:"bytes,12,opt,name=add_paths,json=addPaths" json:"add_paths,omitempty"` } func (m *Peer) Reset() { *m = Peer{} } func (m *Peer) String() string { return proto.CompactTextString(m) } func (*Peer) ProtoMessage() {} func (*Peer) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{105} } func (m *Peer) GetFamilies() []uint32 { if m != nil { return m.Families } return nil } func (m *Peer) GetApplyPolicy() *ApplyPolicy { if m != nil { return m.ApplyPolicy } return nil } func (m *Peer) GetConf() *PeerConf { if m != nil { return m.Conf } return nil } func (m *Peer) GetEbgpMultihop() *EbgpMultihop { if m != nil { return m.EbgpMultihop } return nil } func (m *Peer) GetRouteReflector() *RouteReflector { if m != nil { return m.RouteReflector } return nil } func (m *Peer) GetInfo() *PeerState { if m != nil { return m.Info } return nil } func (m *Peer) GetTimers() *Timers { if m != nil { return m.Timers } return nil } func (m *Peer) GetTransport() *Transport { if m != nil { return m.Transport } return nil } func (m *Peer) GetRouteServer() *RouteServer { if m != nil { return m.RouteServer } return nil } func (m *Peer) GetGracefulRestart() *GracefulRestart { if m != nil { return m.GracefulRestart } return nil } func (m *Peer) GetAfiSafis() []*AfiSafi { if m != nil { return m.AfiSafis } return nil } func (m *Peer) GetAddPaths() *AddPaths { if m != nil { return m.AddPaths } return nil } type ApplyPolicy struct { InPolicy *PolicyAssignment `protobuf:"bytes,1,opt,name=in_policy,json=inPolicy" json:"in_policy,omitempty"` ExportPolicy *PolicyAssignment `protobuf:"bytes,2,opt,name=export_policy,json=exportPolicy" json:"export_policy,omitempty"` ImportPolicy *PolicyAssignment `protobuf:"bytes,3,opt,name=import_policy,json=importPolicy" json:"import_policy,omitempty"` } func (m *ApplyPolicy) Reset() { *m = ApplyPolicy{} } func (m *ApplyPolicy) String() string { return proto.CompactTextString(m) } func (*ApplyPolicy) ProtoMessage() {} func (*ApplyPolicy) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{106} } func (m *ApplyPolicy) GetInPolicy() *PolicyAssignment { if m != nil { return m.InPolicy } return nil } func (m *ApplyPolicy) GetExportPolicy() *PolicyAssignment { if m != nil { return m.ExportPolicy } return nil } func (m *ApplyPolicy) GetImportPolicy() *PolicyAssignment { if m != nil { return m.ImportPolicy } return nil } type PrefixLimit struct { Family uint32 `protobuf:"varint,1,opt,name=family" json:"family,omitempty"` MaxPrefixes uint32 `protobuf:"varint,2,opt,name=max_prefixes,json=maxPrefixes" json:"max_prefixes,omitempty"` ShutdownThresholdPct uint32 `protobuf:"varint,3,opt,name=shutdown_threshold_pct,json=shutdownThresholdPct" json:"shutdown_threshold_pct,omitempty"` } func (m *PrefixLimit) Reset() { *m = PrefixLimit{} } func (m *PrefixLimit) String() string { return proto.CompactTextString(m) } func (*PrefixLimit) ProtoMessage() {} func (*PrefixLimit) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{107} } func (m *PrefixLimit) GetFamily() uint32 { if m != nil { return m.Family } return 0 } func (m *PrefixLimit) GetMaxPrefixes() uint32 { if m != nil { return m.MaxPrefixes } return 0 } func (m *PrefixLimit) GetShutdownThresholdPct() uint32 { if m != nil { return m.ShutdownThresholdPct } return 0 } type PeerConf struct { AuthPassword string `protobuf:"bytes,1,opt,name=auth_password,json=authPassword" json:"auth_password,omitempty"` Description string `protobuf:"bytes,2,opt,name=description" json:"description,omitempty"` LocalAs uint32 `protobuf:"varint,3,opt,name=local_as,json=localAs" json:"local_as,omitempty"` NeighborAddress string `protobuf:"bytes,4,opt,name=neighbor_address,json=neighborAddress" json:"neighbor_address,omitempty"` PeerAs uint32 `protobuf:"varint,5,opt,name=peer_as,json=peerAs" json:"peer_as,omitempty"` PeerGroup string `protobuf:"bytes,6,opt,name=peer_group,json=peerGroup" json:"peer_group,omitempty"` PeerType uint32 `protobuf:"varint,7,opt,name=peer_type,json=peerType" json:"peer_type,omitempty"` RemovePrivateAs PeerConf_RemovePrivateAs `protobuf:"varint,8,opt,name=remove_private_as,json=removePrivateAs,enum=gobgpapi.PeerConf_RemovePrivateAs" json:"remove_private_as,omitempty"` RouteFlapDamping bool `protobuf:"varint,9,opt,name=route_flap_damping,json=routeFlapDamping" json:"route_flap_damping,omitempty"` SendCommunity uint32 `protobuf:"varint,10,opt,name=send_community,json=sendCommunity" json:"send_community,omitempty"` RemoteCap [][]byte `protobuf:"bytes,11,rep,name=remote_cap,json=remoteCap,proto3" json:"remote_cap,omitempty"` LocalCap [][]byte `protobuf:"bytes,12,rep,name=local_cap,json=localCap,proto3" json:"local_cap,omitempty"` Id string `protobuf:"bytes,13,opt,name=id" json:"id,omitempty"` // Note: Regarding to the consistency with OpenConfig model, list of // PrefixLimit should be removed from here, and list of PrefixLimit in // AfiSafi should be used instead. PrefixLimits []*PrefixLimit `protobuf:"bytes,14,rep,name=prefix_limits,json=prefixLimits" json:"prefix_limits,omitempty"` LocalAddress string `protobuf:"bytes,15,opt,name=local_address,json=localAddress" json:"local_address,omitempty"` NeighborInterface string `protobuf:"bytes,16,opt,name=neighbor_interface,json=neighborInterface" json:"neighbor_interface,omitempty"` Vrf string `protobuf:"bytes,17,opt,name=vrf" json:"vrf,omitempty"` AllowOwnAs uint32 `protobuf:"varint,18,opt,name=allow_own_as,json=allowOwnAs" json:"allow_own_as,omitempty"` ReplacePeerAs bool `protobuf:"varint,19,opt,name=replace_peer_as,json=replacePeerAs" json:"replace_peer_as,omitempty"` } func (m *PeerConf) Reset() { *m = PeerConf{} } func (m *PeerConf) String() string { return proto.CompactTextString(m) } func (*PeerConf) ProtoMessage() {} func (*PeerConf) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{108} } func (m *PeerConf) GetAuthPassword() string { if m != nil { return m.AuthPassword } return "" } func (m *PeerConf) GetDescription() string { if m != nil { return m.Description } return "" } func (m *PeerConf) GetLocalAs() uint32 { if m != nil { return m.LocalAs } return 0 } func (m *PeerConf) GetNeighborAddress() string { if m != nil { return m.NeighborAddress } return "" } func (m *PeerConf) GetPeerAs() uint32 { if m != nil { return m.PeerAs } return 0 } func (m *PeerConf) GetPeerGroup() string { if m != nil { return m.PeerGroup } return "" } func (m *PeerConf) GetPeerType() uint32 { if m != nil { return m.PeerType } return 0 } func (m *PeerConf) GetRemovePrivateAs() PeerConf_RemovePrivateAs { if m != nil { return m.RemovePrivateAs } return PeerConf_NONE } func (m *PeerConf) GetRouteFlapDamping() bool { if m != nil { return m.RouteFlapDamping } return false } func (m *PeerConf) GetSendCommunity() uint32 { if m != nil { return m.SendCommunity } return 0 } func (m *PeerConf) GetRemoteCap() [][]byte { if m != nil { return m.RemoteCap } return nil } func (m *PeerConf) GetLocalCap() [][]byte { if m != nil { return m.LocalCap } return nil } func (m *PeerConf) GetId() string { if m != nil { return m.Id } return "" } func (m *PeerConf) GetPrefixLimits() []*PrefixLimit { if m != nil { return m.PrefixLimits } return nil } func (m *PeerConf) GetLocalAddress() string { if m != nil { return m.LocalAddress } return "" } func (m *PeerConf) GetNeighborInterface() string { if m != nil { return m.NeighborInterface } return "" } func (m *PeerConf) GetVrf() string { if m != nil { return m.Vrf } return "" } func (m *PeerConf) GetAllowOwnAs() uint32 { if m != nil { return m.AllowOwnAs } return 0 } func (m *PeerConf) GetReplacePeerAs() bool { if m != nil { return m.ReplacePeerAs } return false } type EbgpMultihop struct { Enabled bool `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"` MultihopTtl uint32 `protobuf:"varint,2,opt,name=multihop_ttl,json=multihopTtl" json:"multihop_ttl,omitempty"` } func (m *EbgpMultihop) Reset() { *m = EbgpMultihop{} } func (m *EbgpMultihop) String() string { return proto.CompactTextString(m) } func (*EbgpMultihop) ProtoMessage() {} func (*EbgpMultihop) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{109} } func (m *EbgpMultihop) GetEnabled() bool { if m != nil { return m.Enabled } return false } func (m *EbgpMultihop) GetMultihopTtl() uint32 { if m != nil { return m.MultihopTtl } return 0 } type RouteReflector struct { RouteReflectorClient bool `protobuf:"varint,1,opt,name=route_reflector_client,json=routeReflectorClient" json:"route_reflector_client,omitempty"` RouteReflectorClusterId string `protobuf:"bytes,2,opt,name=route_reflector_cluster_id,json=routeReflectorClusterId" json:"route_reflector_cluster_id,omitempty"` } func (m *RouteReflector) Reset() { *m = RouteReflector{} } func (m *RouteReflector) String() string { return proto.CompactTextString(m) } func (*RouteReflector) ProtoMessage() {} func (*RouteReflector) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{110} } func (m *RouteReflector) GetRouteReflectorClient() bool { if m != nil { return m.RouteReflectorClient } return false } func (m *RouteReflector) GetRouteReflectorClusterId() string { if m != nil { return m.RouteReflectorClusterId } return "" } type PeerState struct { AuthPassword string `protobuf:"bytes,1,opt,name=auth_password,json=authPassword" json:"auth_password,omitempty"` Description string `protobuf:"bytes,2,opt,name=description" json:"description,omitempty"` LocalAs uint32 `protobuf:"varint,3,opt,name=local_as,json=localAs" json:"local_as,omitempty"` Messages *Messages `protobuf:"bytes,4,opt,name=messages" json:"messages,omitempty"` NeighborAddress string `protobuf:"bytes,5,opt,name=neighbor_address,json=neighborAddress" json:"neighbor_address,omitempty"` PeerAs uint32 `protobuf:"varint,6,opt,name=peer_as,json=peerAs" json:"peer_as,omitempty"` PeerGroup string `protobuf:"bytes,7,opt,name=peer_group,json=peerGroup" json:"peer_group,omitempty"` PeerType uint32 `protobuf:"varint,8,opt,name=peer_type,json=peerType" json:"peer_type,omitempty"` Queues *Queues `protobuf:"bytes,9,opt,name=queues" json:"queues,omitempty"` RemovePrivateAs uint32 `protobuf:"varint,10,opt,name=remove_private_as,json=removePrivateAs" json:"remove_private_as,omitempty"` RouteFlapDamping bool `protobuf:"varint,11,opt,name=route_flap_damping,json=routeFlapDamping" json:"route_flap_damping,omitempty"` SendCommunity uint32 `protobuf:"varint,12,opt,name=send_community,json=sendCommunity" json:"send_community,omitempty"` SessionState uint32 `protobuf:"varint,13,opt,name=session_state,json=sessionState" json:"session_state,omitempty"` SupportedCapabilities []string `protobuf:"bytes,14,rep,name=supported_capabilities,json=supportedCapabilities" json:"supported_capabilities,omitempty"` BgpState string `protobuf:"bytes,15,opt,name=bgp_state,json=bgpState" json:"bgp_state,omitempty"` AdminState PeerState_AdminState `protobuf:"varint,16,opt,name=admin_state,json=adminState,enum=gobgpapi.PeerState_AdminState" json:"admin_state,omitempty"` Received uint32 `protobuf:"varint,17,opt,name=received" json:"received,omitempty"` Accepted uint32 `protobuf:"varint,18,opt,name=accepted" json:"accepted,omitempty"` Advertised uint32 `protobuf:"varint,19,opt,name=advertised" json:"advertised,omitempty"` OutQ uint32 `protobuf:"varint,20,opt,name=out_q,json=outQ" json:"out_q,omitempty"` Flops uint32 `protobuf:"varint,21,opt,name=flops" json:"flops,omitempty"` } func (m *PeerState) Reset() { *m = PeerState{} } func (m *PeerState) String() string { return proto.CompactTextString(m) } func (*PeerState) ProtoMessage() {} func (*PeerState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{111} } func (m *PeerState) GetAuthPassword() string { if m != nil { return m.AuthPassword } return "" } func (m *PeerState) GetDescription() string { if m != nil { return m.Description } return "" } func (m *PeerState) GetLocalAs() uint32 { if m != nil { return m.LocalAs } return 0 } func (m *PeerState) GetMessages() *Messages { if m != nil { return m.Messages } return nil } func (m *PeerState) GetNeighborAddress() string { if m != nil { return m.NeighborAddress } return "" } func (m *PeerState) GetPeerAs() uint32 { if m != nil { return m.PeerAs } return 0 } func (m *PeerState) GetPeerGroup() string { if m != nil { return m.PeerGroup } return "" } func (m *PeerState) GetPeerType() uint32 { if m != nil { return m.PeerType } return 0 } func (m *PeerState) GetQueues() *Queues { if m != nil { return m.Queues } return nil } func (m *PeerState) GetRemovePrivateAs() uint32 { if m != nil { return m.RemovePrivateAs } return 0 } func (m *PeerState) GetRouteFlapDamping() bool { if m != nil { return m.RouteFlapDamping } return false } func (m *PeerState) GetSendCommunity() uint32 { if m != nil { return m.SendCommunity } return 0 } func (m *PeerState) GetSessionState() uint32 { if m != nil { return m.SessionState } return 0 } func (m *PeerState) GetSupportedCapabilities() []string { if m != nil { return m.SupportedCapabilities } return nil } func (m *PeerState) GetBgpState() string { if m != nil { return m.BgpState } return "" } func (m *PeerState) GetAdminState() PeerState_AdminState { if m != nil { return m.AdminState } return PeerState_UP } func (m *PeerState) GetReceived() uint32 { if m != nil { return m.Received } return 0 } func (m *PeerState) GetAccepted() uint32 { if m != nil { return m.Accepted } return 0 } func (m *PeerState) GetAdvertised() uint32 { if m != nil { return m.Advertised } return 0 } func (m *PeerState) GetOutQ() uint32 { if m != nil { return m.OutQ } return 0 } func (m *PeerState) GetFlops() uint32 { if m != nil { return m.Flops } return 0 } type Messages struct { Received *Message `protobuf:"bytes,1,opt,name=received" json:"received,omitempty"` Sent *Message `protobuf:"bytes,2,opt,name=sent" json:"sent,omitempty"` } func (m *Messages) Reset() { *m = Messages{} } func (m *Messages) String() string { return proto.CompactTextString(m) } func (*Messages) ProtoMessage() {} func (*Messages) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{112} } func (m *Messages) GetReceived() *Message { if m != nil { return m.Received } return nil } func (m *Messages) GetSent() *Message { if m != nil { return m.Sent } return nil } type Message struct { NOTIFICATION uint64 `protobuf:"varint,1,opt,name=NOTIFICATION" json:"NOTIFICATION,omitempty"` UPDATE uint64 `protobuf:"varint,2,opt,name=UPDATE" json:"UPDATE,omitempty"` OPEN uint64 `protobuf:"varint,3,opt,name=OPEN" json:"OPEN,omitempty"` KEEPALIVE uint64 `protobuf:"varint,4,opt,name=KEEPALIVE" json:"KEEPALIVE,omitempty"` REFRESH uint64 `protobuf:"varint,5,opt,name=REFRESH" json:"REFRESH,omitempty"` DISCARDED uint64 `protobuf:"varint,6,opt,name=DISCARDED" json:"DISCARDED,omitempty"` TOTAL uint64 `protobuf:"varint,7,opt,name=TOTAL" json:"TOTAL,omitempty"` } func (m *Message) Reset() { *m = Message{} } func (m *Message) String() string { return proto.CompactTextString(m) } func (*Message) ProtoMessage() {} func (*Message) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{113} } func (m *Message) GetNOTIFICATION() uint64 { if m != nil { return m.NOTIFICATION } return 0 } func (m *Message) GetUPDATE() uint64 { if m != nil { return m.UPDATE } return 0 } func (m *Message) GetOPEN() uint64 { if m != nil { return m.OPEN } return 0 } func (m *Message) GetKEEPALIVE() uint64 { if m != nil { return m.KEEPALIVE } return 0 } func (m *Message) GetREFRESH() uint64 { if m != nil { return m.REFRESH } return 0 } func (m *Message) GetDISCARDED() uint64 { if m != nil { return m.DISCARDED } return 0 } func (m *Message) GetTOTAL() uint64 { if m != nil { return m.TOTAL } return 0 } type Queues struct { Input uint32 `protobuf:"varint,1,opt,name=input" json:"input,omitempty"` Output uint32 `protobuf:"varint,2,opt,name=output" json:"output,omitempty"` } func (m *Queues) Reset() { *m = Queues{} } func (m *Queues) String() string { return proto.CompactTextString(m) } func (*Queues) ProtoMessage() {} func (*Queues) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{114} } func (m *Queues) GetInput() uint32 { if m != nil { return m.Input } return 0 } func (m *Queues) GetOutput() uint32 { if m != nil { return m.Output } return 0 } type Timers struct { Config *TimersConfig `protobuf:"bytes,1,opt,name=config" json:"config,omitempty"` State *TimersState `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` } func (m *Timers) Reset() { *m = Timers{} } func (m *Timers) String() string { return proto.CompactTextString(m) } func (*Timers) ProtoMessage() {} func (*Timers) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{115} } func (m *Timers) GetConfig() *TimersConfig { if m != nil { return m.Config } return nil } func (m *Timers) GetState() *TimersState { if m != nil { return m.State } return nil } type TimersConfig struct { ConnectRetry uint64 `protobuf:"varint,1,opt,name=connect_retry,json=connectRetry" json:"connect_retry,omitempty"` HoldTime uint64 `protobuf:"varint,2,opt,name=hold_time,json=holdTime" json:"hold_time,omitempty"` KeepaliveInterval uint64 `protobuf:"varint,3,opt,name=keepalive_interval,json=keepaliveInterval" json:"keepalive_interval,omitempty"` MinimumAdvertisementInterval uint64 `protobuf:"varint,4,opt,name=minimum_advertisement_interval,json=minimumAdvertisementInterval" json:"minimum_advertisement_interval,omitempty"` } func (m *TimersConfig) Reset() { *m = TimersConfig{} } func (m *TimersConfig) String() string { return proto.CompactTextString(m) } func (*TimersConfig) ProtoMessage() {} func (*TimersConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{116} } func (m *TimersConfig) GetConnectRetry() uint64 { if m != nil { return m.ConnectRetry } return 0 } func (m *TimersConfig) GetHoldTime() uint64 { if m != nil { return m.HoldTime } return 0 } func (m *TimersConfig) GetKeepaliveInterval() uint64 { if m != nil { return m.KeepaliveInterval } return 0 } func (m *TimersConfig) GetMinimumAdvertisementInterval() uint64 { if m != nil { return m.MinimumAdvertisementInterval } return 0 } type TimersState struct { ConnectRetry uint64 `protobuf:"varint,1,opt,name=connect_retry,json=connectRetry" json:"connect_retry,omitempty"` HoldTime uint64 `protobuf:"varint,2,opt,name=hold_time,json=holdTime" json:"hold_time,omitempty"` KeepaliveInterval uint64 `protobuf:"varint,3,opt,name=keepalive_interval,json=keepaliveInterval" json:"keepalive_interval,omitempty"` MinimumAdvertisementInterval uint64 `protobuf:"varint,4,opt,name=minimum_advertisement_interval,json=minimumAdvertisementInterval" json:"minimum_advertisement_interval,omitempty"` NegotiatedHoldTime uint64 `protobuf:"varint,5,opt,name=negotiated_hold_time,json=negotiatedHoldTime" json:"negotiated_hold_time,omitempty"` Uptime uint64 `protobuf:"varint,6,opt,name=uptime" json:"uptime,omitempty"` Downtime uint64 `protobuf:"varint,7,opt,name=downtime" json:"downtime,omitempty"` } func (m *TimersState) Reset() { *m = TimersState{} } func (m *TimersState) String() string { return proto.CompactTextString(m) } func (*TimersState) ProtoMessage() {} func (*TimersState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{117} } func (m *TimersState) GetConnectRetry() uint64 { if m != nil { return m.ConnectRetry } return 0 } func (m *TimersState) GetHoldTime() uint64 { if m != nil { return m.HoldTime } return 0 } func (m *TimersState) GetKeepaliveInterval() uint64 { if m != nil { return m.KeepaliveInterval } return 0 } func (m *TimersState) GetMinimumAdvertisementInterval() uint64 { if m != nil { return m.MinimumAdvertisementInterval } return 0 } func (m *TimersState) GetNegotiatedHoldTime() uint64 { if m != nil { return m.NegotiatedHoldTime } return 0 } func (m *TimersState) GetUptime() uint64 { if m != nil { return m.Uptime } return 0 } func (m *TimersState) GetDowntime() uint64 { if m != nil { return m.Downtime } return 0 } type Transport struct { LocalAddress string `protobuf:"bytes,1,opt,name=local_address,json=localAddress" json:"local_address,omitempty"` LocalPort uint32 `protobuf:"varint,2,opt,name=local_port,json=localPort" json:"local_port,omitempty"` MtuDiscovery bool `protobuf:"varint,3,opt,name=mtu_discovery,json=mtuDiscovery" json:"mtu_discovery,omitempty"` PassiveMode bool `protobuf:"varint,4,opt,name=passive_mode,json=passiveMode" json:"passive_mode,omitempty"` RemoteAddress string `protobuf:"bytes,5,opt,name=remote_address,json=remoteAddress" json:"remote_address,omitempty"` RemotePort uint32 `protobuf:"varint,6,opt,name=remote_port,json=remotePort" json:"remote_port,omitempty"` TcpMss uint32 `protobuf:"varint,7,opt,name=tcp_mss,json=tcpMss" json:"tcp_mss,omitempty"` } func (m *Transport) Reset() { *m = Transport{} } func (m *Transport) String() string { return proto.CompactTextString(m) } func (*Transport) ProtoMessage() {} func (*Transport) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{118} } func (m *Transport) GetLocalAddress() string { if m != nil { return m.LocalAddress } return "" } func (m *Transport) GetLocalPort() uint32 { if m != nil { return m.LocalPort } return 0 } func (m *Transport) GetMtuDiscovery() bool { if m != nil { return m.MtuDiscovery } return false } func (m *Transport) GetPassiveMode() bool { if m != nil { return m.PassiveMode } return false } func (m *Transport) GetRemoteAddress() string { if m != nil { return m.RemoteAddress } return "" } func (m *Transport) GetRemotePort() uint32 { if m != nil { return m.RemotePort } return 0 } func (m *Transport) GetTcpMss() uint32 { if m != nil { return m.TcpMss } return 0 } type RouteServer struct { RouteServerClient bool `protobuf:"varint,1,opt,name=route_server_client,json=routeServerClient" json:"route_server_client,omitempty"` } func (m *RouteServer) Reset() { *m = RouteServer{} } func (m *RouteServer) String() string { return proto.CompactTextString(m) } func (*RouteServer) ProtoMessage() {} func (*RouteServer) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{119} } func (m *RouteServer) GetRouteServerClient() bool { if m != nil { return m.RouteServerClient } return false } type GracefulRestart struct { Enabled bool `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"` RestartTime uint32 `protobuf:"varint,2,opt,name=restart_time,json=restartTime" json:"restart_time,omitempty"` HelperOnly bool `protobuf:"varint,3,opt,name=helper_only,json=helperOnly" json:"helper_only,omitempty"` DeferralTime uint32 `protobuf:"varint,4,opt,name=deferral_time,json=deferralTime" json:"deferral_time,omitempty"` NotificationEnabled bool `protobuf:"varint,5,opt,name=notification_enabled,json=notificationEnabled" json:"notification_enabled,omitempty"` LonglivedEnabled bool `protobuf:"varint,6,opt,name=longlived_enabled,json=longlivedEnabled" json:"longlived_enabled,omitempty"` } func (m *GracefulRestart) Reset() { *m = GracefulRestart{} } func (m *GracefulRestart) String() string { return proto.CompactTextString(m) } func (*GracefulRestart) ProtoMessage() {} func (*GracefulRestart) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{120} } func (m *GracefulRestart) GetEnabled() bool { if m != nil { return m.Enabled } return false } func (m *GracefulRestart) GetRestartTime() uint32 { if m != nil { return m.RestartTime } return 0 } func (m *GracefulRestart) GetHelperOnly() bool { if m != nil { return m.HelperOnly } return false } func (m *GracefulRestart) GetDeferralTime() uint32 { if m != nil { return m.DeferralTime } return 0 } func (m *GracefulRestart) GetNotificationEnabled() bool { if m != nil { return m.NotificationEnabled } return false } func (m *GracefulRestart) GetLonglivedEnabled() bool { if m != nil { return m.LonglivedEnabled } return false } type MpGracefulRestartConfig struct { Enabled bool `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"` } func (m *MpGracefulRestartConfig) Reset() { *m = MpGracefulRestartConfig{} } func (m *MpGracefulRestartConfig) String() string { return proto.CompactTextString(m) } func (*MpGracefulRestartConfig) ProtoMessage() {} func (*MpGracefulRestartConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{121} } func (m *MpGracefulRestartConfig) GetEnabled() bool { if m != nil { return m.Enabled } return false } type MpGracefulRestartState struct { Enabled bool `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"` Received bool `protobuf:"varint,2,opt,name=received" json:"received,omitempty"` Advertised bool `protobuf:"varint,3,opt,name=advertised" json:"advertised,omitempty"` EndOfRibReceived bool `protobuf:"varint,4,opt,name=end_of_rib_received,json=endOfRibReceived" json:"end_of_rib_received,omitempty"` EndOfRibSent bool `protobuf:"varint,5,opt,name=end_of_rib_sent,json=endOfRibSent" json:"end_of_rib_sent,omitempty"` } func (m *MpGracefulRestartState) Reset() { *m = MpGracefulRestartState{} } func (m *MpGracefulRestartState) String() string { return proto.CompactTextString(m) } func (*MpGracefulRestartState) ProtoMessage() {} func (*MpGracefulRestartState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{122} } func (m *MpGracefulRestartState) GetEnabled() bool { if m != nil { return m.Enabled } return false } func (m *MpGracefulRestartState) GetReceived() bool { if m != nil { return m.Received } return false } func (m *MpGracefulRestartState) GetAdvertised() bool { if m != nil { return m.Advertised } return false } func (m *MpGracefulRestartState) GetEndOfRibReceived() bool { if m != nil { return m.EndOfRibReceived } return false } func (m *MpGracefulRestartState) GetEndOfRibSent() bool { if m != nil { return m.EndOfRibSent } return false } type MpGracefulRestart struct { Config *MpGracefulRestartConfig `protobuf:"bytes,1,opt,name=config" json:"config,omitempty"` State *MpGracefulRestartState `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` } func (m *MpGracefulRestart) Reset() { *m = MpGracefulRestart{} } func (m *MpGracefulRestart) String() string { return proto.CompactTextString(m) } func (*MpGracefulRestart) ProtoMessage() {} func (*MpGracefulRestart) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{123} } func (m *MpGracefulRestart) GetConfig() *MpGracefulRestartConfig { if m != nil { return m.Config } return nil } func (m *MpGracefulRestart) GetState() *MpGracefulRestartState { if m != nil { return m.State } return nil } type AfiSafiConfig struct { Family uint32 `protobuf:"varint,1,opt,name=family" json:"family,omitempty"` Enabled bool `protobuf:"varint,2,opt,name=enabled" json:"enabled,omitempty"` } func (m *AfiSafiConfig) Reset() { *m = AfiSafiConfig{} } func (m *AfiSafiConfig) String() string { return proto.CompactTextString(m) } func (*AfiSafiConfig) ProtoMessage() {} func (*AfiSafiConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{124} } func (m *AfiSafiConfig) GetFamily() uint32 { if m != nil { return m.Family } return 0 } func (m *AfiSafiConfig) GetEnabled() bool { if m != nil { return m.Enabled } return false } type AfiSafiState struct { Family uint32 `protobuf:"varint,1,opt,name=family" json:"family,omitempty"` Enabled bool `protobuf:"varint,2,opt,name=enabled" json:"enabled,omitempty"` TotalPaths uint32 `protobuf:"varint,3,opt,name=total_paths,json=totalPaths" json:"total_paths,omitempty"` TotalPrefixes uint32 `protobuf:"varint,4,opt,name=total_prefixes,json=totalPrefixes" json:"total_prefixes,omitempty"` } func (m *AfiSafiState) Reset() { *m = AfiSafiState{} } func (m *AfiSafiState) String() string { return proto.CompactTextString(m) } func (*AfiSafiState) ProtoMessage() {} func (*AfiSafiState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{125} } func (m *AfiSafiState) GetFamily() uint32 { if m != nil { return m.Family } return 0 } func (m *AfiSafiState) GetEnabled() bool { if m != nil { return m.Enabled } return false } func (m *AfiSafiState) GetTotalPaths() uint32 { if m != nil { return m.TotalPaths } return 0 } func (m *AfiSafiState) GetTotalPrefixes() uint32 { if m != nil { return m.TotalPrefixes } return 0 } type RouteSelectionOptionsConfig struct { AlwaysCompareMed bool `protobuf:"varint,1,opt,name=always_compare_med,json=alwaysCompareMed" json:"always_compare_med,omitempty"` IgnoreAsPathLength bool `protobuf:"varint,2,opt,name=ignore_as_path_length,json=ignoreAsPathLength" json:"ignore_as_path_length,omitempty"` ExternalCompareRouterId bool `protobuf:"varint,3,opt,name=external_compare_router_id,json=externalCompareRouterId" json:"external_compare_router_id,omitempty"` AdvertiseInactiveRoutes bool `protobuf:"varint,4,opt,name=advertise_inactive_routes,json=advertiseInactiveRoutes" json:"advertise_inactive_routes,omitempty"` EnableAigp bool `protobuf:"varint,5,opt,name=enable_aigp,json=enableAigp" json:"enable_aigp,omitempty"` IgnoreNextHopIgpMetric bool `protobuf:"varint,6,opt,name=ignore_next_hop_igp_metric,json=ignoreNextHopIgpMetric" json:"ignore_next_hop_igp_metric,omitempty"` } func (m *RouteSelectionOptionsConfig) Reset() { *m = RouteSelectionOptionsConfig{} } func (m *RouteSelectionOptionsConfig) String() string { return proto.CompactTextString(m) } func (*RouteSelectionOptionsConfig) ProtoMessage() {} func (*RouteSelectionOptionsConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{126} } func (m *RouteSelectionOptionsConfig) GetAlwaysCompareMed() bool { if m != nil { return m.AlwaysCompareMed } return false } func (m *RouteSelectionOptionsConfig) GetIgnoreAsPathLength() bool { if m != nil { return m.IgnoreAsPathLength } return false } func (m *RouteSelectionOptionsConfig) GetExternalCompareRouterId() bool { if m != nil { return m.ExternalCompareRouterId } return false } func (m *RouteSelectionOptionsConfig) GetAdvertiseInactiveRoutes() bool { if m != nil { return m.AdvertiseInactiveRoutes } return false } func (m *RouteSelectionOptionsConfig) GetEnableAigp() bool { if m != nil { return m.EnableAigp } return false } func (m *RouteSelectionOptionsConfig) GetIgnoreNextHopIgpMetric() bool { if m != nil { return m.IgnoreNextHopIgpMetric } return false } type RouteSelectionOptionsState struct { AlwaysCompareMed bool `protobuf:"varint,1,opt,name=always_compare_med,json=alwaysCompareMed" json:"always_compare_med,omitempty"` IgnoreAsPathLength bool `protobuf:"varint,2,opt,name=ignore_as_path_length,json=ignoreAsPathLength" json:"ignore_as_path_length,omitempty"` ExternalCompareRouterId bool `protobuf:"varint,3,opt,name=external_compare_router_id,json=externalCompareRouterId" json:"external_compare_router_id,omitempty"` AdvertiseInactiveRoutes bool `protobuf:"varint,4,opt,name=advertise_inactive_routes,json=advertiseInactiveRoutes" json:"advertise_inactive_routes,omitempty"` EnableAigp bool `protobuf:"varint,5,opt,name=enable_aigp,json=enableAigp" json:"enable_aigp,omitempty"` IgnoreNextHopIgpMetric bool `protobuf:"varint,6,opt,name=ignore_next_hop_igp_metric,json=ignoreNextHopIgpMetric" json:"ignore_next_hop_igp_metric,omitempty"` } func (m *RouteSelectionOptionsState) Reset() { *m = RouteSelectionOptionsState{} } func (m *RouteSelectionOptionsState) String() string { return proto.CompactTextString(m) } func (*RouteSelectionOptionsState) ProtoMessage() {} func (*RouteSelectionOptionsState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{127} } func (m *RouteSelectionOptionsState) GetAlwaysCompareMed() bool { if m != nil { return m.AlwaysCompareMed } return false } func (m *RouteSelectionOptionsState) GetIgnoreAsPathLength() bool { if m != nil { return m.IgnoreAsPathLength } return false } func (m *RouteSelectionOptionsState) GetExternalCompareRouterId() bool { if m != nil { return m.ExternalCompareRouterId } return false } func (m *RouteSelectionOptionsState) GetAdvertiseInactiveRoutes() bool { if m != nil { return m.AdvertiseInactiveRoutes } return false } func (m *RouteSelectionOptionsState) GetEnableAigp() bool { if m != nil { return m.EnableAigp } return false } func (m *RouteSelectionOptionsState) GetIgnoreNextHopIgpMetric() bool { if m != nil { return m.IgnoreNextHopIgpMetric } return false } type RouteSelectionOptions struct { Config *RouteSelectionOptionsConfig `protobuf:"bytes,1,opt,name=config" json:"config,omitempty"` State *RouteSelectionOptionsState `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` } func (m *RouteSelectionOptions) Reset() { *m = RouteSelectionOptions{} } func (m *RouteSelectionOptions) String() string { return proto.CompactTextString(m) } func (*RouteSelectionOptions) ProtoMessage() {} func (*RouteSelectionOptions) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{128} } func (m *RouteSelectionOptions) GetConfig() *RouteSelectionOptionsConfig { if m != nil { return m.Config } return nil } func (m *RouteSelectionOptions) GetState() *RouteSelectionOptionsState { if m != nil { return m.State } return nil } type UseMultiplePathsConfig struct { Enabled bool `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"` } func (m *UseMultiplePathsConfig) Reset() { *m = UseMultiplePathsConfig{} } func (m *UseMultiplePathsConfig) String() string { return proto.CompactTextString(m) } func (*UseMultiplePathsConfig) ProtoMessage() {} func (*UseMultiplePathsConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{129} } func (m *UseMultiplePathsConfig) GetEnabled() bool { if m != nil { return m.Enabled } return false } type UseMultiplePathsState struct { Enabled bool `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"` } func (m *UseMultiplePathsState) Reset() { *m = UseMultiplePathsState{} } func (m *UseMultiplePathsState) String() string { return proto.CompactTextString(m) } func (*UseMultiplePathsState) ProtoMessage() {} func (*UseMultiplePathsState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{130} } func (m *UseMultiplePathsState) GetEnabled() bool { if m != nil { return m.Enabled } return false } type EbgpConfig struct { AllowMultipleAs bool `protobuf:"varint,1,opt,name=allow_multiple_as,json=allowMultipleAs" json:"allow_multiple_as,omitempty"` MaximumPaths uint32 `protobuf:"varint,2,opt,name=maximum_paths,json=maximumPaths" json:"maximum_paths,omitempty"` } func (m *EbgpConfig) Reset() { *m = EbgpConfig{} } func (m *EbgpConfig) String() string { return proto.CompactTextString(m) } func (*EbgpConfig) ProtoMessage() {} func (*EbgpConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{131} } func (m *EbgpConfig) GetAllowMultipleAs() bool { if m != nil { return m.AllowMultipleAs } return false } func (m *EbgpConfig) GetMaximumPaths() uint32 { if m != nil { return m.MaximumPaths } return 0 } type EbgpState struct { AllowMultipleAs bool `protobuf:"varint,1,opt,name=allow_multiple_as,json=allowMultipleAs" json:"allow_multiple_as,omitempty"` MaximumPaths uint32 `protobuf:"varint,2,opt,name=maximum_paths,json=maximumPaths" json:"maximum_paths,omitempty"` } func (m *EbgpState) Reset() { *m = EbgpState{} } func (m *EbgpState) String() string { return proto.CompactTextString(m) } func (*EbgpState) ProtoMessage() {} func (*EbgpState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{132} } func (m *EbgpState) GetAllowMultipleAs() bool { if m != nil { return m.AllowMultipleAs } return false } func (m *EbgpState) GetMaximumPaths() uint32 { if m != nil { return m.MaximumPaths } return 0 } type Ebgp struct { Config *EbgpConfig `protobuf:"bytes,1,opt,name=config" json:"config,omitempty"` State *EbgpState `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` } func (m *Ebgp) Reset() { *m = Ebgp{} } func (m *Ebgp) String() string { return proto.CompactTextString(m) } func (*Ebgp) ProtoMessage() {} func (*Ebgp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{133} } func (m *Ebgp) GetConfig() *EbgpConfig { if m != nil { return m.Config } return nil } func (m *Ebgp) GetState() *EbgpState { if m != nil { return m.State } return nil } type IbgpConfig struct { MaximumPaths uint32 `protobuf:"varint,1,opt,name=maximum_paths,json=maximumPaths" json:"maximum_paths,omitempty"` } func (m *IbgpConfig) Reset() { *m = IbgpConfig{} } func (m *IbgpConfig) String() string { return proto.CompactTextString(m) } func (*IbgpConfig) ProtoMessage() {} func (*IbgpConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{134} } func (m *IbgpConfig) GetMaximumPaths() uint32 { if m != nil { return m.MaximumPaths } return 0 } type IbgpState struct { MaximumPaths uint32 `protobuf:"varint,1,opt,name=maximum_paths,json=maximumPaths" json:"maximum_paths,omitempty"` } func (m *IbgpState) Reset() { *m = IbgpState{} } func (m *IbgpState) String() string { return proto.CompactTextString(m) } func (*IbgpState) ProtoMessage() {} func (*IbgpState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{135} } func (m *IbgpState) GetMaximumPaths() uint32 { if m != nil { return m.MaximumPaths } return 0 } type Ibgp struct { Config *IbgpConfig `protobuf:"bytes,1,opt,name=config" json:"config,omitempty"` State *IbgpState `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` } func (m *Ibgp) Reset() { *m = Ibgp{} } func (m *Ibgp) String() string { return proto.CompactTextString(m) } func (*Ibgp) ProtoMessage() {} func (*Ibgp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{136} } func (m *Ibgp) GetConfig() *IbgpConfig { if m != nil { return m.Config } return nil } func (m *Ibgp) GetState() *IbgpState { if m != nil { return m.State } return nil } type UseMultiplePaths struct { Config *UseMultiplePathsConfig `protobuf:"bytes,1,opt,name=config" json:"config,omitempty"` State *UseMultiplePathsState `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` Ebgp *Ebgp `protobuf:"bytes,3,opt,name=ebgp" json:"ebgp,omitempty"` Ibgp *Ibgp `protobuf:"bytes,4,opt,name=ibgp" json:"ibgp,omitempty"` } func (m *UseMultiplePaths) Reset() { *m = UseMultiplePaths{} } func (m *UseMultiplePaths) String() string { return proto.CompactTextString(m) } func (*UseMultiplePaths) ProtoMessage() {} func (*UseMultiplePaths) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{137} } func (m *UseMultiplePaths) GetConfig() *UseMultiplePathsConfig { if m != nil { return m.Config } return nil } func (m *UseMultiplePaths) GetState() *UseMultiplePathsState { if m != nil { return m.State } return nil } func (m *UseMultiplePaths) GetEbgp() *Ebgp { if m != nil { return m.Ebgp } return nil } func (m *UseMultiplePaths) GetIbgp() *Ibgp { if m != nil { return m.Ibgp } return nil } type RouteTargetMembershipConfig struct { DeferralTime uint32 `protobuf:"varint,1,opt,name=deferral_time,json=deferralTime" json:"deferral_time,omitempty"` } func (m *RouteTargetMembershipConfig) Reset() { *m = RouteTargetMembershipConfig{} } func (m *RouteTargetMembershipConfig) String() string { return proto.CompactTextString(m) } func (*RouteTargetMembershipConfig) ProtoMessage() {} func (*RouteTargetMembershipConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{138} } func (m *RouteTargetMembershipConfig) GetDeferralTime() uint32 { if m != nil { return m.DeferralTime } return 0 } type RouteTargetMembershipState struct { DeferralTime uint32 `protobuf:"varint,1,opt,name=deferral_time,json=deferralTime" json:"deferral_time,omitempty"` } func (m *RouteTargetMembershipState) Reset() { *m = RouteTargetMembershipState{} } func (m *RouteTargetMembershipState) String() string { return proto.CompactTextString(m) } func (*RouteTargetMembershipState) ProtoMessage() {} func (*RouteTargetMembershipState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{139} } func (m *RouteTargetMembershipState) GetDeferralTime() uint32 { if m != nil { return m.DeferralTime } return 0 } type RouteTargetMembership struct { Config *RouteTargetMembershipConfig `protobuf:"bytes,1,opt,name=config" json:"config,omitempty"` State *RouteTargetMembershipState `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` } func (m *RouteTargetMembership) Reset() { *m = RouteTargetMembership{} } func (m *RouteTargetMembership) String() string { return proto.CompactTextString(m) } func (*RouteTargetMembership) ProtoMessage() {} func (*RouteTargetMembership) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{140} } func (m *RouteTargetMembership) GetConfig() *RouteTargetMembershipConfig { if m != nil { return m.Config } return nil } func (m *RouteTargetMembership) GetState() *RouteTargetMembershipState { if m != nil { return m.State } return nil } type LongLivedGracefulRestartConfig struct { Enabled bool `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"` RestartTime uint32 `protobuf:"varint,2,opt,name=restart_time,json=restartTime" json:"restart_time,omitempty"` } func (m *LongLivedGracefulRestartConfig) Reset() { *m = LongLivedGracefulRestartConfig{} } func (m *LongLivedGracefulRestartConfig) String() string { return proto.CompactTextString(m) } func (*LongLivedGracefulRestartConfig) ProtoMessage() {} func (*LongLivedGracefulRestartConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{141} } func (m *LongLivedGracefulRestartConfig) GetEnabled() bool { if m != nil { return m.Enabled } return false } func (m *LongLivedGracefulRestartConfig) GetRestartTime() uint32 { if m != nil { return m.RestartTime } return 0 } type LongLivedGracefulRestartState struct { Enabled bool `protobuf:"varint,1,opt,name=enabled" json:"enabled,omitempty"` Received bool `protobuf:"varint,2,opt,name=received" json:"received,omitempty"` Advertised bool `protobuf:"varint,3,opt,name=advertised" json:"advertised,omitempty"` PeerRestartTime uint32 `protobuf:"varint,4,opt,name=peer_restart_time,json=peerRestartTime" json:"peer_restart_time,omitempty"` PeerRestartTimerExpired bool `protobuf:"varint,5,opt,name=peer_restart_timer_expired,json=peerRestartTimerExpired" json:"peer_restart_timer_expired,omitempty"` } func (m *LongLivedGracefulRestartState) Reset() { *m = LongLivedGracefulRestartState{} } func (m *LongLivedGracefulRestartState) String() string { return proto.CompactTextString(m) } func (*LongLivedGracefulRestartState) ProtoMessage() {} func (*LongLivedGracefulRestartState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{142} } func (m *LongLivedGracefulRestartState) GetEnabled() bool { if m != nil { return m.Enabled } return false } func (m *LongLivedGracefulRestartState) GetReceived() bool { if m != nil { return m.Received } return false } func (m *LongLivedGracefulRestartState) GetAdvertised() bool { if m != nil { return m.Advertised } return false } func (m *LongLivedGracefulRestartState) GetPeerRestartTime() uint32 { if m != nil { return m.PeerRestartTime } return 0 } func (m *LongLivedGracefulRestartState) GetPeerRestartTimerExpired() bool { if m != nil { return m.PeerRestartTimerExpired } return false } type LongLivedGracefulRestart struct { Config *LongLivedGracefulRestartConfig `protobuf:"bytes,1,opt,name=config" json:"config,omitempty"` State *LongLivedGracefulRestartState `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` } func (m *LongLivedGracefulRestart) Reset() { *m = LongLivedGracefulRestart{} } func (m *LongLivedGracefulRestart) String() string { return proto.CompactTextString(m) } func (*LongLivedGracefulRestart) ProtoMessage() {} func (*LongLivedGracefulRestart) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{143} } func (m *LongLivedGracefulRestart) GetConfig() *LongLivedGracefulRestartConfig { if m != nil { return m.Config } return nil } func (m *LongLivedGracefulRestart) GetState() *LongLivedGracefulRestartState { if m != nil { return m.State } return nil } type AfiSafi struct { MpGracefulRestart *MpGracefulRestart `protobuf:"bytes,1,opt,name=mp_graceful_restart,json=mpGracefulRestart" json:"mp_graceful_restart,omitempty"` Config *AfiSafiConfig `protobuf:"bytes,2,opt,name=config" json:"config,omitempty"` ApplyPolicy *ApplyPolicy `protobuf:"bytes,3,opt,name=apply_policy,json=applyPolicy" json:"apply_policy,omitempty"` // TODO: // Support the following structures: // - Ipv4Unicast // - Ipv6Unicast // - Ipv4LabelledUnicast // - Ipv6LabelledUnicast // - L3vpnIpv4Unicast // - L3vpnIpv6Unicast // - L3vpnIpv4Multicast // - L3vpnIpv6Multicast // - L2vpnVpls // - L2vpnEvpn RouteSelectionOptions *RouteSelectionOptions `protobuf:"bytes,4,opt,name=route_selection_options,json=routeSelectionOptions" json:"route_selection_options,omitempty"` UseMultiplePaths *UseMultiplePaths `protobuf:"bytes,5,opt,name=use_multiple_paths,json=useMultiplePaths" json:"use_multiple_paths,omitempty"` PrefixLimits *PrefixLimit `protobuf:"bytes,6,opt,name=prefix_limits,json=prefixLimits" json:"prefix_limits,omitempty"` RouteTargetMembership *RouteTargetMembership `protobuf:"bytes,7,opt,name=route_target_membership,json=routeTargetMembership" json:"route_target_membership,omitempty"` LongLivedGracefulRestart *LongLivedGracefulRestart `protobuf:"bytes,8,opt,name=long_lived_graceful_restart,json=longLivedGracefulRestart" json:"long_lived_graceful_restart,omitempty"` AddPaths *AddPaths `protobuf:"bytes,9,opt,name=add_paths,json=addPaths" json:"add_paths,omitempty"` } func (m *AfiSafi) Reset() { *m = AfiSafi{} } func (m *AfiSafi) String() string { return proto.CompactTextString(m) } func (*AfiSafi) ProtoMessage() {} func (*AfiSafi) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{144} } func (m *AfiSafi) GetMpGracefulRestart() *MpGracefulRestart { if m != nil { return m.MpGracefulRestart } return nil } func (m *AfiSafi) GetConfig() *AfiSafiConfig { if m != nil { return m.Config } return nil } func (m *AfiSafi) GetApplyPolicy() *ApplyPolicy { if m != nil { return m.ApplyPolicy } return nil } func (m *AfiSafi) GetRouteSelectionOptions() *RouteSelectionOptions { if m != nil { return m.RouteSelectionOptions } return nil } func (m *AfiSafi) GetUseMultiplePaths() *UseMultiplePaths { if m != nil { return m.UseMultiplePaths } return nil } func (m *AfiSafi) GetPrefixLimits() *PrefixLimit { if m != nil { return m.PrefixLimits } return nil } func (m *AfiSafi) GetRouteTargetMembership() *RouteTargetMembership { if m != nil { return m.RouteTargetMembership } return nil } func (m *AfiSafi) GetLongLivedGracefulRestart() *LongLivedGracefulRestart { if m != nil { return m.LongLivedGracefulRestart } return nil } func (m *AfiSafi) GetAddPaths() *AddPaths { if m != nil { return m.AddPaths } return nil } type AddPathsConfig struct { Receive bool `protobuf:"varint,1,opt,name=receive" json:"receive,omitempty"` SendMax uint32 `protobuf:"varint,2,opt,name=send_max,json=sendMax" json:"send_max,omitempty"` } func (m *AddPathsConfig) Reset() { *m = AddPathsConfig{} } func (m *AddPathsConfig) String() string { return proto.CompactTextString(m) } func (*AddPathsConfig) ProtoMessage() {} func (*AddPathsConfig) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{145} } func (m *AddPathsConfig) GetReceive() bool { if m != nil { return m.Receive } return false } func (m *AddPathsConfig) GetSendMax() uint32 { if m != nil { return m.SendMax } return 0 } type AddPathsState struct { Receive bool `protobuf:"varint,1,opt,name=receive" json:"receive,omitempty"` SendMax uint32 `protobuf:"varint,2,opt,name=send_max,json=sendMax" json:"send_max,omitempty"` } func (m *AddPathsState) Reset() { *m = AddPathsState{} } func (m *AddPathsState) String() string { return proto.CompactTextString(m) } func (*AddPathsState) ProtoMessage() {} func (*AddPathsState) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{146} } func (m *AddPathsState) GetReceive() bool { if m != nil { return m.Receive } return false } func (m *AddPathsState) GetSendMax() uint32 { if m != nil { return m.SendMax } return 0 } type AddPaths struct { Config *AddPathsConfig `protobuf:"bytes,1,opt,name=config" json:"config,omitempty"` State *AddPathsState `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"` } func (m *AddPaths) Reset() { *m = AddPaths{} } func (m *AddPaths) String() string { return proto.CompactTextString(m) } func (*AddPaths) ProtoMessage() {} func (*AddPaths) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{147} } func (m *AddPaths) GetConfig() *AddPathsConfig { if m != nil { return m.Config } return nil } func (m *AddPaths) GetState() *AddPathsState { if m != nil { return m.State } return nil } type Prefix struct { IpPrefix string `protobuf:"bytes,1,opt,name=ip_prefix,json=ipPrefix" json:"ip_prefix,omitempty"` MaskLengthMin uint32 `protobuf:"varint,2,opt,name=mask_length_min,json=maskLengthMin" json:"mask_length_min,omitempty"` MaskLengthMax uint32 `protobuf:"varint,3,opt,name=mask_length_max,json=maskLengthMax" json:"mask_length_max,omitempty"` } func (m *Prefix) Reset() { *m = Prefix{} } func (m *Prefix) String() string { return proto.CompactTextString(m) } func (*Prefix) ProtoMessage() {} func (*Prefix) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{148} } func (m *Prefix) GetIpPrefix() string { if m != nil { return m.IpPrefix } return "" } func (m *Prefix) GetMaskLengthMin() uint32 { if m != nil { return m.MaskLengthMin } return 0 } func (m *Prefix) GetMaskLengthMax() uint32 { if m != nil { return m.MaskLengthMax } return 0 } type DefinedSet struct { Type DefinedType `protobuf:"varint,1,opt,name=type,enum=gobgpapi.DefinedType" json:"type,omitempty"` Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` List []string `protobuf:"bytes,3,rep,name=list" json:"list,omitempty"` Prefixes []*Prefix `protobuf:"bytes,4,rep,name=prefixes" json:"prefixes,omitempty"` } func (m *DefinedSet) Reset() { *m = DefinedSet{} } func (m *DefinedSet) String() string { return proto.CompactTextString(m) } func (*DefinedSet) ProtoMessage() {} func (*DefinedSet) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{149} } func (m *DefinedSet) GetType() DefinedType { if m != nil { return m.Type } return DefinedType_PREFIX } func (m *DefinedSet) GetName() string { if m != nil { return m.Name } return "" } func (m *DefinedSet) GetList() []string { if m != nil { return m.List } return nil } func (m *DefinedSet) GetPrefixes() []*Prefix { if m != nil { return m.Prefixes } return nil } type MatchSet struct { Type MatchType `protobuf:"varint,1,opt,name=type,enum=gobgpapi.MatchType" json:"type,omitempty"` Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` } func (m *MatchSet) Reset() { *m = MatchSet{} } func (m *MatchSet) String() string { return proto.CompactTextString(m) } func (*MatchSet) ProtoMessage() {} func (*MatchSet) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{150} } func (m *MatchSet) GetType() MatchType { if m != nil { return m.Type } return MatchType_ANY } func (m *MatchSet) GetName() string { if m != nil { return m.Name } return "" } type AsPathLength struct { Type AsPathLengthType `protobuf:"varint,1,opt,name=type,enum=gobgpapi.AsPathLengthType" json:"type,omitempty"` Length uint32 `protobuf:"varint,2,opt,name=length" json:"length,omitempty"` } func (m *AsPathLength) Reset() { *m = AsPathLength{} } func (m *AsPathLength) String() string { return proto.CompactTextString(m) } func (*AsPathLength) ProtoMessage() {} func (*AsPathLength) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{151} } func (m *AsPathLength) GetType() AsPathLengthType { if m != nil { return m.Type } return AsPathLengthType_EQ } func (m *AsPathLength) GetLength() uint32 { if m != nil { return m.Length } return 0 } type Conditions struct { PrefixSet *MatchSet `protobuf:"bytes,1,opt,name=prefix_set,json=prefixSet" json:"prefix_set,omitempty"` NeighborSet *MatchSet `protobuf:"bytes,2,opt,name=neighbor_set,json=neighborSet" json:"neighbor_set,omitempty"` AsPathLength *AsPathLength `protobuf:"bytes,3,opt,name=as_path_length,json=asPathLength" json:"as_path_length,omitempty"` AsPathSet *MatchSet `protobuf:"bytes,4,opt,name=as_path_set,json=asPathSet" json:"as_path_set,omitempty"` CommunitySet *MatchSet `protobuf:"bytes,5,opt,name=community_set,json=communitySet" json:"community_set,omitempty"` ExtCommunitySet *MatchSet `protobuf:"bytes,6,opt,name=ext_community_set,json=extCommunitySet" json:"ext_community_set,omitempty"` RpkiResult int32 `protobuf:"varint,7,opt,name=rpki_result,json=rpkiResult" json:"rpki_result,omitempty"` RouteType Conditions_RouteType `protobuf:"varint,8,opt,name=route_type,json=routeType,enum=gobgpapi.Conditions_RouteType" json:"route_type,omitempty"` LargeCommunitySet *MatchSet `protobuf:"bytes,9,opt,name=large_community_set,json=largeCommunitySet" json:"large_community_set,omitempty"` } func (m *Conditions) Reset() { *m = Conditions{} } func (m *Conditions) String() string { return proto.CompactTextString(m) } func (*Conditions) ProtoMessage() {} func (*Conditions) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{152} } func (m *Conditions) GetPrefixSet() *MatchSet { if m != nil { return m.PrefixSet } return nil } func (m *Conditions) GetNeighborSet() *MatchSet { if m != nil { return m.NeighborSet } return nil } func (m *Conditions) GetAsPathLength() *AsPathLength { if m != nil { return m.AsPathLength } return nil } func (m *Conditions) GetAsPathSet() *MatchSet { if m != nil { return m.AsPathSet } return nil } func (m *Conditions) GetCommunitySet() *MatchSet { if m != nil { return m.CommunitySet } return nil } func (m *Conditions) GetExtCommunitySet() *MatchSet { if m != nil { return m.ExtCommunitySet } return nil } func (m *Conditions) GetRpkiResult() int32 { if m != nil { return m.RpkiResult } return 0 } func (m *Conditions) GetRouteType() Conditions_RouteType { if m != nil { return m.RouteType } return Conditions_ROUTE_TYPE_NONE } func (m *Conditions) GetLargeCommunitySet() *MatchSet { if m != nil { return m.LargeCommunitySet } return nil } type CommunityAction struct { Type CommunityActionType `protobuf:"varint,1,opt,name=type,enum=gobgpapi.CommunityActionType" json:"type,omitempty"` Communities []string `protobuf:"bytes,2,rep,name=communities" json:"communities,omitempty"` } func (m *CommunityAction) Reset() { *m = CommunityAction{} } func (m *CommunityAction) String() string { return proto.CompactTextString(m) } func (*CommunityAction) ProtoMessage() {} func (*CommunityAction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{153} } func (m *CommunityAction) GetType() CommunityActionType { if m != nil { return m.Type } return CommunityActionType_COMMUNITY_ADD } func (m *CommunityAction) GetCommunities() []string { if m != nil { return m.Communities } return nil } type MedAction struct { Type MedActionType `protobuf:"varint,1,opt,name=type,enum=gobgpapi.MedActionType" json:"type,omitempty"` Value int64 `protobuf:"varint,2,opt,name=value" json:"value,omitempty"` } func (m *MedAction) Reset() { *m = MedAction{} } func (m *MedAction) String() string { return proto.CompactTextString(m) } func (*MedAction) ProtoMessage() {} func (*MedAction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{154} } func (m *MedAction) GetType() MedActionType { if m != nil { return m.Type } return MedActionType_MED_MOD } func (m *MedAction) GetValue() int64 { if m != nil { return m.Value } return 0 } type AsPrependAction struct { Asn uint32 `protobuf:"varint,1,opt,name=asn" json:"asn,omitempty"` Repeat uint32 `protobuf:"varint,2,opt,name=repeat" json:"repeat,omitempty"` UseLeftMost bool `protobuf:"varint,3,opt,name=use_left_most,json=useLeftMost" json:"use_left_most,omitempty"` } func (m *AsPrependAction) Reset() { *m = AsPrependAction{} } func (m *AsPrependAction) String() string { return proto.CompactTextString(m) } func (*AsPrependAction) ProtoMessage() {} func (*AsPrependAction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{155} } func (m *AsPrependAction) GetAsn() uint32 { if m != nil { return m.Asn } return 0 } func (m *AsPrependAction) GetRepeat() uint32 { if m != nil { return m.Repeat } return 0 } func (m *AsPrependAction) GetUseLeftMost() bool { if m != nil { return m.UseLeftMost } return false } type NexthopAction struct { Address string `protobuf:"bytes,1,opt,name=address" json:"address,omitempty"` Self bool `protobuf:"varint,2,opt,name=self" json:"self,omitempty"` } func (m *NexthopAction) Reset() { *m = NexthopAction{} } func (m *NexthopAction) String() string { return proto.CompactTextString(m) } func (*NexthopAction) ProtoMessage() {} func (*NexthopAction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{156} } func (m *NexthopAction) GetAddress() string { if m != nil { return m.Address } return "" } func (m *NexthopAction) GetSelf() bool { if m != nil { return m.Self } return false } type LocalPrefAction struct { Value uint32 `protobuf:"varint,1,opt,name=value" json:"value,omitempty"` } func (m *LocalPrefAction) Reset() { *m = LocalPrefAction{} } func (m *LocalPrefAction) String() string { return proto.CompactTextString(m) } func (*LocalPrefAction) ProtoMessage() {} func (*LocalPrefAction) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{157} } func (m *LocalPrefAction) GetValue() uint32 { if m != nil { return m.Value } return 0 } type Actions struct { RouteAction RouteAction `protobuf:"varint,1,opt,name=route_action,json=routeAction,enum=gobgpapi.RouteAction" json:"route_action,omitempty"` Community *CommunityAction `protobuf:"bytes,2,opt,name=community" json:"community,omitempty"` Med *MedAction `protobuf:"bytes,3,opt,name=med" json:"med,omitempty"` AsPrepend *AsPrependAction `protobuf:"bytes,4,opt,name=as_prepend,json=asPrepend" json:"as_prepend,omitempty"` ExtCommunity *CommunityAction `protobuf:"bytes,5,opt,name=ext_community,json=extCommunity" json:"ext_community,omitempty"` Nexthop *NexthopAction `protobuf:"bytes,6,opt,name=nexthop" json:"nexthop,omitempty"` LocalPref *LocalPrefAction `protobuf:"bytes,7,opt,name=local_pref,json=localPref" json:"local_pref,omitempty"` LargeCommunity *CommunityAction `protobuf:"bytes,8,opt,name=large_community,json=largeCommunity" json:"large_community,omitempty"` } func (m *Actions) Reset() { *m = Actions{} } func (m *Actions) String() string { return proto.CompactTextString(m) } func (*Actions) ProtoMessage() {} func (*Actions) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{158} } func (m *Actions) GetRouteAction() RouteAction { if m != nil { return m.RouteAction } return RouteAction_NONE } func (m *Actions) GetCommunity() *CommunityAction { if m != nil { return m.Community } return nil } func (m *Actions) GetMed() *MedAction { if m != nil { return m.Med } return nil } func (m *Actions) GetAsPrepend() *AsPrependAction { if m != nil { return m.AsPrepend } return nil } func (m *Actions) GetExtCommunity() *CommunityAction { if m != nil { return m.ExtCommunity } return nil } func (m *Actions) GetNexthop() *NexthopAction { if m != nil { return m.Nexthop } return nil } func (m *Actions) GetLocalPref() *LocalPrefAction { if m != nil { return m.LocalPref } return nil } func (m *Actions) GetLargeCommunity() *CommunityAction { if m != nil { return m.LargeCommunity } return nil } type Statement struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Conditions *Conditions `protobuf:"bytes,2,opt,name=conditions" json:"conditions,omitempty"` Actions *Actions `protobuf:"bytes,3,opt,name=actions" json:"actions,omitempty"` } func (m *Statement) Reset() { *m = Statement{} } func (m *Statement) String() string { return proto.CompactTextString(m) } func (*Statement) ProtoMessage() {} func (*Statement) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{159} } func (m *Statement) GetName() string { if m != nil { return m.Name } return "" } func (m *Statement) GetConditions() *Conditions { if m != nil { return m.Conditions } return nil } func (m *Statement) GetActions() *Actions { if m != nil { return m.Actions } return nil } type Policy struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Statements []*Statement `protobuf:"bytes,2,rep,name=statements" json:"statements,omitempty"` } func (m *Policy) Reset() { *m = Policy{} } func (m *Policy) String() string { return proto.CompactTextString(m) } func (*Policy) ProtoMessage() {} func (*Policy) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{160} } func (m *Policy) GetName() string { if m != nil { return m.Name } return "" } func (m *Policy) GetStatements() []*Statement { if m != nil { return m.Statements } return nil } type PolicyAssignment struct { Type PolicyType `protobuf:"varint,1,opt,name=type,enum=gobgpapi.PolicyType" json:"type,omitempty"` Resource Resource `protobuf:"varint,2,opt,name=resource,enum=gobgpapi.Resource" json:"resource,omitempty"` Name string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"` Policies []*Policy `protobuf:"bytes,4,rep,name=policies" json:"policies,omitempty"` Default RouteAction `protobuf:"varint,5,opt,name=default,enum=gobgpapi.RouteAction" json:"default,omitempty"` } func (m *PolicyAssignment) Reset() { *m = PolicyAssignment{} } func (m *PolicyAssignment) String() string { return proto.CompactTextString(m) } func (*PolicyAssignment) ProtoMessage() {} func (*PolicyAssignment) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{161} } func (m *PolicyAssignment) GetType() PolicyType { if m != nil { return m.Type } return PolicyType_IN } func (m *PolicyAssignment) GetResource() Resource { if m != nil { return m.Resource } return Resource_GLOBAL } func (m *PolicyAssignment) GetName() string { if m != nil { return m.Name } return "" } func (m *PolicyAssignment) GetPolicies() []*Policy { if m != nil { return m.Policies } return nil } func (m *PolicyAssignment) GetDefault() RouteAction { if m != nil { return m.Default } return RouteAction_NONE } type Roa struct { As uint32 `protobuf:"varint,1,opt,name=as" json:"as,omitempty"` Prefixlen uint32 `protobuf:"varint,2,opt,name=prefixlen" json:"prefixlen,omitempty"` Maxlen uint32 `protobuf:"varint,3,opt,name=maxlen" json:"maxlen,omitempty"` Prefix string `protobuf:"bytes,4,opt,name=prefix" json:"prefix,omitempty"` Conf *RPKIConf `protobuf:"bytes,5,opt,name=conf" json:"conf,omitempty"` } func (m *Roa) Reset() { *m = Roa{} } func (m *Roa) String() string { return proto.CompactTextString(m) } func (*Roa) ProtoMessage() {} func (*Roa) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{162} } func (m *Roa) GetAs() uint32 { if m != nil { return m.As } return 0 } func (m *Roa) GetPrefixlen() uint32 { if m != nil { return m.Prefixlen } return 0 } func (m *Roa) GetMaxlen() uint32 { if m != nil { return m.Maxlen } return 0 } func (m *Roa) GetPrefix() string { if m != nil { return m.Prefix } return "" } func (m *Roa) GetConf() *RPKIConf { if m != nil { return m.Conf } return nil } type GetRoaRequest struct { Family uint32 `protobuf:"varint,1,opt,name=family" json:"family,omitempty"` } func (m *GetRoaRequest) Reset() { *m = GetRoaRequest{} } func (m *GetRoaRequest) String() string { return proto.CompactTextString(m) } func (*GetRoaRequest) ProtoMessage() {} func (*GetRoaRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{163} } func (m *GetRoaRequest) GetFamily() uint32 { if m != nil { return m.Family } return 0 } type GetRoaResponse struct { Roas []*Roa `protobuf:"bytes,1,rep,name=roas" json:"roas,omitempty"` } func (m *GetRoaResponse) Reset() { *m = GetRoaResponse{} } func (m *GetRoaResponse) String() string { return proto.CompactTextString(m) } func (*GetRoaResponse) ProtoMessage() {} func (*GetRoaResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{164} } func (m *GetRoaResponse) GetRoas() []*Roa { if m != nil { return m.Roas } return nil } type Vrf struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Rd []byte `protobuf:"bytes,2,opt,name=rd,proto3" json:"rd,omitempty"` ImportRt [][]byte `protobuf:"bytes,3,rep,name=import_rt,json=importRt,proto3" json:"import_rt,omitempty"` ExportRt [][]byte `protobuf:"bytes,4,rep,name=export_rt,json=exportRt,proto3" json:"export_rt,omitempty"` Id uint32 `protobuf:"varint,5,opt,name=id" json:"id,omitempty"` } func (m *Vrf) Reset() { *m = Vrf{} } func (m *Vrf) String() string { return proto.CompactTextString(m) } func (*Vrf) ProtoMessage() {} func (*Vrf) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{165} } func (m *Vrf) GetName() string { if m != nil { return m.Name } return "" } func (m *Vrf) GetRd() []byte { if m != nil { return m.Rd } return nil } func (m *Vrf) GetImportRt() [][]byte { if m != nil { return m.ImportRt } return nil } func (m *Vrf) GetExportRt() [][]byte { if m != nil { return m.ExportRt } return nil } func (m *Vrf) GetId() uint32 { if m != nil { return m.Id } return 0 } type Global struct { As uint32 `protobuf:"varint,1,opt,name=as" json:"as,omitempty"` RouterId string `protobuf:"bytes,2,opt,name=router_id,json=routerId" json:"router_id,omitempty"` ListenPort int32 `protobuf:"varint,3,opt,name=listen_port,json=listenPort" json:"listen_port,omitempty"` ListenAddresses []string `protobuf:"bytes,4,rep,name=listen_addresses,json=listenAddresses" json:"listen_addresses,omitempty"` Families []uint32 `protobuf:"varint,5,rep,packed,name=families" json:"families,omitempty"` UseMultiplePaths bool `protobuf:"varint,6,opt,name=use_multiple_paths,json=useMultiplePaths" json:"use_multiple_paths,omitempty"` } func (m *Global) Reset() { *m = Global{} } func (m *Global) String() string { return proto.CompactTextString(m) } func (*Global) ProtoMessage() {} func (*Global) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{166} } func (m *Global) GetAs() uint32 { if m != nil { return m.As } return 0 } func (m *Global) GetRouterId() string { if m != nil { return m.RouterId } return "" } func (m *Global) GetListenPort() int32 { if m != nil { return m.ListenPort } return 0 } func (m *Global) GetListenAddresses() []string { if m != nil { return m.ListenAddresses } return nil } func (m *Global) GetFamilies() []uint32 { if m != nil { return m.Families } return nil } func (m *Global) GetUseMultiplePaths() bool { if m != nil { return m.UseMultiplePaths } return false } type TableInfo struct { Type Resource `protobuf:"varint,1,opt,name=type,enum=gobgpapi.Resource" json:"type,omitempty"` Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` Family uint32 `protobuf:"varint,3,opt,name=family" json:"family,omitempty"` NumDestination uint64 `protobuf:"varint,4,opt,name=num_destination,json=numDestination" json:"num_destination,omitempty"` NumPath uint64 `protobuf:"varint,5,opt,name=num_path,json=numPath" json:"num_path,omitempty"` NumAccepted uint64 `protobuf:"varint,6,opt,name=num_accepted,json=numAccepted" json:"num_accepted,omitempty"` } func (m *TableInfo) Reset() { *m = TableInfo{} } func (m *TableInfo) String() string { return proto.CompactTextString(m) } func (*TableInfo) ProtoMessage() {} func (*TableInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{167} } func (m *TableInfo) GetType() Resource { if m != nil { return m.Type } return Resource_GLOBAL } func (m *TableInfo) GetName() string { if m != nil { return m.Name } return "" } func (m *TableInfo) GetFamily() uint32 { if m != nil { return m.Family } return 0 } func (m *TableInfo) GetNumDestination() uint64 { if m != nil { return m.NumDestination } return 0 } func (m *TableInfo) GetNumPath() uint64 { if m != nil { return m.NumPath } return 0 } func (m *TableInfo) GetNumAccepted() uint64 { if m != nil { return m.NumAccepted } return 0 } type GetRibInfoRequest struct { Info *TableInfo `protobuf:"bytes,1,opt,name=info" json:"info,omitempty"` } func (m *GetRibInfoRequest) Reset() { *m = GetRibInfoRequest{} } func (m *GetRibInfoRequest) String() string { return proto.CompactTextString(m) } func (*GetRibInfoRequest) ProtoMessage() {} func (*GetRibInfoRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{168} } func (m *GetRibInfoRequest) GetInfo() *TableInfo { if m != nil { return m.Info } return nil } type GetRibInfoResponse struct { Info *TableInfo `protobuf:"bytes,1,opt,name=info" json:"info,omitempty"` } func (m *GetRibInfoResponse) Reset() { *m = GetRibInfoResponse{} } func (m *GetRibInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetRibInfoResponse) ProtoMessage() {} func (*GetRibInfoResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{169} } func (m *GetRibInfoResponse) GetInfo() *TableInfo { if m != nil { return m.Info } return nil } func init() { proto.RegisterType((*GetNeighborRequest)(nil), "gobgpapi.GetNeighborRequest") proto.RegisterType((*GetNeighborResponse)(nil), "gobgpapi.GetNeighborResponse") proto.RegisterType((*Arguments)(nil), "gobgpapi.Arguments") proto.RegisterType((*AddPathRequest)(nil), "gobgpapi.AddPathRequest") proto.RegisterType((*AddPathResponse)(nil), "gobgpapi.AddPathResponse") proto.RegisterType((*DeletePathRequest)(nil), "gobgpapi.DeletePathRequest") proto.RegisterType((*DeletePathResponse)(nil), "gobgpapi.DeletePathResponse") proto.RegisterType((*AddNeighborRequest)(nil), "gobgpapi.AddNeighborRequest") proto.RegisterType((*AddNeighborResponse)(nil), "gobgpapi.AddNeighborResponse") proto.RegisterType((*DeleteNeighborRequest)(nil), "gobgpapi.DeleteNeighborRequest") proto.RegisterType((*DeleteNeighborResponse)(nil), "gobgpapi.DeleteNeighborResponse") proto.RegisterType((*ResetNeighborRequest)(nil), "gobgpapi.ResetNeighborRequest") proto.RegisterType((*ResetNeighborResponse)(nil), "gobgpapi.ResetNeighborResponse") proto.RegisterType((*SoftResetNeighborRequest)(nil), "gobgpapi.SoftResetNeighborRequest") proto.RegisterType((*SoftResetNeighborResponse)(nil), "gobgpapi.SoftResetNeighborResponse") proto.RegisterType((*ShutdownNeighborRequest)(nil), "gobgpapi.ShutdownNeighborRequest") proto.RegisterType((*ShutdownNeighborResponse)(nil), "gobgpapi.ShutdownNeighborResponse") proto.RegisterType((*EnableNeighborRequest)(nil), "gobgpapi.EnableNeighborRequest") proto.RegisterType((*EnableNeighborResponse)(nil), "gobgpapi.EnableNeighborResponse") proto.RegisterType((*DisableNeighborRequest)(nil), "gobgpapi.DisableNeighborRequest") proto.RegisterType((*DisableNeighborResponse)(nil), "gobgpapi.DisableNeighborResponse") proto.RegisterType((*EnableMrtRequest)(nil), "gobgpapi.EnableMrtRequest") proto.RegisterType((*EnableMrtResponse)(nil), "gobgpapi.EnableMrtResponse") proto.RegisterType((*DisableMrtRequest)(nil), "gobgpapi.DisableMrtRequest") proto.RegisterType((*DisableMrtResponse)(nil), "gobgpapi.DisableMrtResponse") proto.RegisterType((*InjectMrtRequest)(nil), "gobgpapi.InjectMrtRequest") proto.RegisterType((*InjectMrtResponse)(nil), "gobgpapi.InjectMrtResponse") proto.RegisterType((*AddBmpRequest)(nil), "gobgpapi.AddBmpRequest") proto.RegisterType((*AddBmpResponse)(nil), "gobgpapi.AddBmpResponse") proto.RegisterType((*DeleteBmpRequest)(nil), "gobgpapi.DeleteBmpRequest") proto.RegisterType((*DeleteBmpResponse)(nil), "gobgpapi.DeleteBmpResponse") proto.RegisterType((*MonitorRibRequest)(nil), "gobgpapi.MonitorRibRequest") proto.RegisterType((*RPKIConf)(nil), "gobgpapi.RPKIConf") proto.RegisterType((*RPKIState)(nil), "gobgpapi.RPKIState") proto.RegisterType((*Rpki)(nil), "gobgpapi.Rpki") proto.RegisterType((*GetRpkiRequest)(nil), "gobgpapi.GetRpkiRequest") proto.RegisterType((*GetRpkiResponse)(nil), "gobgpapi.GetRpkiResponse") proto.RegisterType((*AddRpkiRequest)(nil), "gobgpapi.AddRpkiRequest") proto.RegisterType((*AddRpkiResponse)(nil), "gobgpapi.AddRpkiResponse") proto.RegisterType((*DeleteRpkiRequest)(nil), "gobgpapi.DeleteRpkiRequest") proto.RegisterType((*DeleteRpkiResponse)(nil), "gobgpapi.DeleteRpkiResponse") proto.RegisterType((*EnableRpkiRequest)(nil), "gobgpapi.EnableRpkiRequest") proto.RegisterType((*EnableRpkiResponse)(nil), "gobgpapi.EnableRpkiResponse") proto.RegisterType((*DisableRpkiRequest)(nil), "gobgpapi.DisableRpkiRequest") proto.RegisterType((*DisableRpkiResponse)(nil), "gobgpapi.DisableRpkiResponse") proto.RegisterType((*ResetRpkiRequest)(nil), "gobgpapi.ResetRpkiRequest") proto.RegisterType((*ResetRpkiResponse)(nil), "gobgpapi.ResetRpkiResponse") proto.RegisterType((*SoftResetRpkiRequest)(nil), "gobgpapi.SoftResetRpkiRequest") proto.RegisterType((*SoftResetRpkiResponse)(nil), "gobgpapi.SoftResetRpkiResponse") proto.RegisterType((*EnableZebraRequest)(nil), "gobgpapi.EnableZebraRequest") proto.RegisterType((*EnableZebraResponse)(nil), "gobgpapi.EnableZebraResponse") proto.RegisterType((*GetVrfRequest)(nil), "gobgpapi.GetVrfRequest") proto.RegisterType((*GetVrfResponse)(nil), "gobgpapi.GetVrfResponse") proto.RegisterType((*AddVrfRequest)(nil), "gobgpapi.AddVrfRequest") proto.RegisterType((*AddVrfResponse)(nil), "gobgpapi.AddVrfResponse") proto.RegisterType((*DeleteVrfRequest)(nil), "gobgpapi.DeleteVrfRequest") proto.RegisterType((*DeleteVrfResponse)(nil), "gobgpapi.DeleteVrfResponse") proto.RegisterType((*GetDefinedSetRequest)(nil), "gobgpapi.GetDefinedSetRequest") proto.RegisterType((*GetDefinedSetResponse)(nil), "gobgpapi.GetDefinedSetResponse") proto.RegisterType((*AddDefinedSetRequest)(nil), "gobgpapi.AddDefinedSetRequest") proto.RegisterType((*AddDefinedSetResponse)(nil), "gobgpapi.AddDefinedSetResponse") proto.RegisterType((*DeleteDefinedSetRequest)(nil), "gobgpapi.DeleteDefinedSetRequest") proto.RegisterType((*DeleteDefinedSetResponse)(nil), "gobgpapi.DeleteDefinedSetResponse") proto.RegisterType((*ReplaceDefinedSetRequest)(nil), "gobgpapi.ReplaceDefinedSetRequest") proto.RegisterType((*ReplaceDefinedSetResponse)(nil), "gobgpapi.ReplaceDefinedSetResponse") proto.RegisterType((*GetStatementRequest)(nil), "gobgpapi.GetStatementRequest") proto.RegisterType((*GetStatementResponse)(nil), "gobgpapi.GetStatementResponse") proto.RegisterType((*AddStatementRequest)(nil), "gobgpapi.AddStatementRequest") proto.RegisterType((*AddStatementResponse)(nil), "gobgpapi.AddStatementResponse") proto.RegisterType((*DeleteStatementRequest)(nil), "gobgpapi.DeleteStatementRequest") proto.RegisterType((*DeleteStatementResponse)(nil), "gobgpapi.DeleteStatementResponse") proto.RegisterType((*ReplaceStatementRequest)(nil), "gobgpapi.ReplaceStatementRequest") proto.RegisterType((*ReplaceStatementResponse)(nil), "gobgpapi.ReplaceStatementResponse") proto.RegisterType((*GetPolicyRequest)(nil), "gobgpapi.GetPolicyRequest") proto.RegisterType((*GetPolicyResponse)(nil), "gobgpapi.GetPolicyResponse") proto.RegisterType((*AddPolicyRequest)(nil), "gobgpapi.AddPolicyRequest") proto.RegisterType((*AddPolicyResponse)(nil), "gobgpapi.AddPolicyResponse") proto.RegisterType((*DeletePolicyRequest)(nil), "gobgpapi.DeletePolicyRequest") proto.RegisterType((*DeletePolicyResponse)(nil), "gobgpapi.DeletePolicyResponse") proto.RegisterType((*ReplacePolicyRequest)(nil), "gobgpapi.ReplacePolicyRequest") proto.RegisterType((*ReplacePolicyResponse)(nil), "gobgpapi.ReplacePolicyResponse") proto.RegisterType((*GetPolicyAssignmentRequest)(nil), "gobgpapi.GetPolicyAssignmentRequest") proto.RegisterType((*GetPolicyAssignmentResponse)(nil), "gobgpapi.GetPolicyAssignmentResponse") proto.RegisterType((*AddPolicyAssignmentRequest)(nil), "gobgpapi.AddPolicyAssignmentRequest") proto.RegisterType((*AddPolicyAssignmentResponse)(nil), "gobgpapi.AddPolicyAssignmentResponse") proto.RegisterType((*DeletePolicyAssignmentRequest)(nil), "gobgpapi.DeletePolicyAssignmentRequest") proto.RegisterType((*DeletePolicyAssignmentResponse)(nil), "gobgpapi.DeletePolicyAssignmentResponse") proto.RegisterType((*ReplacePolicyAssignmentRequest)(nil), "gobgpapi.ReplacePolicyAssignmentRequest") proto.RegisterType((*ReplacePolicyAssignmentResponse)(nil), "gobgpapi.ReplacePolicyAssignmentResponse") proto.RegisterType((*GetServerRequest)(nil), "gobgpapi.GetServerRequest") proto.RegisterType((*GetServerResponse)(nil), "gobgpapi.GetServerResponse") proto.RegisterType((*StartServerRequest)(nil), "gobgpapi.StartServerRequest") proto.RegisterType((*StartServerResponse)(nil), "gobgpapi.StartServerResponse") proto.RegisterType((*StopServerRequest)(nil), "gobgpapi.StopServerRequest") proto.RegisterType((*StopServerResponse)(nil), "gobgpapi.StopServerResponse") proto.RegisterType((*RPKIValidation)(nil), "gobgpapi.RPKIValidation") proto.RegisterType((*Path)(nil), "gobgpapi.Path") proto.RegisterType((*Destination)(nil), "gobgpapi.Destination") proto.RegisterType((*Table)(nil), "gobgpapi.Table") proto.RegisterType((*GetRibRequest)(nil), "gobgpapi.GetRibRequest") proto.RegisterType((*GetRibResponse)(nil), "gobgpapi.GetRibResponse") proto.RegisterType((*TableLookupPrefix)(nil), "gobgpapi.TableLookupPrefix") proto.RegisterType((*GetPathRequest)(nil), "gobgpapi.GetPathRequest") proto.RegisterType((*ValidateRibRequest)(nil), "gobgpapi.ValidateRibRequest") proto.RegisterType((*ValidateRibResponse)(nil), "gobgpapi.ValidateRibResponse") proto.RegisterType((*Peer)(nil), "gobgpapi.Peer") proto.RegisterType((*ApplyPolicy)(nil), "gobgpapi.ApplyPolicy") proto.RegisterType((*PrefixLimit)(nil), "gobgpapi.PrefixLimit") proto.RegisterType((*PeerConf)(nil), "gobgpapi.PeerConf") proto.RegisterType((*EbgpMultihop)(nil), "gobgpapi.EbgpMultihop") proto.RegisterType((*RouteReflector)(nil), "gobgpapi.RouteReflector") proto.RegisterType((*PeerState)(nil), "gobgpapi.PeerState") proto.RegisterType((*Messages)(nil), "gobgpapi.Messages") proto.RegisterType((*Message)(nil), "gobgpapi.Message") proto.RegisterType((*Queues)(nil), "gobgpapi.Queues") proto.RegisterType((*Timers)(nil), "gobgpapi.Timers") proto.RegisterType((*TimersConfig)(nil), "gobgpapi.TimersConfig") proto.RegisterType((*TimersState)(nil), "gobgpapi.TimersState") proto.RegisterType((*Transport)(nil), "gobgpapi.Transport") proto.RegisterType((*RouteServer)(nil), "gobgpapi.RouteServer") proto.RegisterType((*GracefulRestart)(nil), "gobgpapi.GracefulRestart") proto.RegisterType((*MpGracefulRestartConfig)(nil), "gobgpapi.MpGracefulRestartConfig") proto.RegisterType((*MpGracefulRestartState)(nil), "gobgpapi.MpGracefulRestartState") proto.RegisterType((*MpGracefulRestart)(nil), "gobgpapi.MpGracefulRestart") proto.RegisterType((*AfiSafiConfig)(nil), "gobgpapi.AfiSafiConfig") proto.RegisterType((*AfiSafiState)(nil), "gobgpapi.AfiSafiState") proto.RegisterType((*RouteSelectionOptionsConfig)(nil), "gobgpapi.RouteSelectionOptionsConfig") proto.RegisterType((*RouteSelectionOptionsState)(nil), "gobgpapi.RouteSelectionOptionsState") proto.RegisterType((*RouteSelectionOptions)(nil), "gobgpapi.RouteSelectionOptions") proto.RegisterType((*UseMultiplePathsConfig)(nil), "gobgpapi.UseMultiplePathsConfig") proto.RegisterType((*UseMultiplePathsState)(nil), "gobgpapi.UseMultiplePathsState") proto.RegisterType((*EbgpConfig)(nil), "gobgpapi.EbgpConfig") proto.RegisterType((*EbgpState)(nil), "gobgpapi.EbgpState") proto.RegisterType((*Ebgp)(nil), "gobgpapi.Ebgp") proto.RegisterType((*IbgpConfig)(nil), "gobgpapi.IbgpConfig") proto.RegisterType((*IbgpState)(nil), "gobgpapi.IbgpState") proto.RegisterType((*Ibgp)(nil), "gobgpapi.Ibgp") proto.RegisterType((*UseMultiplePaths)(nil), "gobgpapi.UseMultiplePaths") proto.RegisterType((*RouteTargetMembershipConfig)(nil), "gobgpapi.RouteTargetMembershipConfig") proto.RegisterType((*RouteTargetMembershipState)(nil), "gobgpapi.RouteTargetMembershipState") proto.RegisterType((*RouteTargetMembership)(nil), "gobgpapi.RouteTargetMembership") proto.RegisterType((*LongLivedGracefulRestartConfig)(nil), "gobgpapi.LongLivedGracefulRestartConfig") proto.RegisterType((*LongLivedGracefulRestartState)(nil), "gobgpapi.LongLivedGracefulRestartState") proto.RegisterType((*LongLivedGracefulRestart)(nil), "gobgpapi.LongLivedGracefulRestart") proto.RegisterType((*AfiSafi)(nil), "gobgpapi.AfiSafi") proto.RegisterType((*AddPathsConfig)(nil), "gobgpapi.AddPathsConfig") proto.RegisterType((*AddPathsState)(nil), "gobgpapi.AddPathsState") proto.RegisterType((*AddPaths)(nil), "gobgpapi.AddPaths") proto.RegisterType((*Prefix)(nil), "gobgpapi.Prefix") proto.RegisterType((*DefinedSet)(nil), "gobgpapi.DefinedSet") proto.RegisterType((*MatchSet)(nil), "gobgpapi.MatchSet") proto.RegisterType((*AsPathLength)(nil), "gobgpapi.AsPathLength") proto.RegisterType((*Conditions)(nil), "gobgpapi.Conditions") proto.RegisterType((*CommunityAction)(nil), "gobgpapi.CommunityAction") proto.RegisterType((*MedAction)(nil), "gobgpapi.MedAction") proto.RegisterType((*AsPrependAction)(nil), "gobgpapi.AsPrependAction") proto.RegisterType((*NexthopAction)(nil), "gobgpapi.NexthopAction") proto.RegisterType((*LocalPrefAction)(nil), "gobgpapi.LocalPrefAction") proto.RegisterType((*Actions)(nil), "gobgpapi.Actions") proto.RegisterType((*Statement)(nil), "gobgpapi.Statement") proto.RegisterType((*Policy)(nil), "gobgpapi.Policy") proto.RegisterType((*PolicyAssignment)(nil), "gobgpapi.PolicyAssignment") proto.RegisterType((*Roa)(nil), "gobgpapi.Roa") proto.RegisterType((*GetRoaRequest)(nil), "gobgpapi.GetRoaRequest") proto.RegisterType((*GetRoaResponse)(nil), "gobgpapi.GetRoaResponse") proto.RegisterType((*Vrf)(nil), "gobgpapi.Vrf") proto.RegisterType((*Global)(nil), "gobgpapi.Global") proto.RegisterType((*TableInfo)(nil), "gobgpapi.TableInfo") proto.RegisterType((*GetRibInfoRequest)(nil), "gobgpapi.GetRibInfoRequest") proto.RegisterType((*GetRibInfoResponse)(nil), "gobgpapi.GetRibInfoResponse") proto.RegisterEnum("gobgpapi.Family", Family_name, Family_value) proto.RegisterEnum("gobgpapi.Resource", Resource_name, Resource_value) proto.RegisterEnum("gobgpapi.TableLookupOption", TableLookupOption_name, TableLookupOption_value) proto.RegisterEnum("gobgpapi.DefinedType", DefinedType_name, DefinedType_value) proto.RegisterEnum("gobgpapi.MatchType", MatchType_name, MatchType_value) proto.RegisterEnum("gobgpapi.AsPathLengthType", AsPathLengthType_name, AsPathLengthType_value) proto.RegisterEnum("gobgpapi.RouteAction", RouteAction_name, RouteAction_value) proto.RegisterEnum("gobgpapi.CommunityActionType", CommunityActionType_name, CommunityActionType_value) proto.RegisterEnum("gobgpapi.MedActionType", MedActionType_name, MedActionType_value) proto.RegisterEnum("gobgpapi.PolicyType", PolicyType_name, PolicyType_value) proto.RegisterEnum("gobgpapi.SoftResetNeighborRequest_SoftResetDirection", SoftResetNeighborRequest_SoftResetDirection_name, SoftResetNeighborRequest_SoftResetDirection_value) proto.RegisterEnum("gobgpapi.AddBmpRequest_MonitoringPolicy", AddBmpRequest_MonitoringPolicy_name, AddBmpRequest_MonitoringPolicy_value) proto.RegisterEnum("gobgpapi.RPKIValidation_State", RPKIValidation_State_name, RPKIValidation_State_value) proto.RegisterEnum("gobgpapi.RPKIValidation_Reason", RPKIValidation_Reason_name, RPKIValidation_Reason_value) proto.RegisterEnum("gobgpapi.PeerConf_RemovePrivateAs", PeerConf_RemovePrivateAs_name, PeerConf_RemovePrivateAs_value) proto.RegisterEnum("gobgpapi.PeerState_AdminState", PeerState_AdminState_name, PeerState_AdminState_value) proto.RegisterEnum("gobgpapi.Conditions_RouteType", Conditions_RouteType_name, Conditions_RouteType_value) } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion4 // Client API for GobgpApi service type GobgpApiClient interface { StartServer(ctx context.Context, in *StartServerRequest, opts ...grpc.CallOption) (*StartServerResponse, error) StopServer(ctx context.Context, in *StopServerRequest, opts ...grpc.CallOption) (*StopServerResponse, error) GetServer(ctx context.Context, in *GetServerRequest, opts ...grpc.CallOption) (*GetServerResponse, error) AddNeighbor(ctx context.Context, in *AddNeighborRequest, opts ...grpc.CallOption) (*AddNeighborResponse, error) DeleteNeighbor(ctx context.Context, in *DeleteNeighborRequest, opts ...grpc.CallOption) (*DeleteNeighborResponse, error) GetNeighbor(ctx context.Context, in *GetNeighborRequest, opts ...grpc.CallOption) (*GetNeighborResponse, error) ResetNeighbor(ctx context.Context, in *ResetNeighborRequest, opts ...grpc.CallOption) (*ResetNeighborResponse, error) SoftResetNeighbor(ctx context.Context, in *SoftResetNeighborRequest, opts ...grpc.CallOption) (*SoftResetNeighborResponse, error) ShutdownNeighbor(ctx context.Context, in *ShutdownNeighborRequest, opts ...grpc.CallOption) (*ShutdownNeighborResponse, error) EnableNeighbor(ctx context.Context, in *EnableNeighborRequest, opts ...grpc.CallOption) (*EnableNeighborResponse, error) DisableNeighbor(ctx context.Context, in *DisableNeighborRequest, opts ...grpc.CallOption) (*DisableNeighborResponse, error) GetRib(ctx context.Context, in *GetRibRequest, opts ...grpc.CallOption) (*GetRibResponse, error) GetPath(ctx context.Context, in *GetPathRequest, opts ...grpc.CallOption) (GobgpApi_GetPathClient, error) ValidateRib(ctx context.Context, in *ValidateRibRequest, opts ...grpc.CallOption) (*ValidateRibResponse, error) AddPath(ctx context.Context, in *AddPathRequest, opts ...grpc.CallOption) (*AddPathResponse, error) DeletePath(ctx context.Context, in *DeletePathRequest, opts ...grpc.CallOption) (*DeletePathResponse, error) MonitorRib(ctx context.Context, in *MonitorRibRequest, opts ...grpc.CallOption) (GobgpApi_MonitorRibClient, error) MonitorPeerState(ctx context.Context, in *Arguments, opts ...grpc.CallOption) (GobgpApi_MonitorPeerStateClient, error) EnableMrt(ctx context.Context, in *EnableMrtRequest, opts ...grpc.CallOption) (*EnableMrtResponse, error) DisableMrt(ctx context.Context, in *DisableMrtRequest, opts ...grpc.CallOption) (*DisableMrtResponse, error) InjectMrt(ctx context.Context, opts ...grpc.CallOption) (GobgpApi_InjectMrtClient, error) AddBmp(ctx context.Context, in *AddBmpRequest, opts ...grpc.CallOption) (*AddBmpResponse, error) DeleteBmp(ctx context.Context, in *DeleteBmpRequest, opts ...grpc.CallOption) (*DeleteBmpResponse, error) GetRpki(ctx context.Context, in *GetRpkiRequest, opts ...grpc.CallOption) (*GetRpkiResponse, error) AddRpki(ctx context.Context, in *AddRpkiRequest, opts ...grpc.CallOption) (*AddRpkiResponse, error) DeleteRpki(ctx context.Context, in *DeleteRpkiRequest, opts ...grpc.CallOption) (*DeleteRpkiResponse, error) EnableRpki(ctx context.Context, in *EnableRpkiRequest, opts ...grpc.CallOption) (*EnableRpkiResponse, error) DisableRpki(ctx context.Context, in *DisableRpkiRequest, opts ...grpc.CallOption) (*DisableRpkiResponse, error) ResetRpki(ctx context.Context, in *ResetRpkiRequest, opts ...grpc.CallOption) (*ResetRpkiResponse, error) SoftResetRpki(ctx context.Context, in *SoftResetRpkiRequest, opts ...grpc.CallOption) (*SoftResetRpkiResponse, error) GetRoa(ctx context.Context, in *GetRoaRequest, opts ...grpc.CallOption) (*GetRoaResponse, error) EnableZebra(ctx context.Context, in *EnableZebraRequest, opts ...grpc.CallOption) (*EnableZebraResponse, error) AddVrf(ctx context.Context, in *AddVrfRequest, opts ...grpc.CallOption) (*AddVrfResponse, error) DeleteVrf(ctx context.Context, in *DeleteVrfRequest, opts ...grpc.CallOption) (*DeleteVrfResponse, error) GetVrf(ctx context.Context, in *GetVrfRequest, opts ...grpc.CallOption) (*GetVrfResponse, error) GetDefinedSet(ctx context.Context, in *GetDefinedSetRequest, opts ...grpc.CallOption) (*GetDefinedSetResponse, error) AddDefinedSet(ctx context.Context, in *AddDefinedSetRequest, opts ...grpc.CallOption) (*AddDefinedSetResponse, error) DeleteDefinedSet(ctx context.Context, in *DeleteDefinedSetRequest, opts ...grpc.CallOption) (*DeleteDefinedSetResponse, error) ReplaceDefinedSet(ctx context.Context, in *ReplaceDefinedSetRequest, opts ...grpc.CallOption) (*ReplaceDefinedSetResponse, error) GetStatement(ctx context.Context, in *GetStatementRequest, opts ...grpc.CallOption) (*GetStatementResponse, error) AddStatement(ctx context.Context, in *AddStatementRequest, opts ...grpc.CallOption) (*AddStatementResponse, error) DeleteStatement(ctx context.Context, in *DeleteStatementRequest, opts ...grpc.CallOption) (*DeleteStatementResponse, error) ReplaceStatement(ctx context.Context, in *ReplaceStatementRequest, opts ...grpc.CallOption) (*ReplaceStatementResponse, error) GetPolicy(ctx context.Context, in *GetPolicyRequest, opts ...grpc.CallOption) (*GetPolicyResponse, error) AddPolicy(ctx context.Context, in *AddPolicyRequest, opts ...grpc.CallOption) (*AddPolicyResponse, error) DeletePolicy(ctx context.Context, in *DeletePolicyRequest, opts ...grpc.CallOption) (*DeletePolicyResponse, error) ReplacePolicy(ctx context.Context, in *ReplacePolicyRequest, opts ...grpc.CallOption) (*ReplacePolicyResponse, error) GetPolicyAssignment(ctx context.Context, in *GetPolicyAssignmentRequest, opts ...grpc.CallOption) (*GetPolicyAssignmentResponse, error) AddPolicyAssignment(ctx context.Context, in *AddPolicyAssignmentRequest, opts ...grpc.CallOption) (*AddPolicyAssignmentResponse, error) DeletePolicyAssignment(ctx context.Context, in *DeletePolicyAssignmentRequest, opts ...grpc.CallOption) (*DeletePolicyAssignmentResponse, error) ReplacePolicyAssignment(ctx context.Context, in *ReplacePolicyAssignmentRequest, opts ...grpc.CallOption) (*ReplacePolicyAssignmentResponse, error) GetRibInfo(ctx context.Context, in *GetRibInfoRequest, opts ...grpc.CallOption) (*GetRibInfoResponse, error) } type gobgpApiClient struct { cc *grpc.ClientConn } func NewGobgpApiClient(cc *grpc.ClientConn) GobgpApiClient { return &gobgpApiClient{cc} } func (c *gobgpApiClient) StartServer(ctx context.Context, in *StartServerRequest, opts ...grpc.CallOption) (*StartServerResponse, error) { out := new(StartServerResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/StartServer", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) StopServer(ctx context.Context, in *StopServerRequest, opts ...grpc.CallOption) (*StopServerResponse, error) { out := new(StopServerResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/StopServer", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetServer(ctx context.Context, in *GetServerRequest, opts ...grpc.CallOption) (*GetServerResponse, error) { out := new(GetServerResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/GetServer", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) AddNeighbor(ctx context.Context, in *AddNeighborRequest, opts ...grpc.CallOption) (*AddNeighborResponse, error) { out := new(AddNeighborResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/AddNeighbor", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DeleteNeighbor(ctx context.Context, in *DeleteNeighborRequest, opts ...grpc.CallOption) (*DeleteNeighborResponse, error) { out := new(DeleteNeighborResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DeleteNeighbor", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetNeighbor(ctx context.Context, in *GetNeighborRequest, opts ...grpc.CallOption) (*GetNeighborResponse, error) { out := new(GetNeighborResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/GetNeighbor", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) ResetNeighbor(ctx context.Context, in *ResetNeighborRequest, opts ...grpc.CallOption) (*ResetNeighborResponse, error) { out := new(ResetNeighborResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/ResetNeighbor", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) SoftResetNeighbor(ctx context.Context, in *SoftResetNeighborRequest, opts ...grpc.CallOption) (*SoftResetNeighborResponse, error) { out := new(SoftResetNeighborResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/SoftResetNeighbor", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) ShutdownNeighbor(ctx context.Context, in *ShutdownNeighborRequest, opts ...grpc.CallOption) (*ShutdownNeighborResponse, error) { out := new(ShutdownNeighborResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/ShutdownNeighbor", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) EnableNeighbor(ctx context.Context, in *EnableNeighborRequest, opts ...grpc.CallOption) (*EnableNeighborResponse, error) { out := new(EnableNeighborResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/EnableNeighbor", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DisableNeighbor(ctx context.Context, in *DisableNeighborRequest, opts ...grpc.CallOption) (*DisableNeighborResponse, error) { out := new(DisableNeighborResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DisableNeighbor", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetRib(ctx context.Context, in *GetRibRequest, opts ...grpc.CallOption) (*GetRibResponse, error) { out := new(GetRibResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/GetRib", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetPath(ctx context.Context, in *GetPathRequest, opts ...grpc.CallOption) (GobgpApi_GetPathClient, error) { stream, err := grpc.NewClientStream(ctx, &_GobgpApi_serviceDesc.Streams[0], c.cc, "/gobgpapi.GobgpApi/GetPath", opts...) if err != nil { return nil, err } x := &gobgpApiGetPathClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type GobgpApi_GetPathClient interface { Recv() (*Path, error) grpc.ClientStream } type gobgpApiGetPathClient struct { grpc.ClientStream } func (x *gobgpApiGetPathClient) Recv() (*Path, error) { m := new(Path) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *gobgpApiClient) ValidateRib(ctx context.Context, in *ValidateRibRequest, opts ...grpc.CallOption) (*ValidateRibResponse, error) { out := new(ValidateRibResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/ValidateRib", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) AddPath(ctx context.Context, in *AddPathRequest, opts ...grpc.CallOption) (*AddPathResponse, error) { out := new(AddPathResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/AddPath", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DeletePath(ctx context.Context, in *DeletePathRequest, opts ...grpc.CallOption) (*DeletePathResponse, error) { out := new(DeletePathResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DeletePath", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) MonitorRib(ctx context.Context, in *MonitorRibRequest, opts ...grpc.CallOption) (GobgpApi_MonitorRibClient, error) { stream, err := grpc.NewClientStream(ctx, &_GobgpApi_serviceDesc.Streams[1], c.cc, "/gobgpapi.GobgpApi/MonitorRib", opts...) if err != nil { return nil, err } x := &gobgpApiMonitorRibClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type GobgpApi_MonitorRibClient interface { Recv() (*Destination, error) grpc.ClientStream } type gobgpApiMonitorRibClient struct { grpc.ClientStream } func (x *gobgpApiMonitorRibClient) Recv() (*Destination, error) { m := new(Destination) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *gobgpApiClient) MonitorPeerState(ctx context.Context, in *Arguments, opts ...grpc.CallOption) (GobgpApi_MonitorPeerStateClient, error) { stream, err := grpc.NewClientStream(ctx, &_GobgpApi_serviceDesc.Streams[2], c.cc, "/gobgpapi.GobgpApi/MonitorPeerState", opts...) if err != nil { return nil, err } x := &gobgpApiMonitorPeerStateClient{stream} if err := x.ClientStream.SendMsg(in); err != nil { return nil, err } if err := x.ClientStream.CloseSend(); err != nil { return nil, err } return x, nil } type GobgpApi_MonitorPeerStateClient interface { Recv() (*Peer, error) grpc.ClientStream } type gobgpApiMonitorPeerStateClient struct { grpc.ClientStream } func (x *gobgpApiMonitorPeerStateClient) Recv() (*Peer, error) { m := new(Peer) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *gobgpApiClient) EnableMrt(ctx context.Context, in *EnableMrtRequest, opts ...grpc.CallOption) (*EnableMrtResponse, error) { out := new(EnableMrtResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/EnableMrt", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DisableMrt(ctx context.Context, in *DisableMrtRequest, opts ...grpc.CallOption) (*DisableMrtResponse, error) { out := new(DisableMrtResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DisableMrt", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) InjectMrt(ctx context.Context, opts ...grpc.CallOption) (GobgpApi_InjectMrtClient, error) { stream, err := grpc.NewClientStream(ctx, &_GobgpApi_serviceDesc.Streams[3], c.cc, "/gobgpapi.GobgpApi/InjectMrt", opts...) if err != nil { return nil, err } x := &gobgpApiInjectMrtClient{stream} return x, nil } type GobgpApi_InjectMrtClient interface { Send(*InjectMrtRequest) error CloseAndRecv() (*InjectMrtResponse, error) grpc.ClientStream } type gobgpApiInjectMrtClient struct { grpc.ClientStream } func (x *gobgpApiInjectMrtClient) Send(m *InjectMrtRequest) error { return x.ClientStream.SendMsg(m) } func (x *gobgpApiInjectMrtClient) CloseAndRecv() (*InjectMrtResponse, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } m := new(InjectMrtResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func (c *gobgpApiClient) AddBmp(ctx context.Context, in *AddBmpRequest, opts ...grpc.CallOption) (*AddBmpResponse, error) { out := new(AddBmpResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/AddBmp", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DeleteBmp(ctx context.Context, in *DeleteBmpRequest, opts ...grpc.CallOption) (*DeleteBmpResponse, error) { out := new(DeleteBmpResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DeleteBmp", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetRpki(ctx context.Context, in *GetRpkiRequest, opts ...grpc.CallOption) (*GetRpkiResponse, error) { out := new(GetRpkiResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/GetRpki", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) AddRpki(ctx context.Context, in *AddRpkiRequest, opts ...grpc.CallOption) (*AddRpkiResponse, error) { out := new(AddRpkiResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/AddRpki", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DeleteRpki(ctx context.Context, in *DeleteRpkiRequest, opts ...grpc.CallOption) (*DeleteRpkiResponse, error) { out := new(DeleteRpkiResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DeleteRpki", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) EnableRpki(ctx context.Context, in *EnableRpkiRequest, opts ...grpc.CallOption) (*EnableRpkiResponse, error) { out := new(EnableRpkiResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/EnableRpki", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DisableRpki(ctx context.Context, in *DisableRpkiRequest, opts ...grpc.CallOption) (*DisableRpkiResponse, error) { out := new(DisableRpkiResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DisableRpki", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) ResetRpki(ctx context.Context, in *ResetRpkiRequest, opts ...grpc.CallOption) (*ResetRpkiResponse, error) { out := new(ResetRpkiResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/ResetRpki", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) SoftResetRpki(ctx context.Context, in *SoftResetRpkiRequest, opts ...grpc.CallOption) (*SoftResetRpkiResponse, error) { out := new(SoftResetRpkiResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/SoftResetRpki", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetRoa(ctx context.Context, in *GetRoaRequest, opts ...grpc.CallOption) (*GetRoaResponse, error) { out := new(GetRoaResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/GetRoa", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) EnableZebra(ctx context.Context, in *EnableZebraRequest, opts ...grpc.CallOption) (*EnableZebraResponse, error) { out := new(EnableZebraResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/EnableZebra", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) AddVrf(ctx context.Context, in *AddVrfRequest, opts ...grpc.CallOption) (*AddVrfResponse, error) { out := new(AddVrfResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/AddVrf", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DeleteVrf(ctx context.Context, in *DeleteVrfRequest, opts ...grpc.CallOption) (*DeleteVrfResponse, error) { out := new(DeleteVrfResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DeleteVrf", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetVrf(ctx context.Context, in *GetVrfRequest, opts ...grpc.CallOption) (*GetVrfResponse, error) { out := new(GetVrfResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/GetVrf", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetDefinedSet(ctx context.Context, in *GetDefinedSetRequest, opts ...grpc.CallOption) (*GetDefinedSetResponse, error) { out := new(GetDefinedSetResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/GetDefinedSet", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) AddDefinedSet(ctx context.Context, in *AddDefinedSetRequest, opts ...grpc.CallOption) (*AddDefinedSetResponse, error) { out := new(AddDefinedSetResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/AddDefinedSet", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DeleteDefinedSet(ctx context.Context, in *DeleteDefinedSetRequest, opts ...grpc.CallOption) (*DeleteDefinedSetResponse, error) { out := new(DeleteDefinedSetResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DeleteDefinedSet", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) ReplaceDefinedSet(ctx context.Context, in *ReplaceDefinedSetRequest, opts ...grpc.CallOption) (*ReplaceDefinedSetResponse, error) { out := new(ReplaceDefinedSetResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/ReplaceDefinedSet", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetStatement(ctx context.Context, in *GetStatementRequest, opts ...grpc.CallOption) (*GetStatementResponse, error) { out := new(GetStatementResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/GetStatement", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) AddStatement(ctx context.Context, in *AddStatementRequest, opts ...grpc.CallOption) (*AddStatementResponse, error) { out := new(AddStatementResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/AddStatement", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DeleteStatement(ctx context.Context, in *DeleteStatementRequest, opts ...grpc.CallOption) (*DeleteStatementResponse, error) { out := new(DeleteStatementResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DeleteStatement", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) ReplaceStatement(ctx context.Context, in *ReplaceStatementRequest, opts ...grpc.CallOption) (*ReplaceStatementResponse, error) { out := new(ReplaceStatementResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/ReplaceStatement", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetPolicy(ctx context.Context, in *GetPolicyRequest, opts ...grpc.CallOption) (*GetPolicyResponse, error) { out := new(GetPolicyResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/GetPolicy", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) AddPolicy(ctx context.Context, in *AddPolicyRequest, opts ...grpc.CallOption) (*AddPolicyResponse, error) { out := new(AddPolicyResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/AddPolicy", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DeletePolicy(ctx context.Context, in *DeletePolicyRequest, opts ...grpc.CallOption) (*DeletePolicyResponse, error) { out := new(DeletePolicyResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DeletePolicy", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) ReplacePolicy(ctx context.Context, in *ReplacePolicyRequest, opts ...grpc.CallOption) (*ReplacePolicyResponse, error) { out := new(ReplacePolicyResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/ReplacePolicy", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetPolicyAssignment(ctx context.Context, in *GetPolicyAssignmentRequest, opts ...grpc.CallOption) (*GetPolicyAssignmentResponse, error) { out := new(GetPolicyAssignmentResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/GetPolicyAssignment", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) AddPolicyAssignment(ctx context.Context, in *AddPolicyAssignmentRequest, opts ...grpc.CallOption) (*AddPolicyAssignmentResponse, error) { out := new(AddPolicyAssignmentResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/AddPolicyAssignment", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) DeletePolicyAssignment(ctx context.Context, in *DeletePolicyAssignmentRequest, opts ...grpc.CallOption) (*DeletePolicyAssignmentResponse, error) { out := new(DeletePolicyAssignmentResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/DeletePolicyAssignment", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) ReplacePolicyAssignment(ctx context.Context, in *ReplacePolicyAssignmentRequest, opts ...grpc.CallOption) (*ReplacePolicyAssignmentResponse, error) { out := new(ReplacePolicyAssignmentResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/ReplacePolicyAssignment", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } func (c *gobgpApiClient) GetRibInfo(ctx context.Context, in *GetRibInfoRequest, opts ...grpc.CallOption) (*GetRibInfoResponse, error) { out := new(GetRibInfoResponse) err := grpc.Invoke(ctx, "/gobgpapi.GobgpApi/GetRibInfo", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } // Server API for GobgpApi service type GobgpApiServer interface { StartServer(context.Context, *StartServerRequest) (*StartServerResponse, error) StopServer(context.Context, *StopServerRequest) (*StopServerResponse, error) GetServer(context.Context, *GetServerRequest) (*GetServerResponse, error) AddNeighbor(context.Context, *AddNeighborRequest) (*AddNeighborResponse, error) DeleteNeighbor(context.Context, *DeleteNeighborRequest) (*DeleteNeighborResponse, error) GetNeighbor(context.Context, *GetNeighborRequest) (*GetNeighborResponse, error) ResetNeighbor(context.Context, *ResetNeighborRequest) (*ResetNeighborResponse, error) SoftResetNeighbor(context.Context, *SoftResetNeighborRequest) (*SoftResetNeighborResponse, error) ShutdownNeighbor(context.Context, *ShutdownNeighborRequest) (*ShutdownNeighborResponse, error) EnableNeighbor(context.Context, *EnableNeighborRequest) (*EnableNeighborResponse, error) DisableNeighbor(context.Context, *DisableNeighborRequest) (*DisableNeighborResponse, error) GetRib(context.Context, *GetRibRequest) (*GetRibResponse, error) GetPath(*GetPathRequest, GobgpApi_GetPathServer) error ValidateRib(context.Context, *ValidateRibRequest) (*ValidateRibResponse, error) AddPath(context.Context, *AddPathRequest) (*AddPathResponse, error) DeletePath(context.Context, *DeletePathRequest) (*DeletePathResponse, error) MonitorRib(*MonitorRibRequest, GobgpApi_MonitorRibServer) error MonitorPeerState(*Arguments, GobgpApi_MonitorPeerStateServer) error EnableMrt(context.Context, *EnableMrtRequest) (*EnableMrtResponse, error) DisableMrt(context.Context, *DisableMrtRequest) (*DisableMrtResponse, error) InjectMrt(GobgpApi_InjectMrtServer) error AddBmp(context.Context, *AddBmpRequest) (*AddBmpResponse, error) DeleteBmp(context.Context, *DeleteBmpRequest) (*DeleteBmpResponse, error) GetRpki(context.Context, *GetRpkiRequest) (*GetRpkiResponse, error) AddRpki(context.Context, *AddRpkiRequest) (*AddRpkiResponse, error) DeleteRpki(context.Context, *DeleteRpkiRequest) (*DeleteRpkiResponse, error) EnableRpki(context.Context, *EnableRpkiRequest) (*EnableRpkiResponse, error) DisableRpki(context.Context, *DisableRpkiRequest) (*DisableRpkiResponse, error) ResetRpki(context.Context, *ResetRpkiRequest) (*ResetRpkiResponse, error) SoftResetRpki(context.Context, *SoftResetRpkiRequest) (*SoftResetRpkiResponse, error) GetRoa(context.Context, *GetRoaRequest) (*GetRoaResponse, error) EnableZebra(context.Context, *EnableZebraRequest) (*EnableZebraResponse, error) AddVrf(context.Context, *AddVrfRequest) (*AddVrfResponse, error) DeleteVrf(context.Context, *DeleteVrfRequest) (*DeleteVrfResponse, error) GetVrf(context.Context, *GetVrfRequest) (*GetVrfResponse, error) GetDefinedSet(context.Context, *GetDefinedSetRequest) (*GetDefinedSetResponse, error) AddDefinedSet(context.Context, *AddDefinedSetRequest) (*AddDefinedSetResponse, error) DeleteDefinedSet(context.Context, *DeleteDefinedSetRequest) (*DeleteDefinedSetResponse, error) ReplaceDefinedSet(context.Context, *ReplaceDefinedSetRequest) (*ReplaceDefinedSetResponse, error) GetStatement(context.Context, *GetStatementRequest) (*GetStatementResponse, error) AddStatement(context.Context, *AddStatementRequest) (*AddStatementResponse, error) DeleteStatement(context.Context, *DeleteStatementRequest) (*DeleteStatementResponse, error) ReplaceStatement(context.Context, *ReplaceStatementRequest) (*ReplaceStatementResponse, error) GetPolicy(context.Context, *GetPolicyRequest) (*GetPolicyResponse, error) AddPolicy(context.Context, *AddPolicyRequest) (*AddPolicyResponse, error) DeletePolicy(context.Context, *DeletePolicyRequest) (*DeletePolicyResponse, error) ReplacePolicy(context.Context, *ReplacePolicyRequest) (*ReplacePolicyResponse, error) GetPolicyAssignment(context.Context, *GetPolicyAssignmentRequest) (*GetPolicyAssignmentResponse, error) AddPolicyAssignment(context.Context, *AddPolicyAssignmentRequest) (*AddPolicyAssignmentResponse, error) DeletePolicyAssignment(context.Context, *DeletePolicyAssignmentRequest) (*DeletePolicyAssignmentResponse, error) ReplacePolicyAssignment(context.Context, *ReplacePolicyAssignmentRequest) (*ReplacePolicyAssignmentResponse, error) GetRibInfo(context.Context, *GetRibInfoRequest) (*GetRibInfoResponse, error) } func RegisterGobgpApiServer(s *grpc.Server, srv GobgpApiServer) { s.RegisterService(&_GobgpApi_serviceDesc, srv) } func _GobgpApi_StartServer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StartServerRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).StartServer(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/StartServer", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).StartServer(ctx, req.(*StartServerRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_StopServer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StopServerRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).StopServer(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/StopServer", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).StopServer(ctx, req.(*StopServerRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetServer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetServerRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).GetServer(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/GetServer", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).GetServer(ctx, req.(*GetServerRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_AddNeighbor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddNeighborRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).AddNeighbor(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/AddNeighbor", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).AddNeighbor(ctx, req.(*AddNeighborRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DeleteNeighbor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteNeighborRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DeleteNeighbor(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DeleteNeighbor", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DeleteNeighbor(ctx, req.(*DeleteNeighborRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetNeighbor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetNeighborRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).GetNeighbor(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/GetNeighbor", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).GetNeighbor(ctx, req.(*GetNeighborRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_ResetNeighbor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ResetNeighborRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).ResetNeighbor(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/ResetNeighbor", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).ResetNeighbor(ctx, req.(*ResetNeighborRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_SoftResetNeighbor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SoftResetNeighborRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).SoftResetNeighbor(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/SoftResetNeighbor", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).SoftResetNeighbor(ctx, req.(*SoftResetNeighborRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_ShutdownNeighbor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ShutdownNeighborRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).ShutdownNeighbor(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/ShutdownNeighbor", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).ShutdownNeighbor(ctx, req.(*ShutdownNeighborRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_EnableNeighbor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EnableNeighborRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).EnableNeighbor(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/EnableNeighbor", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).EnableNeighbor(ctx, req.(*EnableNeighborRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DisableNeighbor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DisableNeighborRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DisableNeighbor(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DisableNeighbor", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DisableNeighbor(ctx, req.(*DisableNeighborRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetRib_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetRibRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).GetRib(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/GetRib", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).GetRib(ctx, req.(*GetRibRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetPath_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(GetPathRequest) if err := stream.RecvMsg(m); err != nil { return err } return srv.(GobgpApiServer).GetPath(m, &gobgpApiGetPathServer{stream}) } type GobgpApi_GetPathServer interface { Send(*Path) error grpc.ServerStream } type gobgpApiGetPathServer struct { grpc.ServerStream } func (x *gobgpApiGetPathServer) Send(m *Path) error { return x.ServerStream.SendMsg(m) } func _GobgpApi_ValidateRib_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ValidateRibRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).ValidateRib(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/ValidateRib", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).ValidateRib(ctx, req.(*ValidateRibRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_AddPath_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddPathRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).AddPath(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/AddPath", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).AddPath(ctx, req.(*AddPathRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DeletePath_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeletePathRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DeletePath(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DeletePath", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DeletePath(ctx, req.(*DeletePathRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_MonitorRib_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(MonitorRibRequest) if err := stream.RecvMsg(m); err != nil { return err } return srv.(GobgpApiServer).MonitorRib(m, &gobgpApiMonitorRibServer{stream}) } type GobgpApi_MonitorRibServer interface { Send(*Destination) error grpc.ServerStream } type gobgpApiMonitorRibServer struct { grpc.ServerStream } func (x *gobgpApiMonitorRibServer) Send(m *Destination) error { return x.ServerStream.SendMsg(m) } func _GobgpApi_MonitorPeerState_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(Arguments) if err := stream.RecvMsg(m); err != nil { return err } return srv.(GobgpApiServer).MonitorPeerState(m, &gobgpApiMonitorPeerStateServer{stream}) } type GobgpApi_MonitorPeerStateServer interface { Send(*Peer) error grpc.ServerStream } type gobgpApiMonitorPeerStateServer struct { grpc.ServerStream } func (x *gobgpApiMonitorPeerStateServer) Send(m *Peer) error { return x.ServerStream.SendMsg(m) } func _GobgpApi_EnableMrt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EnableMrtRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).EnableMrt(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/EnableMrt", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).EnableMrt(ctx, req.(*EnableMrtRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DisableMrt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DisableMrtRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DisableMrt(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DisableMrt", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DisableMrt(ctx, req.(*DisableMrtRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_InjectMrt_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(GobgpApiServer).InjectMrt(&gobgpApiInjectMrtServer{stream}) } type GobgpApi_InjectMrtServer interface { SendAndClose(*InjectMrtResponse) error Recv() (*InjectMrtRequest, error) grpc.ServerStream } type gobgpApiInjectMrtServer struct { grpc.ServerStream } func (x *gobgpApiInjectMrtServer) SendAndClose(m *InjectMrtResponse) error { return x.ServerStream.SendMsg(m) } func (x *gobgpApiInjectMrtServer) Recv() (*InjectMrtRequest, error) { m := new(InjectMrtRequest) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } return m, nil } func _GobgpApi_AddBmp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddBmpRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).AddBmp(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/AddBmp", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).AddBmp(ctx, req.(*AddBmpRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DeleteBmp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteBmpRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DeleteBmp(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DeleteBmp", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DeleteBmp(ctx, req.(*DeleteBmpRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetRpki_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetRpkiRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).GetRpki(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/GetRpki", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).GetRpki(ctx, req.(*GetRpkiRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_AddRpki_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddRpkiRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).AddRpki(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/AddRpki", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).AddRpki(ctx, req.(*AddRpkiRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DeleteRpki_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteRpkiRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DeleteRpki(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DeleteRpki", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DeleteRpki(ctx, req.(*DeleteRpkiRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_EnableRpki_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EnableRpkiRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).EnableRpki(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/EnableRpki", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).EnableRpki(ctx, req.(*EnableRpkiRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DisableRpki_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DisableRpkiRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DisableRpki(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DisableRpki", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DisableRpki(ctx, req.(*DisableRpkiRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_ResetRpki_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ResetRpkiRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).ResetRpki(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/ResetRpki", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).ResetRpki(ctx, req.(*ResetRpkiRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_SoftResetRpki_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SoftResetRpkiRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).SoftResetRpki(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/SoftResetRpki", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).SoftResetRpki(ctx, req.(*SoftResetRpkiRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetRoa_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetRoaRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).GetRoa(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/GetRoa", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).GetRoa(ctx, req.(*GetRoaRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_EnableZebra_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EnableZebraRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).EnableZebra(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/EnableZebra", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).EnableZebra(ctx, req.(*EnableZebraRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_AddVrf_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddVrfRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).AddVrf(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/AddVrf", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).AddVrf(ctx, req.(*AddVrfRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DeleteVrf_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteVrfRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DeleteVrf(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DeleteVrf", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DeleteVrf(ctx, req.(*DeleteVrfRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetVrf_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetVrfRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).GetVrf(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/GetVrf", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).GetVrf(ctx, req.(*GetVrfRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetDefinedSet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetDefinedSetRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).GetDefinedSet(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/GetDefinedSet", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).GetDefinedSet(ctx, req.(*GetDefinedSetRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_AddDefinedSet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddDefinedSetRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).AddDefinedSet(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/AddDefinedSet", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).AddDefinedSet(ctx, req.(*AddDefinedSetRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DeleteDefinedSet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteDefinedSetRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DeleteDefinedSet(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DeleteDefinedSet", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DeleteDefinedSet(ctx, req.(*DeleteDefinedSetRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_ReplaceDefinedSet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ReplaceDefinedSetRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).ReplaceDefinedSet(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/ReplaceDefinedSet", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).ReplaceDefinedSet(ctx, req.(*ReplaceDefinedSetRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetStatement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetStatementRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).GetStatement(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/GetStatement", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).GetStatement(ctx, req.(*GetStatementRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_AddStatement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddStatementRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).AddStatement(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/AddStatement", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).AddStatement(ctx, req.(*AddStatementRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DeleteStatement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeleteStatementRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DeleteStatement(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DeleteStatement", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DeleteStatement(ctx, req.(*DeleteStatementRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_ReplaceStatement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ReplaceStatementRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).ReplaceStatement(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/ReplaceStatement", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).ReplaceStatement(ctx, req.(*ReplaceStatementRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetPolicyRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).GetPolicy(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/GetPolicy", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).GetPolicy(ctx, req.(*GetPolicyRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_AddPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddPolicyRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).AddPolicy(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/AddPolicy", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).AddPolicy(ctx, req.(*AddPolicyRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DeletePolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeletePolicyRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DeletePolicy(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DeletePolicy", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DeletePolicy(ctx, req.(*DeletePolicyRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_ReplacePolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ReplacePolicyRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).ReplacePolicy(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/ReplacePolicy", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).ReplacePolicy(ctx, req.(*ReplacePolicyRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetPolicyAssignment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetPolicyAssignmentRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).GetPolicyAssignment(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/GetPolicyAssignment", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).GetPolicyAssignment(ctx, req.(*GetPolicyAssignmentRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_AddPolicyAssignment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AddPolicyAssignmentRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).AddPolicyAssignment(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/AddPolicyAssignment", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).AddPolicyAssignment(ctx, req.(*AddPolicyAssignmentRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_DeletePolicyAssignment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeletePolicyAssignmentRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).DeletePolicyAssignment(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/DeletePolicyAssignment", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).DeletePolicyAssignment(ctx, req.(*DeletePolicyAssignmentRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_ReplacePolicyAssignment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ReplacePolicyAssignmentRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).ReplacePolicyAssignment(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/ReplacePolicyAssignment", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).ReplacePolicyAssignment(ctx, req.(*ReplacePolicyAssignmentRequest)) } return interceptor(ctx, in, info, handler) } func _GobgpApi_GetRibInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetRibInfoRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(GobgpApiServer).GetRibInfo(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/gobgpapi.GobgpApi/GetRibInfo", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(GobgpApiServer).GetRibInfo(ctx, req.(*GetRibInfoRequest)) } return interceptor(ctx, in, info, handler) } var _GobgpApi_serviceDesc = grpc.ServiceDesc{ ServiceName: "gobgpapi.GobgpApi", HandlerType: (*GobgpApiServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "StartServer", Handler: _GobgpApi_StartServer_Handler, }, { MethodName: "StopServer", Handler: _GobgpApi_StopServer_Handler, }, { MethodName: "GetServer", Handler: _GobgpApi_GetServer_Handler, }, { MethodName: "AddNeighbor", Handler: _GobgpApi_AddNeighbor_Handler, }, { MethodName: "DeleteNeighbor", Handler: _GobgpApi_DeleteNeighbor_Handler, }, { MethodName: "GetNeighbor", Handler: _GobgpApi_GetNeighbor_Handler, }, { MethodName: "ResetNeighbor", Handler: _GobgpApi_ResetNeighbor_Handler, }, { MethodName: "SoftResetNeighbor", Handler: _GobgpApi_SoftResetNeighbor_Handler, }, { MethodName: "ShutdownNeighbor", Handler: _GobgpApi_ShutdownNeighbor_Handler, }, { MethodName: "EnableNeighbor", Handler: _GobgpApi_EnableNeighbor_Handler, }, { MethodName: "DisableNeighbor", Handler: _GobgpApi_DisableNeighbor_Handler, }, { MethodName: "GetRib", Handler: _GobgpApi_GetRib_Handler, }, { MethodName: "ValidateRib", Handler: _GobgpApi_ValidateRib_Handler, }, { MethodName: "AddPath", Handler: _GobgpApi_AddPath_Handler, }, { MethodName: "DeletePath", Handler: _GobgpApi_DeletePath_Handler, }, { MethodName: "EnableMrt", Handler: _GobgpApi_EnableMrt_Handler, }, { MethodName: "DisableMrt", Handler: _GobgpApi_DisableMrt_Handler, }, { MethodName: "AddBmp", Handler: _GobgpApi_AddBmp_Handler, }, { MethodName: "DeleteBmp", Handler: _GobgpApi_DeleteBmp_Handler, }, { MethodName: "GetRpki", Handler: _GobgpApi_GetRpki_Handler, }, { MethodName: "AddRpki", Handler: _GobgpApi_AddRpki_Handler, }, { MethodName: "DeleteRpki", Handler: _GobgpApi_DeleteRpki_Handler, }, { MethodName: "EnableRpki", Handler: _GobgpApi_EnableRpki_Handler, }, { MethodName: "DisableRpki", Handler: _GobgpApi_DisableRpki_Handler, }, { MethodName: "ResetRpki", Handler: _GobgpApi_ResetRpki_Handler, }, { MethodName: "SoftResetRpki", Handler: _GobgpApi_SoftResetRpki_Handler, }, { MethodName: "GetRoa", Handler: _GobgpApi_GetRoa_Handler, }, { MethodName: "EnableZebra", Handler: _GobgpApi_EnableZebra_Handler, }, { MethodName: "AddVrf", Handler: _GobgpApi_AddVrf_Handler, }, { MethodName: "DeleteVrf", Handler: _GobgpApi_DeleteVrf_Handler, }, { MethodName: "GetVrf", Handler: _GobgpApi_GetVrf_Handler, }, { MethodName: "GetDefinedSet", Handler: _GobgpApi_GetDefinedSet_Handler, }, { MethodName: "AddDefinedSet", Handler: _GobgpApi_AddDefinedSet_Handler, }, { MethodName: "DeleteDefinedSet", Handler: _GobgpApi_DeleteDefinedSet_Handler, }, { MethodName: "ReplaceDefinedSet", Handler: _GobgpApi_ReplaceDefinedSet_Handler, }, { MethodName: "GetStatement", Handler: _GobgpApi_GetStatement_Handler, }, { MethodName: "AddStatement", Handler: _GobgpApi_AddStatement_Handler, }, { MethodName: "DeleteStatement", Handler: _GobgpApi_DeleteStatement_Handler, }, { MethodName: "ReplaceStatement", Handler: _GobgpApi_ReplaceStatement_Handler, }, { MethodName: "GetPolicy", Handler: _GobgpApi_GetPolicy_Handler, }, { MethodName: "AddPolicy", Handler: _GobgpApi_AddPolicy_Handler, }, { MethodName: "DeletePolicy", Handler: _GobgpApi_DeletePolicy_Handler, }, { MethodName: "ReplacePolicy", Handler: _GobgpApi_ReplacePolicy_Handler, }, { MethodName: "GetPolicyAssignment", Handler: _GobgpApi_GetPolicyAssignment_Handler, }, { MethodName: "AddPolicyAssignment", Handler: _GobgpApi_AddPolicyAssignment_Handler, }, { MethodName: "DeletePolicyAssignment", Handler: _GobgpApi_DeletePolicyAssignment_Handler, }, { MethodName: "ReplacePolicyAssignment", Handler: _GobgpApi_ReplacePolicyAssignment_Handler, }, { MethodName: "GetRibInfo", Handler: _GobgpApi_GetRibInfo_Handler, }, }, Streams: []grpc.StreamDesc{ { StreamName: "GetPath", Handler: _GobgpApi_GetPath_Handler, ServerStreams: true, }, { StreamName: "MonitorRib", Handler: _GobgpApi_MonitorRib_Handler, ServerStreams: true, }, { StreamName: "MonitorPeerState", Handler: _GobgpApi_MonitorPeerState_Handler, ServerStreams: true, }, { StreamName: "InjectMrt", Handler: _GobgpApi_InjectMrt_Handler, ClientStreams: true, }, }, Metadata: "gobgp.proto", } func init() { proto.RegisterFile("gobgp.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ // 7420 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x7c, 0x5d, 0x6f, 0x23, 0x47, 0x76, 0xa8, 0xf8, 0x21, 0x8a, 0x3c, 0x24, 0xc5, 0x56, 0x49, 0x1a, 0x71, 0x24, 0xcf, 0x57, 0xaf, 0xc7, 0x33, 0x96, 0xed, 0xb1, 0x67, 0x6c, 0xcb, 0x5e, 0xcf, 0xda, 0x6b, 0x8e, 0xc4, 0xd1, 0x70, 0x4d, 0x89, 0x74, 0x8b, 0x33, 0x1e, 0xef, 0xdd, 0xbd, 0x7d, 0x5b, 0xec, 0x22, 0xd5, 0xd7, 0xcd, 0xee, 0x76, 0x77, 0x53, 0xd6, 0xe0, 0x02, 0xf7, 0xce, 0xec, 0xdd, 0xdd, 0x7b, 0x81, 0x60, 0x5f, 0xf2, 0x9a, 0x0d, 0xf2, 0x90, 0xb7, 0x05, 0xf2, 0x10, 0x20, 0x40, 0x80, 0x3c, 0x25, 0x40, 0x76, 0x11, 0x60, 0x81, 0xbc, 0x24, 0xcf, 0xc9, 0x0f, 0xc8, 0x43, 0x12, 0x20, 0x0f, 0x79, 0xd8, 0x87, 0xa0, 0x3e, 0xba, 0xbb, 0xfa, 0x83, 0x92, 0xc6, 0x3b, 0x4e, 0x10, 0x20, 0x4f, 0x64, 0x9d, 0x73, 0xea, 0xd4, 0xa9, 0xaa, 0x53, 0xa7, 0x4e, 0x9d, 0xaa, 0x3e, 0x50, 0x1d, 0xdb, 0x87, 0x63, 0xe7, 0x96, 0xe3, 0xda, 0xbe, 0x8d, 0xca, 0xb4, 0xa0, 0x39, 0x86, 0xfc, 0x7d, 0x40, 0xbb, 0xd8, 0xdf, 0xc7, 0xc6, 0xf8, 0xe8, 0xd0, 0x76, 0x15, 0xfc, 0xe5, 0x14, 0x7b, 0x3e, 0xda, 0x04, 0x09, 0x5b, 0xda, 0xa1, 0x89, 0x5b, 0xfa, 0x31, 0x76, 0x7d, 0xc3, 0xc3, 0x7a, 0x33, 0x77, 0x35, 0x77, 0xb3, 0xac, 0xa4, 0xe0, 0xa8, 0x09, 0x0b, 0x9a, 0xae, 0xbb, 0xd8, 0xf3, 0x9a, 0xf9, 0xab, 0xb9, 0x9b, 0x15, 0x25, 0x28, 0xca, 0x77, 0x61, 0x39, 0xc6, 0xdb, 0x73, 0x6c, 0xcb, 0xc3, 0xe8, 0x65, 0x98, 0x77, 0x30, 0x76, 0xbd, 0x66, 0xee, 0x6a, 0xe1, 0x66, 0xf5, 0xce, 0xe2, 0xad, 0x40, 0x98, 0x5b, 0x7d, 0x8c, 0x5d, 0x85, 0x21, 0xe5, 0x67, 0x39, 0xa8, 0xb4, 0xdc, 0xf1, 0x74, 0x82, 0x2d, 0xdf, 0x43, 0xb7, 0xa0, 0xec, 0x62, 0xcf, 0x9e, 0xba, 0x43, 0x4c, 0x05, 0x59, 0xbc, 0x83, 0xa2, 0x6a, 0x0a, 0xc7, 0x28, 0x21, 0x0d, 0xba, 0x00, 0xa5, 0x91, 0x36, 0x31, 0xcc, 0x27, 0x54, 0xa6, 0xba, 0xc2, 0x4b, 0x08, 0x41, 0xd1, 0xd2, 0x26, 0xb8, 0x59, 0xa0, 0x92, 0xd2, 0xff, 0xa4, 0x03, 0xc3, 0xa9, 0xeb, 0x62, 0xcb, 0x6f, 0x16, 0x69, 0x1f, 0x83, 0xa2, 0xfc, 0xbf, 0x60, 0xb1, 0xa5, 0xeb, 0x7d, 0xcd, 0x3f, 0x0a, 0x06, 0xe6, 0x79, 0xe5, 0x58, 0x85, 0xd2, 0xb1, 0x3b, 0x52, 0x0d, 0x9d, 0x8f, 0xcd, 0xfc, 0xb1, 0x3b, 0xea, 0xe8, 0x48, 0x86, 0xa2, 0xa3, 0xf9, 0x47, 0x54, 0x8c, 0xf8, 0x08, 0x90, 0xb6, 0x28, 0x4e, 0xbe, 0x0e, 0x8d, 0xb0, 0x71, 0x3e, 0x72, 0x08, 0x8a, 0xd3, 0xa9, 0xc1, 0xa6, 0xa2, 0xa6, 0xd0, 0xff, 0xf2, 0x2f, 0x72, 0xb0, 0xb4, 0x83, 0x4d, 0xec, 0xe3, 0x6f, 0x40, 0xce, 0x68, 0x18, 0x0b, 0xb1, 0x61, 0x0c, 0xe4, 0x2f, 0xce, 0x96, 0x3f, 0x14, 0x76, 0x5e, 0x10, 0x76, 0x05, 0x90, 0x28, 0x2b, 0xeb, 0x96, 0xfc, 0x3e, 0xa0, 0x96, 0xae, 0x27, 0x75, 0x90, 0xb4, 0x81, 0xb1, 0x4b, 0xc5, 0x4f, 0x6b, 0x09, 0xc5, 0xc9, 0xab, 0xb0, 0x1c, 0xab, 0xc9, 0x19, 0xde, 0x85, 0x55, 0xd6, 0xcc, 0xd7, 0xe1, 0xd9, 0x84, 0x0b, 0xc9, 0xca, 0x9c, 0xed, 0x23, 0x58, 0x51, 0xb0, 0x97, 0x5e, 0x2d, 0xc2, 0x0a, 0xc8, 0xc5, 0x56, 0x00, 0x7a, 0x19, 0xea, 0x43, 0x7b, 0x32, 0x99, 0x5a, 0xc6, 0x50, 0xf3, 0x0d, 0xdb, 0xe2, 0xa3, 0x1b, 0x07, 0xca, 0x6b, 0xb0, 0x9a, 0xe0, 0xcb, 0x1b, 0xfc, 0xb3, 0x1c, 0x34, 0x0f, 0xec, 0x91, 0xff, 0x9c, 0xad, 0x1e, 0x40, 0x45, 0x37, 0x5c, 0x3c, 0x0c, 0x5b, 0x5c, 0xbc, 0xf3, 0x6e, 0xd4, 0xd5, 0x59, 0x0c, 0x23, 0xc4, 0x4e, 0x50, 0x59, 0x89, 0xf8, 0xc8, 0x6f, 0x02, 0x4a, 0x13, 0xa0, 0x12, 0xe4, 0x3b, 0xfb, 0xd2, 0x1c, 0x5a, 0x80, 0x42, 0xef, 0xe1, 0x40, 0xca, 0xa1, 0x32, 0x14, 0xef, 0xf5, 0x06, 0x0f, 0xa4, 0xbc, 0xbc, 0x01, 0x17, 0x33, 0x9a, 0xe2, 0x3d, 0xfb, 0x1c, 0xd6, 0x0e, 0x8e, 0xa6, 0xbe, 0x6e, 0x7f, 0x65, 0xbd, 0xe8, 0xd1, 0x5c, 0x87, 0x66, 0x9a, 0x35, 0x6f, 0xf6, 0x36, 0xac, 0xb6, 0xa9, 0xfd, 0x3a, 0x77, 0xa3, 0x44, 0x1d, 0x92, 0x55, 0x38, 0xb3, 0xc7, 0x70, 0x61, 0xc7, 0xf0, 0x9e, 0x8b, 0xdb, 0x39, 0xbb, 0x70, 0x11, 0xd6, 0x52, 0x9c, 0x79, 0xa3, 0x63, 0x90, 0x98, 0x38, 0x7b, 0xae, 0x1f, 0x34, 0xb7, 0x01, 0x15, 0x7d, 0x3a, 0x71, 0x54, 0xff, 0x89, 0xc3, 0x56, 0xfb, 0xbc, 0x52, 0x26, 0x80, 0xc1, 0x13, 0x07, 0xa3, 0x75, 0x28, 0x8f, 0x0c, 0x13, 0x53, 0xab, 0xc7, 0x1a, 0x0b, 0xcb, 0x04, 0x67, 0x58, 0x3e, 0x76, 0x8f, 0x35, 0x93, 0x2e, 0xf0, 0xa2, 0x12, 0x96, 0xe5, 0x65, 0x58, 0x12, 0x1a, 0xe2, 0xad, 0x2f, 0xc3, 0x12, 0x17, 0x2c, 0x6a, 0x9e, 0x2e, 0x6a, 0x01, 0xc8, 0x49, 0xff, 0x0f, 0x48, 0x1d, 0xeb, 0x7f, 0xe2, 0xa1, 0x2f, 0x08, 0xfa, 0x82, 0xac, 0x12, 0xd9, 0x40, 0x34, 0xff, 0xc8, 0x6b, 0x16, 0x52, 0x1b, 0x08, 0x31, 0x2b, 0x0c, 0x49, 0x64, 0x15, 0x04, 0xe0, 0x52, 0xfd, 0x79, 0x0e, 0xea, 0x2d, 0x5d, 0xbf, 0x37, 0x71, 0xce, 0x9e, 0x2b, 0x04, 0x45, 0xc7, 0x76, 0x7d, 0xbe, 0x83, 0xd0, 0xff, 0xe8, 0x3b, 0x50, 0xa4, 0xa3, 0x5c, 0xa0, 0xd2, 0xdf, 0x8c, 0x5a, 0x8e, 0x31, 0xbd, 0xb5, 0x67, 0x5b, 0x86, 0x6f, 0xbb, 0x86, 0x35, 0xee, 0xdb, 0xa6, 0x31, 0x7c, 0xa2, 0xd0, 0x5a, 0xf2, 0x36, 0x48, 0x49, 0x0c, 0x59, 0x39, 0x7d, 0xa5, 0x2d, 0xcd, 0x91, 0x95, 0xd3, 0xef, 0x1d, 0xc4, 0xd6, 0x10, 0xaa, 0xc0, 0x7c, 0xb7, 0xb7, 0xdd, 0xea, 0x4a, 0x05, 0x42, 0xd7, 0xea, 0x76, 0xa5, 0xa2, 0x2c, 0xd1, 0x4d, 0x89, 0x36, 0xc6, 0x3b, 0xf5, 0x31, 0x48, 0xcc, 0x62, 0x7d, 0xdd, 0x6e, 0xd1, 0x79, 0x8d, 0x38, 0x70, 0xb6, 0x03, 0x58, 0xe2, 0xd2, 0x2a, 0xc6, 0x61, 0xc0, 0xf7, 0x3a, 0xcc, 0xfb, 0x64, 0xaa, 0xb9, 0x09, 0x6d, 0x44, 0x23, 0x30, 0x20, 0x60, 0x85, 0x61, 0xc5, 0x3d, 0x35, 0x1f, 0xdf, 0x53, 0xdb, 0x50, 0x56, 0xfa, 0x9f, 0x74, 0xb6, 0x6d, 0x6b, 0x74, 0x8a, 0x90, 0x57, 0xa0, 0xea, 0xe2, 0x89, 0xed, 0x63, 0x35, 0x94, 0xb5, 0xa2, 0x00, 0x03, 0xf5, 0x89, 0xc4, 0x3f, 0x2f, 0x42, 0x85, 0xf0, 0x39, 0xf0, 0x35, 0x9f, 0x6e, 0xf7, 0x53, 0xc7, 0x37, 0x26, 0x4c, 0xac, 0x82, 0xc2, 0x4b, 0x44, 0xc1, 0x89, 0x1d, 0xa0, 0x98, 0x3c, 0xc5, 0x84, 0x65, 0xb4, 0x08, 0xf9, 0xa9, 0x43, 0x27, 0xb2, 0xac, 0xe4, 0xa7, 0x0e, 0x6b, 0x72, 0x68, 0xbb, 0xba, 0x6a, 0x38, 0xc7, 0xef, 0xd0, 0xad, 0xad, 0x4e, 0x9a, 0x24, 0xa0, 0x8e, 0x73, 0xfc, 0x4e, 0x9c, 0x60, 0x8b, 0xee, 0x6b, 0x22, 0xc1, 0x16, 0x21, 0x70, 0x5c, 0x3c, 0x32, 0x4e, 0x18, 0x87, 0x12, 0x23, 0x60, 0xa0, 0x80, 0x43, 0x44, 0xb0, 0xd5, 0x5c, 0x48, 0x10, 0x6c, 0x91, 0x7e, 0x78, 0xd8, 0x35, 0x34, 0xb3, 0x59, 0x66, 0xfb, 0x2d, 0x2b, 0xa1, 0x6f, 0x41, 0xdd, 0xc5, 0x43, 0x6c, 0x1c, 0x63, 0x2e, 0x5d, 0x85, 0x76, 0xa6, 0x16, 0x00, 0x29, 0xf7, 0x04, 0xd1, 0x56, 0x13, 0x52, 0x44, 0x5b, 0x84, 0x88, 0xf1, 0x54, 0x2d, 0xdb, 0x37, 0x46, 0x4f, 0x9a, 0x55, 0x46, 0xc4, 0x80, 0xfb, 0x14, 0x46, 0xe4, 0x1c, 0x6a, 0xc3, 0x23, 0xac, 0xba, 0xc4, 0x78, 0x37, 0x6b, 0x94, 0x04, 0x28, 0x88, 0x9a, 0x73, 0x74, 0x1d, 0x16, 0x43, 0x02, 0xaa, 0x2c, 0xcd, 0x3a, 0xa5, 0xa9, 0x07, 0x34, 0xcc, 0x5f, 0xb9, 0x0c, 0x55, 0x6c, 0xe9, 0xaa, 0x3d, 0x52, 0x75, 0xcd, 0xd7, 0x9a, 0x8b, 0x94, 0xa6, 0x82, 0x2d, 0xbd, 0x37, 0xda, 0xd1, 0x7c, 0x0d, 0xad, 0xc0, 0x3c, 0x76, 0x5d, 0xdb, 0x6d, 0x36, 0x28, 0x86, 0x15, 0xd0, 0x35, 0xe0, 0xd2, 0xa8, 0x5f, 0x4e, 0xb1, 0xfb, 0xa4, 0x29, 0x51, 0x64, 0x95, 0xc1, 0x3e, 0x25, 0x20, 0x36, 0x15, 0x1e, 0xf6, 0x39, 0xc5, 0x12, 0x13, 0x90, 0x82, 0x28, 0x81, 0xfc, 0x39, 0x14, 0x15, 0xe7, 0x0b, 0x03, 0xbd, 0x02, 0xc5, 0xa1, 0x6d, 0x8d, 0xb8, 0xb6, 0x8a, 0xd6, 0x86, 0xeb, 0xa0, 0x42, 0xf1, 0xe8, 0x55, 0x98, 0xf7, 0x88, 0x26, 0x51, 0x2d, 0xa9, 0xde, 0x59, 0x8e, 0x13, 0x52, 0x25, 0x53, 0x18, 0x85, 0x7c, 0x13, 0x16, 0x77, 0xb1, 0x4f, 0xb8, 0x07, 0x6b, 0x22, 0xf2, 0x92, 0x72, 0xa2, 0x97, 0x24, 0xdf, 0x85, 0x46, 0x48, 0xc9, 0x47, 0xe4, 0x26, 0x2c, 0x78, 0xd8, 0x3d, 0xce, 0xf4, 0x7e, 0x29, 0x61, 0x80, 0x96, 0xbf, 0x4f, 0x97, 0xb9, 0xd8, 0xcc, 0xf3, 0x59, 0xaa, 0x75, 0x28, 0x9b, 0xc6, 0x08, 0x53, 0xd5, 0x2f, 0x30, 0xd5, 0x0f, 0xca, 0xf2, 0x12, 0x75, 0x2d, 0x45, 0xc1, 0xe4, 0x56, 0x60, 0x01, 0xbe, 0x76, 0x8b, 0x91, 0x73, 0x17, 0x63, 0xfc, 0x46, 0xb0, 0x8f, 0x9c, 0x8b, 0x31, 0x61, 0x22, 0x92, 0x73, 0x26, 0xb7, 0xc2, 0x2d, 0xe6, 0x7c, 0x5c, 0x56, 0x61, 0x39, 0x46, 0xcf, 0xd9, 0xbc, 0x0e, 0x12, 0xd5, 0xdf, 0xf3, 0x31, 0x59, 0x86, 0x25, 0x81, 0x9a, 0xb3, 0x78, 0x0b, 0x56, 0x42, 0xaf, 0xe6, 0x7c, 0x6c, 0xd6, 0x60, 0x35, 0x51, 0x83, 0xb3, 0xfa, 0x75, 0x2e, 0xe8, 0xeb, 0xf7, 0xf1, 0xa1, 0xab, 0x05, 0x9c, 0x24, 0x28, 0x4c, 0x5d, 0x93, 0x73, 0x21, 0x7f, 0xa9, 0xb6, 0xdb, 0x53, 0x1f, 0xd3, 0x0d, 0x9e, 0x9c, 0xb2, 0x0a, 0xd4, 0x18, 0x12, 0x10, 0xd9, 0xe2, 0x3d, 0xd2, 0x38, 0xd1, 0x19, 0xe2, 0x4f, 0x30, 0x3f, 0x3d, 0x28, 0xa2, 0x77, 0xe0, 0x82, 0x85, 0x4f, 0xfc, 0x23, 0xdb, 0x51, 0x7d, 0xd7, 0x18, 0x8f, 0xb1, 0xab, 0xb2, 0x03, 0x1c, 0x3f, 0xea, 0xac, 0x70, 0xec, 0x80, 0x21, 0x99, 0x38, 0xe8, 0x0e, 0xac, 0x26, 0x6b, 0xe9, 0xd8, 0xd4, 0x9e, 0x70, 0x9b, 0xb7, 0x1c, 0xaf, 0xb4, 0x43, 0x50, 0x64, 0xc8, 0x63, 0x9d, 0xe1, 0x9d, 0x6c, 0x40, 0x7d, 0x17, 0xfb, 0x8f, 0xdc, 0x51, 0xe0, 0x2d, 0xbc, 0x4d, 0x97, 0x0f, 0x05, 0xf0, 0x35, 0x71, 0x0d, 0x8a, 0xc7, 0xee, 0x28, 0x58, 0x10, 0xf5, 0x68, 0x41, 0x10, 0x22, 0x8a, 0x92, 0xdf, 0xa2, 0xbb, 0x76, 0xc4, 0x05, 0x5d, 0x81, 0xc2, 0xb1, 0x1b, 0x2c, 0xeb, 0x44, 0x15, 0x82, 0xe1, 0xbb, 0xa4, 0xd0, 0x8c, 0xfc, 0x76, 0xb0, 0x4b, 0x3e, 0x0f, 0x9b, 0x70, 0x63, 0x14, 0x39, 0x3d, 0x84, 0x95, 0x5d, 0xec, 0xef, 0xe0, 0x91, 0x61, 0x61, 0xfd, 0x00, 0x87, 0xee, 0xcd, 0xab, 0xdc, 0x39, 0x60, 0xae, 0xcd, 0x6a, 0xc4, 0x8e, 0x93, 0x92, 0xc9, 0x62, 0x9e, 0x40, 0x78, 0x0e, 0xcd, 0x47, 0xe7, 0x50, 0xb9, 0x05, 0xab, 0x09, 0xb6, 0xa1, 0xd1, 0x28, 0x7a, 0xd8, 0x0f, 0x06, 0x68, 0x25, 0xc5, 0x97, 0xd0, 0x52, 0x0a, 0xf9, 0x23, 0x58, 0x69, 0xe9, 0x7a, 0x5a, 0xb2, 0x57, 0xa0, 0x40, 0x0c, 0x39, 0xeb, 0x67, 0x36, 0x03, 0x42, 0x40, 0x74, 0x35, 0x51, 0x9f, 0x77, 0xf9, 0x00, 0xd6, 0xd8, 0x38, 0x7c, 0x6d, 0xde, 0x44, 0xaf, 0x35, 0xd3, 0xe4, 0xee, 0x00, 0xf9, 0x4b, 0x3c, 0xf5, 0x34, 0x53, 0xde, 0xe0, 0x3d, 0x68, 0x2a, 0xd8, 0x31, 0xb5, 0xe1, 0xd7, 0x6f, 0x91, 0x9c, 0x40, 0x32, 0x78, 0xf0, 0x06, 0x56, 0x69, 0x70, 0x82, 0x5a, 0xf6, 0x09, 0xb6, 0x42, 0x67, 0xf6, 0x13, 0x3a, 0xb7, 0x02, 0x98, 0xcf, 0xc1, 0xdb, 0x00, 0x5e, 0x00, 0x0c, 0x66, 0x42, 0xd8, 0x25, 0xa2, 0x0a, 0x02, 0x99, 0xfc, 0x80, 0x1e, 0x4f, 0x93, 0x6d, 0xa0, 0xdb, 0x50, 0x09, 0x89, 0x78, 0x2f, 0x32, 0x59, 0x45, 0x54, 0xf2, 0x05, 0x3a, 0xb1, 0x29, 0xb1, 0xe4, 0x1f, 0x06, 0x87, 0xd5, 0x17, 0xd0, 0x48, 0xc6, 0x0c, 0x5d, 0x0c, 0xa6, 0x3d, 0xdd, 0x72, 0x17, 0xd6, 0xf8, 0xe0, 0xbe, 0x88, 0xfe, 0xad, 0x87, 0xd3, 0x9d, 0x6e, 0x09, 0x81, 0xb4, 0x8b, 0x7d, 0xee, 0x48, 0xf3, 0x69, 0x6a, 0xc1, 0x92, 0x00, 0xe3, 0x73, 0xf4, 0x3a, 0x94, 0x1d, 0x02, 0x31, 0x70, 0x30, 0x43, 0x92, 0x70, 0x34, 0x60, 0xb4, 0x21, 0x85, 0x7c, 0x02, 0x52, 0x4b, 0xd7, 0x63, 0x6c, 0xd1, 0x4d, 0x28, 0x51, 0xfc, 0x13, 0x2e, 0x76, 0xba, 0x3e, 0xc7, 0xa3, 0x0f, 0xe0, 0xa2, 0x8b, 0x47, 0xc4, 0x9c, 0x9e, 0x18, 0x9e, 0x6f, 0x58, 0x63, 0x55, 0x50, 0x0f, 0x36, 0x82, 0x6b, 0x94, 0xa0, 0xcd, 0xf1, 0x07, 0x91, 0x5a, 0x2c, 0xc3, 0x92, 0xd0, 0x32, 0xef, 0xe5, 0x8f, 0x72, 0xb0, 0xcc, 0x63, 0x23, 0x5f, 0x53, 0xa4, 0x37, 0x61, 0xd9, 0x21, 0x2e, 0x90, 0x7b, 0x8c, 0xd3, 0xc2, 0xa0, 0x00, 0x15, 0xc9, 0x11, 0xcc, 0x77, 0x21, 0x9a, 0xef, 0x0b, 0xb0, 0x12, 0x97, 0x81, 0x0b, 0xf7, 0x47, 0x39, 0x58, 0xe1, 0xf3, 0xf3, 0x1f, 0x30, 0x60, 0xb3, 0x7a, 0x56, 0x98, 0xd5, 0x33, 0x16, 0x51, 0x89, 0x89, 0x1b, 0x9e, 0xd9, 0xd7, 0x43, 0xbd, 0x69, 0x79, 0x9e, 0x31, 0xb6, 0x44, 0xc5, 0xfd, 0x00, 0x40, 0x0b, 0x81, 0xbc, 0x47, 0xeb, 0xc9, 0x1e, 0x09, 0xd5, 0x04, 0x6a, 0xf9, 0x73, 0xd8, 0xc8, 0xe4, 0xcc, 0x75, 0xf3, 0xb7, 0x61, 0xfd, 0x18, 0xd6, 0x43, 0x7d, 0x79, 0xb1, 0x42, 0x5f, 0x82, 0x8d, 0x4c, 0xce, 0x7c, 0xb4, 0x26, 0x70, 0x49, 0x54, 0x87, 0x17, 0xda, 0x76, 0x86, 0xb5, 0xb9, 0x0a, 0x97, 0x67, 0x35, 0xc7, 0x05, 0xfa, 0x01, 0x5c, 0x8e, 0xcd, 0xeb, 0x8b, 0x1d, 0x8d, 0x6b, 0x70, 0x65, 0x26, 0xf7, 0x98, 0x2d, 0x3a, 0xa0, 0x3e, 0x7a, 0x60, 0x8b, 0x3e, 0xa4, 0xb6, 0x28, 0x80, 0x85, 0x7b, 0x76, 0x69, 0x6c, 0xda, 0x87, 0x9a, 0x99, 0x5e, 0x18, 0xbb, 0x14, 0xae, 0x70, 0xbc, 0xfc, 0x11, 0xa0, 0x03, 0x5f, 0x73, 0xe3, 0x4c, 0x9f, 0xa3, 0xfe, 0x2a, 0x2c, 0xc7, 0xea, 0x47, 0xa1, 0x9a, 0x03, 0xdf, 0x76, 0xe2, 0xa2, 0xae, 0x90, 0xb6, 0x22, 0x20, 0x27, 0xfd, 0xc3, 0x02, 0x2c, 0x92, 0x63, 0xce, 0x23, 0xcd, 0x34, 0x74, 0x1a, 0x81, 0x42, 0xef, 0x04, 0xe7, 0x21, 0xe6, 0xcb, 0x5c, 0x8e, 0x9f, 0x87, 0x22, 0xc2, 0x5b, 0xe2, 0xd1, 0x08, 0xbd, 0x07, 0x25, 0x17, 0x6b, 0x5e, 0x18, 0x75, 0xbc, 0x32, 0xb3, 0x9a, 0x42, 0xc9, 0x14, 0x4e, 0x8e, 0x6e, 0xc0, 0xc2, 0x44, 0xf3, 0x87, 0x47, 0x58, 0xe7, 0x31, 0x1d, 0xc1, 0x17, 0x53, 0x6c, 0x4d, 0x09, 0xb0, 0xe8, 0x2d, 0xa8, 0x4d, 0x2d, 0x5e, 0x50, 0x35, 0xaf, 0x59, 0xcc, 0xa2, 0xae, 0x86, 0x24, 0x2d, 0x0f, 0xbd, 0x0f, 0x52, 0x54, 0xc3, 0xc4, 0xd6, 0xd8, 0x3f, 0x6a, 0xce, 0x67, 0xd5, 0x6a, 0x84, 0x64, 0x5d, 0x4a, 0x25, 0xf7, 0x61, 0x9e, 0x45, 0x17, 0x16, 0x01, 0x0e, 0x06, 0xad, 0x41, 0x5b, 0xdd, 0xef, 0xed, 0xb7, 0xa5, 0x39, 0xb4, 0x0c, 0x8d, 0xa0, 0x3c, 0x50, 0xef, 0xf7, 0x1e, 0xee, 0xef, 0x48, 0x39, 0xd4, 0x80, 0x2a, 0x03, 0x3e, 0x6a, 0x75, 0x3b, 0x3b, 0x52, 0x1e, 0x2d, 0x41, 0x9d, 0x01, 0x3a, 0xfb, 0x0c, 0x54, 0x90, 0xef, 0x42, 0x89, 0x75, 0x9c, 0x50, 0x2b, 0xed, 0xd6, 0x41, 0x6f, 0x10, 0xf0, 0xac, 0x43, 0x85, 0x02, 0xf6, 0xd5, 0xd6, 0x81, 0x94, 0x23, 0x95, 0x79, 0xb1, 0xdb, 0xde, 0xdf, 0xa5, 0xf1, 0xd4, 0x7f, 0x2a, 0x42, 0xb1, 0xcf, 0x03, 0xeb, 0x96, 0xe9, 0x1a, 0xc1, 0x2d, 0x00, 0xf9, 0x4f, 0x8e, 0xa0, 0x8e, 0xe6, 0xfb, 0x2e, 0x3b, 0x1d, 0xd4, 0x14, 0x5e, 0xa2, 0x8b, 0x6c, 0x1c, 0x1c, 0x00, 0xc9, 0x5f, 0x52, 0xfb, 0x10, 0x7b, 0xc1, 0x55, 0x07, 0xfd, 0x4f, 0x0e, 0x18, 0x86, 0xa7, 0x7e, 0x65, 0xf8, 0x47, 0xba, 0xab, 0x7d, 0x45, 0xbd, 0xfc, 0xb2, 0x02, 0x86, 0xf7, 0x19, 0x87, 0xa0, 0xcb, 0x00, 0xc7, 0xe1, 0xe4, 0xd1, 0xc0, 0xc6, 0xbc, 0x22, 0x40, 0x50, 0x1b, 0x96, 0xa2, 0x92, 0xaa, 0x63, 0x5f, 0x33, 0x4c, 0x1a, 0xde, 0xa8, 0xde, 0x69, 0xce, 0xd2, 0x01, 0x45, 0x8a, 0xaa, 0xec, 0xd0, 0x1a, 0xe8, 0x2d, 0x58, 0xb1, 0x6c, 0xd5, 0x98, 0x38, 0x64, 0x8b, 0xf6, 0x23, 0x81, 0xca, 0xcc, 0xd0, 0x5b, 0x76, 0x87, 0xa3, 0x42, 0xc1, 0xa2, 0xa3, 0x77, 0x25, 0x76, 0x41, 0x71, 0x09, 0x80, 0xc5, 0x10, 0x55, 0xcd, 0xb3, 0x68, 0x20, 0xa4, 0xae, 0x54, 0x18, 0xa4, 0xe5, 0x59, 0x68, 0x03, 0x78, 0x41, 0x35, 0x74, 0x1a, 0x01, 0xa9, 0x28, 0x65, 0x06, 0xe8, 0xe8, 0x3c, 0x62, 0xea, 0x63, 0x17, 0xeb, 0x34, 0xf4, 0x51, 0x56, 0xc2, 0x32, 0x5a, 0xa1, 0xeb, 0xc2, 0x64, 0xf1, 0x8e, 0xb2, 0xc2, 0x0a, 0xe8, 0x26, 0x48, 0x86, 0xa7, 0x8e, 0x5c, 0x7b, 0xa2, 0xe2, 0x13, 0x1f, 0xbb, 0x96, 0x66, 0xd2, 0x60, 0x47, 0x59, 0x59, 0x34, 0xbc, 0xfb, 0xae, 0x3d, 0x69, 0x73, 0x28, 0x19, 0x69, 0x8b, 0x87, 0x74, 0x55, 0xc3, 0xa1, 0x71, 0x8f, 0x8a, 0x02, 0x01, 0xa8, 0xe3, 0x84, 0xb7, 0x26, 0x52, 0x74, 0x6b, 0x82, 0x5e, 0x07, 0x64, 0x78, 0x6a, 0x70, 0x22, 0x33, 0x2c, 0x3a, 0x6e, 0x34, 0xe8, 0x51, 0x56, 0x24, 0xc3, 0xdb, 0x67, 0x88, 0x0e, 0x83, 0x93, 0xb9, 0x32, 0x74, 0x6c, 0xf9, 0xc6, 0xc8, 0xc0, 0x6e, 0x13, 0xb1, 0x18, 0x53, 0x04, 0x41, 0xaf, 0x82, 0x64, 0xda, 0x43, 0xcd, 0x54, 0x05, 0xaa, 0x65, 0x4a, 0xd5, 0xa0, 0xf0, 0x4e, 0x08, 0x96, 0xff, 0x20, 0x07, 0xd5, 0x1d, 0x4c, 0x76, 0x63, 0x36, 0xcd, 0x44, 0xcb, 0x68, 0xb0, 0x8a, 0x9f, 0x4e, 0x79, 0x29, 0x0a, 0xc8, 0xe6, 0x4f, 0x09, 0xc8, 0xa2, 0x1b, 0xd0, 0x30, 0x6d, 0x8b, 0x1c, 0x26, 0x59, 0x35, 0x1c, 0xec, 0xe0, 0x8b, 0x0c, 0xdc, 0xe7, 0x50, 0x22, 0xa1, 0x77, 0x64, 0xbb, 0xbe, 0x48, 0xc9, 0xd4, 0xb5, 0xc1, 0xe1, 0x01, 0xa9, 0xfc, 0xa7, 0x39, 0x98, 0xa7, 0x81, 0x47, 0xf4, 0x4a, 0xec, 0xf0, 0x95, 0x15, 0x57, 0x9e, 0x79, 0xf2, 0x9a, 0x79, 0xcd, 0xf5, 0x6d, 0xa8, 0xe9, 0x51, 0xf7, 0x03, 0x6b, 0x13, 0x3b, 0xd8, 0x85, 0x58, 0x25, 0x46, 0x4a, 0x43, 0x7d, 0xb6, 0xe7, 0xab, 0xdc, 0x3b, 0xe2, 0x4b, 0x8a, 0x80, 0xd8, 0xde, 0x22, 0x6f, 0xd1, 0x83, 0xf1, 0x73, 0x47, 0x56, 0xe5, 0xf7, 0x58, 0xf8, 0x89, 0xd4, 0xe3, 0x5b, 0xcd, 0x39, 0x2b, 0x4e, 0x60, 0x89, 0x96, 0xbb, 0xb6, 0xfd, 0xc5, 0xd4, 0x61, 0x23, 0x38, 0x73, 0x46, 0x3f, 0x86, 0xba, 0x49, 0xe9, 0x54, 0xdb, 0x11, 0xae, 0x91, 0x36, 0x12, 0xbc, 0x19, 0xaf, 0x9e, 0xc3, 0x06, 0xc0, 0x14, 0x4a, 0xf2, 0xef, 0xe7, 0xa8, 0xa0, 0xe2, 0xa5, 0xe4, 0x37, 0x31, 0x45, 0xef, 0x41, 0x59, 0xd0, 0x11, 0x32, 0x3d, 0xd9, 0x32, 0xb2, 0xfe, 0x2a, 0x21, 0xb1, 0x6c, 0x02, 0xe2, 0xb6, 0x08, 0x0b, 0x93, 0x70, 0x5e, 0x11, 0x67, 0xdd, 0x2f, 0x47, 0xe3, 0x59, 0x10, 0xc7, 0x93, 0x6c, 0xd2, 0xb1, 0xd6, 0xf8, 0xce, 0xfb, 0x0f, 0xc4, 0xa6, 0x63, 0xec, 0x52, 0x9b, 0x43, 0x38, 0x04, 0x47, 0x97, 0xba, 0x12, 0x96, 0xd1, 0xfb, 0x50, 0xd3, 0x1c, 0xc7, 0x7c, 0x12, 0xe8, 0x12, 0x0b, 0x51, 0x0a, 0x5a, 0xd8, 0x22, 0x58, 0xee, 0xe8, 0x56, 0xb5, 0xa8, 0x10, 0x46, 0x3f, 0x0b, 0xc9, 0xe8, 0x27, 0x69, 0x53, 0x88, 0x7e, 0xde, 0x85, 0x3a, 0x3e, 0x1c, 0x3b, 0xea, 0x64, 0x6a, 0xfa, 0xc6, 0x91, 0xed, 0xf0, 0x7b, 0xdd, 0x0b, 0x51, 0x85, 0xf6, 0xe1, 0xd8, 0xd9, 0xe3, 0x58, 0xa5, 0x86, 0x85, 0x12, 0x6a, 0x41, 0x83, 0x45, 0xa7, 0x5c, 0x3c, 0x32, 0xf1, 0xd0, 0xb7, 0x5d, 0xaa, 0xed, 0x71, 0xcb, 0x4f, 0x08, 0x94, 0x00, 0xaf, 0x2c, 0xba, 0xb1, 0x32, 0xba, 0x01, 0x45, 0xc3, 0x1a, 0xd9, 0x74, 0x63, 0x89, 0x9d, 0x15, 0x89, 0x9c, 0xcc, 0xc3, 0xa0, 0x04, 0xc4, 0x2b, 0xf2, 0x8d, 0x09, 0x76, 0x3d, 0xbe, 0xb9, 0x08, 0x5e, 0xd1, 0x80, 0xc2, 0x15, 0x8e, 0x27, 0x67, 0x50, 0xdf, 0xd5, 0x2c, 0x8f, 0x46, 0x29, 0xcb, 0x49, 0xbe, 0x83, 0x00, 0xa5, 0x44, 0x54, 0x64, 0x9c, 0x59, 0x47, 0x58, 0x08, 0x96, 0xee, 0x28, 0xb1, 0x71, 0xa6, 0xbd, 0xe0, 0xbe, 0x13, 0x8b, 0xc8, 0xb1, 0x02, 0xda, 0x01, 0x69, 0xec, 0x6a, 0x43, 0x3c, 0x9a, 0x9a, 0xaa, 0x8b, 0x3d, 0xe2, 0x8d, 0xd1, 0x3d, 0xa7, 0x7a, 0xe7, 0xa2, 0xe0, 0xb6, 0x71, 0x0a, 0x85, 0x11, 0x28, 0x8d, 0x71, 0x1c, 0x80, 0x6e, 0x41, 0x45, 0x1b, 0x19, 0xaa, 0xa7, 0x8d, 0x0c, 0xaf, 0x59, 0xa5, 0xba, 0xbc, 0x24, 0x4c, 0xf2, 0xc8, 0x38, 0xd0, 0x46, 0x86, 0x52, 0xd6, 0xd8, 0x1f, 0x72, 0x2a, 0xaa, 0x68, 0xba, 0xae, 0x32, 0xcb, 0x5b, 0x4b, 0x4e, 0x31, 0x7f, 0x3b, 0xe0, 0x29, 0x65, 0x8d, 0xff, 0x93, 0xff, 0x2a, 0x07, 0x55, 0x41, 0x57, 0xd0, 0x7b, 0x50, 0x31, 0x2c, 0x35, 0x76, 0x7e, 0x3b, 0xcd, 0x55, 0x2e, 0x1b, 0x16, 0xaf, 0xf8, 0x5d, 0xa8, 0xe3, 0x13, 0x32, 0x66, 0x71, 0x95, 0x3c, 0xad, 0x72, 0x8d, 0x55, 0x88, 0x18, 0x18, 0x13, 0x91, 0x41, 0xe1, 0x6c, 0x06, 0xac, 0x02, 0xb7, 0x9e, 0xff, 0x1b, 0xaa, 0x6c, 0x45, 0x77, 0x8d, 0x89, 0x31, 0x33, 0x02, 0x8f, 0xae, 0x41, 0x6d, 0xa2, 0x9d, 0x44, 0xbb, 0x08, 0x5b, 0xac, 0xd5, 0x89, 0x76, 0x12, 0x6e, 0x36, 0xef, 0xc0, 0x05, 0x8f, 0x5f, 0x17, 0xab, 0xfe, 0x91, 0x8b, 0xbd, 0x23, 0xdb, 0xd4, 0x55, 0x67, 0xe8, 0x73, 0x43, 0xb3, 0x12, 0x60, 0x07, 0x01, 0xb2, 0x3f, 0xf4, 0xe5, 0xdf, 0xcc, 0x43, 0x39, 0x58, 0x44, 0xe8, 0x5b, 0x50, 0xd7, 0xa6, 0xfe, 0x91, 0xea, 0x68, 0x9e, 0xf7, 0x95, 0xed, 0xea, 0xdc, 0x96, 0xd6, 0x08, 0xb0, 0xcf, 0x61, 0xe8, 0x2a, 0x54, 0x75, 0xec, 0x0d, 0x5d, 0xc3, 0x11, 0xee, 0x7d, 0x45, 0x10, 0xba, 0x08, 0x65, 0xb6, 0x31, 0x6b, 0x5e, 0x10, 0xc6, 0xa5, 0xe5, 0x16, 0xdd, 0x11, 0x43, 0xb7, 0x21, 0x08, 0x33, 0x17, 0x29, 0x87, 0x46, 0x00, 0x6f, 0xf1, 0xc8, 0xfc, 0x1a, 0x2c, 0x38, 0x18, 0xbb, 0x84, 0x09, 0x8b, 0xd6, 0x96, 0x48, 0xb1, 0xe5, 0x11, 0x97, 0x88, 0x22, 0xc6, 0xae, 0x3d, 0x75, 0xe8, 0x52, 0xab, 0x28, 0x15, 0x02, 0xd9, 0x25, 0x00, 0xe2, 0x12, 0x51, 0x34, 0x35, 0x7f, 0xec, 0x66, 0xaa, 0x4c, 0x00, 0xf4, 0x12, 0x79, 0x1f, 0x96, 0x5c, 0x3c, 0xb1, 0x8f, 0xb1, 0xea, 0xb8, 0xc6, 0xb1, 0xe6, 0x13, 0xb7, 0x8a, 0xae, 0xaa, 0xc5, 0x3b, 0x72, 0xda, 0xaa, 0xdc, 0x52, 0x28, 0x6d, 0x9f, 0x91, 0xb6, 0x3c, 0xa5, 0xe1, 0xc6, 0x01, 0xc4, 0xa3, 0x61, 0x4b, 0x6d, 0x64, 0x6a, 0x8e, 0xaa, 0x6b, 0x13, 0xc7, 0xb0, 0xc6, 0x74, 0xc1, 0x95, 0x15, 0x89, 0x62, 0xee, 0x9b, 0x9a, 0xb3, 0xc3, 0xe0, 0xe8, 0x3a, 0x2c, 0x7a, 0xd8, 0xd2, 0x55, 0x7e, 0x49, 0xee, 0x3f, 0xe1, 0x0e, 0x5d, 0x9d, 0x40, 0xb7, 0x03, 0x20, 0xe9, 0x20, 0xbf, 0x33, 0x1c, 0x6a, 0x0e, 0x5d, 0x40, 0x35, 0xa5, 0xc2, 0x20, 0xdb, 0x1a, 0xed, 0x20, 0x1b, 0x5e, 0x82, 0xad, 0x51, 0x2c, 0x1b, 0x6f, 0x82, 0x5c, 0x84, 0xbc, 0xa1, 0x53, 0xa7, 0xae, 0xa2, 0xe4, 0x0d, 0x1d, 0x7d, 0x00, 0x75, 0x7e, 0x53, 0x67, 0x12, 0x05, 0xf3, 0x9a, 0x8b, 0xc9, 0xad, 0x5f, 0x50, 0x3f, 0xa5, 0xe6, 0x44, 0x05, 0x8f, 0xa8, 0x03, 0x9f, 0x47, 0x3e, 0x53, 0xcc, 0xcb, 0xab, 0xb1, 0xc9, 0xe4, 0xd3, 0xf4, 0x06, 0xa0, 0xc8, 0x11, 0xb4, 0x7c, 0xec, 0x8e, 0xb4, 0x21, 0xa6, 0x5e, 0x5f, 0x45, 0x59, 0x0a, 0xfd, 0xc1, 0x00, 0x41, 0xfc, 0xf8, 0x63, 0x77, 0x44, 0x7d, 0xbe, 0x0a, 0x8d, 0x4c, 0xa3, 0xab, 0x50, 0xd3, 0x4c, 0xd3, 0xfe, 0x4a, 0x25, 0x8a, 0xab, 0x79, 0x81, 0xa3, 0x47, 0x61, 0xbd, 0xaf, 0xac, 0x96, 0x87, 0x5e, 0x81, 0x86, 0xcb, 0x8e, 0xb3, 0x6a, 0xa0, 0x11, 0xcb, 0x74, 0x84, 0xeb, 0x1c, 0xdc, 0xa7, 0x8a, 0x21, 0xdf, 0x86, 0x46, 0x62, 0xc2, 0x50, 0x19, 0x8a, 0xfc, 0x5c, 0xc2, 0xaf, 0x9d, 0x73, 0xa8, 0x0a, 0x0b, 0x4a, 0xbb, 0xdf, 0x6d, 0x6d, 0xb7, 0xa5, 0xbc, 0xfc, 0x09, 0xd4, 0xc4, 0x1d, 0x01, 0x35, 0x61, 0x81, 0x5d, 0x2b, 0x04, 0xcf, 0xc4, 0x82, 0x22, 0x5d, 0x81, 0x9c, 0x4a, 0xf5, 0x7d, 0x33, 0x5c, 0x81, 0x1c, 0x36, 0xf0, 0x4d, 0xf9, 0xff, 0xe6, 0x60, 0x31, 0xbe, 0x41, 0x90, 0x45, 0x99, 0xd8, 0x53, 0xd4, 0xa1, 0x69, 0x04, 0x27, 0xfa, 0xb2, 0xb2, 0x12, 0xdf, 0x40, 0xb6, 0x29, 0x0e, 0xdd, 0x85, 0xf5, 0x74, 0xad, 0xa9, 0x47, 0xfc, 0xc8, 0xf0, 0x09, 0xc1, 0x5a, 0xb2, 0x26, 0xc5, 0x77, 0x74, 0xf9, 0x8f, 0x4b, 0x50, 0x09, 0xb7, 0x9b, 0x7f, 0x87, 0x25, 0x7d, 0x0b, 0xca, 0x13, 0xec, 0x79, 0xda, 0x98, 0x3b, 0xb7, 0x31, 0xe3, 0xbd, 0xc7, 0x31, 0x4a, 0x48, 0x93, 0x69, 0x02, 0xe6, 0xcf, 0x34, 0x01, 0xa5, 0x53, 0x4c, 0xc0, 0xc2, 0xa9, 0x26, 0xa0, 0x9c, 0x30, 0x01, 0x37, 0xa1, 0xf4, 0xe5, 0x14, 0x4f, 0xb1, 0xc7, 0xf7, 0x45, 0x61, 0xeb, 0xfd, 0x94, 0xc2, 0x15, 0x8e, 0x47, 0x9b, 0x59, 0xc6, 0x82, 0xad, 0xd8, 0x73, 0x1a, 0x82, 0xea, 0xb9, 0x0d, 0x41, 0x2d, 0xcb, 0x10, 0xd0, 0x3b, 0x6e, 0xcf, 0x23, 0x47, 0x51, 0x16, 0xc4, 0xa8, 0x53, 0xaa, 0x1a, 0x07, 0xb2, 0x19, 0x7e, 0x17, 0x2e, 0x78, 0x53, 0x87, 0x6c, 0x29, 0x58, 0x27, 0x26, 0x41, 0x3b, 0x34, 0x4c, 0xc3, 0x27, 0xfe, 0xd7, 0x22, 0xbd, 0x5f, 0x5b, 0x0d, 0xb1, 0xdb, 0x02, 0x92, 0x8c, 0x11, 0xf1, 0x94, 0x18, 0x5f, 0xb6, 0xb0, 0xcb, 0x87, 0x63, 0x87, 0xf1, 0xfc, 0x2e, 0x54, 0x35, 0x7d, 0x62, 0x04, 0xcd, 0x4a, 0xc9, 0xd8, 0x49, 0xa8, 0x5f, 0xb7, 0x5a, 0x84, 0x8c, 0x79, 0x36, 0xa0, 0x85, 0xff, 0x89, 0x1b, 0x18, 0xdc, 0xd6, 0xd3, 0xb5, 0x5e, 0x57, 0xc2, 0x32, 0xc1, 0x69, 0xc3, 0x21, 0x76, 0x7c, 0xac, 0xf3, 0xc5, 0x1e, 0x96, 0xc9, 0x99, 0x4f, 0x8b, 0x5e, 0x6a, 0x2e, 0x73, 0x53, 0x10, 0xbd, 0xd1, 0x5c, 0x86, 0x79, 0x7b, 0xea, 0xab, 0x5f, 0x36, 0x57, 0xd8, 0x7d, 0xad, 0x3d, 0xf5, 0x3f, 0x25, 0x67, 0xd9, 0x91, 0x69, 0x3b, 0x5e, 0x73, 0x95, 0x02, 0x59, 0x41, 0xde, 0x04, 0x88, 0x84, 0x43, 0x25, 0xc8, 0x3f, 0xec, 0xb3, 0xc7, 0x29, 0x3b, 0xbd, 0xcf, 0xf6, 0xa5, 0x1c, 0x02, 0x28, 0xf5, 0xef, 0x3f, 0x56, 0xb7, 0x07, 0x52, 0x5e, 0xfe, 0x1f, 0x50, 0x0e, 0x34, 0x15, 0xbd, 0x21, 0x88, 0xce, 0x7c, 0x89, 0xa5, 0x94, 0x3e, 0x0b, 0xbd, 0xb9, 0x0e, 0x45, 0x2f, 0x78, 0x1d, 0x92, 0x49, 0x4a, 0xd1, 0xf2, 0x2f, 0x73, 0xb0, 0xc0, 0x21, 0x48, 0x86, 0xda, 0x7e, 0x6f, 0xd0, 0xb9, 0xdf, 0xd9, 0x6e, 0x0d, 0x3a, 0xbd, 0x7d, 0xda, 0x4a, 0x51, 0x89, 0xc1, 0x88, 0x23, 0xf0, 0xb0, 0xbf, 0xd3, 0x1a, 0xb4, 0x29, 0xe3, 0xa2, 0xc2, 0x4b, 0xe4, 0x48, 0xd1, 0xeb, 0xb7, 0xf7, 0xf9, 0x2b, 0x27, 0xfa, 0x1f, 0xbd, 0x04, 0x95, 0x4f, 0xda, 0xed, 0x7e, 0xab, 0xdb, 0x79, 0xd4, 0xa6, 0x4b, 0xb0, 0xa8, 0x44, 0x00, 0x62, 0xd2, 0x94, 0xf6, 0x7d, 0xa5, 0x7d, 0xf0, 0x80, 0x2e, 0xb3, 0xa2, 0x12, 0x14, 0x49, 0xbd, 0x9d, 0xce, 0xc1, 0x76, 0x4b, 0xd9, 0x69, 0xef, 0xd0, 0x05, 0x56, 0x54, 0x22, 0x00, 0x19, 0xd5, 0x41, 0x6f, 0xd0, 0xea, 0xd2, 0xe5, 0x55, 0x54, 0x58, 0x41, 0xde, 0x82, 0x12, 0x5b, 0x25, 0x04, 0x6f, 0x58, 0xce, 0xd4, 0xe7, 0x9e, 0x0a, 0x2b, 0x10, 0xb9, 0xed, 0xa9, 0x4f, 0xc0, 0xfc, 0x3c, 0xc1, 0x4a, 0x32, 0x86, 0x12, 0x73, 0x6c, 0xd1, 0x2d, 0x28, 0x11, 0x5f, 0xdd, 0x18, 0xf3, 0xd1, 0xbd, 0x90, 0x74, 0x7d, 0xb7, 0x29, 0x56, 0xe1, 0x54, 0xe8, 0xb5, 0xf8, 0x8b, 0x86, 0xd5, 0x24, 0x79, 0xec, 0x4d, 0xc3, 0x2f, 0x73, 0x50, 0x13, 0xb9, 0x90, 0x25, 0x34, 0xb4, 0x2d, 0x0b, 0x0f, 0x7d, 0xd5, 0xc5, 0xbe, 0xfb, 0x24, 0x18, 0x6c, 0x0e, 0x54, 0x08, 0x8c, 0xac, 0x05, 0xea, 0x2c, 0x85, 0xcf, 0x6b, 0x8a, 0x4a, 0x99, 0x00, 0x08, 0x27, 0xb2, 0xc1, 0x7d, 0x81, 0xb1, 0xa3, 0x99, 0xc6, 0x31, 0x56, 0x13, 0xaf, 0xcc, 0x96, 0x42, 0x4c, 0x87, 0x23, 0xd0, 0x0e, 0x5c, 0x9e, 0x18, 0x96, 0x31, 0x99, 0x4e, 0xd4, 0x50, 0x6f, 0x89, 0xdf, 0x17, 0x55, 0x65, 0x33, 0xf4, 0x12, 0xa7, 0x6a, 0x89, 0x44, 0x01, 0x17, 0xf9, 0x17, 0x79, 0xa8, 0x0a, 0xdd, 0xfb, 0x4f, 0xda, 0x0d, 0x1a, 0x07, 0xc3, 0x63, 0xdb, 0x37, 0x34, 0x62, 0x9c, 0x22, 0xe1, 0x98, 0x22, 0xa2, 0x08, 0xf7, 0x20, 0x10, 0x33, 0x7a, 0x00, 0xc5, 0x14, 0x32, 0xeb, 0x01, 0x14, 0x53, 0xc8, 0xb0, 0x2c, 0xff, 0x6b, 0x0e, 0x2a, 0xe1, 0x41, 0x28, 0xed, 0xb5, 0xe4, 0x32, 0xbc, 0x96, 0x4b, 0x00, 0x8c, 0x48, 0x78, 0xfc, 0xc1, 0xbc, 0xaa, 0x3e, 0xe7, 0x31, 0xf1, 0xa7, 0xaa, 0x6e, 0x78, 0x43, 0xfb, 0x18, 0xbb, 0x4f, 0x78, 0x7c, 0xa7, 0x36, 0xf1, 0xa7, 0x3b, 0x01, 0x8c, 0x78, 0x04, 0x64, 0x57, 0x25, 0xe3, 0x39, 0xb1, 0xf5, 0xe0, 0x21, 0x42, 0x95, 0xc3, 0xf6, 0x6c, 0x1d, 0x13, 0x3b, 0xcf, 0x3d, 0xb9, 0xf8, 0x4e, 0x57, 0x67, 0xd0, 0x56, 0xf6, 0x23, 0xb1, 0x52, 0xf0, 0x20, 0x2b, 0x78, 0x24, 0x46, 0x36, 0x42, 0x7f, 0xe8, 0xa8, 0x13, 0xcf, 0xe3, 0x1e, 0x6d, 0xc9, 0x1f, 0x3a, 0x7b, 0x9e, 0x27, 0x7f, 0x08, 0x55, 0xe1, 0x30, 0x87, 0x6e, 0xc1, 0xb2, 0x78, 0xf2, 0x8b, 0xfb, 0x1a, 0x4b, 0xc2, 0x49, 0x8f, 0x39, 0x1a, 0xf2, 0xbf, 0xe4, 0xa0, 0x91, 0x38, 0xce, 0x9d, 0xee, 0x02, 0xf1, 0x43, 0x61, 0xa4, 0x62, 0x75, 0xa5, 0xca, 0x61, 0x74, 0xfa, 0xae, 0x40, 0xf5, 0x08, 0x9b, 0x0e, 0x76, 0x55, 0xdb, 0x32, 0x83, 0x61, 0x03, 0x06, 0xea, 0x59, 0x26, 0xdd, 0xd2, 0x74, 0x3c, 0xc2, 0xae, 0xab, 0x99, 0x8c, 0x09, 0x7b, 0x9e, 0x56, 0x0b, 0x80, 0x94, 0xcb, 0x6d, 0x58, 0xa1, 0x8f, 0xba, 0xf8, 0x33, 0x52, 0x35, 0x90, 0x87, 0x05, 0x9f, 0x96, 0x45, 0x5c, 0x9b, 0xcb, 0xf6, 0x1a, 0x2c, 0x99, 0xb6, 0x35, 0x36, 0xe9, 0xa3, 0xb1, 0x80, 0xbe, 0xc4, 0xb6, 0xdf, 0x10, 0xc1, 0x89, 0xe5, 0xb7, 0x61, 0x6d, 0xcf, 0x49, 0xf4, 0x9b, 0xdb, 0x8b, 0x99, 0xbd, 0x97, 0xff, 0x32, 0x07, 0x17, 0x52, 0xb5, 0xd8, 0xea, 0x9c, 0x3d, 0x64, 0xe2, 0x3e, 0xc8, 0x2e, 0x88, 0xa2, 0x9d, 0x23, 0xbe, 0xd7, 0xf1, 0xa1, 0x12, 0xf6, 0xba, 0x37, 0x60, 0x99, 0x3f, 0x3a, 0x73, 0x8d, 0x43, 0x35, 0x64, 0x53, 0x0c, 0x3e, 0x5f, 0xd0, 0x7b, 0x23, 0x1a, 0x82, 0x09, 0x37, 0xa2, 0x86, 0x40, 0x4e, 0xf7, 0x24, 0x36, 0x5e, 0xb5, 0x80, 0xf4, 0x80, 0x4c, 0xf9, 0x4f, 0x73, 0xb0, 0x94, 0xea, 0x06, 0xfa, 0x76, 0xc2, 0x28, 0x5f, 0x13, 0xf6, 0xb1, 0xec, 0x91, 0x0a, 0xed, 0xf3, 0x56, 0xdc, 0x3e, 0x5f, 0x3d, 0xa5, 0x66, 0xcc, 0x54, 0xb7, 0xa0, 0xce, 0x43, 0x01, 0x7c, 0xe8, 0x67, 0x9d, 0x7d, 0x85, 0xd1, 0xcd, 0xc7, 0xa7, 0xe4, 0xff, 0xe7, 0xa0, 0xc6, 0x79, 0x84, 0xcf, 0x27, 0x9f, 0x8f, 0x05, 0x51, 0x58, 0xdf, 0xf6, 0x89, 0x21, 0xe0, 0x0f, 0x71, 0xe9, 0xd2, 0xa3, 0x20, 0x1a, 0x6b, 0x20, 0x4b, 0x98, 0x13, 0x88, 0x11, 0xdc, 0xba, 0x52, 0x67, 0x34, 0x41, 0x14, 0xee, 0x6f, 0xf3, 0xb0, 0xc1, 0x57, 0xa2, 0xc9, 0x9e, 0x94, 0xb3, 0xf0, 0x61, 0xb0, 0x0f, 0xbd, 0x0e, 0x48, 0x33, 0xbf, 0xd2, 0x9e, 0x78, 0xc4, 0xe7, 0x73, 0x34, 0x17, 0xab, 0x93, 0xe8, 0x53, 0x14, 0x86, 0xd9, 0x66, 0x88, 0x3d, 0xac, 0xa3, 0xdb, 0xb0, 0x6a, 0x8c, 0x2d, 0xdb, 0x25, 0x1e, 0x27, 0x95, 0x2c, 0xb8, 0xf0, 0xe1, 0x77, 0xe0, 0x0c, 0xd9, 0xf2, 0x88, 0x88, 0xec, 0x92, 0x87, 0x9c, 0x19, 0x82, 0x90, 0x7d, 0xd8, 0x04, 0x5d, 0xf0, 0xf4, 0xcc, 0xc0, 0xb4, 0x6b, 0x2d, 0xa0, 0xe0, 0x4d, 0x51, 0x81, 0xdd, 0x0e, 0x39, 0x25, 0x5e, 0x0c, 0x15, 0x4f, 0x35, 0x2c, 0x6d, 0xe8, 0x13, 0xab, 0x46, 0xab, 0x07, 0x11, 0xeb, 0xb5, 0x90, 0xa0, 0xc3, 0xf1, 0xb4, 0x36, 0x35, 0x5e, 0x6c, 0x30, 0x55, 0xcd, 0x18, 0x3b, 0x41, 0x80, 0x98, 0x7f, 0x5d, 0x63, 0x8c, 0x1d, 0xf4, 0x01, 0xac, 0xf3, 0xce, 0x58, 0xf8, 0xc4, 0x57, 0x69, 0xe8, 0x7f, 0xec, 0xa8, 0x13, 0xec, 0xbb, 0xc6, 0x90, 0xaf, 0xd1, 0x0b, 0x8c, 0x62, 0x1f, 0x9f, 0xf8, 0x0f, 0x6c, 0xa7, 0x33, 0x76, 0xf6, 0x28, 0x56, 0xfe, 0x9b, 0x3c, 0xac, 0x67, 0x0e, 0x2b, 0x9b, 0xef, 0xff, 0x1a, 0xd5, 0xaf, 0x35, 0xaa, 0xbf, 0x9b, 0x83, 0xd5, 0xcc, 0x51, 0x45, 0x1f, 0x26, 0xec, 0xc0, 0xf5, 0x54, 0xd0, 0x30, 0x4b, 0xbb, 0x43, 0x5b, 0xf0, 0x41, 0xdc, 0x16, 0xbc, 0x7c, 0x46, 0xed, 0x98, 0x3d, 0xb8, 0x03, 0x17, 0x1e, 0x7a, 0x98, 0x9e, 0xc4, 0x1d, 0x93, 0x7e, 0x57, 0xe3, 0x9d, 0x69, 0x93, 0x6f, 0xc3, 0x6a, 0xb2, 0xce, 0x19, 0x16, 0x59, 0xfe, 0x21, 0x00, 0x39, 0xf1, 0x73, 0xd6, 0x9b, 0xb0, 0xc4, 0x82, 0x0f, 0x13, 0xce, 0x83, 0x1c, 0xf1, 0x58, 0x8d, 0x06, 0x45, 0x04, 0xbc, 0x5b, 0x34, 0x1c, 0x32, 0xd1, 0x4e, 0xa8, 0x4b, 0x14, 0x5c, 0x12, 0xd1, 0xad, 0x8b, 0x03, 0x59, 0x68, 0xf2, 0x07, 0x50, 0x69, 0x87, 0xc7, 0xa8, 0x17, 0xce, 0x5d, 0x85, 0x22, 0xe1, 0x8e, 0x5e, 0x4f, 0x4c, 0xd3, 0x4a, 0x3c, 0xc0, 0x9d, 0x98, 0x95, 0xd9, 0x6f, 0x82, 0x43, 0x51, 0x83, 0x49, 0xb8, 0x0d, 0xd0, 0x89, 0x46, 0x27, 0x25, 0x53, 0x2e, 0x43, 0xa6, 0xb7, 0xa0, 0xd2, 0x09, 0x7b, 0x7c, 0xae, 0x1a, 0x2a, 0x14, 0x3b, 0x67, 0xf4, 0xa2, 0xf3, 0x3c, 0xbd, 0xe8, 0x24, 0x7b, 0xf1, 0xeb, 0x1c, 0x48, 0x49, 0xbd, 0x40, 0xef, 0x27, 0x5a, 0x13, 0x36, 0xaa, 0x6c, 0xbd, 0x0b, 0x5b, 0x7e, 0x37, 0xde, 0xf2, 0x95, 0xd9, 0x15, 0x63, 0x8f, 0x08, 0x64, 0x28, 0xe2, 0xc3, 0xb1, 0x93, 0xfe, 0x36, 0x8e, 0x8c, 0xba, 0x42, 0x71, 0x84, 0xc6, 0x20, 0x34, 0xa9, 0xef, 0xcf, 0x3a, 0x94, 0x86, 0xe0, 0xe4, 0x7b, 0x7c, 0x67, 0x19, 0x68, 0xee, 0x18, 0xfb, 0x7b, 0x78, 0x72, 0x88, 0x5d, 0xef, 0xc8, 0x10, 0x26, 0x29, 0xee, 0x51, 0xe5, 0xd2, 0x1e, 0x95, 0xdc, 0xe2, 0x66, 0x34, 0xc9, 0x23, 0x9c, 0xb5, 0xb3, 0x59, 0x84, 0x46, 0x23, 0xc9, 0xe3, 0x4c, 0xa3, 0x91, 0x2d, 0xf8, 0x79, 0x8d, 0x46, 0xa6, 0xc8, 0xc1, 0x4c, 0xff, 0x10, 0x2e, 0x77, 0x6d, 0x6b, 0xdc, 0x25, 0x1e, 0xd0, 0x73, 0x3a, 0x74, 0xe7, 0x70, 0x67, 0xe5, 0xbf, 0xcb, 0xc1, 0xa5, 0x59, 0xfc, 0xbf, 0x49, 0xd7, 0x6f, 0x13, 0x96, 0x68, 0x00, 0x2b, 0x26, 0x1f, 0xf3, 0x3b, 0x1a, 0x04, 0xa1, 0x08, 0x2e, 0xf7, 0x5d, 0x58, 0x4f, 0xd1, 0xba, 0x2a, 0x3e, 0x71, 0x0c, 0x37, 0x74, 0x99, 0xd7, 0x12, 0x95, 0xdc, 0x36, 0x43, 0xcb, 0xbf, 0x97, 0x83, 0xe6, 0xac, 0x0e, 0xa2, 0x8f, 0x13, 0xf3, 0x2a, 0x7c, 0x25, 0x74, 0xfa, 0xa0, 0x87, 0x53, 0xfb, 0x61, 0x7c, 0x6a, 0x6f, 0x9c, 0xcd, 0x20, 0x36, 0xbb, 0x3f, 0x9b, 0x87, 0x05, 0xee, 0xdf, 0xa1, 0x4f, 0x60, 0x79, 0xe2, 0xa8, 0xa9, 0xdb, 0x29, 0x26, 0xd9, 0xc6, 0x29, 0x4e, 0xa7, 0xb2, 0x34, 0x49, 0xb9, 0xbb, 0x6f, 0x86, 0x3d, 0x63, 0x82, 0xad, 0xa5, 0xae, 0xa7, 0x12, 0x1d, 0x49, 0x5e, 0x5d, 0x16, 0xce, 0x7d, 0x75, 0xf9, 0x19, 0xac, 0x05, 0x47, 0x32, 0xbe, 0xf9, 0xf1, 0x9b, 0xe8, 0x20, 0x5a, 0x7a, 0xe5, 0x8c, 0x4d, 0x52, 0x59, 0x75, 0x33, 0xb7, 0xea, 0x07, 0x80, 0xa6, 0x1e, 0x8e, 0xb6, 0x16, 0x66, 0x6f, 0xe7, 0x93, 0xf7, 0x4f, 0x49, 0x13, 0xa5, 0x48, 0xd3, 0xa4, 0x65, 0x4c, 0xdd, 0x11, 0x94, 0x92, 0xbd, 0x9b, 0x7d, 0x47, 0x10, 0x76, 0xcf, 0xa7, 0xcb, 0x54, 0x9d, 0x84, 0xeb, 0x94, 0xdf, 0x6c, 0x5e, 0x39, 0x63, 0x39, 0xf3, 0xee, 0xa5, 0x8c, 0x8a, 0x06, 0x1b, 0xe4, 0xdc, 0xa6, 0xb2, 0x13, 0x5d, 0x6a, 0xde, 0xd9, 0x4d, 0xa8, 0x7c, 0xb6, 0x42, 0x29, 0x4d, 0x73, 0x96, 0x7e, 0xc7, 0xee, 0x1d, 0x2b, 0xe7, 0xb8, 0x77, 0x6c, 0x87, 0x9f, 0x51, 0x0b, 0xae, 0x09, 0x5f, 0xd4, 0xc1, 0xf2, 0xe7, 0x45, 0x74, 0x11, 0xca, 0x34, 0xc4, 0x3b, 0xd1, 0x4e, 0xb8, 0x65, 0x59, 0x20, 0xe5, 0x3d, 0xed, 0x44, 0xde, 0xa1, 0x1f, 0x01, 0xc4, 0xbd, 0x95, 0xe7, 0xe7, 0xf2, 0x05, 0x94, 0x03, 0x2e, 0xe8, 0xad, 0xc4, 0x4a, 0x6d, 0xa6, 0xbb, 0x91, 0x50, 0xe8, 0x37, 0xe2, 0x2b, 0x73, 0x2d, 0x5d, 0x21, 0xb6, 0x12, 0xa7, 0x50, 0xe2, 0x0f, 0x2d, 0x36, 0xa0, 0x62, 0x38, 0x6a, 0xec, 0xad, 0x45, 0xd9, 0x08, 0x5e, 0x61, 0xbc, 0x02, 0x8d, 0x89, 0xe6, 0x7d, 0xc1, 0xfd, 0x6a, 0x75, 0x62, 0x58, 0x5c, 0xea, 0x3a, 0x01, 0x33, 0x9f, 0x7a, 0xcf, 0xb0, 0x52, 0x74, 0xda, 0x09, 0x3f, 0x79, 0x89, 0x74, 0xda, 0x89, 0xfc, 0xb3, 0x1c, 0x40, 0xf4, 0xe4, 0xfd, 0xb7, 0xfc, 0x2e, 0x81, 0xc0, 0x4c, 0xc3, 0xf3, 0xe9, 0xcb, 0xbc, 0x8a, 0x42, 0xff, 0xd3, 0xa7, 0xd6, 0xf1, 0x67, 0x17, 0x52, 0x52, 0xed, 0x85, 0xb7, 0x16, 0xbb, 0x50, 0xde, 0xd3, 0xfc, 0xe1, 0x11, 0x11, 0xe6, 0x46, 0x4c, 0x18, 0xc1, 0x1d, 0xa1, 0x14, 0x67, 0x7c, 0x22, 0xf1, 0x08, 0x6a, 0xb1, 0x73, 0xc6, 0xad, 0x18, 0x33, 0x61, 0xf9, 0x8a, 0x54, 0x02, 0xcf, 0x0b, 0x50, 0x12, 0xce, 0x2e, 0x75, 0x85, 0x97, 0xe4, 0x7f, 0x2c, 0x02, 0x6c, 0xdb, 0x96, 0x6e, 0x30, 0x1b, 0x71, 0x1b, 0xf8, 0x47, 0x79, 0x6a, 0xf4, 0x9d, 0x01, 0x4a, 0x48, 0x7a, 0x80, 0x7d, 0xa5, 0xc2, 0xa8, 0x48, 0xb7, 0xde, 0x85, 0x5a, 0x78, 0x3d, 0x43, 0x2a, 0xe5, 0x67, 0x56, 0x0a, 0x1f, 0x80, 0x91, 0x6a, 0xdf, 0x81, 0xc5, 0xc4, 0xa1, 0xaa, 0x90, 0x8c, 0xee, 0x8a, 0x5d, 0x51, 0x6a, 0x9a, 0xd8, 0xfd, 0x3b, 0x50, 0x0d, 0x6a, 0x93, 0x36, 0x8b, 0xb3, 0x05, 0x65, 0xd5, 0x48, 0x8b, 0xef, 0x85, 0x5f, 0x20, 0xfb, 0x4f, 0x68, 0xad, 0xf9, 0x99, 0xb5, 0x6a, 0x21, 0x21, 0xa9, 0xf8, 0x11, 0x2c, 0x91, 0x13, 0x53, 0xbc, 0x72, 0x69, 0x66, 0xe5, 0x06, 0x3e, 0xf1, 0xb7, 0xc5, 0xfa, 0x57, 0xa0, 0xea, 0x3a, 0x5f, 0x18, 0xc4, 0x14, 0x4d, 0x4d, 0x9f, 0x9a, 0xb9, 0x79, 0x05, 0x5c, 0xf6, 0x45, 0xd4, 0xd4, 0xf4, 0xd1, 0x87, 0x00, 0xd1, 0x67, 0x4e, 0xfc, 0x76, 0x59, 0xb8, 0x3c, 0x89, 0xe6, 0x87, 0x5b, 0x44, 0x32, 0xad, 0x95, 0xf0, 0x2b, 0x28, 0x74, 0x0f, 0x96, 0x4d, 0x62, 0x0d, 0x13, 0x12, 0x56, 0x66, 0x4a, 0xb8, 0x44, 0xc9, 0x45, 0x19, 0xe5, 0x23, 0xa8, 0x84, 0xbc, 0xd1, 0x32, 0x34, 0x94, 0xde, 0xc3, 0x41, 0x5b, 0x1d, 0x7c, 0xde, 0x0f, 0xdf, 0x7e, 0xae, 0xc1, 0xb2, 0x00, 0xec, 0xec, 0x0f, 0xda, 0xca, 0x7e, 0xab, 0x2b, 0xe5, 0x12, 0x88, 0xf6, 0x63, 0x8e, 0xc8, 0xa3, 0x15, 0x90, 0x04, 0x04, 0xff, 0x9c, 0x57, 0x1e, 0x41, 0x23, 0x6c, 0xb9, 0xc5, 0xbe, 0xa5, 0xbf, 0x1d, 0x53, 0xe6, 0x4b, 0x62, 0xcf, 0x63, 0x84, 0x82, 0x3e, 0x5f, 0x85, 0x6a, 0xd0, 0x5b, 0x23, 0xfc, 0x32, 0x4c, 0x04, 0xc9, 0xfb, 0x50, 0xd9, 0xc3, 0x3a, 0x6f, 0xe1, 0xb5, 0x58, 0x0b, 0x6b, 0xe2, 0xa5, 0x8b, 0x9e, 0xe2, 0xbd, 0x02, 0xf3, 0xc7, 0x9a, 0x39, 0x0d, 0x3e, 0x9c, 0x65, 0x05, 0x59, 0x85, 0x46, 0xcb, 0xeb, 0xbb, 0xd8, 0xc1, 0x56, 0xc0, 0x55, 0x82, 0x82, 0xe6, 0x59, 0xdc, 0xf9, 0x25, 0x7f, 0xc9, 0x32, 0x23, 0x14, 0x5a, 0x78, 0x9b, 0xc1, 0x4a, 0x48, 0x86, 0x3a, 0xd9, 0x7b, 0x4d, 0x3c, 0xf2, 0xd5, 0x89, 0xed, 0xf9, 0xdc, 0x85, 0xab, 0x4e, 0x3d, 0xdc, 0xc5, 0x23, 0x7f, 0xcf, 0xa6, 0xaf, 0xa9, 0xeb, 0xfc, 0x41, 0x23, 0x67, 0x7f, 0xea, 0x47, 0x88, 0x1e, 0x36, 0x47, 0xdc, 0x4d, 0xa4, 0xff, 0xe5, 0x1b, 0xd0, 0xe8, 0xd2, 0x78, 0xb4, 0x8b, 0x47, 0x9c, 0x41, 0xd8, 0x11, 0x7e, 0xe3, 0xc2, 0x3a, 0xf2, 0xd7, 0x05, 0x58, 0x60, 0x04, 0x5e, 0xf4, 0xf2, 0x47, 0x63, 0x39, 0x13, 0x52, 0x86, 0x92, 0x2a, 0x05, 0xa3, 0xe6, 0x2f, 0x7f, 0x38, 0xef, 0xf7, 0xa0, 0x12, 0x5d, 0x46, 0xe6, 0x93, 0x4f, 0x7e, 0x12, 0x13, 0xa7, 0x44, 0xb4, 0xe8, 0x3a, 0x14, 0x26, 0xdc, 0x87, 0x8d, 0x1d, 0xca, 0xc2, 0x99, 0x50, 0x08, 0x1e, 0xbd, 0x0f, 0x40, 0x56, 0x38, 0x1b, 0x6f, 0xbe, 0xc0, 0x2f, 0xc6, 0x6c, 0x83, 0x38, 0x15, 0x74, 0x9d, 0x33, 0x00, 0xfa, 0x08, 0xea, 0xb1, 0xe5, 0xca, 0xd7, 0xf9, 0x29, 0xd2, 0xd5, 0xc4, 0x15, 0x8b, 0x6e, 0xc3, 0x02, 0x7f, 0x71, 0xca, 0x17, 0xb9, 0xa0, 0x2e, 0xb1, 0x09, 0x52, 0x02, 0x3a, 0x22, 0x2c, 0xbf, 0x1d, 0x70, 0xf1, 0x88, 0xfb, 0x31, 0x17, 0x45, 0x57, 0x23, 0x36, 0x2f, 0xc1, 0xc5, 0x81, 0x8b, 0x47, 0xe8, 0x1e, 0x34, 0x12, 0x6b, 0x97, 0x7b, 0x2a, 0xa7, 0x88, 0xbb, 0x18, 0x5f, 0xbe, 0xf2, 0x8f, 0x72, 0x50, 0x09, 0x3f, 0x01, 0x09, 0x77, 0x8f, 0x9c, 0xb0, 0x91, 0xbd, 0x03, 0x30, 0x0c, 0x8d, 0x08, 0x9f, 0xad, 0x95, 0x2c, 0x03, 0xa3, 0x08, 0x74, 0xe8, 0x35, 0x58, 0x60, 0x6a, 0xe1, 0xf1, 0xd9, 0x12, 0x1f, 0x65, 0x31, 0x84, 0x12, 0x50, 0xc8, 0x9f, 0x42, 0x89, 0x3b, 0xb0, 0x59, 0x02, 0xc4, 0x3f, 0x22, 0xcb, 0x9f, 0xef, 0x23, 0xb2, 0xbf, 0xcf, 0x81, 0x94, 0x7c, 0x0d, 0x85, 0x6e, 0xc6, 0x56, 0xf2, 0x4a, 0xf2, 0xdd, 0x94, 0xb0, 0x8c, 0xc5, 0x9c, 0x0b, 0xf9, 0x73, 0xe4, 0x5c, 0xc8, 0xca, 0x90, 0x23, 0x7e, 0x58, 0x55, 0x3c, 0xeb, 0xc3, 0x2a, 0xf4, 0x26, 0x2c, 0xe8, 0x78, 0xa4, 0x11, 0x23, 0x3f, 0x7f, 0xda, 0x42, 0x0a, 0xa8, 0xe4, 0xdf, 0xc9, 0x41, 0x41, 0xb1, 0x35, 0xb4, 0x08, 0x79, 0x2d, 0x08, 0x7d, 0xe4, 0x35, 0x0f, 0xbd, 0x04, 0x7c, 0x83, 0x35, 0x71, 0xe0, 0x10, 0x45, 0x00, 0x62, 0x64, 0x26, 0x1a, 0x45, 0xf1, 0x17, 0xa1, 0xac, 0x24, 0x3c, 0xc1, 0x2c, 0xc6, 0x9e, 0xb4, 0x06, 0x8f, 0x21, 0xe7, 0x4f, 0xff, 0x14, 0x5c, 0xbe, 0xc1, 0x1e, 0xe6, 0xda, 0xda, 0x59, 0x9f, 0x77, 0xb3, 0x2f, 0x59, 0x29, 0x61, 0xf4, 0x25, 0xab, 0x6b, 0x6b, 0x19, 0x5f, 0xb2, 0x12, 0x22, 0x8a, 0x92, 0x3d, 0x28, 0x3c, 0x72, 0x47, 0x99, 0xda, 0xb1, 0x08, 0x79, 0x97, 0x9d, 0x79, 0x6b, 0x4a, 0xde, 0xd5, 0xa9, 0xcb, 0xc8, 0x1e, 0xc9, 0xb9, 0xcc, 0xf9, 0xaa, 0x29, 0x65, 0x06, 0x50, 0x68, 0xce, 0x0f, 0xfe, 0x04, 0xcf, 0xf5, 0xe9, 0x9c, 0xd4, 0x94, 0x32, 0x03, 0x28, 0x3e, 0x7f, 0xcd, 0xc4, 0x9e, 0x7f, 0xe5, 0x0d, 0x5d, 0xfe, 0x75, 0x0e, 0x4a, 0xec, 0xab, 0x91, 0xd4, 0x18, 0x6f, 0x40, 0x25, 0x0a, 0xcc, 0xf2, 0xfc, 0x20, 0x6e, 0x10, 0x89, 0xbd, 0x02, 0x55, 0xe2, 0xed, 0x61, 0x8b, 0x5d, 0xb0, 0x15, 0xd8, 0x96, 0xcd, 0x40, 0xf4, 0x82, 0xed, 0x55, 0x90, 0x38, 0x01, 0xb7, 0xc9, 0x5c, 0x41, 0x2a, 0x4a, 0x83, 0xc1, 0x5b, 0x01, 0x38, 0xf6, 0xc2, 0x75, 0x3e, 0xf1, 0xc2, 0xf5, 0xf5, 0xcc, 0x33, 0x19, 0xbf, 0x86, 0x4a, 0x9e, 0xbb, 0xe4, 0x5f, 0xe5, 0xa0, 0x42, 0x5f, 0xf6, 0x76, 0xac, 0x91, 0xfd, 0x8d, 0x3c, 0x2a, 0xbe, 0x01, 0x0d, 0x6b, 0x3a, 0x51, 0x85, 0x07, 0xdd, 0xfc, 0xda, 0x76, 0xd1, 0x9a, 0x4e, 0xc4, 0x07, 0xf1, 0x17, 0xa1, 0x6c, 0xf1, 0x98, 0x5d, 0xf0, 0x4a, 0xc0, 0x62, 0xe1, 0x3a, 0x74, 0x0d, 0x6a, 0x04, 0x15, 0x3e, 0xd9, 0x60, 0xf7, 0xb2, 0x55, 0x6b, 0x3a, 0x69, 0x71, 0x90, 0xfc, 0x1d, 0xfa, 0xe1, 0x90, 0x62, 0x1c, 0x92, 0x8e, 0x04, 0xda, 0x16, 0xbc, 0x85, 0x4d, 0x7d, 0x37, 0x19, 0x76, 0x99, 0xbd, 0x85, 0x95, 0x3f, 0xa4, 0x99, 0xbb, 0xc2, 0xda, 0x5c, 0x05, 0xcf, 0x5b, 0x7d, 0xf3, 0x37, 0x79, 0x28, 0xdd, 0x67, 0xdd, 0x9d, 0x87, 0x9c, 0x2a, 0xcd, 0x21, 0x80, 0x62, 0xa7, 0x7f, 0xfc, 0x8e, 0xf4, 0xec, 0x69, 0x91, 0xff, 0xdf, 0x92, 0x9e, 0x3d, 0x2d, 0xa3, 0x3a, 0x2c, 0x10, 0xb8, 0xba, 0xb7, 0x2d, 0xfd, 0xe8, 0x69, 0x91, 0x17, 0xb7, 0x58, 0xb1, 0x8c, 0x1a, 0x50, 0x61, 0xd8, 0x7e, 0xf7, 0x40, 0xfa, 0xf1, 0xd3, 0x22, 0x07, 0x6c, 0x05, 0x80, 0x32, 0x5a, 0x84, 0x32, 0xa5, 0x78, 0xd4, 0xdf, 0x97, 0x9e, 0x3e, 0x2b, 0xf2, 0xf2, 0x16, 0x2f, 0x97, 0xd1, 0x12, 0x54, 0x03, 0x3c, 0x61, 0xfa, 0xec, 0x59, 0x91, 0x83, 0xb6, 0x22, 0x50, 0x99, 0x48, 0xf4, 0x88, 0x70, 0xfc, 0x8b, 0xa7, 0x3a, 0xf9, 0xdf, 0x26, 0xb5, 0x7f, 0xf5, 0x54, 0x47, 0x15, 0x28, 0x28, 0x83, 0x6d, 0xe9, 0xc7, 0xcf, 0x8a, 0x48, 0x02, 0xa0, 0x8c, 0xda, 0xfb, 0xdb, 0xad, 0xbe, 0xf4, 0xff, 0x9e, 0x06, 0x90, 0xad, 0x10, 0x52, 0x46, 0x2b, 0xb0, 0x78, 0xbf, 0xdb, 0xfb, 0x4c, 0x3d, 0xe8, 0xb7, 0xb7, 0x55, 0xda, 0xdd, 0x9f, 0x3c, 0x2b, 0xa6, 0xa0, 0x5b, 0xd2, 0x4f, 0x9e, 0x95, 0x51, 0x13, 0x50, 0x9c, 0x96, 0x8a, 0xfc, 0xd3, 0x67, 0xc5, 0x14, 0x66, 0x8b, 0x63, 0xca, 0xe8, 0x02, 0x48, 0x11, 0xa6, 0x7b, 0x87, 0xc3, 0x75, 0xb4, 0x08, 0xa5, 0x5e, 0xbf, 0xf5, 0xe9, 0xc3, 0xb6, 0xf4, 0xcf, 0xcf, 0xfe, 0xe4, 0x69, 0x71, 0x73, 0x1b, 0xca, 0x81, 0x82, 0x22, 0x80, 0xd2, 0x6e, 0xb7, 0x77, 0xaf, 0xd5, 0x95, 0xe6, 0xa2, 0x8c, 0x2f, 0xf4, 0xa5, 0x4d, 0x6b, 0xe7, 0x7b, 0x6a, 0x67, 0x5f, 0xca, 0xa3, 0x2a, 0x2c, 0x90, 0xff, 0xbd, 0x87, 0x03, 0x96, 0x0a, 0xe6, 0x91, 0x72, 0x5f, 0x2a, 0x6e, 0x76, 0x63, 0x4f, 0xfa, 0x59, 0x84, 0x03, 0x49, 0x50, 0xeb, 0xf6, 0x7a, 0x9f, 0x3c, 0xec, 0xab, 0xed, 0xc7, 0xad, 0xed, 0x81, 0x34, 0x87, 0x96, 0xa0, 0xce, 0x21, 0xdd, 0xde, 0xfe, 0x6e, 0x5b, 0x91, 0x72, 0x08, 0xc1, 0x22, 0x07, 0x1d, 0x3c, 0xe8, 0x29, 0x83, 0xb6, 0x22, 0xe5, 0x37, 0x7d, 0xa8, 0x0a, 0x07, 0x42, 0xfa, 0xd0, 0x47, 0x69, 0xdf, 0xef, 0x3c, 0x96, 0xe6, 0x50, 0x0d, 0xca, 0xfb, 0xed, 0xce, 0xee, 0x83, 0x7b, 0x3d, 0x52, 0x79, 0x01, 0x0a, 0x83, 0xd6, 0x2e, 0x97, 0xea, 0x40, 0xed, 0xb7, 0x06, 0x0f, 0xa4, 0x02, 0xaa, 0x43, 0x65, 0xbb, 0xb7, 0xb7, 0xf7, 0x70, 0xbf, 0x33, 0xf8, 0x5c, 0x22, 0x53, 0x58, 0x6f, 0x3f, 0x1e, 0xa8, 0x11, 0x68, 0x9e, 0x38, 0xd4, 0xdd, 0x96, 0xb2, 0xdb, 0x16, 0x80, 0xa5, 0xcd, 0x57, 0xa1, 0x12, 0x9e, 0xfc, 0xe8, 0x6b, 0xc3, 0xfd, 0xcf, 0xc5, 0x67, 0x87, 0x00, 0xa5, 0xce, 0xfe, 0xa3, 0xb6, 0x32, 0x90, 0xf2, 0x9b, 0x9b, 0x20, 0x25, 0xcf, 0x75, 0xa8, 0x04, 0xf9, 0xf6, 0xa7, 0xd2, 0x1c, 0xf9, 0xdd, 0x6d, 0x4b, 0x39, 0xf2, 0xdb, 0x6d, 0x4b, 0xf9, 0xcd, 0x37, 0xf9, 0x0d, 0x3f, 0xf7, 0xd3, 0xa2, 0x07, 0x8d, 0x64, 0x54, 0xb7, 0xb7, 0xdb, 0xfd, 0x01, 0x63, 0xae, 0xb4, 0xbf, 0xd7, 0xde, 0x26, 0xcc, 0x1f, 0xc2, 0x72, 0x86, 0x9f, 0x4d, 0xba, 0x11, 0x4a, 0xab, 0xb6, 0x76, 0x76, 0xa4, 0x39, 0xe2, 0xd0, 0x47, 0x20, 0xa5, 0xbd, 0xd7, 0x7b, 0x44, 0x1a, 0x5e, 0x85, 0x25, 0x11, 0xca, 0x5f, 0x4a, 0x6e, 0xbe, 0x01, 0xf5, 0x98, 0x73, 0x4d, 0xc6, 0x6c, 0xaf, 0xbd, 0xa3, 0xee, 0xf5, 0x08, 0xab, 0x06, 0x54, 0x49, 0x21, 0x20, 0xcf, 0x6d, 0xbe, 0x0e, 0x10, 0xed, 0xe0, 0x61, 0x76, 0x2d, 0x32, 0x08, 0x7b, 0xfd, 0x9e, 0xc2, 0x65, 0x6e, 0x3f, 0xa6, 0xff, 0xf3, 0x77, 0x7e, 0x7e, 0x15, 0xca, 0xbb, 0x64, 0x81, 0xb7, 0x1c, 0x03, 0x75, 0xa1, 0x2a, 0x7c, 0x07, 0x88, 0x5e, 0x8a, 0xf9, 0x15, 0x89, 0xcf, 0x0b, 0xd7, 0x2f, 0xcd, 0xc0, 0xf2, 0xef, 0x12, 0xe6, 0x50, 0x07, 0x20, 0xfa, 0x52, 0x10, 0x6d, 0x88, 0xe4, 0x89, 0x8f, 0x0a, 0xd7, 0x5f, 0xca, 0x46, 0x86, 0xac, 0xee, 0x43, 0x25, 0xfc, 0x3e, 0x12, 0x09, 0x67, 0xf4, 0xe4, 0x87, 0x94, 0xeb, 0x1b, 0x99, 0xb8, 0x90, 0x4f, 0x17, 0xaa, 0x42, 0xb2, 0x37, 0xb1, 0x83, 0xe9, 0xec, 0x71, 0x62, 0x07, 0xb3, 0x32, 0xc4, 0xcd, 0xa1, 0x87, 0xb0, 0x18, 0x4f, 0xf3, 0x86, 0xae, 0x88, 0x81, 0x91, 0x8c, 0xec, 0x71, 0xeb, 0x57, 0x67, 0x13, 0x88, 0x42, 0x0a, 0x39, 0x0f, 0x45, 0x21, 0xd3, 0x69, 0x16, 0x45, 0x21, 0x33, 0x12, 0x25, 0xca, 0x73, 0x48, 0x81, 0x7a, 0x2c, 0x7f, 0x1a, 0xba, 0x1c, 0xdb, 0xdf, 0xd2, 0x1c, 0xaf, 0xcc, 0xc4, 0x87, 0x3c, 0xff, 0x3b, 0x2c, 0xa5, 0xf2, 0xb2, 0x21, 0xf9, 0xec, 0xfc, 0x70, 0xeb, 0xdf, 0x3a, 0x95, 0x26, 0xe4, 0xff, 0xdf, 0x40, 0x4a, 0xe6, 0x5f, 0x43, 0xc2, 0xbb, 0x88, 0x19, 0x69, 0xdf, 0xd6, 0xe5, 0xd3, 0x48, 0xc4, 0x59, 0x8b, 0x67, 0x63, 0x13, 0x67, 0x2d, 0x33, 0xb5, 0x9b, 0x38, 0x6b, 0x33, 0x12, 0xb9, 0xcd, 0xa1, 0xc7, 0xd0, 0x48, 0x24, 0x5c, 0x43, 0xe2, 0x64, 0x67, 0x66, 0x79, 0x5b, 0xbf, 0x76, 0x0a, 0x45, 0xc8, 0xf9, 0x43, 0x28, 0xb1, 0x5d, 0x1a, 0xad, 0xc5, 0x26, 0x3b, 0xfa, 0xe6, 0x68, 0xbd, 0x99, 0x46, 0x84, 0xd5, 0xdf, 0x83, 0x05, 0xfe, 0x11, 0x15, 0x8a, 0x93, 0x09, 0xdf, 0x55, 0xad, 0x27, 0xbe, 0xb7, 0x93, 0xe7, 0xde, 0xca, 0x11, 0x3d, 0x14, 0x3e, 0x38, 0x12, 0xf5, 0x30, 0xfd, 0xd5, 0x93, 0xa8, 0x87, 0x59, 0x5f, 0x29, 0xcd, 0xa1, 0x8f, 0x61, 0x81, 0xc7, 0x37, 0x51, 0x3a, 0x46, 0x1a, 0x70, 0xb9, 0x98, 0x81, 0x11, 0xed, 0x49, 0x94, 0xf9, 0x51, 0xb4, 0x27, 0xa9, 0xdc, 0x95, 0xa2, 0x3d, 0xc9, 0x48, 0x16, 0x39, 0x87, 0x76, 0x00, 0xa2, 0xbc, 0x64, 0x22, 0xab, 0x54, 0xb6, 0xb2, 0xf5, 0xec, 0x4f, 0xf5, 0xe8, 0x00, 0xdd, 0x0d, 0x73, 0xb1, 0x45, 0xaf, 0xbe, 0x05, 0x77, 0x29, 0x4c, 0x3d, 0xba, 0x9e, 0xc8, 0x12, 0x49, 0x2b, 0xdf, 0x87, 0x4a, 0x98, 0x1c, 0x4f, 0x34, 0x69, 0xc9, 0xd4, 0x7c, 0xa2, 0x49, 0x4b, 0x67, 0xd3, 0x63, 0xa3, 0x12, 0xa6, 0xce, 0x8b, 0x8d, 0x4a, 0x32, 0xcb, 0x5e, 0x6c, 0x54, 0xd2, 0xd9, 0xf6, 0xe6, 0xd0, 0x03, 0xa8, 0x84, 0xe9, 0xee, 0x44, 0x91, 0x92, 0x49, 0xf8, 0x44, 0x91, 0xd2, 0xf9, 0xf1, 0xe6, 0x6e, 0xe6, 0x88, 0xca, 0xb2, 0x04, 0x73, 0x68, 0x6d, 0x46, 0x7e, 0xbb, 0xf5, 0x66, 0x1a, 0x21, 0x9a, 0xfb, 0x30, 0x97, 0x9c, 0x28, 0x48, 0x32, 0x45, 0xdd, 0xfa, 0x46, 0x26, 0x4e, 0xd4, 0x39, 0x9e, 0x3d, 0x2b, 0xa1, 0xfa, 0x42, 0xda, 0x25, 0x51, 0xe7, 0x12, 0xa9, 0xb6, 0x42, 0xad, 0x4d, 0x72, 0x88, 0x67, 0xd5, 0x4a, 0x68, 0x6d, 0x82, 0x43, 0xa8, 0xb5, 0x94, 0x49, 0x4a, 0x60, 0x91, 0xcf, 0x4b, 0xd9, 0x48, 0x91, 0x55, 0x94, 0xd8, 0x0a, 0xa5, 0xf4, 0x62, 0x06, 0xab, 0x8c, 0x5c, 0x58, 0x74, 0x8f, 0x11, 0xb2, 0x5b, 0xa1, 0xb4, 0x66, 0x88, 0xcc, 0x2e, 0xcd, 0xc0, 0x8a, 0xf3, 0x15, 0xe6, 0xa6, 0x12, 0xe7, 0x2b, 0x99, 0xe2, 0x4a, 0x9c, 0xaf, 0x74, 0x32, 0x2b, 0xba, 0x57, 0xc5, 0xf2, 0x5c, 0x89, 0x7b, 0x55, 0x56, 0xca, 0x2c, 0x71, 0xaf, 0xca, 0x4e, 0x90, 0x15, 0x5a, 0x4f, 0x5b, 0x4b, 0x5a, 0xcf, 0xf0, 0x74, 0x9e, 0xb4, 0x9e, 0xd1, 0x69, 0x9c, 0x0d, 0x94, 0x90, 0x93, 0x0a, 0xa5, 0xc6, 0x55, 0xcc, 0xbb, 0x25, 0x0e, 0x54, 0x56, 0x22, 0xab, 0x39, 0xbe, 0x2e, 0xc8, 0xe9, 0x3d, 0xbe, 0x2e, 0xa2, 0x7c, 0x52, 0x89, 0x75, 0x21, 0xe6, 0x8c, 0x12, 0xd6, 0x05, 0xe1, 0x90, 0x5a, 0x17, 0x02, 0x93, 0x8d, 0x4c, 0x5c, 0x62, 0x4c, 0x12, 0x62, 0xc4, 0x72, 0x6c, 0x25, 0xc6, 0x24, 0x5e, 0x5d, 0xa1, 0xe1, 0x0d, 0xe1, 0x76, 0xe8, 0x72, 0x8c, 0x38, 0x95, 0x6d, 0x49, 0x9c, 0xa6, 0xcc, 0xf4, 0x54, 0x8c, 0x67, 0x2c, 0x6d, 0x94, 0xc8, 0x33, 0x2b, 0x1f, 0x95, 0xc8, 0x33, 0x3b, 0xdf, 0x14, 0x75, 0x23, 0x92, 0xc9, 0xa1, 0x44, 0x37, 0x62, 0x46, 0x36, 0x2a, 0xd1, 0x8d, 0x98, 0x99, 0x5b, 0x8a, 0xfa, 0x40, 0xa9, 0xcc, 0x50, 0xa2, 0x0f, 0x34, 0x2b, 0xf5, 0x94, 0xe8, 0x03, 0xcd, 0x4e, 0x2d, 0x35, 0x87, 0x7a, 0x50, 0x13, 0xb3, 0x48, 0xa1, 0xb8, 0xa3, 0x97, 0x4c, 0x98, 0xb4, 0x7e, 0x79, 0x16, 0x5a, 0x64, 0x28, 0xe6, 0x7f, 0x42, 0x71, 0xf7, 0xf6, 0x34, 0x86, 0x99, 0x69, 0xa3, 0x98, 0xc7, 0x13, 0xcf, 0xec, 0x84, 0x52, 0xee, 0x6d, 0x8a, 0xed, 0xb5, 0x53, 0x28, 0xc4, 0x89, 0x4b, 0xa6, 0x72, 0x12, 0x27, 0x6e, 0x46, 0xd2, 0xa8, 0x75, 0xf9, 0x34, 0x92, 0xc4, 0x59, 0x82, 0x87, 0x58, 0xe3, 0x67, 0x89, 0x58, 0x62, 0xa2, 0xc4, 0x59, 0x22, 0x91, 0x05, 0x88, 0xf2, 0x09, 0x13, 0xdf, 0x88, 0x7c, 0x92, 0x19, 0xa1, 0x44, 0x3e, 0xe9, 0x9c, 0x4d, 0x74, 0x5e, 0xc4, 0x94, 0x35, 0xe2, 0xbc, 0x64, 0x24, 0x73, 0x12, 0xe7, 0x25, 0x33, 0xcf, 0x12, 0xf7, 0xf8, 0x85, 0x1c, 0x34, 0x71, 0x8f, 0x3f, 0x9d, 0x81, 0x29, 0xee, 0xf1, 0x67, 0xa5, 0x3c, 0x9a, 0x43, 0x3a, 0x4d, 0x75, 0x96, 0x8a, 0x21, 0xbf, 0x9c, 0x31, 0x44, 0xa9, 0x84, 0x3a, 0xeb, 0xd7, 0xcf, 0xa0, 0x12, 0x5b, 0xc9, 0xc8, 0x25, 0x24, 0xb6, 0x32, 0x3b, 0x89, 0x91, 0xd8, 0xca, 0x69, 0x09, 0x89, 0xe6, 0xd0, 0x24, 0x48, 0x78, 0x96, 0x6a, 0xe8, 0x46, 0xf6, 0xd8, 0xa6, 0xdb, 0xba, 0x79, 0x36, 0x61, 0xd8, 0x9c, 0x13, 0x66, 0x39, 0x4b, 0x87, 0xe0, 0x67, 0x0c, 0x7c, 0xba, 0xc1, 0x57, 0xcf, 0x41, 0x29, 0xfa, 0x09, 0x51, 0x58, 0x0f, 0x6d, 0x24, 0xcf, 0x06, 0x42, 0xa8, 0x70, 0xfd, 0xa5, 0x6c, 0x64, 0xc0, 0xea, 0xb0, 0x44, 0x93, 0xfd, 0xbf, 0xfd, 0x6f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x48, 0xf3, 0x74, 0xb0, 0xfb, 0x5f, 0x00, 0x00, } gobgp-1.29/api/gobgp.proto000066400000000000000000000571411324612745600155210ustar00rootroot00000000000000// Copyright (C) 2015-2017 Nippon Telegraph and Telephone Corporation. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation files // (the "Software"), to deal in the Software without restriction, // including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, // and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. syntax = "proto3"; package gobgpapi; // Interface exported by the server. service GobgpApi { rpc StartServer(StartServerRequest) returns (StartServerResponse) {} rpc StopServer(StopServerRequest) returns (StopServerResponse) {} rpc GetServer(GetServerRequest) returns (GetServerResponse) {} rpc AddNeighbor(AddNeighborRequest) returns (AddNeighborResponse) {} rpc DeleteNeighbor(DeleteNeighborRequest) returns (DeleteNeighborResponse) {} rpc GetNeighbor(GetNeighborRequest) returns (GetNeighborResponse) {} rpc ResetNeighbor(ResetNeighborRequest) returns (ResetNeighborResponse) {} rpc SoftResetNeighbor(SoftResetNeighborRequest) returns (SoftResetNeighborResponse) {} rpc ShutdownNeighbor(ShutdownNeighborRequest) returns (ShutdownNeighborResponse) {} rpc EnableNeighbor(EnableNeighborRequest) returns (EnableNeighborResponse) {} rpc DisableNeighbor(DisableNeighborRequest) returns (DisableNeighborResponse) {} rpc GetRib(GetRibRequest) returns (GetRibResponse) {} rpc GetPath(GetPathRequest) returns (stream Path) {} rpc ValidateRib(ValidateRibRequest) returns (ValidateRibResponse) {} rpc AddPath(AddPathRequest) returns (AddPathResponse) {} rpc DeletePath(DeletePathRequest) returns (DeletePathResponse) {} rpc MonitorRib(MonitorRibRequest) returns (stream Destination) {} rpc MonitorPeerState(Arguments) returns (stream Peer) {} rpc EnableMrt(EnableMrtRequest) returns (EnableMrtResponse) {} rpc DisableMrt(DisableMrtRequest) returns (DisableMrtResponse) {} rpc InjectMrt(stream InjectMrtRequest) returns (InjectMrtResponse) {} rpc AddBmp(AddBmpRequest) returns (AddBmpResponse) {} rpc DeleteBmp(DeleteBmpRequest) returns (DeleteBmpResponse) {} rpc GetRpki(GetRpkiRequest) returns (GetRpkiResponse) {} rpc AddRpki(AddRpkiRequest) returns (AddRpkiResponse) {} rpc DeleteRpki(DeleteRpkiRequest) returns (DeleteRpkiResponse) {} rpc EnableRpki(EnableRpkiRequest) returns (EnableRpkiResponse) {} rpc DisableRpki(DisableRpkiRequest) returns (DisableRpkiResponse) {} rpc ResetRpki(ResetRpkiRequest) returns (ResetRpkiResponse) {} rpc SoftResetRpki(SoftResetRpkiRequest) returns (SoftResetRpkiResponse) {} rpc GetRoa(GetRoaRequest) returns (GetRoaResponse) {} rpc EnableZebra(EnableZebraRequest) returns (EnableZebraResponse) {} rpc AddVrf(AddVrfRequest) returns (AddVrfResponse) {} rpc DeleteVrf(DeleteVrfRequest) returns (DeleteVrfResponse) {} rpc GetVrf(GetVrfRequest) returns (GetVrfResponse) {} rpc GetDefinedSet(GetDefinedSetRequest) returns (GetDefinedSetResponse) {} rpc AddDefinedSet(AddDefinedSetRequest) returns (AddDefinedSetResponse) {} rpc DeleteDefinedSet(DeleteDefinedSetRequest) returns (DeleteDefinedSetResponse) {} rpc ReplaceDefinedSet(ReplaceDefinedSetRequest) returns (ReplaceDefinedSetResponse) {} rpc GetStatement(GetStatementRequest) returns (GetStatementResponse) {} rpc AddStatement(AddStatementRequest) returns (AddStatementResponse) {} rpc DeleteStatement(DeleteStatementRequest) returns (DeleteStatementResponse) {} rpc ReplaceStatement(ReplaceStatementRequest) returns (ReplaceStatementResponse) {} rpc GetPolicy(GetPolicyRequest) returns (GetPolicyResponse) {} rpc AddPolicy(AddPolicyRequest) returns (AddPolicyResponse) {} rpc DeletePolicy(DeletePolicyRequest) returns (DeletePolicyResponse) {} rpc ReplacePolicy(ReplacePolicyRequest) returns (ReplacePolicyResponse) {} rpc GetPolicyAssignment(GetPolicyAssignmentRequest) returns (GetPolicyAssignmentResponse) {} rpc AddPolicyAssignment(AddPolicyAssignmentRequest) returns (AddPolicyAssignmentResponse) {} rpc DeletePolicyAssignment(DeletePolicyAssignmentRequest) returns (DeletePolicyAssignmentResponse) {} rpc ReplacePolicyAssignment(ReplacePolicyAssignmentRequest) returns (ReplacePolicyAssignmentResponse) {} rpc GetRibInfo(GetRibInfoRequest) returns (GetRibInfoResponse) {} } // Constants for address families enum Family { _ = 0; IPv4 = 65537; IPv6 = 131073; IPv4_MC = 65538; IPv6_MC = 131074; IPv4_MPLS = 65540; IPv6_MPLS = 131076; IPv4_VPN = 65664; IPv6_VPN = 131200; IPv4_VPN_MC = 65665; IPv6_VPN_MC = 131201; VPLS = 1638465; EVPN = 1638470; RTC = 65668; IPv4_ENCAP = 65543; IPv6_ENCAP = 131079; FLOW_SPEC_IPv4 = 65669; FLOW_SPEC_IPv6 = 131205; FLOW_SPEC_IPv4_VPN = 65670; FLOW_SPEC_IPv6_VPN = 131206; FLOW_SPEC_L2_VPN = 1638534; OPAQUE = 1074594033; } message GetNeighborRequest { bool enableAdvertised = 1; string address = 2; } message GetNeighborResponse { repeated Peer peers = 1; } message Arguments { Resource resource = 1; uint32 family = 2; string name = 3; bool current = 4; } message AddPathRequest { Resource resource = 1; string vrf_id = 2; Path path = 3; } message AddPathResponse { bytes uuid = 1; } message DeletePathRequest { Resource resource = 1; string vrf_id = 2; uint32 family = 3; Path path = 4; bytes uuid = 5; } message DeletePathResponse { } message AddNeighborRequest { Peer peer = 1; } message AddNeighborResponse { } message DeleteNeighborRequest { Peer peer = 1; } message DeleteNeighborResponse { } message ResetNeighborRequest { string address = 1; string communication = 2; } message ResetNeighborResponse { } message SoftResetNeighborRequest { string address = 1; enum SoftResetDirection { IN = 0; OUT = 1; BOTH = 2; } SoftResetDirection direction = 2; } message SoftResetNeighborResponse { } message ShutdownNeighborRequest { string address = 1; string communication = 2; } message ShutdownNeighborResponse { } message EnableNeighborRequest { string address = 1; } message EnableNeighborResponse { } message DisableNeighborRequest { string address = 1; string communication = 2; } message DisableNeighborResponse { } message EnableMrtRequest { int32 dump_type = 1; string filename = 2; uint64 interval = 3; } message EnableMrtResponse { } message DisableMrtRequest { } message DisableMrtResponse { } message InjectMrtRequest { Resource resource = 1; string vrf_id = 2; repeated Path paths = 3; } message InjectMrtResponse { } message AddBmpRequest { string address = 1; uint32 port = 2; enum MonitoringPolicy { PRE = 0; POST = 1; BOTH = 2; LOCAL = 3; ALL = 4; } MonitoringPolicy type = 3; } message AddBmpResponse { } message DeleteBmpRequest { string address = 1; uint32 port = 2; } message DeleteBmpResponse { } message MonitorRibRequest { Table table = 1; bool current = 2; } message RPKIConf { string address = 1; string remote_port = 2; } message RPKIState { int64 uptime = 1; int64 downtime = 2; bool up = 3; uint32 record_ipv4 = 4; uint32 record_ipv6 = 5; uint32 prefix_ipv4 = 6; uint32 prefix_ipv6 = 7; uint32 serial = 8; int64 received_ipv4 = 9; int64 received_ipv6 = 10; int64 serial_notify = 11; int64 cache_reset = 12; int64 cache_response = 13; int64 end_of_data = 14; int64 error = 15; int64 serial_query = 16; int64 reset_query = 17; } message Rpki { RPKIConf conf = 1; RPKIState state = 2; } message GetRpkiRequest { uint32 family = 1; } message GetRpkiResponse { repeated Rpki servers = 1; } message AddRpkiRequest { string address = 1; uint32 port = 2; int64 lifetime = 3; } message AddRpkiResponse { } message DeleteRpkiRequest { string address = 1; uint32 port = 2; } message DeleteRpkiResponse { } message EnableRpkiRequest { string address = 1; } message EnableRpkiResponse { } message DisableRpkiRequest { string address = 1; } message DisableRpkiResponse { } message ResetRpkiRequest { string address = 1; } message ResetRpkiResponse { } message SoftResetRpkiRequest { string address = 1; } message SoftResetRpkiResponse { } message EnableZebraRequest { string url = 1; repeated string route_types = 2; uint32 version = 3; bool nexthop_trigger_enable = 4; uint32 nexthop_trigger_delay = 5; } message EnableZebraResponse { } message GetVrfRequest { } message GetVrfResponse { repeated Vrf vrfs = 1; } message AddVrfRequest { Vrf vrf = 1; } message AddVrfResponse { } message DeleteVrfRequest { Vrf vrf = 1; } message DeleteVrfResponse { } message GetDefinedSetRequest { DefinedType type = 1; string name = 2; } message GetDefinedSetResponse { repeated DefinedSet sets = 1; } message AddDefinedSetRequest { DefinedSet set = 1; } message AddDefinedSetResponse { } message DeleteDefinedSetRequest { DefinedSet set = 1; bool all = 2; } message DeleteDefinedSetResponse { } message ReplaceDefinedSetRequest { DefinedSet set = 1; } message ReplaceDefinedSetResponse { } message GetStatementRequest { } message GetStatementResponse { repeated Statement statements = 1; } message AddStatementRequest { Statement statement = 1; } message AddStatementResponse { } message DeleteStatementRequest { Statement statement = 1; bool all = 2; } message DeleteStatementResponse { } message ReplaceStatementRequest { Statement statement = 1; } message ReplaceStatementResponse { } message GetPolicyRequest { } message GetPolicyResponse { repeated Policy policies = 1; } message AddPolicyRequest { Policy policy = 1; // if this flag is set, gobgpd won't define new statements // but refer existing statements using statement's names in this arguments. bool refer_existing_statements = 2; } message AddPolicyResponse { } message DeletePolicyRequest { Policy policy = 1; // if this flag is set, gobgpd won't delete any statements // even if some statements get not used by any policy by this operation. bool preserve_statements = 2; bool all = 3; } message DeletePolicyResponse { } message ReplacePolicyRequest { Policy policy = 1; // if this flag is set, gobgpd won't define new statements // but refer existing statements using statement's names in this arguments. bool refer_existing_statements = 2; // if this flag is set, gobgpd won't delete any statements // even if some statements get not used by any policy by this operation. bool preserve_statements = 3; } message ReplacePolicyResponse { } message GetPolicyAssignmentRequest { PolicyAssignment assignment = 1; } message GetPolicyAssignmentResponse { PolicyAssignment assignment = 1; } message AddPolicyAssignmentRequest { PolicyAssignment assignment = 1; } message AddPolicyAssignmentResponse { } message DeletePolicyAssignmentRequest { PolicyAssignment assignment = 1; bool all = 2; } message DeletePolicyAssignmentResponse { } message ReplacePolicyAssignmentRequest { PolicyAssignment assignment = 1; } message ReplacePolicyAssignmentResponse { } message GetServerRequest { } message GetServerResponse { Global global = 1; } message StartServerRequest { Global global = 1; } message StartServerResponse { } message StopServerRequest { } message StopServerResponse { } enum Resource { GLOBAL = 0; LOCAL = 1; ADJ_IN = 2; ADJ_OUT = 3; VRF = 4; } message RPKIValidation{ enum State { STATE_NONE = 0; STATE_NOT_FOUND = 1; STATE_VALID = 2; STATE_INVALID = 3; } enum Reason { REASOT_NONE = 0; REASON_AS = 1; REASON_LENGTH = 2; } State state = 1; Reason reason = 2; repeated Roa matched = 3; repeated Roa unmatched_as = 4; repeated Roa unmatched_length = 5; } message Path { bytes nlri = 1; repeated bytes pattrs = 2; int64 age = 3; bool best = 4; bool is_withdraw = 5; int32 validation = 6; // remains for backword compatibility RPKIValidation validation_detail = 7; bool no_implicit_withdraw = 8; uint32 family = 9; uint32 source_asn = 10; string source_id = 11; bool filtered = 12; bool stale = 13; bool is_from_external = 14; string neighbor_ip = 15; bytes uuid = 16; // only paths installed by AddPath API have this bool is_nexthop_invalid = 17; uint32 identifier = 18; uint32 local_identifier = 19; } message Destination { string prefix = 1; repeated Path paths = 2; bool longer_prefixes = 3; bool shorter_prefixes = 4; } message Table { Resource type = 1; string name = 2; uint32 family = 3; repeated Destination destinations = 4; bool post_policy = 5; } message GetRibRequest { Table table = 1; } message GetRibResponse { Table table = 1; } // API representation of table.LookupOption enum TableLookupOption { LOOKUP_EXACT = 0; LOOKUP_LONGER = 1; LOOKUP_SHORTER = 2; } // API representation of table.LookupPrefix message TableLookupPrefix { string prefix = 1; TableLookupOption lookup_option = 2; } message GetPathRequest { Resource type = 1; string name = 2; uint32 family = 3; repeated TableLookupPrefix prefixes = 4; } message ValidateRibRequest { Resource type = 1; uint32 family = 2; string prefix = 3; } message ValidateRibResponse { } message Peer { // Note: Regarding to the consistency with OpenConfig model, a list of // address family should be removed from here, and should be configured with // the list of AfiSafi instead. repeated uint32 families = 1; ApplyPolicy apply_policy = 2; PeerConf conf = 3; EbgpMultihop ebgp_multihop = 4; RouteReflector route_reflector = 5; PeerState info = 6; Timers timers = 7; Transport transport = 8; RouteServer route_server = 9; GracefulRestart graceful_restart = 10; repeated AfiSafi afi_safis = 11; AddPaths add_paths = 12; } message ApplyPolicy { PolicyAssignment in_policy = 1; PolicyAssignment export_policy = 2; PolicyAssignment import_policy = 3; } message PrefixLimit { uint32 family = 1; uint32 max_prefixes = 2; uint32 shutdown_threshold_pct = 3; } message PeerConf { string auth_password = 1; string description = 2; uint32 local_as = 3; string neighbor_address = 4; uint32 peer_as = 5; string peer_group = 6; uint32 peer_type = 7; enum RemovePrivateAs { NONE = 0; ALL = 1; REPLACE = 2; } RemovePrivateAs remove_private_as = 8; bool route_flap_damping = 9; uint32 send_community = 10; repeated bytes remote_cap = 11; repeated bytes local_cap = 12; string id = 13; // Note: Regarding to the consistency with OpenConfig model, list of // PrefixLimit should be removed from here, and list of PrefixLimit in // AfiSafi should be used instead. repeated PrefixLimit prefix_limits = 14; string local_address = 15; string neighbor_interface = 16; string vrf = 17; uint32 allow_own_as = 18; bool replace_peer_as = 19; } message EbgpMultihop { bool enabled = 1; uint32 multihop_ttl = 2; } message RouteReflector { bool route_reflector_client = 1; string route_reflector_cluster_id = 2; } message PeerState { string auth_password = 1; string description = 2; uint32 local_as = 3; Messages messages = 4; string neighbor_address = 5; uint32 peer_as = 6; string peer_group = 7; uint32 peer_type = 8; Queues queues = 9; uint32 remove_private_as = 10; bool route_flap_damping = 11; uint32 send_community = 12; uint32 session_state = 13; repeated string supported_capabilities = 14; string bgp_state = 15; enum AdminState { UP = 0; DOWN = 1; PFX_CT = 2; // prefix counter over limit } AdminState admin_state = 16; uint32 received = 17; uint32 accepted = 18; uint32 advertised = 19; uint32 out_q = 20; uint32 flops = 21; } message Messages { Message received = 1; Message sent = 2; } message Message { uint64 NOTIFICATION = 1; uint64 UPDATE = 2; uint64 OPEN = 3; uint64 KEEPALIVE = 4; uint64 REFRESH = 5; uint64 DISCARDED = 6; uint64 TOTAL = 7; } message Queues { uint32 input = 1; uint32 output = 2; } message Timers { TimersConfig config =1; TimersState state = 2; } message TimersConfig{ uint64 connect_retry = 1; uint64 hold_time = 2; uint64 keepalive_interval = 3; uint64 minimum_advertisement_interval = 4; } message TimersState{ uint64 connect_retry = 1; uint64 hold_time = 2; uint64 keepalive_interval = 3; uint64 minimum_advertisement_interval = 4; uint64 negotiated_hold_time = 5; uint64 uptime = 6; uint64 downtime = 7; } message Transport { string local_address = 1; uint32 local_port = 2; bool mtu_discovery = 3; bool passive_mode = 4; string remote_address = 5; uint32 remote_port = 6; uint32 tcp_mss = 7; } message RouteServer { bool route_server_client = 1; } message GracefulRestart { bool enabled = 1; uint32 restart_time = 2; bool helper_only = 3; uint32 deferral_time = 4; bool notification_enabled = 5; bool longlived_enabled = 6; } message MpGracefulRestartConfig { bool enabled = 1; } message MpGracefulRestartState { bool enabled = 1; bool received = 2; bool advertised = 3; bool end_of_rib_received = 4; bool end_of_rib_sent = 5; } message MpGracefulRestart { MpGracefulRestartConfig config = 1; MpGracefulRestartState state = 2; } message AfiSafiConfig { uint32 family = 1; bool enabled = 2; } message AfiSafiState { uint32 family = 1; bool enabled = 2; uint32 total_paths = 3; uint32 total_prefixes = 4; } message RouteSelectionOptionsConfig { bool always_compare_med = 1; bool ignore_as_path_length = 2; bool external_compare_router_id = 3; bool advertise_inactive_routes = 4; bool enable_aigp = 5; bool ignore_next_hop_igp_metric = 6; } message RouteSelectionOptionsState { bool always_compare_med = 1; bool ignore_as_path_length = 2; bool external_compare_router_id = 3; bool advertise_inactive_routes = 4; bool enable_aigp = 5; bool ignore_next_hop_igp_metric = 6; } message RouteSelectionOptions { RouteSelectionOptionsConfig config = 1; RouteSelectionOptionsState state = 2; } message UseMultiplePathsConfig { bool enabled = 1; } message UseMultiplePathsState { bool enabled = 1; } message EbgpConfig { bool allow_multiple_as = 1; uint32 maximum_paths = 2; } message EbgpState { bool allow_multiple_as = 1; uint32 maximum_paths = 2; } message Ebgp { EbgpConfig config = 1; EbgpState state = 2; } message IbgpConfig { uint32 maximum_paths = 1; } message IbgpState { uint32 maximum_paths = 1; } message Ibgp { IbgpConfig config = 1; IbgpState state = 2; } message UseMultiplePaths { UseMultiplePathsConfig config = 1; UseMultiplePathsState state = 2; Ebgp ebgp = 3; Ibgp ibgp = 4; } message RouteTargetMembershipConfig { uint32 deferral_time = 1; } message RouteTargetMembershipState { uint32 deferral_time = 1; } message RouteTargetMembership { RouteTargetMembershipConfig config = 1; RouteTargetMembershipState state = 2; } message LongLivedGracefulRestartConfig { bool enabled = 1; uint32 restart_time = 2; } message LongLivedGracefulRestartState { bool enabled = 1; bool received = 2; bool advertised = 3; uint32 peer_restart_time = 4; bool peer_restart_timer_expired = 5; } message LongLivedGracefulRestart { LongLivedGracefulRestartConfig config = 1; LongLivedGracefulRestartState state = 2; } message AfiSafi { MpGracefulRestart mp_graceful_restart = 1; AfiSafiConfig config = 2; ApplyPolicy apply_policy = 3; // TODO: // Support the following structures: // - Ipv4Unicast // - Ipv6Unicast // - Ipv4LabelledUnicast // - Ipv6LabelledUnicast // - L3vpnIpv4Unicast // - L3vpnIpv6Unicast // - L3vpnIpv4Multicast // - L3vpnIpv6Multicast // - L2vpnVpls // - L2vpnEvpn RouteSelectionOptions route_selection_options = 4; UseMultiplePaths use_multiple_paths = 5; PrefixLimit prefix_limits = 6; RouteTargetMembership route_target_membership = 7; LongLivedGracefulRestart long_lived_graceful_restart = 8; AddPaths add_paths = 9; } message AddPathsConfig { bool receive = 1; uint32 send_max = 2; } message AddPathsState { bool receive = 1; uint32 send_max = 2; } message AddPaths { AddPathsConfig config = 1; AddPathsState state = 2; } message Prefix { string ip_prefix = 1; uint32 mask_length_min = 2; uint32 mask_length_max = 3; } enum DefinedType { PREFIX = 0; NEIGHBOR = 1; TAG = 2; AS_PATH = 3; COMMUNITY = 4; EXT_COMMUNITY = 5; LARGE_COMMUNITY = 6; } message DefinedSet { DefinedType type = 1; string name = 2; repeated string list = 3; repeated Prefix prefixes = 4; } enum MatchType { ANY = 0; ALL = 1; INVERT = 2; } message MatchSet { MatchType type = 1; string name = 2; } enum AsPathLengthType { EQ = 0; GE = 1; LE = 2; } message AsPathLength { AsPathLengthType type = 1; uint32 length = 2; } message Conditions { MatchSet prefix_set = 1; MatchSet neighbor_set = 2; AsPathLength as_path_length = 3; MatchSet as_path_set = 4; MatchSet community_set = 5; MatchSet ext_community_set = 6; int32 rpki_result = 7; enum RouteType { ROUTE_TYPE_NONE = 0; ROUTE_TYPE_INTERNAL = 1; ROUTE_TYPE_EXTERNAL = 2; ROUTE_TYPE_LOCAL = 3; } RouteType route_type = 8; MatchSet large_community_set = 9; } enum RouteAction { NONE = 0; ACCEPT = 1; REJECT = 2; } enum CommunityActionType { COMMUNITY_ADD = 0; COMMUNITY_REMOVE = 1; COMMUNITY_REPLACE = 2; } message CommunityAction { CommunityActionType type = 1; repeated string communities = 2; } enum MedActionType { MED_MOD = 0; MED_REPLACE = 1; } message MedAction { MedActionType type = 1; int64 value = 2; } message AsPrependAction { uint32 asn = 1; uint32 repeat = 2; bool use_left_most = 3; } message NexthopAction { string address = 1; bool self = 2; } message LocalPrefAction { uint32 value = 1; } message Actions { RouteAction route_action = 1; CommunityAction community = 2; MedAction med = 3; AsPrependAction as_prepend = 4; CommunityAction ext_community = 5; NexthopAction nexthop = 6; LocalPrefAction local_pref = 7; CommunityAction large_community = 8; } message Statement { string name = 1; Conditions conditions = 2; Actions actions = 3; } message Policy { string name = 1; repeated Statement statements = 2; } enum PolicyType { IN = 0; IMPORT = 1; EXPORT = 2; } message PolicyAssignment { PolicyType type = 1; Resource resource = 2; string name = 3; repeated Policy policies = 4; RouteAction default = 5; } message Roa { uint32 as = 1; uint32 prefixlen = 2; uint32 maxlen = 3; string prefix = 4; RPKIConf conf = 5; } message GetRoaRequest { uint32 family = 1; } message GetRoaResponse { repeated Roa roas = 1; } message Vrf { string name = 1; bytes rd = 2; repeated bytes import_rt = 3; repeated bytes export_rt = 4; uint32 id = 5; } message Global { uint32 as = 1; string router_id = 2; int32 listen_port = 3; repeated string listen_addresses = 4; repeated uint32 families = 5; bool use_multiple_paths = 6; } message TableInfo { Resource type = 1; string name = 2; uint32 family = 3; uint64 num_destination = 4; uint64 num_path = 5; uint64 num_accepted = 6; // only meaningful when type == ADJ_IN } message GetRibInfoRequest { TableInfo info = 1; } message GetRibInfoResponse { TableInfo info = 1; } gobgp-1.29/api/grpc_server.go000066400000000000000000002230021324612745600161750ustar00rootroot00000000000000// Copyright (C) 2014-2016 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package gobgpapi import ( "bytes" "fmt" "io" "net" "reflect" "regexp" "strconv" "strings" "sync" "time" farm "github.com/dgryski/go-farm" log "github.com/sirupsen/logrus" "golang.org/x/net/context" "google.golang.org/grpc" "github.com/osrg/gobgp/config" "github.com/osrg/gobgp/packet/bgp" "github.com/osrg/gobgp/server" "github.com/osrg/gobgp/table" ) type Server struct { bgpServer *server.BgpServer grpcServer *grpc.Server hosts string } func NewGrpcServer(b *server.BgpServer, hosts string) *Server { size := 256 << 20 return NewServer(b, grpc.NewServer(grpc.MaxRecvMsgSize(size), grpc.MaxSendMsgSize(size)), hosts) } func NewServer(b *server.BgpServer, g *grpc.Server, hosts string) *Server { grpc.EnableTracing = false s := &Server{ bgpServer: b, grpcServer: g, hosts: hosts, } RegisterGobgpApiServer(g, s) return s } func (s *Server) Serve() error { var wg sync.WaitGroup l := strings.Split(s.hosts, ",") wg.Add(len(l)) serve := func(host string) { defer wg.Done() lis, err := net.Listen("tcp", fmt.Sprintf(host)) if err != nil { log.WithFields(log.Fields{ "Topic": "grpc", "Key": host, "Error": err, }).Warn("listen failed") return } err = s.grpcServer.Serve(lis) log.WithFields(log.Fields{ "Topic": "grpc", "Key": host, "Error": err, }).Warn("accept failed") } for _, host := range l { go serve(host) } wg.Wait() return nil } func NewMpGracefulRestartFromConfigStruct(c *config.MpGracefulRestart) *MpGracefulRestart { return &MpGracefulRestart{ Config: &MpGracefulRestartConfig{ Enabled: c.Config.Enabled, }, } } func NewAfiSafiConfigFromConfigStruct(c *config.AfiSafi) *AfiSafiConfig { return &AfiSafiConfig{ Family: extractFamilyFromConfigAfiSafi(c), Enabled: c.Config.Enabled, } } func NewApplyPolicyFromConfigStruct(c *config.ApplyPolicy) *ApplyPolicy { applyPolicy := &ApplyPolicy{ ImportPolicy: &PolicyAssignment{ Type: PolicyType_IMPORT, Default: RouteAction(c.Config.DefaultImportPolicy.ToInt()), }, ExportPolicy: &PolicyAssignment{ Type: PolicyType_EXPORT, Default: RouteAction(c.Config.DefaultExportPolicy.ToInt()), }, InPolicy: &PolicyAssignment{ Type: PolicyType_IN, Default: RouteAction(c.Config.DefaultInPolicy.ToInt()), }, } for _, pname := range c.Config.ImportPolicyList { applyPolicy.ImportPolicy.Policies = append(applyPolicy.ImportPolicy.Policies, &Policy{Name: pname}) } for _, pname := range c.Config.ExportPolicyList { applyPolicy.ExportPolicy.Policies = append(applyPolicy.ExportPolicy.Policies, &Policy{Name: pname}) } for _, pname := range c.Config.InPolicyList { applyPolicy.InPolicy.Policies = append(applyPolicy.InPolicy.Policies, &Policy{Name: pname}) } return applyPolicy } func NewRouteSelectionOptionsFromConfigStruct(c *config.RouteSelectionOptions) *RouteSelectionOptions { return &RouteSelectionOptions{ Config: &RouteSelectionOptionsConfig{ AlwaysCompareMed: c.Config.AlwaysCompareMed, IgnoreAsPathLength: c.Config.IgnoreAsPathLength, ExternalCompareRouterId: c.Config.ExternalCompareRouterId, AdvertiseInactiveRoutes: c.Config.AdvertiseInactiveRoutes, EnableAigp: c.Config.EnableAigp, IgnoreNextHopIgpMetric: c.Config.IgnoreNextHopIgpMetric, }, } } func NewUseMultiplePathsFromConfigStruct(c *config.UseMultiplePaths) *UseMultiplePaths { return &UseMultiplePaths{ Config: &UseMultiplePathsConfig{ Enabled: c.Config.Enabled, }, Ebgp: &Ebgp{ Config: &EbgpConfig{ AllowMultipleAs: c.Ebgp.Config.AllowMultipleAs, MaximumPaths: c.Ebgp.Config.MaximumPaths, }, }, Ibgp: &Ibgp{ Config: &IbgpConfig{ MaximumPaths: c.Ibgp.Config.MaximumPaths, }, }, } } func NewPrefixLimitFromConfigStruct(c *config.AfiSafi) *PrefixLimit { if c.PrefixLimit.Config.MaxPrefixes == 0 { return nil } return &PrefixLimit{ Family: uint32(c.State.Family), MaxPrefixes: c.PrefixLimit.Config.MaxPrefixes, ShutdownThresholdPct: uint32(c.PrefixLimit.Config.ShutdownThresholdPct), } } func NewRouteTargetMembershipFromConfigStruct(c *config.RouteTargetMembership) *RouteTargetMembership { return &RouteTargetMembership{ Config: &RouteTargetMembershipConfig{ DeferralTime: uint32(c.Config.DeferralTime), }, } } func NewLongLivedGracefulRestartFromConfigStruct(c *config.LongLivedGracefulRestart) *LongLivedGracefulRestart { return &LongLivedGracefulRestart{ Config: &LongLivedGracefulRestartConfig{ Enabled: c.Config.Enabled, RestartTime: c.Config.RestartTime, }, } } func NewAddPathsFromConfigStruct(c *config.AddPaths) *AddPaths { return &AddPaths{ Config: &AddPathsConfig{ Receive: c.Config.Receive, SendMax: uint32(c.Config.SendMax), }, } } func NewAfiSafiFromConfigStruct(c *config.AfiSafi) *AfiSafi { return &AfiSafi{ MpGracefulRestart: NewMpGracefulRestartFromConfigStruct(&c.MpGracefulRestart), Config: NewAfiSafiConfigFromConfigStruct(c), ApplyPolicy: NewApplyPolicyFromConfigStruct(&c.ApplyPolicy), RouteSelectionOptions: NewRouteSelectionOptionsFromConfigStruct(&c.RouteSelectionOptions), UseMultiplePaths: NewUseMultiplePathsFromConfigStruct(&c.UseMultiplePaths), PrefixLimits: NewPrefixLimitFromConfigStruct(c), RouteTargetMembership: NewRouteTargetMembershipFromConfigStruct(&c.RouteTargetMembership), LongLivedGracefulRestart: NewLongLivedGracefulRestartFromConfigStruct(&c.LongLivedGracefulRestart), AddPaths: NewAddPathsFromConfigStruct(&c.AddPaths), } } func NewPeerFromConfigStruct(pconf *config.Neighbor) *Peer { families := make([]uint32, 0, len(pconf.AfiSafis)) prefixLimits := make([]*PrefixLimit, 0, len(pconf.AfiSafis)) afiSafis := make([]*AfiSafi, 0, len(pconf.AfiSafis)) for _, f := range pconf.AfiSafis { families = append(families, extractFamilyFromConfigAfiSafi(&f)) if prefixLimit := NewPrefixLimitFromConfigStruct(&f); prefixLimit != nil { prefixLimits = append(prefixLimits, prefixLimit) } if afiSafi := NewAfiSafiFromConfigStruct(&f); afiSafi != nil { afiSafis = append(afiSafis, afiSafi) } } timer := pconf.Timers s := pconf.State localAddress := pconf.Transport.Config.LocalAddress if pconf.Transport.State.LocalAddress != "" { localAddress = pconf.Transport.State.LocalAddress } var remoteCap, localCap [][]byte for _, c := range pconf.State.RemoteCapabilityList { cBuf, _ := c.Serialize() remoteCap = append(remoteCap, cBuf) } for _, c := range pconf.State.LocalCapabilityList { cBuf, _ := c.Serialize() localCap = append(localCap, cBuf) } var removePrivateAs PeerConf_RemovePrivateAs switch pconf.Config.RemovePrivateAs { case config.REMOVE_PRIVATE_AS_OPTION_ALL: removePrivateAs = PeerConf_ALL case config.REMOVE_PRIVATE_AS_OPTION_REPLACE: removePrivateAs = PeerConf_REPLACE } return &Peer{ Families: families, ApplyPolicy: NewApplyPolicyFromConfigStruct(&pconf.ApplyPolicy), Conf: &PeerConf{ NeighborAddress: pconf.Config.NeighborAddress, Id: s.RemoteRouterId, PeerAs: pconf.Config.PeerAs, LocalAs: pconf.Config.LocalAs, PeerType: uint32(pconf.Config.PeerType.ToInt()), AuthPassword: pconf.Config.AuthPassword, RouteFlapDamping: pconf.Config.RouteFlapDamping, Description: pconf.Config.Description, PeerGroup: pconf.Config.PeerGroup, RemoteCap: remoteCap, LocalCap: localCap, PrefixLimits: prefixLimits, LocalAddress: localAddress, NeighborInterface: pconf.Config.NeighborInterface, Vrf: pconf.Config.Vrf, AllowOwnAs: uint32(pconf.AsPathOptions.Config.AllowOwnAs), RemovePrivateAs: removePrivateAs, ReplacePeerAs: pconf.AsPathOptions.Config.ReplacePeerAs, }, Info: &PeerState{ BgpState: string(s.SessionState), AdminState: PeerState_AdminState(s.AdminState.ToInt()), Messages: &Messages{ Received: &Message{ NOTIFICATION: s.Messages.Received.Notification, UPDATE: s.Messages.Received.Update, OPEN: s.Messages.Received.Open, KEEPALIVE: s.Messages.Received.Keepalive, REFRESH: s.Messages.Received.Refresh, DISCARDED: s.Messages.Received.Discarded, TOTAL: s.Messages.Received.Total, }, Sent: &Message{ NOTIFICATION: s.Messages.Sent.Notification, UPDATE: s.Messages.Sent.Update, OPEN: s.Messages.Sent.Open, KEEPALIVE: s.Messages.Sent.Keepalive, REFRESH: s.Messages.Sent.Refresh, DISCARDED: s.Messages.Sent.Discarded, TOTAL: s.Messages.Sent.Total, }, }, Received: s.AdjTable.Received, Accepted: s.AdjTable.Accepted, Advertised: s.AdjTable.Advertised, PeerAs: s.PeerAs, PeerType: uint32(s.PeerType.ToInt()), NeighborAddress: pconf.State.NeighborAddress, }, Timers: &Timers{ Config: &TimersConfig{ ConnectRetry: uint64(timer.Config.ConnectRetry), HoldTime: uint64(timer.Config.HoldTime), KeepaliveInterval: uint64(timer.Config.KeepaliveInterval), }, State: &TimersState{ KeepaliveInterval: uint64(timer.State.KeepaliveInterval), NegotiatedHoldTime: uint64(timer.State.NegotiatedHoldTime), Uptime: uint64(timer.State.Uptime), Downtime: uint64(timer.State.Downtime), }, }, RouteReflector: &RouteReflector{ RouteReflectorClient: pconf.RouteReflector.Config.RouteReflectorClient, RouteReflectorClusterId: string(pconf.RouteReflector.Config.RouteReflectorClusterId), }, RouteServer: &RouteServer{ RouteServerClient: pconf.RouteServer.Config.RouteServerClient, }, GracefulRestart: &GracefulRestart{ Enabled: pconf.GracefulRestart.Config.Enabled, RestartTime: uint32(pconf.GracefulRestart.Config.RestartTime), HelperOnly: pconf.GracefulRestart.Config.HelperOnly, DeferralTime: uint32(pconf.GracefulRestart.Config.DeferralTime), NotificationEnabled: pconf.GracefulRestart.Config.NotificationEnabled, LonglivedEnabled: pconf.GracefulRestart.Config.LongLivedEnabled, }, Transport: &Transport{ RemotePort: uint32(pconf.Transport.Config.RemotePort), LocalAddress: pconf.Transport.Config.LocalAddress, }, AfiSafis: afiSafis, AddPaths: NewAddPathsFromConfigStruct(&pconf.AddPaths), } } func (s *Server) GetNeighbor(ctx context.Context, arg *GetNeighborRequest) (*GetNeighborResponse, error) { if arg == nil { return nil, fmt.Errorf("invalid request") } neighbors := s.bgpServer.GetNeighbor(arg.Address, arg.EnableAdvertised) peers := make([]*Peer, 0, len(neighbors)) for _, e := range neighbors { peers = append(peers, NewPeerFromConfigStruct(e)) } return &GetNeighborResponse{Peers: peers}, nil } func NewValidationFromTableStruct(v *table.Validation) *RPKIValidation { if v == nil { return &RPKIValidation{} } return &RPKIValidation{ Reason: RPKIValidation_Reason(v.Reason.ToInt()), Matched: NewRoaListFromTableStructList(v.Matched), UnmatchedAs: NewRoaListFromTableStructList(v.UnmatchedAs), UnmatchedLength: NewRoaListFromTableStructList(v.UnmatchedLength), } } func ToPathApi(path *table.Path) *Path { nlri := path.GetNlri() n, _ := nlri.Serialize() family := uint32(bgp.AfiSafiToRouteFamily(nlri.AFI(), nlri.SAFI())) pattrs := func(arg []bgp.PathAttributeInterface) [][]byte { ret := make([][]byte, 0, len(arg)) for _, a := range arg { aa, _ := a.Serialize() ret = append(ret, aa) } return ret }(path.GetPathAttrs()) p := &Path{ Nlri: n, Pattrs: pattrs, Age: path.GetTimestamp().Unix(), IsWithdraw: path.IsWithdraw, Validation: int32(path.ValidationStatus().ToInt()), ValidationDetail: NewValidationFromTableStruct(path.Validation()), Filtered: path.Filtered("") == table.POLICY_DIRECTION_IN, Family: family, Stale: path.IsStale(), IsFromExternal: path.IsFromExternal(), NoImplicitWithdraw: path.NoImplicitWithdraw(), Uuid: path.UUID().Bytes(), IsNexthopInvalid: path.IsNexthopInvalid, Identifier: nlri.PathIdentifier(), LocalIdentifier: nlri.PathLocalIdentifier(), } if s := path.GetSource(); s != nil { p.SourceAsn = s.AS p.SourceId = s.ID.String() p.NeighborIp = s.Address.String() } return p } func (s *Server) GetRib(ctx context.Context, arg *GetRibRequest) (*GetRibResponse, error) { if arg == nil || arg.Table == nil { return nil, fmt.Errorf("invalid request") } f := func() []*table.LookupPrefix { l := make([]*table.LookupPrefix, 0, len(arg.Table.Destinations)) for _, p := range arg.Table.Destinations { l = append(l, &table.LookupPrefix{ Prefix: p.Prefix, LookupOption: func() table.LookupOption { if p.LongerPrefixes { return table.LOOKUP_LONGER } else if p.ShorterPrefixes { return table.LOOKUP_SHORTER } return table.LOOKUP_EXACT }(), }) } return l } var in bool var err error var tbl *table.Table family := bgp.RouteFamily(arg.Table.Family) switch arg.Table.Type { case Resource_LOCAL, Resource_GLOBAL: tbl, err = s.bgpServer.GetRib(arg.Table.Name, family, f()) case Resource_ADJ_IN: in = true fallthrough case Resource_ADJ_OUT: tbl, err = s.bgpServer.GetAdjRib(arg.Table.Name, family, in, f()) case Resource_VRF: tbl, err = s.bgpServer.GetVrfRib(arg.Table.Name, family, []*table.LookupPrefix{}) default: return nil, fmt.Errorf("unsupported resource type: %v", arg.Table.Type) } if err != nil { return nil, err } tblDsts := tbl.GetDestinations() dsts := make([]*Destination, 0, len(tblDsts)) for _, dst := range tblDsts { dsts = append(dsts, &Destination{ Prefix: dst.GetNlri().String(), Paths: func(paths []*table.Path) []*Path { l := make([]*Path, 0, len(paths)) for i, p := range paths { pp := ToPathApi(p) switch arg.Table.Type { case Resource_LOCAL, Resource_GLOBAL: if i == 0 && !table.SelectionOptions.DisableBestPathSelection { pp.Best = true } } l = append(l, pp) } return l }(dst.GetAllKnownPathList()), }) } return &GetRibResponse{Table: &Table{ Type: arg.Table.Type, Family: uint32(tbl.GetRoutefamily()), Destinations: dsts}, }, err } func (s *Server) GetPath(arg *GetPathRequest, stream GobgpApi_GetPathServer) error { f := func() []*table.LookupPrefix { l := make([]*table.LookupPrefix, 0, len(arg.Prefixes)) for _, p := range arg.Prefixes { l = append(l, &table.LookupPrefix{ Prefix: p.Prefix, LookupOption: table.LookupOption(p.LookupOption), }) } return l } in := false family := bgp.RouteFamily(arg.Family) var tbl *table.Table var err error switch arg.Type { case Resource_LOCAL, Resource_GLOBAL: tbl, err = s.bgpServer.GetRib(arg.Name, family, f()) case Resource_ADJ_IN: in = true fallthrough case Resource_ADJ_OUT: tbl, err = s.bgpServer.GetAdjRib(arg.Name, family, in, f()) case Resource_VRF: tbl, err = s.bgpServer.GetVrfRib(arg.Name, family, []*table.LookupPrefix{}) default: return fmt.Errorf("unsupported resource type: %v", arg.Type) } if err != nil { return err } return func() error { for _, dst := range tbl.GetDestinations() { for idx, path := range dst.GetAllKnownPathList() { p := ToPathApi(path) if idx == 0 && !table.SelectionOptions.DisableBestPathSelection { switch arg.Type { case Resource_LOCAL, Resource_GLOBAL: p.Best = true } } if err := stream.Send(p); err != nil { return err } } } return nil }() } func (s *Server) MonitorRib(arg *MonitorRibRequest, stream GobgpApi_MonitorRibServer) error { if arg == nil || arg.Table == nil { return fmt.Errorf("invalid request") } t := arg.Table w, err := func() (*server.Watcher, error) { switch t.Type { case Resource_GLOBAL: return s.bgpServer.Watch(server.WatchBestPath(arg.Current)), nil case Resource_ADJ_IN: if t.PostPolicy { return s.bgpServer.Watch(server.WatchPostUpdate(arg.Current)), nil } return s.bgpServer.Watch(server.WatchUpdate(arg.Current)), nil default: return nil, fmt.Errorf("unsupported resource type: %v", t.Type) } }() if err != nil { return nil } return func() error { defer func() { w.Stop() }() sendPath := func(pathList []*table.Path) error { dsts := make(map[string]*Destination) for _, path := range pathList { if path == nil || (t.Family != 0 && bgp.RouteFamily(t.Family) != path.GetRouteFamily()) { continue } if dst, y := dsts[path.GetNlri().String()]; y { dst.Paths = append(dst.Paths, ToPathApi(path)) } else { dsts[path.GetNlri().String()] = &Destination{ Prefix: path.GetNlri().String(), Paths: []*Path{ToPathApi(path)}, } } } for _, dst := range dsts { if err := stream.Send(dst); err != nil { return err } } return nil } for { select { case ev := <-w.Event(): switch msg := ev.(type) { case *server.WatchEventBestPath: if err := sendPath(func() []*table.Path { if len(msg.MultiPathList) > 0 { l := make([]*table.Path, 0) for _, p := range msg.MultiPathList { l = append(l, p...) } return l } else { return msg.PathList } }()); err != nil { return err } case *server.WatchEventUpdate: if err := sendPath(msg.PathList); err != nil { return err } } } } }() } func (s *Server) MonitorPeerState(arg *Arguments, stream GobgpApi_MonitorPeerStateServer) error { if arg == nil { return fmt.Errorf("invalid request") } return func() error { w := s.bgpServer.Watch(server.WatchPeerState(arg.Current)) defer func() { w.Stop() }() for { select { case ev := <-w.Event(): switch msg := ev.(type) { case *server.WatchEventPeerState: if len(arg.Name) > 0 && arg.Name != msg.PeerAddress.String() && arg.Name != msg.PeerInterface { continue } if err := stream.Send(&Peer{ Conf: &PeerConf{ PeerAs: msg.PeerAS, LocalAs: msg.LocalAS, NeighborAddress: msg.PeerAddress.String(), Id: msg.PeerID.String(), NeighborInterface: msg.PeerInterface, }, Info: &PeerState{ PeerAs: msg.PeerAS, LocalAs: msg.LocalAS, NeighborAddress: msg.PeerAddress.String(), BgpState: msg.State.String(), AdminState: PeerState_AdminState(msg.AdminState), }, Transport: &Transport{ LocalAddress: msg.LocalAddress.String(), LocalPort: uint32(msg.LocalPort), RemotePort: uint32(msg.PeerPort), }, }); err != nil { return err } } } } }() } func (s *Server) ResetNeighbor(ctx context.Context, arg *ResetNeighborRequest) (*ResetNeighborResponse, error) { return &ResetNeighborResponse{}, s.bgpServer.ResetNeighbor(arg.Address, arg.Communication) } func (s *Server) SoftResetNeighbor(ctx context.Context, arg *SoftResetNeighborRequest) (*SoftResetNeighborResponse, error) { var err error addr := arg.Address if addr == "all" { addr = "" } family := bgp.RouteFamily(0) switch arg.Direction { case SoftResetNeighborRequest_IN: err = s.bgpServer.SoftResetIn(addr, family) case SoftResetNeighborRequest_OUT: err = s.bgpServer.SoftResetOut(addr, family) default: err = s.bgpServer.SoftReset(addr, family) } return &SoftResetNeighborResponse{}, err } func (s *Server) ShutdownNeighbor(ctx context.Context, arg *ShutdownNeighborRequest) (*ShutdownNeighborResponse, error) { return &ShutdownNeighborResponse{}, s.bgpServer.ShutdownNeighbor(arg.Address, arg.Communication) } func (s *Server) EnableNeighbor(ctx context.Context, arg *EnableNeighborRequest) (*EnableNeighborResponse, error) { return &EnableNeighborResponse{}, s.bgpServer.EnableNeighbor(arg.Address) } func (s *Server) DisableNeighbor(ctx context.Context, arg *DisableNeighborRequest) (*DisableNeighborResponse, error) { return &DisableNeighborResponse{}, s.bgpServer.DisableNeighbor(arg.Address, arg.Communication) } func (s *Server) api2PathList(resource Resource, ApiPathList []*Path) ([]*table.Path, error) { var nlri bgp.AddrPrefixInterface var nexthop string var pi *table.PeerInfo var err error pathList := make([]*table.Path, 0, len(ApiPathList)) for _, path := range ApiPathList { seen := make(map[bgp.BGPAttrType]bool) pattr := make([]bgp.PathAttributeInterface, 0) extcomms := make([]bgp.ExtendedCommunityInterface, 0) if path.SourceAsn != 0 { pi = &table.PeerInfo{ AS: path.SourceAsn, LocalID: net.ParseIP(path.SourceId), } } if len(path.Nlri) > 0 { afi, safi := bgp.RouteFamilyToAfiSafi(bgp.RouteFamily(path.Family)) if nlri, err = bgp.NewPrefixFromRouteFamily(afi, safi); err != nil { return nil, err } err := nlri.DecodeFromBytes(path.Nlri) if err != nil { return nil, err } nlri.SetPathIdentifier(path.Identifier) } for _, attr := range path.Pattrs { p, err := bgp.GetPathAttribute(attr) if err != nil { return nil, err } err = p.DecodeFromBytes(attr) if err != nil { return nil, err } if _, ok := seen[p.GetType()]; !ok { seen[p.GetType()] = true } else { return nil, fmt.Errorf("the path attribute appears twice. Type : " + strconv.Itoa(int(p.GetType()))) } switch p.GetType() { case bgp.BGP_ATTR_TYPE_NEXT_HOP: nexthop = p.(*bgp.PathAttributeNextHop).Value.String() case bgp.BGP_ATTR_TYPE_EXTENDED_COMMUNITIES: value := p.(*bgp.PathAttributeExtendedCommunities).Value if len(value) > 0 { extcomms = append(extcomms, value...) } case bgp.BGP_ATTR_TYPE_MP_REACH_NLRI: mpreach := p.(*bgp.PathAttributeMpReachNLRI) if len(mpreach.Value) != 1 { return nil, fmt.Errorf("include only one route in mp_reach_nlri") } nlri = mpreach.Value[0] nexthop = mpreach.Nexthop.String() default: pattr = append(pattr, p) } } if nlri == nil || (!path.IsWithdraw && nexthop == "") { return nil, fmt.Errorf("not found nlri or nexthop") } if resource != Resource_VRF && bgp.RouteFamily(path.Family) == bgp.RF_IPv4_UC && net.ParseIP(nexthop).To4() != nil { pattr = append(pattr, bgp.NewPathAttributeNextHop(nexthop)) } else { pattr = append(pattr, bgp.NewPathAttributeMpReachNLRI(nexthop, []bgp.AddrPrefixInterface{nlri})) } if len(extcomms) > 0 { pattr = append(pattr, bgp.NewPathAttributeExtendedCommunities(extcomms)) } newPath := table.NewPath(pi, nlri, path.IsWithdraw, pattr, time.Now(), path.NoImplicitWithdraw) if path.IsWithdraw == false { total := bytes.NewBuffer(make([]byte, 0)) for _, a := range newPath.GetPathAttrs() { if a.GetType() == bgp.BGP_ATTR_TYPE_MP_REACH_NLRI { continue } b, _ := a.Serialize() total.Write(b) } newPath.SetHash(farm.Hash32(total.Bytes())) } newPath.SetIsFromExternal(path.IsFromExternal) pathList = append(pathList, newPath) } return pathList, nil } func (s *Server) AddPath(ctx context.Context, arg *AddPathRequest) (*AddPathResponse, error) { pathList, err := s.api2PathList(arg.Resource, []*Path{arg.Path}) var uuid []byte if err == nil { uuid, err = s.bgpServer.AddPath(arg.VrfId, pathList) } return &AddPathResponse{Uuid: uuid}, err } func (s *Server) DeletePath(ctx context.Context, arg *DeletePathRequest) (*DeletePathResponse, error) { pathList, err := func() ([]*table.Path, error) { if arg.Path != nil { arg.Path.IsWithdraw = true return s.api2PathList(arg.Resource, []*Path{arg.Path}) } return []*table.Path{}, nil }() if err != nil { return nil, err } return &DeletePathResponse{}, s.bgpServer.DeletePath(arg.Uuid, bgp.RouteFamily(arg.Family), arg.VrfId, pathList) } func (s *Server) EnableMrt(ctx context.Context, arg *EnableMrtRequest) (*EnableMrtResponse, error) { return &EnableMrtResponse{}, s.bgpServer.EnableMrt(&config.MrtConfig{ RotationInterval: arg.Interval, DumpType: config.IntToMrtTypeMap[int(arg.DumpType)], FileName: arg.Filename, }) } func (s *Server) DisableMrt(ctx context.Context, arg *DisableMrtRequest) (*DisableMrtResponse, error) { return &DisableMrtResponse{}, s.bgpServer.DisableMrt(&config.MrtConfig{}) } func (s *Server) InjectMrt(stream GobgpApi_InjectMrtServer) error { for { arg, err := stream.Recv() if err == io.EOF { break } else if err != nil { return err } if arg.Resource != Resource_GLOBAL && arg.Resource != Resource_VRF { return fmt.Errorf("unsupported resource: %s", arg.Resource) } if pathList, err := s.api2PathList(arg.Resource, arg.Paths); err != nil { return err } else { if _, err = s.bgpServer.AddPath("", pathList); err != nil { return err } } } return stream.SendAndClose(&InjectMrtResponse{}) } func (s *Server) AddBmp(ctx context.Context, arg *AddBmpRequest) (*AddBmpResponse, error) { t, ok := config.IntToBmpRouteMonitoringPolicyTypeMap[int(arg.Type)] if !ok { return nil, fmt.Errorf("invalid bmp route monitoring policy: %d", arg.Type) } return &AddBmpResponse{}, s.bgpServer.AddBmp(&config.BmpServerConfig{ Address: arg.Address, Port: arg.Port, RouteMonitoringPolicy: t, }) } func (s *Server) DeleteBmp(ctx context.Context, arg *DeleteBmpRequest) (*DeleteBmpResponse, error) { return &DeleteBmpResponse{}, s.bgpServer.DeleteBmp(&config.BmpServerConfig{ Address: arg.Address, Port: arg.Port, }) } func (s *Server) ValidateRib(ctx context.Context, arg *ValidateRibRequest) (*ValidateRibResponse, error) { return &ValidateRibResponse{}, s.bgpServer.ValidateRib(arg.Prefix) } func (s *Server) AddRpki(ctx context.Context, arg *AddRpkiRequest) (*AddRpkiResponse, error) { return &AddRpkiResponse{}, s.bgpServer.AddRpki(&config.RpkiServerConfig{ Address: arg.Address, Port: arg.Port, RecordLifetime: arg.Lifetime, }) } func (s *Server) DeleteRpki(ctx context.Context, arg *DeleteRpkiRequest) (*DeleteRpkiResponse, error) { return &DeleteRpkiResponse{}, s.bgpServer.DeleteRpki(&config.RpkiServerConfig{ Address: arg.Address, Port: arg.Port, }) } func (s *Server) EnableRpki(ctx context.Context, arg *EnableRpkiRequest) (*EnableRpkiResponse, error) { return &EnableRpkiResponse{}, s.bgpServer.EnableRpki(&config.RpkiServerConfig{ Address: arg.Address, }) } func (s *Server) DisableRpki(ctx context.Context, arg *DisableRpkiRequest) (*DisableRpkiResponse, error) { return &DisableRpkiResponse{}, s.bgpServer.DisableRpki(&config.RpkiServerConfig{ Address: arg.Address, }) } func (s *Server) ResetRpki(ctx context.Context, arg *ResetRpkiRequest) (*ResetRpkiResponse, error) { return &ResetRpkiResponse{}, s.bgpServer.ResetRpki(&config.RpkiServerConfig{ Address: arg.Address, }) } func (s *Server) SoftResetRpki(ctx context.Context, arg *SoftResetRpkiRequest) (*SoftResetRpkiResponse, error) { return &SoftResetRpkiResponse{}, s.bgpServer.SoftResetRpki(&config.RpkiServerConfig{ Address: arg.Address, }) } func (s *Server) GetRpki(ctx context.Context, arg *GetRpkiRequest) (*GetRpkiResponse, error) { servers, err := s.bgpServer.GetRpki() if err != nil { return nil, err } l := make([]*Rpki, 0, len(servers)) for _, s := range servers { received := &s.State.RpkiMessages.RpkiReceived sent := &s.State.RpkiMessages.RpkiSent rpki := &Rpki{ Conf: &RPKIConf{ Address: s.Config.Address, RemotePort: strconv.Itoa(int(s.Config.Port)), }, State: &RPKIState{ Uptime: s.State.Uptime, Downtime: s.State.Downtime, Up: s.State.Up, RecordIpv4: s.State.RecordsV4, RecordIpv6: s.State.RecordsV6, PrefixIpv4: s.State.PrefixesV4, PrefixIpv6: s.State.PrefixesV6, Serial: s.State.SerialNumber, ReceivedIpv4: received.Ipv4Prefix, ReceivedIpv6: received.Ipv6Prefix, SerialNotify: received.SerialNotify, CacheReset: received.CacheReset, CacheResponse: received.CacheResponse, EndOfData: received.EndOfData, Error: received.Error, SerialQuery: sent.SerialQuery, ResetQuery: sent.ResetQuery, }, } l = append(l, rpki) } return &GetRpkiResponse{Servers: l}, nil } func (s *Server) GetRoa(ctx context.Context, arg *GetRoaRequest) (*GetRoaResponse, error) { roas, err := s.bgpServer.GetRoa(bgp.RouteFamily(arg.Family)) if err != nil { return nil, err } return &GetRoaResponse{Roas: NewRoaListFromTableStructList(roas)}, nil } func (s *Server) EnableZebra(ctx context.Context, arg *EnableZebraRequest) (*EnableZebraResponse, error) { l := make([]config.InstallProtocolType, 0, len(arg.RouteTypes)) for _, p := range arg.RouteTypes { if err := config.InstallProtocolType(p).Validate(); err != nil { return &EnableZebraResponse{}, err } else { l = append(l, config.InstallProtocolType(p)) } } return &EnableZebraResponse{}, s.bgpServer.StartZebraClient(&config.ZebraConfig{ Url: arg.Url, RedistributeRouteTypeList: l, Version: uint8(arg.Version), NexthopTriggerEnable: arg.NexthopTriggerEnable, NexthopTriggerDelay: uint8(arg.NexthopTriggerDelay), }) } func (s *Server) GetVrf(ctx context.Context, arg *GetVrfRequest) (*GetVrfResponse, error) { toApi := func(v *table.Vrf) *Vrf { f := func(rts []bgp.ExtendedCommunityInterface) [][]byte { ret := make([][]byte, 0, len(rts)) for _, rt := range rts { b, _ := rt.Serialize() ret = append(ret, b) } return ret } rd, _ := v.Rd.Serialize() return &Vrf{ Name: v.Name, Rd: rd, Id: v.Id, ImportRt: f(v.ImportRt), ExportRt: f(v.ExportRt), } } vrfs := s.bgpServer.GetVrf() l := make([]*Vrf, 0, len(vrfs)) for _, v := range vrfs { l = append(l, toApi(v)) } return &GetVrfResponse{Vrfs: l}, nil } func (s *Server) AddVrf(ctx context.Context, arg *AddVrfRequest) (r *AddVrfResponse, err error) { if arg == nil || arg.Vrf == nil { return nil, fmt.Errorf("invalid request") } rd := bgp.GetRouteDistinguisher(arg.Vrf.Rd) f := func(bufs [][]byte) ([]bgp.ExtendedCommunityInterface, error) { ret := make([]bgp.ExtendedCommunityInterface, 0, len(bufs)) for _, rt := range bufs { r, err := bgp.ParseExtended(rt) if err != nil { return nil, err } ret = append(ret, r) } return ret, nil } im, err := f(arg.Vrf.ImportRt) if err != nil { return &AddVrfResponse{}, err } ex, err := f(arg.Vrf.ExportRt) if err != nil { return &AddVrfResponse{}, err } return &AddVrfResponse{}, s.bgpServer.AddVrf(arg.Vrf.Name, arg.Vrf.Id, rd, im, ex) } func (s *Server) DeleteVrf(ctx context.Context, arg *DeleteVrfRequest) (*DeleteVrfResponse, error) { if arg == nil || arg.Vrf == nil { return nil, fmt.Errorf("invalid request") } return &DeleteVrfResponse{}, s.bgpServer.DeleteVrf(arg.Vrf.Name) } func ReadMpGracefulRestartFromAPIStruct(c *config.MpGracefulRestart, a *MpGracefulRestart) { if c == nil || a == nil { return } if a.Config != nil { c.Config.Enabled = a.Config.Enabled } } func ReadAfiSafiConfigFromAPIStruct(c *config.AfiSafiConfig, a *AfiSafiConfig) { if c == nil || a == nil { return } c.AfiSafiName = config.AfiSafiType(bgp.RouteFamily(a.Family).String()) c.Enabled = a.Enabled } func ReadAfiSafiStateFromAPIStruct(s *config.AfiSafiState, a *AfiSafiConfig) { if s == nil || a == nil { return } // Store only address family value for the convenience s.Family = bgp.RouteFamily(a.Family) } func ReadPrefixLimitFromAPIStruct(c *config.PrefixLimit, a *PrefixLimit) { if c == nil || a == nil { return } c.Config.MaxPrefixes = a.MaxPrefixes c.Config.ShutdownThresholdPct = config.Percentage(a.ShutdownThresholdPct) } func ReadApplyPolicyFromAPIStruct(c *config.ApplyPolicy, a *ApplyPolicy) { if c == nil || a == nil { return } if a.ImportPolicy != nil { c.Config.DefaultImportPolicy = config.IntToDefaultPolicyTypeMap[int(a.ImportPolicy.Default)] for _, p := range a.ImportPolicy.Policies { c.Config.ImportPolicyList = append(c.Config.ImportPolicyList, p.Name) } } if a.ExportPolicy != nil { c.Config.DefaultExportPolicy = config.IntToDefaultPolicyTypeMap[int(a.ExportPolicy.Default)] for _, p := range a.ExportPolicy.Policies { c.Config.ExportPolicyList = append(c.Config.ExportPolicyList, p.Name) } } if a.InPolicy != nil { c.Config.DefaultInPolicy = config.IntToDefaultPolicyTypeMap[int(a.InPolicy.Default)] for _, p := range a.InPolicy.Policies { c.Config.InPolicyList = append(c.Config.InPolicyList, p.Name) } } } func ReadRouteSelectionOptionsFromAPIStruct(c *config.RouteSelectionOptions, a *RouteSelectionOptions) { if c == nil || a == nil { return } if a.Config != nil { c.Config.AlwaysCompareMed = a.Config.AlwaysCompareMed c.Config.IgnoreAsPathLength = a.Config.IgnoreAsPathLength c.Config.ExternalCompareRouterId = a.Config.ExternalCompareRouterId c.Config.AdvertiseInactiveRoutes = a.Config.AdvertiseInactiveRoutes c.Config.EnableAigp = a.Config.EnableAigp c.Config.IgnoreNextHopIgpMetric = a.Config.IgnoreNextHopIgpMetric } } func ReadUseMultiplePathsFromAPIStruct(c *config.UseMultiplePaths, a *UseMultiplePaths) { if c == nil || a == nil { return } if a.Config != nil { c.Config.Enabled = a.Config.Enabled } if a.Ebgp != nil && a.Ebgp.Config != nil { c.Ebgp = config.Ebgp{ Config: config.EbgpConfig{ AllowMultipleAs: a.Ebgp.Config.AllowMultipleAs, MaximumPaths: a.Ebgp.Config.MaximumPaths, }, } } if a.Ibgp != nil && a.Ibgp.Config != nil { c.Ibgp = config.Ibgp{ Config: config.IbgpConfig{ MaximumPaths: a.Ibgp.Config.MaximumPaths, }, } } } func ReadRouteTargetMembershipFromAPIStruct(c *config.RouteTargetMembership, a *RouteTargetMembership) { if c == nil || a == nil { return } if a.Config != nil { c.Config.DeferralTime = uint16(a.Config.DeferralTime) } } func ReadLongLivedGracefulRestartFromAPIStruct(c *config.LongLivedGracefulRestart, a *LongLivedGracefulRestart) { if c == nil || a == nil { return } if a.Config != nil { c.Config.Enabled = a.Config.Enabled c.Config.RestartTime = a.Config.RestartTime } } func ReadAddPathsFromAPIStruct(c *config.AddPaths, a *AddPaths) { if c == nil || a == nil { return } if a.Config != nil { c.Config.Receive = a.Config.Receive c.Config.SendMax = uint8(a.Config.SendMax) } } func NewNeighborFromAPIStruct(a *Peer) (*config.Neighbor, error) { pconf := &config.Neighbor{} if a.Conf != nil { pconf.Config.PeerAs = a.Conf.PeerAs pconf.Config.LocalAs = a.Conf.LocalAs pconf.Config.AuthPassword = a.Conf.AuthPassword pconf.Config.RouteFlapDamping = a.Conf.RouteFlapDamping pconf.Config.Description = a.Conf.Description pconf.Config.PeerGroup = a.Conf.PeerGroup pconf.Config.NeighborAddress = a.Conf.NeighborAddress pconf.Config.NeighborInterface = a.Conf.NeighborInterface pconf.Config.Vrf = a.Conf.Vrf pconf.AsPathOptions.Config.AllowOwnAs = uint8(a.Conf.AllowOwnAs) pconf.AsPathOptions.Config.ReplacePeerAs = a.Conf.ReplacePeerAs switch a.Conf.RemovePrivateAs { case PeerConf_ALL: pconf.Config.RemovePrivateAs = config.REMOVE_PRIVATE_AS_OPTION_ALL case PeerConf_REPLACE: pconf.Config.RemovePrivateAs = config.REMOVE_PRIVATE_AS_OPTION_REPLACE } f := func(bufs [][]byte) ([]bgp.ParameterCapabilityInterface, error) { var caps []bgp.ParameterCapabilityInterface for _, buf := range bufs { c, err := bgp.DecodeCapability(buf) if err != nil { return nil, err } caps = append(caps, c) } return caps, nil } localCaps, err := f(a.Conf.LocalCap) if err != nil { return nil, err } remoteCaps, err := f(a.Conf.RemoteCap) if err != nil { return nil, err } pconf.State.LocalCapabilityList = localCaps pconf.State.RemoteCapabilityList = remoteCaps pconf.State.RemoteRouterId = a.Conf.Id for _, af := range a.AfiSafis { afiSafi := config.AfiSafi{} ReadMpGracefulRestartFromAPIStruct(&afiSafi.MpGracefulRestart, af.MpGracefulRestart) ReadAfiSafiConfigFromAPIStruct(&afiSafi.Config, af.Config) ReadAfiSafiStateFromAPIStruct(&afiSafi.State, af.Config) ReadApplyPolicyFromAPIStruct(&afiSafi.ApplyPolicy, af.ApplyPolicy) ReadRouteSelectionOptionsFromAPIStruct(&afiSafi.RouteSelectionOptions, af.RouteSelectionOptions) ReadUseMultiplePathsFromAPIStruct(&afiSafi.UseMultiplePaths, af.UseMultiplePaths) ReadPrefixLimitFromAPIStruct(&afiSafi.PrefixLimit, af.PrefixLimits) ReadRouteTargetMembershipFromAPIStruct(&afiSafi.RouteTargetMembership, af.RouteTargetMembership) ReadLongLivedGracefulRestartFromAPIStruct(&afiSafi.LongLivedGracefulRestart, af.LongLivedGracefulRestart) ReadAddPathsFromAPIStruct(&afiSafi.AddPaths, af.AddPaths) pconf.AfiSafis = append(pconf.AfiSafis, afiSafi) } // For the backward compatibility, we override AfiSafi configurations // with Peer.Families. for _, family := range a.Families { found := false for _, afiSafi := range pconf.AfiSafis { if uint32(afiSafi.State.Family) == family { // If Peer.Families contains the same address family, // we enable this address family. afiSafi.Config.Enabled = true found = true } } if !found { // If Peer.Families does not contain the same address family, // we append AfiSafi structure with the default value. pconf.AfiSafis = append(pconf.AfiSafis, config.AfiSafi{ Config: config.AfiSafiConfig{ AfiSafiName: config.AfiSafiType(bgp.RouteFamily(family).String()), Enabled: true, }, }) } } // For the backward compatibility, we override AfiSafi configurations // with Peer.Conf.PrefixLimits. for _, prefixLimit := range a.Conf.PrefixLimits { for _, afiSafi := range pconf.AfiSafis { // If Peer.Conf.PrefixLimits contains the configuration for // the same address family, we override AfiSafi.PrefixLimit. if uint32(afiSafi.State.Family) == prefixLimit.Family { ReadPrefixLimitFromAPIStruct(&afiSafi.PrefixLimit, prefixLimit) } } } } if a.Timers != nil { if a.Timers.Config != nil { pconf.Timers.Config.ConnectRetry = float64(a.Timers.Config.ConnectRetry) pconf.Timers.Config.HoldTime = float64(a.Timers.Config.HoldTime) pconf.Timers.Config.KeepaliveInterval = float64(a.Timers.Config.KeepaliveInterval) pconf.Timers.Config.MinimumAdvertisementInterval = float64(a.Timers.Config.MinimumAdvertisementInterval) } if a.Timers.State != nil { pconf.Timers.State.KeepaliveInterval = float64(a.Timers.State.KeepaliveInterval) pconf.Timers.State.NegotiatedHoldTime = float64(a.Timers.State.NegotiatedHoldTime) pconf.Timers.State.Uptime = int64(a.Timers.State.Uptime) pconf.Timers.State.Downtime = int64(a.Timers.State.Downtime) } } if a.RouteReflector != nil { pconf.RouteReflector.Config.RouteReflectorClusterId = config.RrClusterIdType(a.RouteReflector.RouteReflectorClusterId) pconf.RouteReflector.Config.RouteReflectorClient = a.RouteReflector.RouteReflectorClient } if a.RouteServer != nil { pconf.RouteServer.Config.RouteServerClient = a.RouteServer.RouteServerClient } if a.GracefulRestart != nil { pconf.GracefulRestart.Config.Enabled = a.GracefulRestart.Enabled pconf.GracefulRestart.Config.RestartTime = uint16(a.GracefulRestart.RestartTime) pconf.GracefulRestart.Config.HelperOnly = a.GracefulRestart.HelperOnly pconf.GracefulRestart.Config.DeferralTime = uint16(a.GracefulRestart.DeferralTime) pconf.GracefulRestart.Config.NotificationEnabled = a.GracefulRestart.NotificationEnabled pconf.GracefulRestart.Config.LongLivedEnabled = a.GracefulRestart.LonglivedEnabled } ReadApplyPolicyFromAPIStruct(&pconf.ApplyPolicy, a.ApplyPolicy) if a.Transport != nil { pconf.Transport.Config.LocalAddress = a.Transport.LocalAddress pconf.Transport.Config.PassiveMode = a.Transport.PassiveMode pconf.Transport.Config.RemotePort = uint16(a.Transport.RemotePort) } if a.EbgpMultihop != nil { pconf.EbgpMultihop.Config.Enabled = a.EbgpMultihop.Enabled pconf.EbgpMultihop.Config.MultihopTtl = uint8(a.EbgpMultihop.MultihopTtl) } if a.Info != nil { pconf.State.SessionState = config.SessionState(a.Info.BgpState) pconf.State.AdminState = config.IntToAdminStateMap[int(a.Info.AdminState)] pconf.State.AdjTable.Received = a.Info.Received pconf.State.AdjTable.Accepted = a.Info.Accepted pconf.State.AdjTable.Advertised = a.Info.Advertised pconf.State.PeerAs = a.Info.PeerAs pconf.State.PeerType = config.IntToPeerTypeMap[int(a.Info.PeerType)] pconf.State.NeighborAddress = a.Info.NeighborAddress if a.Info.Messages != nil { if a.Info.Messages.Sent != nil { pconf.State.Messages.Sent.Update = a.Info.Messages.Sent.UPDATE pconf.State.Messages.Sent.Notification = a.Info.Messages.Sent.NOTIFICATION pconf.State.Messages.Sent.Open = a.Info.Messages.Sent.OPEN pconf.State.Messages.Sent.Refresh = a.Info.Messages.Sent.REFRESH pconf.State.Messages.Sent.Keepalive = a.Info.Messages.Sent.KEEPALIVE pconf.State.Messages.Sent.Discarded = a.Info.Messages.Sent.DISCARDED pconf.State.Messages.Sent.Total = a.Info.Messages.Sent.TOTAL } if a.Info.Messages.Received != nil { pconf.State.Messages.Received.Update = a.Info.Messages.Received.UPDATE pconf.State.Messages.Received.Open = a.Info.Messages.Received.OPEN pconf.State.Messages.Received.Refresh = a.Info.Messages.Received.REFRESH pconf.State.Messages.Received.Keepalive = a.Info.Messages.Received.KEEPALIVE pconf.State.Messages.Received.Discarded = a.Info.Messages.Received.DISCARDED pconf.State.Messages.Received.Total = a.Info.Messages.Received.TOTAL } } } ReadAddPathsFromAPIStruct(&pconf.AddPaths, a.AddPaths) return pconf, nil } func (s *Server) AddNeighbor(ctx context.Context, arg *AddNeighborRequest) (*AddNeighborResponse, error) { c, err := NewNeighborFromAPIStruct(arg.Peer) if err != nil { return nil, err } return &AddNeighborResponse{}, s.bgpServer.AddNeighbor(c) } func (s *Server) DeleteNeighbor(ctx context.Context, arg *DeleteNeighborRequest) (*DeleteNeighborResponse, error) { return &DeleteNeighborResponse{}, s.bgpServer.DeleteNeighbor(&config.Neighbor{Config: config.NeighborConfig{ NeighborAddress: arg.Peer.Conf.NeighborAddress, NeighborInterface: arg.Peer.Conf.NeighborInterface, }}) } func NewPrefixFromApiStruct(a *Prefix) (*table.Prefix, error) { _, prefix, err := net.ParseCIDR(a.IpPrefix) if err != nil { return nil, err } rf := bgp.RF_IPv4_UC if strings.Contains(a.IpPrefix, ":") { rf = bgp.RF_IPv6_UC } return &table.Prefix{ Prefix: prefix, AddressFamily: rf, MasklengthRangeMin: uint8(a.MaskLengthMin), MasklengthRangeMax: uint8(a.MaskLengthMax), }, nil } func NewAPIPrefixFromConfigStruct(c config.Prefix) (*Prefix, error) { min, max, err := config.ParseMaskLength(c.IpPrefix, c.MasklengthRange) if err != nil { return nil, err } return &Prefix{ IpPrefix: c.IpPrefix, MaskLengthMin: uint32(min), MaskLengthMax: uint32(max), }, nil } func NewAPIDefinedSetFromTableStruct(t table.DefinedSet) (*DefinedSet, error) { a := &DefinedSet{ Type: DefinedType(t.Type()), Name: t.Name(), } switch t.Type() { case table.DEFINED_TYPE_PREFIX: s := t.(*table.PrefixSet) c := s.ToConfig() for _, p := range c.PrefixList { ap, err := NewAPIPrefixFromConfigStruct(p) if err != nil { return nil, err } a.Prefixes = append(a.Prefixes, ap) } case table.DEFINED_TYPE_NEIGHBOR: s := t.(*table.NeighborSet) c := s.ToConfig() for _, n := range c.NeighborInfoList { a.List = append(a.List, n) } case table.DEFINED_TYPE_AS_PATH: s := t.(*table.AsPathSet) c := s.ToConfig() for _, n := range c.AsPathList { a.List = append(a.List, n) } case table.DEFINED_TYPE_COMMUNITY: s := t.(*table.CommunitySet) c := s.ToConfig() for _, n := range c.CommunityList { a.List = append(a.List, n) } case table.DEFINED_TYPE_EXT_COMMUNITY: s := t.(*table.ExtCommunitySet) c := s.ToConfig() for _, n := range c.ExtCommunityList { a.List = append(a.List, n) } case table.DEFINED_TYPE_LARGE_COMMUNITY: s := t.(*table.LargeCommunitySet) c := s.ToConfig() for _, n := range c.LargeCommunityList { a.List = append(a.List, n) } default: return nil, fmt.Errorf("invalid defined type") } return a, nil } func NewDefinedSetFromApiStruct(a *DefinedSet) (table.DefinedSet, error) { if a.Name == "" { return nil, fmt.Errorf("empty neighbor set name") } switch table.DefinedType(a.Type) { case table.DEFINED_TYPE_PREFIX: prefixes := make([]*table.Prefix, 0, len(a.Prefixes)) for _, p := range a.Prefixes { prefix, err := NewPrefixFromApiStruct(p) if err != nil { return nil, err } prefixes = append(prefixes, prefix) } return table.NewPrefixSetFromApiStruct(a.Name, prefixes) case table.DEFINED_TYPE_NEIGHBOR: list := make([]net.IPNet, 0, len(a.List)) for _, x := range a.List { _, addr, err := net.ParseCIDR(x) if err != nil { return nil, fmt.Errorf("invalid address or prefix: %s", x) } list = append(list, *addr) } return table.NewNeighborSetFromApiStruct(a.Name, list) case table.DEFINED_TYPE_AS_PATH: return table.NewAsPathSet(config.AsPathSet{ AsPathSetName: a.Name, AsPathList: a.List, }) case table.DEFINED_TYPE_COMMUNITY: return table.NewCommunitySet(config.CommunitySet{ CommunitySetName: a.Name, CommunityList: a.List, }) case table.DEFINED_TYPE_EXT_COMMUNITY: return table.NewExtCommunitySet(config.ExtCommunitySet{ ExtCommunitySetName: a.Name, ExtCommunityList: a.List, }) case table.DEFINED_TYPE_LARGE_COMMUNITY: return table.NewLargeCommunitySet(config.LargeCommunitySet{ LargeCommunitySetName: a.Name, LargeCommunityList: a.List, }) default: return nil, fmt.Errorf("invalid defined type") } } func (s *Server) GetDefinedSet(ctx context.Context, arg *GetDefinedSetRequest) (*GetDefinedSetResponse, error) { cd, err := s.bgpServer.GetDefinedSet(table.DefinedType(arg.Type), arg.Name) if err != nil { return nil, err } sets := make([]*DefinedSet, 0) for _, cs := range cd.PrefixSets { ad := &DefinedSet{ Type: DefinedType_PREFIX, Name: cs.PrefixSetName, Prefixes: func() []*Prefix { l := make([]*Prefix, 0, len(cs.PrefixList)) for _, p := range cs.PrefixList { exp := regexp.MustCompile("(\\d+)\\.\\.(\\d+)") elems := exp.FindStringSubmatch(p.MasklengthRange) min, _ := strconv.ParseUint(elems[1], 10, 32) max, _ := strconv.ParseUint(elems[2], 10, 32) l = append(l, &Prefix{IpPrefix: p.IpPrefix, MaskLengthMin: uint32(min), MaskLengthMax: uint32(max)}) } return l }(), } sets = append(sets, ad) } for _, cs := range cd.NeighborSets { ad := &DefinedSet{ Type: DefinedType_NEIGHBOR, Name: cs.NeighborSetName, List: cs.NeighborInfoList, } sets = append(sets, ad) } for _, cs := range cd.BgpDefinedSets.CommunitySets { ad := &DefinedSet{ Type: DefinedType_COMMUNITY, Name: cs.CommunitySetName, List: cs.CommunityList, } sets = append(sets, ad) } for _, cs := range cd.BgpDefinedSets.ExtCommunitySets { ad := &DefinedSet{ Type: DefinedType_EXT_COMMUNITY, Name: cs.ExtCommunitySetName, List: cs.ExtCommunityList, } sets = append(sets, ad) } for _, cs := range cd.BgpDefinedSets.LargeCommunitySets { ad := &DefinedSet{ Type: DefinedType_LARGE_COMMUNITY, Name: cs.LargeCommunitySetName, List: cs.LargeCommunityList, } sets = append(sets, ad) } for _, cs := range cd.BgpDefinedSets.AsPathSets { ad := &DefinedSet{ Type: DefinedType_AS_PATH, Name: cs.AsPathSetName, List: cs.AsPathList, } sets = append(sets, ad) } return &GetDefinedSetResponse{Sets: sets}, nil } func (s *Server) AddDefinedSet(ctx context.Context, arg *AddDefinedSetRequest) (*AddDefinedSetResponse, error) { if arg == nil || arg.Set == nil { return nil, fmt.Errorf("invalid request") } set, err := NewDefinedSetFromApiStruct(arg.Set) if err != nil { return nil, err } return &AddDefinedSetResponse{}, s.bgpServer.AddDefinedSet(set) } func (s *Server) DeleteDefinedSet(ctx context.Context, arg *DeleteDefinedSetRequest) (*DeleteDefinedSetResponse, error) { if arg == nil || arg.Set == nil { return nil, fmt.Errorf("invalid request") } set, err := NewDefinedSetFromApiStruct(arg.Set) if err != nil { return nil, err } return &DeleteDefinedSetResponse{}, s.bgpServer.DeleteDefinedSet(set, arg.All) } func (s *Server) ReplaceDefinedSet(ctx context.Context, arg *ReplaceDefinedSetRequest) (*ReplaceDefinedSetResponse, error) { if arg == nil || arg.Set == nil { return nil, fmt.Errorf("invalid request") } set, err := NewDefinedSetFromApiStruct(arg.Set) if err != nil { return nil, err } return &ReplaceDefinedSetResponse{}, s.bgpServer.ReplaceDefinedSet(set) } func NewAPIStatementFromTableStruct(t *table.Statement) *Statement { return toStatementApi(t.ToConfig()) } func toStatementApi(s *config.Statement) *Statement { cs := &Conditions{} if s.Conditions.MatchPrefixSet.PrefixSet != "" { o, _ := table.NewMatchOption(s.Conditions.MatchPrefixSet.MatchSetOptions) cs.PrefixSet = &MatchSet{ Type: MatchType(o), Name: s.Conditions.MatchPrefixSet.PrefixSet, } } if s.Conditions.MatchNeighborSet.NeighborSet != "" { o, _ := table.NewMatchOption(s.Conditions.MatchNeighborSet.MatchSetOptions) cs.NeighborSet = &MatchSet{ Type: MatchType(o), Name: s.Conditions.MatchNeighborSet.NeighborSet, } } if s.Conditions.BgpConditions.AsPathLength.Operator != "" { cs.AsPathLength = &AsPathLength{ Length: s.Conditions.BgpConditions.AsPathLength.Value, Type: AsPathLengthType(s.Conditions.BgpConditions.AsPathLength.Operator.ToInt()), } } if s.Conditions.BgpConditions.MatchAsPathSet.AsPathSet != "" { cs.AsPathSet = &MatchSet{ Type: MatchType(s.Conditions.BgpConditions.MatchAsPathSet.MatchSetOptions.ToInt()), Name: s.Conditions.BgpConditions.MatchAsPathSet.AsPathSet, } } if s.Conditions.BgpConditions.MatchCommunitySet.CommunitySet != "" { cs.CommunitySet = &MatchSet{ Type: MatchType(s.Conditions.BgpConditions.MatchCommunitySet.MatchSetOptions.ToInt()), Name: s.Conditions.BgpConditions.MatchCommunitySet.CommunitySet, } } if s.Conditions.BgpConditions.MatchExtCommunitySet.ExtCommunitySet != "" { cs.ExtCommunitySet = &MatchSet{ Type: MatchType(s.Conditions.BgpConditions.MatchExtCommunitySet.MatchSetOptions.ToInt()), Name: s.Conditions.BgpConditions.MatchExtCommunitySet.ExtCommunitySet, } } if s.Conditions.BgpConditions.MatchLargeCommunitySet.LargeCommunitySet != "" { cs.LargeCommunitySet = &MatchSet{ Type: MatchType(s.Conditions.BgpConditions.MatchLargeCommunitySet.MatchSetOptions.ToInt()), Name: s.Conditions.BgpConditions.MatchLargeCommunitySet.LargeCommunitySet, } } if s.Conditions.BgpConditions.RouteType != "" { cs.RouteType = Conditions_RouteType(s.Conditions.BgpConditions.RouteType.ToInt()) } cs.RpkiResult = int32(s.Conditions.BgpConditions.RpkiValidationResult.ToInt()) as := &Actions{ RouteAction: func() RouteAction { switch s.Actions.RouteDisposition { case config.ROUTE_DISPOSITION_ACCEPT_ROUTE: return RouteAction_ACCEPT case config.ROUTE_DISPOSITION_REJECT_ROUTE: return RouteAction_REJECT } return RouteAction_NONE }(), Community: func() *CommunityAction { if len(s.Actions.BgpActions.SetCommunity.SetCommunityMethod.CommunitiesList) == 0 { return nil } return &CommunityAction{ Type: CommunityActionType(config.BgpSetCommunityOptionTypeToIntMap[config.BgpSetCommunityOptionType(s.Actions.BgpActions.SetCommunity.Options)]), Communities: s.Actions.BgpActions.SetCommunity.SetCommunityMethod.CommunitiesList} }(), Med: func() *MedAction { medStr := strings.TrimSpace(string(s.Actions.BgpActions.SetMed)) if len(medStr) == 0 { return nil } re := regexp.MustCompile("([+-]?)(\\d+)") matches := re.FindStringSubmatch(medStr) if len(matches) == 0 { return nil } action := MedActionType_MED_REPLACE switch matches[1] { case "+", "-": action = MedActionType_MED_MOD } value, err := strconv.ParseInt(matches[1]+matches[2], 10, 64) if err != nil { return nil } return &MedAction{ Value: value, Type: action, } }(), AsPrepend: func() *AsPrependAction { if len(s.Actions.BgpActions.SetAsPathPrepend.As) == 0 { return nil } var asn uint64 useleft := false if s.Actions.BgpActions.SetAsPathPrepend.As != "last-as" { asn, _ = strconv.ParseUint(s.Actions.BgpActions.SetAsPathPrepend.As, 10, 32) } else { useleft = true } return &AsPrependAction{ Asn: uint32(asn), Repeat: uint32(s.Actions.BgpActions.SetAsPathPrepend.RepeatN), UseLeftMost: useleft, } }(), ExtCommunity: func() *CommunityAction { if len(s.Actions.BgpActions.SetExtCommunity.SetExtCommunityMethod.CommunitiesList) == 0 { return nil } return &CommunityAction{ Type: CommunityActionType(config.BgpSetCommunityOptionTypeToIntMap[config.BgpSetCommunityOptionType(s.Actions.BgpActions.SetExtCommunity.Options)]), Communities: s.Actions.BgpActions.SetExtCommunity.SetExtCommunityMethod.CommunitiesList, } }(), LargeCommunity: func() *CommunityAction { if len(s.Actions.BgpActions.SetLargeCommunity.SetLargeCommunityMethod.CommunitiesList) == 0 { return nil } return &CommunityAction{ Type: CommunityActionType(config.BgpSetCommunityOptionTypeToIntMap[config.BgpSetCommunityOptionType(s.Actions.BgpActions.SetLargeCommunity.Options)]), Communities: s.Actions.BgpActions.SetLargeCommunity.SetLargeCommunityMethod.CommunitiesList, } }(), Nexthop: func() *NexthopAction { if len(string(s.Actions.BgpActions.SetNextHop)) == 0 { return nil } if string(s.Actions.BgpActions.SetNextHop) == "self" { return &NexthopAction{ Self: true, } } return &NexthopAction{ Address: string(s.Actions.BgpActions.SetNextHop), } }(), LocalPref: func() *LocalPrefAction { if s.Actions.BgpActions.SetLocalPref == 0 { return nil } return &LocalPrefAction{Value: s.Actions.BgpActions.SetLocalPref} }(), } return &Statement{ Name: s.Name, Conditions: cs, Actions: as, } } func toConfigMatchSetOption(a MatchType) (config.MatchSetOptionsType, error) { var typ config.MatchSetOptionsType switch a { case MatchType_ANY: typ = config.MATCH_SET_OPTIONS_TYPE_ANY case MatchType_ALL: typ = config.MATCH_SET_OPTIONS_TYPE_ALL case MatchType_INVERT: typ = config.MATCH_SET_OPTIONS_TYPE_INVERT default: return typ, fmt.Errorf("invalid match type") } return typ, nil } func toConfigMatchSetOptionRestricted(a MatchType) (config.MatchSetOptionsRestrictedType, error) { var typ config.MatchSetOptionsRestrictedType switch a { case MatchType_ANY: typ = config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY case MatchType_INVERT: typ = config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_INVERT default: return typ, fmt.Errorf("invalid match type") } return typ, nil } func NewPrefixConditionFromApiStruct(a *MatchSet) (*table.PrefixCondition, error) { if a == nil { return nil, nil } typ, err := toConfigMatchSetOptionRestricted(a.Type) if err != nil { return nil, err } c := config.MatchPrefixSet{ PrefixSet: a.Name, MatchSetOptions: typ, } return table.NewPrefixCondition(c) } func NewNeighborConditionFromApiStruct(a *MatchSet) (*table.NeighborCondition, error) { if a == nil { return nil, nil } typ, err := toConfigMatchSetOptionRestricted(a.Type) if err != nil { return nil, err } c := config.MatchNeighborSet{ NeighborSet: a.Name, MatchSetOptions: typ, } return table.NewNeighborCondition(c) } func NewAsPathLengthConditionFromApiStruct(a *AsPathLength) (*table.AsPathLengthCondition, error) { if a == nil { return nil, nil } return table.NewAsPathLengthCondition(config.AsPathLength{ Operator: config.IntToAttributeComparisonMap[int(a.Type)], Value: a.Length, }) } func NewAsPathConditionFromApiStruct(a *MatchSet) (*table.AsPathCondition, error) { if a == nil { return nil, nil } typ, err := toConfigMatchSetOption(a.Type) if err != nil { return nil, err } c := config.MatchAsPathSet{ AsPathSet: a.Name, MatchSetOptions: typ, } return table.NewAsPathCondition(c) } func NewRpkiValidationConditionFromApiStruct(a int32) (*table.RpkiValidationCondition, error) { if a < 1 { return nil, nil } return table.NewRpkiValidationCondition(config.IntToRpkiValidationResultTypeMap[int(a)]) } func NewRouteTypeConditionFromApiStruct(a Conditions_RouteType) (*table.RouteTypeCondition, error) { if a == 0 { return nil, nil } typ, ok := config.IntToRouteTypeMap[int(a)] if !ok { return nil, fmt.Errorf("invalid route type: %d", a) } return table.NewRouteTypeCondition(typ) } func NewCommunityConditionFromApiStruct(a *MatchSet) (*table.CommunityCondition, error) { if a == nil { return nil, nil } typ, err := toConfigMatchSetOption(a.Type) if err != nil { return nil, err } c := config.MatchCommunitySet{ CommunitySet: a.Name, MatchSetOptions: typ, } return table.NewCommunityCondition(c) } func NewExtCommunityConditionFromApiStruct(a *MatchSet) (*table.ExtCommunityCondition, error) { if a == nil { return nil, nil } typ, err := toConfigMatchSetOption(a.Type) if err != nil { return nil, err } c := config.MatchExtCommunitySet{ ExtCommunitySet: a.Name, MatchSetOptions: typ, } return table.NewExtCommunityCondition(c) } func NewLargeCommunityConditionFromApiStruct(a *MatchSet) (*table.LargeCommunityCondition, error) { if a == nil { return nil, nil } typ, err := toConfigMatchSetOption(a.Type) if err != nil { return nil, err } c := config.MatchLargeCommunitySet{ LargeCommunitySet: a.Name, MatchSetOptions: typ, } return table.NewLargeCommunityCondition(c) } func NewRoutingActionFromApiStruct(a RouteAction) (*table.RoutingAction, error) { if a == RouteAction_NONE { return nil, nil } accept := false if a == RouteAction_ACCEPT { accept = true } return &table.RoutingAction{ AcceptRoute: accept, }, nil } func NewCommunityActionFromApiStruct(a *CommunityAction) (*table.CommunityAction, error) { if a == nil { return nil, nil } return table.NewCommunityAction(config.SetCommunity{ Options: string(config.IntToBgpSetCommunityOptionTypeMap[int(a.Type)]), SetCommunityMethod: config.SetCommunityMethod{ CommunitiesList: a.Communities, }, }) } func NewExtCommunityActionFromApiStruct(a *CommunityAction) (*table.ExtCommunityAction, error) { if a == nil { return nil, nil } return table.NewExtCommunityAction(config.SetExtCommunity{ Options: string(config.IntToBgpSetCommunityOptionTypeMap[int(a.Type)]), SetExtCommunityMethod: config.SetExtCommunityMethod{ CommunitiesList: a.Communities, }, }) } func NewLargeCommunityActionFromApiStruct(a *CommunityAction) (*table.LargeCommunityAction, error) { if a == nil { return nil, nil } return table.NewLargeCommunityAction(config.SetLargeCommunity{ Options: config.IntToBgpSetCommunityOptionTypeMap[int(a.Type)], SetLargeCommunityMethod: config.SetLargeCommunityMethod{ CommunitiesList: a.Communities, }, }) } func NewMedActionFromApiStruct(a *MedAction) (*table.MedAction, error) { if a == nil { return nil, nil } return table.NewMedActionFromApiStruct(table.MedActionType(a.Type), a.Value), nil } func NewLocalPrefActionFromApiStruct(a *LocalPrefAction) (*table.LocalPrefAction, error) { if a == nil || a.Value == 0 { return nil, nil } return table.NewLocalPrefAction(a.Value) } func NewAsPathPrependActionFromApiStruct(a *AsPrependAction) (*table.AsPathPrependAction, error) { if a == nil { return nil, nil } return table.NewAsPathPrependAction(config.SetAsPathPrepend{ RepeatN: uint8(a.Repeat), As: func() string { if a.UseLeftMost { return "last-as" } return fmt.Sprintf("%d", a.Asn) }(), }) } func NewNexthopActionFromApiStruct(a *NexthopAction) (*table.NexthopAction, error) { if a == nil { return nil, nil } return table.NewNexthopAction(config.BgpNextHopType( func() string { if a.Self { return "self" } return a.Address }(), )) } func NewStatementFromApiStruct(a *Statement) (*table.Statement, error) { if a.Name == "" { return nil, fmt.Errorf("empty statement name") } var ra table.Action var as []table.Action var cs []table.Condition var err error if a.Conditions != nil { cfs := []func() (table.Condition, error){ func() (table.Condition, error) { return NewPrefixConditionFromApiStruct(a.Conditions.PrefixSet) }, func() (table.Condition, error) { return NewNeighborConditionFromApiStruct(a.Conditions.NeighborSet) }, func() (table.Condition, error) { return NewAsPathLengthConditionFromApiStruct(a.Conditions.AsPathLength) }, func() (table.Condition, error) { return NewRpkiValidationConditionFromApiStruct(a.Conditions.RpkiResult) }, func() (table.Condition, error) { return NewRouteTypeConditionFromApiStruct(a.Conditions.RouteType) }, func() (table.Condition, error) { return NewAsPathConditionFromApiStruct(a.Conditions.AsPathSet) }, func() (table.Condition, error) { return NewCommunityConditionFromApiStruct(a.Conditions.CommunitySet) }, func() (table.Condition, error) { return NewExtCommunityConditionFromApiStruct(a.Conditions.ExtCommunitySet) }, func() (table.Condition, error) { return NewLargeCommunityConditionFromApiStruct(a.Conditions.LargeCommunitySet) }, } cs = make([]table.Condition, 0, len(cfs)) for _, f := range cfs { c, err := f() if err != nil { return nil, err } if !reflect.ValueOf(c).IsNil() { cs = append(cs, c) } } } if a.Actions != nil { ra, err = NewRoutingActionFromApiStruct(a.Actions.RouteAction) if err != nil { return nil, err } afs := []func() (table.Action, error){ func() (table.Action, error) { return NewCommunityActionFromApiStruct(a.Actions.Community) }, func() (table.Action, error) { return NewExtCommunityActionFromApiStruct(a.Actions.ExtCommunity) }, func() (table.Action, error) { return NewLargeCommunityActionFromApiStruct(a.Actions.LargeCommunity) }, func() (table.Action, error) { return NewMedActionFromApiStruct(a.Actions.Med) }, func() (table.Action, error) { return NewLocalPrefActionFromApiStruct(a.Actions.LocalPref) }, func() (table.Action, error) { return NewAsPathPrependActionFromApiStruct(a.Actions.AsPrepend) }, func() (table.Action, error) { return NewNexthopActionFromApiStruct(a.Actions.Nexthop) }, } as = make([]table.Action, 0, len(afs)) for _, f := range afs { a, err := f() if err != nil { return nil, err } if !reflect.ValueOf(a).IsNil() { as = append(as, a) } } } return &table.Statement{ Name: a.Name, Conditions: cs, RouteAction: ra, ModActions: as, }, nil } func (s *Server) GetStatement(ctx context.Context, arg *GetStatementRequest) (*GetStatementResponse, error) { l := make([]*Statement, 0) for _, s := range s.bgpServer.GetStatement() { l = append(l, toStatementApi(s)) } return &GetStatementResponse{Statements: l}, nil } func (s *Server) AddStatement(ctx context.Context, arg *AddStatementRequest) (*AddStatementResponse, error) { if arg == nil || arg.Statement == nil { return nil, fmt.Errorf("invalid request") } st, err := NewStatementFromApiStruct(arg.Statement) if err == nil { err = s.bgpServer.AddStatement(st) } return &AddStatementResponse{}, err } func (s *Server) DeleteStatement(ctx context.Context, arg *DeleteStatementRequest) (*DeleteStatementResponse, error) { if arg == nil || arg.Statement == nil { return nil, fmt.Errorf("invalid request") } st, err := NewStatementFromApiStruct(arg.Statement) if err == nil { err = s.bgpServer.DeleteStatement(st, arg.All) } return &DeleteStatementResponse{}, err } func (s *Server) ReplaceStatement(ctx context.Context, arg *ReplaceStatementRequest) (*ReplaceStatementResponse, error) { if arg == nil || arg.Statement == nil { return nil, fmt.Errorf("invalid request") } st, err := NewStatementFromApiStruct(arg.Statement) if err == nil { err = s.bgpServer.ReplaceStatement(st) } return &ReplaceStatementResponse{}, err } func NewAPIPolicyFromTableStruct(p *table.Policy) *Policy { return toPolicyApi(p.ToConfig()) } func toPolicyApi(p *config.PolicyDefinition) *Policy { return &Policy{ Name: p.Name, Statements: func() []*Statement { l := make([]*Statement, 0) for _, s := range p.Statements { l = append(l, toStatementApi(&s)) } return l }(), } } func NewAPIPolicyAssignmentFromTableStruct(t *table.PolicyAssignment) *PolicyAssignment { return &PolicyAssignment{ Type: func() PolicyType { switch t.Type { case table.POLICY_DIRECTION_IN: return PolicyType_IN case table.POLICY_DIRECTION_IMPORT: return PolicyType_IMPORT case table.POLICY_DIRECTION_EXPORT: return PolicyType_EXPORT } log.Errorf("invalid policy-type: %s", t.Type) return PolicyType(-1) }(), Default: func() RouteAction { switch t.Default { case table.ROUTE_TYPE_ACCEPT: return RouteAction_ACCEPT case table.ROUTE_TYPE_REJECT: return RouteAction_REJECT } return RouteAction_NONE }(), Name: t.Name, Resource: func() Resource { if t.Name != "" { return Resource_LOCAL } return Resource_GLOBAL }(), Policies: func() []*Policy { l := make([]*Policy, 0) for _, p := range t.Policies { l = append(l, NewAPIPolicyFromTableStruct(p)) } return l }(), } } func NewPolicyFromApiStruct(a *Policy) (*table.Policy, error) { if a.Name == "" { return nil, fmt.Errorf("empty policy name") } stmts := make([]*table.Statement, 0, len(a.Statements)) for idx, x := range a.Statements { if x.Name == "" { x.Name = fmt.Sprintf("%s_stmt%d", a.Name, idx) } y, err := NewStatementFromApiStruct(x) if err != nil { return nil, err } stmts = append(stmts, y) } return &table.Policy{ Name: a.Name, Statements: stmts, }, nil } func NewRoaListFromTableStructList(origin []*table.ROA) []*Roa { l := make([]*Roa, 0) for _, r := range origin { host, port, _ := net.SplitHostPort(r.Src) l = append(l, &Roa{ As: r.AS, Maxlen: uint32(r.MaxLen), Prefixlen: uint32(r.Prefix.Length), Prefix: r.Prefix.Prefix.String(), Conf: &RPKIConf{ Address: host, RemotePort: port, }, }) } return l } func (s *Server) GetPolicy(ctx context.Context, arg *GetPolicyRequest) (*GetPolicyResponse, error) { l := make([]*Policy, 0) for _, p := range s.bgpServer.GetPolicy() { l = append(l, toPolicyApi(p)) } return &GetPolicyResponse{Policies: l}, nil } func (s *Server) AddPolicy(ctx context.Context, arg *AddPolicyRequest) (*AddPolicyResponse, error) { if arg == nil || arg.Policy == nil { return nil, fmt.Errorf("invalid request") } x, err := NewPolicyFromApiStruct(arg.Policy) if err != nil { return nil, err } return &AddPolicyResponse{}, s.bgpServer.AddPolicy(x, arg.ReferExistingStatements) } func (s *Server) DeletePolicy(ctx context.Context, arg *DeletePolicyRequest) (*DeletePolicyResponse, error) { if arg == nil || arg.Policy == nil { return nil, fmt.Errorf("invalid request") } x, err := NewPolicyFromApiStruct(arg.Policy) if err != nil { return nil, err } return &DeletePolicyResponse{}, s.bgpServer.DeletePolicy(x, arg.All, arg.PreserveStatements) } func (s *Server) ReplacePolicy(ctx context.Context, arg *ReplacePolicyRequest) (*ReplacePolicyResponse, error) { if arg == nil || arg.Policy == nil { return nil, fmt.Errorf("invalid request") } x, err := NewPolicyFromApiStruct(arg.Policy) if err != nil { return nil, err } return &ReplacePolicyResponse{}, s.bgpServer.ReplacePolicy(x, arg.ReferExistingStatements, arg.PreserveStatements) } func toPolicyAssignmentName(a *PolicyAssignment) (string, table.PolicyDirection, error) { switch a.Resource { case Resource_GLOBAL: switch a.Type { case PolicyType_IMPORT: return "", table.POLICY_DIRECTION_IMPORT, nil case PolicyType_EXPORT: return "", table.POLICY_DIRECTION_EXPORT, nil default: return "", table.POLICY_DIRECTION_NONE, fmt.Errorf("invalid policy type") } case Resource_LOCAL: switch a.Type { case PolicyType_IN: return a.Name, table.POLICY_DIRECTION_IN, nil case PolicyType_IMPORT: return a.Name, table.POLICY_DIRECTION_IMPORT, nil case PolicyType_EXPORT: return a.Name, table.POLICY_DIRECTION_EXPORT, nil default: return "", table.POLICY_DIRECTION_NONE, fmt.Errorf("invalid policy type") } default: return "", table.POLICY_DIRECTION_NONE, fmt.Errorf("invalid resource type") } } func (s *Server) GetPolicyAssignment(ctx context.Context, arg *GetPolicyAssignmentRequest) (*GetPolicyAssignmentResponse, error) { if arg == nil || arg.Assignment == nil { return nil, fmt.Errorf("invalid request") } name, dir, err := toPolicyAssignmentName(arg.Assignment) if err != nil { return nil, err } def, pols, err := s.bgpServer.GetPolicyAssignment(name, dir) if err != nil { return nil, err } policies := make([]*table.Policy, 0, len(pols)) for _, p := range pols { t, err := table.NewPolicy(*p) if err != nil { return nil, err } policies = append(policies, t) } t := &table.PolicyAssignment{ Name: name, Type: dir, Default: def, Policies: policies, } return &GetPolicyAssignmentResponse{NewAPIPolicyAssignmentFromTableStruct(t)}, err } func defaultRouteType(d RouteAction) table.RouteType { switch d { case RouteAction_ACCEPT: return table.ROUTE_TYPE_ACCEPT case RouteAction_REJECT: return table.ROUTE_TYPE_REJECT default: return table.ROUTE_TYPE_NONE } } func toPolicyDefinition(policies []*Policy) []*config.PolicyDefinition { l := make([]*config.PolicyDefinition, 0, len(policies)) for _, p := range policies { l = append(l, &config.PolicyDefinition{Name: p.Name}) } return l } func (s *Server) AddPolicyAssignment(ctx context.Context, arg *AddPolicyAssignmentRequest) (*AddPolicyAssignmentResponse, error) { if arg == nil || arg.Assignment == nil { return nil, fmt.Errorf("invalid request") } name, dir, err := toPolicyAssignmentName(arg.Assignment) if err != nil { return nil, err } return &AddPolicyAssignmentResponse{}, s.bgpServer.AddPolicyAssignment(name, dir, toPolicyDefinition(arg.Assignment.Policies), defaultRouteType(arg.Assignment.Default)) } func (s *Server) DeletePolicyAssignment(ctx context.Context, arg *DeletePolicyAssignmentRequest) (*DeletePolicyAssignmentResponse, error) { if arg == nil || arg.Assignment == nil { return nil, fmt.Errorf("invalid request") } name, dir, err := toPolicyAssignmentName(arg.Assignment) if err != nil { return nil, err } return &DeletePolicyAssignmentResponse{}, s.bgpServer.DeletePolicyAssignment(name, dir, toPolicyDefinition(arg.Assignment.Policies), arg.All) } func (s *Server) ReplacePolicyAssignment(ctx context.Context, arg *ReplacePolicyAssignmentRequest) (*ReplacePolicyAssignmentResponse, error) { if arg == nil || arg.Assignment == nil { return nil, fmt.Errorf("invalid request") } name, dir, err := toPolicyAssignmentName(arg.Assignment) if err != nil { return nil, err } return &ReplacePolicyAssignmentResponse{}, s.bgpServer.ReplacePolicyAssignment(name, dir, toPolicyDefinition(arg.Assignment.Policies), defaultRouteType(arg.Assignment.Default)) } func (s *Server) GetServer(ctx context.Context, arg *GetServerRequest) (*GetServerResponse, error) { g := s.bgpServer.GetServer() return &GetServerResponse{ Global: &Global{ As: g.Config.As, RouterId: g.Config.RouterId, ListenPort: g.Config.Port, ListenAddresses: g.Config.LocalAddressList, UseMultiplePaths: g.UseMultiplePaths.Config.Enabled, }, }, nil } func (s *Server) StartServer(ctx context.Context, arg *StartServerRequest) (*StartServerResponse, error) { if arg == nil || arg.Global == nil { return nil, fmt.Errorf("invalid request") } g := arg.Global if net.ParseIP(g.RouterId) == nil { return nil, fmt.Errorf("invalid router-id format: %s", g.RouterId) } families := make([]config.AfiSafi, 0, len(g.Families)) for _, f := range g.Families { name := config.AfiSafiType(bgp.RouteFamily(f).String()) families = append(families, config.AfiSafi{ Config: config.AfiSafiConfig{ AfiSafiName: name, Enabled: true, }, State: config.AfiSafiState{ AfiSafiName: name, }, }) } b := &config.BgpConfigSet{ Global: config.Global{ Config: config.GlobalConfig{ As: g.As, RouterId: g.RouterId, Port: g.ListenPort, LocalAddressList: g.ListenAddresses, }, AfiSafis: families, UseMultiplePaths: config.UseMultiplePaths{ Config: config.UseMultiplePathsConfig{ Enabled: g.UseMultiplePaths, }, }, }, } return &StartServerResponse{}, s.bgpServer.Start(&b.Global) } func (s *Server) StopServer(ctx context.Context, arg *StopServerRequest) (*StopServerResponse, error) { return &StopServerResponse{}, s.bgpServer.Stop() } func (s *Server) GetRibInfo(ctx context.Context, arg *GetRibInfoRequest) (*GetRibInfoResponse, error) { if arg == nil || arg.Info == nil { return nil, fmt.Errorf("invalid request") } family := bgp.RouteFamily(arg.Info.Family) var in bool var err error var info *table.TableInfo switch arg.Info.Type { case Resource_GLOBAL, Resource_LOCAL: info, err = s.bgpServer.GetRibInfo(arg.Info.Name, family) case Resource_ADJ_IN: in = true fallthrough case Resource_ADJ_OUT: info, err = s.bgpServer.GetAdjRibInfo(arg.Info.Name, family, in) default: return nil, fmt.Errorf("unsupported resource type: %s", arg.Info.Type) } if err != nil { return nil, err } return &GetRibInfoResponse{ Info: &TableInfo{ Type: arg.Info.Type, Family: arg.Info.Family, Name: arg.Info.Name, NumDestination: uint64(info.NumDestination), NumPath: uint64(info.NumPath), NumAccepted: uint64(info.NumAccepted), }, }, nil } gobgp-1.29/api/util.go000066400000000000000000000123341324612745600146350ustar00rootroot00000000000000// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package gobgpapi import ( "fmt" "net" "time" "github.com/osrg/gobgp/config" "github.com/osrg/gobgp/packet/bgp" "github.com/osrg/gobgp/table" ) type ToNativeOption struct { LocalAS uint32 LocalID net.IP RouteReflectorClient bool RouteReflectorClusterID net.IP NLRI bgp.AddrPrefixInterface } func (t *Table) ToNativeTable(option ...ToNativeOption) (*table.Table, error) { dsts := make([]*table.Destination, 0, len(t.Destinations)) for _, d := range t.Destinations { dst, err := d.ToNativeDestination(option...) if err != nil { return nil, err } dsts = append(dsts, dst) } return table.NewTable(bgp.RouteFamily(t.Family), dsts...), nil } func getNLRI(family bgp.RouteFamily, buf []byte) (bgp.AddrPrefixInterface, error) { afi, safi := bgp.RouteFamilyToAfiSafi(family) nlri, err := bgp.NewPrefixFromRouteFamily(afi, safi) if err != nil { return nil, err } if err := nlri.DecodeFromBytes(buf); err != nil { return nil, err } return nlri, nil } func (d *Destination) ToNativeDestination(option ...ToNativeOption) (*table.Destination, error) { if len(d.Paths) == 0 { return nil, fmt.Errorf("no path in destination") } nlri, err := getNLRI(bgp.RouteFamily(d.Paths[0].Family), d.Paths[0].Nlri) if err != nil { return nil, err } option = append(option, ToNativeOption{ NLRI: nlri, }) paths := make([]*table.Path, 0, len(d.Paths)) for _, p := range d.Paths { var path *table.Path var err error if p.Identifier > 0 { path, err = p.ToNativePath() } else { path, err = p.ToNativePath(option...) } if err != nil { return nil, err } paths = append(paths, path) } return table.NewDestination(nlri, 0, paths...), nil } func (p *Path) GetNativeNlri() (bgp.AddrPrefixInterface, error) { return getNLRI(bgp.RouteFamily(p.Family), p.Nlri) } func (p *Path) ToNativePath(option ...ToNativeOption) (*table.Path, error) { info := &table.PeerInfo{ AS: p.SourceAsn, ID: net.ParseIP(p.SourceId), Address: net.ParseIP(p.NeighborIp), } var nlri bgp.AddrPrefixInterface for _, o := range option { info.LocalAS = o.LocalAS info.LocalID = o.LocalID info.RouteReflectorClient = o.RouteReflectorClient info.RouteReflectorClusterID = o.RouteReflectorClusterID nlri = o.NLRI } if nlri == nil { var err error nlri, err = getNLRI(bgp.RouteFamily(p.Family), p.Nlri) if err != nil { return nil, err } } pattr := make([]bgp.PathAttributeInterface, 0, len(p.Pattrs)) for _, attr := range p.Pattrs { p, err := bgp.GetPathAttribute(attr) if err != nil { return nil, err } err = p.DecodeFromBytes(attr) if err != nil { return nil, err } pattr = append(pattr, p) } t := time.Unix(p.Age, 0) nlri.SetPathIdentifier(p.Identifier) nlri.SetPathLocalIdentifier(p.LocalIdentifier) path := table.NewPath(info, nlri, p.IsWithdraw, pattr, t, false) path.SetValidation(&table.Validation{ Status: config.IntToRpkiValidationResultTypeMap[int(p.Validation)], Reason: table.IntToRpkiValidationReasonTypeMap[int(p.ValidationDetail.Reason)], Matched: NewROAListFromApiStructList(p.ValidationDetail.Matched), UnmatchedAs: NewROAListFromApiStructList(p.ValidationDetail.UnmatchedAs), UnmatchedLength: NewROAListFromApiStructList(p.ValidationDetail.UnmatchedLength), }) path.MarkStale(p.Stale) path.SetUUID(p.Uuid) if p.Filtered { path.Filter("", table.POLICY_DIRECTION_IN) } path.IsNexthopInvalid = p.IsNexthopInvalid return path, nil } func NewROAListFromApiStructList(l []*Roa) []*table.ROA { roas := make([]*table.ROA, 0, len(l)) for _, r := range l { ip := net.ParseIP(r.Prefix) rf := func(prefix string) bgp.RouteFamily { a, _, _ := net.ParseCIDR(prefix) if a.To4() != nil { return bgp.RF_IPv4_UC } else { return bgp.RF_IPv6_UC } }(r.Prefix) afi, _ := bgp.RouteFamilyToAfiSafi(rf) roa := table.NewROA(int(afi), []byte(ip), uint8(r.Prefixlen), uint8(r.Maxlen), r.As, net.JoinHostPort(r.Conf.Address, r.Conf.RemotePort)) roas = append(roas, roa) } return roas } func extractFamilyFromConfigAfiSafi(c *config.AfiSafi) uint32 { if c == nil { return 0 } // If address family value is already stored in AfiSafiState structure, // we prefer to use this value. if c.State.Family != 0 { return uint32(c.State.Family) } // In case that Neighbor structure came from CLI or gRPC, address family // value in AfiSafiState structure can be omitted. // Here extracts value from AfiSafiName field in AfiSafiConfig structure. if rf, err := bgp.GetRouteFamily(string(c.Config.AfiSafiName)); err == nil { return uint32(rf) } // Ignores invalid address family name return 0 } gobgp-1.29/client/000077500000000000000000000000001324612745600140335ustar00rootroot00000000000000gobgp-1.29/client/client.go000066400000000000000000000667631324612745600156620ustar00rootroot00000000000000// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. // Package client provides a wrapper for GoBGP's gRPC API package client import ( "fmt" "io" "net" "strconv" "time" "golang.org/x/net/context" "google.golang.org/grpc" api "github.com/osrg/gobgp/api" "github.com/osrg/gobgp/config" "github.com/osrg/gobgp/packet/bgp" "github.com/osrg/gobgp/table" ) type Client struct { conn *grpc.ClientConn cli api.GobgpApiClient } func defaultGRPCOptions() []grpc.DialOption { return []grpc.DialOption{grpc.WithTimeout(time.Second), grpc.WithBlock(), grpc.WithInsecure()} } // New returns a new Client using the given target and options for dialing // to the grpc server. If an error occurs during dialing it will be returned and // Client will be nil. func New(target string, opts ...grpc.DialOption) (*Client, error) { return NewWith(context.Background(), target, opts...) } // NewWith is like New, but uses the given ctx to cancel or expire the current // attempt to connect if it becomes Done before the connection succeeds. func NewWith(ctx context.Context, target string, opts ...grpc.DialOption) (*Client, error) { if target == "" { target = ":50051" } if len(opts) == 0 { opts = defaultGRPCOptions() } conn, err := grpc.DialContext(ctx, target, opts...) if err != nil { return nil, err } cli := api.NewGobgpApiClient(conn) return &Client{conn: conn, cli: cli}, nil } // NewFrom returns a new Client, using the given conn and cli for the // underlying connection. The given grpc.ClientConn connection is expected to be // initialized and paired with the api client. See New to have the connection // dialed for you. func NewFrom(conn *grpc.ClientConn, cli api.GobgpApiClient) *Client { return &Client{conn: conn, cli: cli} } func (cli *Client) Close() error { return cli.conn.Close() } func (cli *Client) StartServer(c *config.Global) error { _, err := cli.cli.StartServer(context.Background(), &api.StartServerRequest{ Global: &api.Global{ As: c.Config.As, RouterId: c.Config.RouterId, ListenPort: c.Config.Port, ListenAddresses: c.Config.LocalAddressList, UseMultiplePaths: c.UseMultiplePaths.Config.Enabled, }, }) return err } func (cli *Client) StopServer() error { _, err := cli.cli.StopServer(context.Background(), &api.StopServerRequest{}) return err } func (cli *Client) GetServer() (*config.Global, error) { ret, err := cli.cli.GetServer(context.Background(), &api.GetServerRequest{}) if err != nil { return nil, err } return &config.Global{ Config: config.GlobalConfig{ As: ret.Global.As, RouterId: ret.Global.RouterId, Port: ret.Global.ListenPort, LocalAddressList: ret.Global.ListenAddresses, }, UseMultiplePaths: config.UseMultiplePaths{ Config: config.UseMultiplePathsConfig{ Enabled: ret.Global.UseMultiplePaths, }, }, }, nil } func (cli *Client) EnableZebra(c *config.Zebra) error { req := &api.EnableZebraRequest{ Url: c.Config.Url, Version: uint32(c.Config.Version), NexthopTriggerEnable: c.Config.NexthopTriggerEnable, NexthopTriggerDelay: uint32(c.Config.NexthopTriggerDelay), } for _, t := range c.Config.RedistributeRouteTypeList { req.RouteTypes = append(req.RouteTypes, string(t)) } _, err := cli.cli.EnableZebra(context.Background(), req) return err } func (cli *Client) getNeighbor(name string, afi int, vrf string, enableAdvertised bool) ([]*config.Neighbor, error) { ret, err := cli.cli.GetNeighbor(context.Background(), &api.GetNeighborRequest{EnableAdvertised: enableAdvertised, Address: name}) if err != nil { return nil, err } neighbors := make([]*config.Neighbor, 0, len(ret.Peers)) for _, p := range ret.Peers { if name != "" && name != p.Info.NeighborAddress && name != p.Conf.NeighborInterface { continue } if vrf != "" && name != p.Conf.Vrf { continue } if afi > 0 { v6 := net.ParseIP(p.Info.NeighborAddress).To4() == nil if afi == bgp.AFI_IP && v6 || afi == bgp.AFI_IP6 && !v6 { continue } } n, err := api.NewNeighborFromAPIStruct(p) if err != nil { return nil, err } neighbors = append(neighbors, n) } return neighbors, nil } func (cli *Client) ListNeighbor() ([]*config.Neighbor, error) { return cli.getNeighbor("", 0, "", false) } func (cli *Client) ListNeighborByTransport(afi int) ([]*config.Neighbor, error) { return cli.getNeighbor("", afi, "", false) } func (cli *Client) ListNeighborByVRF(vrf string) ([]*config.Neighbor, error) { return cli.getNeighbor("", 0, vrf, false) } func (cli *Client) GetNeighbor(name string, options ...bool) (*config.Neighbor, error) { enableAdvertised := false if len(options) > 0 && options[0] { enableAdvertised = true } ns, err := cli.getNeighbor(name, 0, "", enableAdvertised) if err != nil { return nil, err } if len(ns) == 0 { return nil, fmt.Errorf("not found neighbor %s", name) } return ns[0], nil } func (cli *Client) AddNeighbor(c *config.Neighbor) error { peer := api.NewPeerFromConfigStruct(c) _, err := cli.cli.AddNeighbor(context.Background(), &api.AddNeighborRequest{Peer: peer}) return err } func (cli *Client) DeleteNeighbor(c *config.Neighbor) error { peer := api.NewPeerFromConfigStruct(c) _, err := cli.cli.DeleteNeighbor(context.Background(), &api.DeleteNeighborRequest{Peer: peer}) return err } //func (cli *Client) UpdateNeighbor(c *config.Neighbor) (bool, error) { //} func (cli *Client) ShutdownNeighbor(addr, communication string) error { _, err := cli.cli.ShutdownNeighbor(context.Background(), &api.ShutdownNeighborRequest{Address: addr, Communication: communication}) return err } func (cli *Client) ResetNeighbor(addr, communication string) error { _, err := cli.cli.ResetNeighbor(context.Background(), &api.ResetNeighborRequest{Address: addr, Communication: communication}) return err } func (cli *Client) EnableNeighbor(addr string) error { _, err := cli.cli.EnableNeighbor(context.Background(), &api.EnableNeighborRequest{Address: addr}) return err } func (cli *Client) DisableNeighbor(addr, communication string) error { _, err := cli.cli.DisableNeighbor(context.Background(), &api.DisableNeighborRequest{Address: addr, Communication: communication}) return err } func (cli *Client) softreset(addr string, family bgp.RouteFamily, dir api.SoftResetNeighborRequest_SoftResetDirection) error { _, err := cli.cli.SoftResetNeighbor(context.Background(), &api.SoftResetNeighborRequest{ Address: addr, Direction: dir, }) return err } func (cli *Client) SoftResetIn(addr string, family bgp.RouteFamily) error { return cli.softreset(addr, family, api.SoftResetNeighborRequest_IN) } func (cli *Client) SoftResetOut(addr string, family bgp.RouteFamily) error { return cli.softreset(addr, family, api.SoftResetNeighborRequest_OUT) } func (cli *Client) SoftReset(addr string, family bgp.RouteFamily) error { return cli.softreset(addr, family, api.SoftResetNeighborRequest_BOTH) } func (cli *Client) getRIB(resource api.Resource, name string, family bgp.RouteFamily, prefixes []*table.LookupPrefix) (*table.Table, error) { prefixList := make([]*api.TableLookupPrefix, 0, len(prefixes)) for _, p := range prefixes { prefixList = append(prefixList, &api.TableLookupPrefix{ Prefix: p.Prefix, LookupOption: api.TableLookupOption(p.LookupOption), }) } stream, err := cli.cli.GetPath(context.Background(), &api.GetPathRequest{ Type: resource, Family: uint32(family), Name: name, Prefixes: prefixList, }) if err != nil { return nil, err } pathMap := make(map[string][]*table.Path) for { p, err := stream.Recv() if err != nil { if err == io.EOF { break } return nil, err } nlri, err := p.GetNativeNlri() if err != nil { return nil, err } var path *table.Path if p.Identifier > 0 { path, err = p.ToNativePath() } else { path, err = p.ToNativePath(api.ToNativeOption{ NLRI: nlri, }) } if err != nil { return nil, err } nlriStr := nlri.String() pathMap[nlriStr] = append(pathMap[nlriStr], path) } dstList := make([]*table.Destination, 0, len(pathMap)) for _, pathList := range pathMap { dstList = append(dstList, table.NewDestination(pathList[0].GetNlri(), 0, pathList...)) } return table.NewTable(family, dstList...), nil } func (cli *Client) GetRIB(family bgp.RouteFamily, prefixes []*table.LookupPrefix) (*table.Table, error) { return cli.getRIB(api.Resource_GLOBAL, "", family, prefixes) } func (cli *Client) GetLocalRIB(name string, family bgp.RouteFamily, prefixes []*table.LookupPrefix) (*table.Table, error) { return cli.getRIB(api.Resource_LOCAL, name, family, prefixes) } func (cli *Client) GetAdjRIBIn(name string, family bgp.RouteFamily, prefixes []*table.LookupPrefix) (*table.Table, error) { return cli.getRIB(api.Resource_ADJ_IN, name, family, prefixes) } func (cli *Client) GetAdjRIBOut(name string, family bgp.RouteFamily, prefixes []*table.LookupPrefix) (*table.Table, error) { return cli.getRIB(api.Resource_ADJ_OUT, name, family, prefixes) } func (cli *Client) GetVRFRIB(name string, family bgp.RouteFamily, prefixes []*table.LookupPrefix) (*table.Table, error) { return cli.getRIB(api.Resource_VRF, name, family, prefixes) } func (cli *Client) getRIBInfo(resource api.Resource, name string, family bgp.RouteFamily) (*table.TableInfo, error) { res, err := cli.cli.GetRibInfo(context.Background(), &api.GetRibInfoRequest{ Info: &api.TableInfo{ Type: resource, Name: name, Family: uint32(family), }, }) if err != nil { return nil, err } return &table.TableInfo{ NumDestination: int(res.Info.NumDestination), NumPath: int(res.Info.NumPath), NumAccepted: int(res.Info.NumAccepted), }, nil } func (cli *Client) GetRIBInfo(family bgp.RouteFamily) (*table.TableInfo, error) { return cli.getRIBInfo(api.Resource_GLOBAL, "", family) } func (cli *Client) GetLocalRIBInfo(name string, family bgp.RouteFamily) (*table.TableInfo, error) { return cli.getRIBInfo(api.Resource_LOCAL, name, family) } func (cli *Client) GetAdjRIBInInfo(name string, family bgp.RouteFamily) (*table.TableInfo, error) { return cli.getRIBInfo(api.Resource_ADJ_IN, name, family) } func (cli *Client) GetAdjRIBOutInfo(name string, family bgp.RouteFamily) (*table.TableInfo, error) { return cli.getRIBInfo(api.Resource_ADJ_OUT, name, family) } type AddPathByStreamClient struct { stream api.GobgpApi_InjectMrtClient } func (c *AddPathByStreamClient) Send(paths ...*table.Path) error { ps := make([]*api.Path, 0, len(paths)) for _, p := range paths { ps = append(ps, api.ToPathApi(p)) } return c.stream.Send(&api.InjectMrtRequest{ Resource: api.Resource_GLOBAL, Paths: ps, }) } func (c *AddPathByStreamClient) Close() error { _, err := c.stream.CloseAndRecv() return err } func (cli *Client) AddPathByStream() (*AddPathByStreamClient, error) { stream, err := cli.cli.InjectMrt(context.Background()) if err != nil { return nil, err } return &AddPathByStreamClient{stream}, nil } func (cli *Client) addPath(vrfID string, pathList []*table.Path) ([]byte, error) { resource := api.Resource_GLOBAL if vrfID != "" { resource = api.Resource_VRF } var uuid []byte for _, path := range pathList { r, err := cli.cli.AddPath(context.Background(), &api.AddPathRequest{ Resource: resource, VrfId: vrfID, Path: api.ToPathApi(path), }) if err != nil { return nil, err } uuid = r.Uuid } return uuid, nil } func (cli *Client) AddPath(pathList []*table.Path) ([]byte, error) { return cli.addPath("", pathList) } func (cli *Client) AddVRFPath(vrfID string, pathList []*table.Path) ([]byte, error) { if vrfID == "" { return nil, fmt.Errorf("VRF ID is empty") } return cli.addPath(vrfID, pathList) } func (cli *Client) deletePath(uuid []byte, f bgp.RouteFamily, vrfID string, pathList []*table.Path) error { var reqs []*api.DeletePathRequest resource := api.Resource_GLOBAL if vrfID != "" { resource = api.Resource_VRF } switch { case len(pathList) != 0: for _, path := range pathList { nlri := path.GetNlri() n, err := nlri.Serialize() if err != nil { return err } p := &api.Path{ Nlri: n, Family: uint32(path.GetRouteFamily()), Identifier: nlri.PathIdentifier(), LocalIdentifier: nlri.PathLocalIdentifier(), } reqs = append(reqs, &api.DeletePathRequest{ Resource: resource, VrfId: vrfID, Path: p, }) } default: reqs = append(reqs, &api.DeletePathRequest{ Resource: resource, VrfId: vrfID, Uuid: uuid, Family: uint32(f), }) } for _, req := range reqs { if _, err := cli.cli.DeletePath(context.Background(), req); err != nil { return err } } return nil } func (cli *Client) DeletePath(pathList []*table.Path) error { return cli.deletePath(nil, bgp.RouteFamily(0), "", pathList) } func (cli *Client) DeleteVRFPath(vrfID string, pathList []*table.Path) error { if vrfID == "" { return fmt.Errorf("VRF ID is empty") } return cli.deletePath(nil, bgp.RouteFamily(0), vrfID, pathList) } func (cli *Client) DeletePathByUUID(uuid []byte) error { return cli.deletePath(uuid, bgp.RouteFamily(0), "", nil) } func (cli *Client) DeletePathByFamily(family bgp.RouteFamily) error { return cli.deletePath(nil, family, "", nil) } func (cli *Client) GetVRF() ([]*table.Vrf, error) { ret, err := cli.cli.GetVrf(context.Background(), &api.GetVrfRequest{}) if err != nil { return nil, err } var vrfs []*table.Vrf f := func(bufs [][]byte) ([]bgp.ExtendedCommunityInterface, error) { ret := make([]bgp.ExtendedCommunityInterface, 0, len(bufs)) for _, rt := range bufs { r, err := bgp.ParseExtended(rt) if err != nil { return nil, err } ret = append(ret, r) } return ret, nil } for _, vrf := range ret.Vrfs { importRT, err := f(vrf.ImportRt) if err != nil { return nil, err } exportRT, err := f(vrf.ExportRt) if err != nil { return nil, err } vrfs = append(vrfs, &table.Vrf{ Name: vrf.Name, Id: vrf.Id, Rd: bgp.GetRouteDistinguisher(vrf.Rd), ImportRt: importRT, ExportRt: exportRT, }) } return vrfs, nil } func (cli *Client) AddVRF(name string, id int, rd bgp.RouteDistinguisherInterface, im, ex []bgp.ExtendedCommunityInterface) error { buf, err := rd.Serialize() if err != nil { return err } f := func(comms []bgp.ExtendedCommunityInterface) ([][]byte, error) { var bufs [][]byte for _, c := range comms { buf, err := c.Serialize() if err != nil { return nil, err } bufs = append(bufs, buf) } return bufs, err } importRT, err := f(im) if err != nil { return err } exportRT, err := f(ex) if err != nil { return err } arg := &api.AddVrfRequest{ Vrf: &api.Vrf{ Name: name, Rd: buf, Id: uint32(id), ImportRt: importRT, ExportRt: exportRT, }, } _, err = cli.cli.AddVrf(context.Background(), arg) return err } func (cli *Client) DeleteVRF(name string) error { arg := &api.DeleteVrfRequest{ Vrf: &api.Vrf{ Name: name, }, } _, err := cli.cli.DeleteVrf(context.Background(), arg) return err } func (cli *Client) getDefinedSet(typ table.DefinedType, name string) ([]table.DefinedSet, error) { ret, err := cli.cli.GetDefinedSet(context.Background(), &api.GetDefinedSetRequest{ Type: api.DefinedType(typ), Name: name, }) if err != nil { return nil, err } ds := make([]table.DefinedSet, 0, len(ret.Sets)) for _, s := range ret.Sets { d, err := api.NewDefinedSetFromApiStruct(s) if err != nil { return nil, err } ds = append(ds, d) } return ds, nil } func (cli *Client) GetDefinedSet(typ table.DefinedType) ([]table.DefinedSet, error) { return cli.getDefinedSet(typ, "") } func (cli *Client) GetDefinedSetByName(typ table.DefinedType, name string) (table.DefinedSet, error) { sets, err := cli.getDefinedSet(typ, name) if err != nil { return nil, err } if len(sets) == 0 { return nil, fmt.Errorf("not found defined set: %s", name) } else if len(sets) > 1 { return nil, fmt.Errorf("invalid response for GetDefinedSetByName") } return sets[0], nil } func (cli *Client) AddDefinedSet(d table.DefinedSet) error { a, err := api.NewAPIDefinedSetFromTableStruct(d) if err != nil { return err } _, err = cli.cli.AddDefinedSet(context.Background(), &api.AddDefinedSetRequest{ Set: a, }) return err } func (cli *Client) DeleteDefinedSet(d table.DefinedSet, all bool) error { a, err := api.NewAPIDefinedSetFromTableStruct(d) if err != nil { return err } _, err = cli.cli.DeleteDefinedSet(context.Background(), &api.DeleteDefinedSetRequest{ Set: a, All: all, }) return err } func (cli *Client) ReplaceDefinedSet(d table.DefinedSet) error { a, err := api.NewAPIDefinedSetFromTableStruct(d) if err != nil { return err } _, err = cli.cli.ReplaceDefinedSet(context.Background(), &api.ReplaceDefinedSetRequest{ Set: a, }) return err } func (cli *Client) GetStatement() ([]*table.Statement, error) { ret, err := cli.cli.GetStatement(context.Background(), &api.GetStatementRequest{}) if err != nil { return nil, err } sts := make([]*table.Statement, 0, len(ret.Statements)) for _, s := range ret.Statements { st, err := api.NewStatementFromApiStruct(s) if err != nil { return nil, err } sts = append(sts, st) } return sts, nil } func (cli *Client) AddStatement(t *table.Statement) error { a := api.NewAPIStatementFromTableStruct(t) _, err := cli.cli.AddStatement(context.Background(), &api.AddStatementRequest{ Statement: a, }) return err } func (cli *Client) DeleteStatement(t *table.Statement, all bool) error { a := api.NewAPIStatementFromTableStruct(t) _, err := cli.cli.DeleteStatement(context.Background(), &api.DeleteStatementRequest{ Statement: a, All: all, }) return err } func (cli *Client) ReplaceStatement(t *table.Statement) error { a := api.NewAPIStatementFromTableStruct(t) _, err := cli.cli.ReplaceStatement(context.Background(), &api.ReplaceStatementRequest{ Statement: a, }) return err } func (cli *Client) GetPolicy() ([]*table.Policy, error) { ret, err := cli.cli.GetPolicy(context.Background(), &api.GetPolicyRequest{}) if err != nil { return nil, err } pols := make([]*table.Policy, 0, len(ret.Policies)) for _, p := range ret.Policies { pol, err := api.NewPolicyFromApiStruct(p) if err != nil { return nil, err } pols = append(pols, pol) } return pols, nil } func (cli *Client) AddPolicy(t *table.Policy, refer bool) error { a := api.NewAPIPolicyFromTableStruct(t) _, err := cli.cli.AddPolicy(context.Background(), &api.AddPolicyRequest{ Policy: a, ReferExistingStatements: refer, }) return err } func (cli *Client) DeletePolicy(t *table.Policy, all, preserve bool) error { a := api.NewAPIPolicyFromTableStruct(t) _, err := cli.cli.DeletePolicy(context.Background(), &api.DeletePolicyRequest{ Policy: a, All: all, PreserveStatements: preserve, }) return err } func (cli *Client) ReplacePolicy(t *table.Policy, refer, preserve bool) error { a := api.NewAPIPolicyFromTableStruct(t) _, err := cli.cli.ReplacePolicy(context.Background(), &api.ReplacePolicyRequest{ Policy: a, ReferExistingStatements: refer, PreserveStatements: preserve, }) return err } func (cli *Client) getPolicyAssignment(name string, dir table.PolicyDirection) (*table.PolicyAssignment, error) { var typ api.PolicyType switch dir { case table.POLICY_DIRECTION_IN: typ = api.PolicyType_IN case table.POLICY_DIRECTION_IMPORT: typ = api.PolicyType_IMPORT case table.POLICY_DIRECTION_EXPORT: typ = api.PolicyType_EXPORT } resource := api.Resource_GLOBAL if name != "" { resource = api.Resource_LOCAL } ret, err := cli.cli.GetPolicyAssignment(context.Background(), &api.GetPolicyAssignmentRequest{ Assignment: &api.PolicyAssignment{ Name: name, Resource: resource, Type: typ, }, }) if err != nil { return nil, err } def := table.ROUTE_TYPE_ACCEPT if ret.Assignment.Default == api.RouteAction_REJECT { def = table.ROUTE_TYPE_REJECT } pols := make([]*table.Policy, 0, len(ret.Assignment.Policies)) for _, p := range ret.Assignment.Policies { pol, err := api.NewPolicyFromApiStruct(p) if err != nil { return nil, err } pols = append(pols, pol) } return &table.PolicyAssignment{ Name: name, Type: dir, Policies: pols, Default: def, }, nil } func (cli *Client) GetImportPolicy() (*table.PolicyAssignment, error) { return cli.getPolicyAssignment("", table.POLICY_DIRECTION_IMPORT) } func (cli *Client) GetExportPolicy() (*table.PolicyAssignment, error) { return cli.getPolicyAssignment("", table.POLICY_DIRECTION_EXPORT) } func (cli *Client) GetRouteServerInPolicy(name string) (*table.PolicyAssignment, error) { return cli.getPolicyAssignment(name, table.POLICY_DIRECTION_IN) } func (cli *Client) GetRouteServerImportPolicy(name string) (*table.PolicyAssignment, error) { return cli.getPolicyAssignment(name, table.POLICY_DIRECTION_IMPORT) } func (cli *Client) GetRouteServerExportPolicy(name string) (*table.PolicyAssignment, error) { return cli.getPolicyAssignment(name, table.POLICY_DIRECTION_EXPORT) } func (cli *Client) AddPolicyAssignment(assignment *table.PolicyAssignment) error { _, err := cli.cli.AddPolicyAssignment(context.Background(), &api.AddPolicyAssignmentRequest{ Assignment: api.NewAPIPolicyAssignmentFromTableStruct(assignment), }) return err } func (cli *Client) DeletePolicyAssignment(assignment *table.PolicyAssignment, all bool) error { a := api.NewAPIPolicyAssignmentFromTableStruct(assignment) _, err := cli.cli.DeletePolicyAssignment(context.Background(), &api.DeletePolicyAssignmentRequest{ Assignment: a, All: all}) return err } func (cli *Client) ReplacePolicyAssignment(assignment *table.PolicyAssignment) error { _, err := cli.cli.ReplacePolicyAssignment(context.Background(), &api.ReplacePolicyAssignmentRequest{ Assignment: api.NewAPIPolicyAssignmentFromTableStruct(assignment), }) return err } //func (cli *Client) EnableMrt(c *config.MrtConfig) error { //} // //func (cli *Client) DisableMrt(c *config.MrtConfig) error { //} // func (cli *Client) GetRPKI() ([]*config.RpkiServer, error) { rsp, err := cli.cli.GetRpki(context.Background(), &api.GetRpkiRequest{}) if err != nil { return nil, err } servers := make([]*config.RpkiServer, 0, len(rsp.Servers)) for _, s := range rsp.Servers { // Note: RpkiServerConfig.Port is uint32 type, but the TCP/UDP port is // 16-bit length. port, err := strconv.ParseUint(s.Conf.RemotePort, 10, 16) if err != nil { return nil, err } server := &config.RpkiServer{ Config: config.RpkiServerConfig{ Address: s.Conf.Address, Port: uint32(port), }, State: config.RpkiServerState{ Up: s.State.Up, SerialNumber: s.State.Serial, RecordsV4: s.State.RecordIpv4, RecordsV6: s.State.RecordIpv6, PrefixesV4: s.State.PrefixIpv4, PrefixesV6: s.State.PrefixIpv6, Uptime: s.State.Uptime, Downtime: s.State.Downtime, RpkiMessages: config.RpkiMessages{ RpkiReceived: config.RpkiReceived{ SerialNotify: s.State.SerialNotify, CacheReset: s.State.CacheReset, CacheResponse: s.State.CacheResponse, Ipv4Prefix: s.State.ReceivedIpv4, Ipv6Prefix: s.State.ReceivedIpv6, EndOfData: s.State.EndOfData, Error: s.State.Error, }, RpkiSent: config.RpkiSent{ SerialQuery: s.State.SerialQuery, ResetQuery: s.State.ResetQuery, }, }, }, } servers = append(servers, server) } return servers, nil } func (cli *Client) GetROA(family bgp.RouteFamily) ([]*table.ROA, error) { rsp, err := cli.cli.GetRoa(context.Background(), &api.GetRoaRequest{ Family: uint32(family), }) if err != nil { return nil, err } return api.NewROAListFromApiStructList(rsp.Roas), nil } func (cli *Client) AddRPKIServer(address string, port, lifetime int) error { _, err := cli.cli.AddRpki(context.Background(), &api.AddRpkiRequest{ Address: address, Port: uint32(port), Lifetime: int64(lifetime), }) return err } func (cli *Client) DeleteRPKIServer(address string) error { _, err := cli.cli.DeleteRpki(context.Background(), &api.DeleteRpkiRequest{ Address: address, }) return err } func (cli *Client) EnableRPKIServer(address string) error { _, err := cli.cli.EnableRpki(context.Background(), &api.EnableRpkiRequest{ Address: address, }) return err } func (cli *Client) DisableRPKIServer(address string) error { _, err := cli.cli.DisableRpki(context.Background(), &api.DisableRpkiRequest{ Address: address, }) return err } func (cli *Client) ResetRPKIServer(address string) error { _, err := cli.cli.ResetRpki(context.Background(), &api.ResetRpkiRequest{ Address: address, }) return err } func (cli *Client) SoftResetRPKIServer(address string) error { _, err := cli.cli.SoftResetRpki(context.Background(), &api.SoftResetRpkiRequest{ Address: address, }) return err } func (cli *Client) ValidateRIBWithRPKI(prefixes ...string) error { req := &api.ValidateRibRequest{} if len(prefixes) > 1 { return fmt.Errorf("too many prefixes: %d", len(prefixes)) } else if len(prefixes) == 1 { req.Prefix = prefixes[0] } _, err := cli.cli.ValidateRib(context.Background(), req) return err } func (cli *Client) AddBMP(c *config.BmpServerConfig) error { _, err := cli.cli.AddBmp(context.Background(), &api.AddBmpRequest{ Address: c.Address, Port: c.Port, Type: api.AddBmpRequest_MonitoringPolicy(c.RouteMonitoringPolicy.ToInt()), }) return err } func (cli *Client) DeleteBMP(c *config.BmpServerConfig) error { _, err := cli.cli.DeleteBmp(context.Background(), &api.DeleteBmpRequest{ Address: c.Address, Port: c.Port, }) return err } type MonitorRIBClient struct { stream api.GobgpApi_MonitorRibClient } func (c *MonitorRIBClient) Recv() (*table.Destination, error) { d, err := c.stream.Recv() if err != nil { return nil, err } return d.ToNativeDestination() } func (cli *Client) MonitorRIB(family bgp.RouteFamily, current bool) (*MonitorRIBClient, error) { stream, err := cli.cli.MonitorRib(context.Background(), &api.MonitorRibRequest{ Table: &api.Table{ Type: api.Resource_GLOBAL, Family: uint32(family), }, Current: current, }) if err != nil { return nil, err } return &MonitorRIBClient{stream}, nil } func (cli *Client) MonitorAdjRIBIn(name string, family bgp.RouteFamily, current bool) (*MonitorRIBClient, error) { stream, err := cli.cli.MonitorRib(context.Background(), &api.MonitorRibRequest{ Table: &api.Table{ Type: api.Resource_ADJ_IN, Name: name, Family: uint32(family), }, Current: current, }) if err != nil { return nil, err } return &MonitorRIBClient{stream}, nil } type MonitorNeighborStateClient struct { stream api.GobgpApi_MonitorPeerStateClient } func (c *MonitorNeighborStateClient) Recv() (*config.Neighbor, error) { p, err := c.stream.Recv() if err != nil { return nil, err } return api.NewNeighborFromAPIStruct(p) } func (cli *Client) MonitorNeighborState(name string, current bool) (*MonitorNeighborStateClient, error) { stream, err := cli.cli.MonitorPeerState(context.Background(), &api.Arguments{ Name: name, Current: current, }) if err != nil { return nil, err } return &MonitorNeighborStateClient{stream}, nil } gobgp-1.29/client/client_test.go000066400000000000000000000027311324612745600167020ustar00rootroot00000000000000// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package client import ( "testing" "time" api "github.com/osrg/gobgp/api" "github.com/osrg/gobgp/config" "github.com/osrg/gobgp/server" "github.com/stretchr/testify/assert" ) func TestGetNeighbor(test *testing.T) { assert := assert.New(test) s := server.NewBgpServer() go s.Serve() g := api.NewGrpcServer(s, ":50051") go g.Serve() time.Sleep(time.Second) cli, err := New("") assert.Nil(err) err = cli.StartServer(&config.Global{ Config: config.GlobalConfig{ As: 1, RouterId: "1.1.1.1", Port: 1790, }, }) assert.Nil(err) err = cli.AddNeighbor(&config.Neighbor{ Config: config.NeighborConfig{ NeighborAddress: "10.0.0.1", PeerAs: 2, }, }) assert.Nil(err) _, err = cli.GetNeighbor("10.0.0.1") assert.Nil(err) _, err = cli.GetNeighbor("10.0.0.2") assert.Equal(err.Error(), "not found neighbor 10.0.0.2") } gobgp-1.29/config/000077500000000000000000000000001324612745600140225ustar00rootroot00000000000000gobgp-1.29/config/bgp_configs.go000066400000000000000000006325711324612745600166470ustar00rootroot00000000000000// DO NOT EDIT // generated by pyang using OpenConfig https://github.com/openconfig/public // // Copyright (C) 2014-2016 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "fmt" "github.com/osrg/gobgp/packet/bgp" ) func mapkey(index int, name string) string { if name != "" { return name } return fmt.Sprintf("%v", index) } // typedef for typedef openconfig-types:std-regexp. type StdRegexp string // typedef for typedef openconfig-types:percentage. type Percentage uint8 // typedef for typedef bgp-types:rr-cluster-id-type. type RrClusterIdType string // typedef for identity bgp-types:remove-private-as-option. // set of options for configuring how private AS path numbers // are removed from advertisements. type RemovePrivateAsOption string const ( REMOVE_PRIVATE_AS_OPTION_ALL RemovePrivateAsOption = "all" REMOVE_PRIVATE_AS_OPTION_REPLACE RemovePrivateAsOption = "replace" ) var RemovePrivateAsOptionToIntMap = map[RemovePrivateAsOption]int{ REMOVE_PRIVATE_AS_OPTION_ALL: 0, REMOVE_PRIVATE_AS_OPTION_REPLACE: 1, } func (v RemovePrivateAsOption) ToInt() int { i, ok := RemovePrivateAsOptionToIntMap[v] if !ok { return -1 } return i } var IntToRemovePrivateAsOptionMap = map[int]RemovePrivateAsOption{ 0: REMOVE_PRIVATE_AS_OPTION_ALL, 1: REMOVE_PRIVATE_AS_OPTION_REPLACE, } func (v RemovePrivateAsOption) Validate() error { if _, ok := RemovePrivateAsOptionToIntMap[v]; !ok { return fmt.Errorf("invalid RemovePrivateAsOption: %s", v) } return nil } // typedef for typedef bgp-types:bgp-community-regexp-type. type BgpCommunityRegexpType StdRegexp // typedef for identity bgp-types:community-type. // type describing variations of community attributes: // STANDARD: standard BGP community [rfc1997] // EXTENDED: extended BGP community [rfc4360] // BOTH: both standard and extended community. type CommunityType string const ( COMMUNITY_TYPE_STANDARD CommunityType = "standard" COMMUNITY_TYPE_EXTENDED CommunityType = "extended" COMMUNITY_TYPE_BOTH CommunityType = "both" COMMUNITY_TYPE_NONE CommunityType = "none" ) var CommunityTypeToIntMap = map[CommunityType]int{ COMMUNITY_TYPE_STANDARD: 0, COMMUNITY_TYPE_EXTENDED: 1, COMMUNITY_TYPE_BOTH: 2, COMMUNITY_TYPE_NONE: 3, } func (v CommunityType) ToInt() int { i, ok := CommunityTypeToIntMap[v] if !ok { return -1 } return i } var IntToCommunityTypeMap = map[int]CommunityType{ 0: COMMUNITY_TYPE_STANDARD, 1: COMMUNITY_TYPE_EXTENDED, 2: COMMUNITY_TYPE_BOTH, 3: COMMUNITY_TYPE_NONE, } func (v CommunityType) Validate() error { if _, ok := CommunityTypeToIntMap[v]; !ok { return fmt.Errorf("invalid CommunityType: %s", v) } return nil } // typedef for typedef bgp-types:bgp-ext-community-type. type BgpExtCommunityType string // typedef for typedef bgp-types:bgp-std-community-type. type BgpStdCommunityType string // typedef for identity bgp-types:peer-type. // labels a peer or peer group as explicitly internal or // external. type PeerType string const ( PEER_TYPE_INTERNAL PeerType = "internal" PEER_TYPE_EXTERNAL PeerType = "external" ) var PeerTypeToIntMap = map[PeerType]int{ PEER_TYPE_INTERNAL: 0, PEER_TYPE_EXTERNAL: 1, } func (v PeerType) ToInt() int { i, ok := PeerTypeToIntMap[v] if !ok { return -1 } return i } var IntToPeerTypeMap = map[int]PeerType{ 0: PEER_TYPE_INTERNAL, 1: PEER_TYPE_EXTERNAL, } func (v PeerType) Validate() error { if _, ok := PeerTypeToIntMap[v]; !ok { return fmt.Errorf("invalid PeerType: %s", v) } return nil } // typedef for identity bgp-types:bgp-session-direction. // Type to describe the direction of NLRI transmission. type BgpSessionDirection string const ( BGP_SESSION_DIRECTION_INBOUND BgpSessionDirection = "inbound" BGP_SESSION_DIRECTION_OUTBOUND BgpSessionDirection = "outbound" ) var BgpSessionDirectionToIntMap = map[BgpSessionDirection]int{ BGP_SESSION_DIRECTION_INBOUND: 0, BGP_SESSION_DIRECTION_OUTBOUND: 1, } func (v BgpSessionDirection) ToInt() int { i, ok := BgpSessionDirectionToIntMap[v] if !ok { return -1 } return i } var IntToBgpSessionDirectionMap = map[int]BgpSessionDirection{ 0: BGP_SESSION_DIRECTION_INBOUND, 1: BGP_SESSION_DIRECTION_OUTBOUND, } func (v BgpSessionDirection) Validate() error { if _, ok := BgpSessionDirectionToIntMap[v]; !ok { return fmt.Errorf("invalid BgpSessionDirection: %s", v) } return nil } // typedef for identity bgp-types:bgp-origin-attr-type. // Type definition for standard BGP origin attribute. type BgpOriginAttrType string const ( BGP_ORIGIN_ATTR_TYPE_IGP BgpOriginAttrType = "igp" BGP_ORIGIN_ATTR_TYPE_EGP BgpOriginAttrType = "egp" BGP_ORIGIN_ATTR_TYPE_INCOMPLETE BgpOriginAttrType = "incomplete" ) var BgpOriginAttrTypeToIntMap = map[BgpOriginAttrType]int{ BGP_ORIGIN_ATTR_TYPE_IGP: 0, BGP_ORIGIN_ATTR_TYPE_EGP: 1, BGP_ORIGIN_ATTR_TYPE_INCOMPLETE: 2, } func (v BgpOriginAttrType) ToInt() int { i, ok := BgpOriginAttrTypeToIntMap[v] if !ok { return -1 } return i } var IntToBgpOriginAttrTypeMap = map[int]BgpOriginAttrType{ 0: BGP_ORIGIN_ATTR_TYPE_IGP, 1: BGP_ORIGIN_ATTR_TYPE_EGP, 2: BGP_ORIGIN_ATTR_TYPE_INCOMPLETE, } func (v BgpOriginAttrType) Validate() error { if _, ok := BgpOriginAttrTypeToIntMap[v]; !ok { return fmt.Errorf("invalid BgpOriginAttrType: %s", v) } return nil } // typedef for identity bgp-types:afi-safi-type. // Base identity type for AFI,SAFI tuples for BGP-4. type AfiSafiType string const ( AFI_SAFI_TYPE_IPV4_UNICAST AfiSafiType = "ipv4-unicast" AFI_SAFI_TYPE_IPV6_UNICAST AfiSafiType = "ipv6-unicast" AFI_SAFI_TYPE_IPV4_LABELLED_UNICAST AfiSafiType = "ipv4-labelled-unicast" AFI_SAFI_TYPE_IPV6_LABELLED_UNICAST AfiSafiType = "ipv6-labelled-unicast" AFI_SAFI_TYPE_L3VPN_IPV4_UNICAST AfiSafiType = "l3vpn-ipv4-unicast" AFI_SAFI_TYPE_L3VPN_IPV6_UNICAST AfiSafiType = "l3vpn-ipv6-unicast" AFI_SAFI_TYPE_L3VPN_IPV4_MULTICAST AfiSafiType = "l3vpn-ipv4-multicast" AFI_SAFI_TYPE_L3VPN_IPV6_MULTICAST AfiSafiType = "l3vpn-ipv6-multicast" AFI_SAFI_TYPE_L2VPN_VPLS AfiSafiType = "l2vpn-vpls" AFI_SAFI_TYPE_L2VPN_EVPN AfiSafiType = "l2vpn-evpn" AFI_SAFI_TYPE_IPV4_MULTICAST AfiSafiType = "ipv4-multicast" AFI_SAFI_TYPE_IPV6_MULTICAST AfiSafiType = "ipv6-multicast" AFI_SAFI_TYPE_RTC AfiSafiType = "rtc" AFI_SAFI_TYPE_IPV4_ENCAP AfiSafiType = "ipv4-encap" AFI_SAFI_TYPE_IPV6_ENCAP AfiSafiType = "ipv6-encap" AFI_SAFI_TYPE_IPV4_FLOWSPEC AfiSafiType = "ipv4-flowspec" AFI_SAFI_TYPE_L3VPN_IPV4_FLOWSPEC AfiSafiType = "l3vpn-ipv4-flowspec" AFI_SAFI_TYPE_IPV6_FLOWSPEC AfiSafiType = "ipv6-flowspec" AFI_SAFI_TYPE_L3VPN_IPV6_FLOWSPEC AfiSafiType = "l3vpn-ipv6-flowspec" AFI_SAFI_TYPE_L2VPN_FLOWSPEC AfiSafiType = "l2vpn-flowspec" AFI_SAFI_TYPE_OPAQUE AfiSafiType = "opaque" ) var AfiSafiTypeToIntMap = map[AfiSafiType]int{ AFI_SAFI_TYPE_IPV4_UNICAST: 0, AFI_SAFI_TYPE_IPV6_UNICAST: 1, AFI_SAFI_TYPE_IPV4_LABELLED_UNICAST: 2, AFI_SAFI_TYPE_IPV6_LABELLED_UNICAST: 3, AFI_SAFI_TYPE_L3VPN_IPV4_UNICAST: 4, AFI_SAFI_TYPE_L3VPN_IPV6_UNICAST: 5, AFI_SAFI_TYPE_L3VPN_IPV4_MULTICAST: 6, AFI_SAFI_TYPE_L3VPN_IPV6_MULTICAST: 7, AFI_SAFI_TYPE_L2VPN_VPLS: 8, AFI_SAFI_TYPE_L2VPN_EVPN: 9, AFI_SAFI_TYPE_IPV4_MULTICAST: 10, AFI_SAFI_TYPE_IPV6_MULTICAST: 11, AFI_SAFI_TYPE_RTC: 12, AFI_SAFI_TYPE_IPV4_ENCAP: 13, AFI_SAFI_TYPE_IPV6_ENCAP: 14, AFI_SAFI_TYPE_IPV4_FLOWSPEC: 15, AFI_SAFI_TYPE_L3VPN_IPV4_FLOWSPEC: 16, AFI_SAFI_TYPE_IPV6_FLOWSPEC: 17, AFI_SAFI_TYPE_L3VPN_IPV6_FLOWSPEC: 18, AFI_SAFI_TYPE_L2VPN_FLOWSPEC: 19, AFI_SAFI_TYPE_OPAQUE: 20, } func (v AfiSafiType) ToInt() int { i, ok := AfiSafiTypeToIntMap[v] if !ok { return -1 } return i } var IntToAfiSafiTypeMap = map[int]AfiSafiType{ 0: AFI_SAFI_TYPE_IPV4_UNICAST, 1: AFI_SAFI_TYPE_IPV6_UNICAST, 2: AFI_SAFI_TYPE_IPV4_LABELLED_UNICAST, 3: AFI_SAFI_TYPE_IPV6_LABELLED_UNICAST, 4: AFI_SAFI_TYPE_L3VPN_IPV4_UNICAST, 5: AFI_SAFI_TYPE_L3VPN_IPV6_UNICAST, 6: AFI_SAFI_TYPE_L3VPN_IPV4_MULTICAST, 7: AFI_SAFI_TYPE_L3VPN_IPV6_MULTICAST, 8: AFI_SAFI_TYPE_L2VPN_VPLS, 9: AFI_SAFI_TYPE_L2VPN_EVPN, 10: AFI_SAFI_TYPE_IPV4_MULTICAST, 11: AFI_SAFI_TYPE_IPV6_MULTICAST, 12: AFI_SAFI_TYPE_RTC, 13: AFI_SAFI_TYPE_IPV4_ENCAP, 14: AFI_SAFI_TYPE_IPV6_ENCAP, 15: AFI_SAFI_TYPE_IPV4_FLOWSPEC, 16: AFI_SAFI_TYPE_L3VPN_IPV4_FLOWSPEC, 17: AFI_SAFI_TYPE_IPV6_FLOWSPEC, 18: AFI_SAFI_TYPE_L3VPN_IPV6_FLOWSPEC, 19: AFI_SAFI_TYPE_L2VPN_FLOWSPEC, 20: AFI_SAFI_TYPE_OPAQUE, } func (v AfiSafiType) Validate() error { if _, ok := AfiSafiTypeToIntMap[v]; !ok { return fmt.Errorf("invalid AfiSafiType: %s", v) } return nil } // typedef for identity bgp-types:bgp-capability. // Base identity for a BGP capability. type BgpCapability string const ( BGP_CAPABILITY_MPBGP BgpCapability = "mpbgp" BGP_CAPABILITY_ROUTE_REFRESH BgpCapability = "route-refresh" BGP_CAPABILITY_ASN32 BgpCapability = "asn32" BGP_CAPABILITY_GRACEFUL_RESTART BgpCapability = "graceful-restart" BGP_CAPABILITY_ADD_PATHS BgpCapability = "add-paths" ) var BgpCapabilityToIntMap = map[BgpCapability]int{ BGP_CAPABILITY_MPBGP: 0, BGP_CAPABILITY_ROUTE_REFRESH: 1, BGP_CAPABILITY_ASN32: 2, BGP_CAPABILITY_GRACEFUL_RESTART: 3, BGP_CAPABILITY_ADD_PATHS: 4, } func (v BgpCapability) ToInt() int { i, ok := BgpCapabilityToIntMap[v] if !ok { return -1 } return i } var IntToBgpCapabilityMap = map[int]BgpCapability{ 0: BGP_CAPABILITY_MPBGP, 1: BGP_CAPABILITY_ROUTE_REFRESH, 2: BGP_CAPABILITY_ASN32, 3: BGP_CAPABILITY_GRACEFUL_RESTART, 4: BGP_CAPABILITY_ADD_PATHS, } func (v BgpCapability) Validate() error { if _, ok := BgpCapabilityToIntMap[v]; !ok { return fmt.Errorf("invalid BgpCapability: %s", v) } return nil } // typedef for identity bgp-types:bgp-well-known-std-community. // Reserved communities within the standard community space // defined by RFC1997. These communities must fall within the // range 0x00000000 to 0xFFFFFFFF. type BgpWellKnownStdCommunity string const ( BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT BgpWellKnownStdCommunity = "no_export" BGP_WELL_KNOWN_STD_COMMUNITY_NO_ADVERTISE BgpWellKnownStdCommunity = "no_advertise" BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT_SUBCONFED BgpWellKnownStdCommunity = "no_export_subconfed" BGP_WELL_KNOWN_STD_COMMUNITY_NOPEER BgpWellKnownStdCommunity = "nopeer" ) var BgpWellKnownStdCommunityToIntMap = map[BgpWellKnownStdCommunity]int{ BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT: 0, BGP_WELL_KNOWN_STD_COMMUNITY_NO_ADVERTISE: 1, BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT_SUBCONFED: 2, BGP_WELL_KNOWN_STD_COMMUNITY_NOPEER: 3, } func (v BgpWellKnownStdCommunity) ToInt() int { i, ok := BgpWellKnownStdCommunityToIntMap[v] if !ok { return -1 } return i } var IntToBgpWellKnownStdCommunityMap = map[int]BgpWellKnownStdCommunity{ 0: BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT, 1: BGP_WELL_KNOWN_STD_COMMUNITY_NO_ADVERTISE, 2: BGP_WELL_KNOWN_STD_COMMUNITY_NO_EXPORT_SUBCONFED, 3: BGP_WELL_KNOWN_STD_COMMUNITY_NOPEER, } func (v BgpWellKnownStdCommunity) Validate() error { if _, ok := BgpWellKnownStdCommunityToIntMap[v]; !ok { return fmt.Errorf("invalid BgpWellKnownStdCommunity: %s", v) } return nil } // typedef for identity ptypes:match-set-options-restricted-type. // Options that govern the behavior of a match statement. The // default behavior is ANY, i.e., the given value matches any // of the members of the defined set. Note this type is a // restricted version of the match-set-options-type. type MatchSetOptionsRestrictedType string const ( MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY MatchSetOptionsRestrictedType = "any" MATCH_SET_OPTIONS_RESTRICTED_TYPE_INVERT MatchSetOptionsRestrictedType = "invert" ) var MatchSetOptionsRestrictedTypeToIntMap = map[MatchSetOptionsRestrictedType]int{ MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY: 0, MATCH_SET_OPTIONS_RESTRICTED_TYPE_INVERT: 1, } func (v MatchSetOptionsRestrictedType) ToInt() int { i, ok := MatchSetOptionsRestrictedTypeToIntMap[v] if !ok { return -1 } return i } var IntToMatchSetOptionsRestrictedTypeMap = map[int]MatchSetOptionsRestrictedType{ 0: MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY, 1: MATCH_SET_OPTIONS_RESTRICTED_TYPE_INVERT, } func (v MatchSetOptionsRestrictedType) Validate() error { if _, ok := MatchSetOptionsRestrictedTypeToIntMap[v]; !ok { return fmt.Errorf("invalid MatchSetOptionsRestrictedType: %s", v) } return nil } func (v MatchSetOptionsRestrictedType) Default() MatchSetOptionsRestrictedType { return MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY } func (v MatchSetOptionsRestrictedType) DefaultAsNeeded() MatchSetOptionsRestrictedType { if string(v) == "" { return v.Default() } return v } // typedef for identity ptypes:match-set-options-type. // Options that govern the behavior of a match statement. The // default behavior is ANY, i.e., the given value matches any // of the members of the defined set. type MatchSetOptionsType string const ( MATCH_SET_OPTIONS_TYPE_ANY MatchSetOptionsType = "any" MATCH_SET_OPTIONS_TYPE_ALL MatchSetOptionsType = "all" MATCH_SET_OPTIONS_TYPE_INVERT MatchSetOptionsType = "invert" ) var MatchSetOptionsTypeToIntMap = map[MatchSetOptionsType]int{ MATCH_SET_OPTIONS_TYPE_ANY: 0, MATCH_SET_OPTIONS_TYPE_ALL: 1, MATCH_SET_OPTIONS_TYPE_INVERT: 2, } func (v MatchSetOptionsType) ToInt() int { i, ok := MatchSetOptionsTypeToIntMap[v] if !ok { return -1 } return i } var IntToMatchSetOptionsTypeMap = map[int]MatchSetOptionsType{ 0: MATCH_SET_OPTIONS_TYPE_ANY, 1: MATCH_SET_OPTIONS_TYPE_ALL, 2: MATCH_SET_OPTIONS_TYPE_INVERT, } func (v MatchSetOptionsType) Validate() error { if _, ok := MatchSetOptionsTypeToIntMap[v]; !ok { return fmt.Errorf("invalid MatchSetOptionsType: %s", v) } return nil } func (v MatchSetOptionsType) Default() MatchSetOptionsType { return MATCH_SET_OPTIONS_TYPE_ANY } func (v MatchSetOptionsType) DefaultAsNeeded() MatchSetOptionsType { if string(v) == "" { return v.Default() } return v } // typedef for typedef ptypes:tag-type. type TagType string // typedef for identity ptypes:install-protocol-type. // Base type for protocols which can install prefixes into the // RIB. type InstallProtocolType string const ( INSTALL_PROTOCOL_TYPE_BGP InstallProtocolType = "bgp" INSTALL_PROTOCOL_TYPE_ISIS InstallProtocolType = "isis" INSTALL_PROTOCOL_TYPE_OSPF InstallProtocolType = "ospf" INSTALL_PROTOCOL_TYPE_OSPF3 InstallProtocolType = "ospf3" INSTALL_PROTOCOL_TYPE_STATIC InstallProtocolType = "static" INSTALL_PROTOCOL_TYPE_DIRECTLY_CONNECTED InstallProtocolType = "directly-connected" INSTALL_PROTOCOL_TYPE_LOCAL_AGGREGATE InstallProtocolType = "local-aggregate" ) var InstallProtocolTypeToIntMap = map[InstallProtocolType]int{ INSTALL_PROTOCOL_TYPE_BGP: 0, INSTALL_PROTOCOL_TYPE_ISIS: 1, INSTALL_PROTOCOL_TYPE_OSPF: 2, INSTALL_PROTOCOL_TYPE_OSPF3: 3, INSTALL_PROTOCOL_TYPE_STATIC: 4, INSTALL_PROTOCOL_TYPE_DIRECTLY_CONNECTED: 5, INSTALL_PROTOCOL_TYPE_LOCAL_AGGREGATE: 6, } func (v InstallProtocolType) ToInt() int { i, ok := InstallProtocolTypeToIntMap[v] if !ok { return -1 } return i } var IntToInstallProtocolTypeMap = map[int]InstallProtocolType{ 0: INSTALL_PROTOCOL_TYPE_BGP, 1: INSTALL_PROTOCOL_TYPE_ISIS, 2: INSTALL_PROTOCOL_TYPE_OSPF, 3: INSTALL_PROTOCOL_TYPE_OSPF3, 4: INSTALL_PROTOCOL_TYPE_STATIC, 5: INSTALL_PROTOCOL_TYPE_DIRECTLY_CONNECTED, 6: INSTALL_PROTOCOL_TYPE_LOCAL_AGGREGATE, } func (v InstallProtocolType) Validate() error { if _, ok := InstallProtocolTypeToIntMap[v]; !ok { return fmt.Errorf("invalid InstallProtocolType: %s", v) } return nil } // typedef for identity ptypes:attribute-comparison. // base type for supported comparison operators on route // attributes. type AttributeComparison string const ( ATTRIBUTE_COMPARISON_ATTRIBUTE_EQ AttributeComparison = "attribute-eq" ATTRIBUTE_COMPARISON_ATTRIBUTE_GE AttributeComparison = "attribute-ge" ATTRIBUTE_COMPARISON_ATTRIBUTE_LE AttributeComparison = "attribute-le" ATTRIBUTE_COMPARISON_EQ AttributeComparison = "eq" ATTRIBUTE_COMPARISON_GE AttributeComparison = "ge" ATTRIBUTE_COMPARISON_LE AttributeComparison = "le" ) var AttributeComparisonToIntMap = map[AttributeComparison]int{ ATTRIBUTE_COMPARISON_ATTRIBUTE_EQ: 0, ATTRIBUTE_COMPARISON_ATTRIBUTE_GE: 1, ATTRIBUTE_COMPARISON_ATTRIBUTE_LE: 2, ATTRIBUTE_COMPARISON_EQ: 3, ATTRIBUTE_COMPARISON_GE: 4, ATTRIBUTE_COMPARISON_LE: 5, } func (v AttributeComparison) ToInt() int { i, ok := AttributeComparisonToIntMap[v] if !ok { return -1 } return i } var IntToAttributeComparisonMap = map[int]AttributeComparison{ 0: ATTRIBUTE_COMPARISON_ATTRIBUTE_EQ, 1: ATTRIBUTE_COMPARISON_ATTRIBUTE_GE, 2: ATTRIBUTE_COMPARISON_ATTRIBUTE_LE, 3: ATTRIBUTE_COMPARISON_EQ, 4: ATTRIBUTE_COMPARISON_GE, 5: ATTRIBUTE_COMPARISON_LE, } func (v AttributeComparison) Validate() error { if _, ok := AttributeComparisonToIntMap[v]; !ok { return fmt.Errorf("invalid AttributeComparison: %s", v) } return nil } // typedef for identity rpol:route-disposition. // Select the final disposition for the route, either // accept or reject. type RouteDisposition string const ( ROUTE_DISPOSITION_NONE RouteDisposition = "none" ROUTE_DISPOSITION_ACCEPT_ROUTE RouteDisposition = "accept-route" ROUTE_DISPOSITION_REJECT_ROUTE RouteDisposition = "reject-route" ) var RouteDispositionToIntMap = map[RouteDisposition]int{ ROUTE_DISPOSITION_NONE: 0, ROUTE_DISPOSITION_ACCEPT_ROUTE: 1, ROUTE_DISPOSITION_REJECT_ROUTE: 2, } func (v RouteDisposition) ToInt() int { i, ok := RouteDispositionToIntMap[v] if !ok { return -1 } return i } var IntToRouteDispositionMap = map[int]RouteDisposition{ 0: ROUTE_DISPOSITION_NONE, 1: ROUTE_DISPOSITION_ACCEPT_ROUTE, 2: ROUTE_DISPOSITION_REJECT_ROUTE, } func (v RouteDisposition) Validate() error { if _, ok := RouteDispositionToIntMap[v]; !ok { return fmt.Errorf("invalid RouteDisposition: %s", v) } return nil } // typedef for identity rpol:route-type. // Condition to check the route type in the route update. type RouteType string const ( ROUTE_TYPE_NONE RouteType = "none" ROUTE_TYPE_INTERNAL RouteType = "internal" ROUTE_TYPE_EXTERNAL RouteType = "external" ROUTE_TYPE_LOCAL RouteType = "local" ) var RouteTypeToIntMap = map[RouteType]int{ ROUTE_TYPE_NONE: 0, ROUTE_TYPE_INTERNAL: 1, ROUTE_TYPE_EXTERNAL: 2, ROUTE_TYPE_LOCAL: 3, } func (v RouteType) ToInt() int { i, ok := RouteTypeToIntMap[v] if !ok { return -1 } return i } var IntToRouteTypeMap = map[int]RouteType{ 0: ROUTE_TYPE_NONE, 1: ROUTE_TYPE_INTERNAL, 2: ROUTE_TYPE_EXTERNAL, 3: ROUTE_TYPE_LOCAL, } func (v RouteType) Validate() error { if _, ok := RouteTypeToIntMap[v]; !ok { return fmt.Errorf("invalid RouteType: %s", v) } return nil } // typedef for identity rpol:default-policy-type. // type used to specify default route disposition in // a policy chain. type DefaultPolicyType string const ( DEFAULT_POLICY_TYPE_ACCEPT_ROUTE DefaultPolicyType = "accept-route" DEFAULT_POLICY_TYPE_REJECT_ROUTE DefaultPolicyType = "reject-route" ) var DefaultPolicyTypeToIntMap = map[DefaultPolicyType]int{ DEFAULT_POLICY_TYPE_ACCEPT_ROUTE: 0, DEFAULT_POLICY_TYPE_REJECT_ROUTE: 1, } func (v DefaultPolicyType) ToInt() int { i, ok := DefaultPolicyTypeToIntMap[v] if !ok { return -1 } return i } var IntToDefaultPolicyTypeMap = map[int]DefaultPolicyType{ 0: DEFAULT_POLICY_TYPE_ACCEPT_ROUTE, 1: DEFAULT_POLICY_TYPE_REJECT_ROUTE, } func (v DefaultPolicyType) Validate() error { if _, ok := DefaultPolicyTypeToIntMap[v]; !ok { return fmt.Errorf("invalid DefaultPolicyType: %s", v) } return nil } // typedef for identity bgp:session-state. // Operational state of the BGP peer. type SessionState string const ( SESSION_STATE_IDLE SessionState = "idle" SESSION_STATE_CONNECT SessionState = "connect" SESSION_STATE_ACTIVE SessionState = "active" SESSION_STATE_OPENSENT SessionState = "opensent" SESSION_STATE_OPENCONFIRM SessionState = "openconfirm" SESSION_STATE_ESTABLISHED SessionState = "established" ) var SessionStateToIntMap = map[SessionState]int{ SESSION_STATE_IDLE: 0, SESSION_STATE_CONNECT: 1, SESSION_STATE_ACTIVE: 2, SESSION_STATE_OPENSENT: 3, SESSION_STATE_OPENCONFIRM: 4, SESSION_STATE_ESTABLISHED: 5, } func (v SessionState) ToInt() int { i, ok := SessionStateToIntMap[v] if !ok { return -1 } return i } var IntToSessionStateMap = map[int]SessionState{ 0: SESSION_STATE_IDLE, 1: SESSION_STATE_CONNECT, 2: SESSION_STATE_ACTIVE, 3: SESSION_STATE_OPENSENT, 4: SESSION_STATE_OPENCONFIRM, 5: SESSION_STATE_ESTABLISHED, } func (v SessionState) Validate() error { if _, ok := SessionStateToIntMap[v]; !ok { return fmt.Errorf("invalid SessionState: %s", v) } return nil } // typedef for identity bgp:admin-state. type AdminState string const ( ADMIN_STATE_UP AdminState = "up" ADMIN_STATE_DOWN AdminState = "down" ADMIN_STATE_PFX_CT AdminState = "pfx_ct" ) var AdminStateToIntMap = map[AdminState]int{ ADMIN_STATE_UP: 0, ADMIN_STATE_DOWN: 1, ADMIN_STATE_PFX_CT: 2, } func (v AdminState) ToInt() int { i, ok := AdminStateToIntMap[v] if !ok { return -1 } return i } var IntToAdminStateMap = map[int]AdminState{ 0: ADMIN_STATE_UP, 1: ADMIN_STATE_DOWN, 2: ADMIN_STATE_PFX_CT, } func (v AdminState) Validate() error { if _, ok := AdminStateToIntMap[v]; !ok { return fmt.Errorf("invalid AdminState: %s", v) } return nil } // typedef for identity bgp:mode. // Ths leaf indicates the mode of operation of BGP graceful // restart with the peer. type Mode string const ( MODE_HELPER_ONLY Mode = "helper-only" MODE_BILATERAL Mode = "bilateral" MODE_REMOTE_HELPER Mode = "remote-helper" ) var ModeToIntMap = map[Mode]int{ MODE_HELPER_ONLY: 0, MODE_BILATERAL: 1, MODE_REMOTE_HELPER: 2, } func (v Mode) ToInt() int { i, ok := ModeToIntMap[v] if !ok { return -1 } return i } var IntToModeMap = map[int]Mode{ 0: MODE_HELPER_ONLY, 1: MODE_BILATERAL, 2: MODE_REMOTE_HELPER, } func (v Mode) Validate() error { if _, ok := ModeToIntMap[v]; !ok { return fmt.Errorf("invalid Mode: %s", v) } return nil } // typedef for typedef bgp-pol:bgp-next-hop-type. type BgpNextHopType string // typedef for typedef bgp-pol:bgp-as-path-prepend-repeat. type BgpAsPathPrependRepeat uint8 // typedef for typedef bgp-pol:bgp-set-med-type. type BgpSetMedType string // typedef for identity bgp-pol:bgp-set-community-option-type. // Type definition for options when setting the community // attribute in a policy action. type BgpSetCommunityOptionType string const ( BGP_SET_COMMUNITY_OPTION_TYPE_ADD BgpSetCommunityOptionType = "add" BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE BgpSetCommunityOptionType = "remove" BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE BgpSetCommunityOptionType = "replace" ) var BgpSetCommunityOptionTypeToIntMap = map[BgpSetCommunityOptionType]int{ BGP_SET_COMMUNITY_OPTION_TYPE_ADD: 0, BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE: 1, BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE: 2, } func (v BgpSetCommunityOptionType) ToInt() int { i, ok := BgpSetCommunityOptionTypeToIntMap[v] if !ok { return -1 } return i } var IntToBgpSetCommunityOptionTypeMap = map[int]BgpSetCommunityOptionType{ 0: BGP_SET_COMMUNITY_OPTION_TYPE_ADD, 1: BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE, 2: BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE, } func (v BgpSetCommunityOptionType) Validate() error { if _, ok := BgpSetCommunityOptionTypeToIntMap[v]; !ok { return fmt.Errorf("invalid BgpSetCommunityOptionType: %s", v) } return nil } // typedef for identity gobgp:bmp-route-monitoring-policy-type. type BmpRouteMonitoringPolicyType string const ( BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY BmpRouteMonitoringPolicyType = "pre-policy" BMP_ROUTE_MONITORING_POLICY_TYPE_POST_POLICY BmpRouteMonitoringPolicyType = "post-policy" BMP_ROUTE_MONITORING_POLICY_TYPE_BOTH BmpRouteMonitoringPolicyType = "both" BMP_ROUTE_MONITORING_POLICY_TYPE_LOCAL_RIB BmpRouteMonitoringPolicyType = "local-rib" BMP_ROUTE_MONITORING_POLICY_TYPE_ALL BmpRouteMonitoringPolicyType = "all" ) var BmpRouteMonitoringPolicyTypeToIntMap = map[BmpRouteMonitoringPolicyType]int{ BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY: 0, BMP_ROUTE_MONITORING_POLICY_TYPE_POST_POLICY: 1, BMP_ROUTE_MONITORING_POLICY_TYPE_BOTH: 2, BMP_ROUTE_MONITORING_POLICY_TYPE_LOCAL_RIB: 3, BMP_ROUTE_MONITORING_POLICY_TYPE_ALL: 4, } func (v BmpRouteMonitoringPolicyType) ToInt() int { i, ok := BmpRouteMonitoringPolicyTypeToIntMap[v] if !ok { return -1 } return i } var IntToBmpRouteMonitoringPolicyTypeMap = map[int]BmpRouteMonitoringPolicyType{ 0: BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY, 1: BMP_ROUTE_MONITORING_POLICY_TYPE_POST_POLICY, 2: BMP_ROUTE_MONITORING_POLICY_TYPE_BOTH, 3: BMP_ROUTE_MONITORING_POLICY_TYPE_LOCAL_RIB, 4: BMP_ROUTE_MONITORING_POLICY_TYPE_ALL, } func (v BmpRouteMonitoringPolicyType) Validate() error { if _, ok := BmpRouteMonitoringPolicyTypeToIntMap[v]; !ok { return fmt.Errorf("invalid BmpRouteMonitoringPolicyType: %s", v) } return nil } // typedef for identity gobgp:mrt-type. type MrtType string const ( MRT_TYPE_UPDATES MrtType = "updates" MRT_TYPE_TABLE MrtType = "table" ) var MrtTypeToIntMap = map[MrtType]int{ MRT_TYPE_UPDATES: 0, MRT_TYPE_TABLE: 1, } func (v MrtType) ToInt() int { i, ok := MrtTypeToIntMap[v] if !ok { return -1 } return i } var IntToMrtTypeMap = map[int]MrtType{ 0: MRT_TYPE_UPDATES, 1: MRT_TYPE_TABLE, } func (v MrtType) Validate() error { if _, ok := MrtTypeToIntMap[v]; !ok { return fmt.Errorf("invalid MrtType: %s", v) } return nil } // typedef for identity gobgp:rpki-validation-result-type. // indicate the validation result of RPKI based on ROA. type RpkiValidationResultType string const ( RPKI_VALIDATION_RESULT_TYPE_NONE RpkiValidationResultType = "none" RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND RpkiValidationResultType = "not-found" RPKI_VALIDATION_RESULT_TYPE_VALID RpkiValidationResultType = "valid" RPKI_VALIDATION_RESULT_TYPE_INVALID RpkiValidationResultType = "invalid" ) var RpkiValidationResultTypeToIntMap = map[RpkiValidationResultType]int{ RPKI_VALIDATION_RESULT_TYPE_NONE: 0, RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND: 1, RPKI_VALIDATION_RESULT_TYPE_VALID: 2, RPKI_VALIDATION_RESULT_TYPE_INVALID: 3, } func (v RpkiValidationResultType) ToInt() int { i, ok := RpkiValidationResultTypeToIntMap[v] if !ok { return -1 } return i } var IntToRpkiValidationResultTypeMap = map[int]RpkiValidationResultType{ 0: RPKI_VALIDATION_RESULT_TYPE_NONE, 1: RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND, 2: RPKI_VALIDATION_RESULT_TYPE_VALID, 3: RPKI_VALIDATION_RESULT_TYPE_INVALID, } func (v RpkiValidationResultType) Validate() error { if _, ok := RpkiValidationResultTypeToIntMap[v]; !ok { return fmt.Errorf("invalid RpkiValidationResultType: %s", v) } return nil } // struct for container gobgp:state. type DynamicNeighborState struct { // original -> gobgp:prefix Prefix string `mapstructure:"prefix" json:"prefix,omitempty"` // original -> gobgp:peer-group PeerGroup string `mapstructure:"peer-group" json:"peer-group,omitempty"` } // struct for container gobgp:config. type DynamicNeighborConfig struct { // original -> gobgp:prefix Prefix string `mapstructure:"prefix" json:"prefix,omitempty"` // original -> gobgp:peer-group PeerGroup string `mapstructure:"peer-group" json:"peer-group,omitempty"` } func (lhs *DynamicNeighborConfig) Equal(rhs *DynamicNeighborConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Prefix != rhs.Prefix { return false } if lhs.PeerGroup != rhs.PeerGroup { return false } return true } // struct for container gobgp:dynamic-neighbor. type DynamicNeighbor struct { // original -> gobgp:prefix // original -> gobgp:dynamic-neighbor-config Config DynamicNeighborConfig `mapstructure:"config" json:"config,omitempty"` // original -> gobgp:dynamic-neighbor-state State DynamicNeighborState `mapstructure:"state" json:"state,omitempty"` } func (lhs *DynamicNeighbor) Equal(rhs *DynamicNeighbor) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container gobgp:state. type CollectorState struct { // original -> gobgp:url Url string `mapstructure:"url" json:"url,omitempty"` // original -> gobgp:db-name DbName string `mapstructure:"db-name" json:"db-name,omitempty"` // original -> gobgp:table-dump-interval TableDumpInterval uint64 `mapstructure:"table-dump-interval" json:"table-dump-interval,omitempty"` } // struct for container gobgp:config. type CollectorConfig struct { // original -> gobgp:url Url string `mapstructure:"url" json:"url,omitempty"` // original -> gobgp:db-name DbName string `mapstructure:"db-name" json:"db-name,omitempty"` // original -> gobgp:table-dump-interval TableDumpInterval uint64 `mapstructure:"table-dump-interval" json:"table-dump-interval,omitempty"` } func (lhs *CollectorConfig) Equal(rhs *CollectorConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Url != rhs.Url { return false } if lhs.DbName != rhs.DbName { return false } if lhs.TableDumpInterval != rhs.TableDumpInterval { return false } return true } // struct for container gobgp:collector. type Collector struct { // original -> gobgp:collector-config Config CollectorConfig `mapstructure:"config" json:"config,omitempty"` // original -> gobgp:collector-state State CollectorState `mapstructure:"state" json:"state,omitempty"` } func (lhs *Collector) Equal(rhs *Collector) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container gobgp:state. type ZebraState struct { // original -> gobgp:enabled // gobgp:enabled's original type is boolean. // Configure enabling to connect to zebra. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> gobgp:url // Configure url for zebra. Url string `mapstructure:"url" json:"url,omitempty"` // original -> gobgp:redistribute-route-type RedistributeRouteTypeList []InstallProtocolType `mapstructure:"redistribute-route-type-list" json:"redistribute-route-type-list,omitempty"` // original -> gobgp:version // Configure version of zebra protocol. Default is 2. Supported up to 3. Version uint8 `mapstructure:"version" json:"version,omitempty"` // original -> gobgp:nexthop-trigger-enable // gobgp:nexthop-trigger-enable's original type is boolean. NexthopTriggerEnable bool `mapstructure:"nexthop-trigger-enable" json:"nexthop-trigger-enable,omitempty"` // original -> gobgp:nexthop-trigger-delay NexthopTriggerDelay uint8 `mapstructure:"nexthop-trigger-delay" json:"nexthop-trigger-delay,omitempty"` } // struct for container gobgp:config. type ZebraConfig struct { // original -> gobgp:enabled // gobgp:enabled's original type is boolean. // Configure enabling to connect to zebra. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> gobgp:url // Configure url for zebra. Url string `mapstructure:"url" json:"url,omitempty"` // original -> gobgp:redistribute-route-type RedistributeRouteTypeList []InstallProtocolType `mapstructure:"redistribute-route-type-list" json:"redistribute-route-type-list,omitempty"` // original -> gobgp:version // Configure version of zebra protocol. Default is 2. Supported up to 3. Version uint8 `mapstructure:"version" json:"version,omitempty"` // original -> gobgp:nexthop-trigger-enable // gobgp:nexthop-trigger-enable's original type is boolean. NexthopTriggerEnable bool `mapstructure:"nexthop-trigger-enable" json:"nexthop-trigger-enable,omitempty"` // original -> gobgp:nexthop-trigger-delay NexthopTriggerDelay uint8 `mapstructure:"nexthop-trigger-delay" json:"nexthop-trigger-delay,omitempty"` } func (lhs *ZebraConfig) Equal(rhs *ZebraConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Enabled != rhs.Enabled { return false } if lhs.Url != rhs.Url { return false } if len(lhs.RedistributeRouteTypeList) != len(rhs.RedistributeRouteTypeList) { return false } for idx, l := range lhs.RedistributeRouteTypeList { if l != rhs.RedistributeRouteTypeList[idx] { return false } } if lhs.Version != rhs.Version { return false } if lhs.NexthopTriggerEnable != rhs.NexthopTriggerEnable { return false } if lhs.NexthopTriggerDelay != rhs.NexthopTriggerDelay { return false } return true } // struct for container gobgp:zebra. type Zebra struct { // original -> gobgp:zebra-config Config ZebraConfig `mapstructure:"config" json:"config,omitempty"` // original -> gobgp:zebra-state State ZebraState `mapstructure:"state" json:"state,omitempty"` } func (lhs *Zebra) Equal(rhs *Zebra) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container gobgp:config. type MrtConfig struct { // original -> gobgp:dump-type DumpType MrtType `mapstructure:"dump-type" json:"dump-type,omitempty"` // original -> gobgp:file-name // Configures a file name to be written. FileName string `mapstructure:"file-name" json:"file-name,omitempty"` // original -> gobgp:table-name // specify the table name with route server setup. TableName string `mapstructure:"table-name" json:"table-name,omitempty"` // original -> gobgp:dump-interval DumpInterval uint64 `mapstructure:"dump-interval" json:"dump-interval,omitempty"` // original -> gobgp:rotation-interval RotationInterval uint64 `mapstructure:"rotation-interval" json:"rotation-interval,omitempty"` } func (lhs *MrtConfig) Equal(rhs *MrtConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.DumpType != rhs.DumpType { return false } if lhs.FileName != rhs.FileName { return false } if lhs.TableName != rhs.TableName { return false } if lhs.DumpInterval != rhs.DumpInterval { return false } if lhs.RotationInterval != rhs.RotationInterval { return false } return true } // struct for container gobgp:mrt. type Mrt struct { // original -> gobgp:file-name // original -> gobgp:mrt-config Config MrtConfig `mapstructure:"config" json:"config,omitempty"` } func (lhs *Mrt) Equal(rhs *Mrt) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container gobgp:state. // Configuration parameters relating to BMP server. type BmpServerState struct { // original -> gobgp:address // gobgp:address's original type is inet:ip-address. // Reference to the address of the BMP server used as // a key in the BMP server list. Address string `mapstructure:"address" json:"address,omitempty"` // original -> gobgp:port // Reference to the port of the BMP server. Port uint32 `mapstructure:"port" json:"port,omitempty"` // original -> gobgp:route-monitoring-policy RouteMonitoringPolicy BmpRouteMonitoringPolicyType `mapstructure:"route-monitoring-policy" json:"route-monitoring-policy,omitempty"` // original -> gobgp:statistics-timeout // Interval seconds of statistics messages sent to BMP server. StatisticsTimeout uint16 `mapstructure:"statistics-timeout" json:"statistics-timeout,omitempty"` // original -> gobgp:route-mirroring-enabled // gobgp:route-mirroring-enabled's original type is boolean. // Enable feature for mirroring of received BGP messages // mainly for debugging purpose. RouteMirroringEnabled bool `mapstructure:"route-mirroring-enabled" json:"route-mirroring-enabled,omitempty"` } // struct for container gobgp:config. // Configuration parameters relating to BMP server. type BmpServerConfig struct { // original -> gobgp:address // gobgp:address's original type is inet:ip-address. // Reference to the address of the BMP server used as // a key in the BMP server list. Address string `mapstructure:"address" json:"address,omitempty"` // original -> gobgp:port // Reference to the port of the BMP server. Port uint32 `mapstructure:"port" json:"port,omitempty"` // original -> gobgp:route-monitoring-policy RouteMonitoringPolicy BmpRouteMonitoringPolicyType `mapstructure:"route-monitoring-policy" json:"route-monitoring-policy,omitempty"` // original -> gobgp:statistics-timeout // Interval seconds of statistics messages sent to BMP server. StatisticsTimeout uint16 `mapstructure:"statistics-timeout" json:"statistics-timeout,omitempty"` // original -> gobgp:route-mirroring-enabled // gobgp:route-mirroring-enabled's original type is boolean. // Enable feature for mirroring of received BGP messages // mainly for debugging purpose. RouteMirroringEnabled bool `mapstructure:"route-mirroring-enabled" json:"route-mirroring-enabled,omitempty"` } func (lhs *BmpServerConfig) Equal(rhs *BmpServerConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Address != rhs.Address { return false } if lhs.Port != rhs.Port { return false } if lhs.RouteMonitoringPolicy != rhs.RouteMonitoringPolicy { return false } if lhs.StatisticsTimeout != rhs.StatisticsTimeout { return false } if lhs.RouteMirroringEnabled != rhs.RouteMirroringEnabled { return false } return true } // struct for container gobgp:bmp-server. // List of BMP servers configured on the local system. type BmpServer struct { // original -> gobgp:address // original -> gobgp:bmp-server-config // Configuration parameters relating to BMP server. Config BmpServerConfig `mapstructure:"config" json:"config,omitempty"` // original -> gobgp:bmp-server-state // Configuration parameters relating to BMP server. State BmpServerState `mapstructure:"state" json:"state,omitempty"` } func (lhs *BmpServer) Equal(rhs *BmpServer) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container gobgp:rpki-received. // Counters for reception RPKI Message types. type RpkiReceived struct { // original -> gobgp:serial-notify // Number of serial notify message received from RPKI server. SerialNotify int64 `mapstructure:"serial-notify" json:"serial-notify,omitempty"` // original -> gobgp:cache-reset // Number of cache reset message received from RPKI server. CacheReset int64 `mapstructure:"cache-reset" json:"cache-reset,omitempty"` // original -> gobgp:cache-response // Number of cache response message received from RPKI server. CacheResponse int64 `mapstructure:"cache-response" json:"cache-response,omitempty"` // original -> gobgp:ipv4-prefix // Number of ipv4 prefix message received from RPKI server. Ipv4Prefix int64 `mapstructure:"ipv4-prefix" json:"ipv4-prefix,omitempty"` // original -> gobgp:ipv6-prefix // Number of ipv6 prefix message received from RPKI server. Ipv6Prefix int64 `mapstructure:"ipv6-prefix" json:"ipv6-prefix,omitempty"` // original -> gobgp:end-of-data // Number of end of data message received from RPKI server. EndOfData int64 `mapstructure:"end-of-data" json:"end-of-data,omitempty"` // original -> gobgp:error // Number of error message received from RPKI server. Error int64 `mapstructure:"error" json:"error,omitempty"` } func (lhs *RpkiReceived) Equal(rhs *RpkiReceived) bool { if lhs == nil || rhs == nil { return false } if lhs.SerialNotify != rhs.SerialNotify { return false } if lhs.CacheReset != rhs.CacheReset { return false } if lhs.CacheResponse != rhs.CacheResponse { return false } if lhs.Ipv4Prefix != rhs.Ipv4Prefix { return false } if lhs.Ipv6Prefix != rhs.Ipv6Prefix { return false } if lhs.EndOfData != rhs.EndOfData { return false } if lhs.Error != rhs.Error { return false } return true } // struct for container gobgp:rpki-sent. // Counters for transmission RPKI Message types. type RpkiSent struct { // original -> gobgp:serial-query // Number of serial query message sent to RPKI server. SerialQuery int64 `mapstructure:"serial-query" json:"serial-query,omitempty"` // original -> gobgp:reset-query // Number of reset query message sent to RPKI server. ResetQuery int64 `mapstructure:"reset-query" json:"reset-query,omitempty"` // original -> gobgp:error // Number of error message sent to RPKI server. Error int64 `mapstructure:"error" json:"error,omitempty"` } func (lhs *RpkiSent) Equal(rhs *RpkiSent) bool { if lhs == nil || rhs == nil { return false } if lhs.SerialQuery != rhs.SerialQuery { return false } if lhs.ResetQuery != rhs.ResetQuery { return false } if lhs.Error != rhs.Error { return false } return true } // struct for container gobgp:rpki-messages. // Counters for transmission and reception RPKI Message types. type RpkiMessages struct { // original -> gobgp:rpki-sent // Counters for transmission RPKI Message types. RpkiSent RpkiSent `mapstructure:"rpki-sent" json:"rpki-sent,omitempty"` // original -> gobgp:rpki-received // Counters for reception RPKI Message types. RpkiReceived RpkiReceived `mapstructure:"rpki-received" json:"rpki-received,omitempty"` } func (lhs *RpkiMessages) Equal(rhs *RpkiMessages) bool { if lhs == nil || rhs == nil { return false } if !lhs.RpkiSent.Equal(&(rhs.RpkiSent)) { return false } if !lhs.RpkiReceived.Equal(&(rhs.RpkiReceived)) { return false } return true } // struct for container gobgp:state. // State information relating to RPKI server. type RpkiServerState struct { // original -> gobgp:up // gobgp:up's original type is boolean. Up bool `mapstructure:"up" json:"up,omitempty"` // original -> gobgp:serial-number SerialNumber uint32 `mapstructure:"serial-number" json:"serial-number,omitempty"` // original -> gobgp:records-v4 RecordsV4 uint32 `mapstructure:"records-v4" json:"records-v4,omitempty"` // original -> gobgp:records-v6 RecordsV6 uint32 `mapstructure:"records-v6" json:"records-v6,omitempty"` // original -> gobgp:prefixes-v4 PrefixesV4 uint32 `mapstructure:"prefixes-v4" json:"prefixes-v4,omitempty"` // original -> gobgp:prefixes-v6 PrefixesV6 uint32 `mapstructure:"prefixes-v6" json:"prefixes-v6,omitempty"` // original -> gobgp:uptime // This timer determines the amount of time since the // RPKI last transitioned in of the Established state. Uptime int64 `mapstructure:"uptime" json:"uptime,omitempty"` // original -> gobgp:downtime // This timer determines the amount of time since the // RPKI last transitioned out of the Established state. Downtime int64 `mapstructure:"downtime" json:"downtime,omitempty"` // original -> gobgp:last-pdu-recv-time // last time the received an pdu message from RPKI server. LastPduRecvTime int64 `mapstructure:"last-pdu-recv-time" json:"last-pdu-recv-time,omitempty"` // original -> gobgp:rpki-messages // Counters for transmission and reception RPKI Message types. RpkiMessages RpkiMessages `mapstructure:"rpki-messages" json:"rpki-messages,omitempty"` } // struct for container gobgp:config. // Configuration parameters relating to RPKI server. type RpkiServerConfig struct { // original -> gobgp:address // gobgp:address's original type is inet:ip-address. // Reference to the address of the RPKI server used as // a key in the RPKI server list. Address string `mapstructure:"address" json:"address,omitempty"` // original -> gobgp:port // Reference to the port of the RPKI server. Port uint32 `mapstructure:"port" json:"port,omitempty"` // original -> gobgp:refresh-time // Check interval for a configured RPKI server. RefreshTime int64 `mapstructure:"refresh-time" json:"refresh-time,omitempty"` // original -> gobgp:hold-time // Specify the length of time in seconds that the session between // the router and RPKI server is to be considered operational // without any activity. HoldTime int64 `mapstructure:"hold-time" json:"hold-time,omitempty"` // original -> gobgp:record-lifetime // Indicate the expiration date of the route validation recode // received from RPKI server. RecordLifetime int64 `mapstructure:"record-lifetime" json:"record-lifetime,omitempty"` // original -> gobgp:preference // RPKI server has a static preference. // Higher the preference values indicates a higher priority RPKI server. Preference uint8 `mapstructure:"preference" json:"preference,omitempty"` } func (lhs *RpkiServerConfig) Equal(rhs *RpkiServerConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Address != rhs.Address { return false } if lhs.Port != rhs.Port { return false } if lhs.RefreshTime != rhs.RefreshTime { return false } if lhs.HoldTime != rhs.HoldTime { return false } if lhs.RecordLifetime != rhs.RecordLifetime { return false } if lhs.Preference != rhs.Preference { return false } return true } // struct for container gobgp:rpki-server. // List of RPKI servers configured on the local system. type RpkiServer struct { // original -> gobgp:address // original -> gobgp:rpki-server-config // Configuration parameters relating to RPKI server. Config RpkiServerConfig `mapstructure:"config" json:"config,omitempty"` // original -> gobgp:rpki-server-state // State information relating to RPKI server. State RpkiServerState `mapstructure:"state" json:"state,omitempty"` } func (lhs *RpkiServer) Equal(rhs *RpkiServer) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp:state. // State information relating to the BGP neighbor or group. type PeerGroupState struct { // original -> bgp:peer-as // bgp:peer-as's original type is inet:as-number. // AS number of the peer. PeerAs uint32 `mapstructure:"peer-as" json:"peer-as,omitempty"` // original -> bgp:local-as // bgp:local-as's original type is inet:as-number. // The local autonomous system number that is to be used // when establishing sessions with the remote peer or peer // group, if this differs from the global BGP router // autonomous system number. LocalAs uint32 `mapstructure:"local-as" json:"local-as,omitempty"` // original -> bgp:peer-type // Explicitly designate the peer or peer group as internal // (iBGP) or external (eBGP). PeerType PeerType `mapstructure:"peer-type" json:"peer-type,omitempty"` // original -> bgp:auth-password // Configures an MD5 authentication password for use with // neighboring devices. AuthPassword string `mapstructure:"auth-password" json:"auth-password,omitempty"` // original -> bgp:remove-private-as // Remove private AS numbers from updates sent to peers. RemovePrivateAs RemovePrivateAsOption `mapstructure:"remove-private-as" json:"remove-private-as,omitempty"` // original -> bgp:route-flap-damping // bgp:route-flap-damping's original type is boolean. // Enable route flap damping. RouteFlapDamping bool `mapstructure:"route-flap-damping" json:"route-flap-damping,omitempty"` // original -> bgp:send-community // Specify which types of community should be sent to the // neighbor or group. The default is to not send the // community attribute. SendCommunity CommunityType `mapstructure:"send-community" json:"send-community,omitempty"` // original -> bgp:description // An optional textual description (intended primarily for use // with a peer or group. Description string `mapstructure:"description" json:"description,omitempty"` // original -> bgp:peer-group-name // Name of the BGP peer-group. PeerGroupName string `mapstructure:"peer-group-name" json:"peer-group-name,omitempty"` // original -> bgp-op:total-paths // Total number of BGP paths within the context. TotalPaths uint32 `mapstructure:"total-paths" json:"total-paths,omitempty"` // original -> bgp-op:total-prefixes // . TotalPrefixes uint32 `mapstructure:"total-prefixes" json:"total-prefixes,omitempty"` } // struct for container bgp:config. // Configuration parameters relating to the BGP neighbor or // group. type PeerGroupConfig struct { // original -> bgp:peer-as // bgp:peer-as's original type is inet:as-number. // AS number of the peer. PeerAs uint32 `mapstructure:"peer-as" json:"peer-as,omitempty"` // original -> bgp:local-as // bgp:local-as's original type is inet:as-number. // The local autonomous system number that is to be used // when establishing sessions with the remote peer or peer // group, if this differs from the global BGP router // autonomous system number. LocalAs uint32 `mapstructure:"local-as" json:"local-as,omitempty"` // original -> bgp:peer-type // Explicitly designate the peer or peer group as internal // (iBGP) or external (eBGP). PeerType PeerType `mapstructure:"peer-type" json:"peer-type,omitempty"` // original -> bgp:auth-password // Configures an MD5 authentication password for use with // neighboring devices. AuthPassword string `mapstructure:"auth-password" json:"auth-password,omitempty"` // original -> bgp:remove-private-as // Remove private AS numbers from updates sent to peers. RemovePrivateAs RemovePrivateAsOption `mapstructure:"remove-private-as" json:"remove-private-as,omitempty"` // original -> bgp:route-flap-damping // bgp:route-flap-damping's original type is boolean. // Enable route flap damping. RouteFlapDamping bool `mapstructure:"route-flap-damping" json:"route-flap-damping,omitempty"` // original -> bgp:send-community // Specify which types of community should be sent to the // neighbor or group. The default is to not send the // community attribute. SendCommunity CommunityType `mapstructure:"send-community" json:"send-community,omitempty"` // original -> bgp:description // An optional textual description (intended primarily for use // with a peer or group. Description string `mapstructure:"description" json:"description,omitempty"` // original -> bgp:peer-group-name // Name of the BGP peer-group. PeerGroupName string `mapstructure:"peer-group-name" json:"peer-group-name,omitempty"` } func (lhs *PeerGroupConfig) Equal(rhs *PeerGroupConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.PeerAs != rhs.PeerAs { return false } if lhs.LocalAs != rhs.LocalAs { return false } if lhs.PeerType != rhs.PeerType { return false } if lhs.AuthPassword != rhs.AuthPassword { return false } if lhs.RemovePrivateAs != rhs.RemovePrivateAs { return false } if lhs.RouteFlapDamping != rhs.RouteFlapDamping { return false } if lhs.SendCommunity != rhs.SendCommunity { return false } if lhs.Description != rhs.Description { return false } if lhs.PeerGroupName != rhs.PeerGroupName { return false } return true } // struct for container bgp:peer-group. // List of BGP peer-groups configured on the local system - // uniquely identified by peer-group name. type PeerGroup struct { // original -> bgp:peer-group-name // original -> bgp:peer-group-config // Configuration parameters relating to the BGP neighbor or // group. Config PeerGroupConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:peer-group-state // State information relating to the BGP neighbor or group. State PeerGroupState `mapstructure:"state" json:"state,omitempty"` // original -> bgp:timers // Timers related to a BGP neighbor or group. Timers Timers `mapstructure:"timers" json:"timers,omitempty"` // original -> bgp:transport // Transport session parameters for the BGP neighbor or group. Transport Transport `mapstructure:"transport" json:"transport,omitempty"` // original -> bgp:error-handling // Error handling parameters used for the BGP neighbor or // group. ErrorHandling ErrorHandling `mapstructure:"error-handling" json:"error-handling,omitempty"` // original -> bgp:logging-options // Logging options for events related to the BGP neighbor or // group. LoggingOptions LoggingOptions `mapstructure:"logging-options" json:"logging-options,omitempty"` // original -> bgp:ebgp-multihop // eBGP multi-hop parameters for the BGP neighbor or group. EbgpMultihop EbgpMultihop `mapstructure:"ebgp-multihop" json:"ebgp-multihop,omitempty"` // original -> bgp:route-reflector // Route reflector parameters for the BGP neighbor or group. RouteReflector RouteReflector `mapstructure:"route-reflector" json:"route-reflector,omitempty"` // original -> bgp:as-path-options // AS_PATH manipulation parameters for the BGP neighbor or // group. AsPathOptions AsPathOptions `mapstructure:"as-path-options" json:"as-path-options,omitempty"` // original -> bgp:add-paths // Parameters relating to the advertisement and receipt of // multiple paths for a single NLRI (add-paths). AddPaths AddPaths `mapstructure:"add-paths" json:"add-paths,omitempty"` // original -> bgp:afi-safis // Per-address-family configuration parameters associated with // the neighbor or group. AfiSafis []AfiSafi `mapstructure:"afi-safis" json:"afi-safis,omitempty"` // original -> bgp:graceful-restart // Parameters relating the graceful restart mechanism for BGP. GracefulRestart GracefulRestart `mapstructure:"graceful-restart" json:"graceful-restart,omitempty"` // original -> rpol:apply-policy // Anchor point for routing policies in the model. // Import and export policies are with respect to the local // routing table, i.e., export (send) and import (receive), // depending on the context. ApplyPolicy ApplyPolicy `mapstructure:"apply-policy" json:"apply-policy,omitempty"` // original -> bgp-mp:use-multiple-paths // Parameters related to the use of multiple paths for the // same NLRI. UseMultiplePaths UseMultiplePaths `mapstructure:"use-multiple-paths" json:"use-multiple-paths,omitempty"` // original -> gobgp:route-server // Configure the local router as a route server. RouteServer RouteServer `mapstructure:"route-server" json:"route-server,omitempty"` // original -> gobgp:ttl-security // Configure TTL Security feature. TtlSecurity TtlSecurity `mapstructure:"ttl-security" json:"ttl-security,omitempty"` } func (lhs *PeerGroup) Equal(rhs *PeerGroup) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } if !lhs.Timers.Equal(&(rhs.Timers)) { return false } if !lhs.Transport.Equal(&(rhs.Transport)) { return false } if !lhs.ErrorHandling.Equal(&(rhs.ErrorHandling)) { return false } if !lhs.LoggingOptions.Equal(&(rhs.LoggingOptions)) { return false } if !lhs.EbgpMultihop.Equal(&(rhs.EbgpMultihop)) { return false } if !lhs.RouteReflector.Equal(&(rhs.RouteReflector)) { return false } if !lhs.AsPathOptions.Equal(&(rhs.AsPathOptions)) { return false } if !lhs.AddPaths.Equal(&(rhs.AddPaths)) { return false } if len(lhs.AfiSafis) != len(rhs.AfiSafis) { return false } { lmap := make(map[string]*AfiSafi) for i, l := range lhs.AfiSafis { lmap[mapkey(i, string(l.Config.AfiSafiName))] = &lhs.AfiSafis[i] } for i, r := range rhs.AfiSafis { if l, y := lmap[mapkey(i, string(r.Config.AfiSafiName))]; !y { return false } else if !r.Equal(l) { return false } } } if !lhs.GracefulRestart.Equal(&(rhs.GracefulRestart)) { return false } if !lhs.ApplyPolicy.Equal(&(rhs.ApplyPolicy)) { return false } if !lhs.UseMultiplePaths.Equal(&(rhs.UseMultiplePaths)) { return false } if !lhs.RouteServer.Equal(&(rhs.RouteServer)) { return false } if !lhs.TtlSecurity.Equal(&(rhs.TtlSecurity)) { return false } return true } // struct for container gobgp:state. // State information for TTL Security. type TtlSecurityState struct { // original -> gobgp:enabled // gobgp:enabled's original type is boolean. // Enable features for TTL Security. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> gobgp:ttl-min // Reference to the port of the BMP server. TtlMin uint8 `mapstructure:"ttl-min" json:"ttl-min,omitempty"` } // struct for container gobgp:config. // Configuration parameters for TTL Security. type TtlSecurityConfig struct { // original -> gobgp:enabled // gobgp:enabled's original type is boolean. // Enable features for TTL Security. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> gobgp:ttl-min // Reference to the port of the BMP server. TtlMin uint8 `mapstructure:"ttl-min" json:"ttl-min,omitempty"` } func (lhs *TtlSecurityConfig) Equal(rhs *TtlSecurityConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Enabled != rhs.Enabled { return false } if lhs.TtlMin != rhs.TtlMin { return false } return true } // struct for container gobgp:ttl-security. // Configure TTL Security feature. type TtlSecurity struct { // original -> gobgp:ttl-security-config // Configuration parameters for TTL Security. Config TtlSecurityConfig `mapstructure:"config" json:"config,omitempty"` // original -> gobgp:ttl-security-state // State information for TTL Security. State TtlSecurityState `mapstructure:"state" json:"state,omitempty"` } func (lhs *TtlSecurity) Equal(rhs *TtlSecurity) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container gobgp:state. // State information relating to route server // client(s) used for the BGP neighbor. type RouteServerState struct { // original -> gobgp:route-server-client // gobgp:route-server-client's original type is boolean. // Configure the neighbor as a route server client. RouteServerClient bool `mapstructure:"route-server-client" json:"route-server-client,omitempty"` } // struct for container gobgp:config. // Configuration parameters relating to route server // client(s) used for the BGP neighbor. type RouteServerConfig struct { // original -> gobgp:route-server-client // gobgp:route-server-client's original type is boolean. // Configure the neighbor as a route server client. RouteServerClient bool `mapstructure:"route-server-client" json:"route-server-client,omitempty"` } func (lhs *RouteServerConfig) Equal(rhs *RouteServerConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.RouteServerClient != rhs.RouteServerClient { return false } return true } // struct for container gobgp:route-server. // Configure the local router as a route server. type RouteServer struct { // original -> gobgp:route-server-config // Configuration parameters relating to route server // client(s) used for the BGP neighbor. Config RouteServerConfig `mapstructure:"config" json:"config,omitempty"` // original -> gobgp:route-server-state // State information relating to route server // client(s) used for the BGP neighbor. State RouteServerState `mapstructure:"state" json:"state,omitempty"` } func (lhs *RouteServer) Equal(rhs *RouteServer) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp-op:prefixes. // Prefix counters for the BGP session. type Prefixes struct { // original -> bgp-op:received // The number of prefixes received from the neighbor. Received uint32 `mapstructure:"received" json:"received,omitempty"` // original -> bgp-op:sent // The number of prefixes advertised to the neighbor. Sent uint32 `mapstructure:"sent" json:"sent,omitempty"` // original -> bgp-op:installed // The number of advertised prefixes installed in the // Loc-RIB. Installed uint32 `mapstructure:"installed" json:"installed,omitempty"` } func (lhs *Prefixes) Equal(rhs *Prefixes) bool { if lhs == nil || rhs == nil { return false } if lhs.Received != rhs.Received { return false } if lhs.Sent != rhs.Sent { return false } if lhs.Installed != rhs.Installed { return false } return true } // struct for container bgp:state. // State information associated with ADD_PATHS. type AddPathsState struct { // original -> bgp:receive // bgp:receive's original type is boolean. // Enable ability to receive multiple path advertisements // for an NLRI from the neighbor or group. Receive bool `mapstructure:"receive" json:"receive,omitempty"` // original -> bgp:send-max // The maximum number of paths to advertise to neighbors // for a single NLRI. SendMax uint8 `mapstructure:"send-max" json:"send-max,omitempty"` } // struct for container bgp:config. // Configuration parameters relating to ADD_PATHS. type AddPathsConfig struct { // original -> bgp:receive // bgp:receive's original type is boolean. // Enable ability to receive multiple path advertisements // for an NLRI from the neighbor or group. Receive bool `mapstructure:"receive" json:"receive,omitempty"` // original -> bgp:send-max // The maximum number of paths to advertise to neighbors // for a single NLRI. SendMax uint8 `mapstructure:"send-max" json:"send-max,omitempty"` } func (lhs *AddPathsConfig) Equal(rhs *AddPathsConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Receive != rhs.Receive { return false } if lhs.SendMax != rhs.SendMax { return false } return true } // struct for container bgp:add-paths. // Parameters relating to the advertisement and receipt of // multiple paths for a single NLRI (add-paths). type AddPaths struct { // original -> bgp:add-paths-config // Configuration parameters relating to ADD_PATHS. Config AddPathsConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:add-paths-state // State information associated with ADD_PATHS. State AddPathsState `mapstructure:"state" json:"state,omitempty"` } func (lhs *AddPaths) Equal(rhs *AddPaths) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp:state. // State information relating to the AS_PATH manipulation // mechanisms for the BGP peer or group. type AsPathOptionsState struct { // original -> bgp:allow-own-as // Specify the number of occurrences of the local BGP speaker's // AS that can occur within the AS_PATH before it is rejected. AllowOwnAs uint8 `mapstructure:"allow-own-as" json:"allow-own-as,omitempty"` // original -> bgp:replace-peer-as // bgp:replace-peer-as's original type is boolean. // Replace occurrences of the peer's AS in the AS_PATH // with the local autonomous system number. ReplacePeerAs bool `mapstructure:"replace-peer-as" json:"replace-peer-as,omitempty"` } // struct for container bgp:config. // Configuration parameters relating to AS_PATH manipulation // for the BGP peer or group. type AsPathOptionsConfig struct { // original -> bgp:allow-own-as // Specify the number of occurrences of the local BGP speaker's // AS that can occur within the AS_PATH before it is rejected. AllowOwnAs uint8 `mapstructure:"allow-own-as" json:"allow-own-as,omitempty"` // original -> bgp:replace-peer-as // bgp:replace-peer-as's original type is boolean. // Replace occurrences of the peer's AS in the AS_PATH // with the local autonomous system number. ReplacePeerAs bool `mapstructure:"replace-peer-as" json:"replace-peer-as,omitempty"` } func (lhs *AsPathOptionsConfig) Equal(rhs *AsPathOptionsConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.AllowOwnAs != rhs.AllowOwnAs { return false } if lhs.ReplacePeerAs != rhs.ReplacePeerAs { return false } return true } // struct for container bgp:as-path-options. // AS_PATH manipulation parameters for the BGP neighbor or // group. type AsPathOptions struct { // original -> bgp:as-path-options-config // Configuration parameters relating to AS_PATH manipulation // for the BGP peer or group. Config AsPathOptionsConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:as-path-options-state // State information relating to the AS_PATH manipulation // mechanisms for the BGP peer or group. State AsPathOptionsState `mapstructure:"state" json:"state,omitempty"` } func (lhs *AsPathOptions) Equal(rhs *AsPathOptions) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp:state. // State information relating to route reflection for the // BGP neighbor or group. type RouteReflectorState struct { // original -> bgp:route-reflector-cluster-id // route-reflector cluster id to use when local router is // configured as a route reflector. Commonly set at the group // level, but allows a different cluster // id to be set for each neighbor. RouteReflectorClusterId RrClusterIdType `mapstructure:"route-reflector-cluster-id" json:"route-reflector-cluster-id,omitempty"` // original -> bgp:route-reflector-client // bgp:route-reflector-client's original type is boolean. // Configure the neighbor as a route reflector client. RouteReflectorClient bool `mapstructure:"route-reflector-client" json:"route-reflector-client,omitempty"` } // struct for container bgp:config. // Configuraton parameters relating to route reflection // for the BGP neighbor or group. type RouteReflectorConfig struct { // original -> bgp:route-reflector-cluster-id // route-reflector cluster id to use when local router is // configured as a route reflector. Commonly set at the group // level, but allows a different cluster // id to be set for each neighbor. RouteReflectorClusterId RrClusterIdType `mapstructure:"route-reflector-cluster-id" json:"route-reflector-cluster-id,omitempty"` // original -> bgp:route-reflector-client // bgp:route-reflector-client's original type is boolean. // Configure the neighbor as a route reflector client. RouteReflectorClient bool `mapstructure:"route-reflector-client" json:"route-reflector-client,omitempty"` } func (lhs *RouteReflectorConfig) Equal(rhs *RouteReflectorConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.RouteReflectorClusterId != rhs.RouteReflectorClusterId { return false } if lhs.RouteReflectorClient != rhs.RouteReflectorClient { return false } return true } // struct for container bgp:route-reflector. // Route reflector parameters for the BGP neighbor or group. type RouteReflector struct { // original -> bgp:route-reflector-config // Configuraton parameters relating to route reflection // for the BGP neighbor or group. Config RouteReflectorConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:route-reflector-state // State information relating to route reflection for the // BGP neighbor or group. State RouteReflectorState `mapstructure:"state" json:"state,omitempty"` } func (lhs *RouteReflector) Equal(rhs *RouteReflector) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp:state. // State information for eBGP multihop, for the BGP neighbor // or group. type EbgpMultihopState struct { // original -> bgp:enabled // bgp:enabled's original type is boolean. // When enabled the referenced group or neighbors are permitted // to be indirectly connected - including cases where the TTL // can be decremented between the BGP peers. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> bgp:multihop-ttl // Time-to-live value to use when packets are sent to the // referenced group or neighbors and ebgp-multihop is enabled. MultihopTtl uint8 `mapstructure:"multihop-ttl" json:"multihop-ttl,omitempty"` } // struct for container bgp:config. // Configuration parameters relating to eBGP multihop for the // BGP neighbor or group. type EbgpMultihopConfig struct { // original -> bgp:enabled // bgp:enabled's original type is boolean. // When enabled the referenced group or neighbors are permitted // to be indirectly connected - including cases where the TTL // can be decremented between the BGP peers. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> bgp:multihop-ttl // Time-to-live value to use when packets are sent to the // referenced group or neighbors and ebgp-multihop is enabled. MultihopTtl uint8 `mapstructure:"multihop-ttl" json:"multihop-ttl,omitempty"` } func (lhs *EbgpMultihopConfig) Equal(rhs *EbgpMultihopConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Enabled != rhs.Enabled { return false } if lhs.MultihopTtl != rhs.MultihopTtl { return false } return true } // struct for container bgp:ebgp-multihop. // eBGP multi-hop parameters for the BGP neighbor or group. type EbgpMultihop struct { // original -> bgp:ebgp-multihop-config // Configuration parameters relating to eBGP multihop for the // BGP neighbor or group. Config EbgpMultihopConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:ebgp-multihop-state // State information for eBGP multihop, for the BGP neighbor // or group. State EbgpMultihopState `mapstructure:"state" json:"state,omitempty"` } func (lhs *EbgpMultihop) Equal(rhs *EbgpMultihop) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp:state. // State information relating to logging for the BGP neighbor // or group. type LoggingOptionsState struct { // original -> bgp:log-neighbor-state-changes // bgp:log-neighbor-state-changes's original type is boolean. // Configure logging of peer state changes. Default is // to enable logging of peer state changes. LogNeighborStateChanges bool `mapstructure:"log-neighbor-state-changes" json:"log-neighbor-state-changes,omitempty"` } // struct for container bgp:config. // Configuration parameters enabling or modifying logging // for events relating to the BGP neighbor or group. type LoggingOptionsConfig struct { // original -> bgp:log-neighbor-state-changes // bgp:log-neighbor-state-changes's original type is boolean. // Configure logging of peer state changes. Default is // to enable logging of peer state changes. LogNeighborStateChanges bool `mapstructure:"log-neighbor-state-changes" json:"log-neighbor-state-changes,omitempty"` } func (lhs *LoggingOptionsConfig) Equal(rhs *LoggingOptionsConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.LogNeighborStateChanges != rhs.LogNeighborStateChanges { return false } return true } // struct for container bgp:logging-options. // Logging options for events related to the BGP neighbor or // group. type LoggingOptions struct { // original -> bgp:logging-options-config // Configuration parameters enabling or modifying logging // for events relating to the BGP neighbor or group. Config LoggingOptionsConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:logging-options-state // State information relating to logging for the BGP neighbor // or group. State LoggingOptionsState `mapstructure:"state" json:"state,omitempty"` } func (lhs *LoggingOptions) Equal(rhs *LoggingOptions) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp:state. // State information relating to enhanced error handling // mechanisms for the BGP neighbor or group. type ErrorHandlingState struct { // original -> bgp:treat-as-withdraw // bgp:treat-as-withdraw's original type is boolean. // Specify whether erroneous UPDATE messages for which the // NLRI can be extracted are reated as though the NLRI is // withdrawn - avoiding session reset. TreatAsWithdraw bool `mapstructure:"treat-as-withdraw" json:"treat-as-withdraw,omitempty"` // original -> bgp-op:erroneous-update-messages // The number of BGP UPDATE messages for which the // treat-as-withdraw mechanism has been applied based // on erroneous message contents. ErroneousUpdateMessages uint32 `mapstructure:"erroneous-update-messages" json:"erroneous-update-messages,omitempty"` } // struct for container bgp:config. // Configuration parameters enabling or modifying the // behavior or enhanced error handling mechanisms for the BGP // neighbor or group. type ErrorHandlingConfig struct { // original -> bgp:treat-as-withdraw // bgp:treat-as-withdraw's original type is boolean. // Specify whether erroneous UPDATE messages for which the // NLRI can be extracted are reated as though the NLRI is // withdrawn - avoiding session reset. TreatAsWithdraw bool `mapstructure:"treat-as-withdraw" json:"treat-as-withdraw,omitempty"` } func (lhs *ErrorHandlingConfig) Equal(rhs *ErrorHandlingConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.TreatAsWithdraw != rhs.TreatAsWithdraw { return false } return true } // struct for container bgp:error-handling. // Error handling parameters used for the BGP neighbor or // group. type ErrorHandling struct { // original -> bgp:error-handling-config // Configuration parameters enabling or modifying the // behavior or enhanced error handling mechanisms for the BGP // neighbor or group. Config ErrorHandlingConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:error-handling-state // State information relating to enhanced error handling // mechanisms for the BGP neighbor or group. State ErrorHandlingState `mapstructure:"state" json:"state,omitempty"` } func (lhs *ErrorHandling) Equal(rhs *ErrorHandling) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp:state. // State information relating to the transport session(s) // used for the BGP neighbor or group. type TransportState struct { // original -> bgp:tcp-mss // Sets the max segment size for BGP TCP sessions. TcpMss uint16 `mapstructure:"tcp-mss" json:"tcp-mss,omitempty"` // original -> bgp:mtu-discovery // bgp:mtu-discovery's original type is boolean. // Turns path mtu discovery for BGP TCP sessions on (true) // or off (false). MtuDiscovery bool `mapstructure:"mtu-discovery" json:"mtu-discovery,omitempty"` // original -> bgp:passive-mode // bgp:passive-mode's original type is boolean. // Wait for peers to issue requests to open a BGP session, // rather than initiating sessions from the local router. PassiveMode bool `mapstructure:"passive-mode" json:"passive-mode,omitempty"` // original -> bgp:local-address // bgp:local-address's original type is union. // Set the local IP (either IPv4 or IPv6) address to use // for the session when sending BGP update messages. This // may be expressed as either an IP address or reference // to the name of an interface. LocalAddress string `mapstructure:"local-address" json:"local-address,omitempty"` // original -> bgp-op:local-port // bgp-op:local-port's original type is inet:port-number. // Local TCP port being used for the TCP session supporting // the BGP session. LocalPort uint16 `mapstructure:"local-port" json:"local-port,omitempty"` // original -> bgp-op:remote-address // bgp-op:remote-address's original type is inet:ip-address. // Remote address to which the BGP session has been // established. RemoteAddress string `mapstructure:"remote-address" json:"remote-address,omitempty"` // original -> bgp-op:remote-port // bgp-op:remote-port's original type is inet:port-number. // Remote port being used by the peer for the TCP session // supporting the BGP session. RemotePort uint16 `mapstructure:"remote-port" json:"remote-port,omitempty"` } // struct for container bgp:config. // Configuration parameters relating to the transport // session(s) used for the BGP neighbor or group. type TransportConfig struct { // original -> bgp:tcp-mss // Sets the max segment size for BGP TCP sessions. TcpMss uint16 `mapstructure:"tcp-mss" json:"tcp-mss,omitempty"` // original -> bgp:mtu-discovery // bgp:mtu-discovery's original type is boolean. // Turns path mtu discovery for BGP TCP sessions on (true) // or off (false). MtuDiscovery bool `mapstructure:"mtu-discovery" json:"mtu-discovery,omitempty"` // original -> bgp:passive-mode // bgp:passive-mode's original type is boolean. // Wait for peers to issue requests to open a BGP session, // rather than initiating sessions from the local router. PassiveMode bool `mapstructure:"passive-mode" json:"passive-mode,omitempty"` // original -> bgp:local-address // bgp:local-address's original type is union. // Set the local IP (either IPv4 or IPv6) address to use // for the session when sending BGP update messages. This // may be expressed as either an IP address or reference // to the name of an interface. LocalAddress string `mapstructure:"local-address" json:"local-address,omitempty"` // original -> gobgp:remote-port // gobgp:remote-port's original type is inet:port-number. RemotePort uint16 `mapstructure:"remote-port" json:"remote-port,omitempty"` // original -> gobgp:ttl // TTL value for BGP packets. Ttl uint8 `mapstructure:"ttl" json:"ttl,omitempty"` } func (lhs *TransportConfig) Equal(rhs *TransportConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.TcpMss != rhs.TcpMss { return false } if lhs.MtuDiscovery != rhs.MtuDiscovery { return false } if lhs.PassiveMode != rhs.PassiveMode { return false } if lhs.LocalAddress != rhs.LocalAddress { return false } if lhs.RemotePort != rhs.RemotePort { return false } if lhs.Ttl != rhs.Ttl { return false } return true } // struct for container bgp:transport. // Transport session parameters for the BGP neighbor or group. type Transport struct { // original -> bgp:transport-config // Configuration parameters relating to the transport // session(s) used for the BGP neighbor or group. Config TransportConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:transport-state // State information relating to the transport session(s) // used for the BGP neighbor or group. State TransportState `mapstructure:"state" json:"state,omitempty"` } func (lhs *Transport) Equal(rhs *Transport) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp:state. // State information relating to the timers used for the BGP // neighbor or group. type TimersState struct { // original -> bgp:connect-retry // bgp:connect-retry's original type is decimal64. // Time interval in seconds between attempts to establish a // session with the peer. ConnectRetry float64 `mapstructure:"connect-retry" json:"connect-retry,omitempty"` // original -> bgp:hold-time // bgp:hold-time's original type is decimal64. // Time interval in seconds that a BGP session will be // considered active in the absence of keepalive or other // messages from the peer. The hold-time is typically // set to 3x the keepalive-interval. HoldTime float64 `mapstructure:"hold-time" json:"hold-time,omitempty"` // original -> bgp:keepalive-interval // bgp:keepalive-interval's original type is decimal64. // Time interval in seconds between transmission of keepalive // messages to the neighbor. Typically set to 1/3 the // hold-time. KeepaliveInterval float64 `mapstructure:"keepalive-interval" json:"keepalive-interval,omitempty"` // original -> bgp:minimum-advertisement-interval // bgp:minimum-advertisement-interval's original type is decimal64. // Minimum time which must elapse between subsequent UPDATE // messages relating to a common set of NLRI being transmitted // to a peer. This timer is referred to as // MinRouteAdvertisementIntervalTimer by RFC 4721 and serves to // reduce the number of UPDATE messages transmitted when a // particular set of NLRI exhibit instability. MinimumAdvertisementInterval float64 `mapstructure:"minimum-advertisement-interval" json:"minimum-advertisement-interval,omitempty"` // original -> bgp-op:uptime // bgp-op:uptime's original type is yang:timeticks. // This timer determines the amount of time since the // BGP last transitioned in or out of the Established // state. Uptime int64 `mapstructure:"uptime" json:"uptime,omitempty"` // original -> bgp-op:negotiated-hold-time // bgp-op:negotiated-hold-time's original type is decimal64. // The negotiated hold-time for the BGP session. NegotiatedHoldTime float64 `mapstructure:"negotiated-hold-time" json:"negotiated-hold-time,omitempty"` // original -> gobgp:idle-hold-time-after-reset // gobgp:idle-hold-time-after-reset's original type is decimal64. // Time interval in seconds that a BGP session will be // in idle state after neighbor reset operation. IdleHoldTimeAfterReset float64 `mapstructure:"idle-hold-time-after-reset" json:"idle-hold-time-after-reset,omitempty"` // original -> gobgp:downtime // gobgp:downtime's original type is yang:timeticks. // This timer determines the amount of time since the // BGP last transitioned out of the Established state. Downtime int64 `mapstructure:"downtime" json:"downtime,omitempty"` // original -> gobgp:update-recv-time // The number of seconds elasped since January 1, 1970 UTC // last time the BGP session received an UPDATE message. UpdateRecvTime int64 `mapstructure:"update-recv-time" json:"update-recv-time,omitempty"` } // struct for container bgp:config. // Configuration parameters relating to timers used for the // BGP neighbor or group. type TimersConfig struct { // original -> bgp:connect-retry // bgp:connect-retry's original type is decimal64. // Time interval in seconds between attempts to establish a // session with the peer. ConnectRetry float64 `mapstructure:"connect-retry" json:"connect-retry,omitempty"` // original -> bgp:hold-time // bgp:hold-time's original type is decimal64. // Time interval in seconds that a BGP session will be // considered active in the absence of keepalive or other // messages from the peer. The hold-time is typically // set to 3x the keepalive-interval. HoldTime float64 `mapstructure:"hold-time" json:"hold-time,omitempty"` // original -> bgp:keepalive-interval // bgp:keepalive-interval's original type is decimal64. // Time interval in seconds between transmission of keepalive // messages to the neighbor. Typically set to 1/3 the // hold-time. KeepaliveInterval float64 `mapstructure:"keepalive-interval" json:"keepalive-interval,omitempty"` // original -> bgp:minimum-advertisement-interval // bgp:minimum-advertisement-interval's original type is decimal64. // Minimum time which must elapse between subsequent UPDATE // messages relating to a common set of NLRI being transmitted // to a peer. This timer is referred to as // MinRouteAdvertisementIntervalTimer by RFC 4721 and serves to // reduce the number of UPDATE messages transmitted when a // particular set of NLRI exhibit instability. MinimumAdvertisementInterval float64 `mapstructure:"minimum-advertisement-interval" json:"minimum-advertisement-interval,omitempty"` // original -> gobgp:idle-hold-time-after-reset // gobgp:idle-hold-time-after-reset's original type is decimal64. // Time interval in seconds that a BGP session will be // in idle state after neighbor reset operation. IdleHoldTimeAfterReset float64 `mapstructure:"idle-hold-time-after-reset" json:"idle-hold-time-after-reset,omitempty"` } func (lhs *TimersConfig) Equal(rhs *TimersConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.ConnectRetry != rhs.ConnectRetry { return false } if lhs.HoldTime != rhs.HoldTime { return false } if lhs.KeepaliveInterval != rhs.KeepaliveInterval { return false } if lhs.MinimumAdvertisementInterval != rhs.MinimumAdvertisementInterval { return false } if lhs.IdleHoldTimeAfterReset != rhs.IdleHoldTimeAfterReset { return false } return true } // struct for container bgp:timers. // Timers related to a BGP neighbor or group. type Timers struct { // original -> bgp:timers-config // Configuration parameters relating to timers used for the // BGP neighbor or group. Config TimersConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:timers-state // State information relating to the timers used for the BGP // neighbor or group. State TimersState `mapstructure:"state" json:"state,omitempty"` } func (lhs *Timers) Equal(rhs *Timers) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container gobgp:adj-table. type AdjTable struct { // original -> gobgp:ADVERTISED Advertised uint32 `mapstructure:"advertised" json:"advertised,omitempty"` // original -> gobgp:FILTERED Filtered uint32 `mapstructure:"filtered" json:"filtered,omitempty"` // original -> gobgp:RECEIVED Received uint32 `mapstructure:"received" json:"received,omitempty"` // original -> gobgp:ACCEPTED Accepted uint32 `mapstructure:"accepted" json:"accepted,omitempty"` } func (lhs *AdjTable) Equal(rhs *AdjTable) bool { if lhs == nil || rhs == nil { return false } if lhs.Advertised != rhs.Advertised { return false } if lhs.Filtered != rhs.Filtered { return false } if lhs.Received != rhs.Received { return false } if lhs.Accepted != rhs.Accepted { return false } return true } // struct for container bgp:queues. // Counters related to queued messages associated with the // BGP neighbor. type Queues struct { // original -> bgp-op:input // The number of messages received from the peer currently // queued. Input uint32 `mapstructure:"input" json:"input,omitempty"` // original -> bgp-op:output // The number of messages queued to be sent to the peer. Output uint32 `mapstructure:"output" json:"output,omitempty"` } func (lhs *Queues) Equal(rhs *Queues) bool { if lhs == nil || rhs == nil { return false } if lhs.Input != rhs.Input { return false } if lhs.Output != rhs.Output { return false } return true } // struct for container bgp:received. // Counters for BGP messages received from the neighbor. type Received struct { // original -> bgp-op:UPDATE // Number of BGP UPDATE messages announcing, withdrawing // or modifying paths exchanged. Update uint64 `mapstructure:"update" json:"update,omitempty"` // original -> bgp-op:NOTIFICATION // Number of BGP NOTIFICATION messages indicating an // error condition has occurred exchanged. Notification uint64 `mapstructure:"notification" json:"notification,omitempty"` // original -> gobgp:OPEN // Number of BGP open messages announcing, withdrawing // or modifying paths exchanged. Open uint64 `mapstructure:"open" json:"open,omitempty"` // original -> gobgp:REFRESH // Number of BGP Route-Refresh messages indicating an // error condition has occurred exchanged. Refresh uint64 `mapstructure:"refresh" json:"refresh,omitempty"` // original -> gobgp:KEEPALIVE // Number of BGP Keepalive messages indicating an // error condition has occurred exchanged. Keepalive uint64 `mapstructure:"keepalive" json:"keepalive,omitempty"` // original -> gobgp:DYNAMIC-CAP // Number of BGP dynamic-cap messages indicating an // error condition has occurred exchanged. DynamicCap uint64 `mapstructure:"dynamic-cap" json:"dynamic-cap,omitempty"` // original -> gobgp:WITHDRAW-UPDATE // Number of updates subjected to treat-as-withdraw treatment. WithdrawUpdate uint32 `mapstructure:"withdraw-update" json:"withdraw-update,omitempty"` // original -> gobgp:WITHDRAW-PREFIX // Number of prefixes subjected to treat-as-withdraw treatment. WithdrawPrefix uint32 `mapstructure:"withdraw-prefix" json:"withdraw-prefix,omitempty"` // original -> gobgp:DISCARDED // Number of discarded messages indicating an // error condition has occurred exchanged. Discarded uint64 `mapstructure:"discarded" json:"discarded,omitempty"` // original -> gobgp:TOTAL // Number of total messages indicating an // error condition has occurred exchanged. Total uint64 `mapstructure:"total" json:"total,omitempty"` } func (lhs *Received) Equal(rhs *Received) bool { if lhs == nil || rhs == nil { return false } if lhs.Update != rhs.Update { return false } if lhs.Notification != rhs.Notification { return false } if lhs.Open != rhs.Open { return false } if lhs.Refresh != rhs.Refresh { return false } if lhs.Keepalive != rhs.Keepalive { return false } if lhs.DynamicCap != rhs.DynamicCap { return false } if lhs.WithdrawUpdate != rhs.WithdrawUpdate { return false } if lhs.WithdrawPrefix != rhs.WithdrawPrefix { return false } if lhs.Discarded != rhs.Discarded { return false } if lhs.Total != rhs.Total { return false } return true } // struct for container bgp:sent. // Counters relating to BGP messages sent to the neighbor. type Sent struct { // original -> bgp-op:UPDATE // Number of BGP UPDATE messages announcing, withdrawing // or modifying paths exchanged. Update uint64 `mapstructure:"update" json:"update,omitempty"` // original -> bgp-op:NOTIFICATION // Number of BGP NOTIFICATION messages indicating an // error condition has occurred exchanged. Notification uint64 `mapstructure:"notification" json:"notification,omitempty"` // original -> gobgp:OPEN // Number of BGP open messages announcing, withdrawing // or modifying paths exchanged. Open uint64 `mapstructure:"open" json:"open,omitempty"` // original -> gobgp:REFRESH // Number of BGP Route-Refresh messages indicating an // error condition has occurred exchanged. Refresh uint64 `mapstructure:"refresh" json:"refresh,omitempty"` // original -> gobgp:KEEPALIVE // Number of BGP Keepalive messages indicating an // error condition has occurred exchanged. Keepalive uint64 `mapstructure:"keepalive" json:"keepalive,omitempty"` // original -> gobgp:DYNAMIC-CAP // Number of BGP dynamic-cap messages indicating an // error condition has occurred exchanged. DynamicCap uint64 `mapstructure:"dynamic-cap" json:"dynamic-cap,omitempty"` // original -> gobgp:WITHDRAW-UPDATE // Number of updates subjected to treat-as-withdraw treatment. WithdrawUpdate uint32 `mapstructure:"withdraw-update" json:"withdraw-update,omitempty"` // original -> gobgp:WITHDRAW-PREFIX // Number of prefixes subjected to treat-as-withdraw treatment. WithdrawPrefix uint32 `mapstructure:"withdraw-prefix" json:"withdraw-prefix,omitempty"` // original -> gobgp:DISCARDED // Number of discarded messages indicating an // error condition has occurred exchanged. Discarded uint64 `mapstructure:"discarded" json:"discarded,omitempty"` // original -> gobgp:TOTAL // Number of total messages indicating an // error condition has occurred exchanged. Total uint64 `mapstructure:"total" json:"total,omitempty"` } func (lhs *Sent) Equal(rhs *Sent) bool { if lhs == nil || rhs == nil { return false } if lhs.Update != rhs.Update { return false } if lhs.Notification != rhs.Notification { return false } if lhs.Open != rhs.Open { return false } if lhs.Refresh != rhs.Refresh { return false } if lhs.Keepalive != rhs.Keepalive { return false } if lhs.DynamicCap != rhs.DynamicCap { return false } if lhs.WithdrawUpdate != rhs.WithdrawUpdate { return false } if lhs.WithdrawPrefix != rhs.WithdrawPrefix { return false } if lhs.Discarded != rhs.Discarded { return false } if lhs.Total != rhs.Total { return false } return true } // struct for container bgp:messages. // Counters for BGP messages sent and received from the // neighbor. type Messages struct { // original -> bgp:sent // Counters relating to BGP messages sent to the neighbor. Sent Sent `mapstructure:"sent" json:"sent,omitempty"` // original -> bgp:received // Counters for BGP messages received from the neighbor. Received Received `mapstructure:"received" json:"received,omitempty"` } func (lhs *Messages) Equal(rhs *Messages) bool { if lhs == nil || rhs == nil { return false } if !lhs.Sent.Equal(&(rhs.Sent)) { return false } if !lhs.Received.Equal(&(rhs.Received)) { return false } return true } // struct for container bgp:state. // State information relating to the BGP neighbor or group. type NeighborState struct { // original -> bgp:peer-as // bgp:peer-as's original type is inet:as-number. // AS number of the peer. PeerAs uint32 `mapstructure:"peer-as" json:"peer-as,omitempty"` // original -> bgp:local-as // bgp:local-as's original type is inet:as-number. // The local autonomous system number that is to be used // when establishing sessions with the remote peer or peer // group, if this differs from the global BGP router // autonomous system number. LocalAs uint32 `mapstructure:"local-as" json:"local-as,omitempty"` // original -> bgp:peer-type // Explicitly designate the peer or peer group as internal // (iBGP) or external (eBGP). PeerType PeerType `mapstructure:"peer-type" json:"peer-type,omitempty"` // original -> bgp:auth-password // Configures an MD5 authentication password for use with // neighboring devices. AuthPassword string `mapstructure:"auth-password" json:"auth-password,omitempty"` // original -> bgp:remove-private-as // Remove private AS numbers from updates sent to peers. RemovePrivateAs RemovePrivateAsOption `mapstructure:"remove-private-as" json:"remove-private-as,omitempty"` // original -> bgp:route-flap-damping // bgp:route-flap-damping's original type is boolean. // Enable route flap damping. RouteFlapDamping bool `mapstructure:"route-flap-damping" json:"route-flap-damping,omitempty"` // original -> bgp:send-community // Specify which types of community should be sent to the // neighbor or group. The default is to not send the // community attribute. SendCommunity CommunityType `mapstructure:"send-community" json:"send-community,omitempty"` // original -> bgp:description // An optional textual description (intended primarily for use // with a peer or group. Description string `mapstructure:"description" json:"description,omitempty"` // original -> bgp:peer-group // The peer-group with which this neighbor is associated. PeerGroup string `mapstructure:"peer-group" json:"peer-group,omitempty"` // original -> bgp:neighbor-address // bgp:neighbor-address's original type is inet:ip-address. // Address of the BGP peer, either in IPv4 or IPv6. NeighborAddress string `mapstructure:"neighbor-address" json:"neighbor-address,omitempty"` // original -> bgp-op:session-state // Operational state of the BGP peer. SessionState SessionState `mapstructure:"session-state" json:"session-state,omitempty"` // original -> bgp-op:supported-capabilities // BGP capabilities negotiated as supported with the peer. SupportedCapabilitiesList []BgpCapability `mapstructure:"supported-capabilities-list" json:"supported-capabilities-list,omitempty"` // original -> bgp:messages // Counters for BGP messages sent and received from the // neighbor. Messages Messages `mapstructure:"messages" json:"messages,omitempty"` // original -> bgp:queues // Counters related to queued messages associated with the // BGP neighbor. Queues Queues `mapstructure:"queues" json:"queues,omitempty"` // original -> gobgp:adj-table AdjTable AdjTable `mapstructure:"adj-table" json:"adj-table,omitempty"` // original -> gobgp:remote-capability // original type is list of bgp-capability RemoteCapabilityList []bgp.ParameterCapabilityInterface `mapstructure:"remote-capability-list" json:"remote-capability-list,omitempty"` // original -> gobgp:local-capability // original type is list of bgp-capability LocalCapabilityList []bgp.ParameterCapabilityInterface `mapstructure:"local-capability-list" json:"local-capability-list,omitempty"` // original -> gobgp:received-open-message // gobgp:received-open-message's original type is bgp-open-message. ReceivedOpenMessage *bgp.BGPMessage `mapstructure:"received-open-message" json:"received-open-message,omitempty"` // original -> gobgp:admin-down // gobgp:admin-down's original type is boolean. // The state of administrative operation. If the state is true, it indicates the neighbor is disabled by the administrator. AdminDown bool `mapstructure:"admin-down" json:"admin-down,omitempty"` // original -> gobgp:admin-state AdminState AdminState `mapstructure:"admin-state" json:"admin-state,omitempty"` // original -> gobgp:established-count // The number of how many the peer became established state. EstablishedCount uint32 `mapstructure:"established-count" json:"established-count,omitempty"` // original -> gobgp:flops // The number of flip-flops. Flops uint32 `mapstructure:"flops" json:"flops,omitempty"` // original -> gobgp:neighbor-interface NeighborInterface string `mapstructure:"neighbor-interface" json:"neighbor-interface,omitempty"` // original -> gobgp:vrf Vrf string `mapstructure:"vrf" json:"vrf,omitempty"` // original -> gobgp:remote-router-id RemoteRouterId string `mapstructure:"remote-router-id" json:"remote-router-id,omitempty"` } // struct for container bgp:config. // Configuration parameters relating to the BGP neighbor or // group. type NeighborConfig struct { // original -> bgp:peer-as // bgp:peer-as's original type is inet:as-number. // AS number of the peer. PeerAs uint32 `mapstructure:"peer-as" json:"peer-as,omitempty"` // original -> bgp:local-as // bgp:local-as's original type is inet:as-number. // The local autonomous system number that is to be used // when establishing sessions with the remote peer or peer // group, if this differs from the global BGP router // autonomous system number. LocalAs uint32 `mapstructure:"local-as" json:"local-as,omitempty"` // original -> bgp:peer-type // Explicitly designate the peer or peer group as internal // (iBGP) or external (eBGP). PeerType PeerType `mapstructure:"peer-type" json:"peer-type,omitempty"` // original -> bgp:auth-password // Configures an MD5 authentication password for use with // neighboring devices. AuthPassword string `mapstructure:"auth-password" json:"auth-password,omitempty"` // original -> bgp:remove-private-as // Remove private AS numbers from updates sent to peers. RemovePrivateAs RemovePrivateAsOption `mapstructure:"remove-private-as" json:"remove-private-as,omitempty"` // original -> bgp:route-flap-damping // bgp:route-flap-damping's original type is boolean. // Enable route flap damping. RouteFlapDamping bool `mapstructure:"route-flap-damping" json:"route-flap-damping,omitempty"` // original -> bgp:send-community // Specify which types of community should be sent to the // neighbor or group. The default is to not send the // community attribute. SendCommunity CommunityType `mapstructure:"send-community" json:"send-community,omitempty"` // original -> bgp:description // An optional textual description (intended primarily for use // with a peer or group. Description string `mapstructure:"description" json:"description,omitempty"` // original -> bgp:peer-group // The peer-group with which this neighbor is associated. PeerGroup string `mapstructure:"peer-group" json:"peer-group,omitempty"` // original -> bgp:neighbor-address // bgp:neighbor-address's original type is inet:ip-address. // Address of the BGP peer, either in IPv4 or IPv6. NeighborAddress string `mapstructure:"neighbor-address" json:"neighbor-address,omitempty"` // original -> gobgp:admin-down // gobgp:admin-down's original type is boolean. // The config of administrative operation. If state, indicates the neighbor is disabled by the administrator. AdminDown bool `mapstructure:"admin-down" json:"admin-down,omitempty"` // original -> gobgp:neighbor-interface NeighborInterface string `mapstructure:"neighbor-interface" json:"neighbor-interface,omitempty"` // original -> gobgp:vrf Vrf string `mapstructure:"vrf" json:"vrf,omitempty"` } func (lhs *NeighborConfig) Equal(rhs *NeighborConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.PeerAs != rhs.PeerAs { return false } if lhs.LocalAs != rhs.LocalAs { return false } if lhs.PeerType != rhs.PeerType { return false } if lhs.AuthPassword != rhs.AuthPassword { return false } if lhs.RemovePrivateAs != rhs.RemovePrivateAs { return false } if lhs.RouteFlapDamping != rhs.RouteFlapDamping { return false } if lhs.SendCommunity != rhs.SendCommunity { return false } if lhs.Description != rhs.Description { return false } if lhs.PeerGroup != rhs.PeerGroup { return false } if lhs.NeighborAddress != rhs.NeighborAddress { return false } if lhs.AdminDown != rhs.AdminDown { return false } if lhs.NeighborInterface != rhs.NeighborInterface { return false } if lhs.Vrf != rhs.Vrf { return false } return true } // struct for container bgp:neighbor. // List of BGP neighbors configured on the local system, // uniquely identified by peer IPv[46] address. type Neighbor struct { // original -> bgp:neighbor-address // original -> bgp:neighbor-config // Configuration parameters relating to the BGP neighbor or // group. Config NeighborConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:neighbor-state // State information relating to the BGP neighbor or group. State NeighborState `mapstructure:"state" json:"state,omitempty"` // original -> bgp:timers // Timers related to a BGP neighbor or group. Timers Timers `mapstructure:"timers" json:"timers,omitempty"` // original -> bgp:transport // Transport session parameters for the BGP neighbor or group. Transport Transport `mapstructure:"transport" json:"transport,omitempty"` // original -> bgp:error-handling // Error handling parameters used for the BGP neighbor or // group. ErrorHandling ErrorHandling `mapstructure:"error-handling" json:"error-handling,omitempty"` // original -> bgp:logging-options // Logging options for events related to the BGP neighbor or // group. LoggingOptions LoggingOptions `mapstructure:"logging-options" json:"logging-options,omitempty"` // original -> bgp:ebgp-multihop // eBGP multi-hop parameters for the BGP neighbor or group. EbgpMultihop EbgpMultihop `mapstructure:"ebgp-multihop" json:"ebgp-multihop,omitempty"` // original -> bgp:route-reflector // Route reflector parameters for the BGP neighbor or group. RouteReflector RouteReflector `mapstructure:"route-reflector" json:"route-reflector,omitempty"` // original -> bgp:as-path-options // AS_PATH manipulation parameters for the BGP neighbor or // group. AsPathOptions AsPathOptions `mapstructure:"as-path-options" json:"as-path-options,omitempty"` // original -> bgp:add-paths // Parameters relating to the advertisement and receipt of // multiple paths for a single NLRI (add-paths). AddPaths AddPaths `mapstructure:"add-paths" json:"add-paths,omitempty"` // original -> bgp:afi-safis // Per-address-family configuration parameters associated with // the neighbor or group. AfiSafis []AfiSafi `mapstructure:"afi-safis" json:"afi-safis,omitempty"` // original -> bgp:graceful-restart // Parameters relating the graceful restart mechanism for BGP. GracefulRestart GracefulRestart `mapstructure:"graceful-restart" json:"graceful-restart,omitempty"` // original -> rpol:apply-policy // Anchor point for routing policies in the model. // Import and export policies are with respect to the local // routing table, i.e., export (send) and import (receive), // depending on the context. ApplyPolicy ApplyPolicy `mapstructure:"apply-policy" json:"apply-policy,omitempty"` // original -> bgp-mp:use-multiple-paths // Parameters related to the use of multiple-paths for the same // NLRI when they are received only from this neighbor. UseMultiplePaths UseMultiplePaths `mapstructure:"use-multiple-paths" json:"use-multiple-paths,omitempty"` // original -> gobgp:route-server // Configure the local router as a route server. RouteServer RouteServer `mapstructure:"route-server" json:"route-server,omitempty"` // original -> gobgp:ttl-security // Configure TTL Security feature. TtlSecurity TtlSecurity `mapstructure:"ttl-security" json:"ttl-security,omitempty"` } func (lhs *Neighbor) Equal(rhs *Neighbor) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } if !lhs.Timers.Equal(&(rhs.Timers)) { return false } if !lhs.Transport.Equal(&(rhs.Transport)) { return false } if !lhs.ErrorHandling.Equal(&(rhs.ErrorHandling)) { return false } if !lhs.LoggingOptions.Equal(&(rhs.LoggingOptions)) { return false } if !lhs.EbgpMultihop.Equal(&(rhs.EbgpMultihop)) { return false } if !lhs.RouteReflector.Equal(&(rhs.RouteReflector)) { return false } if !lhs.AsPathOptions.Equal(&(rhs.AsPathOptions)) { return false } if !lhs.AddPaths.Equal(&(rhs.AddPaths)) { return false } if len(lhs.AfiSafis) != len(rhs.AfiSafis) { return false } { lmap := make(map[string]*AfiSafi) for i, l := range lhs.AfiSafis { lmap[mapkey(i, string(l.Config.AfiSafiName))] = &lhs.AfiSafis[i] } for i, r := range rhs.AfiSafis { if l, y := lmap[mapkey(i, string(r.Config.AfiSafiName))]; !y { return false } else if !r.Equal(l) { return false } } } if !lhs.GracefulRestart.Equal(&(rhs.GracefulRestart)) { return false } if !lhs.ApplyPolicy.Equal(&(rhs.ApplyPolicy)) { return false } if !lhs.UseMultiplePaths.Equal(&(rhs.UseMultiplePaths)) { return false } if !lhs.RouteServer.Equal(&(rhs.RouteServer)) { return false } if !lhs.TtlSecurity.Equal(&(rhs.TtlSecurity)) { return false } return true } // struct for container gobgp:state. type LongLivedGracefulRestartState struct { // original -> gobgp:enabled // gobgp:enabled's original type is boolean. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> gobgp:received // gobgp:received's original type is boolean. Received bool `mapstructure:"received" json:"received,omitempty"` // original -> gobgp:advertised // gobgp:advertised's original type is boolean. Advertised bool `mapstructure:"advertised" json:"advertised,omitempty"` // original -> gobgp:peer-restart-time PeerRestartTime uint32 `mapstructure:"peer-restart-time" json:"peer-restart-time,omitempty"` // original -> gobgp:peer-restart-timer-expired // gobgp:peer-restart-timer-expired's original type is boolean. PeerRestartTimerExpired bool `mapstructure:"peer-restart-timer-expired" json:"peer-restart-timer-expired,omitempty"` } // struct for container gobgp:config. type LongLivedGracefulRestartConfig struct { // original -> gobgp:enabled // gobgp:enabled's original type is boolean. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> gobgp:restart-time RestartTime uint32 `mapstructure:"restart-time" json:"restart-time,omitempty"` } func (lhs *LongLivedGracefulRestartConfig) Equal(rhs *LongLivedGracefulRestartConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Enabled != rhs.Enabled { return false } if lhs.RestartTime != rhs.RestartTime { return false } return true } // struct for container gobgp:long-lived-graceful-restart. type LongLivedGracefulRestart struct { // original -> gobgp:long-lived-graceful-restart-config Config LongLivedGracefulRestartConfig `mapstructure:"config" json:"config,omitempty"` // original -> gobgp:long-lived-graceful-restart-state State LongLivedGracefulRestartState `mapstructure:"state" json:"state,omitempty"` } func (lhs *LongLivedGracefulRestart) Equal(rhs *LongLivedGracefulRestart) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container gobgp:state. type RouteTargetMembershipState struct { // original -> gobgp:deferral-time DeferralTime uint16 `mapstructure:"deferral-time" json:"deferral-time,omitempty"` } // struct for container gobgp:config. type RouteTargetMembershipConfig struct { // original -> gobgp:deferral-time DeferralTime uint16 `mapstructure:"deferral-time" json:"deferral-time,omitempty"` } func (lhs *RouteTargetMembershipConfig) Equal(rhs *RouteTargetMembershipConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.DeferralTime != rhs.DeferralTime { return false } return true } // struct for container gobgp:route-target-membership. type RouteTargetMembership struct { // original -> gobgp:route-target-membership-config Config RouteTargetMembershipConfig `mapstructure:"config" json:"config,omitempty"` // original -> gobgp:route-target-membership-state State RouteTargetMembershipState `mapstructure:"state" json:"state,omitempty"` } func (lhs *RouteTargetMembership) Equal(rhs *RouteTargetMembership) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp-mp:l2vpn-evpn. // BGP EVPN configuration options. type L2vpnEvpn struct { // original -> bgp-mp:prefix-limit // Configure the maximum number of prefixes that will be // accepted from a peer. PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` } func (lhs *L2vpnEvpn) Equal(rhs *L2vpnEvpn) bool { if lhs == nil || rhs == nil { return false } if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } return true } // struct for container bgp-mp:l2vpn-vpls. // BGP-signalled VPLS configuration options. type L2vpnVpls struct { // original -> bgp-mp:prefix-limit // Configure the maximum number of prefixes that will be // accepted from a peer. PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` } func (lhs *L2vpnVpls) Equal(rhs *L2vpnVpls) bool { if lhs == nil || rhs == nil { return false } if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } return true } // struct for container bgp-mp:l3vpn-ipv6-multicast. // Multicast IPv6 L3VPN configuration options. type L3vpnIpv6Multicast struct { // original -> bgp-mp:prefix-limit // Configure the maximum number of prefixes that will be // accepted from a peer. PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` } func (lhs *L3vpnIpv6Multicast) Equal(rhs *L3vpnIpv6Multicast) bool { if lhs == nil || rhs == nil { return false } if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } return true } // struct for container bgp-mp:l3vpn-ipv4-multicast. // Multicast IPv4 L3VPN configuration options. type L3vpnIpv4Multicast struct { // original -> bgp-mp:prefix-limit // Configure the maximum number of prefixes that will be // accepted from a peer. PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` } func (lhs *L3vpnIpv4Multicast) Equal(rhs *L3vpnIpv4Multicast) bool { if lhs == nil || rhs == nil { return false } if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } return true } // struct for container bgp-mp:l3vpn-ipv6-unicast. // Unicast IPv6 L3VPN configuration options. type L3vpnIpv6Unicast struct { // original -> bgp-mp:prefix-limit // Configure the maximum number of prefixes that will be // accepted from a peer. PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` } func (lhs *L3vpnIpv6Unicast) Equal(rhs *L3vpnIpv6Unicast) bool { if lhs == nil || rhs == nil { return false } if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } return true } // struct for container bgp-mp:l3vpn-ipv4-unicast. // Unicast IPv4 L3VPN configuration options. type L3vpnIpv4Unicast struct { // original -> bgp-mp:prefix-limit // Configure the maximum number of prefixes that will be // accepted from a peer. PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` } func (lhs *L3vpnIpv4Unicast) Equal(rhs *L3vpnIpv4Unicast) bool { if lhs == nil || rhs == nil { return false } if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } return true } // struct for container bgp-mp:ipv6-labelled-unicast. // IPv6 Labelled Unicast configuration options. type Ipv6LabelledUnicast struct { // original -> bgp-mp:prefix-limit // Configure the maximum number of prefixes that will be // accepted from a peer. PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` } func (lhs *Ipv6LabelledUnicast) Equal(rhs *Ipv6LabelledUnicast) bool { if lhs == nil || rhs == nil { return false } if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } return true } // struct for container bgp-mp:ipv4-labelled-unicast. // IPv4 Labelled Unicast configuration options. type Ipv4LabelledUnicast struct { // original -> bgp-mp:prefix-limit // Configure the maximum number of prefixes that will be // accepted from a peer. PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` } func (lhs *Ipv4LabelledUnicast) Equal(rhs *Ipv4LabelledUnicast) bool { if lhs == nil || rhs == nil { return false } if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } return true } // struct for container bgp-mp:state. // State information for common IPv4 and IPv6 unicast // parameters. type Ipv6UnicastState struct { // original -> bgp-mp:send-default-route // bgp-mp:send-default-route's original type is boolean. // If set to true, send the default-route to the neighbour(s). SendDefaultRoute bool `mapstructure:"send-default-route" json:"send-default-route,omitempty"` } // struct for container bgp-mp:config. // Configuration parameters for common IPv4 and IPv6 unicast // AFI-SAFI options. type Ipv6UnicastConfig struct { // original -> bgp-mp:send-default-route // bgp-mp:send-default-route's original type is boolean. // If set to true, send the default-route to the neighbour(s). SendDefaultRoute bool `mapstructure:"send-default-route" json:"send-default-route,omitempty"` } func (lhs *Ipv6UnicastConfig) Equal(rhs *Ipv6UnicastConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.SendDefaultRoute != rhs.SendDefaultRoute { return false } return true } // struct for container bgp-mp:ipv6-unicast. // IPv6 unicast configuration options. type Ipv6Unicast struct { // original -> bgp-mp:prefix-limit // Configure the maximum number of prefixes that will be // accepted from a peer. PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` // original -> bgp-mp:ipv6-unicast-config // Configuration parameters for common IPv4 and IPv6 unicast // AFI-SAFI options. Config Ipv6UnicastConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp-mp:ipv6-unicast-state // State information for common IPv4 and IPv6 unicast // parameters. State Ipv6UnicastState `mapstructure:"state" json:"state,omitempty"` } func (lhs *Ipv6Unicast) Equal(rhs *Ipv6Unicast) bool { if lhs == nil || rhs == nil { return false } if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp-mp:state. // State information for common IPv4 and IPv6 unicast // parameters. type Ipv4UnicastState struct { // original -> bgp-mp:send-default-route // bgp-mp:send-default-route's original type is boolean. // If set to true, send the default-route to the neighbour(s). SendDefaultRoute bool `mapstructure:"send-default-route" json:"send-default-route,omitempty"` } // struct for container bgp-mp:config. // Configuration parameters for common IPv4 and IPv6 unicast // AFI-SAFI options. type Ipv4UnicastConfig struct { // original -> bgp-mp:send-default-route // bgp-mp:send-default-route's original type is boolean. // If set to true, send the default-route to the neighbour(s). SendDefaultRoute bool `mapstructure:"send-default-route" json:"send-default-route,omitempty"` } func (lhs *Ipv4UnicastConfig) Equal(rhs *Ipv4UnicastConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.SendDefaultRoute != rhs.SendDefaultRoute { return false } return true } // struct for container bgp-mp:state. // State information relating to the prefix-limit for the // AFI-SAFI. type PrefixLimitState struct { // original -> bgp-mp:max-prefixes // Maximum number of prefixes that will be accepted // from the neighbour. MaxPrefixes uint32 `mapstructure:"max-prefixes" json:"max-prefixes,omitempty"` // original -> bgp-mp:shutdown-threshold-pct // Threshold on number of prefixes that can be received // from a neighbour before generation of warning messages // or log entries. Expressed as a percentage of // max-prefixes. ShutdownThresholdPct Percentage `mapstructure:"shutdown-threshold-pct" json:"shutdown-threshold-pct,omitempty"` // original -> bgp-mp:restart-timer // bgp-mp:restart-timer's original type is decimal64. // Time interval in seconds after which the BGP session // is re-established after being torn down due to exceeding // the max-prefix limit. RestartTimer float64 `mapstructure:"restart-timer" json:"restart-timer,omitempty"` } // struct for container bgp-mp:config. // Configuration parameters relating to the prefix // limit for the AFI-SAFI. type PrefixLimitConfig struct { // original -> bgp-mp:max-prefixes // Maximum number of prefixes that will be accepted // from the neighbour. MaxPrefixes uint32 `mapstructure:"max-prefixes" json:"max-prefixes,omitempty"` // original -> bgp-mp:shutdown-threshold-pct // Threshold on number of prefixes that can be received // from a neighbour before generation of warning messages // or log entries. Expressed as a percentage of // max-prefixes. ShutdownThresholdPct Percentage `mapstructure:"shutdown-threshold-pct" json:"shutdown-threshold-pct,omitempty"` // original -> bgp-mp:restart-timer // bgp-mp:restart-timer's original type is decimal64. // Time interval in seconds after which the BGP session // is re-established after being torn down due to exceeding // the max-prefix limit. RestartTimer float64 `mapstructure:"restart-timer" json:"restart-timer,omitempty"` } func (lhs *PrefixLimitConfig) Equal(rhs *PrefixLimitConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.MaxPrefixes != rhs.MaxPrefixes { return false } if lhs.ShutdownThresholdPct != rhs.ShutdownThresholdPct { return false } if lhs.RestartTimer != rhs.RestartTimer { return false } return true } // struct for container bgp-mp:prefix-limit. // Configure the maximum number of prefixes that will be // accepted from a peer. type PrefixLimit struct { // original -> bgp-mp:prefix-limit-config // Configuration parameters relating to the prefix // limit for the AFI-SAFI. Config PrefixLimitConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp-mp:prefix-limit-state // State information relating to the prefix-limit for the // AFI-SAFI. State PrefixLimitState `mapstructure:"state" json:"state,omitempty"` } func (lhs *PrefixLimit) Equal(rhs *PrefixLimit) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp-mp:ipv4-unicast. // IPv4 unicast configuration options. type Ipv4Unicast struct { // original -> bgp-mp:prefix-limit // Configure the maximum number of prefixes that will be // accepted from a peer. PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` // original -> bgp-mp:ipv4-unicast-config // Configuration parameters for common IPv4 and IPv6 unicast // AFI-SAFI options. Config Ipv4UnicastConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp-mp:ipv4-unicast-state // State information for common IPv4 and IPv6 unicast // parameters. State Ipv4UnicastState `mapstructure:"state" json:"state,omitempty"` } func (lhs *Ipv4Unicast) Equal(rhs *Ipv4Unicast) bool { if lhs == nil || rhs == nil { return false } if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container rpol:state. // Operational state for routing policy. type ApplyPolicyState struct { // original -> rpol:import-policy // list of policy names in sequence to be applied on // receiving a routing update in the current context, e.g., // for the current peer group, neighbor, address family, // etc. ImportPolicyList []string `mapstructure:"import-policy-list" json:"import-policy-list,omitempty"` // original -> rpol:default-import-policy // explicitly set a default policy if no policy definition // in the import policy chain is satisfied. DefaultImportPolicy DefaultPolicyType `mapstructure:"default-import-policy" json:"default-import-policy,omitempty"` // original -> rpol:export-policy // list of policy names in sequence to be applied on // sending a routing update in the current context, e.g., // for the current peer group, neighbor, address family, // etc. ExportPolicyList []string `mapstructure:"export-policy-list" json:"export-policy-list,omitempty"` // original -> rpol:default-export-policy // explicitly set a default policy if no policy definition // in the export policy chain is satisfied. DefaultExportPolicy DefaultPolicyType `mapstructure:"default-export-policy" json:"default-export-policy,omitempty"` // original -> gobgp:in-policy // list of policy names in sequence to be applied on // sending a routing update in the current context, e.g., // for the current other route server clients. InPolicyList []string `mapstructure:"in-policy-list" json:"in-policy-list,omitempty"` // original -> gobgp:default-in-policy // explicitly set a default policy if no policy definition // in the in-policy chain is satisfied. DefaultInPolicy DefaultPolicyType `mapstructure:"default-in-policy" json:"default-in-policy,omitempty"` } // struct for container rpol:config. // Policy configuration data. type ApplyPolicyConfig struct { // original -> rpol:import-policy // list of policy names in sequence to be applied on // receiving a routing update in the current context, e.g., // for the current peer group, neighbor, address family, // etc. ImportPolicyList []string `mapstructure:"import-policy-list" json:"import-policy-list,omitempty"` // original -> rpol:default-import-policy // explicitly set a default policy if no policy definition // in the import policy chain is satisfied. DefaultImportPolicy DefaultPolicyType `mapstructure:"default-import-policy" json:"default-import-policy,omitempty"` // original -> rpol:export-policy // list of policy names in sequence to be applied on // sending a routing update in the current context, e.g., // for the current peer group, neighbor, address family, // etc. ExportPolicyList []string `mapstructure:"export-policy-list" json:"export-policy-list,omitempty"` // original -> rpol:default-export-policy // explicitly set a default policy if no policy definition // in the export policy chain is satisfied. DefaultExportPolicy DefaultPolicyType `mapstructure:"default-export-policy" json:"default-export-policy,omitempty"` // original -> gobgp:in-policy // list of policy names in sequence to be applied on // sending a routing update in the current context, e.g., // for the current other route server clients. InPolicyList []string `mapstructure:"in-policy-list" json:"in-policy-list,omitempty"` // original -> gobgp:default-in-policy // explicitly set a default policy if no policy definition // in the in-policy chain is satisfied. DefaultInPolicy DefaultPolicyType `mapstructure:"default-in-policy" json:"default-in-policy,omitempty"` } func (lhs *ApplyPolicyConfig) Equal(rhs *ApplyPolicyConfig) bool { if lhs == nil || rhs == nil { return false } if len(lhs.ImportPolicyList) != len(rhs.ImportPolicyList) { return false } for idx, l := range lhs.ImportPolicyList { if l != rhs.ImportPolicyList[idx] { return false } } if lhs.DefaultImportPolicy != rhs.DefaultImportPolicy { return false } if len(lhs.ExportPolicyList) != len(rhs.ExportPolicyList) { return false } for idx, l := range lhs.ExportPolicyList { if l != rhs.ExportPolicyList[idx] { return false } } if lhs.DefaultExportPolicy != rhs.DefaultExportPolicy { return false } if len(lhs.InPolicyList) != len(rhs.InPolicyList) { return false } for idx, l := range lhs.InPolicyList { if l != rhs.InPolicyList[idx] { return false } } if lhs.DefaultInPolicy != rhs.DefaultInPolicy { return false } return true } // struct for container rpol:apply-policy. // Anchor point for routing policies in the model. // Import and export policies are with respect to the local // routing table, i.e., export (send) and import (receive), // depending on the context. type ApplyPolicy struct { // original -> rpol:apply-policy-config // Policy configuration data. Config ApplyPolicyConfig `mapstructure:"config" json:"config,omitempty"` // original -> rpol:apply-policy-state // Operational state for routing policy. State ApplyPolicyState `mapstructure:"state" json:"state,omitempty"` } func (lhs *ApplyPolicy) Equal(rhs *ApplyPolicy) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp-mp:state. // State information relating to the AFI-SAFI. type AfiSafiState struct { // original -> bgp-mp:afi-safi-name // AFI,SAFI. AfiSafiName AfiSafiType `mapstructure:"afi-safi-name" json:"afi-safi-name,omitempty"` // original -> bgp-mp:enabled // bgp-mp:enabled's original type is boolean. // This leaf indicates whether the IPv4 Unicast AFI,SAFI is // enabled for the neighbour or group. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> bgp-op:total-paths // Total number of BGP paths within the context. TotalPaths uint32 `mapstructure:"total-paths" json:"total-paths,omitempty"` // original -> bgp-op:total-prefixes // . TotalPrefixes uint32 `mapstructure:"total-prefixes" json:"total-prefixes,omitempty"` // original -> gobgp:family // gobgp:family's original type is route-family. // Address family value of AFI-SAFI pair translated from afi-safi-name. Family bgp.RouteFamily `mapstructure:"family" json:"family,omitempty"` } // struct for container bgp-mp:config. // Configuration parameters for the AFI-SAFI. type AfiSafiConfig struct { // original -> bgp-mp:afi-safi-name // AFI,SAFI. AfiSafiName AfiSafiType `mapstructure:"afi-safi-name" json:"afi-safi-name,omitempty"` // original -> bgp-mp:enabled // bgp-mp:enabled's original type is boolean. // This leaf indicates whether the IPv4 Unicast AFI,SAFI is // enabled for the neighbour or group. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` } func (lhs *AfiSafiConfig) Equal(rhs *AfiSafiConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.AfiSafiName != rhs.AfiSafiName { return false } if lhs.Enabled != rhs.Enabled { return false } return true } // struct for container bgp-mp:state. // State information for BGP graceful-restart. type MpGracefulRestartState struct { // original -> bgp-mp:enabled // bgp-mp:enabled's original type is boolean. // This leaf indicates whether graceful-restart is enabled for // this AFI-SAFI. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> bgp-op:received // bgp-op:received's original type is boolean. // This leaf indicates whether the neighbor advertised the // ability to support graceful-restart for this AFI-SAFI. Received bool `mapstructure:"received" json:"received,omitempty"` // original -> bgp-op:advertised // bgp-op:advertised's original type is boolean. // This leaf indicates whether the ability to support // graceful-restart has been advertised to the peer. Advertised bool `mapstructure:"advertised" json:"advertised,omitempty"` // original -> gobgp:end-of-rib-received // gobgp:end-of-rib-received's original type is boolean. EndOfRibReceived bool `mapstructure:"end-of-rib-received" json:"end-of-rib-received,omitempty"` // original -> gobgp:end-of-rib-sent // gobgp:end-of-rib-sent's original type is boolean. EndOfRibSent bool `mapstructure:"end-of-rib-sent" json:"end-of-rib-sent,omitempty"` } // struct for container bgp-mp:config. // Configuration options for BGP graceful-restart. type MpGracefulRestartConfig struct { // original -> bgp-mp:enabled // bgp-mp:enabled's original type is boolean. // This leaf indicates whether graceful-restart is enabled for // this AFI-SAFI. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` } func (lhs *MpGracefulRestartConfig) Equal(rhs *MpGracefulRestartConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Enabled != rhs.Enabled { return false } return true } // struct for container bgp-mp:graceful-restart. // Parameters relating to BGP graceful-restart. type MpGracefulRestart struct { // original -> bgp-mp:mp-graceful-restart-config // Configuration options for BGP graceful-restart. Config MpGracefulRestartConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp-mp:mp-graceful-restart-state // State information for BGP graceful-restart. State MpGracefulRestartState `mapstructure:"state" json:"state,omitempty"` } func (lhs *MpGracefulRestart) Equal(rhs *MpGracefulRestart) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp-mp:afi-safi. // AFI,SAFI configuration available for the // neighbour or group. type AfiSafi struct { // original -> bgp-mp:afi-safi-name // original -> bgp-mp:mp-graceful-restart // Parameters relating to BGP graceful-restart. MpGracefulRestart MpGracefulRestart `mapstructure:"mp-graceful-restart" json:"mp-graceful-restart,omitempty"` // original -> bgp-mp:afi-safi-config // Configuration parameters for the AFI-SAFI. Config AfiSafiConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp-mp:afi-safi-state // State information relating to the AFI-SAFI. State AfiSafiState `mapstructure:"state" json:"state,omitempty"` // original -> rpol:apply-policy // Anchor point for routing policies in the model. // Import and export policies are with respect to the local // routing table, i.e., export (send) and import (receive), // depending on the context. ApplyPolicy ApplyPolicy `mapstructure:"apply-policy" json:"apply-policy,omitempty"` // original -> bgp-mp:ipv4-unicast // IPv4 unicast configuration options. Ipv4Unicast Ipv4Unicast `mapstructure:"ipv4-unicast" json:"ipv4-unicast,omitempty"` // original -> bgp-mp:ipv6-unicast // IPv6 unicast configuration options. Ipv6Unicast Ipv6Unicast `mapstructure:"ipv6-unicast" json:"ipv6-unicast,omitempty"` // original -> bgp-mp:ipv4-labelled-unicast // IPv4 Labelled Unicast configuration options. Ipv4LabelledUnicast Ipv4LabelledUnicast `mapstructure:"ipv4-labelled-unicast" json:"ipv4-labelled-unicast,omitempty"` // original -> bgp-mp:ipv6-labelled-unicast // IPv6 Labelled Unicast configuration options. Ipv6LabelledUnicast Ipv6LabelledUnicast `mapstructure:"ipv6-labelled-unicast" json:"ipv6-labelled-unicast,omitempty"` // original -> bgp-mp:l3vpn-ipv4-unicast // Unicast IPv4 L3VPN configuration options. L3vpnIpv4Unicast L3vpnIpv4Unicast `mapstructure:"l3vpn-ipv4-unicast" json:"l3vpn-ipv4-unicast,omitempty"` // original -> bgp-mp:l3vpn-ipv6-unicast // Unicast IPv6 L3VPN configuration options. L3vpnIpv6Unicast L3vpnIpv6Unicast `mapstructure:"l3vpn-ipv6-unicast" json:"l3vpn-ipv6-unicast,omitempty"` // original -> bgp-mp:l3vpn-ipv4-multicast // Multicast IPv4 L3VPN configuration options. L3vpnIpv4Multicast L3vpnIpv4Multicast `mapstructure:"l3vpn-ipv4-multicast" json:"l3vpn-ipv4-multicast,omitempty"` // original -> bgp-mp:l3vpn-ipv6-multicast // Multicast IPv6 L3VPN configuration options. L3vpnIpv6Multicast L3vpnIpv6Multicast `mapstructure:"l3vpn-ipv6-multicast" json:"l3vpn-ipv6-multicast,omitempty"` // original -> bgp-mp:l2vpn-vpls // BGP-signalled VPLS configuration options. L2vpnVpls L2vpnVpls `mapstructure:"l2vpn-vpls" json:"l2vpn-vpls,omitempty"` // original -> bgp-mp:l2vpn-evpn // BGP EVPN configuration options. L2vpnEvpn L2vpnEvpn `mapstructure:"l2vpn-evpn" json:"l2vpn-evpn,omitempty"` // original -> bgp-mp:route-selection-options // Parameters relating to options for route selection. RouteSelectionOptions RouteSelectionOptions `mapstructure:"route-selection-options" json:"route-selection-options,omitempty"` // original -> bgp-mp:use-multiple-paths // Parameters related to the use of multiple paths for the // same NLRI. UseMultiplePaths UseMultiplePaths `mapstructure:"use-multiple-paths" json:"use-multiple-paths,omitempty"` // original -> bgp-mp:prefix-limit // Configure the maximum number of prefixes that will be // accepted from a peer. PrefixLimit PrefixLimit `mapstructure:"prefix-limit" json:"prefix-limit,omitempty"` // original -> gobgp:route-target-membership RouteTargetMembership RouteTargetMembership `mapstructure:"route-target-membership" json:"route-target-membership,omitempty"` // original -> gobgp:long-lived-graceful-restart LongLivedGracefulRestart LongLivedGracefulRestart `mapstructure:"long-lived-graceful-restart" json:"long-lived-graceful-restart,omitempty"` // original -> gobgp:add-paths // add-paths configuration options related to a particular AFI-SAFI. AddPaths AddPaths `mapstructure:"add-paths" json:"add-paths,omitempty"` } func (lhs *AfiSafi) Equal(rhs *AfiSafi) bool { if lhs == nil || rhs == nil { return false } if !lhs.MpGracefulRestart.Equal(&(rhs.MpGracefulRestart)) { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } if !lhs.ApplyPolicy.Equal(&(rhs.ApplyPolicy)) { return false } if !lhs.Ipv4Unicast.Equal(&(rhs.Ipv4Unicast)) { return false } if !lhs.Ipv6Unicast.Equal(&(rhs.Ipv6Unicast)) { return false } if !lhs.Ipv4LabelledUnicast.Equal(&(rhs.Ipv4LabelledUnicast)) { return false } if !lhs.Ipv6LabelledUnicast.Equal(&(rhs.Ipv6LabelledUnicast)) { return false } if !lhs.L3vpnIpv4Unicast.Equal(&(rhs.L3vpnIpv4Unicast)) { return false } if !lhs.L3vpnIpv6Unicast.Equal(&(rhs.L3vpnIpv6Unicast)) { return false } if !lhs.L3vpnIpv4Multicast.Equal(&(rhs.L3vpnIpv4Multicast)) { return false } if !lhs.L3vpnIpv6Multicast.Equal(&(rhs.L3vpnIpv6Multicast)) { return false } if !lhs.L2vpnVpls.Equal(&(rhs.L2vpnVpls)) { return false } if !lhs.L2vpnEvpn.Equal(&(rhs.L2vpnEvpn)) { return false } if !lhs.RouteSelectionOptions.Equal(&(rhs.RouteSelectionOptions)) { return false } if !lhs.UseMultiplePaths.Equal(&(rhs.UseMultiplePaths)) { return false } if !lhs.PrefixLimit.Equal(&(rhs.PrefixLimit)) { return false } if !lhs.RouteTargetMembership.Equal(&(rhs.RouteTargetMembership)) { return false } if !lhs.LongLivedGracefulRestart.Equal(&(rhs.LongLivedGracefulRestart)) { return false } if !lhs.AddPaths.Equal(&(rhs.AddPaths)) { return false } return true } // struct for container bgp:state. // State information associated with graceful-restart. type GracefulRestartState struct { // original -> bgp:enabled // bgp:enabled's original type is boolean. // Enable or disable the graceful-restart capability. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> bgp:restart-time // Estimated time (in seconds) for the local BGP speaker to // restart a session. This value is advertise in the graceful // restart BGP capability. This is a 12-bit value, referred to // as Restart Time in RFC4724. Per RFC4724, the suggested // default value is <= the hold-time value. RestartTime uint16 `mapstructure:"restart-time" json:"restart-time,omitempty"` // original -> bgp:stale-routes-time // bgp:stale-routes-time's original type is decimal64. // An upper-bound on the time thate stale routes will be // retained by a router after a session is restarted. If an // End-of-RIB (EOR) marker is received prior to this timer // expiring stale-routes will be flushed upon its receipt - if // no EOR is received, then when this timer expires stale paths // will be purged. This timer is referred to as the // Selection_Deferral_Timer in RFC4724. StaleRoutesTime float64 `mapstructure:"stale-routes-time" json:"stale-routes-time,omitempty"` // original -> bgp:helper-only // bgp:helper-only's original type is boolean. // Enable graceful-restart in helper mode only. When this // leaf is set, the local system does not retain forwarding // its own state during a restart, but supports procedures // for the receiving speaker, as defined in RFC4724. HelperOnly bool `mapstructure:"helper-only" json:"helper-only,omitempty"` // original -> bgp-op:peer-restart-time // The period of time (advertised by the peer) that // the peer expects a restart of a BGP session to // take. PeerRestartTime uint16 `mapstructure:"peer-restart-time" json:"peer-restart-time,omitempty"` // original -> bgp-op:peer-restarting // bgp-op:peer-restarting's original type is boolean. // This flag indicates whether the remote neighbor is currently // in the process of restarting, and hence received routes are // currently stale. PeerRestarting bool `mapstructure:"peer-restarting" json:"peer-restarting,omitempty"` // original -> bgp-op:local-restarting // bgp-op:local-restarting's original type is boolean. // This flag indicates whether the local neighbor is currently // restarting. The flag is unset after all NLRI have been // advertised to the peer, and the End-of-RIB (EOR) marker has // been unset. LocalRestarting bool `mapstructure:"local-restarting" json:"local-restarting,omitempty"` // original -> bgp-op:mode // Ths leaf indicates the mode of operation of BGP graceful // restart with the peer. Mode Mode `mapstructure:"mode" json:"mode,omitempty"` // original -> gobgp:deferral-time DeferralTime uint16 `mapstructure:"deferral-time" json:"deferral-time,omitempty"` // original -> gobgp:notification-enabled // gobgp:notification-enabled's original type is boolean. NotificationEnabled bool `mapstructure:"notification-enabled" json:"notification-enabled,omitempty"` // original -> gobgp:long-lived-enabled // gobgp:long-lived-enabled's original type is boolean. LongLivedEnabled bool `mapstructure:"long-lived-enabled" json:"long-lived-enabled,omitempty"` } // struct for container bgp:config. // Configuration parameters relating to graceful-restart. type GracefulRestartConfig struct { // original -> bgp:enabled // bgp:enabled's original type is boolean. // Enable or disable the graceful-restart capability. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> bgp:restart-time // Estimated time (in seconds) for the local BGP speaker to // restart a session. This value is advertise in the graceful // restart BGP capability. This is a 12-bit value, referred to // as Restart Time in RFC4724. Per RFC4724, the suggested // default value is <= the hold-time value. RestartTime uint16 `mapstructure:"restart-time" json:"restart-time,omitempty"` // original -> bgp:stale-routes-time // bgp:stale-routes-time's original type is decimal64. // An upper-bound on the time thate stale routes will be // retained by a router after a session is restarted. If an // End-of-RIB (EOR) marker is received prior to this timer // expiring stale-routes will be flushed upon its receipt - if // no EOR is received, then when this timer expires stale paths // will be purged. This timer is referred to as the // Selection_Deferral_Timer in RFC4724. StaleRoutesTime float64 `mapstructure:"stale-routes-time" json:"stale-routes-time,omitempty"` // original -> bgp:helper-only // bgp:helper-only's original type is boolean. // Enable graceful-restart in helper mode only. When this // leaf is set, the local system does not retain forwarding // its own state during a restart, but supports procedures // for the receiving speaker, as defined in RFC4724. HelperOnly bool `mapstructure:"helper-only" json:"helper-only,omitempty"` // original -> gobgp:deferral-time DeferralTime uint16 `mapstructure:"deferral-time" json:"deferral-time,omitempty"` // original -> gobgp:notification-enabled // gobgp:notification-enabled's original type is boolean. NotificationEnabled bool `mapstructure:"notification-enabled" json:"notification-enabled,omitempty"` // original -> gobgp:long-lived-enabled // gobgp:long-lived-enabled's original type is boolean. LongLivedEnabled bool `mapstructure:"long-lived-enabled" json:"long-lived-enabled,omitempty"` } func (lhs *GracefulRestartConfig) Equal(rhs *GracefulRestartConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Enabled != rhs.Enabled { return false } if lhs.RestartTime != rhs.RestartTime { return false } if lhs.StaleRoutesTime != rhs.StaleRoutesTime { return false } if lhs.HelperOnly != rhs.HelperOnly { return false } if lhs.DeferralTime != rhs.DeferralTime { return false } if lhs.NotificationEnabled != rhs.NotificationEnabled { return false } if lhs.LongLivedEnabled != rhs.LongLivedEnabled { return false } return true } // struct for container bgp:graceful-restart. // Parameters relating the graceful restart mechanism for BGP. type GracefulRestart struct { // original -> bgp:graceful-restart-config // Configuration parameters relating to graceful-restart. Config GracefulRestartConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:graceful-restart-state // State information associated with graceful-restart. State GracefulRestartState `mapstructure:"state" json:"state,omitempty"` } func (lhs *GracefulRestart) Equal(rhs *GracefulRestart) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp-mp:state. // State information relating to iBGP multipath. type IbgpState struct { // original -> bgp-mp:maximum-paths // Maximum number of parallel paths to consider when using // iBGP multipath. The default is to use a single path. MaximumPaths uint32 `mapstructure:"maximum-paths" json:"maximum-paths,omitempty"` } // struct for container bgp-mp:config. // Configuration parameters relating to iBGP multipath. type IbgpConfig struct { // original -> bgp-mp:maximum-paths // Maximum number of parallel paths to consider when using // iBGP multipath. The default is to use a single path. MaximumPaths uint32 `mapstructure:"maximum-paths" json:"maximum-paths,omitempty"` } func (lhs *IbgpConfig) Equal(rhs *IbgpConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.MaximumPaths != rhs.MaximumPaths { return false } return true } // struct for container bgp-mp:ibgp. // Multipath parameters for iBGP. type Ibgp struct { // original -> bgp-mp:ibgp-config // Configuration parameters relating to iBGP multipath. Config IbgpConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp-mp:ibgp-state // State information relating to iBGP multipath. State IbgpState `mapstructure:"state" json:"state,omitempty"` } func (lhs *Ibgp) Equal(rhs *Ibgp) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp-mp:state. // State information relating to eBGP multipath. type EbgpState struct { // original -> bgp-mp:allow-multiple-as // bgp-mp:allow-multiple-as's original type is boolean. // Allow multipath to use paths from different neighbouring // ASes. The default is to only consider multiple paths from // the same neighbouring AS. AllowMultipleAs bool `mapstructure:"allow-multiple-as" json:"allow-multiple-as,omitempty"` // original -> bgp-mp:maximum-paths // Maximum number of parallel paths to consider when using // BGP multipath. The default is use a single path. MaximumPaths uint32 `mapstructure:"maximum-paths" json:"maximum-paths,omitempty"` } // struct for container bgp-mp:config. // Configuration parameters relating to eBGP multipath. type EbgpConfig struct { // original -> bgp-mp:allow-multiple-as // bgp-mp:allow-multiple-as's original type is boolean. // Allow multipath to use paths from different neighbouring // ASes. The default is to only consider multiple paths from // the same neighbouring AS. AllowMultipleAs bool `mapstructure:"allow-multiple-as" json:"allow-multiple-as,omitempty"` // original -> bgp-mp:maximum-paths // Maximum number of parallel paths to consider when using // BGP multipath. The default is use a single path. MaximumPaths uint32 `mapstructure:"maximum-paths" json:"maximum-paths,omitempty"` } func (lhs *EbgpConfig) Equal(rhs *EbgpConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.AllowMultipleAs != rhs.AllowMultipleAs { return false } if lhs.MaximumPaths != rhs.MaximumPaths { return false } return true } // struct for container bgp-mp:ebgp. // Multipath parameters for eBGP. type Ebgp struct { // original -> bgp-mp:ebgp-config // Configuration parameters relating to eBGP multipath. Config EbgpConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp-mp:ebgp-state // State information relating to eBGP multipath. State EbgpState `mapstructure:"state" json:"state,omitempty"` } func (lhs *Ebgp) Equal(rhs *Ebgp) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp-mp:state. // State parameters relating to multipath. type UseMultiplePathsState struct { // original -> bgp-mp:enabled // bgp-mp:enabled's original type is boolean. // Whether the use of multiple paths for the same NLRI is // enabled for the neighbor. This value is overridden by // any more specific configuration value. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` } // struct for container bgp-mp:config. // Configuration parameters relating to multipath. type UseMultiplePathsConfig struct { // original -> bgp-mp:enabled // bgp-mp:enabled's original type is boolean. // Whether the use of multiple paths for the same NLRI is // enabled for the neighbor. This value is overridden by // any more specific configuration value. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` } func (lhs *UseMultiplePathsConfig) Equal(rhs *UseMultiplePathsConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Enabled != rhs.Enabled { return false } return true } // struct for container bgp-mp:use-multiple-paths. // Parameters related to the use of multiple paths for the // same NLRI. type UseMultiplePaths struct { // original -> bgp-mp:use-multiple-paths-config // Configuration parameters relating to multipath. Config UseMultiplePathsConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp-mp:use-multiple-paths-state // State parameters relating to multipath. State UseMultiplePathsState `mapstructure:"state" json:"state,omitempty"` // original -> bgp-mp:ebgp // Multipath parameters for eBGP. Ebgp Ebgp `mapstructure:"ebgp" json:"ebgp,omitempty"` // original -> bgp-mp:ibgp // Multipath parameters for iBGP. Ibgp Ibgp `mapstructure:"ibgp" json:"ibgp,omitempty"` } func (lhs *UseMultiplePaths) Equal(rhs *UseMultiplePaths) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } if !lhs.Ebgp.Equal(&(rhs.Ebgp)) { return false } if !lhs.Ibgp.Equal(&(rhs.Ibgp)) { return false } return true } // struct for container bgp:state. // State information relating to the BGP confederations. type ConfederationState struct { // original -> bgp:enabled // bgp:enabled's original type is boolean. // When this leaf is set to true it indicates that // the local-AS is part of a BGP confederation. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> bgp:identifier // bgp:identifier's original type is inet:as-number. // Confederation identifier for the autonomous system. Identifier uint32 `mapstructure:"identifier" json:"identifier,omitempty"` // original -> bgp:member-as // original type is list of inet:as-number // Remote autonomous systems that are to be treated // as part of the local confederation. MemberAsList []uint32 `mapstructure:"member-as-list" json:"member-as-list,omitempty"` } // struct for container bgp:config. // Configuration parameters relating to BGP confederations. type ConfederationConfig struct { // original -> bgp:enabled // bgp:enabled's original type is boolean. // When this leaf is set to true it indicates that // the local-AS is part of a BGP confederation. Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"` // original -> bgp:identifier // bgp:identifier's original type is inet:as-number. // Confederation identifier for the autonomous system. Identifier uint32 `mapstructure:"identifier" json:"identifier,omitempty"` // original -> bgp:member-as // original type is list of inet:as-number // Remote autonomous systems that are to be treated // as part of the local confederation. MemberAsList []uint32 `mapstructure:"member-as-list" json:"member-as-list,omitempty"` } func (lhs *ConfederationConfig) Equal(rhs *ConfederationConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.Enabled != rhs.Enabled { return false } if lhs.Identifier != rhs.Identifier { return false } if len(lhs.MemberAsList) != len(rhs.MemberAsList) { return false } for idx, l := range lhs.MemberAsList { if l != rhs.MemberAsList[idx] { return false } } return true } // struct for container bgp:confederation. // Parameters indicating whether the local system acts as part // of a BGP confederation. type Confederation struct { // original -> bgp:confederation-config // Configuration parameters relating to BGP confederations. Config ConfederationConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:confederation-state // State information relating to the BGP confederations. State ConfederationState `mapstructure:"state" json:"state,omitempty"` } func (lhs *Confederation) Equal(rhs *Confederation) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp:state. // State information relating to the default route distance. type DefaultRouteDistanceState struct { // original -> bgp:external-route-distance // Administrative distance for routes learned from external // BGP (eBGP). ExternalRouteDistance uint8 `mapstructure:"external-route-distance" json:"external-route-distance,omitempty"` // original -> bgp:internal-route-distance // Administrative distance for routes learned from internal // BGP (iBGP). InternalRouteDistance uint8 `mapstructure:"internal-route-distance" json:"internal-route-distance,omitempty"` } // struct for container bgp:config. // Configuration parameters relating to the default route // distance. type DefaultRouteDistanceConfig struct { // original -> bgp:external-route-distance // Administrative distance for routes learned from external // BGP (eBGP). ExternalRouteDistance uint8 `mapstructure:"external-route-distance" json:"external-route-distance,omitempty"` // original -> bgp:internal-route-distance // Administrative distance for routes learned from internal // BGP (iBGP). InternalRouteDistance uint8 `mapstructure:"internal-route-distance" json:"internal-route-distance,omitempty"` } func (lhs *DefaultRouteDistanceConfig) Equal(rhs *DefaultRouteDistanceConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.ExternalRouteDistance != rhs.ExternalRouteDistance { return false } if lhs.InternalRouteDistance != rhs.InternalRouteDistance { return false } return true } // struct for container bgp:default-route-distance. // Administrative distance (or preference) assigned to // routes received from different sources // (external, internal, and local). type DefaultRouteDistance struct { // original -> bgp:default-route-distance-config // Configuration parameters relating to the default route // distance. Config DefaultRouteDistanceConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:default-route-distance-state // State information relating to the default route distance. State DefaultRouteDistanceState `mapstructure:"state" json:"state,omitempty"` } func (lhs *DefaultRouteDistance) Equal(rhs *DefaultRouteDistance) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp-mp:state. // State information for the route selection options. type RouteSelectionOptionsState struct { // original -> bgp-mp:always-compare-med // bgp-mp:always-compare-med's original type is boolean. // Compare multi-exit discriminator (MED) value from // different ASes when selecting the best route. The // default behavior is to only compare MEDs for paths // received from the same AS. AlwaysCompareMed bool `mapstructure:"always-compare-med" json:"always-compare-med,omitempty"` // original -> bgp-mp:ignore-as-path-length // bgp-mp:ignore-as-path-length's original type is boolean. // Ignore the AS path length when selecting the best path. // The default is to use the AS path length and prefer paths // with shorter length. IgnoreAsPathLength bool `mapstructure:"ignore-as-path-length" json:"ignore-as-path-length,omitempty"` // original -> bgp-mp:external-compare-router-id // bgp-mp:external-compare-router-id's original type is boolean. // When comparing similar routes received from external // BGP peers, use the router-id as a criterion to select // the active path. ExternalCompareRouterId bool `mapstructure:"external-compare-router-id" json:"external-compare-router-id,omitempty"` // original -> bgp-mp:advertise-inactive-routes // bgp-mp:advertise-inactive-routes's original type is boolean. // Advertise inactive routes to external peers. The // default is to only advertise active routes. AdvertiseInactiveRoutes bool `mapstructure:"advertise-inactive-routes" json:"advertise-inactive-routes,omitempty"` // original -> bgp-mp:enable-aigp // bgp-mp:enable-aigp's original type is boolean. // Flag to enable sending / receiving accumulated IGP // attribute in routing updates. EnableAigp bool `mapstructure:"enable-aigp" json:"enable-aigp,omitempty"` // original -> bgp-mp:ignore-next-hop-igp-metric // bgp-mp:ignore-next-hop-igp-metric's original type is boolean. // Ignore the IGP metric to the next-hop when calculating // BGP best-path. The default is to select the route for // which the metric to the next-hop is lowest. IgnoreNextHopIgpMetric bool `mapstructure:"ignore-next-hop-igp-metric" json:"ignore-next-hop-igp-metric,omitempty"` // original -> gobgp:disable-best-path-selection // gobgp:disable-best-path-selection's original type is boolean. // Disables best path selection process. DisableBestPathSelection bool `mapstructure:"disable-best-path-selection" json:"disable-best-path-selection,omitempty"` } // struct for container bgp-mp:config. // Configuration parameters relating to route selection // options. type RouteSelectionOptionsConfig struct { // original -> bgp-mp:always-compare-med // bgp-mp:always-compare-med's original type is boolean. // Compare multi-exit discriminator (MED) value from // different ASes when selecting the best route. The // default behavior is to only compare MEDs for paths // received from the same AS. AlwaysCompareMed bool `mapstructure:"always-compare-med" json:"always-compare-med,omitempty"` // original -> bgp-mp:ignore-as-path-length // bgp-mp:ignore-as-path-length's original type is boolean. // Ignore the AS path length when selecting the best path. // The default is to use the AS path length and prefer paths // with shorter length. IgnoreAsPathLength bool `mapstructure:"ignore-as-path-length" json:"ignore-as-path-length,omitempty"` // original -> bgp-mp:external-compare-router-id // bgp-mp:external-compare-router-id's original type is boolean. // When comparing similar routes received from external // BGP peers, use the router-id as a criterion to select // the active path. ExternalCompareRouterId bool `mapstructure:"external-compare-router-id" json:"external-compare-router-id,omitempty"` // original -> bgp-mp:advertise-inactive-routes // bgp-mp:advertise-inactive-routes's original type is boolean. // Advertise inactive routes to external peers. The // default is to only advertise active routes. AdvertiseInactiveRoutes bool `mapstructure:"advertise-inactive-routes" json:"advertise-inactive-routes,omitempty"` // original -> bgp-mp:enable-aigp // bgp-mp:enable-aigp's original type is boolean. // Flag to enable sending / receiving accumulated IGP // attribute in routing updates. EnableAigp bool `mapstructure:"enable-aigp" json:"enable-aigp,omitempty"` // original -> bgp-mp:ignore-next-hop-igp-metric // bgp-mp:ignore-next-hop-igp-metric's original type is boolean. // Ignore the IGP metric to the next-hop when calculating // BGP best-path. The default is to select the route for // which the metric to the next-hop is lowest. IgnoreNextHopIgpMetric bool `mapstructure:"ignore-next-hop-igp-metric" json:"ignore-next-hop-igp-metric,omitempty"` // original -> gobgp:disable-best-path-selection // gobgp:disable-best-path-selection's original type is boolean. // Disables best path selection process. DisableBestPathSelection bool `mapstructure:"disable-best-path-selection" json:"disable-best-path-selection,omitempty"` } func (lhs *RouteSelectionOptionsConfig) Equal(rhs *RouteSelectionOptionsConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.AlwaysCompareMed != rhs.AlwaysCompareMed { return false } if lhs.IgnoreAsPathLength != rhs.IgnoreAsPathLength { return false } if lhs.ExternalCompareRouterId != rhs.ExternalCompareRouterId { return false } if lhs.AdvertiseInactiveRoutes != rhs.AdvertiseInactiveRoutes { return false } if lhs.EnableAigp != rhs.EnableAigp { return false } if lhs.IgnoreNextHopIgpMetric != rhs.IgnoreNextHopIgpMetric { return false } if lhs.DisableBestPathSelection != rhs.DisableBestPathSelection { return false } return true } // struct for container bgp-mp:route-selection-options. // Parameters relating to options for route selection. type RouteSelectionOptions struct { // original -> bgp-mp:route-selection-options-config // Configuration parameters relating to route selection // options. Config RouteSelectionOptionsConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp-mp:route-selection-options-state // State information for the route selection options. State RouteSelectionOptionsState `mapstructure:"state" json:"state,omitempty"` } func (lhs *RouteSelectionOptions) Equal(rhs *RouteSelectionOptions) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } return true } // struct for container bgp:state. // State information relating to the global BGP router. type GlobalState struct { // original -> bgp:as // bgp:as's original type is inet:as-number. // Local autonomous system number of the router. Uses // the 32-bit as-number type from the model in RFC 6991. As uint32 `mapstructure:"as" json:"as,omitempty"` // original -> bgp:router-id // bgp:router-id's original type is inet:ipv4-address. // Router id of the router, expressed as an // 32-bit value, IPv4 address. RouterId string `mapstructure:"router-id" json:"router-id,omitempty"` // original -> bgp-op:total-paths // Total number of BGP paths within the context. TotalPaths uint32 `mapstructure:"total-paths" json:"total-paths,omitempty"` // original -> bgp-op:total-prefixes // . TotalPrefixes uint32 `mapstructure:"total-prefixes" json:"total-prefixes,omitempty"` // original -> gobgp:port Port int32 `mapstructure:"port" json:"port,omitempty"` // original -> gobgp:local-address LocalAddressList []string `mapstructure:"local-address-list" json:"local-address-list,omitempty"` } // struct for container bgp:config. // Configuration parameters relating to the global BGP router. type GlobalConfig struct { // original -> bgp:as // bgp:as's original type is inet:as-number. // Local autonomous system number of the router. Uses // the 32-bit as-number type from the model in RFC 6991. As uint32 `mapstructure:"as" json:"as,omitempty"` // original -> bgp:router-id // bgp:router-id's original type is inet:ipv4-address. // Router id of the router, expressed as an // 32-bit value, IPv4 address. RouterId string `mapstructure:"router-id" json:"router-id,omitempty"` // original -> gobgp:port Port int32 `mapstructure:"port" json:"port,omitempty"` // original -> gobgp:local-address LocalAddressList []string `mapstructure:"local-address-list" json:"local-address-list,omitempty"` } func (lhs *GlobalConfig) Equal(rhs *GlobalConfig) bool { if lhs == nil || rhs == nil { return false } if lhs.As != rhs.As { return false } if lhs.RouterId != rhs.RouterId { return false } if lhs.Port != rhs.Port { return false } if len(lhs.LocalAddressList) != len(rhs.LocalAddressList) { return false } for idx, l := range lhs.LocalAddressList { if l != rhs.LocalAddressList[idx] { return false } } return true } // struct for container bgp:global. // Global configuration for the BGP router. type Global struct { // original -> bgp:global-config // Configuration parameters relating to the global BGP router. Config GlobalConfig `mapstructure:"config" json:"config,omitempty"` // original -> bgp:global-state // State information relating to the global BGP router. State GlobalState `mapstructure:"state" json:"state,omitempty"` // original -> bgp-mp:route-selection-options // Parameters relating to options for route selection. RouteSelectionOptions RouteSelectionOptions `mapstructure:"route-selection-options" json:"route-selection-options,omitempty"` // original -> bgp:default-route-distance // Administrative distance (or preference) assigned to // routes received from different sources // (external, internal, and local). DefaultRouteDistance DefaultRouteDistance `mapstructure:"default-route-distance" json:"default-route-distance,omitempty"` // original -> bgp:confederation // Parameters indicating whether the local system acts as part // of a BGP confederation. Confederation Confederation `mapstructure:"confederation" json:"confederation,omitempty"` // original -> bgp-mp:use-multiple-paths // Parameters related to the use of multiple paths for the // same NLRI. UseMultiplePaths UseMultiplePaths `mapstructure:"use-multiple-paths" json:"use-multiple-paths,omitempty"` // original -> bgp:graceful-restart // Parameters relating the graceful restart mechanism for BGP. GracefulRestart GracefulRestart `mapstructure:"graceful-restart" json:"graceful-restart,omitempty"` // original -> bgp:afi-safis // Address family specific configuration. AfiSafis []AfiSafi `mapstructure:"afi-safis" json:"afi-safis,omitempty"` // original -> rpol:apply-policy // Anchor point for routing policies in the model. // Import and export policies are with respect to the local // routing table, i.e., export (send) and import (receive), // depending on the context. ApplyPolicy ApplyPolicy `mapstructure:"apply-policy" json:"apply-policy,omitempty"` } func (lhs *Global) Equal(rhs *Global) bool { if lhs == nil || rhs == nil { return false } if !lhs.Config.Equal(&(rhs.Config)) { return false } if !lhs.RouteSelectionOptions.Equal(&(rhs.RouteSelectionOptions)) { return false } if !lhs.DefaultRouteDistance.Equal(&(rhs.DefaultRouteDistance)) { return false } if !lhs.Confederation.Equal(&(rhs.Confederation)) { return false } if !lhs.UseMultiplePaths.Equal(&(rhs.UseMultiplePaths)) { return false } if !lhs.GracefulRestart.Equal(&(rhs.GracefulRestart)) { return false } if len(lhs.AfiSafis) != len(rhs.AfiSafis) { return false } { lmap := make(map[string]*AfiSafi) for i, l := range lhs.AfiSafis { lmap[mapkey(i, string(l.Config.AfiSafiName))] = &lhs.AfiSafis[i] } for i, r := range rhs.AfiSafis { if l, y := lmap[mapkey(i, string(r.Config.AfiSafiName))]; !y { return false } else if !r.Equal(l) { return false } } } if !lhs.ApplyPolicy.Equal(&(rhs.ApplyPolicy)) { return false } return true } // struct for container bgp:bgp. // Top-level configuration and state for the BGP router. type Bgp struct { // original -> bgp:global // Global configuration for the BGP router. Global Global `mapstructure:"global" json:"global,omitempty"` // original -> bgp:neighbors // Configuration for BGP neighbors. Neighbors []Neighbor `mapstructure:"neighbors" json:"neighbors,omitempty"` // original -> bgp:peer-groups // Configuration for BGP peer-groups. PeerGroups []PeerGroup `mapstructure:"peer-groups" json:"peer-groups,omitempty"` // original -> gobgp:rpki-servers RpkiServers []RpkiServer `mapstructure:"rpki-servers" json:"rpki-servers,omitempty"` // original -> gobgp:bmp-servers BmpServers []BmpServer `mapstructure:"bmp-servers" json:"bmp-servers,omitempty"` // original -> gobgp:mrt-dump MrtDump []Mrt `mapstructure:"mrt-dump" json:"mrt-dump,omitempty"` // original -> gobgp:zebra Zebra Zebra `mapstructure:"zebra" json:"zebra,omitempty"` // original -> gobgp:collector Collector Collector `mapstructure:"collector" json:"collector,omitempty"` // original -> gobgp:dynamic-neighbors DynamicNeighbors []DynamicNeighbor `mapstructure:"dynamic-neighbors" json:"dynamic-neighbors,omitempty"` } func (lhs *Bgp) Equal(rhs *Bgp) bool { if lhs == nil || rhs == nil { return false } if !lhs.Global.Equal(&(rhs.Global)) { return false } if len(lhs.Neighbors) != len(rhs.Neighbors) { return false } { lmap := make(map[string]*Neighbor) for i, l := range lhs.Neighbors { lmap[mapkey(i, string(l.Config.NeighborAddress))] = &lhs.Neighbors[i] } for i, r := range rhs.Neighbors { if l, y := lmap[mapkey(i, string(r.Config.NeighborAddress))]; !y { return false } else if !r.Equal(l) { return false } } } if len(lhs.PeerGroups) != len(rhs.PeerGroups) { return false } { lmap := make(map[string]*PeerGroup) for i, l := range lhs.PeerGroups { lmap[mapkey(i, string(l.Config.PeerGroupName))] = &lhs.PeerGroups[i] } for i, r := range rhs.PeerGroups { if l, y := lmap[mapkey(i, string(r.Config.PeerGroupName))]; !y { return false } else if !r.Equal(l) { return false } } } if len(lhs.RpkiServers) != len(rhs.RpkiServers) { return false } { lmap := make(map[string]*RpkiServer) for i, l := range lhs.RpkiServers { lmap[mapkey(i, string(l.Config.Address))] = &lhs.RpkiServers[i] } for i, r := range rhs.RpkiServers { if l, y := lmap[mapkey(i, string(r.Config.Address))]; !y { return false } else if !r.Equal(l) { return false } } } if len(lhs.BmpServers) != len(rhs.BmpServers) { return false } { lmap := make(map[string]*BmpServer) for i, l := range lhs.BmpServers { lmap[mapkey(i, string(l.Config.Address))] = &lhs.BmpServers[i] } for i, r := range rhs.BmpServers { if l, y := lmap[mapkey(i, string(r.Config.Address))]; !y { return false } else if !r.Equal(l) { return false } } } if len(lhs.MrtDump) != len(rhs.MrtDump) { return false } { lmap := make(map[string]*Mrt) for i, l := range lhs.MrtDump { lmap[mapkey(i, string(l.Config.FileName))] = &lhs.MrtDump[i] } for i, r := range rhs.MrtDump { if l, y := lmap[mapkey(i, string(r.Config.FileName))]; !y { return false } else if !r.Equal(l) { return false } } } if !lhs.Zebra.Equal(&(rhs.Zebra)) { return false } if !lhs.Collector.Equal(&(rhs.Collector)) { return false } if len(lhs.DynamicNeighbors) != len(rhs.DynamicNeighbors) { return false } { lmap := make(map[string]*DynamicNeighbor) for i, l := range lhs.DynamicNeighbors { lmap[mapkey(i, string(l.Config.Prefix))] = &lhs.DynamicNeighbors[i] } for i, r := range rhs.DynamicNeighbors { if l, y := lmap[mapkey(i, string(r.Config.Prefix))]; !y { return false } else if !r.Equal(l) { return false } } } return true } // struct for container gobgp:set-large-community-method. type SetLargeCommunityMethod struct { // original -> gobgp:communities CommunitiesList []string `mapstructure:"communities-list" json:"communities-list,omitempty"` } func (lhs *SetLargeCommunityMethod) Equal(rhs *SetLargeCommunityMethod) bool { if lhs == nil || rhs == nil { return false } if len(lhs.CommunitiesList) != len(rhs.CommunitiesList) { return false } for idx, l := range lhs.CommunitiesList { if l != rhs.CommunitiesList[idx] { return false } } return true } // struct for container gobgp:set-large-community. type SetLargeCommunity struct { // original -> gobgp:set-large-community-method SetLargeCommunityMethod SetLargeCommunityMethod `mapstructure:"set-large-community-method" json:"set-large-community-method,omitempty"` // original -> gobgp:options Options BgpSetCommunityOptionType `mapstructure:"options" json:"options,omitempty"` } func (lhs *SetLargeCommunity) Equal(rhs *SetLargeCommunity) bool { if lhs == nil || rhs == nil { return false } if !lhs.SetLargeCommunityMethod.Equal(&(rhs.SetLargeCommunityMethod)) { return false } if lhs.Options != rhs.Options { return false } return true } // struct for container bgp-pol:set-ext-community-method. // Option to set communities using an inline list or // reference to an existing defined set. type SetExtCommunityMethod struct { // original -> bgp-pol:communities // original type is list of union // Set the community values for the update inline with // a list. CommunitiesList []string `mapstructure:"communities-list" json:"communities-list,omitempty"` // original -> bgp-pol:ext-community-set-ref // References a defined extended community set by // name. ExtCommunitySetRef string `mapstructure:"ext-community-set-ref" json:"ext-community-set-ref,omitempty"` } func (lhs *SetExtCommunityMethod) Equal(rhs *SetExtCommunityMethod) bool { if lhs == nil || rhs == nil { return false } if len(lhs.CommunitiesList) != len(rhs.CommunitiesList) { return false } for idx, l := range lhs.CommunitiesList { if l != rhs.CommunitiesList[idx] { return false } } if lhs.ExtCommunitySetRef != rhs.ExtCommunitySetRef { return false } return true } // struct for container bgp-pol:set-ext-community. // Action to set the extended community attributes of the // route, along with options to modify how the community is // modified. type SetExtCommunity struct { // original -> bgp-pol:set-ext-community-method // Option to set communities using an inline list or // reference to an existing defined set. SetExtCommunityMethod SetExtCommunityMethod `mapstructure:"set-ext-community-method" json:"set-ext-community-method,omitempty"` // original -> bgp-pol:options // bgp-pol:options's original type is bgp-set-community-option-type. // options for modifying the extended community // attribute with the specified values. These options // apply to both methods of setting the community // attribute. Options string `mapstructure:"options" json:"options,omitempty"` } func (lhs *SetExtCommunity) Equal(rhs *SetExtCommunity) bool { if lhs == nil || rhs == nil { return false } if !lhs.SetExtCommunityMethod.Equal(&(rhs.SetExtCommunityMethod)) { return false } if lhs.Options != rhs.Options { return false } return true } // struct for container bgp-pol:set-community-method. // Option to set communities using an inline list or // reference to an existing defined set. type SetCommunityMethod struct { // original -> bgp-pol:communities // original type is list of union // Set the community values for the update inline with // a list. CommunitiesList []string `mapstructure:"communities-list" json:"communities-list,omitempty"` // original -> bgp-pol:community-set-ref // References a defined community set by name. CommunitySetRef string `mapstructure:"community-set-ref" json:"community-set-ref,omitempty"` } func (lhs *SetCommunityMethod) Equal(rhs *SetCommunityMethod) bool { if lhs == nil || rhs == nil { return false } if len(lhs.CommunitiesList) != len(rhs.CommunitiesList) { return false } for idx, l := range lhs.CommunitiesList { if l != rhs.CommunitiesList[idx] { return false } } if lhs.CommunitySetRef != rhs.CommunitySetRef { return false } return true } // struct for container bgp-pol:set-community. // action to set the community attributes of the route, along // with options to modify how the community is modified. type SetCommunity struct { // original -> bgp-pol:set-community-method // Option to set communities using an inline list or // reference to an existing defined set. SetCommunityMethod SetCommunityMethod `mapstructure:"set-community-method" json:"set-community-method,omitempty"` // original -> bgp-pol:options // bgp-pol:options's original type is bgp-set-community-option-type. // Options for modifying the community attribute with // the specified values. These options apply to both // methods of setting the community attribute. Options string `mapstructure:"options" json:"options,omitempty"` } func (lhs *SetCommunity) Equal(rhs *SetCommunity) bool { if lhs == nil || rhs == nil { return false } if !lhs.SetCommunityMethod.Equal(&(rhs.SetCommunityMethod)) { return false } if lhs.Options != rhs.Options { return false } return true } // struct for container bgp-pol:set-as-path-prepend. // action to prepend local AS number to the AS-path a // specified number of times. type SetAsPathPrepend struct { // original -> bgp-pol:repeat-n // number of times to prepend the local AS // number. RepeatN uint8 `mapstructure:"repeat-n" json:"repeat-n,omitempty"` // original -> gobgp:as // gobgp:as's original type is union. // autonomous system number or 'last-as' which means // the leftmost as number in the AS-path to be prepended. As string `mapstructure:"as" json:"as,omitempty"` } func (lhs *SetAsPathPrepend) Equal(rhs *SetAsPathPrepend) bool { if lhs == nil || rhs == nil { return false } if lhs.RepeatN != rhs.RepeatN { return false } if lhs.As != rhs.As { return false } return true } // struct for container bgp-pol:bgp-actions. // Definitions for policy action statements that // change BGP-specific attributes of the route. type BgpActions struct { // original -> bgp-pol:set-as-path-prepend // action to prepend local AS number to the AS-path a // specified number of times. SetAsPathPrepend SetAsPathPrepend `mapstructure:"set-as-path-prepend" json:"set-as-path-prepend,omitempty"` // original -> bgp-pol:set-community // action to set the community attributes of the route, along // with options to modify how the community is modified. SetCommunity SetCommunity `mapstructure:"set-community" json:"set-community,omitempty"` // original -> bgp-pol:set-ext-community // Action to set the extended community attributes of the // route, along with options to modify how the community is // modified. SetExtCommunity SetExtCommunity `mapstructure:"set-ext-community" json:"set-ext-community,omitempty"` // original -> bgp-pol:set-route-origin // set the origin attribute to the specified // value. SetRouteOrigin BgpOriginAttrType `mapstructure:"set-route-origin" json:"set-route-origin,omitempty"` // original -> bgp-pol:set-local-pref // set the local pref attribute on the route // update. SetLocalPref uint32 `mapstructure:"set-local-pref" json:"set-local-pref,omitempty"` // original -> bgp-pol:set-next-hop // set the next-hop attribute in the route update. SetNextHop BgpNextHopType `mapstructure:"set-next-hop" json:"set-next-hop,omitempty"` // original -> bgp-pol:set-med // set the med metric attribute in the route // update. SetMed BgpSetMedType `mapstructure:"set-med" json:"set-med,omitempty"` // original -> gobgp:set-large-community SetLargeCommunity SetLargeCommunity `mapstructure:"set-large-community" json:"set-large-community,omitempty"` } func (lhs *BgpActions) Equal(rhs *BgpActions) bool { if lhs == nil || rhs == nil { return false } if !lhs.SetAsPathPrepend.Equal(&(rhs.SetAsPathPrepend)) { return false } if !lhs.SetCommunity.Equal(&(rhs.SetCommunity)) { return false } if !lhs.SetExtCommunity.Equal(&(rhs.SetExtCommunity)) { return false } if lhs.SetRouteOrigin != rhs.SetRouteOrigin { return false } if lhs.SetLocalPref != rhs.SetLocalPref { return false } if lhs.SetNextHop != rhs.SetNextHop { return false } if lhs.SetMed != rhs.SetMed { return false } if !lhs.SetLargeCommunity.Equal(&(rhs.SetLargeCommunity)) { return false } return true } // struct for container rpol:igp-actions. // Actions to set IGP route attributes; these actions // apply to multiple IGPs. type IgpActions struct { // original -> rpol:set-tag // Set the tag value for OSPF or IS-IS routes. SetTag TagType `mapstructure:"set-tag" json:"set-tag,omitempty"` } func (lhs *IgpActions) Equal(rhs *IgpActions) bool { if lhs == nil || rhs == nil { return false } if lhs.SetTag != rhs.SetTag { return false } return true } // struct for container rpol:actions. // Action statements for this policy // statement. type Actions struct { // original -> rpol:route-disposition // Select the final disposition for the route, either // accept or reject. RouteDisposition RouteDisposition `mapstructure:"route-disposition" json:"route-disposition,omitempty"` // original -> rpol:igp-actions // Actions to set IGP route attributes; these actions // apply to multiple IGPs. IgpActions IgpActions `mapstructure:"igp-actions" json:"igp-actions,omitempty"` // original -> bgp-pol:bgp-actions // Definitions for policy action statements that // change BGP-specific attributes of the route. BgpActions BgpActions `mapstructure:"bgp-actions" json:"bgp-actions,omitempty"` } func (lhs *Actions) Equal(rhs *Actions) bool { if lhs == nil || rhs == nil { return false } if lhs.RouteDisposition != rhs.RouteDisposition { return false } if !lhs.IgpActions.Equal(&(rhs.IgpActions)) { return false } if !lhs.BgpActions.Equal(&(rhs.BgpActions)) { return false } return true } // struct for container gobgp:match-large-community-set. type MatchLargeCommunitySet struct { // original -> gobgp:large-community-set LargeCommunitySet string `mapstructure:"large-community-set" json:"large-community-set,omitempty"` // original -> rpol:match-set-options // Optional parameter that governs the behaviour of the // match operation. MatchSetOptions MatchSetOptionsType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` } func (lhs *MatchLargeCommunitySet) Equal(rhs *MatchLargeCommunitySet) bool { if lhs == nil || rhs == nil { return false } if lhs.LargeCommunitySet != rhs.LargeCommunitySet { return false } if lhs.MatchSetOptions != rhs.MatchSetOptions { return false } return true } // struct for container bgp-pol:as-path-length. // Value and comparison operations for conditions based on the // length of the AS path in the route update. type AsPathLength struct { // original -> ptypes:operator // type of comparison to be performed. Operator AttributeComparison `mapstructure:"operator" json:"operator,omitempty"` // original -> ptypes:value // value to compare with the community count. Value uint32 `mapstructure:"value" json:"value,omitempty"` } func (lhs *AsPathLength) Equal(rhs *AsPathLength) bool { if lhs == nil || rhs == nil { return false } if lhs.Operator != rhs.Operator { return false } if lhs.Value != rhs.Value { return false } return true } // struct for container bgp-pol:community-count. // Value and comparison operations for conditions based on the // number of communities in the route update. type CommunityCount struct { // original -> ptypes:operator // type of comparison to be performed. Operator AttributeComparison `mapstructure:"operator" json:"operator,omitempty"` // original -> ptypes:value // value to compare with the community count. Value uint32 `mapstructure:"value" json:"value,omitempty"` } func (lhs *CommunityCount) Equal(rhs *CommunityCount) bool { if lhs == nil || rhs == nil { return false } if lhs.Operator != rhs.Operator { return false } if lhs.Value != rhs.Value { return false } return true } // struct for container bgp-pol:match-as-path-set. // Match a referenced as-path set according to the logic // defined in the match-set-options leaf. type MatchAsPathSet struct { // original -> bgp-pol:as-path-set // References a defined AS path set. AsPathSet string `mapstructure:"as-path-set" json:"as-path-set,omitempty"` // original -> rpol:match-set-options // Optional parameter that governs the behaviour of the // match operation. MatchSetOptions MatchSetOptionsType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` } func (lhs *MatchAsPathSet) Equal(rhs *MatchAsPathSet) bool { if lhs == nil || rhs == nil { return false } if lhs.AsPathSet != rhs.AsPathSet { return false } if lhs.MatchSetOptions != rhs.MatchSetOptions { return false } return true } // struct for container bgp-pol:match-ext-community-set. // Match a referenced extended community-set according to the // logic defined in the match-set-options leaf. type MatchExtCommunitySet struct { // original -> bgp-pol:ext-community-set // References a defined extended community set. ExtCommunitySet string `mapstructure:"ext-community-set" json:"ext-community-set,omitempty"` // original -> rpol:match-set-options // Optional parameter that governs the behaviour of the // match operation. MatchSetOptions MatchSetOptionsType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` } func (lhs *MatchExtCommunitySet) Equal(rhs *MatchExtCommunitySet) bool { if lhs == nil || rhs == nil { return false } if lhs.ExtCommunitySet != rhs.ExtCommunitySet { return false } if lhs.MatchSetOptions != rhs.MatchSetOptions { return false } return true } // struct for container bgp-pol:match-community-set. // Match a referenced community-set according to the logic // defined in the match-set-options leaf. type MatchCommunitySet struct { // original -> bgp-pol:community-set // References a defined community set. CommunitySet string `mapstructure:"community-set" json:"community-set,omitempty"` // original -> rpol:match-set-options // Optional parameter that governs the behaviour of the // match operation. MatchSetOptions MatchSetOptionsType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` } func (lhs *MatchCommunitySet) Equal(rhs *MatchCommunitySet) bool { if lhs == nil || rhs == nil { return false } if lhs.CommunitySet != rhs.CommunitySet { return false } if lhs.MatchSetOptions != rhs.MatchSetOptions { return false } return true } // struct for container bgp-pol:bgp-conditions. // Policy conditions for matching // BGP-specific defined sets or comparing BGP-specific // attributes. type BgpConditions struct { // original -> bgp-pol:match-community-set // Match a referenced community-set according to the logic // defined in the match-set-options leaf. MatchCommunitySet MatchCommunitySet `mapstructure:"match-community-set" json:"match-community-set,omitempty"` // original -> bgp-pol:match-ext-community-set // Match a referenced extended community-set according to the // logic defined in the match-set-options leaf. MatchExtCommunitySet MatchExtCommunitySet `mapstructure:"match-ext-community-set" json:"match-ext-community-set,omitempty"` // original -> bgp-pol:match-as-path-set // Match a referenced as-path set according to the logic // defined in the match-set-options leaf. MatchAsPathSet MatchAsPathSet `mapstructure:"match-as-path-set" json:"match-as-path-set,omitempty"` // original -> bgp-pol:med-eq // Condition to check if the received MED value is equal to // the specified value. MedEq uint32 `mapstructure:"med-eq" json:"med-eq,omitempty"` // original -> bgp-pol:origin-eq // Condition to check if the route origin is equal to the // specified value. OriginEq BgpOriginAttrType `mapstructure:"origin-eq" json:"origin-eq,omitempty"` // original -> bgp-pol:next-hop-in // original type is list of inet:ip-address // List of next hop addresses to check for in the route // update. NextHopInList []string `mapstructure:"next-hop-in-list" json:"next-hop-in-list,omitempty"` // original -> bgp-pol:afi-safi-in // List of address families which the NLRI may be // within. AfiSafiInList []AfiSafiType `mapstructure:"afi-safi-in-list" json:"afi-safi-in-list,omitempty"` // original -> bgp-pol:local-pref-eq // Condition to check if the local pref attribute is equal to // the specified value. LocalPrefEq uint32 `mapstructure:"local-pref-eq" json:"local-pref-eq,omitempty"` // original -> bgp-pol:community-count // Value and comparison operations for conditions based on the // number of communities in the route update. CommunityCount CommunityCount `mapstructure:"community-count" json:"community-count,omitempty"` // original -> bgp-pol:as-path-length // Value and comparison operations for conditions based on the // length of the AS path in the route update. AsPathLength AsPathLength `mapstructure:"as-path-length" json:"as-path-length,omitempty"` // original -> bgp-pol:route-type // Condition to check the route type in the route update. RouteType RouteType `mapstructure:"route-type" json:"route-type,omitempty"` // original -> gobgp:rpki-validation-result // specify the validation result of RPKI based on ROA as conditions. RpkiValidationResult RpkiValidationResultType `mapstructure:"rpki-validation-result" json:"rpki-validation-result,omitempty"` // original -> gobgp:match-large-community-set MatchLargeCommunitySet MatchLargeCommunitySet `mapstructure:"match-large-community-set" json:"match-large-community-set,omitempty"` } func (lhs *BgpConditions) Equal(rhs *BgpConditions) bool { if lhs == nil || rhs == nil { return false } if !lhs.MatchCommunitySet.Equal(&(rhs.MatchCommunitySet)) { return false } if !lhs.MatchExtCommunitySet.Equal(&(rhs.MatchExtCommunitySet)) { return false } if !lhs.MatchAsPathSet.Equal(&(rhs.MatchAsPathSet)) { return false } if lhs.MedEq != rhs.MedEq { return false } if lhs.OriginEq != rhs.OriginEq { return false } if len(lhs.NextHopInList) != len(rhs.NextHopInList) { return false } for idx, l := range lhs.NextHopInList { if l != rhs.NextHopInList[idx] { return false } } if len(lhs.AfiSafiInList) != len(rhs.AfiSafiInList) { return false } for idx, l := range lhs.AfiSafiInList { if l != rhs.AfiSafiInList[idx] { return false } } if lhs.LocalPrefEq != rhs.LocalPrefEq { return false } if !lhs.CommunityCount.Equal(&(rhs.CommunityCount)) { return false } if !lhs.AsPathLength.Equal(&(rhs.AsPathLength)) { return false } if lhs.RouteType != rhs.RouteType { return false } if lhs.RpkiValidationResult != rhs.RpkiValidationResult { return false } if !lhs.MatchLargeCommunitySet.Equal(&(rhs.MatchLargeCommunitySet)) { return false } return true } // struct for container rpol:igp-conditions. // Policy conditions for IGP attributes. type IgpConditions struct { } func (lhs *IgpConditions) Equal(rhs *IgpConditions) bool { if lhs == nil || rhs == nil { return false } return true } // struct for container rpol:match-tag-set. // Match a referenced tag set according to the logic defined // in the match-options-set leaf. type MatchTagSet struct { // original -> rpol:tag-set // References a defined tag set. TagSet string `mapstructure:"tag-set" json:"tag-set,omitempty"` // original -> rpol:match-set-options // Optional parameter that governs the behaviour of the // match operation. This leaf only supports matching on ANY // member of the set or inverting the match. Matching on ALL is // not supported). MatchSetOptions MatchSetOptionsRestrictedType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` } func (lhs *MatchTagSet) Equal(rhs *MatchTagSet) bool { if lhs == nil || rhs == nil { return false } if lhs.TagSet != rhs.TagSet { return false } if lhs.MatchSetOptions != rhs.MatchSetOptions { return false } return true } // struct for container rpol:match-neighbor-set. // Match a referenced neighbor set according to the logic // defined in the match-set-options-leaf. type MatchNeighborSet struct { // original -> rpol:neighbor-set // References a defined neighbor set. NeighborSet string `mapstructure:"neighbor-set" json:"neighbor-set,omitempty"` // original -> rpol:match-set-options // Optional parameter that governs the behaviour of the // match operation. This leaf only supports matching on ANY // member of the set or inverting the match. Matching on ALL is // not supported). MatchSetOptions MatchSetOptionsRestrictedType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` } func (lhs *MatchNeighborSet) Equal(rhs *MatchNeighborSet) bool { if lhs == nil || rhs == nil { return false } if lhs.NeighborSet != rhs.NeighborSet { return false } if lhs.MatchSetOptions != rhs.MatchSetOptions { return false } return true } // struct for container rpol:match-prefix-set. // Match a referenced prefix-set according to the logic // defined in the match-set-options leaf. type MatchPrefixSet struct { // original -> rpol:prefix-set // References a defined prefix set. PrefixSet string `mapstructure:"prefix-set" json:"prefix-set,omitempty"` // original -> rpol:match-set-options // Optional parameter that governs the behaviour of the // match operation. This leaf only supports matching on ANY // member of the set or inverting the match. Matching on ALL is // not supported). MatchSetOptions MatchSetOptionsRestrictedType `mapstructure:"match-set-options" json:"match-set-options,omitempty"` } func (lhs *MatchPrefixSet) Equal(rhs *MatchPrefixSet) bool { if lhs == nil || rhs == nil { return false } if lhs.PrefixSet != rhs.PrefixSet { return false } if lhs.MatchSetOptions != rhs.MatchSetOptions { return false } return true } // struct for container rpol:conditions. // Condition statements for this // policy statement. type Conditions struct { // original -> rpol:call-policy // Applies the statements from the specified policy // definition and then returns control the current // policy statement. Note that the called policy may // itself call other policies (subject to // implementation limitations). This is intended to // provide a policy 'subroutine' capability. The // called policy should contain an explicit or a // default route disposition that returns an // effective true (accept-route) or false // (reject-route), otherwise the behavior may be // ambiguous and implementation dependent. CallPolicy string `mapstructure:"call-policy" json:"call-policy,omitempty"` // original -> rpol:match-prefix-set // Match a referenced prefix-set according to the logic // defined in the match-set-options leaf. MatchPrefixSet MatchPrefixSet `mapstructure:"match-prefix-set" json:"match-prefix-set,omitempty"` // original -> rpol:match-neighbor-set // Match a referenced neighbor set according to the logic // defined in the match-set-options-leaf. MatchNeighborSet MatchNeighborSet `mapstructure:"match-neighbor-set" json:"match-neighbor-set,omitempty"` // original -> rpol:match-tag-set // Match a referenced tag set according to the logic defined // in the match-options-set leaf. MatchTagSet MatchTagSet `mapstructure:"match-tag-set" json:"match-tag-set,omitempty"` // original -> rpol:install-protocol-eq // Condition to check the protocol / method used to install // which installed the route into the local routing table. InstallProtocolEq InstallProtocolType `mapstructure:"install-protocol-eq" json:"install-protocol-eq,omitempty"` // original -> rpol:igp-conditions // Policy conditions for IGP attributes. IgpConditions IgpConditions `mapstructure:"igp-conditions" json:"igp-conditions,omitempty"` // original -> bgp-pol:bgp-conditions // Policy conditions for matching // BGP-specific defined sets or comparing BGP-specific // attributes. BgpConditions BgpConditions `mapstructure:"bgp-conditions" json:"bgp-conditions,omitempty"` } func (lhs *Conditions) Equal(rhs *Conditions) bool { if lhs == nil || rhs == nil { return false } if lhs.CallPolicy != rhs.CallPolicy { return false } if !lhs.MatchPrefixSet.Equal(&(rhs.MatchPrefixSet)) { return false } if !lhs.MatchNeighborSet.Equal(&(rhs.MatchNeighborSet)) { return false } if !lhs.MatchTagSet.Equal(&(rhs.MatchTagSet)) { return false } if lhs.InstallProtocolEq != rhs.InstallProtocolEq { return false } if !lhs.IgpConditions.Equal(&(rhs.IgpConditions)) { return false } if !lhs.BgpConditions.Equal(&(rhs.BgpConditions)) { return false } return true } // struct for container rpol:statement. // Policy statements group conditions and actions // within a policy definition. They are evaluated in // the order specified (see the description of policy // evaluation at the top of this module. type Statement struct { // original -> rpol:name // name of the policy statement. Name string `mapstructure:"name" json:"name,omitempty"` // original -> rpol:conditions // Condition statements for this // policy statement. Conditions Conditions `mapstructure:"conditions" json:"conditions,omitempty"` // original -> rpol:actions // Action statements for this policy // statement. Actions Actions `mapstructure:"actions" json:"actions,omitempty"` } func (lhs *Statement) Equal(rhs *Statement) bool { if lhs == nil || rhs == nil { return false } if lhs.Name != rhs.Name { return false } if !lhs.Conditions.Equal(&(rhs.Conditions)) { return false } if !lhs.Actions.Equal(&(rhs.Actions)) { return false } return true } // struct for container rpol:policy-definition. // List of top-level policy definitions, keyed by unique // name. These policy definitions are expected to be // referenced (by name) in policy chains specified in import // or export configuration statements. type PolicyDefinition struct { // original -> rpol:name // Name of the top-level policy definition -- this name // is used in references to the current policy. Name string `mapstructure:"name" json:"name,omitempty"` // original -> rpol:statements // Enclosing container for policy statements. Statements []Statement `mapstructure:"statements" json:"statements,omitempty"` } func (lhs *PolicyDefinition) Equal(rhs *PolicyDefinition) bool { if lhs == nil || rhs == nil { return false } if lhs.Name != rhs.Name { return false } if len(lhs.Statements) != len(rhs.Statements) { return false } { lmap := make(map[string]*Statement) for i, l := range lhs.Statements { lmap[mapkey(i, string(l.Name))] = &lhs.Statements[i] } for i, r := range rhs.Statements { if l, y := lmap[mapkey(i, string(r.Name))]; !y { return false } else if !r.Equal(l) { return false } } } return true } // struct for container gobgp:large-community-set. type LargeCommunitySet struct { // original -> gobgp:large-community-set-name LargeCommunitySetName string `mapstructure:"large-community-set-name" json:"large-community-set-name,omitempty"` // original -> gobgp:large-community // extended community set member. LargeCommunityList []string `mapstructure:"large-community-list" json:"large-community-list,omitempty"` } func (lhs *LargeCommunitySet) Equal(rhs *LargeCommunitySet) bool { if lhs == nil || rhs == nil { return false } if lhs.LargeCommunitySetName != rhs.LargeCommunitySetName { return false } if len(lhs.LargeCommunityList) != len(rhs.LargeCommunityList) { return false } for idx, l := range lhs.LargeCommunityList { if l != rhs.LargeCommunityList[idx] { return false } } return true } // struct for container bgp-pol:as-path-set. // Definitions for AS path sets. type AsPathSet struct { // original -> bgp-pol:as-path-set-name // name of the AS path set -- this is used to reference // the set in match conditions. AsPathSetName string `mapstructure:"as-path-set-name" json:"as-path-set-name,omitempty"` // original -> gobgp:as-path // AS path expression. AsPathList []string `mapstructure:"as-path-list" json:"as-path-list,omitempty"` } func (lhs *AsPathSet) Equal(rhs *AsPathSet) bool { if lhs == nil || rhs == nil { return false } if lhs.AsPathSetName != rhs.AsPathSetName { return false } if len(lhs.AsPathList) != len(rhs.AsPathList) { return false } for idx, l := range lhs.AsPathList { if l != rhs.AsPathList[idx] { return false } } return true } // struct for container bgp-pol:ext-community-set. // Definitions for extended community sets. type ExtCommunitySet struct { // original -> bgp-pol:ext-community-set-name // name / label of the extended community set -- this is // used to reference the set in match conditions. ExtCommunitySetName string `mapstructure:"ext-community-set-name" json:"ext-community-set-name,omitempty"` // original -> gobgp:ext-community // extended community set member. ExtCommunityList []string `mapstructure:"ext-community-list" json:"ext-community-list,omitempty"` } func (lhs *ExtCommunitySet) Equal(rhs *ExtCommunitySet) bool { if lhs == nil || rhs == nil { return false } if lhs.ExtCommunitySetName != rhs.ExtCommunitySetName { return false } if len(lhs.ExtCommunityList) != len(rhs.ExtCommunityList) { return false } for idx, l := range lhs.ExtCommunityList { if l != rhs.ExtCommunityList[idx] { return false } } return true } // struct for container bgp-pol:community-set. // Definitions for community sets. type CommunitySet struct { // original -> bgp-pol:community-set-name // name / label of the community set -- this is used to // reference the set in match conditions. CommunitySetName string `mapstructure:"community-set-name" json:"community-set-name,omitempty"` // original -> gobgp:community // community set member. CommunityList []string `mapstructure:"community-list" json:"community-list,omitempty"` } func (lhs *CommunitySet) Equal(rhs *CommunitySet) bool { if lhs == nil || rhs == nil { return false } if lhs.CommunitySetName != rhs.CommunitySetName { return false } if len(lhs.CommunityList) != len(rhs.CommunityList) { return false } for idx, l := range lhs.CommunityList { if l != rhs.CommunityList[idx] { return false } } return true } // struct for container bgp-pol:bgp-defined-sets. // BGP-related set definitions for policy match conditions. type BgpDefinedSets struct { // original -> bgp-pol:community-sets // Enclosing container for community sets. CommunitySets []CommunitySet `mapstructure:"community-sets" json:"community-sets,omitempty"` // original -> bgp-pol:ext-community-sets // Enclosing container for extended community sets. ExtCommunitySets []ExtCommunitySet `mapstructure:"ext-community-sets" json:"ext-community-sets,omitempty"` // original -> bgp-pol:as-path-sets // Enclosing container for AS path sets. AsPathSets []AsPathSet `mapstructure:"as-path-sets" json:"as-path-sets,omitempty"` // original -> gobgp:large-community-sets LargeCommunitySets []LargeCommunitySet `mapstructure:"large-community-sets" json:"large-community-sets,omitempty"` } func (lhs *BgpDefinedSets) Equal(rhs *BgpDefinedSets) bool { if lhs == nil || rhs == nil { return false } if len(lhs.CommunitySets) != len(rhs.CommunitySets) { return false } { lmap := make(map[string]*CommunitySet) for i, l := range lhs.CommunitySets { lmap[mapkey(i, string(l.CommunitySetName))] = &lhs.CommunitySets[i] } for i, r := range rhs.CommunitySets { if l, y := lmap[mapkey(i, string(r.CommunitySetName))]; !y { return false } else if !r.Equal(l) { return false } } } if len(lhs.ExtCommunitySets) != len(rhs.ExtCommunitySets) { return false } { lmap := make(map[string]*ExtCommunitySet) for i, l := range lhs.ExtCommunitySets { lmap[mapkey(i, string(l.ExtCommunitySetName))] = &lhs.ExtCommunitySets[i] } for i, r := range rhs.ExtCommunitySets { if l, y := lmap[mapkey(i, string(r.ExtCommunitySetName))]; !y { return false } else if !r.Equal(l) { return false } } } if len(lhs.AsPathSets) != len(rhs.AsPathSets) { return false } { lmap := make(map[string]*AsPathSet) for i, l := range lhs.AsPathSets { lmap[mapkey(i, string(l.AsPathSetName))] = &lhs.AsPathSets[i] } for i, r := range rhs.AsPathSets { if l, y := lmap[mapkey(i, string(r.AsPathSetName))]; !y { return false } else if !r.Equal(l) { return false } } } if len(lhs.LargeCommunitySets) != len(rhs.LargeCommunitySets) { return false } { lmap := make(map[string]*LargeCommunitySet) for i, l := range lhs.LargeCommunitySets { lmap[mapkey(i, string(l.LargeCommunitySetName))] = &lhs.LargeCommunitySets[i] } for i, r := range rhs.LargeCommunitySets { if l, y := lmap[mapkey(i, string(r.LargeCommunitySetName))]; !y { return false } else if !r.Equal(l) { return false } } } return true } // struct for container rpol:tag. // list of tags that are part of the tag set. type Tag struct { // original -> rpol:value // Value of the tag set member. Value TagType `mapstructure:"value" json:"value,omitempty"` } func (lhs *Tag) Equal(rhs *Tag) bool { if lhs == nil || rhs == nil { return false } if lhs.Value != rhs.Value { return false } return true } // struct for container rpol:tag-set. // Definitions for tag sets. type TagSet struct { // original -> rpol:tag-set-name // name / label of the tag set -- this is used to reference // the set in match conditions. TagSetName string `mapstructure:"tag-set-name" json:"tag-set-name,omitempty"` // original -> rpol:tag // list of tags that are part of the tag set. TagList []Tag `mapstructure:"tag-list" json:"tag-list,omitempty"` } func (lhs *TagSet) Equal(rhs *TagSet) bool { if lhs == nil || rhs == nil { return false } if lhs.TagSetName != rhs.TagSetName { return false } if len(lhs.TagList) != len(rhs.TagList) { return false } { lmap := make(map[string]*Tag) for i, l := range lhs.TagList { lmap[mapkey(i, string(l.Value))] = &lhs.TagList[i] } for i, r := range rhs.TagList { if l, y := lmap[mapkey(i, string(r.Value))]; !y { return false } else if !r.Equal(l) { return false } } } return true } // struct for container rpol:neighbor-set. // Definitions for neighbor sets. type NeighborSet struct { // original -> rpol:neighbor-set-name // name / label of the neighbor set -- this is used to // reference the set in match conditions. NeighborSetName string `mapstructure:"neighbor-set-name" json:"neighbor-set-name,omitempty"` // original -> gobgp:neighbor-info // original type is list of inet:ip-address // neighbor ip address or prefix. NeighborInfoList []string `mapstructure:"neighbor-info-list" json:"neighbor-info-list,omitempty"` } func (lhs *NeighborSet) Equal(rhs *NeighborSet) bool { if lhs == nil || rhs == nil { return false } if lhs.NeighborSetName != rhs.NeighborSetName { return false } if len(lhs.NeighborInfoList) != len(rhs.NeighborInfoList) { return false } for idx, l := range lhs.NeighborInfoList { if l != rhs.NeighborInfoList[idx] { return false } } return true } // struct for container rpol:prefix. // List of prefix expressions that are part of the set. type Prefix struct { // original -> rpol:ip-prefix // rpol:ip-prefix's original type is inet:ip-prefix. // The prefix member in CIDR notation -- while the // prefix may be either IPv4 or IPv6, most // implementations require all members of the prefix set // to be the same address family. Mixing address types in // the same prefix set is likely to cause an error. IpPrefix string `mapstructure:"ip-prefix" json:"ip-prefix,omitempty"` // original -> rpol:masklength-range // Defines a range for the masklength, or 'exact' if // the prefix has an exact length. // // Example: 10.3.192.0/21 through 10.3.192.0/24 would be // expressed as prefix: 10.3.192.0/21, // masklength-range: 21..24. // // Example: 10.3.192.0/21 would be expressed as // prefix: 10.3.192.0/21, // masklength-range: exact. MasklengthRange string `mapstructure:"masklength-range" json:"masklength-range,omitempty"` } func (lhs *Prefix) Equal(rhs *Prefix) bool { if lhs == nil || rhs == nil { return false } if lhs.IpPrefix != rhs.IpPrefix { return false } if lhs.MasklengthRange != rhs.MasklengthRange { return false } return true } // struct for container rpol:prefix-set. // List of the defined prefix sets. type PrefixSet struct { // original -> rpol:prefix-set-name // name / label of the prefix set -- this is used to // reference the set in match conditions. PrefixSetName string `mapstructure:"prefix-set-name" json:"prefix-set-name,omitempty"` // original -> rpol:prefix // List of prefix expressions that are part of the set. PrefixList []Prefix `mapstructure:"prefix-list" json:"prefix-list,omitempty"` } func (lhs *PrefixSet) Equal(rhs *PrefixSet) bool { if lhs == nil || rhs == nil { return false } if lhs.PrefixSetName != rhs.PrefixSetName { return false } if len(lhs.PrefixList) != len(rhs.PrefixList) { return false } { lmap := make(map[string]*Prefix) for i, l := range lhs.PrefixList { lmap[mapkey(i, string(l.IpPrefix+l.MasklengthRange))] = &lhs.PrefixList[i] } for i, r := range rhs.PrefixList { if l, y := lmap[mapkey(i, string(r.IpPrefix+r.MasklengthRange))]; !y { return false } else if !r.Equal(l) { return false } } } return true } // struct for container rpol:defined-sets. // Predefined sets of attributes used in policy match // statements. type DefinedSets struct { // original -> rpol:prefix-sets // Enclosing container for defined prefix sets for matching. PrefixSets []PrefixSet `mapstructure:"prefix-sets" json:"prefix-sets,omitempty"` // original -> rpol:neighbor-sets // Enclosing container for defined neighbor sets for matching. NeighborSets []NeighborSet `mapstructure:"neighbor-sets" json:"neighbor-sets,omitempty"` // original -> rpol:tag-sets // Enclosing container for defined tag sets for matching. TagSets []TagSet `mapstructure:"tag-sets" json:"tag-sets,omitempty"` // original -> bgp-pol:bgp-defined-sets // BGP-related set definitions for policy match conditions. BgpDefinedSets BgpDefinedSets `mapstructure:"bgp-defined-sets" json:"bgp-defined-sets,omitempty"` } func (lhs *DefinedSets) Equal(rhs *DefinedSets) bool { if lhs == nil || rhs == nil { return false } if len(lhs.PrefixSets) != len(rhs.PrefixSets) { return false } { lmap := make(map[string]*PrefixSet) for i, l := range lhs.PrefixSets { lmap[mapkey(i, string(l.PrefixSetName))] = &lhs.PrefixSets[i] } for i, r := range rhs.PrefixSets { if l, y := lmap[mapkey(i, string(r.PrefixSetName))]; !y { return false } else if !r.Equal(l) { return false } } } if len(lhs.NeighborSets) != len(rhs.NeighborSets) { return false } { lmap := make(map[string]*NeighborSet) for i, l := range lhs.NeighborSets { lmap[mapkey(i, string(l.NeighborSetName))] = &lhs.NeighborSets[i] } for i, r := range rhs.NeighborSets { if l, y := lmap[mapkey(i, string(r.NeighborSetName))]; !y { return false } else if !r.Equal(l) { return false } } } if len(lhs.TagSets) != len(rhs.TagSets) { return false } { lmap := make(map[string]*TagSet) for i, l := range lhs.TagSets { lmap[mapkey(i, string(l.TagSetName))] = &lhs.TagSets[i] } for i, r := range rhs.TagSets { if l, y := lmap[mapkey(i, string(r.TagSetName))]; !y { return false } else if !r.Equal(l) { return false } } } if !lhs.BgpDefinedSets.Equal(&(rhs.BgpDefinedSets)) { return false } return true } // struct for container rpol:routing-policy. // top-level container for all routing policy configuration. type RoutingPolicy struct { // original -> rpol:defined-sets // Predefined sets of attributes used in policy match // statements. DefinedSets DefinedSets `mapstructure:"defined-sets" json:"defined-sets,omitempty"` // original -> rpol:policy-definitions // Enclosing container for the list of top-level policy // definitions. PolicyDefinitions []PolicyDefinition `mapstructure:"policy-definitions" json:"policy-definitions,omitempty"` } func (lhs *RoutingPolicy) Equal(rhs *RoutingPolicy) bool { if lhs == nil || rhs == nil { return false } if !lhs.DefinedSets.Equal(&(rhs.DefinedSets)) { return false } if len(lhs.PolicyDefinitions) != len(rhs.PolicyDefinitions) { return false } { lmap := make(map[string]*PolicyDefinition) for i, l := range lhs.PolicyDefinitions { lmap[mapkey(i, string(l.Name))] = &lhs.PolicyDefinitions[i] } for i, r := range rhs.PolicyDefinitions { if l, y := lmap[mapkey(i, string(r.Name))]; !y { return false } else if !r.Equal(l) { return false } } } return true } gobgp-1.29/config/bgp_configs_test.go000066400000000000000000000053471324612745600177010ustar00rootroot00000000000000// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "bufio" "os" "path" "runtime" "strings" "testing" "github.com/spf13/viper" "github.com/stretchr/testify/assert" ) func TestEqual(t *testing.T) { assert := assert.New(t) p1 := Prefix{ IpPrefix: "192.168.0.0", MasklengthRange: "24..32", } p2 := Prefix{ IpPrefix: "192.168.0.0", MasklengthRange: "24..32", } assert.True(p1.Equal(&p2)) assert.False(p1.Equal(nil)) var p3 *Prefix assert.False(p3.Equal(&p1)) p3 = &Prefix{ IpPrefix: "192.168.0.0", MasklengthRange: "24..32", } assert.True(p3.Equal(&p1)) p3.IpPrefix = "10.10.0.0" assert.False(p3.Equal(&p1)) ps1 := PrefixSet{ PrefixSetName: "ps", PrefixList: []Prefix{p1, p2}, } ps2 := PrefixSet{ PrefixSetName: "ps", PrefixList: []Prefix{p2, p1}, } assert.True(ps1.Equal(&ps2)) ps2.PrefixSetName = "ps2" assert.False(ps1.Equal(&ps2)) } func extractTomlFromMarkdown(fileMd string, fileToml string) error { fMd, err := os.Open(fileMd) if err != nil { return err } defer fMd.Close() fToml, err := os.Create(fileToml) if err != nil { return err } defer fToml.Close() isBody := false scanner := bufio.NewScanner(fMd) fTomlWriter := bufio.NewWriter(fToml) for scanner.Scan() { if curText := scanner.Text(); strings.HasPrefix(curText, "```toml") { isBody = true } else if strings.HasPrefix(curText, "```") { isBody = false } else if isBody { if _, err := fTomlWriter.WriteString(curText + "\n"); err != nil { return err } } } fTomlWriter.Flush() if err := scanner.Err(); err != nil { return err } return nil } func TestConfigExample(t *testing.T) { assert := assert.New(t) _, f, _, _ := runtime.Caller(0) fileMd := path.Join(path.Dir(f), "../docs/sources/configuration.md") fileToml := "/tmp/gobgpd.example.toml" assert.NoError(extractTomlFromMarkdown(fileMd, fileToml)) defer os.Remove(fileToml) format := detectConfigFileType(fileToml, "") c := &BgpConfigSet{} v := viper.New() v.SetConfigFile(fileToml) v.SetConfigType(format) assert.NoError(v.ReadInConfig()) assert.NoError(v.UnmarshalExact(c)) assert.NoError(setDefaultConfigValuesWithViper(v, c)) } gobgp-1.29/config/default.go000066400000000000000000000325671324612745600160120ustar00rootroot00000000000000package config import ( "fmt" "net" "reflect" "github.com/osrg/gobgp/packet/bgp" "github.com/osrg/gobgp/packet/bmp" "github.com/osrg/gobgp/packet/rtr" "github.com/spf13/viper" ) const ( DEFAULT_HOLDTIME = 90 DEFAULT_IDLE_HOLDTIME_AFTER_RESET = 30 DEFAULT_CONNECT_RETRY = 120 ) var forcedOverwrittenConfig = []string{ "neighbor.config.peer-as", "neighbor.timers.config.minimum-advertisement-interval", } var configuredFields map[string]interface{} func RegisterConfiguredFields(addr string, n interface{}) { if configuredFields == nil { configuredFields = make(map[string]interface{}, 0) } configuredFields[addr] = n } func defaultAfiSafi(typ AfiSafiType, enable bool) AfiSafi { return AfiSafi{ Config: AfiSafiConfig{ AfiSafiName: typ, Enabled: enable, }, State: AfiSafiState{ AfiSafiName: typ, Family: bgp.AddressFamilyValueMap[string(typ)], }, } } func SetDefaultNeighborConfigValues(n *Neighbor, pg *PeerGroup, g *Global) error { // Determines this function is called against the same Neighbor struct, // and if already called, returns immediately. if n.State.LocalAs != 0 { return nil } return setDefaultNeighborConfigValuesWithViper(nil, n, g, pg) } func setDefaultNeighborConfigValuesWithViper(v *viper.Viper, n *Neighbor, g *Global, pg *PeerGroup) error { if n == nil { return fmt.Errorf("neighbor config is nil") } if g == nil { return fmt.Errorf("global config is nil") } if v == nil { v = viper.New() } if pg != nil { if err := OverwriteNeighborConfigWithPeerGroup(n, pg); err != nil { return err } } if n.Config.LocalAs == 0 { n.Config.LocalAs = g.Config.As if !g.Confederation.Config.Enabled || n.IsConfederation(g) { n.Config.LocalAs = g.Config.As } else { n.Config.LocalAs = g.Confederation.Config.Identifier } } n.State.LocalAs = n.Config.LocalAs if n.Config.PeerAs != n.Config.LocalAs { n.Config.PeerType = PEER_TYPE_EXTERNAL n.State.PeerType = PEER_TYPE_EXTERNAL n.State.RemovePrivateAs = n.Config.RemovePrivateAs n.AsPathOptions.State.ReplacePeerAs = n.AsPathOptions.Config.ReplacePeerAs } else { n.Config.PeerType = PEER_TYPE_INTERNAL n.State.PeerType = PEER_TYPE_INTERNAL if string(n.Config.RemovePrivateAs) != "" { return fmt.Errorf("can't set remove-private-as for iBGP peer") } if n.AsPathOptions.Config.ReplacePeerAs { return fmt.Errorf("can't set replace-peer-as for iBGP peer") } } if n.State.NeighborAddress == "" { n.State.NeighborAddress = n.Config.NeighborAddress } n.State.PeerAs = n.Config.PeerAs n.AsPathOptions.State.AllowOwnAs = n.AsPathOptions.Config.AllowOwnAs if !v.IsSet("neighbor.error-handling.config.treat-as-withdraw") { n.ErrorHandling.Config.TreatAsWithdraw = true } if !v.IsSet("neighbor.timers.config.connect-retry") && n.Timers.Config.ConnectRetry == 0 { n.Timers.Config.ConnectRetry = float64(DEFAULT_CONNECT_RETRY) } if !v.IsSet("neighbor.timers.config.hold-time") && n.Timers.Config.HoldTime == 0 { n.Timers.Config.HoldTime = float64(DEFAULT_HOLDTIME) } if !v.IsSet("neighbor.timers.config.keepalive-interval") && n.Timers.Config.KeepaliveInterval == 0 { n.Timers.Config.KeepaliveInterval = n.Timers.Config.HoldTime / 3 } if !v.IsSet("neighbor.timers.config.idle-hold-time-after-reset") && n.Timers.Config.IdleHoldTimeAfterReset == 0 { n.Timers.Config.IdleHoldTimeAfterReset = float64(DEFAULT_IDLE_HOLDTIME_AFTER_RESET) } if n.Config.NeighborInterface != "" { if n.RouteServer.Config.RouteServerClient { return fmt.Errorf("configuring route server client as unnumbered peer is not supported") } addr, err := GetIPv6LinkLocalNeighborAddress(n.Config.NeighborInterface) if err != nil { return err } n.State.NeighborAddress = addr } if n.Transport.Config.LocalAddress == "" { if n.State.NeighborAddress == "" { return fmt.Errorf("no neighbor address/interface specified") } ipAddr, err := net.ResolveIPAddr("ip", n.State.NeighborAddress) if err != nil { return err } localAddress := "0.0.0.0" if ipAddr.IP.To4() == nil { localAddress = "::" if ipAddr.Zone != "" { localAddress, err = getIPv6LinkLocalAddress(ipAddr.Zone) if err != nil { return err } } } n.Transport.Config.LocalAddress = localAddress } if len(n.AfiSafis) == 0 { if n.Config.NeighborInterface != "" { n.AfiSafis = []AfiSafi{ defaultAfiSafi(AFI_SAFI_TYPE_IPV4_UNICAST, true), defaultAfiSafi(AFI_SAFI_TYPE_IPV6_UNICAST, true), } } else if ipAddr, err := net.ResolveIPAddr("ip", n.State.NeighborAddress); err != nil { return fmt.Errorf("invalid neighbor address: %s", n.State.NeighborAddress) } else if ipAddr.IP.To4() != nil { n.AfiSafis = []AfiSafi{defaultAfiSafi(AFI_SAFI_TYPE_IPV4_UNICAST, true)} } else { n.AfiSafis = []AfiSafi{defaultAfiSafi(AFI_SAFI_TYPE_IPV6_UNICAST, true)} } for i := range n.AfiSafis { n.AfiSafis[i].AddPaths.Config.Receive = n.AddPaths.Config.Receive n.AfiSafis[i].AddPaths.State.Receive = n.AddPaths.Config.Receive n.AfiSafis[i].AddPaths.Config.SendMax = n.AddPaths.Config.SendMax n.AfiSafis[i].AddPaths.State.SendMax = n.AddPaths.Config.SendMax } } else { afs, err := extractArray(v.Get("neighbor.afi-safis")) if err != nil { return err } for i := range n.AfiSafis { vv := viper.New() if len(afs) > i { vv.Set("afi-safi", afs[i]) } if rf, err := bgp.GetRouteFamily(string(n.AfiSafis[i].Config.AfiSafiName)); err != nil { return err } else { n.AfiSafis[i].State.Family = rf } n.AfiSafis[i].State.AfiSafiName = n.AfiSafis[i].Config.AfiSafiName if !vv.IsSet("afi-safi.config.enabled") { n.AfiSafis[i].Config.Enabled = true } n.AfiSafis[i].MpGracefulRestart.State.Enabled = n.AfiSafis[i].MpGracefulRestart.Config.Enabled if !vv.IsSet("afi-safi.add-paths.config.receive") { n.AfiSafis[i].AddPaths.Config.Receive = n.AddPaths.Config.Receive } n.AfiSafis[i].AddPaths.State.Receive = n.AfiSafis[i].AddPaths.Config.Receive if !vv.IsSet("afi-safi.add-paths.config.send-max") { n.AfiSafis[i].AddPaths.Config.SendMax = n.AddPaths.Config.SendMax } n.AfiSafis[i].AddPaths.State.SendMax = n.AfiSafis[i].AddPaths.Config.SendMax } } n.State.Description = n.Config.Description n.State.AdminDown = n.Config.AdminDown if n.GracefulRestart.Config.Enabled { if !v.IsSet("neighbor.graceful-restart.config.restart-time") && n.GracefulRestart.Config.RestartTime == 0 { // RFC 4724 4. Operation // A suggested default for the Restart Time is a value less than or // equal to the HOLDTIME carried in the OPEN. n.GracefulRestart.Config.RestartTime = uint16(n.Timers.Config.HoldTime) } if !v.IsSet("neighbor.graceful-restart.config.deferral-time") && n.GracefulRestart.Config.DeferralTime == 0 { // RFC 4724 4.1. Procedures for the Restarting Speaker // The value of this timer should be large // enough, so as to provide all the peers of the Restarting Speaker with // enough time to send all the routes to the Restarting Speaker n.GracefulRestart.Config.DeferralTime = uint16(360) } } if n.EbgpMultihop.Config.Enabled { if n.TtlSecurity.Config.Enabled { return fmt.Errorf("ebgp-multihop and ttl-security are mututally exclusive") } if n.EbgpMultihop.Config.MultihopTtl == 0 { n.EbgpMultihop.Config.MultihopTtl = 255 } } else if n.TtlSecurity.Config.Enabled { if n.TtlSecurity.Config.TtlMin == 0 { n.TtlSecurity.Config.TtlMin = 255 } } if n.RouteReflector.Config.RouteReflectorClient { if n.RouteReflector.Config.RouteReflectorClusterId == "" { n.RouteReflector.Config.RouteReflectorClusterId = RrClusterIdType(g.Config.RouterId) } else if id := net.ParseIP(string(n.RouteReflector.Config.RouteReflectorClusterId)).To4(); id == nil { return fmt.Errorf("route-reflector-cluster-id should be specified in IPv4 address format") } } return nil } func SetDefaultGlobalConfigValues(g *Global) error { if len(g.AfiSafis) == 0 { g.AfiSafis = []AfiSafi{} for k, _ := range AfiSafiTypeToIntMap { g.AfiSafis = append(g.AfiSafis, defaultAfiSafi(k, true)) } } if g.Config.Port == 0 { g.Config.Port = bgp.BGP_PORT } if len(g.Config.LocalAddressList) == 0 { g.Config.LocalAddressList = []string{"0.0.0.0", "::"} } return nil } func SetDefaultConfigValues(b *BgpConfigSet) error { return setDefaultConfigValuesWithViper(nil, b) } func setDefaultPolicyConfigValuesWithViper(v *viper.Viper, p *PolicyDefinition) error { stmts, err := extractArray(v.Get("policy.statements")) if err != nil { return err } for i, _ := range p.Statements { vv := viper.New() if len(stmts) > i { vv.Set("statement", stmts[i]) } if !vv.IsSet("statement.actions.route-disposition") { p.Statements[i].Actions.RouteDisposition = ROUTE_DISPOSITION_NONE } } return nil } func setDefaultConfigValuesWithViper(v *viper.Viper, b *BgpConfigSet) error { if v == nil { v = viper.New() } if err := SetDefaultGlobalConfigValues(&b.Global); err != nil { return err } for idx, server := range b.BmpServers { if server.Config.Port == 0 { server.Config.Port = bmp.BMP_DEFAULT_PORT } if server.Config.RouteMonitoringPolicy == "" { server.Config.RouteMonitoringPolicy = BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY } // statistics-timeout is uint16 value and implicitly less than 65536 if server.Config.StatisticsTimeout != 0 && server.Config.StatisticsTimeout < 15 { return fmt.Errorf("too small statistics-timeout value: %d", server.Config.StatisticsTimeout) } b.BmpServers[idx] = server } if b.Zebra.Config.Url == "" { b.Zebra.Config.Url = "unix:/var/run/quagga/zserv.api" } if b.Zebra.Config.Version < 2 { b.Zebra.Config.Version = 2 } else if b.Zebra.Config.Version > 4 { b.Zebra.Config.Version = 4 } if !v.IsSet("zebra.config.nexthop-trigger-enable") && !b.Zebra.Config.NexthopTriggerEnable && b.Zebra.Config.Version > 2 { b.Zebra.Config.NexthopTriggerEnable = true } if b.Zebra.Config.NexthopTriggerDelay == 0 { b.Zebra.Config.NexthopTriggerDelay = 5 } list, err := extractArray(v.Get("neighbors")) if err != nil { return err } for idx, n := range b.Neighbors { vv := viper.New() if len(list) > idx { vv.Set("neighbor", list[idx]) } pg, err := b.getPeerGroup(n.Config.PeerGroup) if err != nil { return nil } if pg != nil { identifier := vv.Get("neighbor.config.neighbor-address") if identifier == nil { identifier = vv.Get("neighbor.config.neighbor-interface") } RegisterConfiguredFields(identifier.(string), list[idx]) } if err := setDefaultNeighborConfigValuesWithViper(vv, &n, &b.Global, pg); err != nil { return err } b.Neighbors[idx] = n } for _, d := range b.DynamicNeighbors { if err := d.validate(b); err != nil { return err } } for idx, r := range b.RpkiServers { if r.Config.Port == 0 { b.RpkiServers[idx].Config.Port = rtr.RPKI_DEFAULT_PORT } } list, err = extractArray(v.Get("policy-definitions")) if err != nil { return err } for idx, p := range b.PolicyDefinitions { vv := viper.New() if len(list) > idx { vv.Set("policy", list[idx]) } if err := setDefaultPolicyConfigValuesWithViper(vv, &p); err != nil { return err } b.PolicyDefinitions[idx] = p } return nil } func OverwriteNeighborConfigWithPeerGroup(c *Neighbor, pg *PeerGroup) error { v := viper.New() val, ok := configuredFields[c.Config.NeighborAddress] if ok { v.Set("neighbor", val) } else { v.Set("neighbor.config.peer-group", c.Config.PeerGroup) } overwriteConfig(&c.Config, &pg.Config, "neighbor.config", v) overwriteConfig(&c.Timers.Config, &pg.Timers.Config, "neighbor.timers.config", v) overwriteConfig(&c.Transport.Config, &pg.Transport.Config, "neighbor.transport.config", v) overwriteConfig(&c.ErrorHandling.Config, &pg.ErrorHandling.Config, "neighbor.error-handling.config", v) overwriteConfig(&c.LoggingOptions.Config, &pg.LoggingOptions.Config, "neighbor.logging-options.config", v) overwriteConfig(&c.EbgpMultihop.Config, &pg.EbgpMultihop.Config, "neighbor.ebgp-multihop.config", v) overwriteConfig(&c.RouteReflector.Config, &pg.RouteReflector.Config, "neighbor.route-reflector.config", v) overwriteConfig(&c.AsPathOptions.Config, &pg.AsPathOptions.Config, "neighbor.as-path-options.config", v) overwriteConfig(&c.AddPaths.Config, &pg.AddPaths.Config, "neighbor.add-paths.config", v) overwriteConfig(&c.GracefulRestart.Config, &pg.GracefulRestart.Config, "neighbor.gradeful-restart.config", v) overwriteConfig(&c.ApplyPolicy.Config, &pg.ApplyPolicy.Config, "neighbor.apply-policy.config", v) overwriteConfig(&c.UseMultiplePaths.Config, &pg.UseMultiplePaths.Config, "neighbor.use-multiple-paths.config", v) overwriteConfig(&c.RouteServer.Config, &pg.RouteServer.Config, "neighbor.route-server.config", v) overwriteConfig(&c.TtlSecurity.Config, &pg.TtlSecurity.Config, "neighbor.ttl-security.config", v) if !v.IsSet("neighbor.afi-safis") { c.AfiSafis = pg.AfiSafis } return nil } func overwriteConfig(c, pg interface{}, tagPrefix string, v *viper.Viper) { nValue := reflect.Indirect(reflect.ValueOf(c)) nType := reflect.Indirect(nValue).Type() pgValue := reflect.Indirect(reflect.ValueOf(pg)) pgType := reflect.Indirect(pgValue).Type() for i := 0; i < pgType.NumField(); i++ { field := pgType.Field(i).Name tag := tagPrefix + "." + nType.Field(i).Tag.Get("mapstructure") if func() bool { for _, t := range forcedOverwrittenConfig { if t == tag { return true } } return false }() || !v.IsSet(tag) { nValue.FieldByName(field).Set(pgValue.FieldByName(field)) } } } gobgp-1.29/config/default_linux.go000066400000000000000000000027221324612745600172170ustar00rootroot00000000000000// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. // +build linux package config import ( "fmt" "github.com/vishvananda/netlink" "net" ) func GetIPv6LinkLocalNeighborAddress(ifname string) (string, error) { ifi, err := net.InterfaceByName(ifname) if err != nil { return "", err } neighs, err := netlink.NeighList(ifi.Index, netlink.FAMILY_V6) if err != nil { return "", err } cnt := 0 var addr net.IP for _, neigh := range neighs { local, err := isLocalLinkLocalAddress(ifi.Index, neigh.IP) if err != nil { return "", err } if neigh.State&netlink.NUD_FAILED == 0 && neigh.IP.IsLinkLocalUnicast() && !local { addr = neigh.IP cnt += 1 } } if cnt == 0 { return "", fmt.Errorf("no ipv6 link-local neighbor found") } else if cnt > 1 { return "", fmt.Errorf("found %d link-local neighbors. only support p2p link", cnt) } return fmt.Sprintf("%s%%%s", addr, ifname), nil } gobgp-1.29/config/default_nonlinux.go000066400000000000000000000014551324612745600177340ustar00rootroot00000000000000// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. // +build !linux package config import ( "fmt" ) func GetIPv6LinkLocalNeighborAddress(ifname string) (string, error) { return "", fmt.Errorf("unnumbered peering is not supported") } gobgp-1.29/config/serve.go000066400000000000000000000102401324612745600154720ustar00rootroot00000000000000package config import ( log "github.com/sirupsen/logrus" "github.com/spf13/viper" "os" "os/signal" "syscall" ) type BgpConfigSet struct { Global Global `mapstructure:"global"` Neighbors []Neighbor `mapstructure:"neighbors"` PeerGroups []PeerGroup `mapstructure:"peer-groups"` RpkiServers []RpkiServer `mapstructure:"rpki-servers"` BmpServers []BmpServer `mapstructure:"bmp-servers"` MrtDump []Mrt `mapstructure:"mrt-dump"` Zebra Zebra `mapstructure:"zebra"` Collector Collector `mapstructure:"collector"` DefinedSets DefinedSets `mapstructure:"defined-sets"` PolicyDefinitions []PolicyDefinition `mapstructure:"policy-definitions"` DynamicNeighbors []DynamicNeighbor `mapstructure:"dynamic-neighbors"` } func ReadConfigfileServe(path, format string, configCh chan *BgpConfigSet) { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGHUP) // Update config file type, if detectable format = detectConfigFileType(path, format) cnt := 0 for { c := &BgpConfigSet{} v := viper.New() v.SetConfigFile(path) v.SetConfigType(format) var err error if err = v.ReadInConfig(); err != nil { goto ERROR } if err = v.UnmarshalExact(c); err != nil { goto ERROR } if err = setDefaultConfigValuesWithViper(v, c); err != nil { goto ERROR } if cnt == 0 { log.WithFields(log.Fields{ "Topic": "Config", }).Info("Finished reading the config file") } cnt++ configCh <- c goto NEXT ERROR: if cnt == 0 { log.WithFields(log.Fields{ "Topic": "Config", "Error": err, }).Fatalf("Can't read config file %s", path) } else { log.WithFields(log.Fields{ "Topic": "Config", "Error": err, }).Warningf("Can't read config file %s", path) } NEXT: select { case <-sigCh: log.WithFields(log.Fields{ "Topic": "Config", }).Info("Reload the config file") } } } func ConfigSetToRoutingPolicy(c *BgpConfigSet) *RoutingPolicy { return &RoutingPolicy{ DefinedSets: c.DefinedSets, PolicyDefinitions: c.PolicyDefinitions, } } func UpdatePeerGroupConfig(curC, newC *BgpConfigSet) ([]PeerGroup, []PeerGroup, []PeerGroup) { addedPg := []PeerGroup{} deletedPg := []PeerGroup{} updatedPg := []PeerGroup{} for _, n := range newC.PeerGroups { if idx := existPeerGroup(n.Config.PeerGroupName, curC.PeerGroups); idx < 0 { addedPg = append(addedPg, n) } else if !n.Equal(&curC.PeerGroups[idx]) { log.WithFields(log.Fields{ "Topic": "Config", }).Debugf("Current peer-group config:%s", curC.PeerGroups[idx]) log.WithFields(log.Fields{ "Topic": "Config", }).Debugf("New peer-group config:%s", n) updatedPg = append(updatedPg, n) } } for _, n := range curC.PeerGroups { if existPeerGroup(n.Config.PeerGroupName, newC.PeerGroups) < 0 { deletedPg = append(deletedPg, n) } } return addedPg, deletedPg, updatedPg } func UpdateNeighborConfig(curC, newC *BgpConfigSet) ([]Neighbor, []Neighbor, []Neighbor) { added := []Neighbor{} deleted := []Neighbor{} updated := []Neighbor{} for _, n := range newC.Neighbors { if idx := inSlice(n, curC.Neighbors); idx < 0 { added = append(added, n) } else if !n.Equal(&curC.Neighbors[idx]) { log.WithFields(log.Fields{ "Topic": "Config", }).Debugf("Current neighbor config:%s", curC.Neighbors[idx]) log.WithFields(log.Fields{ "Topic": "Config", }).Debugf("New neighbor config:%s", n) updated = append(updated, n) } } for _, n := range curC.Neighbors { if inSlice(n, newC.Neighbors) < 0 { deleted = append(deleted, n) } } return added, deleted, updated } func CheckPolicyDifference(currentPolicy *RoutingPolicy, newPolicy *RoutingPolicy) bool { log.WithFields(log.Fields{ "Topic": "Config", }).Debugf("Current policy:%s", currentPolicy) log.WithFields(log.Fields{ "Topic": "Config", }).Debugf("New policy:%s", newPolicy) var result bool = false if currentPolicy == nil && newPolicy == nil { result = false } else { if currentPolicy != nil && newPolicy != nil { result = !currentPolicy.Equal(newPolicy) } else { result = true } } return result } gobgp-1.29/config/util.go000066400000000000000000000145641324612745600153400ustar00rootroot00000000000000// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "fmt" "net" "path/filepath" "regexp" "strconv" "github.com/osrg/gobgp/packet/bgp" ) // Returns config file type by retrieving extension from the given path. // If no corresponding type found, returns the given def as the default value. func detectConfigFileType(path, def string) string { switch ext := filepath.Ext(path); ext { case ".toml": return "toml" case ".yaml", ".yml": return "yaml" case ".json": return "json" default: return def } } // yaml is decoded as []interface{} // but toml is decoded as []map[string]interface{}. // currently, viper can't hide this difference. // handle the difference here. func extractArray(intf interface{}) ([]interface{}, error) { if intf != nil { list, ok := intf.([]interface{}) if ok { return list, nil } l, ok := intf.([]map[string]interface{}) if !ok { return nil, fmt.Errorf("invalid configuration: neither []interface{} nor []map[string]interface{}") } list = make([]interface{}, 0, len(l)) for _, m := range l { list = append(list, m) } return list, nil } return nil, nil } func getIPv6LinkLocalAddress(ifname string) (string, error) { ifi, err := net.InterfaceByName(ifname) if err != nil { return "", err } addrs, err := ifi.Addrs() if err != nil { return "", err } for _, addr := range addrs { ip := addr.(*net.IPNet).IP if ip.To4() == nil && ip.IsLinkLocalUnicast() { return fmt.Sprintf("%s%%%s", ip.String(), ifname), nil } } return "", fmt.Errorf("no ipv6 link local address for %s", ifname) } func isLocalLinkLocalAddress(ifindex int, addr net.IP) (bool, error) { ifi, err := net.InterfaceByIndex(ifindex) if err != nil { return false, err } addrs, err := ifi.Addrs() if err != nil { return false, err } for _, a := range addrs { if ip, _, _ := net.ParseCIDR(a.String()); addr.Equal(ip) { return true, nil } } return false, nil } func (b *BgpConfigSet) getPeerGroup(n string) (*PeerGroup, error) { if n == "" { return nil, nil } for _, pg := range b.PeerGroups { if n == pg.Config.PeerGroupName { return &pg, nil } } return nil, fmt.Errorf("no such peer-group: %s", n) } func (d *DynamicNeighbor) validate(b *BgpConfigSet) error { if d.Config.PeerGroup == "" { return fmt.Errorf("dynamic neighbor requires the peer group config") } if _, err := b.getPeerGroup(d.Config.PeerGroup); err != nil { return err } if _, _, err := net.ParseCIDR(d.Config.Prefix); err != nil { return fmt.Errorf("invalid dynamic neighbor prefix %s", d.Config.Prefix) } return nil } func (n *Neighbor) IsConfederationMember(g *Global) bool { for _, member := range g.Confederation.Config.MemberAsList { if member == n.Config.PeerAs { return true } } return false } func (n *Neighbor) IsConfederation(g *Global) bool { if n.Config.PeerAs == g.Config.As { return true } return n.IsConfederationMember(g) } func (n *Neighbor) IsEBGPPeer(g *Global) bool { return n.Config.PeerAs != g.Config.As } func (n *Neighbor) CreateRfMap() map[bgp.RouteFamily]bgp.BGPAddPathMode { rfMap := make(map[bgp.RouteFamily]bgp.BGPAddPathMode) for _, af := range n.AfiSafis { mode := bgp.BGP_ADD_PATH_NONE if af.AddPaths.State.Receive { mode |= bgp.BGP_ADD_PATH_RECEIVE } if af.AddPaths.State.SendMax > 0 { mode |= bgp.BGP_ADD_PATH_SEND } rfMap[af.State.Family] = mode } return rfMap } func (n *Neighbor) GetAfiSafi(family bgp.RouteFamily) *AfiSafi { for _, a := range n.AfiSafis { if string(a.Config.AfiSafiName) == family.String() { return &a } } return nil } func (n *Neighbor) ExtractNeighborAddress() (string, error) { addr := n.State.NeighborAddress if addr == "" { addr = n.Config.NeighborAddress if addr == "" { return "", fmt.Errorf("NeighborAddress is not configured") } } return addr, nil } func (n *Neighbor) IsAddPathReceiveEnabled(family bgp.RouteFamily) bool { for _, af := range n.AfiSafis { if af.State.Family == family { return af.AddPaths.State.Receive } } return false } type AfiSafis []AfiSafi func (c AfiSafis) ToRfList() ([]bgp.RouteFamily, error) { rfs := make([]bgp.RouteFamily, 0, len(c)) for _, af := range c { rfs = append(rfs, af.State.Family) } return rfs, nil } func inSlice(n Neighbor, b []Neighbor) int { for i, nb := range b { if nb.State.NeighborAddress == n.State.NeighborAddress { return i } } return -1 } func existPeerGroup(n string, b []PeerGroup) int { for i, nb := range b { if nb.Config.PeerGroupName == n { return i } } return -1 } func CheckAfiSafisChange(x, y []AfiSafi) bool { if len(x) != len(y) { return true } m := make(map[string]bool) for _, e := range x { m[string(e.Config.AfiSafiName)] = true } for _, e := range y { if !m[string(e.Config.AfiSafiName)] { return true } } return false } func ParseMaskLength(prefix, mask string) (int, int, error) { _, ipNet, err := net.ParseCIDR(prefix) if err != nil { return 0, 0, fmt.Errorf("invalid prefix: %s", prefix) } if mask == "" { l, _ := ipNet.Mask.Size() return l, l, nil } exp := regexp.MustCompile("(\\d+)\\.\\.(\\d+)") elems := exp.FindStringSubmatch(mask) if len(elems) != 3 { return 0, 0, fmt.Errorf("invalid mask length range: %s", mask) } // we've already checked the range is sane by regexp min, _ := strconv.ParseUint(elems[1], 10, 8) max, _ := strconv.ParseUint(elems[2], 10, 8) if min > max { return 0, 0, fmt.Errorf("invalid mask length range: %s", mask) } if ipv4 := ipNet.IP.To4(); ipv4 != nil { f := func(i uint64) bool { return i <= 32 } if !f(min) || !f(max) { return 0, 0, fmt.Errorf("ipv4 mask length range outside scope :%s", mask) } } else { f := func(i uint64) bool { return i <= 128 } if !f(min) || !f(max) { return 0, 0, fmt.Errorf("ipv6 mask length range outside scope :%s", mask) } } return int(min), int(max), nil } gobgp-1.29/config/util_test.go000066400000000000000000000021121324612745600163610ustar00rootroot00000000000000// Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "github.com/stretchr/testify/assert" "testing" ) func TestDetectConfigFileType(t *testing.T) { assert := assert.New(t) assert.Equal("toml", detectConfigFileType("bgpd.conf", "toml")) assert.Equal("toml", detectConfigFileType("bgpd.toml", "xxx")) assert.Equal("yaml", detectConfigFileType("bgpd.yaml", "xxx")) assert.Equal("yaml", detectConfigFileType("bgpd.yml", "xxx")) assert.Equal("json", detectConfigFileType("bgpd.json", "xxx")) } gobgp-1.29/contrib/000077500000000000000000000000001324612745600142155ustar00rootroot00000000000000gobgp-1.29/contrib/ubuntu/000077500000000000000000000000001324612745600155375ustar00rootroot00000000000000gobgp-1.29/contrib/ubuntu/gobgpd.conf000066400000000000000000000012731324612745600176530ustar00rootroot00000000000000description "GoBGP BGP daemon" author "Pavel Odintsov " start on (filesystem and net-device-up IFACE=lo) stop on runlevel [!2345] # TODO: use path without version number env DAEMON=/usr/sbin/gobgpd env CONFIGURATION_FILE=/etc/gobgpd.conf env DAEMON_OPTIONS="--disable-stdlog --syslog yes" #expect fork #respawn #respawn limit 10 5 #oom never # Check configuration before start. You could check result in dmesg output: # gobgp pre-start process (12265) terminated with status 1 pre-start script $DAEMON --dry-run -f $CONFIGURATION_FILE if [ $? -ne 0 ]; then exit $? fi end script exec $DAEMON -f $CONFIGURATION_FILE $DAEMON_OPTIONS gobgp-1.29/docs/000077500000000000000000000000001324612745600135055ustar00rootroot00000000000000gobgp-1.29/docs/sources/000077500000000000000000000000001324612745600151705ustar00rootroot00000000000000gobgp-1.29/docs/sources/add-paths.md000066400000000000000000000117041324612745600173620ustar00rootroot00000000000000# Additional Paths This page explains how to configure BGP Additional Paths features on GoBGP. GoBGP supports to advertise ADD-PATH capability according to [RFC7911](https://tools.ietf.org/html/rfc7911) and advertise paths with the "Advertise N Paths" mode described in [draft-ietf-idr-add-paths-guidelines](https://tools.ietf.org/html/draft-ietf-idr-add-paths-guidelines). ## Prerequisites Assumed that you finished [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md). ## Contents - [Configuration](#section0) - [Verification](#section1) - [Example Topology and Configuration](#section1.1) - [Advertise Multiple Paths](#section1.2) ## Configuration In order to advertise multiple paths to the specific neighbors, you need to configure `[neighbors.add-paths.config]` section for each neighbor. In the following example, `send-max = 8` means GoBGP will advertise up to 8 paths per prefix towards this neighbor and `receive = true` enables to receive multiple paths from this neighbor. ```toml [[neighbors]] [neighbors.config] neighbor-address = "10.0.0.2" [neighbors.add-paths.config] send-max = 8 receive = true ``` Also, BGP Additional Paths features are configurable per AFI-SAFI and the per AFI-SAFI configuration overrides the per neighbor configuration. The following example enables BGP Additional Paths features for only IPv4 unicast family. ```toml [[neighbors]] [neighbors.config] neighbor-address = "10.0.0.2" [neighbors.add-paths.config] receive = false send-max = 0 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" [neighbors.afi-safis.add-paths.config] receive = true send-max = 8 ``` ## Verification ### Example Topology and Configuration To test BGP Additional Paths features, this page supposes the following topology. ``` +----------+ +----------+ +----------+ | r1 | | r2 | | r3 | | AS 65001 | ADD-PATH enabled | AS 65002 | | AS 65003 | | 10.0.0.1 |--------------------| 10.0.0.2 |----------| 10.0.0.3 | +----------+ +----------+ +----------+ | | | +----------+ | r4 | | AS 65004 | | 10.0.0.4 | +----------+ ``` Configuration on r1: ```toml [global.config] as = 65001 router-id = "10.0.0.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.0.2" peer-as = 65002 [neighbors.add-paths.config] receive = true send-max = 8 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" ``` Configuration on r2: ```toml [global.config] as = 65002 router-id = "10.0.0.2" [[neighbors]] [neighbors.config] neighbor-address = "10.0.0.1" peer-as = 65001 [neighbors.add-paths.config] receive = true send-max = 8 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" [[neighbors]] [neighbors.config] neighbor-address = "10.0.0.3" peer-as = 65003 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" [[neighbors]] [neighbors.config] neighbor-address = "10.0.0.4" peer-as = 65004 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" ``` ### Advertise Multiple Paths Start GoBGP on r1, r2, r3 and r4, and confirm the establishment of each BGP session. e.g.: ``` r1> gobgpd -f gobgpd.toml {"level":"info","msg":"gobgpd started","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Topic":"Config","level":"info","msg":"Finished reading the config file","time":""YYYY-MM-DDTHH:mm:ss+09:00"} {"level":"info","msg":"Peer 10.0.0.2 is added","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Topic":"Peer","level":"info","msg":"Add a peer configuration for:10.0.0.2","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Key":"10.0.0.2","State":"BGP_FSM_OPENCONFIRM","Topic":"Peer","level":"info","msg":"Peer Up","time":"YYYY-MM-DDTHH:mm:ss+09:00"} ``` Advertise a prefix "192.168.1.0/24" on r3 and r4. ``` r3> gobgp global rib -a ipv4 add 192.168.1.0/24 ``` ``` r4> gobgp global rib -a ipv4 add 192.168.1.0/24 ``` Then confirm 2 paths (from r3 and r4) are advertised to r1 from r2. In the following output shows the path with AS_PATH 65002 65003 (r3->r2->r1) and the path with AS_PATH 65002 65004 (r4->r2->r1). ``` r1> gobgp global rib -a ipv4 Network Next Hop AS_PATH Age Attrs *> 192.168.1.0/24 10.0.0.2 65002 65003 HH:mm:ss [{Origin: ?}] * 192.168.1.0/24 10.0.0.2 65002 65004 HH:mm:ss [{Origin: ?}] ``` gobgp-1.29/docs/sources/bmp.md000066400000000000000000000046301324612745600162730ustar00rootroot00000000000000# BGP Monitoring Protocol GoBGP supports [BGP Monitoring Protocol (RFC 7854)](https://tools.ietf.org/html/rfc7854), which provides a convenient interface for obtaining route views. ## Prerequisites Assume you finished [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md). ## Contents - [Configuration](#config) - [Verification](#verify) ## Configuration Add `[bmp-servers]` session to enable BMP. ```toml [global.config] as = 64512 router-id = "192.168.255.1" [[bmp-servers]] [bmp-servers.config] address = "127.0.0.1" port=11019 ``` The supported route monitoring policy types are: - pre-policy (Default) - post-policy - both (Obsoleted) - local-rib - all Enable post-policy support as follows: ```toml [[bmp-servers]] [bmp-servers.config] address = "127.0.0.1" port=11019 route-monitoring-policy = "post-policy" ``` Enable all policies support as follows: ```toml [[bmp-servers]] [bmp-servers.config] address = "127.0.0.1" port=11019 route-monitoring-policy = "all" ``` To enable BMP stats reports, specify the interval seconds to send statistics messages. The default value is 0 and no statistics messages are sent. Please note the range of this interval is 15 though 65535 seconds. ```toml [[bmp-servers]] [bmp-servers.config] address = "127.0.0.1" port=11019 statistics-timeout = 3600 ``` To enable route mirroring feature, specify `true` for `route-mirroring-enabled` option. Please note this option is mainly for debugging purpose. ```toml [[bmp-servers]] [bmp-servers.config] address = "127.0.0.1" port=11019 route-mirroring-enabled = true ``` ## Verification Let's check if BMP works with a bmp server. GoBGP also supports BMP server (currently, just shows received BMP messages in the json format). ```bash $ go get github.com/osrg/gobgp/gobmpd $ gobmpd ``` Once the BMP server accepts a connection from gobgpd, then you see below on the BMP server side. ```bash INFO[0013] Accepted a new connection from 127.0.0.1:33685 {"Header":{"Version":3,"Length":6,"Type":4},"PeerHeader":{"PeerType":0,"IsPostPolicy":false,"PeerDistinguisher":0,"PeerAddress":"","PeerAS":0,"PeerBGPID":"","Timestamp":0},"Body":{"Info":null}} ``` You also see below on the BGP server side: ```bash {"level":"info","msg":"bmp server is connected, 127.0.0.1:11019","time":"2015-09-15T10:29:03+09:00"} ``` gobgp-1.29/docs/sources/cli-command-syntax.md000066400000000000000000000461651324612745600212350ustar00rootroot00000000000000# CLI command syntax This page explains gobgp client command syntax. ## basic command pattern gobgp \ \ opts... gobgp has six subcommands. - [global](#global) - [neighbor](#neighbor) - [policy](#policy) - [vrf](#vrf) - [monitor](#monitor) - [mrt](#mrt) ## 1. global subcommand ### 1.1 Global Configuration #### syntax ```shell # configure global setting and start acting as bgp daemon % gobgp global as router-id [listen-port ] [listen-addresses ...] [mpls-label-min ] [mpls-label-max ] # delete global setting and stop acting as bgp daemon (all peer sessions will be closed) % gobgp global del all # show global setting % gobgp global ``` ### 1.2. Operations for Global-Rib - add/del/show - #### - syntax ```shell # add Route % gobgp global rib add [-a
] # delete a specific Route % gobgp global rib del [-a
] # delete all locally generated routes % gobgp global rib del all [-a
] # show all Route information % gobgp global rib [-a
] # show a specific route information % gobgp global rib [|] [longer-prefixes|shorter-prefixes] [-a
] # show table summary % gobgp global rib summary [-a
] ``` #### - example If you want to add routes with the address of the ipv4 to global rib: ```shell % gobgp global rib add 10.33.0.0/16 -a ipv4 ``` If you want to remove routes with the address of the ipv6 from global rib: ```shell % gobgp global rib del 2001:123:123:1::/64 -a ipv6 ``` #### more examples ```shell % gobgp global rib add -a ipv4 10.0.0.0/24 origin igp % gobgp global rib add -a ipv4 10.0.0.0/24 origin egp % gobgp global rib add -a ipv4 10.0.0.0/24 aspath 10,20,100.100 % gobgp global rib add -a ipv4 10.0.0.0/24 aspath "10 20 {30,40} 50" % gobgp global rib add -a ipv4 10.0.0.0/24 nexthop 20.20.20.20 % gobgp global rib add -a ipv4 10.0.0.0/24 med 10 % gobgp global rib add -a ipv4 10.0.0.0/24 local-pref 110 % gobgp global rib add -a ipv4 10.0.0.0/24 community 100:100 % gobgp global rib add -a ipv4 10.0.0.0/24 community 100:100,200:200 % gobgp global rib add -a ipv4 10.0.0.0/24 community no-export % gobgp global rib add -a ipv4 10.0.0.0/24 community blackhole % gobgp global rib add -a ipv4 10.0.0.0/24 aigp metric 200 % gobgp global rib add -a ipv4 10.0.0.0/24 large-community 100:100:100 % gobgp global rib add -a ipv4 10.0.0.0/24 large-community 100:100:100,200:200:200 % gobgp global rib add -a ipv4 10.0.0.0/24 identifier 10 % gobgp global rib add -a ipv4-mpls 10.0.0.0/24 100 % gobgp global rib add -a ipv4-mpls 10.0.0.0/24 100/200 % gobgp global rib add -a ipv4-mpls 10.0.0.0/24 100 nexthop 20.20.20.20 % gobgp global rib add -a ipv4-mpls 10.0.0.0/24 100 med 10 % gobgp global rib add -a vpnv4 10.0.0.0/24 label 10 rd 100:100 % gobgp global rib add -a vpnv4 10.0.0.0/24 label 10 rd 100.100:100 % gobgp global rib add -a vpnv4 10.0.0.0/24 label 10 rd 10.10.10.10:100 % gobgp global rib add -a vpnv4 10.0.0.0/24 label 10 rd 100:100 rt 100:200 % gobgp global rib add -a opaque key hello value world ``` #### - option The following options can be specified in the global subcommand: | short |long | description | default | |--------|---------------|--------------------------------------------|---------| |a |address-family |specify any one from among `ipv4`, `ipv6`, `vpnv4`, `vpnv6`, `ipv4-labeled`, `ipv6-labeled`, `evpn`, `encap`, `rtc`, `ipv4-flowspec`, `ipv6-flowspec`, `l2vpn-flowspec`, `opaque` | `ipv4` | Also, refer to the following for the detail syntax of each address family. - `evpn` address family: [CLI Syntax for EVPN](evpn.md#cli-syntax) - `*-flowspec` address family: [CLI Syntax for Flow Specification](flowspec.md#cli-syntax) ## 2. neighbor subcommand ### 2.1. Show Neighbor Status #### - syntax ```shell # show neighbor's status as list % gobgp neighbor # show status of a specific neighbor % gobgp neighbor ``` ### 2.2. Operations for neighbor - shutdown/reset/softreset/enable/disable - #### - syntax ```shell # add neighbor % gobgp neighbor add { | interface } as [ vrf | route-reflector-client [] | route-server-client | allow-own-as | remove-private-as (all|replace) | replace-peer-as ] # delete neighbor % gobgp neighbor delete { | interface } % gobgp neighbor softreset [-a
] % gobgp neighbor softresetin [-a
] % gobgp neighbor softresetout [-a
] % gobgp neighbor enable % gobgp neighbor disable % gobgp neighbor reset ``` #### - option The following options can be specified in the neighbor subcommand: | short |long | description | default | |--------|---------------|--------------------------------------------|---------| |a |address-family |specify any one from among `ipv4`, `ipv6`, `vpnv4`, `vpnv6`, `ipv4-labeled`, `ipv6-labeld`, `evpn`, `encap`, `rtc`, `ipv4-flowspec`, `ipv6-flowspec`, `l2vpn-flowspec`, `opaque` | `ipv4` | ### 2.3. Show Rib - local-rib/adj-rib-in/adj-rib-out - #### - syntax ```shell # show all routes in [local|adj-in|adj-out] table % gobgp neighbor [local|adj-in|adj-out] [-a
] # show a specific route in [local|adj-in|adj-out] table % gobgp neighbor [local|adj-in|adj-out] [|] [longer-prefixes|shorter-prefixes] [-a
] # show table summary % gobgp neighbor [local|adj-in|adj-out] summary [-a
] # show RPKI detailed information in adj-in table % gobgp neighbor adj-in validation ``` #### - example If you want to show the local rib of ipv4 that neighbor(10.0.0.1) has: ```shell % gobgp neighbor 10.0.0.1 local -a ipv4 ``` #### - option The following options can be specified in the neighbor subcommand: | short |long | description | default | |--------|---------------|--------------------------------------------|---------| |a |address-family |specify any one from among `ipv4`, `ipv6`, `vpnv4`, `vpnv6`, `ipv4-labeled`, `ipv6-labeld`, `evpn`, `encap`, `rtc`, `ipv4-flowspec`, `ipv6-flowspec`, `l2vpn-flowspec`, `opaque` | `ipv4` | ### 2.4. Operations for Policy - add/del/show - #### Syntax ```shell # show neighbor policy assignment % gobgp neighbor policy { in | import | export } # add policies to specific neighbor policy % gobgp neighbor policy { in | import | export } add ... [default { accept | reject }] # set policies to specific neighbor policy % gobgp neighbor policy { in | import | export } set ... [default { accept | reject }] # remove attached policies from specific neighbor policy % gobgp neighbor policy { in | import | export } del ... # remove all policies from specific neighbor policy % gobgp neighbor policy { in | import | export } del ``` #### Example If you want to add the import policy to neighbor(10.0.0.1): ```shell % gobgp neighbor 10.0.0.1 policy import add policy1 policy2 default accept ``` You can specify multiple policy to neighbor separated by commas. \ means the operation(accept | reject) in the case where the route does not match the conditions of the policy.
## 3.
policy subcommand ### 3.1. Operations for PrefixSet - add/del/show - #### Syntax ```shell # add PrefixSet % gobgp policy prefix add [] # delete a PrefixSet % gobgp policy prefix del # delete a prefix from specific PrefixSet % gobgp policy prefix del [] # show all PrefixSet information % gobgp policy prefix # show a specific PrefixSet % gobgp policy prefix ``` #### Example If you want to add the PrefixSet: ```shell % gobgp policy prefix add ps1 10.33.0.0/16 16..24 ``` A PrefixSet it is possible to have multiple prefix, if you want to remove the PrefixSet to specify only PrefixSet name. ```shell % gobgp policy prefix del ps1 ``` If you want to remove one element(prefix) of PrefixSet, to specify a prefix in addition to the PrefixSet name. ```shell % gobgp policy prefix del ps1 10.33.0.0/16 ``` ### 3.2. Operations for NeighborSet - add/del/show - #### Syntax ```shell # add NeighborSet % gobgp policy neighbor add # delete a NeighborSet % gobgp policy neighbor del # delete a neighbor from a NeighborSet % gobgp policy neighbor del
# show all NeighborSet information % gobgp policy neighbor # show a specific NeighborSet information % gobgp policy neighbor ``` #### Example If you want to add the NeighborSet: ```shell % gobgp policy neighbor add ns1 10.0.0.1 ``` You can also specify a neighbor address range with the prefix representation: ```shell % gobgp policy neighbor add ns 10.0.0.0/24 `````` A NeighborSet is possible to have multiple address, if you want to remove the NeighborSet to specify only NeighborSet name. ```shell % gobgp policy neighbor del ns1 ``` If you want to remove one element(address) of NeighborSet, to specify a address in addition to the NeighborSet name. ```shell % gobgp policy prefix del ns1 10.0.0.1 ``` ### 3.3. Operations for AsPathSet - add/del/show - #### Syntax ```shell # add AsPathSet % gobgp policy as-path add # delete a specific AsPathSet % gobgp policy as-path del # delete an as-path from a AsPathSet % gobgp policy as-path del # show all AsPathSet information % gobgp policy as-path # show a specific AsPathSet information % gobgp policy as-path ``` #### Example If you want to add the AsPathSet: ```shell % gobgp policy as-path add ass1 ^65100 ``` You can specify the position using regexp-like expression as follows: - From: "^65100" means the route is passed from AS 65100 directly. - Any: "65100" means the route comes through AS 65100. - Origin: "65100$" means the route is originated by AS 65100. - Only: "^65100$" means the route is originated by AS 65100 and comes from it directly. Further you can specify the consecutive aspath and use regexp in each element as follows: - ^65100_65001 - 65100_[0-9]+_.*$ - ^6[0-9]_5.*_65.?00$ An AsPathSet it is possible to have multiple as path, if you want to remove the AsPathSet to specify only AsPathSet name. ```shell % gobgp policy as-path del ass1 ``` If you want to remove one element(as path) of AsPathSet, to specify an as path in addition to the AsPathSet name. ```shell % gobgp policy as-path del ass1 ^65100 ``` ### 3.4. Operations for CommunitySet - add/del/show - #### Syntax ```shell # add CommunitySet % gobgp policy community add # delete a specific CommunitySet % gobgp policy community del # delete a community from a CommunitySet % gobgp policy community del # show all CommunitySet information % gobgp policy community # show a specific CommunitySet information % gobgp policy community ``` #### Example If you want to add the CommunitySet: ```shell % gobgp policy community add cs1 65100:10 ``` You can specify the position using regexp-like expression as follows: - 6[0-9]+:[0-9]+ - ^[0-9]*:300$ A CommunitySet it is possible to have multiple community, if you want to remove the CommunitySet to specify only CommunitySet name. ```shell % gobgp policy neighbor del cs1 ``` If you want to remove one element(community) of CommunitySet, to specify a address in addition to the CommunitySet name. ```shell % gobgp policy prefix del cs1 65100:10 ``` ### 3.5. Operations for ExtCommunitySet - add/del/show - #### Syntax ```shell # add ExtCommunitySet % gobgp policy ext-community add # delete a specific ExtCommunitySet % gobgp policy ext-community del # delete a ext-community from a ExtCommunitySet % gobgp policy ext-community del # show all ExtCommunitySet information % gobgp policy ext-community # show a specific ExtCommunitySet information % gobgp policy ext-community ``` #### Example If you want to add the ExtCommunitySet: ```shell % gobgp policy ext-community add ecs1 RT:65100:10 ``` Extended community set as \:\:\. If you read the [RFC4360](https://tools.ietf.org/html/rfc4360) and [RFC7153](https://tools.ietf.org/html/rfc7153), you can know more about Extended community. You can specify the position using regexp-like expression as follows: - RT:[0-9]+:[0-9]+ - SoO:10.0.10.10:[0-9]+ However, regular expressions for subtype can not be used, to use for the global admin and local admin. A ExtCommunitySet it is possible to have multiple extended community, if you want to remove the ExtCommunitySet to specify only ExtCommunitySet name. ```shell % gobgp policy neighbor del ecs1 ``` If you want to remove one element(extended community) of ExtCommunitySet, to specify a address in addition to the ExtCommunitySet name. ```shell % gobgp policy prefix del ecs1 RT:65100:10 ``` ### 3.6. Operations for LargeCommunitySet - add/del/show - #### Syntax ```shell # add LargeCommunitySet % gobgp policy large-community add ... # delete a specific LargeCommunitySet % gobgp policy large-community del # delete a large-community from a LargeCommunitySet % gobgp policy large-community del # show all LargeCommunitySet information % gobgp policy large-community # show a specific LargeCommunitySet information % gobgp policy large-community ``` #### Example ```shell % gobgp policy large-community add l0 100:100:100 % gobgp policy large-community add l0 ^100: % gobgp policy large-community add l0 :100$ % gobgp policy large-community del l0 100:100:100 % gobgp policy large-community add l0 200:100:100 % gobgp policy large-community % gobgp policy large-community set l0 100:100:100 200:200:200 300:300:300 ``` ### 3.7 Statement Operation - add/del/show - #### Syntax ```shell # mod statement % gobgp policy statement { add | del } # mod a condition to a statement % gobgp policy statement { add | del | set } condition { { prefix | neighbor | as-path | community | ext-community | large-community } [{ any | all | invert }] | as-path-length { eq | ge | le } | rpki { valid | invalid | not-found } } # mod an action to a statement % gobgp policy statement { add | del | set } action { reject | accept | { community | ext-community | large-community } { add | remove | replace } ... | med { add | sub | set } | local-pref | as-prepend { | last-as } } # show all statements % gobgp policy statement # show a specific statement % gobgp policy statement ``` ### 3.8 Policy Operation - add/del/show - #### Syntax ```shell # mod policy % gobgp policy { add | del | set } [...] # show all policies % gobgp policy # show a specific policy % gobgp policy ``` ## 4. vrf subcommand ### 4.1 Add/Delete/Show VRF #### Syntax ```shell # add vrf % gobgp vrf add rd rt {import|export|both} ... # del vrf % gobgp vrf del # show vrf % gobgp vrf ``` #### Example ```shell % gobgp vrf add vrf1 rd 10.100:100 rt both 10.100:100 import 10.100:101 export 10.100:102 % gobgp vrf Name RD Import RT Export RT vrf1 10.100:100 10.100:100, 10.100:101 10.100:100, 10.100:102 % gobgp vrf del vrf1 % gobgp vrf Name RD Import RT Export RT ``` ### 4.2 Add/Delete/Show VRF routes #### Syntax ```shell # add routes to vrf % gobgp vrf rib add [-a
] # del routes from vrf % gobgp vrf rib del [-a
] # show routes in vrf % gobgp vrf rib [-a
] ``` #### Example ```shell % gobgp vrf vrf1 rib add 10.0.0.0/24 % gobgp vrf vrf1 rib add 2001::/64 -a ipv6 % gobgp vrf vrf1 rib Network Next Hop AS_PATH Age Attrs 10.100:100:10.0.0.0/24 0.0.0.0 00:00:40 [{Origin: i} {Extcomms: [10.100:100], [10.100:101]}] % gobgp vrf vrf1 rib -a ipv6 Network Next Hop AS_PATH Age Attrs 10.100:100:2001::/64 :: 00:00:00 [{Origin: i} {Extcomms: [10.100:100], [10.100:101]}] % gobgp vrf vrf1 rib del 10.0.0.0/24 % gobgp vrf vrf1 rib del 2001::/64 ``` ## 5. monitor subcommand ### 5.1 monitor global rib #### Syntax ```shell # monitor global rib % gobgp monitor global rib [-a
] [--current] ``` #### Example ```shell [TERM1] % gobgp monitor global rib [ROUTE] 10.0.0.0/24 via 0.0.0.0 aspath [] attrs [{Origin: i}] [TERM2] # monitor command blocks. add routes from another terminal % gobgp global rib add 10.0.0.0/24 ``` ### 5.2 monitor neighbor status #### Syntax ```shell # monitor neighbor status % gobgp monitor neighbor [--current] # monitor specific neighbor status % gobgp monitor neighbor [--current] ``` #### Example ```shell [TERM1] % gobgp monitor neighbor [NEIGH] 192.168.10.2 fsm: BGP_FSM_IDLE admin: down [NEIGH] 192.168.10.2 fsm: BGP_FSM_ACTIVE admin: up [NEIGH] 192.168.10.2 fsm: BGP_FSM_OPENSENT admin: up [NEIGH] 192.168.10.2 fsm: BGP_FSM_OPENCONFIRM admin: up [NEIGH] 192.168.10.2 fsm: BGP_FSM_ESTABLISHED admin: up [TERM2] % gobgp neighbor 192.168.10.2 disable % gobgp neighbor 192.168.10.2 enable ``` ### 5.3 monitor Adj-RIB-In #### Syntax ```shell # monitor Adj-RIB-In % gobgp monitor adj-in [-a
] [--current] # monitor Adj-RIB-In for specific neighbor % gobgp monitor adj-in [-a
] [--current] ``` #### Example ```shell [GoBGP1] % gobgp monitor adj-in [ROUTE] 0:10.2.1.0/24 via 10.0.0.2 aspath [65002] attrs [{Origin: ?}] [DELROUTE] 0:10.2.1.0/24 via aspath [] attrs [] [GoBGP2] % gobgp global rib -a ipv4 add 10.2.1.0/24 % gobgp global rib -a ipv4 del 10.2.1.0/24 ``` ## 6. mrt subcommand ### 6.1 dump mrt records #### Syntax ```shell % gobgp mrt dump rib global [] % gobgp mrt dump rib neighbor [] ``` #### Options | short |long | description | |--------|--------|--------------------------------| | f | format | filename format | | o | outdir | output directory of dump files | #### Example see [MRT](https://github.com/osrg/gobgp/blob/master/docs/sources/mrt.md). ### 6.2 inject mrt records #### Syntax ```shell % gobgp mrt inject global [] ``` #### Example see [MRT](https://github.com/osrg/gobgp/blob/master/docs/sources/mrt.md). gobgp-1.29/docs/sources/cli-operations.md000066400000000000000000000070631324612745600204500ustar00rootroot00000000000000# CLI Operations This page explains comprehensive examples of operations via GoBGP CLI. ## Prerequisites Assumed that you finished [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md). ## Configuration This example starts with the same configuration with [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md) Make sure that all the peers are connected. ```bash $ gobgp neighbor Peer AS Up/Down State |#Advertised Received Accepted 10.0.255.1 65001 00:00:04 Establ | 2 2 2 10.0.255.2 65002 00:00:04 Establ | 2 2 2 ``` ## Adding or deleting a peer dynamically You can add a new peer or delete the existing peer without stopping GoBGP daemon. You can do such by adding a new peer configuration or deleting the existing configuration of a peer in your configuration file and sending `HUP` signal to GoBGP daemon. In this example, 10.0.255.3 peer is added. The configuration file should be like the following. ```toml [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" peer-as = 65001 [neighbors.route-server.config] route-server-client = true [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.2" peer-as = 65002 [neighbors.route-server.config] route-server-client = true [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.3" peer-as = 65003 [neighbors.route-server.config] route-server-client = true ``` After you send `HUP` signal (`kill` command), you should see 10.0.255.3 peer. ```bash $ gobgp neighbor Peer AS Up/Down State |#Advertised Received Accepted 10.0.255.1 65001 00:03:42 Establ | 3 2 2 10.0.255.2 65002 00:03:42 Establ | 3 2 2 10.0.255.3 65003 00:01:39 Establ | 4 1 1 ``` ## Temporarily disable a configured peer Sometime you might want to disable the configured peer without removing the configuration for the peer. Likely, again you enable the peer later. ```bash $ gobgp neighbor 10.0.255.1 disable $ gobgp neighbor Peer AS Up/Down State |#Advertised Received Accepted 10.0.255.1 65001 never Idle(Admin) | 0 0 0 10.0.255.2 65002 00:12:32 Establ | 1 2 2 10.0.255.3 65003 00:10:29 Establ | 2 1 1 ``` The state of 10.0.255.1 is `Idle(Admin)`. Let's enable the peer again. ```bash $ gobgp neighbor 10.0.255.1 enable $ gobgp neighbor Peer AS Up/Down State |#Advertised Received Accepted 10.0.255.1 65001 never Idle | 0 0 0 10.0.255.2 65002 00:13:33 Establ | 1 2 2 10.0.255.3 65003 00:11:30 Establ | 2 1 1 ``` Eventually, the state should be `Established` again. ```bash $ gobgp neighbor Peer AS Up/Down State |#Advertised Received Accepted 10.0.255.1 65001 00:00:02 Establ | 3 2 2 10.0.255.2 65002 00:14:59 Establ | 3 2 2 10.0.255.3 65003 00:12:56 Establ | 4 1 1 ``` ## Reset, Reset, and Reset Various reset operations are supported. ```bash $ gobgp neighbor 10.0.255.1 reset $ gobgp neighbor 10.0.255.1 softreset $ gobgp neighbor 10.0.255.1 softresetin $ gobgp neighbor 10.0.255.1 softresetout ``` You can know more about gobgp command syntax [here](https://github.com/osrg/gobgp/blob/master/docs/sources/cli-command-syntax.md). gobgp-1.29/docs/sources/configuration.md000066400000000000000000000267761324612745600204030ustar00rootroot00000000000000# Configuration example ```toml [global.config] as = 1 router-id = "1.1.1.1" # listen port (by default 179) port = 1790 # to disable listening # port = -1 # listen address list (by default "0.0.0.0" and "::") local-address-list = ["192.168.10.1", "2001:db8::1"] [global.apply-policy.config] import-policy-list = ["policy1"] default-import-policy = "reject-route" export-policy-list = ["policy2"] default-export-policy = "accept-route" [[rpki-servers]] [rpki-servers.config] address = "210.173.170.254" port = 323 [[bmp-servers]] [bmp-servers.config] address = "127.0.0.1" port = 11019 route-monitoring-policy = "pre-policy" statistics-timeout = 3600 [[mrt-dump]] [mrt-dump.config] dump-type = "updates" file-name = "/tmp/log/2006/01/02.1504.dump" dump-interval = 180 [zebra] [zebra.config] enabled = true url = "unix:/var/run/quagga/zserv.api" redistribute-route-type-list = ["connect"] version = 2 # version used in Quagga on Ubuntu 16.04 [[neighbors]] [neighbors.config] peer-as = 2 auth-password = "password" neighbor-address = "192.168.10.2" # override global.config.as value local-as = 1000 remove-private-as = "all" # To enable peer group setting, uncomment the following #peer-group = "my-peer-group" [neighbors.as-path-options.config] allow-own-as = 1 replace-peer-as = true [neighbors.timers.config] connect-retry = 5 hold-time = 9 keepalive-interval = 3 [neighbors.transport.config] passive-mode = true local-address = "192.168.10.1" remote-port = 2016 ttl = 64 # default value on Linux [neighbors.ebgp-multihop.config] enabled = true multihop-ttl = 100 [neighbors.route-reflector.config] route-reflector-client = true route-reflector-cluster-id = "192.168.0.1" [neighbors.add-paths.config] send-max = 8 receive = true [neighbors.graceful-restart.config] enabled = true notification-enabled = true long-lived-enabled = true # graceful restart restart time restart-time = 20 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" [neighbors.afi-safis.prefix-limit.config] max-prefixes = 1000 shutdown-threshold-pct = 80 [neighbors.afi-safis.mp-graceful-restart.config] enabled = true [neighbors.afi-safis.long-lived-graceful-restart.config] enabled = true # long lived graceful restart restart time restart-time = 100000 [neighbors.afi-safis.add-paths.config] # override neighbors.add-paths.config receive = true send-max = 8 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv6-unicast" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-labelled-unicast" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv6-labelled-unicast" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "l3vpn-ipv4-unicast" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "l3vpn-ipv6-unicast" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "l2vpn-evpn" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "rtc" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-encap" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv6-encap" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-flowspec" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv6-flowspec" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "opaque" [neighbors.apply-policy.config] import-policy-list = ["policy1"] default-import-policy = "reject-route" export-policy-list = ["policy2"] default-export-policy = "accept-route" in-policy-list = ["policy3"] default-in-policy = "reject-route" [neighbors.route-server.config] route-server-client = true # To enable TTL Security, uncomment the following. # Please note that this feature is mututally exclusive with # "neighbors.ebgp-multihop.config". #[neighbors.ttl-security.config] # enabled = true # ttl-min = 255 # 255 means directly connected [[peer-groups]] [peer-groups.config] peer-group-name = "my-peer-group" peer-as = 65000 [[peer-groups.afi-safis]] [peer-groups.afi-safis.config] afi-safi-name = "ipv4-unicast" [[dynamic-neighbors]] [dynamic-neighbors.config] prefix = "20.0.0.0/24" peer-group = "my-peer-group" [[defined-sets.prefix-sets]] prefix-set-name = "ps0" [[defined-sets.prefix-sets.prefix-list]] ip-prefix = "10.0.0.0/8" masklength-range = "24..32" [[defined-sets.neighbor-sets]] neighbor-set-name = "ns0" neighbor-info-list = ["192.168.10.2", "172.13.0.0/24"] [[defined-sets.bgp-defined-sets.community-sets]] community-set-name = "cs0" community-list = ["100:100"] [[defined-sets.bgp-defined-sets.ext-community-sets]] ext-community-set-name = "es0" ext-community-list = ["rt:100:100", "soo:200:200"] [[defined-sets.bgp-defined-sets.as-path-sets]] as-path-set-name = "as0" as-path-list = ["^100", "200$"] [[defined-sets.bgp-defined-sets.large-community-sets]] large-community-set-name = "ls0" large-community-list = ["100:100:100", "200:200:200"] [[policy-definitions]] name = "policy1" [[policy-definitions.statements]] [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps0" match-set-options = "any" [policy-definitions.statements.conditions.match-neighbor-set] neighbor-set = "ns0" match-set-options = "invert" [policy-definitions.statements.conditions.bgp-conditions.match-community-set] community-set = "cs0" match-set-options = "all" [policy-definitions.statements.conditions.bgp-conditions.match-large-community-set] large-community-set = "ls0" match-set-options = "all" [policy-definitions.statements.actions.bgp-actions.set-as-path-prepend] as = "last-as" repeat-n = 5 [policy-definitions.statements.actions] route-disposition = "accept-route" [[policy-definitions.statements]] [policy-definitions.statements.conditions.bgp-conditions.match-ext-community-set] ext-community-set = "es0" [policy-definitions.statements.actions] route-disposition = "reject-route" [[policy-definitions]] name = "policy2" [[policy-definitions.statements]] [policy-definitions.statements.conditions.bgp-conditions.match-as-path-set] as-path-set = "as0" [policy-definitions.statements.actions] route-disposition = "accept-route" [policy-definitions.statements.actions.bgp-actions.set-community] options = "add" [policy-definitions.statements.actions.bgp-actions.set-community.set-community-method] communities-list = ["100:200"] [[policy-definitions]] name = "policy3" [[policy-definitions.statements]] [policy-definitions.statements.conditions.bgp-conditions.match-as-path-set] as-path-set = "as0" [policy-definitions.statements.actions] route-disposition = "accept-route" [policy-definitions.statements.actions.bgp-actions.set-community] options = "add" [policy-definitions.statements.actions.bgp-actions.set-community.set-community-method] communities-list = ["100:200"] [[policy-definitions.statements]] [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps0" match-set-options = "invert" [policy-definitions.statements.actions] route-disposition = "accept-route" [policy-definitions.statements.actions.bgp-actions.set-ext-community] options = "replace" [policy-definitions.statements.actions.bgp-actions.set-ext-community.set-ext-community-method] communities-list = ["soo:100:200", "rt:300:400"] [[policy-definitions.statements]] [policy-definitions.statements.conditions.match-neighbor-set] neighbor-set = "ns0" [policy-definitions.statements.actions] route-disposition = "accept-route" [policy-definitions.statements.actions.bgp-actions.set-ext-community] options = "remove" [policy-definitions.statements.actions.bgp-actions.set-ext-community.set-ext-community-method] communities-list = ["soo:500:600", "rt:700:800"] [[policy-definitions]] name = "route-type-policy" [[policy-definitions.statements]] # this statement matches with locally generated routes [policy-definitions.statements.conditions.bgp-conditions] route-type = "local" [policy-definitions.statements.actions] route-disposition = "accept-route" [[policy-definitions.statements]] # this statement matches with routes from iBGP peers [policy-definitions.statements.conditions.bgp-conditions] route-type = "internal" [policy-definitions.statements.actions] route-disposition = "accept-route" [[policy-definitions.statements]] # this statement matches with routes from eBGP peers [policy-definitions.statements.conditions.bgp-conditions] route-type = "external" [policy-definitions.statements.actions] route-disposition = "accept-route" [[policy-definitions]] name = "large-communty-policy" [[policy-definitions.statements]] # this statement adds specified large communities [policy-definitions.statements.actions] route-disposition = "accept-route" [policy-definitions.statements.actions.bgp-actions.set-large-community] options = "add" [policy-definitions.statements.actions.bgp-actions.set-large-community.set-large-community-method] communities-list = ["100:200:300"] [[policy-definitions.statements]] # this statement adds specified large communities [policy-definitions.statements.actions] route-disposition = "accept-route" [policy-definitions.statements.actions.bgp-actions.set-large-community] options = "replace" [policy-definitions.statements.actions.bgp-actions.set-large-community.set-large-community-method] communities-list = ["100:200:300"] [[policy-definitions.statements]] # this statement removes specified large communities # regular expression is also supported [policy-definitions.statements.actions] route-disposition = "accept-route" [policy-definitions.statements.actions.bgp-actions.set-large-community] options = "remove" [policy-definitions.statements.actions.bgp-actions.set-large-community.set-large-community-method] communities-list = ["100:200:300", "^200:"] ``` gobgp-1.29/docs/sources/dynamic-neighbor.md000066400000000000000000000054251324612745600207370ustar00rootroot00000000000000# Dynamic Neighbor This page explains how to configure the Dynamic Neighbor features. Dynamic Neighbor enables GoBGP to accept connections from the peers in specific prefix. ## Contents - [Prerequisite](#prerequisite) - [Configuration](#configuration) - [Verification](#verification) ## Prerequisite Assumed that you finished [Getting Started](getting-started.md) and learned [Peer Group](peer-group.md). ## Configuration The Dynamic Neighbor feature requires a peer group setting for its configuration. ```toml [global.config] as = 65001 router-id = "172.40.1.2" [[peer-groups]] [peer-groups.config] peer-group-name = "sample-group" peer-as = 65002 [[peer-groups.afi-safis]] [peer-groups.afi-safis.config] afi-safi-name = "ipv4-unicast" [[peer-groups.afi-safis]] [peer-groups.afi-safis.config] afi-safi-name = "ipv4-flowspec" [[dynamic-neighbors]] [dynamic-neighbors.config] prefix = "172.40.0.0/16" peer-group = "sample-group" ``` By this configuration, peers in `172.40.0.0/16` will be accepted by this GoBGP, and the `sample-group` configuration is used as the configuration of members of this dynamic neighbor. Note that GoBGP will be passive mode to members of dynamic neighbors. So if both peers listen to each other as dynamic neighbors, the connection will never be established. # Verification Dynamic neighbors are not shown by `gobgp neighbor` command until the connection is established. ```shell $ gobgp neighbor Peer AS Up/Down State |#Received Accepted ``` After the connection is established, the neighbor will appear by `gobgp neighbor` command. You can see the neighbor config is inherited from the peer group config. ```shell $ gobgp neighbor Peer AS Up/Down State |#Received Accepted 172.40.1.3 65001 00:00:23 Establ | 0 0 $ gobgp neighbor 172.40.1.3 BGP neighbor is 172.40.1.3, remote AS 65002 BGP version 4, remote router ID 172.40.1.3 BGP state = established, up for 00:00:07 BGP OutQ = 0, Flops = 0 Hold time is 90, keepalive interval is 30 seconds Configured hold time is 90, keepalive interval is 30 seconds Neighbor capabilities: multiprotocol: ipv4-unicast: advertised and received ipv4-flowspec: advertised and received route-refresh: advertised and received 4-octet-as: advertised and received Message statistics: Sent Rcvd Opens: 1 1 Notifications: 0 0 Updates: 0 0 Keepalives: 1 1 Route Refresh: 0 0 Discarded: 0 0 Total: 2 2 Route statistics: Advertised: 0 Received: 0 Accepted: 0 ```gobgp-1.29/docs/sources/ebgp-multihop.md000066400000000000000000000140311324612745600202650ustar00rootroot00000000000000# eBGP Multihop This page explains how to configure eBGP multihop feature when external BGP (eBGP) peers are not directly connected and multiple IP hops away. ## Prerequisites Assume you finished [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md). ## Contents - [Configuration](#section0) - [Verification](#section1) ## Configuration If eBGP neighbor "10.0.0.2" is 2 hops away, you need to configure `[neighbors.ebgp-multihop.config]` with `multihop-ttl >= 3` in `[[neighbors]]` section. ```toml [global.config] as = 65001 router-id = "10.0.0.1" [[neighbors]] [neighbors.config] peer-as = 65002 neighbor-address = "10.0.0.2" [neighbors.ebgp-multihop.config] enabled = true multihop-ttl = 3 ``` **NOTE:** eBGP Multihop feature is mututally exclusive with [TTL Security](https://github.com/osrg/gobgp/blob/master/docs/sources/ttl-security.md). These features cannot be configured for the same neighbor. ## Verification Without eBGP multihop configuration, the default TTL for eBGP session is 1, and GoBGP cannot reach the neighbor on 2 hops away. ``` $ gobgpd -f gobgpd.toml {"level":"info","msg":"gobgpd started","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Topic":"Config","level":"info","msg":"Finished reading the config file","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"level":"info","msg":"Peer 10.0.0.2 is added","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Topic":"Peer","level":"info","msg":"Add a peer configuration for:10.0.0.2","time":"YYYY-MM-DDTHH:mm:ss+09:00"} ...(No connection)... ``` ``` $ tcpdump -i ethXX tcp -v tcpdump: listening on ethXX, link-type EN10MB (Ethernet), capture size 262144 bytes hh:mm:ss IP (tos 0x0, ttl 1, id 19110, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.1.xxx > 10.0.0.2.bgp: Flags [S], cksum 0x8382 (incorrect -> 0x540e), seq 31213082, win 29200, options [mss 1460,sackOK,TS val 2231484 ecr 0,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 1, id 19111, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.1.xxx > 10.0.0.2.bgp: Flags [S], cksum 0x8382 (incorrect -> 0x5314), seq 31213082, win 29200, options [mss 1460,sackOK,TS val 2231734 ecr 0,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 1, id 19112, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.1.xxx > 10.0.0.2.bgp: Flags [S], cksum 0x8382 (incorrect -> 0x511f), seq 31213082, win 29200, options [mss 1460,sackOK,TS val 2232235 ecr 0,nop,wscale 9], length 0 ...(snip)... ``` With eBGP multihop configuration, GoBGP will set the given TTL for eBGP session and successfully connect to the neighbor on 2 hops away. ``` $ gobgpd -f gobgpd.toml {"level":"info","msg":"gobgpd started","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Topic":"Config","level":"info","msg":"Finished reading the config file","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"level":"info","msg":"Peer 10.0.0.2 is added","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Topic":"Peer","level":"info","msg":"Add a peer configuration for:10.0.0.2","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Key":"10.0.0.2","State":"BGP_FSM_OPENCONFIRM","Topic":"Peer","level":"info","msg":"Peer Up","time":"YYYY-MM-DDTHH:mm:ss+09:00"} ...(snip)... ``` ``` $ tcpdump -i ethXX tcp -v tcpdump: listening on ethXX, link-type EN10MB (Ethernet), capture size 262144 bytes hh:mm:ss IP (tos 0x0, ttl 3, id 31155, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.1.xxx > 10.0.0.2.bgp: Flags [S], cksum 0x8382 (incorrect -> 0x42a8), seq 3226540591, win 29200, options [mss 1460,sackOK,TS val 3302300 ecr 0,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 253, id 0, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.2.bgp > 10.0.0.1.xxx: Flags [S.], cksum 0x5dd6 (correct), seq 2536172214, ack 3226540592, win 28960, options [mss 1460,sackOK,TS val 3302301 ecr 3302300,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 3, id 31156, offset 0, flags [DF], proto TCP (6), length 52) 10.0.0.1.xxx > 10.0.0.2.bgp: Flags [.], cksum 0x837a (incorrect -> 0xfd89), ack 1, win 58, options [nop,nop,TS val 3302301 ecr 3302301], length 0 hh:mm:ss IP (tos 0x0, ttl 3, id 31157, offset 0, flags [DF], proto TCP (6), length 103) 10.0.0.1.xxx > 10.0.0.2.bgp: Flags [P.], cksum 0x83ad (incorrect -> 0xd68c), seq 1:52, ack 1, win 58, options [nop,nop,TS val 3302301 ecr 3302301], length 51: BGP Open Message (1), length: 51 Version 4, my AS 65001, Holdtime 90s, ID 1.1.1.1 Optional parameters, length: 22 Option Capabilities Advertisement (2), length: 20 Route Refresh (2), length: 0 Multiprotocol Extensions (1), length: 4 AFI IPv4 (1), SAFI Unicast (1) Multiprotocol Extensions (1), length: 4 AFI IPv6 (2), SAFI Unicast (1) 32-Bit AS Number (65), length: 4 4 Byte AS 65001 hh:mm:ss IP (tos 0x0, ttl 1, id 35114, offset 0, flags [DF], proto TCP (6), length 52) 10.0.0.2.bgp > 10.0.0.1.xxx: Flags [.], cksum 0xfd57 (correct), ack 52, win 57, options [nop,nop,TS val 3302301 ecr 3302301], length 0 hh:mm:ss IP (tos 0x0, ttl 1, id 35115, offset 0, flags [DF], proto TCP (6), length 103) 10.0.0.2.bgp > 10.0.0.1.xxx: Flags [P.], cksum 0xd357 (correct), seq 1:52, ack 52, win 57, options [nop,nop,TS val 3302301 ecr 3302301], length 51: BGP Open Message (1), length: 51 Version 4, my AS 65002, Holdtime 90s, ID 2.2.2.2 Optional parameters, length: 22 Option Capabilities Advertisement (2), length: 20 Route Refresh (2), length: 0 Multiprotocol Extensions (1), length: 4 AFI IPv4 (1), SAFI Unicast (1) Multiprotocol Extensions (1), length: 4 AFI IPv6 (2), SAFI Unicast (1) 32-Bit AS Number (65), length: 4 4 Byte AS 65002 hh:mm:ss IP (tos 0x0, ttl 3, id 31158, offset 0, flags [DF], proto TCP (6), length 52) 10.0.0.1.xxx > 10.0.0.2.bgp: Flags [.], cksum 0x837a (incorrect -> 0xfd23), ack 52, win 58, options [nop,nop,TS val 3302301 ecr 3302301], length 0 hh:mm:ss IP (tos 0x0, ttl 1, id 35117, offset 0, flags [DF], proto TCP (6), length 52) 10.0.0.2.bgp > 10.0.0.1.xxx: Flags [.], cksum 0x837a (incorrect -> 0xfcf4), ack 71, win 57, options [nop,nop,TS val 3302311 ecr 3302301], length 0 ...(snip)... ``` gobgp-1.29/docs/sources/evpn.md000066400000000000000000000655051324612745600164750ustar00rootroot00000000000000# Ethernet VPN (EVPN) This page explains an configuration for EVPN. Note that the feature is still very experimental. ## Contents - [CLI Syntax](#cli-syntax) - [Ethernet Segment Identifier](#ethernet-segment-identifier) - [Ethernet Auto-discovery Route](#ethernet-auto-discovery-route) - [MAC/IP Advertisement Route](#macip-advertisement-route) - [Inclusive Multicast Ethernet Tag Route](#inclusive-multicast-ethernet-tag-route) - [Ethernet Segment Route](#ethernet-segment-route) - [IP Prefix Route](#ip-prefix-route) - [BaGPipe](#bagpipe) - [Configuration](#configuration) - [Advertising EVPN route](#advertising-evpn-route) - [YABGP](#yabgp) - [Configuration](#configuration-1) - [Advertising EVPN route](#advertising-evpn-route-1) ## CLI Syntax ### Ethernet Segment Identifier Some route types requires to specify Ethernet Segment Identifier (ESI) for its argument. The supported ESI types and their formats are the following. | Type | Format | Description | | ---- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | 0 | single-homed | Reserved keyword for arbitrary ESI type to denote a single-homed site. | | 0 | 0 | The same with "single-homed". | | 0 | ARBITRARY \ | Arbitrary ESI type with arbitrary value. Value should be colon separated hex values (similar to MAC address). | | 1 | LACP \ \ | Type for LACP configured segment. | | 2 | MSTP \ \ | Type for L2 bridge protocol (e.g., Multiple Spanning Tree Protocol) configured segment. | | 3 | MAC \ \ | Type for ESI based on MAC address. | | 4 | ROUTERID \ \ | Type for ESI based on Router ID. | | 5 | AS \ \ | Type for ESI based on AS number. | ### Example - Ethernet Segment Identifier ```bash # single-homed $ gobgp global rib -a evpn add a-d esi single-homed etag 100 label 200 rd 1.1.1.1:100 $ gobgp global rib -a evpn Network Labels Next Hop AS_PATH Age Attrs *> [type:A-D][rd:1.1.1.1:100][esi:single-homed][etag:100] [200] 0.0.0.0 00:00:00 [{Origin: ?}] # ARBITRARY $ gobgp global rib -a evpn add a-d esi ARBITRARY 11:22:33:44:55:66:77:88:99 etag 100 label 200 rd 1.1.1.1:100 $ gobgp global rib -a evpn Network Labels Next Hop AS_PATH Age Attrs *> [type:A-D][rd:1.1.1.1:100][esi:ESI_ARBITRARY | 11:22:33:44:55:66:77:88:99][etag:100] [200] 0.0.0.0 00:00:00 [{Origin: ?}] # LACP $ gobgp global rib -a evpn add a-d esi LACP aa:bb:cc:dd:ee:ff 10 etag 100 label 200 rd 1.1.1.1:100 $ gobgp global rib -a evpn Network Labels Next Hop AS_PATH Age Attrs *> [type:A-D][rd:1.1.1.1:100][esi:ESI_LACP | system mac aa:bb:cc:dd:ee:ff, port key 10][etag:100] [200] 0.0.0.0 00:00:00 [{Origin: ?}] # MSTP $ gobgp global rib -a evpn add a-d esi MSTP aa:bb:cc:dd:ee:ff 10 etag 100 label 200 rd 1.1.1.1:100 $ gobgp global rib -a evpn Network Labels Next Hop AS_PATH Age Attrs *> [type:A-D][rd:1.1.1.1:100][esi:ESI_MSTP | bridge mac aa:bb:cc:dd:ee:ff, priority 10][etag:100] [200] 0.0.0.0 00:00:00 [{Origin: ?}] # MAC $ gobgp global rib -a evpn add a-d esi MAC aa:bb:cc:dd:ee:ff 10 etag 100 label 200 rd 1.1.1.1:100 $ gobgp global rib -a evpn Network Labels Next Hop AS_PATH Age Attrs *> [type:A-D][rd:1.1.1.1:100][esi:ESI_MAC | system mac aa:bb:cc:dd:ee:ff, local discriminator 10][etag:100] [200] 0.0.0.0 00:00:00 [{Origin: ?}] # ROUTERID $ gobgp global rib -a evpn add a-d esi ROUTERID 1.1.1.1 10 etag 100 label 200 rd 1.1.1.1:100 $ gobgp global rib -a evpn Network Labels Next Hop AS_PATH Age Attrs *> [type:A-D][rd:1.1.1.1:100][esi:ESI_ROUTERID | router id 1.1.1.1, local discriminator 10][etag:100] [200] 0.0.0.0 00:00:00 [{Origin: ?}] # AS $ gobgp global rib -a evpn add a-d esi AS 65000 10 etag 100 label 200 rd 1.1.1.1:100 $ gobgp global rib -a evpn Network Labels Next Hop AS_PATH Age Attrs *> [type:A-D][rd:1.1.1.1:100][esi:ESI_AS | as 65000, local discriminator 10][etag:100] [200] 0.0.0.0 00:00:00 [{Origin: ?}] ``` ### Ethernet Auto-discovery Route ```bash # Add a route $ gobgp global rib -a evpn add a-d esi etag label Helper speaker Below is the configuration to enable helper speaker behavior. ```toml [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" peer-as = 65001 [neighbors.graceful-restart.config] enabled = true ``` Check graceful restart capability is negotiated. ```shell $ gobgp n 10.0.255.1 BGP neighbor is 10.0.255.1, remote AS 65001 BGP version 4, remote router ID 192.168.0.2 BGP state = BGP_FSM_ESTABLISHED, up for 00:00:36 BGP OutQ = 0, Flops = 0 Hold time is 0, keepalive interval is 30 seconds Configured hold time is 90, keepalive interval is 30 seconds Neighbor capabilities: BGP_CAP_MULTIPROTOCOL: RF_IPv4_UC: advertised and received BGP_CAP_ROUTE_REFRESH: advertised and received BGP_CAP_GRACEFUL_RESTART: advertised and received Remote: restart time 90 sec RF_IPv4_UC BGP_CAP_FOUR_OCTET_AS_NUMBER: advertised and received Message statistics: Sent Rcvd Opens: 1 1 Notifications: 0 0 Updates: 2 1 Keepalives: 2 2 Route Refresh: 0 0 Discarded: 0 0 Total: 5 4 Route statistics: Advertised: 1 Received: 0 Accepted: 0 ``` ## Restarting speaker To support restarting speaker behavior, try the configuration below. ```toml [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" peer-as = 65001 [neighbors.graceful-restart.config] enabled = true restart-time = 120 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" [neighbors.afi-safis.mp-graceful-restart.config] enabled = true ``` By this configuration, if graceful restart capability is negotiated with the peer, the peer starts graceful restart helper procedure, when `gobgpd` dies involuntarily or `SIGINT`, `SIGKILL` signal is sent to `gobgpd`. Note when `SIGTERM` signal is sent to `gobgpd`, graceful restart negotiated peers don't start graceful restart helper procedure, since `gobgpd` sends notification messages to these peers before it die. When you restart `gobgpd`, add `-r` option to let peers know `gobgpd` is recovered from graceful restart. ```shell $ gobgpd -f gobgpd.conf -r ``` Let's see how capability negotiation changes. ```shell $ gobgp n 10.0.255.1 BGP neighbor is 10.0.255.1, remote AS 65001 BGP version 4, remote router ID 192.168.0.2 BGP state = BGP_FSM_ESTABLISHED, up for 00:00:03 BGP OutQ = 0, Flops = 0 Hold time is 0, keepalive interval is 30 seconds Configured hold time is 90, keepalive interval is 30 seconds Neighbor capabilities: BGP_CAP_MULTIPROTOCOL: RF_IPv4_UC: advertised and received BGP_CAP_ROUTE_REFRESH: advertised and received BGP_CAP_GRACEFUL_RESTART: advertised and received Local: restart time 90 sec, restart flag set RF_IPv4_UC, forward flag set Remote: restart time 90 sec RF_IPv4_UC BGP_CAP_FOUR_OCTET_AS_NUMBER: advertised and received Message statistics: Sent Rcvd Opens: 1 1 Notifications: 0 0 Updates: 2 1 Keepalives: 1 1 Route Refresh: 0 0 Discarded: 0 0 Total: 4 3 Route statistics: Advertised: 1 Received: 0 Accepted: 0 ``` You can see `restart flag` and `forward flag` is set. Without `-r` option, the peers which are under helper procedure will immediately withdraw all routes which were advertised from `gobgpd`. Also, when `gobgpd` doesn't recovered within `restart-time`, the peers will withdraw all routes. Default value of `restart-time` is equal to `hold-time`. ## Graceful Restart Notification Support [RFC4724](https://tools.ietf.org/html/rfc4724) specifies gracful restart procedures are triggered only when the BGP session between graceful restart capable peers turns down without a notification message for backward compatibility. [Graceful Restart Notification Support](https://tools.ietf.org/html/draft-ietf-idr-bgp-gr-notification-07) expands this to trigger graceful restart procedures also with a notification message. To turn on this feature, add `notification-enabled = true` to configuration like below. ```toml [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" peer-as = 65001 [neighbors.graceful-restart.config] enabled = true notification-enabled = true ``` ## Long Lived Graceful Restart ### Long Lived Graceful Restart Helper Speaker Configuration ```toml [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" peer-as = 65001 [neighbors.graceful-restart.config] enabled = true long-lived-enabled = true ``` ### Long Lived Graceful Restart Restarting Speaker Configuration Unlike normal graceful restart, long-lived graceful restart supports restart-time as per address family. ```toml [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" peer-as = 65001 [neighbors.graceful-restart.config] enabled = true long-lived-enabled = true [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" [neighbors.afi-safis.long-lived-graceful-restart.config] enabled = true restart-time = 100000 ``` ### Conbination with normal Graceful Restart You can also use long lived graceful restart with normal graceful restart. ```toml [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" peer-as = 65001 [neighbors.graceful-restart.config] enabled = true long-lived-enabled = true restart-time = 120 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" [neighbors.afi-safis.mp-graceful-restart.config] enabled = true [neighbors.afi-safis.long-lived-graceful-restart.config] enabled = true restart-time = 100000 ``` gobgp-1.29/docs/sources/grpc-client.md000066400000000000000000000215571324612745600177330ustar00rootroot00000000000000# Managing GoBGP with Your Favorite Language This page explains how to managing GoBGP with your favorite Language. You can use any language supported by [gRPC](http://www.grpc.io/) (10 languages are supported now). This page gives an example in Python, Ruby, C++, Node.js, and Java. It assumes that you use Ubuntu 16.04 (64bit). ## Contents - [Prerequisite](#prerequisite) - [Python](#python) - [Ruby](#ruby) - [C++](#cpp) - [Node.js](#nodejs) - [Java](#java) ## Prerequisite We assumes that you have finished installing `protoc` [protocol buffer](https://github.com/google/protobuf) compiler to generate stub server and client code and "protobuf runtime" for your favorite language. Please refer to [the official docs of gRPC](http://www.grpc.io/docs/) for details. ## Python ### Generating Stub Code We need to generate stub code GoBGP at first. ```bash $ cd $GOPATH/src/github.com/osrg/gobgp/tools/grpc/python $ GOBGP_API=$GOPATH/src/github.com/osrg/gobgp/api $ protoc -I $GOBGP_API --python_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_python_plugin` $GOBGP_API/gobgp.proto ``` ### Get Neighbor ['tools/grpc/python/get_neighbor.py'](https://github.com/osrg/gobgp/blob/master/tools/grpc/python/get_neighbor.py) shows an example for getting neighbor's information. Let's run this script. ```bash $ python get_neighbor.py 172.18.0.2 BGP neighbor is 10.0.0.2, remote AS 65002 BGP version 4, remote router ID BGP state = active, up for 0 BGP OutQ = 0, Flops = 0 Hold time is 0, keepalive interval is 0 seconds Configured hold time is 90, keepalive interval is 30 seconds ``` We got the neighbor information successfully. ## Ruby ### Generating Stub Code We need to generate stub code GoBGP at first. ```bash $ cd $GOPATH/src/github.com/osrg/gobgp/tools/grpc/ruby $ GOBGP_API=$GOPATH/src/github.com/osrg/gobgp/api $ protoc -I $GOBGP_API --ruby_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_ruby_plugin` $GOBGP_API/gobgp.proto ``` ### Get Neighbor ['tools/grpc/ruby/get_neighbor.py'](https://github.com/osrg/gobgp/blob/master/tools/grpc/ruby/get_neighbor.rb) shows an example for getting neighbor's information. Let's run this script. ```bash $ ruby -I . ./get_neighbors.rb 172.18.0.2 BGP neighbor is 10.0.0.2, remote AS 65002 BGP version 4, remote route ID BGP state = active, up for 0 BGP OutQ = 0, Flops = 0 Hold time is 0, keepalive interval is 0 seconds Configured hold time is 90 ``` ## C++ We use .so compilation with golang, please use only 1.5 or newer version of Go Lang. ['tools/grpc/cpp/gobgp_api_client.cc'](https://github.com/osrg/gobgp/blob/master/tools/grpc/cpp/gobgp_api_client.cc) shows an example for getting neighbor's information. We provide ['tools/grpc/cpp/build.sh'](https://github.com/osrg/gobgp/blob/master/tools/grpc/cpp/build.sh) to build this sample code. This script also generates stub codes and builds GoBGP shared library. Let's build the sample code: ```bash $ cd $GOPATH/src/github.com/osrg/gobgp/tools/grpc/cpp $ bash build.sh ``` ### Let's run it: ```bash $ ./gobgp_api_client 172.18.0.2 BGP neighbor is: 10.0.0.2, remote AS: 1 BGP version: 4, remote route ID BGP state = active, up for 0 BGP OutQ = 0, Flops = 0 Hold time is 0, keepalive interval is 0seconds Configured hold time is 90 BGP neighbor is: 10.0.0.3, remote AS: 1 BGP version: 4, remote route ID BGP state = active, up for 0 BGP OutQ = 0, Flops = 0 Hold time is 0, keepalive interval is 0seconds Configured hold time is 90 ``` ## Node.js ### Example Copy protocol definition. ```bash $ cd $GOPATH/src/github.com/osrg/gobgp/tools/grpc/nodejs $ ln -s $GOPATH/src/github.com/osrg/gobgp/api/gobgp.proto ``` ['tools/grpc/nodejs/get_neighbor.js'](https://github.com/osrg/gobgp/blob/master/tools/grpc/nodejs/get_neighbors.js) shows an example to show neighbor information. Let's run this: ``` $ node get_neighbors.js BGP neighbor: 10.0.255.1 , remote AS: 65001 BGP version 4, remote router ID: 10.0.255.1 BGP state: BGP_FSM_ESTABLISHED , uptime: Wed Jul 20 2016 05:37:22 GMT+0900 (JST) BGP OutQ: 0 , Flops: 0 Hold time: 90 , keepalive interval: 30 seconds Configured hold time: 90 BGP neighbor: 10.0.255.2 , remote AS: 65002 BGP version 4, remote router ID: BGP state: BGP_FSM_ACTIVE , uptime: undefined BGP OutQ: 0 , Flops: 0 Hold time: undefined , keepalive interval: undefined seconds Configured hold time: 90 ``` ## Java At the time of this writing, versions of each plugins and tools are as following: * ProtocolBuffer: 3.3.0 * grpc-java: 1.4.0 * java: 1.8.0_131 In proceeding with the following procedure, please substitute versions to the latest. ### Install JDK: We need to install JDK and we use Oracle JDK8 in this example. ```bash $ sudo add-apt-repository ppa:webupd8team/java $ sudo apt-get update $ sudo apt-get install oracle-java8-installer $ java -version java version "1.8.0_131" Java(TM) SE Runtime Environment (build 1.8.0_131-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode) $ echo "export JAVA_HOME=/usr/lib/jvm/java-8-oracle" >> ~/.bashrc $ source ~/.bashrc ``` ### Create protobuf library for Java: We assume you've cloned gRPC repository in your home directory. ```bash $ sudo apt-get install maven $ cd ~/grpc/third_party/protobuf/java $ mvn package ... [INFO] [INFO] --- maven-bundle-plugin:3.0.1:bundle (default-bundle) @ protobuf-java --- [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1:57.106s [INFO] Finished at: Mon Feb 08 11:51:51 JST 2016 [INFO] Final Memory: 31M/228M [INFO] ------------------------------------------------------------------------ $ ls ./core/target/proto* ./core/target/protobuf-java-3.3.0.jar ``` ### Clone grpc-java and get plugins ```bash $ cd ~/work $ git clone https://github.com/grpc/grpc-java.git $ cd ./grpc-java $ git checkout -b v1.4.0 v1.4.0 $ cd ./all $ ../gradlew build $ ls ../compiler/build/binaries/java_pluginExecutable/ protoc-gen-grpc-java ``` ### Generate stub classes: ```bash $ cd $GOPATH/src/github.com/osrg/gobgp/tools/grpc $ mkdir -p java/src $ cd java $ GOBGP_API=$GOPATH/src/github.com/osrg/gobgp/api $ protoc --java_out=./src --proto_path="$GOBGP_API" $GOBGP_API/gobgp.proto $ protoc --plugin=protoc-gen-grpc-java=$HOME/work/grpc-java/compiler/build/exe/java_plugin/protoc-gen-grpc-java --grpc-java_out=./src --proto_path="$GOBGP_API" $GOBGP_API/gobgp.proto $ ls ./src/gobgpapi/ Gobgp.java GobgpApiGrpc.java ``` ### Build sample client: ['tools/grpc/java/src/gobgp/example/GobgpSampleClient.java'](https://github.com/osrg/gobgp/blob/master/tools/grpc/java/src/gobgp/example/GobgpSampleClient.java) is an example to show neighbor information. Let's build and run it. However we need to download and copy some dependencies beforehand. ```bash $ cd $GOPATH/src/github.com/osrg/gobgp/tools/grpc/java $ mkdir lib $ cd lib $ wget http://central.maven.org/maven2/com/google/guava/guava/22.0/guava-22.0.jar $ wget http://central.maven.org/maven2/com/squareup/okhttp/okhttp/2.5.0/okhttp-2.7.5.jar $ wget http://central.maven.org/maven2/com/squareup/okio/okio/1.13.0/okio-1.13.0.jar $ wget http://central.maven.org/maven2/com/google/instrumentation/instrumentation-api/0.4.3/instrumentation-api-0.4.3.jar $ cp ~/grpc/third_party/protobuf/java/core/target/protobuf-java-3.3.0.jar ./ $ cp ~/work/grpc-java/stub/build/libs/grpc-stub-1.4.0.jar ./ $ cp ~/work/grpc-java/core/build/libs/grpc-core-1.4.0.jar ./ $ cp ~/work/grpc-java/protobuf/build/libs/grpc-protobuf-1.4.0.jar ./ $ cp ~/work/grpc-java/protobuf-lite/build/libs/grpc-protobuf-lite-1.4.0.jar ./ $ cp ~/work/grpc-java/context/build/libs/grpc-context-1.4.0.jar ./ $ cp ~/work/grpc-java/okhttp/build/libs/grpc-okhttp-1.4.0.jar ./ ``` We are ready to build and run. ```bash $ cd $GOPATH/src/github.com/osrg/gobgp/tools/grpc/java $ mkdir classes $ CLASSPATH=./lib/protobuf-java-3.3.0.jar:./lib/guava-22.0.jar:./lib/grpc-okhttp-1.4.0.jar:./lib/okio-1.13.0.jar:./lib/grpc-stub-1.4.0.jar:./lib/grpc-core-1.4.0.jar:./lib/grpc-protobuf-1.4.0.jar:./lib/okhttp-2.7.5.jar:./lib/instrumentation-api-0.4.3.jar:./lib/grpc-context-1.4.0.jar:./lib/grpc-protobuf-lite-1.4.0.jar:./classes/ $ javac -classpath $CLASSPATH -d ./classes ./src/gobgpapi/*.java $ javac -classpath $CLASSPATH -d ./classes ./src/gobgp/example/GobgpSampleClient.java $ java -cp $CLASSPATH gobgp.example.GobgpSampleClient localhost BGP neighbor is 10.0.0.2, remote AS 1 BGP version 4, remote router ID BGP state = active, up for 0 BGP OutQ = 0, Flops = 0 Hold time is 0, keepalive interval is 0 seconds Configured hold time is 90 BGP neighbor is 10.0.0.3, remote AS 1 BGP version 4, remote router ID BGP state = active, up for 0 BGP OutQ = 0, Flops = 0 Hold time is 0, keepalive interval is 0 seconds Configured hold time is 90 ``` gobgp-1.29/docs/sources/lib.md000066400000000000000000000035301324612745600162610ustar00rootroot00000000000000# GoBGP as a Go Native BGP library This page explains how to use GoBGP as a Go Native BGP library. ## Contents - [Basic Example](#basic) ## Basic Example ```go package main import ( "fmt" log "github.com/sirupsen/logrus" api "github.com/osrg/gobgp/api" "github.com/osrg/gobgp/config" "github.com/osrg/gobgp/packet/bgp" gobgp "github.com/osrg/gobgp/server" "github.com/osrg/gobgp/table" "time" ) func main() { log.SetLevel(log.DebugLevel) s := gobgp.NewBgpServer() go s.Serve() // start grpc api server. this is not mandatory // but you will be able to use `gobgp` cmd with this. g := api.NewGrpcServer(s, ":50051") go g.Serve() // global configuration global := &config.Global{ Config: config.GlobalConfig{ As: 65000, RouterId: "10.0.255.254", Port: -1, // gobgp won't listen on tcp:179 }, } if err := s.Start(global); err != nil { log.Fatal(err) } // neighbor configuration n := &config.Neighbor{ Config: config.NeighborConfig{ NeighborAddress: "10.0.255.1", PeerAs: 65001, }, } if err := s.AddNeighbor(n); err != nil { log.Fatal(err) } // add routes attrs := []bgp.PathAttributeInterface{ bgp.NewPathAttributeOrigin(0), bgp.NewPathAttributeNextHop("10.0.255.254"), bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(bgp.BGP_ASPATH_ATTR_TYPE_SEQ, []uint32{4000, 400000, 300000, 40001})}), } if _, err := s.AddPath("", []*table.Path{table.NewPath(nil, bgp.NewIPAddrPrefix(24, "10.0.0.0"), false, attrs, time.Now(), false)}); err != nil { log.Fatal(err) } // monitor new routes w := s.Watch(gobgp.WatchBestPath(false)) for { select { case ev := <-w.Event(): switch msg := ev.(type) { case *gobgp.WatchEventBestPath: for _, path := range msg.PathList { // do something useful fmt.Println(path) } } } } } ``` gobgp-1.29/docs/sources/mrt.md000066400000000000000000000047621324612745600163250ustar00rootroot00000000000000# MRT This page explains how to play with GoBGP's MRT feature. ## Prerequisites Assume you finished [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md). ## Contents - [Inject routes from MRT table v2 records](#section0) - [Dump updates in MRT BGP4MP format](#section1) - [Configuration](#section1.1) - [Dump the RIB in MRT TABLE_DUMPv2 format](#section2) - [Configuration](#section2.1) ## Inject routes from MRT table v2 records Route injection can be done by ```bash $ gobgp mrt inject global [] ``` ## Dump updates in MRT BGP4MP format ### Configuration With the following configuration, gobgpd continuously dumps BGP update messages to `/tmp/updates.dump` file in the BGP4MP format. ```toml [[mrt-dump]] [mrt-dump.config] dump-type = "updates" file-name = "/tmp/updates.dump" ``` Also gobgpd supports log rotation; a new dump file is created periodically, and the old file is renamed to a different name. With the following configuration, gobgpd creates a new dump file every 180 seconds such as `/tmp/20160510.1546.dump`. The format of a name can be specified in golang's [time](https://golang.org/pkg/time/#pkg-constants) package's format. ```toml [[mrt-dump]] [mrt-dump.config] dump-type = "updates" file-name = "/tmp/log/20060102.1504.dump" rotation-interval = 180 ``` ## Dump the RIB in MRT TABLE_DUMPv2 format ### Configuration With the following configuration, gobgpd continuously dumps routes in the global rib to `/tmp/table.dump` file in the TABLE_DUMPv2 format every 60 seconds. ```toml [[mrt-dump]] [mrt-dump.config] dump-type = "table" file-name = "/tmp/table.dump" dump-interval = 60 ``` With a route server configuration, gobgpd can dump routes in each peer's RIB. ```toml [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" # ...(snip)... [neighbors.route-server.config] route-server-client = true [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.2" # ...(snip)... [neighbors.route-server.config] route-server-client = true [[mrt-dump]] [mrt-dump.config] dump-type = "table" file-name = "/tmp/table-1.dump" table-name = "10.0.255.1" dump-interval = 60 [[mrt-dump]] [mrt-dump.config] dump-type = "table" file-name = "/tmp/table-2.dump" table-name = "10.0.255.2" dump-interval = 60 ``` gobgp-1.29/docs/sources/peer-group.md000066400000000000000000000046321324612745600176040ustar00rootroot00000000000000# Peer Group This page explains how to configure the Peer Group features. With Peer Group, you can set the same configuration to multiple peers. ## Contents - [Prerequisite](#prerequisite) - [Configuration](#configuration) - [Verification](#verification) ## Prerequisite Assumed that you finished [Getting Started](getting-started.md). ## Configuration Below is the configuration to create a peer group. ```toml [[peer-groups]] [peer-groups.config] peer-group-name = "sample-group" peer-as = 65001 [[peer-groups.afi-safis]] [peer-groups.afi-safis.config] afi-safi-name = "ipv4-unicast" [[peer-groups.afi-safis]] [peer-groups.afi-safis.config] afi-safi-name = "ipv4-flowspec" ``` The configurations in this peer group will be inherited to the neighbors which is the member of this peer group. In addition, you can add additional configurations to each member. Below is the configuration to create a neighbor which belongs this peer group. ```toml [[neighbors]] [neighbors.config] neighbor-address = "172.40.1.3" peer-group = "sample-group" [neighbors.timers.config] hold-time = 99 ``` This neighbor belongs to the peer group, so the peer-as is 65001, and ipv4-unicast and ipv4-flowspec are enabled. Furthermore, an additional configuration is set, the hold timer is 99 secs. ## Verification You can see the neighbor configuration inherits the peer group config by running `gobgp neighbor` command. ```shell $ gobgp neighbor 172.40.1.3 BGP neighbor is 172.40.1.3, remote AS 65001 BGP version 4, remote router ID 172.40.1.3 BGP state = established, up for 00:00:05 BGP OutQ = 0, Flops = 0 Hold time is 99, keepalive interval is 33 seconds Configured hold time is 99, keepalive interval is 33 seconds Neighbor capabilities: multiprotocol: ipv4-unicast: advertised and received ipv4-flowspec: advertised and received route-refresh: advertised and received 4-octet-as: advertised and received Message statistics: Sent Rcvd Opens: 1 1 Notifications: 0 0 Updates: 0 0 Keepalives: 1 1 Route Refresh: 0 0 Discarded: 0 0 Total: 2 2 Route statistics: Advertised: 0 Received: 0 Accepted: 0 ``` gobgp-1.29/docs/sources/policy-component.png000066400000000000000000002623321324612745600212050ustar00rootroot00000000000000‰PNG  IHDRÍ7'ò{sRGB®Îé pHYs  šœ@IDATxì] EϘíÞ£¦ÍåÅißvišyµü, ‹Šg ?ÕÍuÃ]qþÄf®ë¾æ¤à×äáçíLbÍE3€á¤&ˆ ¿¨Eg)£ dIåäýn{W(‰T²vØmÞñ.«XÅù.Zª=ÜKRV¶ö+^JNª¥µ4´§lKëèÌ|?;ÿ‰Í÷¡]Ö™®ÙSV[™0Xn¼¤¿¬PY€ŸïŽÌ¢Š‘(é²û¾&F:ÃjóuÞ Y†RMâ!?tÒ’±¡SQ”¦Ö„¤gëtƒlñ¼w®ÿs8:.N´Xggœ‘5…N¯RFfF É® —¯Þaf4)íoÓUæÿ6QVT/W§1WB£¨°,/@Õ¡çv&i‰wÅßd•³?âøÝæ-0]8E÷r•¡@1¸“,e¤©VŠøñ;î:øâÕ°…Ô ˜FR²òq«­míêj±…‘oß½4¢]gÜœFøß=Šaèº4EoŒ3¯>M˜Q"pöÄ › Fµx`î¸vÔkG>µP•gP3ÕæÎœytŒ¾ñ ÅÒ×’·cÞÍ&w¥ÒÖÚI(L:’W‚g|ü±Xjï²q•ö®°Wåei¼`cÌÇÄõ7÷}éž7EãTlÔ5¹=®ž‡2‹JÙ‡²Šq¶Îþžû9§Ç™v‹ÂÔ×ËM×É{†œµôñž÷>·d šæŒkû쟯36Ù˜RRÙ[©÷ÀhÁ¢”¯?f°ë’”Wytâl€•ª<™Û¥›—9¨ ¦kÍ_&¥®E…äUú‹•g7û½Ê(άӶˆ#£™i²°ìFç#“_ë†&°5Õ€ å]ÓÜø"õ÷VÏ3Ä×À—¡[ôg¾ÒÚuêŒ:¥ˆe¶Æfë‡Ì¯ºUPƒÞ r Ý£·ìsß1VMñ‚=§uøÛhŸÐLªý’¡ÿµÈTN~ÿêŸÑÏ’­[åY¯ô®Ç$ïYÀè[Ë*Ö\´1œ¿1þ 9å­u=nŒ¦¦ï,ýYñá11ÃЀ Ì?%º„«É@1Ôµ j*D2éë.ŠgK((df,-Êéã¾ó† œÄÔT(°6^¶ê¥±|Iä³nNiÈøob8m®Áõ'·µ>E‡wË)*`¿(òîßÿ““ÕQ%âëBÄÀ„q¯£o»ØºõþrÄÚ$žmÎ×]_§}¨>€å>¤d¸{ïöýšñY¥ÒG¿Ô –*:UÙÏ}÷QÚ£5šæª«Ÿu&ŸÞ¼ëJ¨ªZºŽõý€ÅÇq÷ew¢_÷$S¤«fsÇÑ­f†+î%¤³ÇXo,€úå!Ço?m\âl±ðí·ïÿD M%1†—ÓÚ‰¾añD« ëÒpoâca_MÉÊb öÙÅe¢9yy‚ ›A¼~ü$6õOÙ/9ì|y »˜±[j›;’W^žðùµ¨X´ŽCapèäÃÓt–™ AUASDŒ¨Àƒ*ÛÝ/ߨË@ìÇN–ËçïóžÄéÃaå©fVHæd¥è•ÓiŸ>ÏÈ‚“5)éï`ÒóœŒì)вˆú"/=j^ÚŸ+Œ–^ ¿´)2¾5.+Àw¢)³.§²?T9”òÒ>Ô›gÀKŸ8'Ët­F¾Iœç¢è׎þcȰÓzÓõïî;ËAFœlOäýÖø½qºÌÝB*¾ƒˆÀ‚ÏXúŸ¡Q÷”Ü÷ß(&¢á½r!&VËŒôð<`:é´Ã|šI™ì5+W~°°Øðöˆ·¿ïjƒ1fÏY¹œó"`cf²bÂø±Ñššš¯ÆŽŸ=ßxíÆÔbF“|œ8ö¿ãPÃá}Üo]TC:F¦Ì3i ´Ä0qýéúggÎÔ?ú·Ó±‰åÙqä90¥ ¼ûý| ðñ»# ¡k’;P^ôQqEQîÈȰ›6–kOÐ÷jõF§94 ü®Ý˜¸‚§¿j/^³âÖ­€‘¯C• Y}ò"Û(hïµZ0› WV.$ÔTÙøÊ„ÀÜsÐø·ÂÃG¬Y¥¥'DZ?HÎ/¬ºoby•¡ÁGO7XY&åA‚Îã¨×ÊMñOh9yñÿÎDàñ“ö=EuhM›òŒ¿.E5 úJ8¶.3šŸ†îKAòÑH®ÀŒ”Î''ì±_£'Â-æwêBûü[÷ÿ9´~d€«µ32)oŸíwzÏ8!A*/¼}UP¸{Mhø­#TäØý$ª˜åwíšÆ÷îu}Ž3¤w,¯¼áóŸ ²x÷Ir2â›E/PÎ.Î<À˜É’ŠÉîþ5xúÞq§6Ç‚<÷ÙN"ú6›¯]»¢¡&jª¹½\ˆ,ÈÕ!€¶‰Ù¡}“dEIY(ƒš­–åŒèŒË½T¹õyB¾,–ÏX­®™ƒ0^¬½‚È€ü· Ï­º:†êêh%è»&f—°=™¾5~Sû-(:ÂÓnä–r>ck&ªfÚlQù!Ãw’§ËZ¶ÛTQ‚›¡{õãcç Ф°ÚÞê•D?ç’Ê3ÆëG?IxoLÄ(iÔæm‘î´ø¤&ç¯UæâKÚ¿†PsùmœÎç¤ÖÎÓ™™ ÔW½œ5kyyþs/t7ÛéÖåwâÄ8¤Õ+€?Q99¢ßqyÇ_(ߊØ`±Ü`‰œ¤{°®k' ¼Žs1X¸n»˜¼'úŸ y\«±Eùeš(öhAO©W·¤À³g‡!Ð(&=‰ZÏ«%žnfc¸Â@?k×1— 1nA(lo0š“få¼ç¡2wð/øüi6 “ó9ÙÈp.y 8{öÊ0YnY :úÏà)Ÿ=¾ëêÿ£Øg³ƒð“#‰ 3šâŸà;&£)¨ZB®† 'ú‘åa/ýépåŠÉe™üÏŒC¤ÜãNpå\7}ÿßDÝ“«û0\hkMÊÕ'Óí6«ý8lŒ1qùêî 2ìþ&H)ë —Ôjli¼Žäµ1>pÜÏ‹@Ò½ë Dߢ”ýÞ3òÅge1 )µŠEÏSN+6zh1f¡±Y ö5”Þ”S0YR%y§ýºçZÚs u4úœ@y‰Ð’q9"êù$N~p9~z™†Zúr Ç·ãT»ž#è4u4±Þpzÿ8Ue®r¢.gAÖG½UsçÆ&å呾5~?Mì1ЧÝb²]2zÁvˈ‘k?èyoþÔ›>ë쥇Ÿ†¯2³2¼þ`„‘N­ËU]Íø¬# >ÒÚM“AMWÛæä2‘5Xißjüû1“+\@Õž%î[¶LËç ËŠªç´ó8è¤;:É}!Y­±ÞzëÒ± !XäÈ÷ì¯YÂWÅÕDÛëÇâ«ÎCàšCŸÜåjŽ_>|:èjÜå­ín ›Šó±«ÈoÙêðIý¼Ânœt8¶öíÉî7èz̘QɧB8=­¨¨ŒÇ4Þôý§WV,5q\Qï€ÑãŠÈÀ‹½J×Ô±šÁ’h*­#ymªÿ³ PÊ*XìqOPH’*+)PMp^U”2ÒÛ;…o«M&²ÁcÝøT;Pò¤¢S¥ƒ…¹q••Õ<Ï-—Éd1î¸+¤ªò#4þxðôc}¥ ‘†Ž Z±Àë7Ÿ$)²½ªBnBWŒ*ûW1O¥Îúœ™÷Œë®Šd à`Õ–Œß`Ñ€ý“ýaM–Tml0ÞÌ7ä‰7J+ƒÖÓ‡÷#МõóÏ:|}fï6Q;Ú³ýDYàaØm~:]Uÿwn¦zWÚüõIlÿ;A¡®T_8Á 9È;:výíÝ¥ÈRö5s±l?ºkˆþçÝjε 8püòÌßuÌ#ˆjfK)«ª¡L3Oðß bÑi üÇWn³é'Þ¿y …fѧvv}¸pmò[DhåpÌ&†mA@g"ëg‚Ó—Ï_º4ÆdKïÿ§¤÷Ý¡ÀΞDXš™"Ò˜ÈÒ}A²¯Ñ„/KçðÊW ¾ü­ç~€ëF«9„âLXV1žTN':è:hÌ ³ÙÚ×it:&Ñ8–Ç1a%´ =q®Å-$ÌÆ ´d\N¹4žS¶”s&í±/?¼KÖM".öîY%+׋¡èZgùæ%{-濦©]ꮩíû,Äí.‘–ô6m8’€‘ïssã7¢CrÅdÃΘéóÒ4“S>H…ß¼5).þÍ4¹¼Xov\sóì^/¢>v<õm‡Ð%ØÙ³ˆŸ”¬BÜlèàΞPDøU3$ñ ¹zÔ0•ó¨ztÑÏÑ·X©ûº‚¥‰ ŽxŸö=söŒ—¡¶Fa5MzO®ïTö»W†§OžÎrÈÀ|ÚH¢>þʈƒáêÝ}9-d€5††A·¢¢åò iì’Ïáþ/›˜QþOïzþX;¦ç³>[V@W‰´øX•¦fÞ²Ý{(ÖšÉXP<^hd”6sÒˆœÀ3§zCÍðÕ°‡‹9¼µå¿PªõÕf‚žCµŠŒ }%±b¦·w°ŸŸß…”âòZ Ksš@þÚëÚΟ‚¯; ¾šsJÕd…ã8ôKÁ’izøö‰‰’Zf0Ëõß<úŸœ490G{xáÜ…Ó¹cìþÛÆ©šÑL|/¿0®ÒB ̘¦É6q£rÍÝÿaZ“ów Ç}|ãØ>—4`kw`GNS{SÿÔ|µç‰ÅkUr<;’W²øô'@ âœ×²-Ð2l¹ÞbѬéÚgC¢ß¹l/Þ`¾s¼–ÎW‡S¨Š)6ÙÀ ËúíÇß¿:‘~~AŠy,oDÙö[2.Öÿ§ؾnÝô|•À9QOëM¼mÈïÒü]pr;3½o^ÅÜ“:è}½Öš9nòÌÇ-¿Lâ,ËÍTË+¦ ¢Ícttt/3I,”¤>v*(âÚщd.;$a!®–¼!8¦c¨“ ÚI,Ù#ùνKpvjãɨf›ð %Û1Ÿ“« |úüu,:'‰Êf±¿*/^Û841ÐvEQUEuè¤ç[­MãgϳÇãâc_5MÍ÷bš/" ,Û;nœ-^K ŸüÒØ;9?˜iº¹"0¨9j;í66ø`ê9|¢Z­àÖã9\Bïfuêá£@–3¦>’•$}(€KÂqO§Yhù2){ïÉ)­›s¨H³rÛÏýüÅØÇuÛ7ʺÓÔãR/oà a°eûÚ­Ë,ÝW3‹%ÍçN‹åÍC¸ »ÉÛôyãü_×j#sMW„Sš@@øø[5}æš8¤²­fR%/x»_à˽ÊÅi*²ÈjÎ.í/{<í†V˜o¨£©iÈ›µ×hí=-Y•!‹«ÕŒÞÓ³GàïK¼tùÏ« ¶É‰Á–UÆO{ÀÙ†óeRë@^ùHãËÌoLௗ}Gëí!ÖhžûrMÊùkû± oæ¾Z“‘Å…ÑJM3/ þóUöÛŸk\ž©N¡ùú=€Îž¦½jð|ñÓ$®‡h/)¦ìúн|$ „]<é D2ûˆ4ËÆZY3”¿=~“á$]$ #4zÎgêêþå`«‹>rÑ;í²»Ãp?Ù¸Š¢¢‘„PóÇ0u®À3àÐ)|S³ÛÒZ›Óf r¯²÷-=KyÿñÀÁèüyx€Bb•mB&Q$s¾ý’=y踸èœs;0âIB¢uRÒûµE€£Ç^¸fM¡AAyPК=û`–›‡ÿÿºå±nÃU0úÈËÍ(MMÓ¨t\1Aœo• Ùqg®MD™»?Q•ã D;çŸ7'>ïlÈòÃXa®¬¢Èç׉ ÒYÖû<'­ænùŽÆÓóa7LG©v?WŸ/1yžmwKá–ÞÿÝÞ~¾ªŠõh¡~NÐ&ú qMôÕUëW]–æNPEiÐïRRгá ò¿æ„Žå•K~XšÒáuÄãVÂÕYí"Øßé}Áw†ÖPGâš8ª Ÿàvúýuâš8½Š¸FGþq’ÜȸY×[2.+€K×¼4ˆ•78u‘²r¶ëY¹±yˆÂàdÈ-ûé°=ñ)¯288øîµ È¥´%ã7=À"m3^Z´EAoíˆç®´V`FϾ¡-Zj’Ã/þßYÈÉÉ™ººº¦¯^½šíËÖšŠÐ6pÝÅôÕCí‘ñðŠ^S‚/#/–4yæº8ôÕÔXØäá?­å\·# ôC½?âÊ6“œ9×ý¯&A:Ä—U}ZbÀÿÁÝá- ÜekáÌí±i%\-ŒL ã¦Õ/…¯:#FLJHH@Â@f[jjO%êcÐh™™"e••$é®]«áLä–~@Ñ «FiE‰B‘bÉȈs$e‚p ŽDy4ØvQêÚâz[@š¥š+˜LÁ¶ò×Òz~å|íí£›Žè§ˆo_UTìGolU ””7¿´”-©tiEŸæ”®ÿŸè§méG4ÈY\¼¦¹gª#y­Ï9¾j+mé÷ŠŠŠ›Ã¢† Fok½ ËUìì¶—ìÇMÉ ˵=†èïÍËÄØÚzžP{rÈùù%Â""¬îÝ{TŠ×ÎS¨Ï3QGsÏzvjøž/‚!!Jµ4\í©¹g¯~øª-XYY 8qâg°m þ2Mi³ˆ|dy –?\ƒÑÂÚÙ7ŸZ©FÄ£Y´æŽ»æ›Ÿðk*ø¯`ÿƒš«-¶ú×íE€ˆ¤B²“›ÛŠ:³¤00ýËÀ;ÖÑ—-4˪ªŸ«K#jÇÇßôB֦A^\F¦ýÚŠU{Ë«^´lœLKg~‹NÿÏhM_Ey•ÄÅ×!´²%íé§âoUב¼~«.œþ³! ˆ¥ä¾ç-éïm[Q{z1””¾½MKê@Ï?.-៿ ¾n?MÙRZDùÃEÂŵ[”fRëÞ¾7.ýÅÑÜ¡/$´.oy«=§ïZP»äÒ ¾k”WQmýæ½G Ê¡†­„Yw6î éˆÕÒ)ÏK#>uè‹Ga‹ÚóM–úçøˆÀ`:A¸jIwïÞ¡¥g[mµë`^09ŒF#€èÚ%4·•ŸŽúBB›R@¿åZa™àçUôÕn/úׂBŠxmf |Ä`:™5552,é â˜&F#€Àü÷þ÷,t<’²òta8‘ -§ ¬z2øÜ$41 ŒF#Ð Œ†4¥DDDð$œN“Ä`0? ÿ‰¦¹³?D{iáã'Kµ:»L#€øíè @Bó9AAÁ¿=ŒF#ð #ðKjšáû…›†Àü8 ]úfÂZ›½àÃæ#€À`:,4w4¢˜F#ð; €–O /à/ýwh0n#F#€øÝÀBóïÞpû1¶ 0B›†ÕÛ±-„pŒF#€ø9ø%}šè1—ŒÀOŠÀPÈ·üÿIùÇlc¾†††“***+¸D(éðáá­Ý¤ Í?Ì-ÄŒ`0?ŠÇ ð²©ÈOÐfÌ"F MäÁ½rrrþضm[í¶ìm"„ a:K—.õ¾|ù²šSZCºEBóæÍ›ÄÅÅ K*á—DkÐý òVWW³Ž;v·_¿~¸oü÷û7o"¶ù1‡ÁrÍÀ#€øpU™êéÓ§|#NÆ|W®_¿Þ£-¶HhNLL´fÍš\))),µå_¸ÌÉ“'ì&BàÒàÉÏ ümºÒ<6ù‹1Eáè‰=¸uÿÕc{w=Kƒ±Y݆ò¼4’ÿå°c¦Ìù4LM‘ÞÙõaúŒFà{ Ð"¡ù{0‚ëÀ´üa×ô~í²ða7"ÔþÎý,H½‰ ”{ž”º–$*™¥>th舤ÔÇÿ¤'+ÈR¾‹û>îF¿ón¼H=qûì^¯Ÿ7Ì'F#€h,47‡NÃ`~wº@¦Àß%ø«ìL0ÞD—³tò Pì¡t/7ãË” fµd×îƒÂ}/Ú)AK˜£¿âN¯ñ:nb$äZŸ ¿1"x§Í¬¨Ø¤µ•0¯´Bßèãg}¶”%ɬµ;‰x­aVJJuëšO-ú,Q ¯™ðÏÒxÞÆWÅS&/Z·ÉÑbÂÛùÓÏæWÔ(:;c8]]…ÆßÆ3nÛFÝŽÞRB­Pϧ·ÀyŸ½ùsª€ËÆ søëH§X»\¸Šèä'=\«o°Rårˆ½x=ÂUÀÆt™ybvùÿz(Iÿó19Ÿ) ¶q¶|È e ˜™Zlû™¯‹LY"©ì•¶[×™èŽÈ]³pÉö”Òê¾}Uä¾ó~ÁþÆÞ×ILýj $$N5wÞo¼X{8´Œ6Îco2‚ŒF#Ð*ð:Í­‚ gÆ`~#„a[ à  yÝnz%UˆF§)úøÑ¤†"™Cb2%sÒ^.5ÝlŽÃ_ºh_¨—|Nö›V¨L¡T2€4°±·|^_ûÊ©AZE#øî%ÏåÙ1äis-c “ßCañÚ‚…Ùf› mWj~ÐÓœÿ ê`¥³«ñ”"T£¹«bK©Yj²c«;ÿeü—Wd¯Ñ3÷8[,}»sýí!œÒhè®Îr5Üí^BºuzV¥2Àçұ ìsrŸXE¡T2+*DúÿóC?nÓ«™5’ŠªÃRæËËm(’@o¸L±‰ÝG—¦êwçÖßcÄ´£þÇì‚.ùãkû_ÏÑÌJC?kkƒ¡Y §è^N£Vª½|“&©­;×£g^‰üšÍv÷òBd,펮e ]Ì$ÊͳÚ;ÛÎhDÚ¬ñúѹLIûÓ—ÇÍìK+Ÿ¬³,3Ñ"DD7ÎcLrލöµæÜü#€h1Xhn1T8#F#ð!0¶Uþ®wL›ÀÏÛ?8Ÿ‡Ø¬µæ#†7â&0Í`>ÛwZLi£»$99•Z¢–]Âd[I¢=²¬VÎÿ Pœ$ÈYʨ¸Ù­‹t«¥Ë/^&u›,WQ¢jUh™¼aÇ.{ÿÈ™ë¬Y0E}ò [´ö£8[ðjX˜‘ùéDzs.‡4‚Ÿ‹ý˜iäíŒÒD$“õç7jyýµÝd§0cšF6€.ê{§½ø¼%'«PbDo‰¯A×®í¹¯~€à§þQÌžð¹_ˆQHÙ€*(9¸¯B9)}\T“IeŒâÒf1‚BsZ}š¿ßUYY™ l5º¨ ´(”——÷Ú·oß` ‰ªo`0‚0¿Ø·òý*ée%…‚¥¥ÅB,8tÈvíÆ j~•¶ávpÀB3î ŒF >}àåpøCÂkù¾’­Ë–©Ù…åQQQ^Mî߈ÀŒØx9 éø ò© 5$€°_¾‚PDyYˆû2–§¯jô¤±¢C¯õ|“–§´HoBz~Ìû®ì|Mü³µr²B3 ‰÷ƒ¼Ê3¿?L^•ådo¯WB§³½)T†ŒÎ/ÏË'ýoòœK,‡Ÿ¢¿=îv|_FQÎH‡Ýg–rJ7_¿Y ïÍ·ÉHy\+\TWe 8ö¹Sµèkì÷è-ž60{á4ÃÇùL¨âæ üåØ×\œØÙ¾©ßù}Œ!7$ôki UUU‘áŽjµ÷¬©‚Hh†i„q A6VÅGCmãËl_"¾TQ Ù¼1º ü·oþë©_ZÛ. “ÅßËÃ^&ÏBåGÌ·:<<¯ÄÛМ¾{s¡º4©MÏ{zÜ)KÛ]û²ÊXò¼¼)ªv?vÈ£O+è²Jò_}Jg7¹[¿!eÝ¥…ÛÄ/ø¼ãÀBsÇa‰)a0??Ò° úðèú* ´uç´h-óô„»ÖféE%ïã§ YXV9NYŒ’a dñÞ5•ÅB_e•ê9mr°«ÞíÉÍïJ –ÚÛãäjütˆ‡ò³ù&‚B2Tu5É€WIék×/3=}?켩¾A=ùåYÈåãÞÁÁ$Q…¬=ûíÉIQ>gUŒTP꟢÷ºÉúåtö»%3ñá ³LúÑCÎ×ëKeMC[Íb pz ˆæ!Çm³óáDG¢5ß’Ôˆö`DÔõ«¡¶-#™-m«˜˜ØØíÛ·'´dûa´#àüùó5š¤]U!Àù l˜ƒ^V$uå¤Í³ÄÔÿEúït'Ü“ælYÌåý¶ã•`‰ŠVÑK©Ü~U mm ¹oCÄ Í]}+“üLËh–鈑ç–+´°_^ìjîpþ8¢g¸ãŒ¥­ÞzÏdcõà¸ï‡@‡ ÍØ,ñýn®©m >ZMgÈêŽMgmÃð/…^khÇ¿§ð÷Ÿ½¨”C¢¹ ¬‘`{äì±5Âà+ºä ÂÀóüyãùÆÝÒ>¾6 þLñJ{'=dgÏáæ”éÒ…ÝI)ñ"E…`§k@(:7Ú´c¡¥ÁÀ¬9gÌÎ)ú0Òéø¥Á{-¾EiDk`‘5!,Îí1œ|hgióÅË( Ýç¼þ¡ÍàQSõw›43§‹ûå¬:Uùõó[èÊÁ'454”$D(½ª§k r¼ärÄqKâåb±ÿ“˜()PkÔ œ#‡Y\¬FF°}S ò´9Œê—ÇWÿ Â55Ä$Q‘ž´uÖÆÛ…èåä ‡ÜŽzµ ñT™|WëÆÛ § +4ý¥õMæY *êÝ<”M@¤Íïš×ÊrÒ¬ŠÂÿ‘ú뤿¥0I´MË|í\Тzõ©KO;ÛG佋–v´Þ}=µ5•ïŃþ«°Aç}4ËÉÈÖ. )#-U{NÔÿ-íš[j–(ùš*ô)³PLHD–5xpZá²i ÚS¶iªß/ågçÿû!Õ95¥BәݶýŽ©½ykPVŸqøÐÞS­1ñ–oýyؾl†ÕÝäR-¡.#SÃïxmº¼mùôïVñš3Þ'Qrit²¤|/zÿž]¡¤ XÎî“ÍÔgêêÓw­ k}ݸD ІyЋ-¾y;-‹þÚí‡Íu‡Úçå•“¤åeXÂ욤AdLL= 2Y¼{Í¥°(S\m®X!X—Mö3ÏÑ5¯Íoµçô]+î"R–³kãÑõõ‡Oõбñ zúBßaßbÈYL¼Z\œÌQö6S?Y¦õoÐ º½€·Îíǃ¶ÄŽ3кnK£Ý A× ñzæiß[7Ô•#ƒútz€^œšá±Ž>û ItË[d`ð…Ýߌ?izÛ§8úD:"Þ2¾ˆßŽ>ÞïTh‚a—Þ£ßþ5[5òÀî#ÎYe2òÈ­bpNÍnÇ…ãÞê—UÖˆ#׎i ͼìÍ ^èb¾|³ý‡foD«¦ò“øßkm6ï9qlñMÿþ§î½.*©Räøwî¥MÖÇvŠJˆÔH©zÞã- ‡ƒËþ¶J¯dQX"=+¼ü¹+ "µЍ4äƒ0Ý`v¯»œ0«û¸Ù…Çܾ®Y`}ú$J!Ãå9B¼]‡ž¹t×<¯Œ.RÔFißt=ä|®;¥¢f·Õß‹þINÍÍ .ïÚ°åQïQ‰^Çý2ž^‘Û½ÿ¬UZa¾J%·êc§]wÙe&GÀÇNG ]BskÌA‡­gœ‰ÊZ& 2ˆþèìréV4­=e[QM§eýÙùï4`¾áØ·^ë\/n¬ª¬Ä‡SO3œ¼@õ»Lبùåì 75•,ö F)RŒÁÀ1²*^ ˜š¬ö/…1rc—^ußÀ"¨…L”«¤¬¢5*‚C (((@÷ù^^hAöNÉB¸4”–R‹©¼¼0áiÐl}dqñyq¨ˆíÔ dšà§³êGt¡4ò-oŒµº³xlQå8S› WVp¾¹¥sÓß)gggÊξ1Èúà~pUzé¿‚†]üÊS rí¸áãê˜ø9Ç?`Ëÿn&¥eŽâL\åd*J{3êñ»i¥ôwJŸ““µ X ý‡+üp+1g*iÀÑãòË¥w%£“?Ãtþ˜¥ïY_`F±$ +)^ŒÎ(6³'\TQ׈Ð<õÞd…i11« 9iè? 챘»üúË\¶O5'ž’_FÌš¯ûY'$ÒkùG(ôç@˜(SRÓ»¤ ¹wáûk—–[ïg àDj㋈ UsÓŠv„û A›G€øüi>W©üf‰«¡¡‹¼Ý¶®éÆÍO˜%Ð¥„¤4’IB,¾ZÚS¶UuRæŸÿN‚¥ÓÉ"Ô‘G`–PõÒqï~ Ë•ó\¤jkÏŽ.¬½ìÔáZ}QÍŠ-Çnq;²ÆÃËs%2’„)µæJi ‰N> àèuÒÂÃËc¥ÇF£¢,>v §NR Ñh½!5¤Ágv ÕÖSk°.ëÚµkëtÒZ_—ÀüÜ0 ^õÞëq\ÃÃÃMÃÚbñò=Ïí9-"ƒ¾Ýáºß\ý·™H82k¡çýC[çsf1°Ì~¿ÍÝ»WNUW†n=p-•¨€eKû …Ü8³¸·ˆ Å‘$þÈó Yò׸>…4:Û›Æ ÁÏTØî}Ò[e‚áyÀA{¯›¯×¡sd<²mE$:çËl¬n×½O ¸;õ¤ûÞ}‹fO»8n²—OèK6ÉœR æ~cç_¾u÷î³¶!÷¤ýÞå¥á~íÎJû•“Ø®Y¨žé;¶]õ\s=Œ»Ú ˜»ù¯‰‰‰1\ªÝÇå©L~®•TÂj—,‡èàÐ2Ú¡in™Y‚T•%°f¡“ׂŒ^ˆ%fARïÅ :ÀÄ%[£‰ŸÞ=½!·×ý”YJNÉÀÊÊJq ZµáOívï:3D*“e¾|‹}SeY™M™dûÇmïC‘9GVyȧéš1'Ï\s@¦J—Þ©ÛÝl/òéØ'Ô‘%"B•Ï[¹mïžÅãÔÐÄvH —rv9º));oŠURKÚ´ûÀ¾)ƒáÃW\,Ì—'äW*+«Œ—(|Û-’;+WVeØËÃÐô3€ò¥º9þ9µàÿ…À­SgFp‰K©éDßößëÎV]èĻ?zÉTC‡ Hûó,v”NƒØ*\V.pݲcNij¸y¨¯ÀþØÈØ.{ý9n߾yU[ðþ1e“Ýn‡¤ì"ØÄÀ¸©Ú§ x´(OÄEß!ç£ßéˆ@ÓŸuúùm¶Ç žS"άš—=bÛÑÃîòœ™XPªÔOkéƒf3^sè?¥8lßk•”–ËÖ¢ >¬3ÇøŒƒ¥Q,GEÓr^½ß1¼|ù’ròäIm99¹¤ÂÂBö†ÿÂ@II‰W!öß±‚kÆ|wàN€WP®«\aÔ¬›3ÈVœ­‹úV휌&|ÐGßäO??N®~ž®]ËP“Vì’³`4ü›¬¨c0ðKW R^j%ŽéZžÊU®ÈARž^6+ ÌûÔU'?b×”‰Òm 3âéMÃÃN.Óo?Š5Bï ¢,«,KÞ{‡…G.ÝÓFîyWð@¹o×Ow¯]SC+ã|BØÝÅ$Ë9±£ÔûepöS kϾ…ŠÐ÷ZJZŒ;_’¼¬Wž¼£>,büè©Ïö/ÒZ9v˜j©0©5¯wøØÚ!4·Ì,´}ÓÒF±ÕÌlËAfZÚ¨;ã²fvû—ºÂáìq^Æ++ËÄ?%FO]3ÛdêÅûÛ¤À¼•µÑ—O1˜fz©)“ÌÅ}æ×ëÌ9Ùƒ’^‚Z“HEAjï-& Ø_i캡°^Y™!îfmv²÷;‹Çt£0ãCÝ•×îôàå­(;yÐÖ•sü ¼‚W.)[šñþÓ˜L¸Ä jO½|i¯F­^j½ïÎ3Û¦øG ¼eðyG#Pî?x9›C• ¬¶möæ’œJOª#{wZdUV ‰K÷¥sæl°d’ß'žA¯öGd>[<-ngI"Pý­û~Çs“ð×'B†&›üËk›UžFÜYU{É=ÉMOTþœœÂ6&§Ê_Ë((è]—‡Ÿ•7£ÂâÞ+¤&&j'þV¦œ™Ó_gÅIÕØ —åÉß p·~‘?v²?ZÂk£/:&~Ù3¨]°±±™¤®®þ.>>¾Ë/ÛPÜ0ŒÀOHHˆä¬R$åó¦_ذ|Ö;"Žs”óôÇ¡q°JR³kÇëpÌuÿáúyxøðÙX{(4ñ„kqÍTiPªÑÅ#5–ë-¬2úå®"„?/Z'<å} ¥ &¬Ùu,ÌZ«ò¾~º{-xÐ…€ù\Þnù]XµbˆÈS¢üãoûÇÄ÷È*+—GÚ¶Æ¥6ãίk¶íõÝè¥ÑÑÉð]Á`k³‘Fû‚ÏIÐkìÂËîƒxßoµÅñI‡#Ð.•~KÌ$ÊК»·ê«Ë³Í%È4âãÆâÀ]«Ï‡‡èqZ$ö?XóÔpÞ(…›œ¸bPÈ,ÐTÙ£N š3ÉTgΑPÑ|éést¹üÚ$Pê28õ¨Ï©åÚj]¢9qåàyÜ'è¶”\¸³P—©Gƒo,¹rîಞ\óÎÛûݜ̽FÎüz+7¹œõrYÙ‹KŸ ý>V5Í?Á>vŒª1u è¥(UÉ_Ó(i¹³ôfeéŒ̶.\>ä<Ž˜{Œšq3èFÈ╳FyrÊ€M÷­FÚ·îû{h*ssv1&æþÚFþWC//š1ª·oórBôQ!Ð{ÔÊüs^N+¥¸É £æÜ ÏÊ:íÙDœÜ¹ÀÞÊí(AÅÐj—]Hð©%#”Ä“P\)œmîýà_Ø[Æ+Ê÷;†åË—ÿB¡Ð}||^ÿŽíÇmÆüHuQO½~ÿ¾ù}îïÖµ‹ŽVP`æ×¡’$ºçuç®{L’èZÍ…Ç^c¯Ûþ5;vì°Ü»w¯…½½½•••ÝÖÕsnµ¦Ï÷ö#fT®*íþ¨SOSä£Á*I\fbâoºúD³|·~Lc3»×·#Ï,'Æòjj‰|¨{YmµÛ ùÜ ùܹc›¥½ýf“í¾Jj'²¤Á®“G|¡¼âl¿ÞfŒz¿bÕ‘/Ï.-¸—ETÅG_v4íš ³Ä¢©£OKÔ  ³„kHl/ iùê^È\2(wëÆT”“`­ÞèñÜÙe{÷î°Ì{#äç50ö}Á¢‘èË©ñ²U¬¸¸¯úœ|„I&Šc’aG"“Ì¿Š¤ÀþSnû4ÿf·É`'¿x“Ýþÿ JÛl¿òG!1Sž†Êeq#„%Ë3c#c’r奸1«,Eþ#‘P—Q©Ûͼ4R·Ô`’Ú%‚VÓüKÔÿ˜$ àc‡"ÀÛ¹Ñ`Ö|(ᯗ¡þ§­mN~n„9’tÏêÜÌÌ‚ç½WXí^ôékžP\-ì¨Ó±é¥Ü4R—n_T{*bÔKƒiÏÉ£úä_îó²­Kdÿг6[+t|Op]ßâ=Ú\õߣÂ`£ÓßQ+øÕT¦‹›ÏÖ¾È[V@dm6œð‡æ qxSùÏË€Ýj+;BÐ]º××B=ýœœÝñ»Ð¨jn¶ÓìÎéíÞõyT›ì lÖ¹†°e„OÏn,X2ûÆ~Ê;Yyè ®¢ù<¥!kf°ûæ}Áîõs™Û®?‡hWÑ9¡ÔHŸ]ŽQƒhÎæÃ]@êþY`á¸IÁJЪ˜Ík…–+6VµÖ‚ŽÊáÐy´Yhæ˜%,üÑ@+ÔeLjä#›³„ñª™Ë§Ž[Œ:Ç,Q×2WÉQ¶[l‡3Úv’ ´fÍó42Ðýç¶›ÍÒ;‰ÅSëJÔe “ ¢´‚{\ͬ+KÊEEDD˜%%%¢p{OrÏ¡ãs>ÆN¨+Í&¤Dßž’·ÓÊ}Ù~öèÈòJ–ÔB—”P!}¹ïBèM7ü5üóÇãëÎ@ NØEKr>h¬}| wÂò…KŒNÌšÊþ‚*«”§X€ÁhI<~uÈÃ,ù}æ0Ä!2 Ö[Ó¹‰û.HQ¬QÚé(8g¼IµÜaœ6EÖ ¾­V›l=™Ì#A×å’RìU‰ÌxèÙ{7põ¸>OQ*«ª¤vw­›à•ûÂû÷ï…>ýÔúf½{cX\ÁR’VPñ³—… F…œQÞç餴‚¡D¦He¯´ÝºÎDW+—àVZ,‰ÎϺmáy”×jéÑ»Wµ wˆ TŸÏçÏnÚ…^Ê•Ôlµå çmÿ’ùUWPHœj·ÿØ"Ý!€J¤-…iÙ0D‘ɶw;¾bºº ³«€»“ÝÄ;Oâ×ÓØõËd¯Ýæl¶X[£ñ½~«Ï ­YÓ6ýfV”Àí°@ÓÚÙPl‹ÀS ó„ä/Æhy1É®lx¬G´Å©‚+M7ìùœ[ý5Á 1º<m»~ÒiÃÄ(0£âÁ7_ÊK]½aµI<›÷ƒ–"0oŽé%Ù~Ãn×}êù9³¨¶M35ÕJ´L3S‹m2óuQ½"\ÜŒ´ºçÎY}©\ˆR&M_³aÝ¢’Ý>,[>{Ç•3¾ a%d{Çûß Ø€üœšâ‘ó\ór„ÏÛŠ€€‰+4NAHT€=¬ñçSlH;µ·ÊÂa‡Ç‡QUROÚçsÔYŽMNHIˆ€‚²Þ¼å%¤(\™ ¾DûË?N«…²Ë«ÏŒ°€›‘ÀÖÜñ!1IO²iƒŠoMõºôò ¶Î&Êþ§a`ÿ帨‚¥ÓÁÓÛóøÆv’D¯¼mØés7fñ¿yÞtÃêõv/ÓŠØõ "2´ÖŽÛÑdGt=@Ç8w²úƒˆû‰yS%$ªó* |4ÞhÛ§My®G"íð}–]>ˆS¶+ÍÁÝsÃ>k$Jásh³ÐL˜%7>a–Øé°æŠÜyöâÑcS¸= ³ïD#fAzïø÷î4z·ƒÉ¨ëGÚf‰æË¥LNo¤½DYMU¥ d’ùÂîà“ÌÈn”Âä§r;<#_Rs·1k!Ñ¢¨>ÃÇHópå\8¸W±D‡CßWiÈ›×6‹é¾‰«àÜW0zî¤-"Æ“©ŽÿÂõ?WyráÓŽB`ц ÏÞ´X‡îcîËë³ô—åÊÚY›ú€¯I‡÷ÙKôQeí‰þ Ð\¦6 ËóWp MVÙùãû9ÀÉšÉO/Ê݃›‘ ž•Úî9@IDAT%åòxûrÓ|J€®]Ä2@AioFö“A‡¸´z÷û—oPm@£J@ Ã(”å|QÉ+)”—®¯$!IˆW#¡ñÅÏ&n¹þ?#»‘˜'wÒ%Ú4 ¯ {¢ ¢ƒCëÖ­›¨ªªšêèèøÃ-ëF¯¤ Ñè4å×/^n¡HJ&×0©’Yïô]O_Û0I:ÉÎõd(ütŠÊò¡yY9zžŽö†ÞÕ çˆRKÔ>¥d LŽ L:q1µXF¾Ë£ÔMÐ9©¢€Ç]­¤fêR„j¨4z‰²ëÖíg&]ß¹åƒ"5HKË5­É¢QóÕœV­y:2&|Ø'ˉañHÁ!//ñ(//ÿO7;ËHÿ«šRï &M2"$ÄQ KV'£#oع~Í¢’¾¬—Wy4º·dô“o¶ Úš1‡™Ì0z” ·É–‘W|Ä¢æ©¾Ž¾é²Þ©K±E¬’$$HeÁ4&³F’UIkÄòÂ%Ô µÜ„çj‚B*o›¦Ýó_ð×üÅ>ÉEU#e”û„ªÈ¤½Jú¼ÖÓqgÄôG‡Q©j•€ J‹È@X¨šZV”9ÒËÝ놠0U€É”,Îûøçß]ç2»>¯ O:›?äm'>ohÎÓ¸|ZKJ-Ûæ¹l€rBÃ0TgIîm%ËK sHt†€YT¼ZNZ‚G¹, ö]º¿—¿ä‚Í'ž.ØÌÞ”4cÆz>^$ÀákùËñ_Ò[™uKo¥y¬¿ˆVNbÀçDRJ–)_øJ«V¿¶· íNKg@BQе›4³¾8 öœf?Kì牨k¥kìK–!T ÒaYQØFi>úD^|ì<Ú,4Ðr³ê F%wÀû 6™Ì÷ï1Õä´"Ôº}…BDqâõ©Óôcú Væ‰ñ„jò«O{µÀ$sýNÛ€–ÉZ×@<•²,ãåíYÓ4oÏ⥤ µâ-Ù £9þ¯à]Ýx!í”s’ô¨jG«¹v›Ü¯±}Ô ’Ÿim6Æ€ë*ìÜ÷˜_G×–»¶ž×û{<¯AóÙ5>óÙÆÝöûZ¢Yª‚‚¹õ*Ÿ»æ‡Ùu5F«®þúg¼q^â©3§Ý™jîæSÿÔXYèls`› ¿‚ÆL’6fº¯‘Iò[è£ßÊ÷+¤Ã‰#ª««áºÌqß¿=Uà’ÏÉþoÓ •)JäÓhcoù¼¾6–Õ ‰'nžÝïuå¸Ãàý~.¤§¤ ôKùw J¨¥ïxÄeý]ë—¥=NÊ]ëqÐCßzšô3¢=AþWõйúd#ÛÓ{¬î><¨âà~ù‘ŽŽ‚B]©×ŸÜÖ’-Nœ¬kÏd0%ˆ5´–ÿƒ°ájâe5g„fÑ‹•/†D)G?y³ ì›<ÎMX Ù·tçz£E·^|ÞrüçbûyÊì‘mù}28`’<5•´ßÉi"œdYYQQ!2ËØ<¦ŠÁ@ßy š $¦Etïñ,­‹ÊÐì÷áÀ,.¯úèäYO«¤{g{ìp¿x#éIäz·{—ç¸ýÑe…‹ûT¸Kbî%Ÿcõ0ܰaz z¿4Õ&mݹ=óJä×l¶»—Ÿ"ciwt- n²]Âä*RàJLİâ(_¹¿ì¼"…¨wžÜÔŠÜq»zƒQF“m†G0ØØ=DmÅáû# -§§ÌýwAÖ/!Ç·F#ìKËUwãS†4’­‘(†e¥ÛT¶r8ªÕðΕjua¶Yb‡™¥2ƒ,²ý€·Úù¦¨LD^II6‰U ^¼LîŠÎ–­á˜dþ¶èÊ7™d¼¯y¯”ƒåxM/š¼ÿóêCz$kö»¾rÖOÞè|˜öBïmËê}íŠXÃ%BÀjŒ~ºøºs˜`äðÉn¸Ó» %•¿´öÕ¨‹ T¸ŸùÂrcXá*)üyDiÖpAyn_&è4wß•G.*ÝoehÇÛËD%$jýÎaƒ> b³•zµúP]T7Àík:Ë÷¾Ûaa°MŒ`†{ì¢66:0üì ¾øæxåËúK^>yòDùßÿU=zôh”8Üiîû7’ü¼ýƒÃÂn{„„Üò¾àý¡iš¥^}Øc J¿^ìþ" ™™ylsî;¨…ÕÕÑM@3*YZX¢\ŸG2fÚ”X¯c4? õA2©ö ŠDÏf zd¡¶ÀÉ“F–ìšÜ›½M¶,©ÞÝÑ`A›F•¡€8 fg`¨޼¡û1§Õ•º2ŽìÝfÛâŽá/“ºmÚá +Jʪ(Jéd·5òÄ <êÙ$¢< juÎÔC3ŠcP Ôri ÊÊjö'z9òólˆáG(ü¢ü·Iôë-ñ5öI¤õ"]øuvG#ùçr‘%e“‘ëË€ÑãŠN$ %ÛRrRÄ´xÅ MñH¤ã#F#ðë#ÐM3œ–š%º ˜\r²9‹ÅUUU {ÀŽˆ™iXR˜M*$Ù®òÌÆÜ+‹jÿ–I†ßô‚Êô·º°þÖ–h lÌD$ ̶‰4s¨ŠÌË/Bv©®Ý˜®€…42žwžlB4yƒé.¿0Ó]ìÅj£›â¿6>éTÔÆÍ. º3{2åå•׈‰±ºvcHP„O]L¨ºµ© šÏhÐF&‹ÖÈÁÅåëlù}Ÿhdû)zá߆œþÓ˜€Æúè ³¯áϰª¢B ×°®×iØ×ô–Û¿Ó[nkXŸ4IŠKɲ귩å¼Öµï×;ƒë1‹¾zõj4\1ãêàÁƒùÀým&[—-S³ Ë)¢¢¢ :½šÜ_†Ìc>®ãaĸÑÿÖ]¡3A #-‘ òèj/^»ÂÑÔàÕÇ÷ w#Ÿê1`ô’y²#Ï]èD8{•önßsn'F”ÃkƒÕ"Ï"VE™r,†îhŸRÑš°(ˆ (ð–Ò+”sò¤òdÖ«øUP “ÇshŒöD¶2á/ÇsCߤÝ!AÍ4 þ›2&ãytx·åk­ÖþO{LÖÝ‹g†^ óýœmýy¢Â=DGRyHhðyFy‰`ðùÀ¡åp3 ¶ðŽR¡[L6¬!†ªÒì¡™“‰ï?ZÄÙÕ'´ò¿Æ~Þâi³N3|œÏlßo¾ìM^Û›‹Ç& àŒFà—A ÝB3DKÍ$¸s 4Ûñ+ÐÜÐB“JòœÚ[ZžàµUG’0\w±çMÔª‚ 37ÅÜ8¦3àôQŤÆê’€&0‰Ž0µ£ÿ ËÆx«Grðù©‡¯x(**’ÑÕÕ½×RmlÞ¬x. ´uç䶤ŽVµ~ÎÉ“5o<ûxÛäÙí«[N‰”ìxtãʦLè«;Ãl°a±ºU‚Œ—- ½õâà–ô„{Öšš÷Ø“èêSâ¹b0ø;N53_r±±kfÚ¿y´?ÑN–º“trrn»‘µÛÖËž¡=ÈûNHÛßRwÞ¬;гžM”$*ÌöòPUGC?žš€ë9˸¬r½^QÜþ6Òºq%ø.µ ¯CÆOÉ;¨Yïõœ÷»…Öä¼íó,á³µ˜òðÐ Ö žpëvsÞ'\¾që_s²xé"Á˜÷ð¶©ö¾sÄóã¶ÙùЄÈÏßv"žÿ¨1I7GÌõb“<òçÇ׌À¯‹@£æÁ_·¹¸eŒÀï†@=r †¹ègiw­1‹Ë°Y¨bÖJû³´9VRsÕý/ Ym´îû•³>í—,꫹ t³Ù"Ceåž¡=•UB§LŸd‰\„È’’y]…ØeÈ”þ8Šl—¸Š¬zñI©Ðýƒ Ìþ65âûΞóª"\'Ë…„DxW´1Ó-ÍuÇÔ~ðóMð…Ž'Omí.K‰û’ðØz£ÝÞH*S@rÄä9¶U†2÷º¬›*]/ž†Ýô@3E¶GÜٳǶ 7‰ÉÓuî"ºeE¹#¯Ýƒh5xÚD–T=b†VÏ9â¸%âvô{¼ÿ›¥öˆNs<6O§b0¿¦iþ•@ÁmÁ`0ß ]óÜ]óaD½ü×Û½¾ T]/Î+!‘ÅdªÅ¹î ¼ùž…¸)ï÷ VTÕ8w>ÀíЧ‘úW©w¿hqqµšÈ˜˜Zú@¼OÍ}žëº´*7‘–—a!Á•dÁ©K·v2h´]4è $&#SM¤ñÖOäæ?’åUYWâLayþòctMré.V\\•8d ##^ë¶¢m`‘õÔÀ¢Žg>Âäo´i ÄÌ–F»Ac^ºˆL]{áþ65Ç#Kø#€ø…ÀBó/|sqÓ0_ a¸”œ|“žc Œ³T¼¯Æ¥}Œ5ÑÑœ`‚ZOí’µg—ýå–#]Òä…­ƒ 'QÂm‹[à6ÔxmM—‡í’i|ÝüÆ)µ<¶é:[Nƒ“³óxl-'8?F#ðß €…æÿw\+F#€èÀ¥°Ç¦)ÉI¢É)éR Ê*´Áêë¶î„1IŒ@[xýúµøíÛ·{´µ|kËÁ%'[[¤^þšššú>ôõR;ÿâ¿®¿½-l/ÿh!‰æ‚¤¤dÕ¦M›>6—§½iXhn/‚¸œ#€øïxunI}4²ïhý=Çœ‚’b¢¤ž¿ýÜmò¬ÅŸUäÅšwJìöià¼OPÙî´9ºYÄ*BÁ`~k°Ðü[ß~ÜxŒF#бSÄ™r’ÒÉ R{ƒ(ù¸Ï Kȶ.‘ýCÏÚ`lVÇÖÖµ pÂÛ;˜%¤H…B³Vc9pF#€h Xhn j¸ F#€èb£.Ê9í:á]\ÁP¡ŽÔžíºÏÙê!ÚÖÚÝÉnâ'ñëi0L‘É^»ÍÙl±¶F!ÒìZ:ùü¡¡~"õÕ«¥¨lµ‘cHÄ_¹ùè €–šÁãgºyì±½›Üʼ뷞¾±j·ûì¥Úà ÓÃÄÿZ¿7Xu¼Ûúi]cQýÔúf½{cX\ÁR’VPñ³'`¡•KsÚ01 Ì(.øàæKQ¾ÒŠ‹é£W;4F4 ?6N§äúŽº|ö°Q–8¦%Þßè°ß5+¯øO!aj·Þê!gŽ’…¢BÎ(ïóô?RÊÆC*{¥íÖu&ºj¹s&š†²·&dæIN›2ëò©à«‹ø·,'0kŒYˆõ-¶ÓBŸ$XÓé e’¨h–šº–¯×1— Öà‡4Üó¨Õ¤UŽh7>b0?&?÷TÔSÌF#€h5åÙ1d+;·È|j…š„¬l<‹NS~è±ÅíÜ/'ˉaO=J¨L5YY‰ø j¾š›eäƒäQz%UˆóÆF?w)eÔH0™LÉÔ¤Ö†<ð¤VÑéÊñ÷ƒD¶!o³B2¯¨L5&ëc²4ÒÊ/(P&ê|ýâå5°Î‚¬wzŽ®§Ç”çˆRKÔ>¥d ¦ˆU’ ¨<“YW„Í Ó©ÊÁ—g£¸@¿ E4H³ÿèñÐuý@6ë·§CùÑã÷ôV‹ÎüøÒd©©ƒyrÌe);ד¡…Ô*µ® ]£«¨…jžŽöO> QĹs'X@DTAóÍZúŽ~çýÆ R8àVÜÇ=Cô®„4ƒidÉÞÉ7o]Ü@Ê|%âääqwƒŽ±ðã•E¡1þ•úŽ.˜:y²­Ã¾“ºl¿¡,K‰Gy™åôZËlKðkŠGƒúˆßØkž=géjoÕÔ\r(1#‘¼DSóи9ùŸ¿Ná†Y ¢BC•îE'È5xx:¥BL´³¨:»"L#€Àü¾0€Ÿ·0ï3³Öš.C&äÂZh&Ï3¸ƒ.4tMs£tM¡On˜ªyÓ©ÿ³w&ðPm?ÌÂ{Z´ª§ùk{%DêIÊR¯—âi{h“J¥^´R)iAz-¤§´PZÔÓâ%iSI‹²e²ŽaVó?g¸ ©(^çô÷ܳüÎï|ï4÷wÏýsÀp£ž¢¥¹ll-ÎÝ ö®­#JêêèœJ¡T¢ãh+ËkèØ³g§Ûr™–(N„Ï)+`sÕìÖ#ÉÔîÕŽ’n(N•ÈHeC£“¢ö?Ao5ÙOÅ£7û +åW)(i÷Ö¡¡‡>öã—î>|þس»×¼}àm³MÕþÉ6'‡a€Ú{–tÖ×±©eE¥Z¼’ÊšÁ !4£Øé·Úéjzñ<¢ µß˜Iª@ÄH’þƒ‡ ( ع×$áÊ•­~D¥ÇOóユtl Ÿ¶,°Ù/½Ȁþ#õ¯«©Èó;ëôHï ;à™L«µ).øøsݺ¥LY£Êë×w­–ôÿC¼4Ž·}ø¶ýk„5Ä0vO€–ùzË+ª ÉÊÊòØì*JC?[.GCÝ|™þ¼3°Z}ˆg‰|ˆÇÈË’rËØ•Zù ©‹EðàþSmT–êln!/ªÒQ@F*:òjŒV'Â甽“x},˜nžõò­QŸ8ê0|JÄ?u”•“…ƒmT0uŠñ®!ñ£/Æ\ Au~™>ã2†?æÃ­z©Ò§ÜÍ}ù¤!º~8²ç—ëwŸyŸ9¦ß]>0Ø£§Í›íãdó óyšü¥kwt»ô5| (dÑU<<²ö÷ô‰ô8ë ŽÔ à¿fò©Ï; ¶%é¿|ÁÌÜBËž†ã6 ò‰\ïdïv==¯ÖèFõ>Í ”•äß4ª#‚ƒDU•R.Ó{RuG>–ÍNírïmQšrŸâ•ÛƒÆô”åyΙt![¡ëóÞ×bœþ}rÐ _cÎ?¤µ[ÜrJ*UhÊ‹_½ñàÔRen“õ~[ÓJ1#W­”É%¡SƒÑá^Ü.MŸÀóÎêý-…íp\·plÄí# ìVÆlšc,zW°ôÕM™Õk·ýö #WWJV©²›ù¿[–Æv„ÿË^$Eª6lßNO¦táD—U¢§\vÍÚzÎ’Àcwôƒˆârq¼}ÀFsûº^X[Lh—¨ÀØÂºÉ `ÖS,.\J;èñωðÕœW®._\„ÑäÕº1”zvˆÍM/˜ç1ÓáàDcÝ 1‰"ƒÓbŠ•y´8^ÑóøŸ9˽—çÞ¹r]äf!Þa˜‹§5ŒÑë7S+–¬rt˜‘¹#$Tˆ *ƒi5+j4äÃceHí Œå@c×ÖÕݶo¯®ÐhRÐeeŒ©AlJæyÇÛçÿö> SºîFì©¥9Å\ý‰®ým‘X´®€­egï¼foXÈÆéGd‰š«ù#‡qIúWTpDd ’bŽhÝ©1˜«~q)MÇMMbo7¡cÓµäÜJ—_¬õ!ÿœ–”lçʞʤâ—ù´–OwÛuóÀ’·/_ö)Ìh8p¯'¢Töêý·–8šÜo`û¯=ÓÛ¶ÐÕG-r——¬’J ()¹%É)v,S¡ ª^d¼Ö}_Ð¥¨ï–E±뎪>»y|ÌÜé2/S ã€^`öŒÑY`ƒÛÒ¥ÿ–T©Žœ<ýoêË+=®Ý<9fæ-αٴ¿-Ù±JRû$% Ø~.lŸhŠ*%2Òè!·æEÈ|‰ÛqßñÕkÇ«Ž `ÿC-æ̶1v%ÁlB̹|&O§«î°}ë=fÜßfÎñÃÇ!ƒYÇÐb“—³Õ ½»pänHd~V†m)HI/J’¤ÿ¯›Ðuœüç' j&2>»Ÿ&2ÒPE¢oM±¶jBGQãøO£ªýºÐøè5ÇÎü½}Œ .Uø\¼“­J\3í‘Ó¯_¼ys‰ÚÓ+hÇX o¿ðDÀ3M´.AS9õbÈÎc;öhÂ<’ÆÈÜÐ=Þ';ÒxBä²#%+ͦµÁöýžkQÝt‘Á ßJlÿóOZ•ðn\”æÁƒ‡u#"ÂtÂ"Îê”@GÂ'™äƒëò³óç.:´y®Eâ…ãQà ÉíûÕ´o’{âÈÖC=hÒx”ÁjÇ4·ã‹‡UÇ0ÿ78qÎÍ‹;¸„QJ¢È)WÑ锚›¬ 8unÅÚÈâñ¤ä”•«Ð’f( µp+H¶p\}€û¦ƒ—Ü€†CuXt§Læì—ïD3©QÛ§YÂ!¯èC¹*àðk˜"'vdŒWäÕ¥:ž\n©½ëU\¿§”¬A¥ý¨E”€Cë¶/½O‚GÝ©ÉKssˆHõ;îþïéSþ= ¦ fuþi[Ég´ÿmü¨ÅÇÑ'€æGŠb˜&ð5¨@YMMâÎyȈS†.µ_#ýsë6¦ËçÊAåS’b:ݼ›'ô ª­=ÕHGäò‰ÎT@§Sés㬗לœ¦ÚlN}ñ2­¥£xÿ½xUI¡ Zá‚?¯_1º×ï!¼>2hÔV¨ªu`€Œ|ð‹çŽ ¥¿ yS”ýTöFrzWåžzª¤¹µÕÄe"ñuÒaUd0£s!ûm[ä¿=6:è¿^´Í¾é¡JS¥U\R_éáSÅAìÕ~Fþ½»+•%D =ýï”ÿà  :üû©ö…ì*Ùzð±Ýnwc…1LÀÚ%á6äž;wNÿâÅ«zqQû64Åm—ÂJ· !ûm΂5vÁÛ—Ny] ‘n×’†÷ó]”víXØ”Ó ±]þ\ó‡sàž=®O˜ÂFv‘Ëãg‚UK‰|‘ÇN6ÿ[ÊHر~ar‘´´®áÏ¥&&& ãQÆE&&Ê WjãÊ•^kV®ðºò¼TµOÏÐ]¹yP+?Õ>r©*ùGÕy¯ÝëʪF]…<Ú>l4·ýk„5Ä0Là?B€ Ôà(zCWÿHçp7Z˜@IÊ¥aáQ7 Ë… ˜¾Ò/Ð@U4“¶R7n¬kéž·Ä~Ø œ,èè~Ž™¼øowKݼÆÔ!©+æŸÙ¸fìKX@cä¯×ýWn¸é:Yº }Kþðwâ4¨(MÓnõsõïç\ç¾tåŽÈ…h’âú]+v4Ѿ4­¿ÐÁ²ß9$.3åŸa%UØäj€¶Ýb÷Œvwɰ’À]Є۶mÓ mÆë^IpÚ•@vv¶’²²2ðÁÀÚ ’†InÜ¿íUEEÒTEÕ*™kÅïÌ­íõ»@öKvÞµÿƒs—YÆ’4e¡1áŽ6Xx:9®¬QœÏZ®Y¶+ÃnÚ®7‰ó†G]gÆùdÇ%••Õþõ4­Öe¨ÑöáÊåŽë_sk§¡\|Þ¾`£¹}]/¬m#vïÞwùòe•F²qòLÀÆÆ†kggWü#À]ÇÚ é*@¾ÌU%,ô¤«¬ªZoj£!ËU™æ•mTȧ2È€F«^ü£’ߤýZŠߘ6š¿1pÜ\ëèÛ·/~Þ·Žt,h»Ò“ïìU1Y?ìâ%½î¼\©#'ãû 3³~9XGƒý9’¾´ìÃúK µ@=z?adÜÙe(8éL MÀ6mêr`e0Lø<‘¡SBBB¢ÿNzÐéój¶·ÒЧ·N|5µ4 >Oíz8ô˜·ßN§oÕ“‡õ·"*©2PUU¯RU¥µò¨±¤¶q&Ð4<ÒÜ4œ‹ `˜À7#sHkóž#;Ë*yššbžó²U ¦©~°²v;ϧȗïŠ>íP•©ì¹ñ`ˆ¬Qnn¤ž–çŒÞ¶<Šñfî¢MŽâKÌŠtç±r¤\æ¯~‘óÁÀõÔ:éÜÚu`ÚX¤:oÑê—ï#ø| Ð±sŸ‹h½Ît®ÐÅþ×5¯ËªzöÔV»þüñó9<8ËÌÖnÑû”+&Þ¼·!ÃÍFÜÖo™1ÕP©ØÖznT™V®Dïó ŠFÉ*tz1sÖ¤u§…o+‚ý‘Wé~ÿȹ£‹ÔA6˜:~VG±×«È¨ý¨¬×R¶ÖNQU‡…­Ü8ÆUz >/,~ÙõUN±ÚÐdÅ– ‡_Œº—•³ÊUÑPú»‡è>¾ÇàÄ-8u+ýú¼ ÖÓµI¬¼^\YüçN,B~Z›–8Y_LÉš7Û{Ë,'¸-9*[ŠÁ"§ùniog M+ä:¾X¶u×ÂñµY¼’7ÒÎN‹6½‚}@»·é³Øºgûê3û×.ó)Ö<Ø—)ê¯S&~P»2Ä5Õ™Ñ|~ðš5¢#^¤î ã&ÐðHskPÅ21LøLÉ'WÀ芘\Žê“¸Ì"=>^ ©•ÝIƒ{ªÄ2™tæO³=ä¹vïÑRf¥Î×…«”hrRÍŽup.¬‚€Ã’°Ì üfíõ$+ßV@¦1å)‚òœ¬‡¶v¿ÌÞÍ…Þ£Õy9¶<@Š4a^~Ö[ëQȯ´¤¨pPÜÉ/-퉇4l‡Ï¯Tˆ?~?3ßQT6»Tk×Ú GYpÇ´b&S•}—[l‰òÊáî…ÁÁ±*šB>_¡„‘9ú%þÖ¼’2iFq¹~ÑûÂ"D¼J©bØŸês@}{žvÇ#3§ÜB†,draþ«ÖâÂ6²ß嘔0òG—W È00Q}i2‰ÙACó™ ܳ­„ñzô™ÄÇpÛâp=)Ý—Íæk þ jGìφ….ÿ¦¿šGQé|¸^¿ÝåÅùúkç¸Ü*Å`ÊÄé72r ,åU:Þ§‘̇Ig}® Cmë¦ôçç©ÓBÑuSïš4P§ÛÑJÑ5ÞPüü@:ŠuG1L à‘æV€ŠEb˜&PŸD…îïó$«H‹F“âð€ðôZ|mÜ@„á‘p—1úšà³ÓwaìÆ…3³þI/˜·kÛ® GÃöOi=ã4îÐÖ{ð°׊•[˜±õTR¦ïlßÀqn½ 6¯];†K£qø••2}†Œ~ñsï²Ò7pKnirGæùëç-U A9uüô0»XãnòE”G’ÕÈ=ý Ì+Öc&Æå3³tb“3‰Ee§¸ûMZ1]/Ëj䄤¾”‚×Á“#~éɪ05™™Fl/\½½±"8•œ0¸$1\õ÷ÁפÉêÌ 7ÏŽº|³öÊÀ¿cyå,@! QÙ* ©õ·á9ÊCºž¹y~”JIº´©…Ó}>/&…út×·* \_6µÑEÏ|÷‘ 5‘G–>ØuâŸðóÑgÆ×|-è+§õS­àumÀY#¸Ú§2±šá–ÌåñD{MTñ¼¹íô„Î]nguД÷üâ õ~•]­÷ýa{ÜÓ/‡uYx"6ýæµ…—£àîˆâ¬D…Õ»¦‹OnJc‹É»º2JÕ\–¯¸ü!-FyñŠÝóÐ^uy¥|éæòKiBGp».þ‚pÀ0$€æ„‰Ea˜& ™9->äi5ÏMoˆ2¥Æo“ rr¨î38²iarÖ—SVTª€:°ÓÓ÷ṷ̏(}¶×²}D>‡S%‹âì 6™W’'ýw|ü."ïVfyD/[íã輋ްÝÕË˨ƒSñWD~À)  ÐyàÐðê<0rh}ÈÏÏý òй6“~~ ÈÑHy€)­Ð¿§zܧ Ô3~aIŠ‚J’ÓÉpD±<-¹ (ª*"[¿ÙD£ç‰Œ¿†¶˜6§Rtÿò¸"ØÞÉùAȉ@Þ³Û»ž‰JN´wÞï·Úéjzñ<¢*µß˜IK×ù½;m¡Cqq–þÚ«DËŽõ è7Eû*Ç‚£âö¿X¤uxÌB¸•·8kè$ñÑ5µt4ÓGu$ëO½ºË¿<}zÓ‹‹¢ëHÈ'ŽŸÃ¯1{Ôn½NHm»G))©ÚeÛÚ®–X3L Ž6šëXà&€ `­D€–ùzË+ª ÉÊÊòØì*JŸZƒ5 ·ZV’ìÑÆÓæÍöq²yùkëPhÂ:cº¾>CÇþ’ßÖ°™Zï¤-C>Ï&Óí³f9FÖÊiÙµÿÀªÎ*´Ô·iÿx,Yáw ý´õL­—õÓÄ÷ó]0Nº^ÜŠ?» ñ ©tI ò¦BŸfÝ !1ý)ʺUãáõ ìôñN8ŸôÜ·š™øßõœŠøA9M騠 >Å0$ ¥ªªêäïïÿnîܹނL˜0a²§§gž™™YQ ¶EýL›6í¦¦¦ M}ôôôƦ¥¥EÃîæ|I—›óý¹¸&@øÚï(’ÓrßS.\¢”D‘S®¢‹¹º~Í‘UR"–SV¦W5”ÓT^ò-qÎc±¤XÐíC’._"?éb”úÁˆS332ß8 €*ˆOŽ\í§Ý¸´jxRrÊÊUÈ(® ð”TÀA%ɬêÊ}~¬åúýõ:~É÷^CCcy|||âàÁƒ?¹¡ ƒÁ M:uî7®H"åäädæèèX$''Wë $©Ü·H«ªªªÿ–à[4Ú‚m@ý¿JšP(l÷ýˆˆè3jÔ¨Û³gÏÎþ 77·Á:t¸·qãÆ×Ÿ*‹òÝÝÝûîÛ·¯ »g4‡.ƒ `˜À7#ý›ÕÔšû¶þ³´¢Cã°± Må5VçkÒ)tºîø&îqñ5âÀù‡§>È0ÉÕÕöS3j¬qà5P¦6Êêkm¼ÍÏ•Úz:~®&_ZÞÀÀàuXXX/èçÏÿL l±ï²$žmÏ×ê'-Ý"Ž ssóF€%qýÜ4l4.1\À0L Íð ŠÚçYQ±_®FèÛ\gÿ# -\¸ð%ì úà€ ´ Øhn— +ù)Ë—/š:@FF¹6†Ô€¯-AAA—zõê…¿µTþ{4‚«F§·Êýî&€ | l4 5\§Íxôè‘®‹‹K¢¢"6ŒÚÜÕù¾ íß¿¿ïÕ«WU Ñܪ¯í¾o/që˜&€ `­MÍ­MËÿ&?œˆÅÅ“U¿ îvÕHxxøwŸdÔ®€ae1LÀ$ÀF³D,8À0@zr¢â'¯:™ZM{¥­Füncåÿ8—ov1^O·Þf,-Ɇþ ì#£Ò™emc‘[‹–lËÂÚrphKÀˆŒÂr5¤9Y¡gÙÚÕ®×ðÿ‘ïw±ÑüýØã–1LhS"C§Ä§åy”ªüdéa3(—Édõ@ÐZÍ`FÝÏûBB¢d &4šGµ) XLà;”>—þëÔ9O¸g½(Hɰ–c£ù;^øàò][Çc˜&€ Ôà‚­ÞËÌãn¦y°Ù<-’¬l®ÎÀQáÁA¾‘y®Ð—¬ÜâŸË(-E¦2;usèèÞí’–U{œø—êⵡG5ºh^.È~kV wëØY÷bxÔ ¨|Jâ Õõþ‡¶—é#YJêÝ’ÐfñA+F%@ƒ)½myãÍìEÕ ·V?]gg>~>‡ è¡fv«v®_|½Féz‡¬&ôLŒ9¤µyÏ‘e•Ÿ«p'>zÚ‰ðã¶Y 1=3’O*®ðßWÄäêtTï˜ÄeéìññJ¸ùª˜J¦ÑkvYz¾åãµ›‰>=¼{Ï›¨@ûT˜ûÌÒÇÿà°Æ¹¥¨õR’îø–ñ„ò|XïMú¿óŽÇ\ ‘†}å²ÙZ÷¯Do½–‘/Û˜ŽÉyu›~Üiœ‚ ´.OªRLº œÝ: ¸‹µ£MhÖH3ü¡nÛ¶M?44´¬iq8÷G#­¤¬¬ŒW¬øÑ.<îïgà‚¨Ðý}ždiÑhRPž^‹ïÐŤhö4,gÊ[6Ô|JÊDÃnÅNÓÌ^0X£ù,2±ËŒŽóæïH¾õ(ÎÌvZ Å`óÚÀ1\ﬔé3dô‹~r@4ñQI{hô¥¨=*ò’)æ“§e<™QNW ¶o¾¸(¯õK®Z»(?+þßÑ2Tr¥‹×†;%Œ[O%eúÎö çfñSÃå(‘†vî~“–N7ʲ7³8™ÅäèÜ{üB!íÉÕNõûóûÆô<äe‡õ5Ág§ïÂØ gfý“^0oïžËSñûÆM{ÀSèžqö܉EE)²kž5¢Á>UÂ>YÍpK&0©ëŽÙw6lKð©½+ûo9|õØ»×Yý4‡ÅJæÆ&ƒ,Ö¸Úξ”öΣ§á/›Ž­‰t±1óË´L’©z#>Ò\’Ž»¶íšpt»×B|ľ’|ïª^^žB¸ù |”$5¾ü·j·#‘@³ŒæÝ»wÇ]¾|YÒ›@‰BqâCÀÆÆ†kggWüãô÷ø‚ÎÍm¦F £œ¦¯³%ã ³T'¯DJšY˜«qýþåE—â£P¾xàpªdÑ9»œµALšôó[ÔæÀ~]£³î¾òÎÏ}'YB$ëù“mNÉ{–tÖ×Âä¬/!º¬¨T‹WRYóÆSÐÓ÷N¿ÕNWÓ‹çe¨ýÆL²P¢7Ín=ÒQºv¯n tDJ6Î •¨Jêê(F¥PDrF[Y^Cç={vºŒfd4¥#*‹&ðÍ ”Àxø‡¶B@쇱q•úöíË…Ÿ÷—À9˜&€ ` €e¾ÞãòŠ*hpË`›]Eiè·»|ÁÌÜBËž†ã6 ò‰\ïdïv==Ð!A¥O¹›ûòICtýp$dÏ/×ï>ó>s(,l‘óÉQk½¼,KÙl‘ öà 7A4€}'ñÚX0Ý$€ðÉÓ@d^;¬–p÷±7UE;õ`Ø^—¼Äp­5'cê-+‡F¶êŸ;†Ø6Á2úíz)Qψ0ýîò1€Ám·Õ•¢E#°\Od\F¬ßL­X¸ åIäúƒü¥¡ž±’ôcj›’yÞñöù¿½È”®»{jiN1W¢k[$mã'`gkÙÙ;¯Ù²qúÀYâ튆•aBýðêqkhø‹Ë“755Š½Ý„Ž’êà4L u ÀÛçÏiL¾Pô6FЬPÕ·o·J<°u©7%½æµXSEp&€ `˜@kXâµ`iÍ€“ÿ|à¤9Aͤ¼ŒôÂ+Ý›$MËèíÑûO$„K“éLï-~¢!åFS×ÒˆKKºáûšÁ-MVfî r1¶™‘ÛºjT¿Ö÷\ìùO>Ÿ,nÀyÏÒmYðh:Þä://.Ð?›`]-úã[ôÇ)Ðyƒ®#lLO{g¯V£t}8Ìã‡C³Ž¡Å&/g«zwáÈ!ÝP[ùY¶¥Õ£ÚÕM7øÛ°]dì7ÆíÙý´žDuº¼‚È…LA‘.:6”ƒÎ­šÐ‘ƒ˜À·$ ¨|"5ËÑ鈳³óaÑgîÚ}è‡ïG4?ö¸åoH@PzOÚÊ|~T¡¨ME°3î¼Ã0U*àú¬ (eH?xùN4£S¯啨U¬wW©“mWC³dü"æÏ3éÛäˆâg5ˆ ÿ0tŒlÊ’’'fÁ•)(tº-&,§»ŠòPæ‹çIŠO˜·f‡›Å /£‚¤¤¦, 亜dÇ*)‘†V®N§Ô“cl3?÷–ÍüÁbò‚Åâ`MÐñÈ5DЧ‰Ç-§/ÊjLÏÕÛÃάÜ3%ŒREN¹ª®m*ð 9å Ä0Ô­ Ù­V¯†çMq¯ç¾éà%w.4ìOã:5ðø¶Ы¼äÜ·eÞTkx¤¹):8ï?Càò‰£ª fÔ¥2~ð¬Ñ—t.óÞ‰Žnnn¡èsìV†’‘ý"M™XV†ÉááÑ/‹ëÔ Š bÂÀ­¥:ïS3¯f_YQ¼ßRš˜ÁLÈ£+‹­DjK›Ò“ ”ÕÔus˶)™Ûç¶ÑZ:~®¸üO€+%%¾4^rîû#ð þû_¬A«(QQwÜÄ›yxñÂÌ¢åSo©Š'6#®ª¬Â&Š)+)Šâ= >ì á‚ÒÒûIôú—(ƒ˜À·&0ÜfAîéaÓ†*jjŠßo¿µ¸=LøZTya_žIå2ôJ^Y¥ª¡Íì(¥¯•‰ël4>\¹=(~žD{\.M²"ô”?V»ú¤€nÛ_¹rÖ†ë1ûº_™ ‘GKl©k÷½÷ÇÚ?w™õ—gýéþ‡Ã¿ï ‰Â'7.ò¾ÑÝàÑÎÖ…ïqdrªhö^þ{¬õ»ŠžŽõÚw$Ö-»°´;’¥¦Ù#ÝeùªÀI#ú!çCÖ :÷ÄVE«×ËáýhO"£ã•s„tYy5Ƭ¥ë78[ämá#&Ð|T ‰ ææãÂ%16J€Dë! :r<°ª÷Cª…Ý3~ÈËþcuúdèóêË{ç)¾r5Ý?r òqWÏ¿Ü?lGµÁŒrx  ë±Á*gëÃá·^«f¦>™/2€«k•æwϸ—aõ:íF‡û£2³2 Þ–”‹Ö¹ ^1Ízuà‘ÍÕsµ,F^†îŸ³öûÿ§J)x÷L+/ï­nú½kV‡Ž^ðB3Jg—3ÔBÖ-Ýuó=?Ô" 8`˜&€ `m€6šÛÀEÀ*´&,—øz&j$ß“áê:ïaW)Ñèrþí«vY5S¹ïo’ÞÍAå¤dº²¶Eœœ°Îy±hñ[˜¶Íî?7ž¾àìå<Ö•Aaüüu«ÏÄíqPíÖT†þV‰GkÚD²¶†›µ×ïùŠ5ENîØ\½žVÍ.Å0]k i¡ˆC3u:$U«wS3Ôkªà&€ `˜&€ |gØhþÎ7ߺž^U'|YL8"·/oÞ/¼ºÕ\u鱊ߊ?×›pç¿Ããg¸æK×Ü%pdzøðá'­í-ƒ5TU{eW× cמEªJ­Àqã¹~„¬¥Á!óG÷ïÅ20ùµ`©óȃ;ü“úް¡¡8E°~÷†ýú¨\·iñ^ÂP'Éêm0A´‹˜@kHONT í“WÜ€ ~·±ð722}^Âkƽ‚fýb²»ù忤,ðÔïdÌE-.¬ŽÖ†652z`òˬÝõü¬¾D4®ƒ ´1‚ÊL)#£h#ø?#',؆—œû¾©Y¯—/_Þ755u€ŒŒ a |_­qëm†@UU• ((èR¯^½ÚàwCŽ…ÅþJÀº*ÀsBâ¡)………݉´Ø°c³<-7ùe¤¿î[Ö ×ë\;™ÏÆuÅCåë[ÈUDr½c!£H´{òcP;qp ¡þzST¶ºqˆÖ­Éy*I´ôµc²¦ëVú¨'Ÿ`­J 24pJ|ZžG©ÊO–6ƒr™Lt%’4±í¾›R ’Í…+Ê4þnJH“yÙ`_HH´€¬Á´¶±…Š¢ž*6[ƒxØl²:ÎÄÚn¥a$£ï9‰ÃkjiööÔ³v«k³ŒæG麸¸(**¶AèݲÿO(¾ÿþ¾W¯^UFs›[›XPš&}'£Ltc­†Íâ3JãfÝ5H+HË+ÈÖ¬Wòʤ¾´êQÞëp2ßÉäÌ¡rò½J7n\/~Ñ(Äí›Øb5äge+‹×'âäoÔÑî ¢À!"øøÃà‚­ÞËÌãn¦y°Ù<-’¬l®ÎÀQáÁA¾‘y®Ð—¬ÜâŸË(-E¦2;usèèÞí*=NüKuñÚУ]4/d¿5«äW)tì¬{1<êÀT>%ñ„êzÿCÛ ‹Ëô‘,%õnI»öX´bT4˜‘ÈèmË£of/B{gC“üéê<;óñó9<øjf·jçúÅ×EY þT—'¹ÀwÉ"ëÄ”ôy¨ƒ’zϤ½a¡ÞÝ•…UžN3ÝåUü¯‹¦Ò¿™¯gTÕ“[ æÏø}Ƀ7ïmÐ&.úCûì{’òtÞìõ>3N®õ>*º ñ æfV'÷Flž†þëp˜y:³ì§¬y›óÞÕY±%Èá#šÿÓ„>øˆ ´3Ta½uÔ¥dHø…Êw¾„Í2𥤤„ªªª\333Ѭÿï¬3n¾ o³.hmfâ®ÙÇxÒ‘©ÃúÝg³¹ª¬,ï~Â_cîe[¡5›ž¸<`éË{;Ï>…dyàÏ¥kêñ ¤¾O%o†“ùШ/ES:êÍo^dhh|hx9Ìl,þÝyöá´$ý¶U~¿ë\"_ùZ* (veuÙ`Ìpƽ«ÕgBŽ4„øžŸ ý³Oô•;[ÃFø<º“²ì1Ü0ä¢ù}ZäI@IDATµÄMkg³Z Gnbç<ÑËÌ¼çø›ÓÊŠóa~õ6AØØ&™Åfi½ÌÌt”WéJ*.ÔÊÏzhû›Óò§ƒæìs_p ž 61ߤÊÊÍ´ôX¾!˪¯Ü}Ü…P \>_¨ à°jœîyàQÚZF%³RçN|ô®ŒånCtlŒÒð’ù/q²>Ÿ”éK’UÈUS!ÝÏÏͰüÍvF¯‹—Ãì²ß嘔1y:épçA%(·TLîϹ³îeæ8¢75/î$¥ˆÜšÞ¿ÿ@#Óèù€]]ª@F–ž/G5F¼É)² ‘…L»TËÕšCæ—#í¨ •Âç˜@»" ÿÔt¯¨ÿ¶³.Ǿ fÍßFÜ &Ð’Ä×fVÞ^g ”HµþV#TŽ&NZa…Œ‡;QQnt×n#5w§ßÌãé–e\end$6B €µ«ó>tæ²Ùµÿg®…nôI<ªËÚç?B4:Gh¯ÞŸÇ—Gÿ·±Ñ\wEp¬ý Ñú /ܺe‹ü÷‰—‘ıýõæ¿¡ñw¾‘ü7 â^´=Ü÷ɯjÖf¦jëß(f0#m©ôø;ߤò» Êߪ=-¢’vœúÛgõ¢Å³#ÐDPvó=W{XÍ'ìk2£ÀtàÕ„+ãäå«•p@üGLV´’†<Øq:Ögíüù³âëÉ¢€SçìØ¶|ö-$,+%òÅhì•›ø¡ >~>¢¢"•óçÏëzxxˆ®áçKh‰|þس»×¼}àÂRµØvÕyùw|ü.¢ø­Ìò?ÇÞGй¹ÍÔt”ÓÔãuV d¼a–êä•HI3 s5®ß¿¼èR|”ÊN•hÉDvÍ®‚Õy2`Ò¤Ÿß¢6öëu÷•w~î;ùËõÇQÅ/-“¹QX±àZ‘ßäܽ÷¬!w¢ùмúrsä«_]Ñ@h0£rUèï@VõKN^I¥tu]¡È—™0ŠI4zžè„B¢ÿUá Z]ÿÅÚ+ ?üµ¡k‡æ6t1°*-G€Úi$ÿZr²mã•@ð…›Kë竃{OÞ(àf|(†£{Ò c'5¾¸Q €*Øt0&ÖCŸÚœ<·A[ª`=”åSYñ¡ŒEH(‹¸É£Š3W‡_›¹\«#$Ú á…&õ/ãÍ! ¦¦VøæÍ›®Í)Ûze(`™¯÷¸¼¢ |°â±ÙU”>b3jwù‚™¹…–= Çm:ä¹ÞÉÞízzÞ<X€ªÒ§ÜÍ}ù¤!º~8²ç—ëwŸyŸ9¶Èù䨵^^–¥l¶èK{€á› ²ï$^ ¦›„ÃÁÁè €<ȼvX-áîcoªŠvêÁ°½.y‰áZkOÆ6ì·¬œl=·+~µo¾˜¥L“ýBãµZ¦"8xúøÐ®4² )ît×ÇY MËáïRUçJ’[}Sª‰‰U3îQÝ. jõ«‘[ÅÈã‡ÊZ*8‚ `߈6š¿hÜL;"@¢µNê _ª=‰&/ìD«<ûR!¸ÞW——¯$‘HÒ‡Rûý÷ß_%ì‹+S±…u““e+*8jH<>`%ÅѺ#2˜á[R…`g``¾Ó°uu·íÛ«k4š™ÌDllê §ÔÍïÒ.y¸.a—>¿o††]©*Z©Š Jô H‚m”½}N;xøÜ:Ô¦…\‰Ž\†ŽÑë7S+®BqÉ,¡?,ÂÁPä´…ý´äâä–Y®]ºrñðnžIá@íß¼æ–,¥Ê€A;‡gß}ë½{Åï×vK(ˆ|:ìl-;{ç5;÷,ý³^_ û|Ö#‚O0L  Ô¼êjA‰X&€ `mŒ€––Ö»„„„mL­zê,ñZ° žfÀÉ>þûãpR*‘^Øc¥ûo“dàä·èíÑûO$„£"¼·ø9ˆûD×OÔµ4âÒ’nø¾f0GK“•™;Â\Œmfäv®•ůõ={þ“Ï'Ë#+:ïYº-š–o:Þä:/‡ôNÇ&XÃ( ";»:Zó÷ã”Ar²¤üê"T°ç¯¿Vj«ÐRs2:F‹ fàìµÖ²KŒÆä® g8h“šZ‡Ý{ˆÐÓíº¨B¡wŽÒMä푟•a[*¶:¨ …&Ä#Ð-|Ä0–&€Gš[š(–‡ `mŽ€žžÞ[h4BÅþmsÊÕ(¤cdS–”Û**ÊÐÇ™ üBN†Ãºè# Ð «näšÞCxEüœ(„ß……Br¹\᯿þú¿ï¢nh„@vv¶Šè [#E$&c£Y"œˆ `ÿ%=zô(‡¾Ä»wïÖX´hQÍhh[ì!ÄÔF â¦òêú«™ÄWVT]- «‘õ#º²2± G]ÅVŠ¡5:&oV Ÿõž8/öÿçüáÝÇàGTKS÷ç}¿ÀKž6‹aÛ)¤ ¿g›6m:úôéÓ¦^Š´…±&? ¾££cáçv¸ÅŒæòÒ"é²²²È•ŽxòpI­ÏU—Ç0L µèêê¾¾~ýz÷¶m4}ï‡Û,È==lÚPEMÍê,¾^ä7•P=ân>ø^ršâ¸sfïAŽ4;¶Ë¾|Spm´1“rôi£êaµ0Ï"ðÕFó»Ô Š‹—mÜœ[³¼Ѻ†Îð¤À í»zÔ,õUúþ ùeN‘YFEп´œ@³Ã×Ômv#­X°½ëߊh°hLà›pppx³téÒ_X,Vrsܾ™b-Þh¶Sƒ¹D{¡˜&€ ´ _5°àI ÝÖm}hCƒõ,?ãö¨éVNa5/å"wxLtss uqóÝû¹œ_S·-Pnïú·†XLàk  Ñ.¸’ný®ùµ²p}LÀ0ÀWͧ#"ÍdÇývðï¸8‡€U.5+×!ç9=òúSuTF^AI4b@’—cˆÖ4"*6ãø5u›!¾Õ‹´wý[nøFú÷ïÿúÆmzo„7ƒ `˜&ð™¾Âh€b&K¹º=9à´ÈõrgUUÁ“Š‚æ¸z¸¹R.öö+#®eØ£4~az÷ið|ëñë½Ðù³[±ª³ì­VŽ=ú°‘‘QôèѦ‡³ÄõÁûJ² 2³Éº‚Ê)_÷ßLGÕc:>øÏ˜AÄŒ“ó!ëMž<Ù×iþêY‘‡ýûåŒ'8l»òü-í,Ì+jsôaó S·¸•¡Št"›ԋŠN“ñEz¡ÏøÉ3}/?ɯ™ÐP|çÏœ5öeájÿñ^0^[ÎÞeez—ô)ý‰vðÀ¾ 9sæ¼ÎÊÊêV‚Vhç!åâõFFf-ñ¯YŽè Lÿs˜‘ÑØé%¼ë§x{ÿR…¿w¦/\ï@´JypkkS˜÷óøéah»¦Bzr¢bhhhŸ,¸ÂÚÄ¥5ônªý–Ìçƒúòì×ɘ‹Zh d0Là¿Aà+|šI@E^R¡xNúù„öÀ¡ ¦ã.›šØg;¤ *J_›ePç˜Vr²² .ü“šûK§§ÌÙ+ÃöŠ£äpÊé/%s™ä8îÄ•5vð'±®çµ×6æNQïÅ*³Ë‹ÕbCý}½Ê?rb³Û™‚wÏ´òòòtÑ'ý¨Ý¹²ðMwoG»#µU9:‡“MðpÝß=ö´ahüûqZóÖßU[FŠó2tW9[. ŽvvÐW)Ë~þrXôåFý©W.ëÁÜß<6_8庬1ý—MóR¼Žc˜@ë000¨„3ú‹÷îÝÛÙÛÛû]ë·Øš-ðEKR°ÊY*õ[¡€>½uâKß0Ëi”ÚýFêù¢³ºö¨4:_UA)ã']Ç EQà2vhiA%W£a^ÃóÈÐÀ)ñiy¥*?YzØä¶ŽÞ [m­ó:>dƒ}!!ѲÓÚÆbTkµˆåb˜À·%ðU£3=ÝÏ+Šé›õ(eÜþ@¿Í“ÌOŒ0µ »§…¶޹tÎ~Â@µT”$ÿãHlì´ãçþcY]]l‰¾úkrò-Û)êg«ÓJ@‘ ¿Tcuw¯]iWm0Ë™^[D¿„Aí"çœüû2Ëçûîx[ÎV#ò›:u¥¯¥ójäP‚ü·ìhXïúõÛÃíådÀ¢@WB»J¡ ôûw-–¯ÜS§¢L‘´Ê5/ï®®¿ÿÝòù" ÄŸ®ú/w_[X7¼M¨9ú7HƧ˜&ð <˜­¢¢ò!88¸‹èa¾õ›ç‚¨Ðý}ždiÑhR|Ô÷ôZ|§öÇPL´åõþè£cjÒxf'QÖD×Õ¶>ÎV/‚×Îו¿ÏL0œ¢uåeBc/E€*8{ù” ô§ ƒŠÒÐJF)²ÁGÏÑh4Nee¥ŒÕŒ¹É}´âä2-ÿIÎP|ö÷Ñ„µãoG4eòùãLyˆšOI™hØ­ØÉašÙ k4m˜"êÿðÕÈ?}~Š6¾lÿ&÷K·cvi¹ûCiºšÐj¼ùâr  ¼Ö/¹~hí¢ü¬øGËPÉ•.^î”0fl=•”é;Û7pœ›ÅO—‰1¸vóñB´ÕöÒ]?Ûõ,Û°pºÃ¹»¯¼÷nß3Í«¦ïêºcö Û|jïÊþ[_=öîuV?Øß´úýuKFº @°å<<©²À'<®ÖûÆþ°=îé—ú¬ <›~óÚÂ'CØ/PÙ*hÈkôüzªšê¢  ,‡ô+I H—ƒš;*€„[¨”8¿ßi¬Ñ´<…îgÏXDܪëüXáReeeÄ-òÇê<îm›%@&“…jjjÄó}³õ”ð?¾yu¥¤g:Î?‚^A‘; {síÂÎ¥jzñg¸®x8cÎ/³ÆpŽ.ƒyUÌR5ñ} ¥d(5sCÊÁšùk Á,€3Œ²š²gºÅ¿ç<»ð¨Dô#ÜP¢.I¾cšˆäKÉôbmòwõà”VÈÊÈÈðKKKeÙl6¥ë ‘ù/ާüÜPFÝ9(ÊR%îŠÅeÃDMP>é¤×$ÃkYŽB—–2¡|¥‡<©®.¯ªQC™ƒŽ„þâi8Ž `ßžÀ!C‹FØò72šyàpÈ‘èb]µšç¦7D™R÷;R“×yÀ°ƒE›yÔüTBkušÃx‘]<ÁÖ"Í€‘ó΀ªæ:‘|PÝTèõý˜wú­vºš^<(Gí7f’•ÕèCBÎ[þºg ÿeþD•ÅЂNÐe!`ç^“„+W¶úšJäÊ!J®ùÃáT‰FhÙ5»Öå•À‰æ<¤×ph0£t[‹„swƒ½ëÊ Ù­G::×îÕŽè‡[R-Tè6D°MyˆJÃézŒÌÑö¿X¤VŸÁw”ÌBžæNyC£ÿ‰HÉÌqŒ ŒFy2 “œ¦4éñ£‹W#ˆWRYó&R(âð£ÍÈ`666ž×E¯}3K°ÆGLà{€Û»K{xxœ€[i7æÕ%Q½ZãPbn‰$¥®U]`þkøáÞé>ÛýO‡ +]N)ÂQŽ»ƒÌD¿r0Ô¡Ó[y19üÂwÝï?Ϧ íÌbg×lˆ¢~Ùå(ªuJ'~¼i*SÕÉeåYŒ²Ñ®N«/EmÞ ^ûwOŸˆA³.ਭþa6,[K®KÕŽóàmÚ‘O±Jï¡è÷ÜcÁÌÜBËž†ã6 ò‰\ïdïv==¯Öà—[—;«ÜG£ÖÏÓŸk¸ÕuΣ't‘O3,p÷b¨zÂÝÇÞTíÔƒa{]òõÖžŒm(GVN¶Á5Pò²¤Ü2v¥V>ƒGê¢F<¸ÿTÕãƒ:SUo„áÓ†²$õ—‘ú½L¬eKl5® 5 .ú¯]+y¥ÒÑTPäaü¦Ö±¿.r€]ò\voÀÞÍÙÅùúËVlv¬6~«@…È=ƒ ÒŸ½³mؾèœB¢cO O‘XàÇH¬¨¨†o8H‰‰‰W~Œã^¶pßÁÂï¡á\òèÌ8ó É}¤9 zq!À“2>®;÷`ðÚ?v%:o=,ä¼£»M2>!~¡¤dú²&èQtFôòR<§yqªª¾ÀθǑC‰¯gfß;oent¾vå $¡7ïÝŒm›ÒÿÔÆ9ñÍÓ—Â0–&зo_.|5÷~Ïž=]7nÜøº¥å, Œ-¬‘ ñ'I–Z=H+Vr‡§Ë…gV?/½v6~Jî;¨ÿ´¬ šýGœŽðµŒkâœVšu×6øâý‘Å&Ê…¿WG°Ð§æ´æÐôT“½ñ˜Á,lm£cEG É@$ÅѺSc0lÔ¢RÕA_`ê…»oÁÕÃ;­.KwM‹[+zç FA_dÛ“ ¼²·ÏiŸ[‡jIQÈ¢‘_.GCçÑë7S+®ªÓ›tzvˆÍM/˜ç1ÓáàDcÝ 1‰!¨¬Å+øË~EÁÇ#Ô’û+††å ¶CÇZäËùŸÌÜç–ë·Ä óŸô¸öÊCNkHœA÷ò÷{C¢£I²ê¹›¶x9¨*Ò^!suί)År³ Oór§?u–¹÷F4.Rå£?ˆ€­egï¼foXÈÆÎpU‘ áLhW¾ê•ÉP¯·{×¹.V[‘‚è=I¾cmp¤³muÑõTç¹—´ÄÊQx™5[W¬P¬©PZ˜ß½¸\FMSSEôº 9_ܽ—!Uù¸n•Œz[Ö¿?æwl0QEs`zÈég4\-¯H«ð–ô¬/) 9ŠT#qÙqÆÙjØ¢?Äq°±ý‘Õ3¯çè(C‘F+ ¾˜¬’ôoPŸb˜À7& ¯¯ÿ&%%¹h´© é%” ›w.æB‹_¥ ¦­½ÞÃñ¾¬ M4:Ë+/U¡Ðû mM/C‰ðõÿ›*KÊoºST0yŠé®ê2ŠÀÎfT.Š/ñZ° ý2fÀÉ>þûã5õžÝOëY]Î ‘W¨}‰âVÎ^/~ÖíºHˆ9R }QYš?ÌfFnJFeñk}ÏÅžÿäóÉò¨yÏÒmÑÁt¼É%t^^\ :öеœ˜Þ›Ã"‚i«Es‹sôcbB8p¤|Øø‹Ý,†Õ>|4dEãP¤Ä@”§(ëVùù.''nÅŸÝ… fšJ—Ô°° oc›ù¹?é `h­€:?Ì*²UÖê·yýÂësç;îA|XŒ¬Ñ)i¯=è²T7B.j1¡Ð» Gé€Îó³2lK%LÈDy8`˜@û" ¥ªªêäïïÿnîܹµ?D »0a„ɞžžyfffE óˆóò¢|R1«‚ăf§‚¢ _MI¾îQ€¾$RTMXýC#¥E ›G’R鍯oÊmáãºÕ‚KaÛlž”E–^¥ÚH»b*|^TÀŒÅdÔÅŽø4ñ_ÇÏ“$¡ïŸ)  Ÿ6mÚÿLMMšúþèééMKKC¾9_Ò…–úŽ~IÛ¸Nû'ÐØwôíÛ·¸ù‘ÝñãÇ£a™‘_óE”šó=ý<š,€&¾aRtŽ\½4¤3%…~_••é[?OvS¥¹€}£)tºxðoª4‘ǃþ«%GñösAII)‘ÄæÁ¥ñ(rôªúzÃ{£”¤'µ¼>kÚ9_òÛ¬¡¡±<>>>M¨ý”Nh…š©S§Î…»pb÷ŒOÁÂùß”rÏèСýæ¾itwwï»oß¾2Â-î«••WÕÈ«ŠÖÚoR‰DÐÇIì5 (ÁºJMÖªÎü¸nuzsë7£‰‹¨@ .‘÷qÆç§4¦ÿçKÂ50Làk tëÖ§®®žw¤ëöµ²Z«>ŸW§„ðZâŒÆÖj§¾\* Ó©b¿Ñõs;CF¶]ò=àktGráÄïÏÖ§1=ë§SáCˆä áh£²õ£”þ%³îë·‹Ï0L =h1£¹=vëŒ `?6CCC´ŠFH¡EŒ[–&‰>ó Ž‚ª52i°eÛÃÒ0LÀš"€æ¦èà¼vCn!ܶm›>5¬ñco7ªcE[™@vv¶Ü:û£Éu¨ÙÅ‹¿;þüøýùÐÊj|‘x9è ÷E5q%LÀ0–&€æ–&Šå}»w|ù²Êwi7Ú¦ ØØØp½fïܹsöãÇ»¶éN`å0L¨!P^Z$]VVBY Ò±O¾«yax-CÍ-ÃKùÎÐbðóþ;«›o‡ŒŒŒÞ$''m‡ªc•1vM@P™)ek<ã¤h Ð D$ÆØõ­5`ÓüɳÎÜ+°"wÐsñBðRèàÿÉÀzw•:Ùvå1ôÊÑÉ/bþ<“¾.r .ìõ­ªÓ<îG“B=ƒ;;èw•ðÖ²,ž<Þ79§ÛcÜ܃'¾ñÒ±ïR/(.^¶qs®ØÒ¼¨:Óƒ¶ïê¡$¶éšxç$Ä¥ é/߉vê5 ¼³Rc¾ý*ÿÀI_µäÜÌ wÀþ#.\˜ WæAˤ‰Ö þt whû¸•RåµZ¾‹—ls¬=…›8¾{S4 9z]zÓ±ìiÊ„µËäð¾`` JÊekEÀV{L5s'àÆä|nzÁ“º­ÛúІ3’“Ÿq{Ôt+§° ÓVk'óÞ‰Žp‰Pô9v+C£±r8½>l4×çÏ0Là#ý«( òiîõƒuwø¾¨B!Z÷š%÷þ¶ú+5—ؾ¡f×"·yÇvìtAãŸòšW«~)YYú§'S(ög¨/§%ÏNGDšòŽûíàßqq!«\:Õ$ 9Ïé‘ןªe>uTUV©]2PYI±6þ©z?zþ<…ýèÈpÿ1Là¿F@^^þ= ½a¿.þ×ú†ûƒ ´'Aî^»'܈˜¥Ú¨Òâ?èPÔ%7F9[ ¹SèŸõß¾>¢3rí| ÞãÈäTÑì½ü÷X׸YÄÞÖ/ðPôÊŽ.%£Ášö»•ß³ËWÆp¨´Õ»÷mŸóøjä`—À+Ãd ùúãnݸ(^Ü=¤<ÿ©öZwG‡+·ŸÛ¡YÆ’\$ Ÿß¢­\ãçžžU`€ÊÈÊ«1L¬gZ¹xz Zã»àÉ9ú‚UW¨Z•ë¶/Þê½bþíŒüQ»•@1“W^DA8-r½ÜY•*è^˜¼ÎϹ®yȸ—`5Õâ•I̵c³ÊÒnt¸Ÿ‘1 å¿­v³(K<¼²ßº½W7u„œ|úñ½jϯ¤¾T·Uµ£­wÎF/ Ê¢uÒSþšãÓU'w‡ëȇD:ãÑõqñÄ ÏG¥•Ò4ªT…¸¬§W¨£‰(­×ð©'Ï]ºd¿n¾Õjäâ!伤oÜsf(UV¶kE6ØSk0Ëçu‹…þ9KCF íä. ²2¤zuÐHôª€—¥Îã|‰2/<Ò#âÄÑÀÊyOlì‘izš”t”†tð¹8Ž!/÷€ÝD9[÷+b¢üª§I•+˸4*äêÇ®tMôÑÇŸÔé¤R«!c¦§ûùZ¿˜˜õ(eÜþ@¿Í“ÌOŒ0µ »§…Ê~šQâÐÀÓœ½œÇÖömüüu«ÏÄíqH>_ÓÇÿ³wpM$]R !t%=ÔSQ{WÄzxØ6ì'6 Š +ˆE»ÈŠ”SPQá³€SÄ‚B@B!!!ðÍ$,+à›ßoÙÝ)oÞü7$/“7o˜h–ûÑx´ÍøÞM޹y±Ýc²d?½ÍøÓ /H@L `4ÃKü…šÍ²·l™ã¢è:9Îu™/VU)«rórXêžk¢ÿòÊÙ³¦éHÏ+Rtð•±% y²¬ ZÑ-j9`¢·½UW^ÓVý…ë—ý±Œ’Uö<ÔÁeåð®íÒGÙ¯zd^dȧ=ÿ¯s–RE¦Q·ö¡uëšæ{úlZCùf¿~ßD&JVI*šÉ­×eü™%ã¼ä6üUâéµÖ•ªwMi,D¬ªqïûÁg®ÚâáqÚ²U½ŒfvÝÎù!·ÏÛŒÐÉ[CÉà'me9<¯Õ³=Ýî5ª #ºžž¬C›¦ ¤-Iú MÒëéiË´´Õ‹ðIÑ^‡)ûGO›c_`2àõæ½G¦Ü¼lÓ¦ Ñ9’kß_pϨ}ÏF€Àç ‘¯:âƒøRŸ³Ÿ' Z P%2AŽ–a«¡m|C.<Ά#B¤•‘Àdª/¾»yÜËéf™rYN®AÙÙÉo‹-ïîƒ{ÿjÒ´u‡T&ò/6¸©|rÖÖæ¹jHн„UÔT…Ê® Z·¹Mü’IRÕûEÖBƒÎ‚CÁ©áûì·ïX”µ?tôÐy%RO¿A¾>“75¤, ¡Ñ3'¤äQõKÎôúùk¶gÌX·+x!BÁü÷/WÎú›8°(µÈ@¿à{bÚäÖj·©vcTš“ÂÄÒ~Å£+áãÃÃcÓ°{‹T>›Mf´OøìGºŒ>sÜcÑéŠu¤z­Ýg0šk÷ó…Ñ PyÄEã9>Zà#²òÍ &_‹€ó¶­î œ6 ¬ÁLäËP±ÍŒÆ-Xîh¦Ã!5µ|”'dÅFÒÃËäÈF%I«QC1‰%If£ýù ›ÞÕÑ—”F^ 1¡f¨É}éTÖ§øÃ°wÉ ÆT™(Aå­Rìd­zò¨>ï‡?h9½k¹!+“d‡Ø+ %¥â³,ë1mÂÄÙG‰¾Œ:ß„^ڱؠnÓ|[{ÇG¶Ó†NÐuŠ6.+dˆ*Á¨llÍâ„2m´nÿÉo^ň}X'èbÕýÇ/~ßÞñå÷`\Ðxs.éê§M`4ÿ´¾æ \*•’-²»{yy‘ÅZ€ÀW'’’R %ŸcÏð1U6šáuŠ©AªQ”^÷ÕFoºv›‚ųû®XVì‡\¢ZÆ\B¯åR¤Zõâɲ^Ó¦³Ûü4MÔØ ÍÐSê)i]¯Ð»3dãÙXÒn†c~fû:Bþñ¿¯á ÛÏO™/ð¾6"`Zß–){VoNY’,Mv6]ƒS@ S’õ·ï“ÿϼ.=ÿúm–T½&Æx o+¥]»aA\“Œ¥\ME­…p¡½å#!Þ…P9¿âkC´ØÉzá·ù^Þ õçðÀQeëÿå²ÀÓª•DxôŒˆcˆD,.¶ÿB}Ö­ ;n&\3«+ Ã67v ݵ¿^¸˜”ÄÇÑ=HªƒviÆW\ÿ¼‹¡ý¼`ä5€ycO¨z‚еƒ™mþ·ª8xVTµR›…h£ îÎ+GNt=JÂg¨¨Ñ‹Æ5@Gϳ›7}®ã}y eEk5ád‡U+‡´Ð½¾]²³'«(’FGk‡·¾u[Îð:öϰ”Ô,®·õ«ß-ÜܼÚË“ ÏÁÑÖ%ÿ¿òD-ÖÃ^ÈHKƒ•†Òr—è ¨Cb.³¤©ê$î³"§rÞë5…Z,×wÒÆg«‘ÛŠÍ{ÖQ}zuð–×»÷oÛaˆ¯SÊíS!­ìߎÖNo÷° ç»lõ^ÉWr!õèø+¶lqÜÊPÎéSŒH›}mSúµ¹rõ1€†F_„£w5 Fó|TÕh®X"” ð:û×ÂK8¬Ù8C·ÅQhäÊèÚÍ öøo”d¥Ó2ÄRŒ…ôëjçSËêštž^:nqš5Ør+™µÖ5îrÿ´ßÚXþÉÍó»*ŒYudÞªiºaÝ_ò±*eúSE›ü®nTÖo÷¥[‹Kîeˆÿ>ûRÓ°Å:PåVxÆÜjÒ›ôt>]*UQáhéÊ4ت…T¹a+á‡}R¥ž;XMá]°š2+'=™ž!Ì¥K±¡«©¥›o ­QjBüSŒ’õÐï/|MŽâ4j¾Û½Qóe6YYY41æ‹wG,Ð.#¿¸òOxFóOøÐaÈ@|”@2.%á8å“A­ …@üªÚzuµ?ؤ]4P Sý»î$7Έ¿Óa Ew2³šS4[Ë4jÓ¶®j~9 +‘EGuë~¢-ééÕ+¬¢’?VEËÓÐÃë"?‘*Ϩ¬ :Ž ¢‡åÊð-Û¶vßCœæÚý|at@|2ÛL¢h@@ †Xàqüô”aÝw+âK‘Â`f¢Ö]†Ÿ <»}•òöØ5|¨ þ7&3Íß0ˆ@ F F³5>nÖHíAi ”h ûÛBíW P‘(GE"A*Øå€réPª—@à£Àhþ((@à'%@V‰“Ÿ?ëჸk@@ `³5 ÙlTìW\ †CøŽÀ=ã;†®€¨QþÃÚ’(€@ï (€@ 4â¢Ñ²tÜ €ÀÏJfšÖ'ã@àSÈæ&$*UýOU„r €¨ýÀh®ýÏF€Àç€ÙæÏg-µŠ,¬U€ÀW&@Œf[|\Ã,úÊpAÜÏM`ÇŽÍîÝ»×LUõsã${~………*ß¾—oÚÃé_PPjß”oªè— ‰DÌ7^511©¬ŠÚƒÑ\È@ ”…!ðÑo_ÀÇ›ØÛÛ'ãí®+ܨƒFû²Äqûýe÷Kǯ¢¢RÓÇ_iýOœ8ÑèÆúØhNøz¯ÒÒ’Àh.Íî€e PQ4Àh.KîÀ 1˜;vì˜ýb )À¿XTøåëk!ú²¯p_K €¨¾ˆ‹Fs|Àûeõ}F @à›€oŽ:@ †ÈÁú§áø†Ô@/ FóÀƒ¦@ü4žã‘BÌæŸæqÃ@£ùC&€(K€ÍÍðA/[÷@ ðs£ùçxÎ0J ¾Œ€7OÁG“/­@ ¦€è5õÉÞ@|oT¸ò:f2™mÕÕÕ- F^y媩T*ËÎÎöÃzIª›nUÕ§@ôPeLï[yÆý_]ñ[¿›S¡€<ä6Íf|ÀcVûƒ×Î,nÍQŽ¿þ±² ~ó‚¼ô×´oÄÔ7éØ3ù—šÒoÞ!tP!0š+D@Rbñ]o|÷Í||”Jššš-Z”¨­­]ã Rƒ›ZK`ß¾}mŸJç§ÕEH1Ô” fBàce?Žàe°¦ëÆÃ ôúþu%h㟗œ&ÐóÇ_[À (d—)b\4Åñq.› ò¦OŸNÜ8 jOÀÛÛ»ÚÍZˆbUfŒ¿P«ýÀjüÛú·ŸðÛª°ŒDΞ{·l¡+B—*+þZa{ó/ÌUC¿ Ÿvnã¢ÑwiHŠHÞ‚¼<5ò¶ á6sÞ×ù/2Eºz~y]O-+ï5ŸSwû)÷튅 tlÝÒ>C#ûec9½',<±fZE™í]¹ÄòÕƒ»³Åªl˩ˎ8OìC¾4#Þà —õû'žO7Rai‰ºŸvqõœÑ4¬÷ôßç9뷽؈®{$(cè‡3ÙÝ8½½ÉÎWF¾MÎà²u ÓÚ.<£‡NnsítâÊ­ÙâB¶~ó϶îXw\ïý%U»Å¾KHßé×ö ücÚ+ƒ#Þ+ŽU<“NjBúVÀ§ù[‘¹@ÔFÄX†(µñɘª JÂå­ ÓCCŸäµÕbID¢Ì×z«—nž‡—Ìl»6äñ«özš½6` D7ü¶uð3CH„žÇ¾6ûïiB)®7÷·ÇX]Ç0Cåmt“¸MFr7¯€2{²QHÐõ¡4v†XœÅ>°sêCQAÑ–Óy(âÚ mõLRvn·“ýчÉùÉ×cg¬^ó(žg¢×¨ÑkffŠ^È‘ æï¾ÜŽôŸœ™©vd÷„#A‡"lÄ—M¢„ æ’í§ç¾ËT×5qÜѺb¾\ö!,;pÛìN~ÿŽMEú¨Y#½× CÚÛþ¹ta¦´¦Æb‰ˆ,|6jÀM„Ùβd¿ß=õêù~=BO@šK€Ì6‘xͪ5w 9¨Þ˜X=V[Ñß×ϯ8ÿÏž úø¾P,e½¾{Nû>ž…Õ4í³}‡Ç!WÇIûÉHdEVê*ÚÑÄÒW÷Ô^à{z½^¼ÎÙæì³²©ˆ©C¥ ϸ;÷÷¶^õè<„RQìÛlU6jõ¡­~—v/m~Šä\ïtþÐ~ ²`Áì÷%ÿ=zðìÉåÎjø>*0dÕ?©k»zïÖ[·ö/zzÒû—CGŽ˜â}³óáÿ饾y­IÊ Ål“ï'Ξu𯅫vY …GýžŒEØ`vÞ¼}Çö-ë|ÉÏY²ä(îKNß|ïÍSwvMÍ ÚµzJ(éÒ!_X~ wèšI€ø+¿ÃG3|ÄÔÌ!€Ö@ ú éèdè`5 h´bc˜2X±WÍÆZ]ÝBB–üš›(*,š%Æn ¼7bÜ6ëÜ%‚È@즅­Œ¯ÞÆ#ª 1P{tiJ6úìÒ¹EÄõ³1#ùü,¶¢Ü îÝ2™\·koúù=À$)¾+ÿja.wÕà42Ëk€s_fÆé½,꟦Ó#}ª•yC¥²ýÀ©òfø«ýúõí“Çu»sýä­Ø^{V/^LÊ8õÌyæ}ºçÊ+¦¢u3F­‘_Êÿd£‡±I:-5Åä}»žHàËz œrE½HçÐ)@  Q4ZáŒæøð@åšK@–'•»kµ±Ž:½sÎq$ªÜ ¹fœ…´‘![¥™·±¹/ƒƒ¬—Yߪ›|Ÿq7>_É`VÔ¤»jP-©szú6—eÖBC”•+Ÿ}&F¸$O,7ZÓø™ø–ò*||AÓiœÞ¨¨¬}ÀÖ°Yáîm›ÖgK ä:ë7n“—žNk=`Ò]/ûæ!ôìgªÞ[6ˆ`â¼óß^ò6x¹„×¹½ŽÍ5 e±‘·õ'déö°0IE1WµH9MMUn<+êÂßAŒæAú@ &x‰•·Äù\&Ÿ¥€øFhØæ¤¼ƒ´ï™a„|QÒã«í÷oð@+áFÝãØ˜ÙflÔü¡ê©›tÌkÉR=?d´è¹ísþM·Í˜º€¨MÈgók|˜Ö¦AÁX€@u$P@SEê”bœÖ…ž»»ê£lt/Ü#³†qçW¾;Ž3±G‰¿²!Úqzßê¾Í"Œ½²=úT šÜ€¥ ëÒÞÍŠJÏ"Æ£“GÎNÀþȸۄë‹o÷ŽÛiR–Ûœ^jØÿùäîÝöØà5l38ÊwíäË”Š;7ì>=}ŠUÛ ™Ñzs&N]wîÞûΤýÊÉýbî¸ssCac–Ÿ^hWÕçp 7ž»gŸK’õ²C·¬—¡[òœî%e"¡PûD 6›QìúÑg¢slŸ?-æ¨ÐhÌ’2F½KÄ+]qýêý¡ö+òBÙn\¨IÉf4)Üæwuw¾H ’-B*ZzšÅ§eáÁËwÖ+ ‚ËD ø™|¬ÿ;wýû«e?'kkkɨQ£2~ÎÑèbÄEc>È$˜ÂÆý 0|÷íý­DXÚç¾·Ÿ÷†ùWª*ú^è±zŽn§‚H»‘ 6w×7þõÓûz”/‰H,*™”«ªp¨~8âptŠ ÙÔÁ3ÒÓFw>åæ1vÞƒÜåjÈn££›á•+Î`c»ÜR¼+ ‡Qvc”rk–Ÿ‰Ûkê©”WÈ`k걿@vyB!﫨”ÑÜ¢E >Þµ^A@ f ³¿Äp&.Ñ_g(Bt1辋²¬˜[¡hþ•ªÎXhêê*#aaZzZò_síóg²«ú¯ˆ|‹î=Èú%H@ Ö°^´ãî°™©÷bãâ9"ÄFML[ õØ´òàZKöµ TÊhþÚ‚< €@- @¢hXàã«ÍñC9<±Œ«ÌE&æqƒ#cµÆY˜–róóÙÒÜÛ7Ð3K,ÁõéHÇ€{Ãaå†åƒ,êgÿe7mÖï’úRrNoqÞýï‘æ=æxÚ·o¡HZÀÉP«ë2¶w;¹§ï·V§þ ^.á/t¤¥kð`ºÃÇÑ–Ýå;îwsìüOØc{í:õŸ´i¦{ïò¿7×Kò 4UYš¼q3Íœ=Î*žê Î@ :`pô ÍÚêçT'@—šMÖìçÚ ðã¼Á]“}Hø©/NÞ»Ra"Këö”ÿĉ}m•…¯™;nÌ6/…ÁLJd(“ÿ®§Ëü 7\¾kø&öm–ÜV´ ²LâÞL¼~¹îkÏ*™ŸÜ3)#GFk¹½Íä=¾'³BVvF²ù¶U‹B\öø‘°zè}Â+㌌4ó7q&ž¾îI f’/ ¸¾ë#“r)UI6$ €@­%Fs­}´00 ¾1â“ø-¾¼ŸýÎÈ¡³ xŽNË"t4¹O‰y03\à”›ɼ|÷•3¹¦1t¼}»®\0~¸<,Î;´a§ïÞ³½&[w¶'uHê=vÆä Ž›k«³ò9Š¿ixa`hQŸDÖÏý=68Ͱ¢BYû8¡è·äIã_ýwzïìÚŠ«#÷—&[ß ‹(5;®Ü\ j0škÓÓ„± ð½ (-¿´Óð€3\Êÿ¬[wâ M¶(äf£ã'¯“ë€SgZS¡4¦¬Z?²_›–¡ãæÅOÀ3ÓfffûúZõ_£o` ëÔ¾å+J§zÍÞèPͨlðO@;*sþö½ƒY´Íîg=•7Ñú·"ƒ;ý­WÜG*Ø~hϺßÚü&ܲiájÊP§³T!zN $¸ª•’ IªÐ ªF d ᇩ j,âÏ;ò»>otäÈÅùTÛÇWOméß?pº@ (Ž}éøñÅãúÎ{öì s‡“:êÕ½mñb¾YNë"f!¡(+û—2Kç§óÓë*r訹IC!UÚ¹s‡Øÿ“ßf¹q(ÊÔ:‡)_H¥U¿I™‘.‰ôAµ†3@ ö£¹ö>[ßž1"cññÙ³ÍÒÌ8Ú3žÀªDUR6˜I¾˜ÿ¢g\¦”¦©¡VÞ1¥dK¦¦|–÷$^Ìt÷yO6§~úÎ]ëN—È"Kû¨9aå\rM½ýÓ8_ŠuTă~ûêm²5É=SS7ªÏÜÓ@IDAT¶ªQ<Ê“y@šJÜ3jꓽ¨.HÏ6šIlf*~r£v]Üíí§ÙLš4éÏiöö6í›Õ;¢dÚ»Ï÷·Áìn*îehõìykÉôpVÒæ~¼˜ïyLÌ̘¸7ƒå%_>n”™™ùÁ{ý*jIÑú¿:YRa¢Ê^ß`OE¾:2Ð"‰’T •}•”<8 jjª¡¦é ú ª ¬ïœúaúi•c3«¡5›¶1Óaoz`keêÙÿ÷E‰ƒÅý ‹.MœŽY5×õzð"Cb.à=±êka¡4CÐÐISV«âºÒ\qñ{{DÀa/«óÁ‚«TÖÇÔbxvsÝ=rYéñQ6X–ry£N½7à™l¹;†r>\ ~VÅo¬?+7@à+xŽæ~XÎóªÈÊMzÂäÅff˜Üh¦d09êFí¥ 4™±ñ©©LœÆý¯J?h7Óvꢨ¸Ä‰%}©¡Ac'MÆ~Ïò˜É]¬'ó~=yÕÿQ|º ‹…xÒbW E –<’Y³±¬û¥dÑQû~#–xnp¼Bj3ÕhòRhLz¹ñn+rþ(Ñ ®€@ù²²²ÔýüüšDDDPë`˯ø r U¾Øo&ë[JvAAñwëRù5åæ[ðúôiÝfÍš=ý– Àhþ–tA6? gØh.O¹2W7²†GF-î+¯ùý>ªt‰.ÚwüŸm8Rò¶¤¤4yŒä:FFR2Ã\’tÑ¿ kñ=9ŠS¤å¬2}é¢=X–T(Üžš-ÿ<(+ËiÛñsN+‚/˜ÓÂÐê­\®@ù\]]¯^¸p¡¾H$*¿B5ÎýÑ‘?¾´:ýÇ~ÝÅúÑÓ-oüƒ zakkËû"ÁŸh Fó'A1@ xøM\Ï©DݯTEacùkcr8…FÎW‘õ5ô?_~ù%/~ŽÑÂ(kÏðÁ« Æ1 ¾.‡óÐØØ˜¬§ƒ€µ͵ð¡Â€@ ¾.pÏøº´ôú…¯Ð ˆ¨40š+ * €@àûe=§ûûÂBjçMµáR0š¿ü z÷Œ À@6@ ~Uf![©sº‹ÿcc^()ó“^‚Ñü“>x6@ P H¤*ÊÁød9b²Ñ¤GÜ3~{è €å k4+ptrZXÈbå#”èjù°—}¹¨¾[&Íß 5t€@J k£AÖÖo+Yª}àžñ C@ÚB@˜™I ¥_¶Wmã@à§"FóOõ¸a°@OÈåÇÓ½öxµz›ÌR®-ÍŒ¡YZZFÙ.t¤œÿ©kç-,,zÜá~ªnÅåBtÌǧù™€Ë\IÅ• Ô"2ôöy ûéÓ§rÄ<ËŸæûxÁ=£ üã_å¸n?1‘¡ÊåKòÙ£®:2¨ ìV„P@àùƒÀ:>¾ÇNÞ}³ïâ¡{)•}÷íýìµ=mî´3T^eÎêZºÙÚšz±†ºlqeê—_'íóòò—1ê FX[v/¿äÚC@&zª2iâô£%!çÌ„—oš¤]{†XãFFs™÷®£žÄ$ͤš´Ž{wŒfŠœø2´ÅyÉÀ [Ñb±”Kg±x¦mºÞ»Ëõ´*’ Ç^a÷œòò 4Õ5õ^Ø;­]dÓ»]:úD™ë¢y#ÂîÅÌ$í´ MÂ÷òq®ÏL(9ÂÎO£¡é5?Þ,1#§=“­—ää¾kò/è1rp=ñKjÌõ™ƒ­§Ÿ ðqâ ! ºïBg5àu7)ÈÜ«WP£n}ÝÕÓžqoE§8ºÜ>%ôH½M»îÈI˜l­¤)K–Ï™hÙ=ES‹# òðj&y 8È-¯)¼vJÏeÝ>¯L,ƒÁP˜÷î¶iÍŸ×Gõ² ";Êçkì?ìÌÿÆ4×a,H@ ¶ !ç(£BÎýø§ îUxªL¶rô—*´„ª@8ï³¾¹ÿÕˆ-B1Û©{‡UÌ|‘æ“»—½.ß3ôv[ÔùdðmO¡¸«««•‘b¾Åq~hL¦”ö±2·Ev#.†G»Š‘ÒÇíÒx±VãmlO ‘e D¦¯bÎ|—!m¯Å¦'‰)¦.Ó¦ÝNÈ•Ò †ÜÈ¥1è‚:††Ï˜XõøÇW8 b·ƒ•ÕnŸ#s£®^Úr+ú•Bè̓-G·ýA鉩¾¡~¸Dnº{•SHdR.36æqë,|ÏÏÈaÅFž©°^nR$s£{h*ÖMCW7J&roŸô\¶å„9ƒÍIV”!5'Y‰ ?NJ@ ' QQ‘Q,„œûñÏŒæ*<‰T¤gIÄòY  ð ä糫¹‹‹k/7·µ×ºyv–iadÒ)m@¿~K–mÚÒg«ëÊ@®.;ŠTÉÏÍb„üµˆ\ÏÛ´¯Ï… —çèn¶ŠŽ Õ[‘‘ú—]Ñ sEØ`ž¿ÆÍöà­ µ° ©€gú¿—),Å& È÷ÊåAÿ2ÖdÆ"”îóêix¬™jKúkÐ~àΣ^n‡É¶½Þ»Ž"²fÍœô?RFm²0Ü~©ÍÍÈж·NûýNò[v¼Ê÷˜ï²®f†ûpoÈs«ç`*ùQÓ×çôÈŠêm[ï1’ Ûõw¿|áÂ<×™ØØ€OO7ü;x¿:.cj6Ž=áÔ¼ú&Í$¤ÚK@U£°…©Iø/mÚ„43nvì¬É®àšñcwÉ;ÙÕ£Fô>nÊ‚š<¿Åf«JD"‰j¿þj„â $?˜€ùzõOUÒbØÌYíÛ)¹´m×:Ó}Çž¾!W¯nÙ¨T¯@’ª’*š"¤z÷nÝ1rÞvèœ3Bç¤ÂX•S«*(ËöŽÊåä!wÇ9¡îÅ2¥è~Ô˺䖩©«0>™¨c[ãcñØÈ–!ç‰äŸ …RIÑDAŠˆ~ç ÎmÔŒè,DòèÄUcÁ”?^÷‘ÄD~"óYøyW˾籱®HÙéY\ÄAr½‰ƒFeêõi}‰´îhi—fi'÷_&‹ ±)Ž1ä!ÚL€ÎnR¸ëèIÚ<Æš660š«ðÄÌ,zg›Yà©H@*`¢%®Î’ÒsÙ,K*0Ëúã.3Ç!Ž—feÒiÀï]«N¯±=ë:^CASÕ)ÔbÑy9âøøúdûEqþ«ð.i¡±ãúÆ“vå¥~ý,ïÄ]œxçâ?ÎÔ²Vßü{qb†Ä|ˆ}+å Î«7b¤å¥+ÑÞ7Oy^‘÷Êþñ¿— ð¢H ƒº|Ò' ·%'pGž²rÏ!¯uà¢QÞ“€< ¾ði®Ya&Ÿ©EŽð°p=~¦øUT@ b‹œæl 3¨±xñß*¼ NÆ Éã=‹Š6ÙtèÈÞ_ üE æƒ¯Ë æ!“fþi†]%*.ãì>vl™±.ûAbÜ£‰þrƒY Mqr±j ¤FVü6aá°; ™4ç϶x&»cŸ¡Éuðì¶L,à>Џ»ä/×Ê ÝoîÊí”DÈ/‡Mqz1 ûZçá…'}Ož ³i'Ë NS†½PS£‡šûX½Ž–ÓS&[÷¶§£<pÁ+»¥44ë¼oƒm“Ó¸°[»Fr/“äøX›,©ÂE¤¬pj ™(NÅÚÂÂÇ8÷ïŽnƒçl-úi§¶ ±Æfš«ðÈܧO¼„WŠ+Òȇ;~dæ…ªg €À§˜ZXg‡GiKvÛcr8…e}vø]X+ ×eæJiÚ:²’r]TQ“S¿Ð/8Ì·SQnG|¡‰>dQÝåO’òùtuƒb™L旮߶Rè,AIIiL-##Ê+19¦…¡‘‘mKI­À¾Ö+ä\&?‹ÎT×)ààÅza^ÜàˆWKH]–:+Ÿx#—W’5ËiSÄ,'I[eŠ2U´ÑëÌa|MH@ öˆT(#™üóÑó¤Ä“ Ò$Fsà— 9§ÊR-þ©‚¨ €¨€€*âpT+Œ AŒiŽÜKáƒöU*“æËCYÉDb#"HÌ,ÎPEFJsqv…Ø¿YIÞåÀÀB#šÎªÇ‹ý”Kš•®W’O®>VVº&ÜZK@µ°Ô{нlÐZ;ôê:00š«ôdð$‰R‚sJ0àC€©Ó¬ àÂs²ð[O]¹îòÛ·0Wº¿ôìxAŠH@ ¯2(I…y2ÎG¾Ý–T„«oFŒæ* ­Û Y|½Äü ò"Vaê Çïñ¶ Í¡*@ šPEJ³ÁßR©Í€Ë~A6¨éèì…—nß¶!n¨¸èÔ¹¦­¦êFsžÜ §3ЍB¨ €@Ï#@§CLòÏ#÷MZAô‡o‚„ ª Ê„ˆ?Õí¡|†>ä9fÒÈì#$ ¾/0š¿/oè  ðÅž„ÓëjañpÒ"·e…‘ÈýpYA㕬’ ñƒú²´•Q¶ÜWäÙábÇÍ]3†t¦åããÓ<žŸ[©_Þ»;··´´Œ:öDï»* €£èCª4çÉ›]·nÝÂÉÙ3à·Ò¡"@à«È—‡ÐæuËŠdâ0o8S&’ԣʤüGôW8nr³î}V}ЀªçïB@•ÍÉ×ÓÔŽýÅÌô éð´ÇH///ÿÂÊ7”ù¸Bx1™Æ0Œ(ÚRýãõ¡¯I|š«@S!ÿ¼ÉÏÏ×$ÍdåG~ª‚D¨ € HÐç%ƒnE;ˆñ.xt‹gÚ¦ûá½»\O«¢ 4ÛvꢇoÞ[3Ù:IMj\£Z‘óA÷íú‡î$»7æäá¼’-®Úí¾¯Ÿ /%šj?ù2™éœ»Ü'°û°‹ÿ ¶OCFèì©SçÙÍÛð*%½;n‰Ì:[nÙ½mÅ9ák›Óýrìm6zŸ„ËYšu_L˜4|õßoMI4tG½p|ž!îó^˜¿ÞZ7¯m©‚Üæ4†š ]7+÷m–\Ùï2¯×鯷xÝèÙÖÝy÷.2\ºá°¯i7k÷}f_q]4oDؽ˜™y84¶¡IøžC>ÎñÆ-dlÊé^Ø)=—uû¼2q¿ ,ß¼÷p·Mk\WEäáâØëÒ­¨¹B\FÍ\±Æ~lïŽéd¼ó]|Žÿұ;7Ž'm˜šïÒB#êïó7¶"CЪÛPwO¬glëÎ]î8m½Çðñ½Û¥Çã-¿§Ê·ü¶vŸ;Pÿé³i›–‡yÏžØdŠdFÚ†ÍÂø:QãfgjîÂ\BŠbÿûo]êvXûZf¦¸Óô5[m‰Ìh¬ÏB—ÇõL:úÚ¾7-6’õoøÒ¦_¿%5éËOVVV¥fÑ)6pßš¶ã>ë5 Fs•ž àª.¨ €@¥ œ÷YßÜÿjÄ„·¹îܽêÇ÷–<Á»z]špjÍ÷ãR&"¤†tYyQObRgR‚£Ãëí?r˜Ü×1нñ2î%®GL_*e «×Ÿ®bj6е0­'þïµ@K”/Ô ð"5˜šy±¶CÆÝHÅ«ŽA½2¿Ù£ðó®s]êdî^ÚïF†@`š‡(;ƒ‰T‚œŒDó½{i U ž@ÈäÇõü »‰\ÜçÒ|Ç-¡$$ŽÓ|CÈç÷üßUÿ-³™ÈªF¢$_¤yöä©©Øh^{üˆÿ¡XÌ5ëdñÄm‘݈‹áq®t–&Ï@—•Ì‹µocÛôò¿~£”Ãáå&E28º‡’àø”üÛÁ'=uô&7ͼ­u28Ê“ln` qƒÏOíéî8?Ôðè?Zy†P,äÞ pUÅ_DȤǛ˜ÿÍ|ƒ7[a©òÄX(¬ghì„uªXW„ÇÄÏÈa޼¸Xm–•š–Æç©Éû|t÷¾3[S3¶0_ ™Æ{fµÊÍûì¤öÒWé‚,Ó—¯y-›6QFÇ;?Ê0ûüüBMMMVBr2„ÿÉ3Ä?|Ò÷ôÂÉ¢S·¤^-Ég:̹Jî«{RWW/J¥âaÆõ¨îº‚~?‰DB:thzUG V`ˆ)o[…fPŸž€ùùìoþ4>Ëf«äI‘6Zè4?BÙ(42é”6 ŸtIÇ#ï éÔ(ÃnÌØþ/øÂžù¹)Œ˜ØT[b,m<`Ñ×´ŽxíEwâRåÆñyÿ‹Þ¶ýÆ-Ù¿aÁ•;žÜnǃ(䱑ÁZ)Ø(ë1dø<åþh Á~ÿã}òѳêpÇ Ùý‡v/ˆù÷PƒÕ§cn…΢~7Ó1Zèïȶ™Ø@Ÿê¸7”¸\ºu¾{ÔÉMÆË<þ ”b7‘më·Ž$³I§ÁNîr9ÿ8€3nšÛí˜[7¶^Ú8x—ï5”ÿÂ&e¬}&×] ´nÈ›äçJ¾ Ì_ãfÛ¿…š`ÜïÓîe x¦‘±É¬¾ØÈ§Æ±m½ÇHb07l×ßÝßkýa2[½ØÕûŸ´„·Þ>|2žåZìy¤Ç( “ìµsǹp÷•óžm»Ç:ä^"2´;ú_ñÛ½v¥½Íä+ÑïL: Åz®<=ú¿ÛCžÀ*æiœ^u$Æ_•º”~Lufé@þ¸ÀЬ׾ó‡6ïý{ϲV›}¯x÷:¾%jÏ}Eµ™w?ÌäÛnùi˜ìê1`š9Jë3tΜ÷OM¢ŒÃOäÏ]ÙMŽMü t×SÕmþàW¼Õ9%£:Ÿ9x3ž;wî®Î:‚n@ *Àh®­¥ÛŽŸ[ŠÐ9ÒDŠ·º-ľƒUhUøi H‘¯×QÿT¥ñ›9«};%„¶íZgºïØÓ7äêÕ-•êå‹ÒhYØèUÌב‘ã'=zgÕal4 w‰íIõ®ûß#ç.ÖÃxênǵ]©÷¾ØàVG3fŒŽ&åTªßº³w#}é½GŠ!ž1=ÔòU.¤™òsó‹¶ÛÖ%îu;uÍPG{‘”ÍN"Ûxkéi‰¨ú)>NÚuë!Ëiܦ“éÙâtn ªšs5ƒˆqºÉeS/Î×mÖêHÝÌlšBÏ<äî8'Ô½X˜ݽSÍñÅYEýFZËàŽ–v)a–vØ$ °8 o-Ô̤šµeÈ…»{•ÛjÆ’{U&S®sÏaV¡äÞĤ¦RUêÊÄåý'(ò³Q£&ØØEȸi#>9—÷›—W Ÿ©çŠLƒßdÍ X7þãgôÜäæÞ™{²cC6Ct¶á“x¾Ñ«ïH=*Qò_Æ<¯ðv܉؇x’܇x»‹ÎË‹¸É|)½ž…}õŸ1i—¿TP©Pš/׺'F*¹–­T>9W¥nDØõ>h\ßÃñ/ß(Ë ×í»vú¯l^E÷,už©VEŒìí¹Î+¸çå€Ër÷™¡ãl’6‡ž±GHM™2ìEE2 oKŒæoˤ 0UÔÛrDÊÇPäææÉ.öF8ÊÀ3©ÏR7ÌodÈ OçåZÍøc¬O_‹F‡ƒñÏô”,só6.Ý}‹®ùî8±";ÆþAP‹ü·{©L#ôäqã\qòT;¹Fµ!g:KU>ÉÛ±e²ºÛ)$à=·Z³Ù=¨0ùi“;ѯÔ¹í‚fÎè]Y?€#-/]‰öv¸yÊëðмwöd‘!é@ߤy ™q4flÜv¯‹(—tŽ¿QxA /àkÉUÇ3ÐÙV.‹—ÍïÒ¦î­ó!^yØ`ï´¤­¼jÑŸÒò_Ù?þ÷ò"^0©aЈ¯mR'‡¿8Lã=¤·™×¥€0ùx-GóÒQÊb¾Ú5õe#ñÉÍiK—ò"®^ßRV8e˜—ÍW¾§¾ ø»¯Ù¤š»hùÄ1¶˜Sp'4Öº „|Ÿe£Û¬ë‘ÊáïJ€ö]{ƒÎ€@ \‹œæl ¿óÇâÅ«ÜöÉðì2©ø,ê‰É®Cû ñlª(#ÁübðmO&^ÀFÊT˜ Ѱ)N/z˜5܇P ¸à•”‘2:›™|Ì÷Âj¼lÅ3³$O9QKÇ™:f]ç P÷ƒÏ{ƒ™­ÛàÁ¡C»œ‰UO¹mÙk¢GGËé)“­{ÛÓåzx%dˆÌu¸-ƒŽaŸ^RŸÉi\H\4ȵ~³6EÆŸ*Ú}ìØ2c]öƒÄ¸Gý峚âäbUÖ8,-ÿ‚W2Ž"ÑЬó¾5¶Q›Ùû«±¿/R Ë £Îƒlçϲì\}zñ›ËQz­­Ú·oß'::Ú/±¶ŽÆ¾Î{© ± »3”ç·*Ä;Áaë³SÆÕ‚З …*™¹RšNñäp.ŸO2Õ t8%~ >*²Ó\.žHa"JÕ¯H‘#¥1™ªåêYQ+Jm¬yc/i‡åó³èLu‚²ˆ ¡Tª¢®£Sðq%Ò¾ô*3F˜ñ—1Sh!åÇÑ×l±yòøñä,±Œ»Àóhq¦Ùd\©ÙÙŒ:FFÒ¯9®Ïyo®W¯ÞÒààà°¶mÛ/ÒüR†ÐTw ,h±oß¾lpϨîO ô@à'" Š8Õ ýˆ9ج1´ 8¥ƒÇ«ÈÔ+jðA¾*6–U+”ÿAõ 3>ONyú—ß–ÇU^‘¡ƒ]’Ë+ûVyéò9ýÝ ¨{ën´|£žq{ÿ?°ÁLäqq8Š5“Ÿ#Ú ðU€ÑüU0‚ €_F ‹õ_¼ Ý'›3¿ÒÌõ—i­(KŒæ²Dà €À! Š7g)ý‡¨ PŠ,,…n€@ €À‡Àhþ ä €@RÀ=£¸@T'BtÌçts5ÝúÂÖ–¼¯9¡:’èBbê;!Z¢kò 8ø¤]|d˜VÄÓWuû ûÊØ@½Ü…Õm  Ï‡†:0??_ñÃRÈ?†€X,flÛ¶-°C‡Å!"+£ Í•¡u€?„@Úçåå/cÔ`£o]›ûóÖ¹Qh`CâUŸöñä¥û‹•Þà£6¼¶ŽÃñeff6Û°aÃÝÚ:FWÍ$päÈ‘¦õ°Ñüº*#£¹*´ .@à³ Hë¢y#ÂîÅÌÌË/ÐÔ64 ßsÈǹ>z†˜u1Ÿ©‘ãévLAôi…ë¼½ urÔDIõäqÆòùšû;sÀÿŸ1e·ß&êÜ ;¥ç²nŸW¦HjÄ`¨ Ì{wÛ´fÁu23}/Ì_o­›×¶TAns.k×ÍÊ}Û†%WT„¯UlFL÷Ëe°s´Ùè}RJzw–fÝ& _ý÷ÁÃ[Ó±, ÝÆQG/Ÿ§[…º)aÇôæ.÷œ¶ÞcøøÞíÒãñv×SåÛ][»ï\>"dä;?ݦm/f¼lø*1Ã’Æà7ïcÙš) ñîd’<õ.óz…`ƒ™ŒÍëR¿¸[Öüw7j¡qGË}‡¶9ŸC(ý1ÈöPZ¾ºÆ¡sþ£›”·š´…ôã Ðh´‚ž={fþxM@ PBàäÉ“xÛúª'ði®:3h€¨2·Ev#.†G»Š‘Ò×ÕˆJãÅZ·±=-Á;òµ5Ñ RMgµ9¸ÐeÏñ,ÈtݸjlN²¢#Rcq’Õ™Æ ÎMŠd.ptMÅm4tu£db!÷vðIOg÷#íIÙ|Ç-¡ÉÙæ¤¬—ýïªÿ–ÙαCÊL³ñNtïxV4T€w¥K4ßë±70U$3*Ä?©gòãzþµÈmDUêŠó Q¾H“Ÿ‘Ã"ºóâbµb175-Kä±=ŽpˆḴTc $â,®Ûò•%¸,™ŸÖ3)!¹?‹­žG/Ú1ïpÈV×áŸS¹1á×]Év~OÂ.é%à1²t“ëƒÁ¬x‰À_ ¾90š¿9bè €BÂã\6˜ç¯q³=x`ëB- E*à™FÆ&³6Ú¿W‰¢Œds²œ~³.G±Iü;x¿Ùœ„©Ù8öü…Sóêsr 7¹¸ôZëæÖÙŸ\ån[ï1’ÌF7l×ßýò… ó<\g`3‚´^£më·Ž$ÎÀ&o eǽº’-›cnÝtâ³b›g-ôwdxÛ½ëí‡ã,Dc .ݺÙ}Ý‚‘ò{iŽP—äW¥.©O%¦:³ÔŒ‘Ccè ÎÝ ë~ùœ÷5|_ Í× c—1é9“6E ël²…Șìê1`«ëªp]Õe¢[˜×9ÿ‹HÙ`Û?·Öf?o2FH@T•rϸÿ>ÛÏϯ~õQ4©.𖪹àoX]èQ- H3³ir7 ”‡Ü焺k)EwïÇÔíkÚ7Þª—‰ëÑ«q CÑiÉ>REš)*šØ(Äó°©d&Ñþ ö¤šßŽË9ÒS%û~#­/‘sGK»”0K;¹ÿóÚ¹ãÆ¼vÝzD³q›NB>g‹Ó¹¼Ü|’ÇÔÔ%VqÝN]3ÔÑ^$e³“ˆ!ª¥§õÁ™ªÔ•‰%Ä5¹ÜDgs’ˆˆÉ($†r6”ËVÌË+ÏT‹sÅøsJYYvõ|q*ìðiÏ­#rž%Ù ¤lÇ Œ/Ûîߊ@¥Œæ%K– ©S§ŽNýúõÉ/c€@1ìHo„_''Nœ˜Vœ @”&€CE†ò>{²cC6Ct¶á“x¾Ñ«ï¶{Ï̤ޮeÎn#.øÎS)jW •i ”©Ó¬ÀÅÉÉ*K,–¤Æ­;¥írEÚ¼Œy^YvLIÄ>Ä“ä>Ä#Ü ¤R6)ã''éáS<1Þ‰%Lgéð¸ê”N¤Æ×Oa×û q}Ç¿|kð%ÒYê,ùLõèS£½O…¡Ø»7åÛLë7ët¤Á—†¶@*¨”ÑÌb±òííí÷ïß?½Šò¡z-'0vìXV^^™,‚€@˜œÆ…-¹êAyÙV.‹—ÍïÒ¦î­ó!^yHwú«í[ûEÙ¸­ñ/þôô¸_“ùÏzŸä3:G÷ ™8;jô”•{y­lm]ê—Õ‘–—®D{;Ü<åuxEÞ+ûÇÿ^^$K¹uù#zYF—”½³ÿ/,ØžÌXë›4$3½U‰ãVÙºRùÌ0B‰OnN[ê¼”qõº|öw÷a’æ«(ËU¾–üþîk6©æ.Z>Õº+¯9W3XA¿O´=ú¡@È@|;àÓüíØ‚d €@U´ûرeƺì‰q&úË f54ÅÉÅ*7òo­;q©±CÚçwx­ûú¿¦’o¡7O:üÕGÝÚ5’{s$ÇÇÚdI‘Ü¥BkGËé)“­{ÛÓ±ëGHÀ¯dÔ´¡Yç}kl£J—x%dˆÌu¸-ƒŽÚ¼—Ȩ̷]<Û-wÓ¨lÝ.Ö¶<®&3¶ _¨yÌ4Mv,é‹’C®‹“]¨ìáÜG¿A}¯û¼Pñl`Ðâ¢a7qH‘kŠgÕ‹åÀ@à;¨ÔLówк@ÔjLNýB¿à0;©P¨’™+¥ièȨEl‘‘ãÚÞbTöíÈQÅ÷½ÎÆeä¨0Í çf9IÚfò³èLuŽRD‰â²L)ÉT-,.㘆FF÷ƒÊÜw´œ•i9«¸¼*uþ •‰cô"¦z¡Ž‡D’+N¥å4)¼ª¤ƒrYoëÙ¼ÛÖ³‹ú— Ýn«:‡†ß¶%‚¶ëènX,.j ™ñx©ª‚Ü|º¶¤žž†òµf˜Õb 2 ⧦2Ò3ó˜zõ ò ´5JýŸV «¡`4WÇ* P{ 09œBN•<#* CéT`dà2Õïú¡X±.•N©jRpÙK1µ¬…\7­:Rªnj4™(QeÙ<‡×ÇP]£ÑÚõËÿèjúS¹†Z1iоgÓªƒ¼¯œÝF›^ð<†"35 ‰›7Ô—(sªÊµ‚õ\ÌšW†5³ÞTeÖ_K¯ªŒáGÖ£ùGÒ‡¾@ 8èÔåËís¥R<Yª[Ì4W·'ú €ø d<f‡ÆæÉcŠW„=Ø¡vE Cïk5`Õ¬Áƒ·>HËoŒÐ[ñ4…cÓÊìɃBNîjºïh଄´,\ÆDFMbf,]î1¼kK¹ÇE¯Õ¿жÑå¶~9ÈB#rÿÁ³Ërò 9ì:߬tß²27̧ùvŸ U255!KÀ?eÅÆ cå. 9Èuö¬Ixâ¦íûZª¼ kzñÎsÒ‘‰cœ÷qß<Ã{÷ÒÅÓ®b7ܶ¾±yÔ¦]Û<›a©> [ô¾¿kÓ‚sd½‚L§2kÒR§‰*{ΦÝÛæß¡ÍYîí¨¢j(k7Ð×ç®…/ÓDò1˜ã½eݼ` 2¾S‡[ ÖWM­¡ÈÑáݱKö¬¡bº¾98mdìõö µó“’²$F ­f黼7xéávxÙ,rÁúÿ—*äê4éCé!/*óÇbĘ#Z4’/òÝvx‡Çà®ã»“>Rcc:á é§‰Ä•cý²U£¯?x:˜ðcièò޶ßëdoý(íéάô o¿bç®-mY‰Ë­š~?6¡kN^GMMCh`Ü<ÊqÃæ=¿5Ôúl×’2Cø!·`4ÿìÐ)@Ÿ›@⫺ŠMúeظݔÁ¬ ¢6yíu¾zïe=K;¿gK…Á¼×qìˆÃa¯'”“"~R¬Ùz‡IûŸ.Øê¸l\—)ïžq“’’ÌÈs £êŠÒÞ4vž8ê(u°A———Àqw°ßß8ðÒØÎuQ~Âó—“°ûBÒq/³âzxk¡øÇ!FZ…ûâ¶ ±wºOž´DïÚÙí«¨>S °%*7š‘D¤ò:>±ñux–˜¢ÙŒ•”ÿë„PÚ´úaeùBŽM[ÕД·ÝÞòQÊ»ÇÜW±¯ñ—‰ûÆàlBZZc庉ñÿu`µÐŽOÅ2âÑõ§)Ǭñ Éû‡Œ«÷c‡¦y\ôŠZh\Ò¶äê–ϪU}ƒÅôëÛ7в_¿ÿÎÞŽ´QU _SžÛŒ8'à ÐÇmÕãWÉG×XŠî|¨×“Áîn\½ÌómIWsfÕ}žÍØî”¶Rqº÷Œõ¸@Y €@õ ƪô³°xØcиCòé¿ê¡V‘BtÌǧù™€Ë\2­EéÚwè¤ÕO×jî»*q+âWªÃn}»?¤®©³VÃ_%ÖÖÖo‡YõåicƒNôîšêñ"ƒYE­¡p‹Ï‰I{6þ5[«¨ÁYí›[D¹ã| c‹û»}vN2Õ ó)¹Ä5a§ÏI½‹ÝrQă—ò€,L5•â—H×?flß¼Ú~¾:ÕŸÛ ï½¯Ë”ºEyÒ¤ÇfϳdØ–¢ú¤—„rT-,RQI RQ«'\îî5cñ”®Tþ‹‡Û+®)Y ԸÔÔ#¸?jŒ†Fœ ë¶hæ ªÝé“=Èuèٿͨ/!v³&RåÔ¹I×Ñé=ŒUïS÷¤×fÇ:Í™ò§o®ÝýÿZ··µ5çN—e£n3êh‚Óæ…W®ü3z@niû:ìø„w m³ÊÓkfû:ï3‹:P5îwÿvd¤Í)÷ù3Yi(øöõŠŠkäé«Í9Yé4Þ»WªïÞñTsD²b‰ÖH: 4@”K€‰ÃÚ‘ßÑe"I5üL@û¼¼ü·oÝãG)OŒ ‰X\IeÀù‡([š•Ò寥 -)£pñ^¯Ù=[5vèûgÊâ)ÝŠ Oºùàe_b™Zhó÷M[ý&t\l½–êdìbÇÍ¿µúU¸ÔiÊ*®F—ïº#[$5b²µ’¦,Y>g¢åo)¤Ýã¤Üßiÿ/.öµm¢¡ŽýG-ß±fþuÒî ûŠöGýCwJ‰gcn@Σ1éÔä©"ORa¢Š½Ýì/S-ɨZqÝSˆŒÓó"SU‡§¬WQó¢“}|´ÅyÉÀ [Ñb¼"Åâ™¶é~x﮹§Gõ² ’Wù|Íý‡ÙsdÓXò™“'H24zäÊ·‰ï-i ŽÀqó®1C-L+) ´np÷5˜wnu°hæø~øƒ–Ó»6¹­,7*h÷¯b ‡½Gg‹øéuåLÔÀHSLÕmÓÉü ò¹%¿ÍÈÌa\*Q. m¢úêȼU£tR®cbšKRþÂTrnئýM…UÔÒ”{÷Þ·uôßèUª ¶#*Ì“qJʨceÉŠkmmN‘>ü_¦H*jªØFä?­(I Š„j£?FwÞûÐç֪¼wœ‹×.>K’bׄÚ~P›ª¯t~'6qŒxz»Á3^ÿ=nîbIŸvçöõzÇyOzŸ!wÉ||cÀÃwƒ&afEÓ4´Ëmóv%1òËë×ïtY2¨×»âü"½èÚm âÙóu>!«HYžÍ¾ð7>ðõ¶]MøÞgŽÌ5ÓS-AT, f\PÏê³´MyÀ±™µÆ§¬ÁL„%c_ŸqÃ쥡9½ÝaȬY³|fÌrÝóÁ;ì'zÿ’¶Ÿý]Škºþßt~râ<C(r_ÆÅM,dk&Óóó5“ãÙŒ·[: ; ±˜uõÒ–[ѯ*@>«ì‡_ vãÉ:}]¨4^¬ÕxÛÓB\7á]b_ Ý4>þ­¶®v”Ë 8ìµ÷ò=ÃØÈ3ZŽnûƒÒS}Cýp ®·{•SHdR6“´ËÆ;ðÅļ˜Éf3“òó%šÁþž±B©JtØa½ý§B‹°^Ú¸¿—q/'–ÿÉ'ASþëó4>ÙFݰaxÓFÇEò>ֆį^fͨW¿>KÔòEšD/ï°hÅ:¦R¯ã¼ÏúæþW#¶Å4n§îV1±¬'w/;ï Œ¨Ë`s’¢dHÅIVg¢BÅ}z“˜n©Æ(HÄY\·å+ÖèUI¥xÕ¼›&¦-ŠíÕ¨¿-Œ+õ+µ:tzñ‡ÍËËäóòð÷j¾•‰ÄJ»c&Ç'P¶l% 0«j1Ë54ÔåÀÄ þT²œƒø¢qd¿ýU<À~6½2ò“YlA÷kûD1­ž‹v,[¿GÑ—š0Þ*êƒîð"Á•“Çï˜8qâÑ¡-ýîc—Umƒ‚^V6¼ý~ÁGu0}±ƒK’²^—?ô?ùç÷3†èê­Säò"Ëym°ÌÕÛ¦¤QÍ»ú"£ùì‘Óý©!?Ÿ‚‚Æx¹/ŸAùúæ=眾þŸÜOHCS[þmž®¡ÎgS*yþ’¶•ìâ›V«éúS8 ü$ÈÏgWs×^nnk;¯uóì\Ño¸ÚÆý¯_² >ëÙ‘˜é±Om3ð™úun¸ýR››‘GÛFE¾XCü(ç¯q³=x`ëBòá)ðL#cSX”Üû6.\š7yPûùóÕÎ öõ9=’\·ì>x•ï1ße]Í ÷£Üsë¾ÁT»±NVWþ½8Êo‡çgÑý'¯5Ïû_@Úµí7nÉ¥ æy8ÙVšª·åïžýúY.ñ:tÐyƤ!§º‹Ð½{Oõ‰Œ‚üBÍzÍÚ¾þÃÆzÞ豓'[µk•™{åââ‚ù¸u&ç{Ÿ‡‘I§´ýú-Y¶iKŸ­®+¹ºl¹¡P Ó£ÿ¼ßŽø¡25Çž¿pj^}u†Üh¦1ôçn…u¿|Î{™®+ækP?÷½ }_uZ ö4b…–ã¡ñ–# ¸ÒèéÓÿqLø}AX¼D>û‰P4Çîomù?…†¹hëòS‰%+½Vq߸L‘_õêbZì»ü=G#•æÍg£;aÑF$’…¯Ï¹_E‰Š õ:ÍI~kÌÏÊ‘Ûntív”|”I_ªÆæ÷;•;“«ºtitN¡O.š?nÊækb´ÒÓ“é¡]¾Ÿ2ŒÒU•^™Ë• ¦¿e[YYñà…ŒžnnÛ=<<6ÅeÉÔP9z½¾}@¯wïÞg†ÛŒ;‘eÈZ¾Î=øüY)äÿ‘$š£øENÍúûî2”!}QGvóìÿ­Tý®ÃÓw¹¿Ÿ1ÊÁ{?AA—ðTfŒvYö6-¡¹ÏO‹i# Êq-)# n¿)U´ÞÇsÕ¬ÓdÑYa^2gŸÛÊíø[\©4ËÝu†|ö­Õ a7#˜[Ø ÿøÒ€—ä_ä¨ÊMŒôþ•M/¼Oe|Ósñ„¯|óέ‹\M¤ÈgõlOŸÕ•ïüƒ72Méœ2ÙHfɸ‡¼4`–û©“»6ù?{÷×äÑÇüBHB 2¢( ¢b‹‹ÛÖ:ªÕ¢â¬Z7U·¸pϺwÅâ¤.ÔºE¥ÎZÇ몋ª"Š  {$!$yï2 ÊßÓä7¿mÿ÷ÜûÃð¾»ÎÏß§ý傞ÃïÒý‚­2ÐkFàÁ@÷lD:=æßÚ3<‡oÏž¦Nûþ~ŽfBåäy6\r[¹‹MýðìÙ~¿~:Ž ^JOZ7WñŒdoר%‹<*ÓĬŽ}óÜð‡uP ]1$U[@ãÆŸ=Ò/«¬íkÿÃRfs‰E%“xMÎTâÕ³íþ#ÆxlÛw´®²î€Ä›ô‰IöåÑÎ2*4,¬Y\ÆœçTòŠŸþón›.üRu褟¶þÕŒ̬,ö§˜§¯:¹÷²ÿ…"•¼/¯z9”ö}žºþ¨?[?åÕ-‡2pú–^ìX·LÐÀk6Y¬K§[vf1]§‘ÝHVwBLxm¶ìÌ7õ/÷ÖÙ¸xÎÝ® ˜YyqtY›Yn½vÐ>hÀ–¦a}¹xd„.`V§ »ßlä IË“ÉûÛÏÒaƒ*ŠL]8Ó‰þY³'ýó¦óĉÓz~šCÀÌ4n\ºøF%мM’Û³ÑdÝô‚ŒùÃ4èÓ¤1%¾GO5?Kß–7g¢gÏÞ½ûy¸8õRs-•Üô²Ûúè©z„×ÔÒ*ÈÂLôœkï:jØšoë†åmûõîíñCÏöÚQ(ú?T½?}³´lNiõêê‘Üà `:ŠFÈ«‡M2ÃvF³±•*,Û 娏{/q¾pá÷ÆU ’ØUiL ÷‹Ž½Ž¯Ù°µí’…“jXïÊ’^ÛO¾pÈ^sw3ŸÉ'÷dŸƒ¾m®)1§~T š=f̤'1Îv-œ–\¸ùg£vÖ™±–ÖG)WˆtÐ×”…ï›_ÜTqäÒþþíkfoWd=sË·a¯Ñ‘5GÏíҬΉ¬iy¤U_Ï5{8Ã΋L…ê¿lgNçÐOóO/cf±6-ÏàoŽ€—ñ"qÕjÚß…DÄ'ªæ½Ç½pr´ÌÒ‡ºŽŽºVV«‰™EF™™­àS‘f‡î!ÂwÚϵ#nÎ 3ó’$•ªc•Ï{FèþºOˆ ÐéýoNä‹)ìpiV«j¿W“oN\¶g¹»ú¿UôKù•®LREo5&caíäsÔÇMÌrhW2§&wûö…Cm´)FgÌì[3›. Ȳ–Õ­#Í„ öšxêॠƒµ?tÃÛN[Ù׺¥ôéM›èSf/rsnqäÜÉ«'yúa´WôYôνËÇ™òLTûf÷¤3&+ü¡]M£´å£¿ú»úÏñ$VÑ€ó¾¼?Ϩ·Ê|¯Á> _9yìÈ@ú>u¶Ê…—CØÿltfù¼AÖŸ¸*DÀ³egÖ®œ8ïð2ï‘—BbÚä²egZ:›G,œ¿oËkX¹þóµ>+fÚ¤†p'ŽœúËK ¯Ÿºhy—ók<ÕKÓ$Kö/¦³Û„…Z˜>Ÿ7uÁò´üô˜ÚOÒÞß~]»ð T>iß¹WT^zúòïs“<&Kã‚ï}›L3ð-lîÚh§èòóLj«êÛÜHtž7eÆø¯«ýuâH Œ“AÞ£éÒ­ñr?ýo¶S.ž8³Š«×°Ávf&ÉןœrýÔï3$Ì¿rüð”WqiM»y|ÚO—/§Ï¦Mïž¾õ‚\ص~ïìÄ »óÔsši`š%½<£™ëÌ%Bc’¾zîìžo郊,Í¿7¯¶ÙçÐ!®QÕˆ%+¼]ĦÂgáq’¦U­lB?ql•¾2ËÊÜÍ©IW´†4عzÄÏæFP¤z”ÚÈØˆ={H× _Òp›þÜæ¬ß4å'v.cËhcÆ씢WXGµÂ×ßG!IÞúæm/)5K_—žf%i~”²´MLlÞ¿k®$y÷ÛDöÜœ±¨f™®ûkKÚÚ/×úO#×ô³Õi52öæÍ‘Y~ƹ†ªÓtpO?ݦÓMÑ?fûÃï:3|1Qäºk½Æ¯¿Ýk<ÑËkFûžôñNˆý5E*7àY(Åfê¹Ó>º 1ÒLˆ°ZËôÀk'ú¹8µðeûm„½Ç܇þ‰bÙ‘Û¶":ÙÜV»T G LaË¥°F.Ýç·÷ŽÁK—Î])ú‘Ñ][êߎi©Ï M@IDATdÿäœ7M‘Ûr(ÿ»­¿`þ– ½ ŽÐ6‚ï”úêöE«›AQ–¦ÚßœØdv:Ÿ'Ãΰr³çs<ú?phÚ9±÷7öþ¹·?§ÿèrá¨ÈUm¬þ¾zeahtÒ׆æIëwlt×46|²é·ßfÔ¢#µ¯ž<rH0 ˆ›÷<çzxaZäÉ#§}RhÐjY«É¡“†Üëáæý_6seIQöûvíÛËfû—x»uùO“õÝÿµ±3,_[‡št4WFœô‰£ó’Yz®¯}èN“›gî ìBËgƒëçÎ ¡£âŸÐemôÏå´ÿ¡öÖ>å›S>œƒ*¢ŸX:`Ö¸ÓdÝSûeY²¼ô£,ß´ÈÈx˜-óTÞ÷š÷ö~±y¾ÇxKírú9¹"Ûèyt©˜~ ª¦°ó}ÝFžÓ-AÂŽyrž`ÎÊéÓMÙÝb^׎KXZ[[iÎ$ÒõFCª°ýwó*yYåe[4k¿³GHÞÝtËθ/ß}Ì­GËMÙS4j?ÀÏgö`:*ž¹ rXš†M9Ñͱ˩ý™¹±@€ ð± b¤YÓµfÎn'Ý<“é’$q)©\öÀI%S‹tËlË’T®×Qrä|GO…BAÒÒÒ8ìIK6x³{¿„Øhºœ —cQ…>«{¢NO.§¼ìrnË¡ä´lK^—aóýC—ùP’¼^{_ûóšé \ …|ߥˎgq†Mwœ=×Ä¡€óÙËKºtüwš…ÝÝ3gö W¿Yê= ºyÉôÆãgÕ:öp}VËÒ8Ï.›<´×‘«ÿѥꄽ |–[õrxòèÛÜNÝÇÜM£ËËÑWdÿwŠÖÿ³6­¡!Gý]S”Ä”ì=·¯í|—>BâdMtV54$ÙØ}v|éÚu>µ h «Ÿ… /@3=sæLÍ•’5·R©Ô½ >ë…<ÑüyLY²É Õ¯¼¶V¥R•H=ymO~Òñx<ùôéÓÿåÓu¬ó“/?i‹,hÎO¥H @%-À#Ÿ~b&áyR²÷¾U-òÖ&ö¤¢B’f•[êÛ×õ9ów䤋Ϝ'õþ*"·ôºërYš{= ‹²Ïý~xüT·ãØt7ß¾m؃ބ®;­ÒÖ¯K›žÎ­ddÄÓÖ¡$Riš„.y'‘¦Y)ÓÓ+C~ß½H-ÅæYÐíQ.»ŒÚqöLy`ÍÀV8-[¶´üâ‹/Òè@Zž¡Ê­F\[À”[ÝÃuÚÿB5ƒÃá”Y¿û÷ï›ïÝ»7iذaá…Bø@fÍÀÁ%@%%ðèÒoâñó¶ïù¬¹ã/Ïïß/‘[×°oºç«z¢{‡O\YE “´î¾vÃ’©ç8)¯8ÃGÏþïÕÛÎìÏ`¡i¤ÛÔYc†tþ"Êkø`χ‘©_Ô°6ûß“ÐJúÆ´æßöŸµ~Á¨ËÉ)Éb]t¢«Ïª†õQá/¾•Ð@´Ju‡³;ý]dAÂIß.CwÈLë>;à¿u?%”Ó¯×p…ØþÁo;¦,f³Ô”rÜ’I®÷N×ëÒí Q2ZŸYU»«›wlŸù¡_iô³¯ŸÄË ÌÓ”—þz4–•ǶŒWŠk‰m‹nKnœ•±¦´æt Ñ › È®sçÛÚ«×£Ž#®]zîKn:Ñ}Ò¬sþi‹ÀG п¾*iÐÓ¼ysÝ‚[%ÜTWžž>}ZìÏ?îW’ò¤¾@(E©,É0…Ž‚Þ¾zca¢\%J§££Ïƒþ7jß‘ >t:]?ÞæÞùC+/†„¹õuÝþ8ìu?ãª5¯:ÚÛî‘$ÅÚoš»(0Ž®ÍþòU‡Ä¸¨¦AAÿ y‘ééi•nœ9´!$%•îÅG¿þšÀêê{úäÉ•°Òk.­ïu؃~ƒ†Oó”Ç'DÓ 2öML+5‰\‰K’ØgkØT‰Ó×^Œ¢óg-¾\RÝBp/&â‰ó¤i‹ÜùBcWû–¾túö@…,E°lòð^§®þ½PJ¤Š…ˆ¦ qÔoàõÓâïµ—‘{&ÿÏàU’ÜÞ€þòSÒW/8pàdö5`@ÿ9³×îh¢Ÿ.=ã­€d‚GŸYìZò›WúïíÖOŽ}@ï h~‡' µ@ñß¾ñÓyó¶[¶lQËEË6´|_ hV«ù¡?/_vîÔ¸¦ú5Ðv-º/¹|ùOçF6•X«‚?·ïü݆Ž;OõÙ±m¦ûÐn4Ï'KH$‘ÕMHtõ^ç|îSýkUâ…°W_ßyVIwM¿w¬¾ógN?stCs¶¦PlÈãqo×åïÈÿÝ ‹¸ï¼y¡}7&? ëseöùóz©4»UÚþâ·v¶D"QÿajZ…ƺÕò³&×¼u‘N1‘+r4ÉšG€4šñ“@ Øxdê™N‘±©B###¹Tªä}šCÀÌš¡’§ õ›ÃNv,× Ê´Wœk¶ŸN¥#¶îÞKœ];ÕЩߟoÓI– U;!ׇzn\ºø ù¾ÃNB¢È[:ý1¦ ²¹Ï,¨Ô­h¯› ÍÚ¢ÛnÝ^5ðÖ£™|‹Zw}wlv¼´ÓfκƒÇu×uŸFld—ŽVkŽM‰ïÑ}Ík WŽÖ|míâÜö¥.­þ'GP-½Íõ ;ÿPýËߢÆÝ¯ì­¤9­o à %æô•áì+³Œwƒæ·‘yË7ÝÃÒTwl¼óC«d–ƒ=@lÈ ³Ÿ´ïÜ+ª(*Q*MK—ï4&é«çÎîù–>T——²sŠ _þ}n’Çdi\Bð½oÙ\¾…Í]cM€«FÚŒð3LúêŸf2z-{­HMWÿ?„K6L|,ôÝur>k‡g¨%O“ËÕ¿Z»`9?uâ¬ú6Æ÷#çM™1þ+Çj8è#££Çƒ¼§6bù²oYºáˆq/î9ï­¾ôeç®è¯êU5²³~´n}a›?m`¨JR¦UÚqöˆv^³ŒüØ­ýõ*•“‹½fb€@.yü\.¥à2 ‰€‰¨R+¨’©‰úS3_9³hÐVÙ¥Ã\BRÉú¹3O] ¦ë³M?¥þ¾&/—^76â¾Öe~¯jcð÷Õ+ C£“¾604OZ¿c£»‰‰½ªc û%lÙ·à¿oOŠ ¬*qµË¹eÖÔ²÷ÀˆtJ‡$.´©×x¯?_§ŠXÍ‘ÿõcs¶;vépŽ'Ó?×kÓo¿Í¨e!¼ûêɃ!‡Ô³€¸yÏs®‘Ùœ,{ªT¹¡±õruÝ4íð~÷å)r+Ó€gøÎ|döð$k³2‡_"2f.©lóI€ïÑ£Í ºVu–Fâ9(%÷9ý¿üruë³Æ°d±¡§ŽžªùOx’îwÏrí©ÈàÛ€€ëÐXb½"¢ÅHsA¢@…hÞÙ3êfgÏŒ׉K|ÏM¤ËëÊœ³qß9„P÷nsljJÊñº"„¹¹‰Þtšà«ýuyØgÖ|—Çé®ÝÖît5ggç†ÞÑÑ©\3Ks_{~ÞF¿3SRüsªãâÍ›í o”fd$©óI»Åëg¬N#͵¹ýþzj/•ò…5>osñªÙ‡j‘'dŒëT¯×±ÔÊTšô_D}“j "ÇŽêìÿûú-Cþ‹—Xˆm[‡nܱd›µ<„“×´ÕI™ðƒç˜W–í£vmšvP(ayÇ{E×èðbǪïM¢û¦M:ÝD_«ríQt#Ž‘µdæ†-k¾µ•KÙ¿PJ™Lûì´À}Ê®©ìFÆ^ø¥S/×3Õqo¬ëvunýäÞ·Hú+ZÇØ1Á2+£ û7­±f{AKzY;g~ç€kÛ²­ÅÕì#§/[åÛÎÎLFRžrfO˜=ðOÚ_¶2Í=G[:yÀ­Ë¾36Ѐ™Õy`ÖP¯ÄÑ‹·y»|ÊŽ±\Cö·CN@eVà«Þc"ŽÒ) c¿ïðΪe¶Shx9H!³\]¼NÝ~Ò2ÉÈRbc”$ º}¬¥Ëð%?¦ݵ –‰ÄŠT7OŽ{rûTË®Þ^ñDB^¼~mdw÷áÛ&ºJKLØ»3–x?ŠWZ¨¤RaDÈyïõÇ[ü¤•%qž„ÅØ½zeÏV1P¦Ñzâl4Çt™GºÿWÀîÉ™¥I$ñ¡âùÓVŒ–Ñ:‚CBþyî˜&WhðÏn‡~Ö¨e–/¾áÔ•=èÿø‚èFX„]²iÍx»ì3½~då¤Öû/Ü餴mêÚ¥Y@LØ»iß_EÞÝuà¢À‡Ïšˆm? µ4J’\ñ_í:É÷’/J72â¨ë”JUB¥,¥Ä¦…°~–× Asy½³è  ð‰µµµ«G| KX åÙyÁ…× ŽQ#ɱ³‡~Ú{ìàê–µ*?ã'¾4ÿïÑ9!»f`Þ"öØÙýôÚñÕ-Ì b¯/Ûü’Dß϶ºÄÿfàdÿ c–±#ŽQ3ÉÉËçfÿ¶ÔU}œž$Q/3˜×´J…úXŽ‘”åÉ~Ìα¶¾|bö‰ß7/©BUR¹:BÖ\3V©×Mâ»âÇõô©ÛÅ3`Óò9ç;Ô2|FÈ¿äf¸„÷çé3ê)L}‡÷;. ¿Çóõõuؽ{·=û¼Gç@§É$êYS*ǨE×÷'å3oéØe©·Ž™Ý¡q%ûŽAkÖ¯Û±púЭ¬Ž;'N;723dRÏÇØñˆ{ÎòMÛÇV8LÏ(œrC€ PD±¯Õ¿ÇÕíâPU]fu²Þÿô&¶qk—û´kßñ’æZº¹Ã¹[þ\£ß&©WjáZÕˆ`«Ð¾YŠ5ýŒ2ű ÊJ¢wÖÌOZZÄ{7só8:UŠÔtj?ýÔØúRÓ¤ê§s•²4 HϾ]N¬99ö¤ß¶/ø7Bé‹xlI¯í£OÌißù× ’Nº¼1v–åýç©ëZ‡]µór¿º€]käìqÒ¡¹Ëö“身óù•lŸmŠ×¡6¯$JŽ4M©¶å²uÝš«ø^Í…ÑC^@€ŠL€+Ь¨øüÞýÆ2Òû/‰' Fü8èfb ³ãšìae^K'÷þnÈÎñøÙçäk^¥Ã®å¾}8-»ª’*tÓ‡S¸6_¼7îÙó¹õš“äï£~}X«Ö/B+0¹á’•ÓÛ»†tEàtúϧ«Ç½06}ùV;»¸Ç—Ôܼ深÷öunûÝ–×Ô±÷½?ÙC$)œëj%3R•NóxÌ.Ò'0yçyÍ|ϯ€îg ¿ù€ ©€M‹® õ‰/ù7,ÀnÜ|ñ7ŸÊîðO? obX«ñ³&mâë“-×>'ȱ¹!õ‰só* ê¹ylM~_eüÿÄ3æ¯ø†<=g÷–Ö‘ÓЭ’KWÏÜô÷2¹:˳K{ÚÏâ'§Íüý­6ަ÷>LT¯%Þçû®—YNK»†²v$:³BŽîœÝõ·‡ò&õÝNNï]ÿ–ˆÎU~+%ÂjŽmã¬Énùð|“Í{jÜ5 ¿RmOÀýîlö4 Y dËåqsçÎmK¿²_Â1>J©TJ_5NØ‚ ÑVlØ·b¦ûðésîìé~Ÿ¶Œkå±vÃC"|çǨ¶dùŽ ‹ª“W/ßùPg \:ÒËËsZa#Õðïÿ¾òèß}þ 8ÜkU7¢¦yJlD•( øôU=„Ä©¯e­£f“qŸ›ûÅ>Š)>üV¯©“ß8¬÷郓wÑ ùsòm ›„ŠTŸ³bëÞF3»°½ûðBWÄöCÆú}^µ¡rÃÆ) =Ç®š{âןd‰EµZ>Ûº~ÒÜ5ý¶Ó}AÀ/ÝcèÑûO]oAƒæYêNÝèrú As9½±è P²IIIGÙWÉÖŠÚ PþÌìÚÉ\¾1[’Ï‘ÑÑÜD˜±´b济’¾‰Ò$ãš9zóæä ìê›´™póæÈŒëú×H.iûÍðù뻉I×dJC½ú45e-Ç^uP¯ Y®™ÔWm;{ý'M.9½{“}@àé¶ìØÎ¹ÓÉêš 9~7;(7ûŸß”.IâÈ”Ä.Ù(Ц´mÑ?áÔÍÞ“SR’9òlVŸ¶{åæðŒ>çX8NæKAs¾¸€ ’š˜«ÔO÷åP™Ž9œ.¶S†ÂJª¢ ˜$äô¦Ý·Ô­­KfMu¹”—†¿¿ ,˜7/Q¼´·<¦)ºŸò¨ƒ>A€ "0'+/œ"‘)9¦b1¿ÆVVp¯ÊÊB;!@(êQt“‚¿Ñ³\ ”ÁNàå&eð¦¡É€ @%+€ ¹d½Q @€@@Ð\oš @€ P²šKÖµA€ @8V|ÀOB™@Ð\¦n @€ PšKCuB€ ”)Íeêv¡±€ @¥!€ušKCuB€*¸€B¡àDEE .]º$þØ)T*§0mT*•…É^êyiû ÕêWì} תU«XëAÐ\¬¼(€ œÚ¶mûß‘#Gìø|¾"§ëÙÎÔ•­Â¢<,ìC…Í_”})HY…m?ÍŸkµ&&&áÎÎÎorMXˆ𠇬€ LÀÍÍ-Œ},7rA ä0§¹äÍQ# @€@@Ð\Ænš @€ PòšKÞ5B€ ”1Ìi.c7 Í… @ "$“m+Ö¶ ‰I¶d½5¬d—8o¶ÇE~EèúGÚGÍéA³ @¨¸Š„`ƒßŸôJÕpQ)Ó4—ê¦g”*?*‡ @9ðy*¡Þi®È(š«wŒÝ’@Ð\ò樀 |X MΑè¥P$K-ó² µ^챦g1(Šƒ @…àŠ>QN÷ööR¥’N¸ËtQa EþB h.2C€ bàš‘.½{¿(†’Qd0=£€pÈ@€ Pqò4Ò¬P( ?~lþüùs“ŠCƒžæEàÍ›7•ó’i @È€‚¼&¥«ÔœÃJÊzõl%x0?†E›6OAs=®Ÿ?¾¾²h«Gie] F7ú÷ïÿ¶¬÷í‡ |L ÉcÎÐ!#ý2—œsH9{eÇP³©‘¬-y šÇŽɾ*˜ º @€JM€-9§ š±ä\©Ý†ŒŠ1§9ƒ;€ @à#HãpäzMÁ’sz¥´›§‘æRjª… @S€/RÕ³·»š,0‘È%⽇ùcjFéþ( h.]Ô@€Þà ë¨6úí[÷Μ(5LÏ(5zT @€ PV4—•;…vB€ ”š¦g”=*† @9 ($O8ýÚ<A/óè—ªróçgOoš‚WiçìUg1Ò\ʨ€ äG MÂIÖ¦g«h¨dr¼`.?~ÅÖP.—«V­ZÕÆÇÇ'¶ÊG‘ QQQì­2P@€ G¾J¥Ÿ’#à¦èc¿ä “’’öÑ/Ë’¯5V 0ã­膣«€ PXzZ†®•La¢Ðà³TØœfЄ—Jí¨€ @à®°žêôµkýÒè®öªîóÄ8Q"x°D˜Q  @ȧ—KøùÌ‚äÅ'€‹Ï%C€ ”ÍåäF¢€ @Å'€ ¹ølQ2 @€@9Àœærr#Ñ @€ÀÇ$`ll¬LKKSõë×ïË©]h Þ¼ycæêêšš_ ÍùCz@€r011Q­^½ÚïéÓ§Â\#JP@$)úôéŸß*4çW é!@È“@ëÖ­SÙWž#>rÌiþÈoš@€ PúšKÿ € @¹‚æü¡y€ @¥/€ ¹ôïZ@€ ð‘ hþÈoš@€ PúšKÿ € @¹‚æü¡y€ @¥/€ ¹ôïZ@€ ð‘ hþÈoš@€ Púx#`éß´€ PîRRR8ÎÎÎ.Få®sèP™HMMåÿôÓOûœœœ’òÓÍùÑBZ@€ò$@ö×l‹]»vý™§ HX²d‰ÃåË—« h.!pT@€@®ªZµjÉrM…(Aú×eAªÃœæ‚¨! @€@…@Ð\¡n7: @€ PÍQC@€ %€ ¹BÝnt€ @  𠢆<€ @JAs…ºÝè, @€@A°NsAÔ€ 2%œk—Oã#bQ¥š\$äªÊTÊRci$úí[ÃØxOle)³4h‰·­Ëš?¶;‚ö@€ʽ@2ÿ]—…7#åïïjeâ{îÄG3n®W›ç†O_Å , ÔIáêúüîiÓé³WÌ}#©­wšØ8¶ \³zé¯uòP¾~¾²½Ÿé^Çi¤ïþÅ#Î(¢ î?})býªV÷óäêfü\½ßg ¼âÌ7ÖýòÃ'ý4\‘MôäEËgõme«þCûEÙ®Õ“Ÿk˜ž‘-¤… @ Hi*ã$!iNqõÀšIÝ<==·»{.Üœœq–ÛGÖÚºx.Øž=`fI"^vríÔÏÿ‰DÁÑËRîw“bÓk±N&$KÌØç“;û«0;öµ÷Zˆ;W°-–¸wï¿9{ÀÌÊR$GX®˜4xë»/MóZvѵ+¯5æž#͹! @Å(`ãØ2°•q˜\®èª1àU‘åuXTÉ,‘†Á„+2Žj PHpæ.Û¿FWž¨V³;^£\¶ÅÿwÃjçößçÒ t‹ “gü<àØº‰téÊ÷§ˆÌݲut”LÂ[Ö•²¾ŠÍ-ÔŸlßÜÌ4cŸçg ½vXü(YaÉòVnð|ÝÚùsª§*VÏô}5$¦ ;¿w÷Qg—¦ãòd]TíbõÕ† ¹¨$Q @¨LVû®÷±Ë!g\p °ï¤‹ŒD‚•é'±[|—ø˜‘(2yð„‰/e ¡‚g!©$¾Ž ·eÙÓc‚j»0ã«Þcüí£·ˆÑ–ijßáê)¿¥ëøì¸C»¨>][üàÔoÆ^9=|}ývÿB¨‡]QdÙÌù½¯ßí“,S™p¢”O?ÿêÂÂ¥ ü4|2Y8ÚsèÝiÝ&œsž]ª{êzpBx¤–cûÀµ+Ü}7M›2âüÃ0'Ž@R½VÓ{Ë7®Þð ’{Ÿû£Ûây2>Gâмùå°kºEÆ9°:º œ¸Ú­ðéäñ‹V½H–Z è¹&ì\9Ûã"ŸèêLª[¿ÃÐãËÆ÷¾ÍºqÊg~Ãm烺ÑP—¬Øµe™é³ã1³|§søU%®Ã;í:ôóF¯§ê))<ÒÔÉÅwåâqgDtìþ௛º?ŒI´®Û¦ßÞ?§?û_Èˬ<¶\ê€YÛaÍië—.!“š˜ÙI5s$ùá›Þ»žÒ@L›Œ¨dÉ&!w{¸vºÛC3¿š(ß¶Œ¤#ª‘{|ôæcËIØÃ@§>ÎsyU2™IxÈõ6ÆN_8ºfnèßW*‡D¾Pç £mÔ¯#`ûâ¹Ûug‘ÑzoœØ>fVÍO#Wýâ_]œWÑwh*uÐõò_ÖWBâHBáp"/"#iù‘dùüûå"'w1·¦}ÄÖž<|Ø>„¶?ÙæU¨ùÝÇ]_ëõ7!æuí„~mò‚jòâõÁú±ã{eÁhÀmu혙pDú¯ísçvØÔ±C‡ã;vüçèµ›ýøzÍדּ5߆ÌÓ^D†8Ìrëµ+fË·'9¶+¤vlðQÿ¡“VlÕÏ'M޳¼x`Äwaqõ/ú-Z§W~²"ÙÇœæ"aD!€ L •øÌŸ±a¤I['é­Þ×›.™ã³ÕÇF[ð=«¼·œx0†Vnú|ý¼œ;9 «£e ;Ç}íwü¸ë¾Ån¿É3æL ‰­•©Œ]×ßšuèÕùGD‡V Ô§\½ •.`®Ñ¬Û‰Ǹºõh¶I“'†L™¼|¤‚ðœ]9­úº¯Y1ßc¼±îýttä»uË<·jÚsòȇÁ ¾‘Qzf²ÊÄkéÚуœ}3Ï·ù+ÆOøÍ2ݹ{±}]sWÔ 2ô!`—8«”Yk}ܧ¸9-T'¥ßþ»ÿ° ÛÏ,ˈ¬;zÚÍÛ훌4]FÏŸ},À×¥OÏÔýVÉžš\~C‰H&çÎüíÊòsõRz¶ªóÎ}uZ ˆm[‹Ï‚zõ–êp|Ï6ï1n?ìj۪͡ ‹·|£™oNjfÃÊõŸÿ|èø‡w¯\Sëº~êºå rl×&—›ÇΨûÀFö=×ú¹ß¼y³ß öuüX…²m‚¨³¦öâù^¬…O“Q* @å]ÀL$¢³&èÆ­E6mñrËÚ_S²ú·õÓÙ±ÈÌRikU™ ŒÒ€N˜bS­Zº•X˜%zÊýÂdxöÁ`MuSØ4€ÚÕlÒ=f¯¸Ø¼²ásv>)ôicý‡ YоhÚ×Ú9ŠpЦ!Ä–Ì§Ó 5íšØÏ©¾^@ÌJÈÜzx/ôréÐ*jÜ‚ég*kO[}Õç ‡s»ˆïÇϸý¹ˆÍNG? j¯AÈÌ›—½î“æÍéÙªql¹šjÛüOËìe™‰ÅŠfŽuÃueV©ik%6Sô:ä/žöä}§Ú*$ÿq‚bÒk³S º÷ðÉy„Yåié≃§×¨,|ÎÒfnrrãÄÎ1}G,ò¹ ÖÍëàð+¥¾º}ÑêfP”¥©¶ÏŠäPËh®™*§v™škoœl™ä¶uÀˆ1J;§Ð[v»ýyíL¿¼¬´’Ù¦üïazFþÍ€ "0&kŽŸpm]…Ÿ.IË\É‚/f¬£lÓ´wbóÊžßÖnüZ-î´óÙÀo–M%SdL­È¼N’¤4†Î²´\Y0ÂÅãBhbcŽ FÊ®›¦¨G`“‰¥Jf`’9†+"­[}zêö‰Æ(’_X>ÑɬéØäOõ:m„OêÛÛܺ}ýEmÃÊUž›k+~§q™ "ff•ÔÓi’Œ•;„vÊ—±QWÚŽ€›eù<½">¸kff¢-+èFG9~Žeem§f‚Œ°æih°Í¼_Þ½þíQÿ§ÏSÕ5ÒÑð¡½þ—Såo^>ãǧʹ»º‡þ~ì”4º”Ýõk—­öìðz?,N=]$þá§à”¾‡uùå‘·–-»•ñ°¦æ¼f@>§vuö˜ýàÜÕAW5²)1·Ø×Þí[‰íWîY7ù@öQw]]Eñ‰ ¹(Q @’ªUDét& IF ¬_Ø}Këêfv>-ì|³_¯…ŠGf›&Àð´S'2ƒ]6Ïyõ‚UÛož¹KW¦"á¡ÁUº–°:²L–Y ¹ "×ÎíåŠêQ^MZ ~¥ž"ÁU~ßj"‘±z7ç ]W«þ§þìmýóY÷³—'‘ÉŒt)Fœw¦œè®ònùÙËÊL›¹Ç˱E¤¿Ë×¾·7_Xœs·öŠÍd1KųnÔ¢_oš‰.o2™3lÐúûêÕ3ŒÉæs hFÿÐι_ýZºjt¡ïDõ`©•iéÓŽÅ_õ<èݳÅÅT™ÂP à¦'$$I¥\^W©?*žÑ.…Y¼ußúçÏÂ~ ¹_9àT€ó‡OØ/®û÷÷¿û}À ¦6™SÜuÍ+¢OÝ/ ETŠ @ùH$ëç-è²tñâoë}-˜½ Ëÿ^&ò w æ¬;½<{‰¾“¼¶>Ë:IWÏxYû^p¸0žw76SÔc=ºž8ñòÝû¦—öÙôé1f‡.²²ißί*1#öõ*ß`ÉÉ,7ï»\—í‡\Û/þ#$± Û7¨$ŽÖŒ,³£’Ýt!ðëëýïÇJ¸’Ø nÀÅEÝŠçÿ…XÅ&$¨cö¾ÖMÑÕãäÒ{WÎ#¹"òÕW¶Ç4éRÉøïÝV\¸dûš{ñÈ6Û³Ú€™]·mÐ2Vw_ŒÓFéÜrgçŽÁü›.[¶bͺu»–ÇêýÅåѵkëÌ~½Ú·ïtp˜Û¼]Ÿõüb½ï^Ÿñ=lµóÎéÄñI¿T°|E½a¤¹¨EQ @¹ èA¶ºÃ­ÀÓ#nå£R‡îwý™ø£.À´tçhÇ—»ÅÓé([cÙÓc‘Çiß9>r¹L;£â ™2¤¯_ ú¶»Ãômws'~7}ʺ£ê€;†®d1Íóº:άʖ¬[â©öÆ/žõÛYç =X]G×M[~t]f*¶7ù'ïå9ŒYÓeeç>§:3×»{Bb!2ˆ!1¤6!oˆ»sûwÖ9þPù™Îï–ÌΤI¥±àEºŠÇ¥=)g¯ìj&l úÊÑ<ðäÃxíŠ Õˆëw­žå\ !½f tC¤Çü[{†çðíÙÓÖiß߯Iíæòþôá½m—B‡ß9Õ£Ó—§Ô#к´Ÿôè³éúŠóàÚµÀ³ñBr‰ÍˆŽ Z}sÈÚÚ$(R»d!•I§¯>Ñû+®Ä¢ûÄHsÑY¢$@€ò$ $66ÿå–TöÏéʆ¥©çÃZ:vÝ᳨öCgýÛšK,oüÓN~tšF_·‘çl´’±ó<íKRÚ~?ã©ßÚYîµßy0¨_£ýû¥ýýki' ðÅ-ûé*ÙÓ²•(&Ñ•z7¨š±j«ƒð 2£bõ ö ¢nz}iHÕjo5§…DÄÏ:íÄ(ËJÚÌ9–Ç¥uÒD~^¹ÒJoÕBLI³fö'49ytV5!&fíÑ=ÄGèS‘‹ciÛXYz›¶¾zFut´¡«ðˆˆðx©.êÖï .‡y³Ö'êÑ`Vwœý“/n¤8°Ã¥Y­ªw²_#Ę|3pâ²=ËÝÕ¿ ¸/ß}Ì­GËŒb]úFíøùÌ|‘çÔ®ÖßÏ~:…®.¢é_*ÑÌA•”™[6»}¨}º: ó™1½0… / @å_ÀÊÊjÚ™3g.5jÔHû ÙûûÍíÛ·ïÈ+W®œª¢½¢P(HZZ‡=D˜1qV[E2*ÀW+ªV©& ùï “b R¤r?©Ät5‰¢meAKSØØh®\ÎåXT³LÏߨwþê”Ä>áþ~â¯Ooýq ËuíÛü<×îw–mùûJM“$sÞ¾ã%¥¦r+™‹åÕh{³ßu^E‰~gÈFÃM«TKæ˜(§Z$N#‘Ò{ddd¢43å6 ž¥úÊðF•+W¾C§…f¹ðžƒ‰'Öûå—_3†äß“§!@€@™àr¹ôaÂÌU7ô-[)è—ú-uúçsÚ™‰•"³ž¦Ë)q‰ã1íCIT÷âï€Ê6ÿF§Àh6ŽÀ!ǵ™u׳ò…"•MMчfh²pùÄ’.˜=îÇ\º‰˜Ë%{4ç~g€ TJæU¥"úpB§j˜Û|¼`ÕÊâ Óû÷wAóûmp€ T8›¦.‰ç¯¸ ­pÏ¥Ãx0 \† @€‚fü @€ @ ͹á2 @€4ãg€ @¹àAÀ\€p€ ¢¸zõªÙþýû‚,7–µ-Je¾–èÍš™©Tªúº½Óh½…maýôšRZ»jSSÓÔY³f= Kîâ½@Ðü\‚ @ è:T¿eË–333•A©ÿÑû½/9)úž¿["‰ UÿGà÷n§òq†¶¿Pý×Uµ{÷îºÿÑ­aÆYß|¨KPŸš‹E@€ wú•……EZ·nÝ¢òž )!ð~ÔxÿÕ¢¹Rê¿ÞM7P  @€ŠOAsñÙ¢d@€ r"€ ¹œÜHt€ @ ø4Ÿ-J† @('šËÉD7 @€ŠOAsñÙ¢d@€ŠS =ž\ °þãêßâB/ø\œí,‚²•’ûœþ_~¹ºõ€YcØšj²ØPƒSGOÕü'<‰WÅ穈ÈàÛÂê+«ñc…ìtž~2€ \à%ùiþü)³gm÷R|ä--|óLImû:AŸÕ­ñ„­œôôL¥…KNôÚt¢CáËÎ[ í[Òr>õŠ5Ï[Žò• ë4—¯û‰Þ@€ʨ€Œì[½°ÅÞsuJ”ª„U>mûïªõ‹÷Ô!È>“g&YKÚ»sÕ“&žswx¤ó?•VN¼mžÈz+ý[ث׈Éëöú®©oBÞyY†Rò’³xŠw¿«Â¥Iê|Ò.dñú™«Ó((â~€hÞO[‡†¼‰µæ™JÚôqjþ˜^HB8c\§z½ˆ¥V¦Ò„ ÿ"ê›Tk9vTgÿß×oò_¼ÄBlÛ:tãŽ%Û¬åyO[„‘ ?xŽyeÙ>jצi…êzÆ{E×èðbǪïMrïeÚ¤Ó]Aôµ*×E7âYKfnزæ[[¹”ýb ”ɱÏN ܧìšÊº{á—N½\ÏTWĽ±®ÛiÔ¹õ“{ß"é¯hcÇˬŒ6ìß´Æ^˜í"éQdíœù®=nËüÄÕì#§/[åÛÎÎLFRžrfO˜=ðOÚ_B䋞#Ž-<àÖeß™^ugu˜5Ô+qôâmÞ._„²ãвa¤¹¢Üiô€ ð _=ºÅ:ÿ?\ß’*ä[qhøÃÀ&˜æoò¹êÛ¦·_¿þÇfÂð‘£ÇN[7áÅë8›oGõÙobf¯é’ŒM-â9½A9…Ìruñ:uûIË$#K‰Q’4èö±–.×ü(y}ÙÐÕ}þ‚avb[ÛP^|”8p÷’Áã7mLˆ„¼xýÚ&",Èîî÷MDFbÂîØ-˜±ÄûQ¼ÒB%• #BÎ;x¯?Þ"_ieIœ'a1v¯žDÙËiã ”i´ž8ͱ„DÒý¿t¿øHÖÈÔ(M"‰ÏŸ¶b´Œ¶'8$ÔáŸÇáŽir•€ÿ¬ïúY£–mXb|¼ø†ÿQWö¶˜øÇD7hŸ’MkÆÛe˜éõ#+'µÞáN'¥m³P×.ÍbÂØMû~ü’(EFw¸(ðá³&bÛOB-’$WüW»Nò½äÀã‹ÒŒ8ê:¥ô—¥,¥Ä¦…°~~ ‚æá.  € Š,þ„øù?r%4`ž¹bÍú5+ïªK=¯ïÙÜ —ð†,]}¶ Ø’ip¯[µ~yLÇobÖï]ºÉš¦ãZuˆØï·r[Þ[Õ__‡»wÛûÒÏWÿ§<;/¸ðZaÃ1j$9vöÐO{\ݲVågüÄ—æ~Û·~)£ù¾›úû?¿mG÷Íš) Ç÷Žvcó†5Qa]â3p²ÿ†1Ëè)¤6“œ¼|nöoK]ÕÇéI;Ÿ×´J…:-ÇÈ@Êòd?fçX[_>1ûÄï›—T¡Ç*©\!k®H«Ôë&ñ]ñãzz‰Ôíâ°iùœój>#ä_r“zýyúL#v­ïð~Ç%á÷xÌ‚¾fZmrÎN“IøìºJÆ1jÑuàýÉcFùÌ[:vYê­cfwh@\ɾcКõëv,œ>t+KwçÄiçfCf†LêÙà;±qÏ™C¾ aûiÃôŒŠt·ÑW@€ÀÇ( “rRÕízK»÷_ÙÄDr?$Ò¼C »èïØº¿;x0»6x¬ÛYuY:G“VAÇaédÙ+Κ_ýQsŽ£ÖÜËûȯ²ãº]œªª/T'ëýOob»G–oÍ>~ÙTšØ:ÈØ»˜ŸÆ??•¨Ôes­jD°ÀUhß,…èQæ¢8!ýTV¥Ñ,[~ÒfɘíÀÀÜ<ŽMVd °õ“¥¦I¹ìX)K£°€ôìÛåtÀš“cOúmû‚#´%!¶¤Ç×öÑ'æ´ïüëI']Þ»ËÆòþóÔõ ­‚îÚy¹_U{7rö8éÐÜeé’身óù•º<Š×¡6¯$JŽ4M©¶å²uݺëåAsE¹Óè' @à#`£­š­.ñ9¶eú§•TŠ›×ª< O°hû¥Ý[BG¢ÕÌ,ݺyËõ𛻧ͧ’*X KLT›V/ÿ)1M©êªÔvLäÆ5b—žß»ßXFzÿ% ñdÁˆÝL¬aÖ­ž4ˆ]‹‰Žg£ÅÑ„Nˆ¦;æµcm…œls£ÙdмnNË®²6ë‚°§þp¾:xoܳçsë5'ÉßGýú°V­;_þ„V`6rÃ%+§·w ‰!I§ÿ|Ú¸zLpÐ óaÓ—oµs°‹{|ùHÍÍk~{?`_÷à¶ß=`yM{ß;ðó˜=D’¹x¡V1#Ué4Çì"ÝxÝ Óœ¨ ßu÷«‚tÝ„ @àc06P}ãÈ»wðáÓ&«WüܵkcÑ¿¿mÚçñ–X“V?ôŸüË´É=ŸÒF×lÞé†eøyÛ»!§èRgaOÁsp*ãÿ»YØñªÙ‡š·i›¥v]eõ‰/ù7,ÀnÜ|ñ7ŸÊîðO? obX«ñ³=›ÞÝ}ns÷À5sÆV‘¹ø„žÜå¤~0®iãÛl¤7?Ál~Ò²ö)ãÿ'ž1Å7äé9ú[bÂNfÛ hì¯_®þ¾B&WgyviOûYüä´¹“¿¿ÕÆÑ”&6aÅôù¾ëeöii×PÖÁŽþB ·Ý9»ëoåMê;»œÞ»þ-úòVJ„ÕÛÆY“Ý$òáù&›÷Ô¸k~¥Úž€ûÝyŽ®÷¸t M“IÕ#ÍGVNd:~æ>—6ŸfµÖ«£<îbNsy¼«è @ L ¯Ÿýö´©%zò׉vëÔs2~õÊŸ*?="Üñ×ëvt‚Y¶iÑÁµ+¦nfóŽ×,û/©O\œëŸd]}rûÏ–ñʜšêdþ3kÓÀð~Àžî‡.üÛ‰kå±vÃÛC–éå# oɾM›Š¨¹´ÚQZõôöµk×î 6L)hþ¼äCМ%¤ @ È7nœL¿nY(% €9Í%€Œ* @€ʶ‚æ²}ÿÐz@€ @Ð\Ȩ€ @ l `NsÙ¾h= @åX &øOá²-Ç{ ¤J™Ò¨‹§÷ñŽõ*KÊq—?Ú®!hþho @€@E8¼}M§+×#úël{º]DЬÓ(ÙOLÏ(YoÔ@€ò,Àã™Êòœ ‹UAs±ò¢p@€ Pp¹H î¾±Q @È@—!3þ°lùæž‘‘aºTšnèøeÝ7ùÉ´E'€ ¹è,Q @(RÛ_¤Ø6 Åú¦»"mp9. Ó3ÊñÍE× @€ŠF#ÍEãˆR @€@‘ ¤%Dü%d§ËÒ¹6u?K¶4ã+‹¼"˜«‚æ\‰€ ”ŽÀÎ%î=¶]Ь«½ßümã§:¡;ÆgÉ `zFÉY£&@€ /ìKÎ ¸éù*‰‹LAs‘Q¢ @€ PÔY—iÆ’sEí›÷ò0=#ïVH @€JT jMÇ;{ÞUõbÍkÉí?‹,Ñ ² ÍØ @—@7YºyW«*fk0=£bÞwô€ @ šó…¤€ @SAsżïè5 @e@ààŠñ­¾üòËC_ýõ.öùË…àªe Ù岉šËåmE§ @(ɉQ¦¬2™Ì„}Ê ÞkÂJcCÐ\ꨀ äI@½nFžR"Qñ h.^_”@€ , 0âd]¨¹À%!ca°ä\a‘€ “À³w^t™¡¸ÈŠW¤)8\!_ULU¡Ø\4ç„Ë€ @ 4¸\®ºz®‹€¹o¦g”">ª† @(šËÆ}B+!@€JQAs)â£j@€ ²!€9Íeã>¡•€ 2%`ll¬”J¥J''§veªáhl¹HIIá5*1¿EМ_1¤‡ @ WÕ±cǶGDDðrMŒ(A###eƒ ò½”‚æ¼I¨ € P‘¬­­ÓÙWEê3úZ~0§¹üÞ[ô € @ ˆ4$Š @(¿šËï½EÏ @€ŠHAsA¢@€ ò+€ ¹üÞ[ô € @ ˆ4$Š @(¿šËï½EÏ @€ŠHAsA¢@€ ò+€—›”ß{‹žA€JU`ÿþý☗j#P9² TªT)mÆŒáÙNçzˆ 9W"$€ @ ¿ñññ6lpiÖ¬Yt~ó"=ŠSàúõë6†††¿O:5_3‚æâ¼+(€ PAär9‡&ªŸþù~%@·?ROOO’ÀËoó0§9¿bH@€ Pá4W¸[ŽC€ äWAs~Å€ @ Â h®p·† @ȯ‚æüŠ!= @€@…@Ð\án9: @€ _,9—_1¤‡ @P¤‘è·o cãe<±•¥ÌÒL¤,hQÈW²šKÖµA€ M@‘pÇ G§Ñþ1êó¦d}À)—–b¾"[²†^ûUì:Éw뻉xĺFÐñÓ7þСIÔ»×s>£Hˆ6¸ÿô¥ˆ]­V÷óäêf|ef<âµeŸ›KÓš‰9ç~÷¬BòŠ3cÜX÷Ë#œô¯rE6Ñ“-ŸÕ·•}¬þùÜöÄQ)R^%K[é§5«¤å–× /€é…7D € Bàý{>×̬D²Ó÷Ä—…(.[V9‰‹ uX?cÔæu÷l²]|ïá“;û«Ð—`lg_{¯…XeM('QñÉFYÏ}è(–¸wï¿9{ÀÌr(’#,WL¼õÀÝ—¦*AÿšBò€3|Èp?Ö¶ñ«÷õÖ¿†ýâ@Ð\|¶(€ \ˆ¿ÿ OýdΞœ¯aWýÌtßö«n½½§y=rv£Zwt—ýWm™“¬;ÈåSln!Õ%173ÍØ×322I×íçözí°øQ²Â’¥3¬ÜàùÆÝ9´ë‡6ö•¯êòîÝ}ÔY·ŸÛ'—/Té^gg&%ä–׋FÓ3ŠÆ¥@€ P¸à«B]@©Ë®H~dyáq”I¿UStçþ¥çO+vL ‹}[K&S™‰,¢¿êtláb¯3b]"íg§Ã{·²UÇ݇ö_úcGç-¬Er¨eh‚ÂÀÑL¡ôÛ°´ù¡3¹ÆÄ$Ô–Ó|F"Ë课éæ?cöà‹'Npù_È˺b.7óJíf§ ²>¡;÷èÂFîëη¼׌iêäâ»rñ¸3êùºD9|~ÙËew‹z¶viõÎõ뺶Ô†²¿ jAú,ÿó»gM,üyJPd´Kgam4å§•Ë¿m`•õø¤‰çÔÍ t#ó¡ÛFô ¹ÚdöÏW62z¥œ1yîÈ;!á­’e2@”bYëÓ{Ó—¬ØüEMSLá`˜…Ø4Y!@(œÀÁí~4%“n]žÜþûÜTzÂï×Ýû­w€]‹ >*:iE–ùÊÒä8Ë[F|Wÿ¢ß¢uš2rú® Ù'G¯ÝwèÁ;Q=ôSK“£-/žØ9&8Inj~÷q××40×]Oˆy];!&¤¶B/h¾qâÐÝuBäänào#æÖ´XãÑùAæùw÷þÚ>wn‡€A;t8Þ¹cÇŽ^»ÙÏÍLw/`ͨùû6dž¡ý q˜åÖkWÌ–CnãCŒÂcbjg^—“Waš¹{½êÒù36¼È¼@d²d“ð{mÆõsmã{îÄG3.:ÔóÉï.¦gäW é!@("0p)t0+Œ+²‹öðõ ¦€£]~}ýBÿ0m´ûDZ3M4òˆçZ?÷›7oöÔ¾Ž;' ¹Ñ&ˆŽk®k¾_ô÷qòñÙÔpÝŠ¥­~èÚgÕ?S#ìŸÛòŸ¨ÎjfÃÊÍŸŸ£e]»´§mm½on?ê²àèi7o·oêÊì2zþìc›\Ìt'è'G`•2k­û7§Œtÿݨm§^Bº[§Õ€Ø¶µøÓDRèëã{¶yqûaWÛVmMX¼åÍ´‘p²P0V®ÿüçCÇ8¼{Õ`Éú©‹–[~å½{Ë<7ÝèªÍz8rü¸ë¨&•ßÄk«å×êxçí×þµãÝ5§bÈ™kÿd›—µ8Ê]#͹! @Å ðÏ…ãU#´å6ìÜÕODÌH—NõwŸø‡ŽâFÿsl¦:ajf¬Š,'[&¹m=íØ(°u §ë+\Ú¸}Õè“D>—KBõÚ÷ôúýŸ^×;¡ÝiÞœž­Ç’V±Ž]|~7&½vLð?-éã]úÁµ¦j3²ÊÿÒÒÀ}[êþâwÈ31zSŽïØ;ÔËyÉÒγœ»:èêÕ˜6,È {xÛ‰}íݾ•>ô7ààžu“ÕÓ8te±9Æ"‘ Zwlnóiðïù¿~Mç³sWŽnqX³=@= D—F÷©’)ÔÓ2²NéxwVƒ™™‰öáÀ4¢ææø)z3-tE’7/ŸñãSåÜÆ]ÝC?vJ]ÎîúµËV{vøÕ̉&$þá§à”¾‡u™ä‘·–-»µFw¬ùԅŲÌÓr¥€ü¿½;‹²Ú8~ffAA\À%ù¹]Ê\Ð2Í›q¯kj™iš¥f¨7·Ò,׺j˜[Zšš¹å’™ôëgä‚E¦Rjý ,¯Z‚’¢‚lÎ3sŸg`tÐaàóø‚g9ç9ËûÌ_ÏsF¥ki™$=õŽXó†|.¯²ó3éG:^¼¼qêÚO7 )å2~r9l7šoXp„ €å$`ÎøIùýéL)¶o¹Â1`–¯šÎ ýIšzꣳD¬Þºôl¹5çNÇûGïŠîwüLWù¾¤Ã‘"Žîd/FÚ?;oݘñaÍo¹&sú©]Ú)`–³«|¦>3tÐÒîaMæÿ×Rû4‡¢l‡ž×Ãbǔ´=àvÌ%D¶x}ø³KãmSDª‰•_}=0T`y¤çSÉÒϼ·Çô¹>¿ÚbÊ»sû=Ô÷Ó©}Ûï»f4{h4ª¼ŒŒ,/ƒAåÙØGš—lŠëuxz^ ÿ9tÆñzx&ñ³>{wïx 6öðà«Òtù õ´9kŸŠzwÌM\\/…ƒ4—‰, € à\ymæÌ‚"›v黹‡Ç “§ÚË+÷XÌGÆÄ—^ÔË[>ùú¾¸„U×íO–æ>ŠÈCÛŸêÑï¹"#œ^¼#Éö2^úÕ,/á%ìa¥R ´öÖ§\H¨n?î;nÚ¼qýÚ&™ÒŽª.Ì{¶§9îÏþvºnZhÝËŽ×J~ì#z¨aT|Lb¸×Ä„Á#¾1eNë{ürâì ²Ï¯–Ëkxo‡´b³Ôk!ª™Ì^­Ãº¥è¤ùý™czlˆ9.Ý(Ú?Ñg€Î¤PÈ0È[ö¥¤F©ÙÊì_·úÚ¿Üeð¬ufD¼³çÕ´ïcº÷|i›üb¥RãQhÙ<[ü*±As‰©Èˆ €Îp\›¹†˜8}êŽP‡•út¬¹eß)}äÀðûÈÈ»‡·~[ì—gû&‹ýßzõ¼O\,XŽMÑí¡&©âXlÍ’´­vPƒ,{¾íó',ùÿ­µãRÎ]µ¡òœf9Ýd0\‘ö­xcÿ–œUó;N´ßë¸/:DÏÏ5dÒ´˜OcF…_‘N󮜼gڋϯw¼_>nÜeÀæ6÷´Ë ½à¸Nz9ò|Ü®>ÝÜ%ýápckÒçÉM´*«P{[µÒe9¸N=¾»kïn»»þë­7GûKçr[g½°ìëêȹx1D˜å­uë¿ý’Äï;°OùÓû¹@(•€éÏ_< žìªµ»y)4u6y-ý=ÎÊ…š³“tÝ_;;yÈ£óó_~»&ì³BS+gúû+G4—Ii³}»qd¿rc_³ùúñý;Ì–—k»j‘¾ÊúíY9—9;-àŠ4¡¹yØ”´ ŒÉŸ­JUzz^Óéj^Ÿ q£µô¢ž—«J+jäÜò±ã¦ökeþ,úçCÕŽs¼ž\M<:äåù[ŒŠ’ÏG-Ø5¢O‡7çkÕeàæ¤5¤m×UÁbôˆžsn´Azâ¬h Z¿~ΰ@ù¥AiK½0{ŠŽýÇ/™9¸ó™›Ëä¼t7æÄ”î>r#€ €@¨[·îk{öìÙߪU«bÿ«?55UÕ¿ÿ‘±±±{ÇdJƒ!W)}#ŸE§ó)î!oÑU›M"õrº‡ÙÓË*¯ŒQtF禘ôÙŠË—Ó=³®]SU÷õË­S'àÆDfǪ Ú'w°F­:yÚë³3IOÄõz…P«­ò "ö-;#M™#yJ}ÓI}»‘bÏQµ÷Ò×·ò÷÷‹ˆˆp\¥H”—_~¹ùªU«2¯ÿ×C‘9I@@*„€Jèt~R°|›IË%m§J-¤¥ÚJšÝYùÔZk`Ÿâ¿¯„íSkµÒSö¿n>’‘3ŒþZl•?czF•ÿ€ € PœAsqB¤#€ €Ty‚æ*ÿ@@ 8‚æâ„HG@¨òÍUþ# € €@qÍÅ ‘Ž € PåXr®Ê@@ üôz½2%%Åñû9 5Âl6»õ÷Iäææºuû-‹Û´_£ÑX‚ƒƒ‹]?¼Ð‡¬šKEV@pŽÀĉ;IAsPµjÕŠ\³X¡PZƒØ9µ—[)wÕ~¥2B€ÕjuIð*ÕWí/7e©"éKo4ãÇÿ*,,,½¬ê%h.+YÊE@"¤§°šéÓ§ÿÚ®]»Ì"3‘€@ fÏžÝ,))É[Ê^fA3sšK8dC@¨ºÍUwìé9 € €@ šKE6@@ª+@Ð\uÇžž#€ €”P€ ¹„PdC@¨ºÍUwìé9 €Ü$`LKTîú|WƒÿœÏ²­!|d£îÁ\¼ñH²î¦¬N=½xêGmttt½Ä4#±™SeWãþêɤKéZßÚi݆LútúÐGOßÜ‹þ´bÔ  “Ô!~ñ:´~\RZc­oÓôiï,^þH°ÎhÑ'*"&Ï|ê»_η’‰öõ JafÄÚnþ' ÏOÞøª\^Ú7«ºõOX0ºÎùü?_oº/<âë¿]Õ׬Õ4ìäû«þýQí[EQy)â×gu>ôkg¹l¿:Í.N™ÿöZ¹^‘sF1ó¥™C¾ý-¹…ñ@ßð¨y¯ g«8jùìÿéñbôòiý&Yc›Ë\·ÓÈQK¿8þùÜáó¾xùLÂù ¬óÇ·í‰kªV«óL&“Gh§3õj[; ¯ö½ž‰ j™êSÿ¾ôkG¢tqR@\½Ù?N,Y:íCýO[üŸŸöáÔ¸»{ÎZ¸xbꙨY‘¿ _¾eÎÈöµ3nÃD’‹š]Oµ € p“€‡·Ð§þVsûž=7¬™«uLmðð°´ÁØzðô#+gMž,§y×m›Ü¶w§%ëf-ž|Ì!s‹^klS,Tu›$7Ò*­Ò ñèßÿ{@ Jó„R4²f¬^¿ù™M›V?çp›ík&ƒJ¾f1šlÁ¯=½aH“ßåcÿ Ú¶¯ýÖHÇ;VL [ó¾›=Ï•à°ùþ5õÛ]‡_êxêÜwÁ“F}7[NkÕsôÎÇÄQù8Kú#`PϽ‹äcy3_J ¼ ·( &‹­¾\cŽ­þüT~W$‚æŠ4´@*,|d“îýÏ>éÑèá„õËf}àybcÍç¦mž*“ÓÒ”÷uväƒÑMcT™'Õk-|öûsGƒ§/ÚÝ%bùÛs.e媕j¥ÙbÒ¨šk¬R ,¾W¯ÔÌ‘öÞÒObBê=ÒNX¿+^¾lúEé‰ô¤ÅËçö õN©Çà7ã â/AºR£ÎP¾IÚîoÕìBþÑß]G.Û_·ëå£ÒK‚yÒ¿¦­ƒ®œ:‘ä;|Ê‚ÕÁ!Áé¿ØÞ`å’ÆÅGoí}ªó?ËwÖhÙïØ¶÷ÆnúÅá˜oe¨-ö¿ë©ñ6ߨ£Š$@Ð\‘Fƒ¶ € P…ÌF«í)«—ôÌ77ãŒçúUÛÊJizFʉÏ|gÌÚ:YéÛ:í½eã—Ô ðN粃ƒêþÒ¾sFˆƒ›<7Z>µâµác_Ð3ØpvãvBVñP󀫻my=…—Æjþ|å»íâ¥iöÛÍÆ\[öoé2Cmùwa{Ñ0×h,ô8 ø~cX°Hµß+ï?ß0³×GÇsÛ´è9bç”~-ŽøHs•/Ky–Óë‰Mââñ½mVn©´ÆùØ:[¢ã{{¶tlàÓ%šŒÛ“æí‹¦<[cÂô­O?Ü4ͱ\Ž]/À‹€®Z€ €’@ƒ§u¬«JÎ>·/xôÐÑÇŒ>^ò4ˆÄïã:ø=<2mDÏV;-Wò;ô…ˆ()®Ý²×±×‡w9q;¼«?~ÕaCdì yµŠÁÓæ½Û¶~ksøÀŸqQ¼5.üw# ’ë¶ÉRÚ„¥ßç«L³\ýÃoïG7~óSF9/HúËnìÂÕ‡6òI8½¾÷ó£fÎ>kPk» ·ù¾Ú÷[–-Ÿ<§–È;Ö¼÷‚0û4ê°qéÄ-rÙmÿÙ-^nÇ•s'‚?Ùu¸ý_ å¤BÜü¨¢ € P<‚Ä;Q‡ësryJ¨®õ°M³°KŒžµzßè™Æ}Y™&¥Ðj­7§ÛóÙ÷ªºaÉÑQó[¤©ê~MAÔÓï•¥GúŒÉúÑhñÞÞÚ¿Ô!D ±îËÃsíeÈû~ùŠý<°ýÈ Çsûuû^ébY¹wEž>Ka´(…‡··5?(¢aû»~è÷JNN¶"W â}ênúðói±?<½{yì+ŽAsÅ Z‚ €’€V 4‹„ð‚iiÍæ"Ó¥¥E%ä¹Ì–«95s¥½¯Ÿ_¡üÚêÖ² ‚Š._Ô}‹îßí:FšKÊòóâÒŽQ9 €TQïÖmÑ;^¢º´nÎ hvŽ#¥ € €@…ð~~µ =]®0Í£!n)À‹€n9l4@@ <šËS›º@@ÜR€ Ù-‡F#€ €”§AsyjS €Ø +HðYp+‚f·.‹ € à ‚fW¨S' € €€[ 4»ÕpÑX@@W4»B:@@ÜJ€/7q«á¢± €T¥R™·`Á‚{u:ÞÝzdµÞÝ;ŒÒýŠÒôÙÙ/MšÍæRÕ_š¶º*off¦WçÎåoO/³ ¹Ìh)@ŠX´hÑw‰‰‰Ú¢Ò+òu)à¿»¨ÙÅ»Ûö«T*÷ põÆl(œâ¼+Íγ¤$@(¡€ô„Ùܺuëìf'.`N³Ë‡€ € €Tt‚æŠ>B´@@ÀåLÏpùШ„¥>yJ?§+aß蔋@¶X·ðާ¯dÈÕyTÎü÷ÌÑûÔåR7•ÜJ€ ùV*\C@\(`Î8¥ü賓®´A¡IÉy Ù…#"ýáâÒÚ©¨œ‰•³[ô ÊM@íi•—±Í*¯ÔŠ·fE¹iTˆŠ˜Ó\!†F € €¦\…ãÖælC€Ù!™ÃòàIsù›S# € p[•OË”©S'Y½¼ò„È*M@žÏmï ±¬šËZ˜ò@@Ò ¨t¢G¿~I¥½üe'ÀôŒ²³¥d@@J"À“æJ2t@*“€Y$:¥ÍʳÚp*<ª[š7o¨çe@×1A³ëì©@¸¥€Yÿ«bØÐ‘›í«g(4!9_Æ~8LwËÜ\,¦g”‡2u € €¥—œ³o,9g—pÝž ÙuöÔŒ €ÜZÀ¤Pä:¤°äœ†‹™žá"xªE@ŠPûX›7 þ.[ã­ÏÍÔûµï7<’©Ej•KAs¹0S  € Pr•¶±uùæ­ï–ür–µÓ3ÊZ˜ò@@Ü^€ Ùí‡ € €”µÓ3ÊZ˜ò@@R ˜õ¿+žê2äÓdé>OéÇêßîì—»WLæ«´K éÄì Overview Policy is a way to control how BGP routes inserted to RIB or advertised to peers. Policy has two parts, **Condition** and **Action**. When a policy is configured, **Action** is applied to routes which meet **Condition** before routes proceed to next step. GoBGP supports **Condition** like `prefix`, `neighbor`(source/destination of the route), `aspath` etc.., and **Action** like `accept`, `reject`, `MED/aspath/community manipulation` etc... You can configure policy by configuration file, CLI or gRPC API. Here, we show how to configure policy via configuration file. ## Policy Model The following figure shows how policy works in normal BGP configuration.

policy model

There are **Import** and **Export** policy. **Import** policy is invoked before best path calculation and pushing routes to RIB. **Export** policy is invoked after that. You can check each policy by the following commands. ```shell $ gobgp global policy import $ gobgp global policy export ``` ##
Route Server Policy Model The following figure shows how policy works in [route server BGP configuration](https://github.com/osrg/gobgp/blob/master/docs/sources/route-server.md).

Announcement processing model implemented by the route server

In route server mode, adding to **Import** and **Export**, we have **In** policy. **Import** and **Export** policies are defined with respect to the local routing table. The **Import** policy defines what routes will be imported into its local RIBs. The **Export** policy defines what routes will be exported from its local RIBs. **In** polices are defined with respect to a peer. The **In** policy defines what routes will go to other peers' local routing tables. You can check each policy by the following commands. ```shell $ gobgp neighbor policy in $ gobgp neighbor policy import $ gobgp neighbor policy export ``` ##
Policy Stracture

policy component

A policy consists of statements. Each statement has condition(s) and action(s). Conditions are categorized into attributes below: - prefix - neighbor - aspath - aspath length - community - extended community - rpki validation result - route type (internal/external/local) - large community As showed in the figure above, some of the conditions point to defined sets, which are a container for each condition item (e.g. prefixes). Actions are categorized into attributes below: - accept or reject - add/replace/remove community or remove all communities - add/subtract or replace MED value - set next-hop - set local-pref - prepend AS number in the AS_PATH attribute When **ALL** conditions in the statement are `true`, the action(s) in the statement are executed. You can check policy configuration by the following commands. ```shell $ gobgp policy $ gobgp policy statement $ gobgp policy prefix $ gobgp policy neighbor $ gobgp policy as-path $ gobgp policy community $ gobgp policy ext-community $ gobgp policy large-community ``` ##
Policy Configuration Policy Configuration comes from two parts, [definition](#defined-sets) and [attachment](#attachment). For definition, we have [defined-sets](#defined-sets) and [policy-definition](#policy-definition). **defined-sets** defines condition item for some of the condition type. **policy-definitions** defines policies based on actions and conditions. - **defined-sets** A single **defined-sets** entry has prefix match that is named **prefix-sets** and neighbor match part that is named **neighbor-sets**. It also has **bgp-defined-sets**, a subset of **defined-sets** that defines conditions referring to BGP attributes such as aspath. This **defined-sets** has a name and it's used to refer to **defined-sets** items from outside. - **policy-definitions** **policy-definitions** is a list of policy. A single element has **statements** part that combines conditions with an action. Below are the steps for policy configuration 1. define defined-sets 1. define prefix-sets 1. define neighbor-sets 1. define bgp-defined-sets 1. define community-sets 1. define ext-community-sets 1. define as-path-setList 1. define large-community-sets 1. define policy-definitions 1. attach policies to global rib (or neighbor local rib when neighbor is [route-server-client](https://github.com/osrg/gobgp/blob/master/docs/sources/route-server.md)). ### 1. Defining defined-sets defined-sets has prefix information and neighbor information in prefix-sets and neighbor-sets section, and GoBGP uses these information to evaluate routes. Defining defined-sets is needed at first. prefix-sets and neighbor-sets section are prefix match part and neighbor match part. - defined-sets example ```toml # prefix match part [[defined-sets.prefix-sets]] prefix-set-name = "ps1" [[defined-sets.prefix-sets.prefix-list]] ip-prefix = "10.33.0.0/16" masklength-range = "21..24" # neighbor match part [[defined-sets.neighbor-sets]] neighbor-set-name = "ns1" neighbor-info-list = ["10.0.255.1"] ``` ---- #### prefix-sets prefix-sets has prefix-set-list, and prefix-set-list has prefix-set-name and prefix-list as its element. prefix-set-list is used as a condition. Note that prefix-sets has either v4 or v6 addresses. **prefix-set-list** has 1 element and list of subelement. | Element | Description | Example | Optional | |------------------|------------------------------------|---------------|------------| | prefix-set-name | name of prefix-set | "ps1" | | | prefix-list | list of prefix and range of length | | | **PrefixLlist** has 2 elements. | Element | Description | Example | Optional | |------------------|-------------------|----------------|------------| | ip-prefix | prefix value | "10.33.0.0/16" | | | masklength-range | range of length | "21..24" | Yes | ##### Examples - example 1 - Match routes whose high order 2 octets of NLRI is 10.33 and its prefix length is between from 21 to 24 - If you define a prefix-list that doesn't have MasklengthRange, it matches routes that have just 10.33.0.0/16 as NLRI. ```toml # example 1 [[defined-sets.prefix-sets]] prefix-set-name = "ps1" [[defined-sets.prefix-sets.prefix-list]] ip-prefix = "10.33.0.0/16" masklength-range = "21..24" ``` - example 2 - If you want to evaluate multiple routes with a single prefix-set-list, you can do this by adding an another prefix-list like this: - This prefix-set-list match checks if a route has 10.33.0.0/21 to 24 or 10.50.0.0/21 to 24. ```toml # example 2 [[defined-sets.prefix-sets]] prefix-set-name = "ps1" [[defined-sets.prefix-sets.prefix-list]] ip-prefix = "10.33.0.0/16" masklength-range = "21..24" [[defined-sets.prefix-sets.prefix-list]] ip-prefix = "10.50.0.0/16" masklength-range = "21..24" ``` - example 3 - prefix-set-name under prefix-set-list is reference to a single prefix-set. - If you want to add different prefix-set more, you can add other blocks that form the same structure with example 1. ```toml # example 3 [[defined-sets.prefix-sets]] prefix-set-name = "ps1" [[defined-sets.prefix-sets.prefix-list]] ip-prefix = "10.33.0.0/16" masklength-range = "21..24" [[defined-sets.prefix-sets]] prefix-set-name = "ps2" [[defined-sets.prefix-sets.prefix-list]] ip-prefix = "10.50.0.0/16" masklength-range = "21..24" ``` ---- #### neighbor-sets neighbor-sets has neighbor-set-list, and neighbor-set-list has neighbor-set-name and neighbor-info-list as its element. It is necessary to specify a neighbor address in neighbor-info-list. neighbor-set-list is used as a condition. **neighbor-set-list** has 1 element and list of subelement. | Element |Description | Example | Optional | |--------------------|---------------------------|--------------|------------| | neighbor-set-name | name of neighbor-set | "ns1" | | | neighbor-info-list | list of neighbor address | | | **neighbor-info-list** has 1 element. | Element |Description | Example | Optional | |-----------------|---------------------|--------------|------------| | address | neighbor address | "10.0.255.1" | | ##### Examples - example 1 ```toml # example 1 [[defined-sets.neighbor-sets]] neighbor-set-name = "ns1" neighbor-info-list = ["10.0.255.1"] # Prefix representations are also acceptable. [[defined-sets.neighbor-sets]] neighbor-set-name = "ns2" neighbor-info-list = ["10.0.0.0/24"] ``` - example 2 - As with prefix-set-list, neighbor-set-list can have multiple neighbor-info-list like this. ```toml # example 2 [[defined-sets.neighbor-sets]] neighbor-set-name = "ns1" neighbor-info-list = ["10.0.255.1", "10.0.255.2"] ``` - example 3 - As with prefix-set-list, multiple neighbor-set-lists can be defined. ```toml # example 3 [[defined-sets.neighbor-sets]] neighbor-set-name = "ns1" neighbor-info-list = ["10.0.255.1"] # another neighbor-set-list [[defined-sets.neighbor-sets]] neighbor-set-name = "ns2" neighbor-info-list = ["10.0.254.1"] ``` --- ### 2. Defining bgp-defined-sets bgp-defined-sets has Community information, Extended Community information and AS_PATH information in each Sets section respectively. And it is a child element of defined-sets. community-sets, ext-community-sets and as-path-sets section are each match part. Like prefix-sets and neighbor-sets, each can have multiple sets and each set can have multiple values. - bgp-defined-sets example ```toml # Community match part [[defined-sets.bgp-defined-sets.community-sets]] community-set-name = "community1" community-list = ["65100:10"] # Extended Community match part [[defined-sets.bgp-defined-sets.ext-community-sets]] ext-community-set-name = "ecommunity1" ext-community-list = ["RT:65100:10"] # AS_PATH match part [[defined-sets.bgp-defined-sets.as-path-sets]] as-path-set-name = "aspath1" as-path-list = ["^65100"] # Large Community match part [[defined-sets.bgp-defined-sets.large-community-sets]] large-community-set-name = "lcommunity1" large-community-list = ["65100:100:100"] ``` ---- #### community-sets community-sets has community-set-name and community-list as its element. The Community value are used to evaluate communities held by the destination. | Element | Description | Example | Optional | |--------------------|-------------------------|--------------|----------| | community-set-name | name of CommunitySet | "community1" | | | community-list | list of community value | | | **community-list** has 1 element. | Element | Description | Example | Optional | |------------|-------------------------|--------------|----------| | community | community value | "65100:10" | | You can use regular expressions to specify community in community-list. ##### Examples - example 1 - Match routes which has "65100:10" as a community value. ```toml # example 1 [[defined-sets.bgp-defined-sets.community-sets]] community-set-name = "community1" community-list = ["65100:10"] ``` - example 2 - Specifying community by regular expression - You can use regular expressions based on POSIX 1003.2 regular expressions. ```toml # example 2 [[defined-sets.bgp-defined-sets.community-sets]] community-set-name = "community2" community-list = ["6[0-9]+:[0-9]+"] ``` ---- #### ext-community-sets ext-community-sets has ext-community-set-name and ext-community-list as its element. The values are used to evaluate extended communities held by the destination. | Element | Description | Example | Optional | |------------------------|------------------------------------|------------------|----------| | ext-community-set-name | name of ExtCommunitySet | "ecommunity1" | | | ext-community-list | list of extended community value |    | | **ext-community-list** has 1 element. | Element | Description | Example | Optional | |----------------|----------------------------|------------------|----------| | ext-community | extended community value | "RT:65001:200" | | You can use regular expressions to specify extended community in ext-community-list. However, the first one element separated by (part of "RT") does not support to the regular expression. The part of "RT" indicates a subtype of extended community and subtypes that can be used are as follows: - RT: mean the route target. - SoO: mean the site of origin(route origin). ##### Examples - example 1 - Match routes which has "RT:65001:200" as a extended community value. ```toml # example 1 [[defined-sets.bgp-defined-sets.ext-community-sets]] ext-community-set-name = "ecommunity1" ext-community-list = ["RT:65100:200"] ``` - example 2 - Specifying extended community by regular expression - You can use regular expressions that is available in Golang. ```toml # example 2 [[defined-sets.bgp-defined-sets.ext-community-sets]] ext-community-set-name = "ecommunity2" ext-community-list = ["RT:6[0-9]+:[0-9]+"] ``` ---- #### as-path-sets as-path-sets has as-path-set-name and as-path-list as its element. The numbers are used to evaluate AS numbers in the destination's AS_PATH attribute. | Element | Description | Example | Optional | |------------------|---------------------------|------------|----------| | as-path-set-name | name of as-path-set | "aspath1" | | | as-path-list | list of as path value | | | **as-path-list** has 1 elements. | Element | Description | Example | Optional | |------------------|-------------------|------------|----------| | as-path-set | as path value | "^65100" | | The AS path regular expression is compatible with [Quagga](http://www.nongnu.org/quagga/docs/docs-multi/AS-Path-Regular-Expression.html) and Cisco. Note Character `_` has special meaning. It is abbreviation for `(^|[,{}() ]|$)`. Some examples follow: - From: `^65100_` means the route is passed from AS 65100 directly. - Any: `_65100_` means the route comes through AS 65100. - Origin: `_65100$` means the route is originated by AS 65100. - Only: `^65100$` means the route is originated by AS 65100 and comes from it directly. - `^65100_65001` - `65100_[0-9]+_.*$` - `^6[0-9]_5.*_65.?00$` ##### Examples - example 1 - Match routes which come from AS 65100. ```toml # example 1 [[defined-sets.bgp-defined-sets.as-path-sets]] as-path-set-name = "aspath1" as-path-list = ["^65100_"] ``` - example 2 - Match routes which come Origin AS 65100 and use regular expressions to other AS. ```toml # example 2 [[defined-sets.bgp-defined-sets.as-path-sets]] as-path-set-name = "aspath1" as-path-list = ["[0-9]+_65[0-9]+_65100$"] ``` --- ### 3. Defining policy-definitions policy-definitions consists of condition and action. Condition part is used to evaluate routes from neighbors, if matched, action will be applied. - an example of policy-definitions ```toml [[policy-definitions]] name = "example-policy" [[policy-definitions.statements]] name = "statement1" [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps1" match-set-options = "any" [policy-definitions.statements.conditions.match-neighbor-set] neighbor-set = "ns1" match-set-options = "invert" [policy-definitions.statements.conditions.bgp-conditions.match-community-set] community-set = "community1" match-set-options = "any" [policy-definitions.statements.conditions.bgp-conditions.match-ext-community-set] community-set = "ecommunity1" match-set-options = "any" [policy-definitions.statements.conditions.bgp-conditions.match-as-path-set] as-path-set = "aspath1" match-set-options = "any" [policy-definitions.statements.conditions.bgp-conditions.as-path-length] operator = "eq" value = 2 [policy-definitions.statements.actions] route-disposition = "accept-route" [policy-definitions.statements.actions.bgp-actions] set-med = "-200" [policy-definitions.statements.actions.bgp-actions.set-as-path-prepend] as = "65005" repeat-n = 5 [policy-definitions.statements.actions.bgp-actions.set-community] options = "add" [policy-definitions.statements.actions.bgp-actions.set-community.set-community-method] communities-list = ["65100:20"] ``` The elements of policy-definitions are as follows: - policy-definitions | Element | Description | Example | |---------|---------------|------------------| | name | policy's name | "example-policy" | - policy-definitions.statements | Element | Description | Example | |---------|-------------------|----------------| | name | statements's name | "statement1" | - policy-definitions.statements.conditions.match-prefix-set | Element | Description | Example | |------------------|---------------------------------------------------------------------------|---------| | prefix-set | name for defined-sets.prefix-sets.prefix-set-list that is used in this policy | "ps1" | | match-set-options | option for the check:
"any" or "invert". default is "any" | "any" | - policy-definitions.statements.conditions.match-neighbor-set | Element | Description | Example | |-------------------|-------------------------------------------------------------------------------|---------| | neighbor-set | name for defined-sets.neighbor-sets.neighbor-set-list that is used in this policy | "ns1" | | match-set-options | option for the check:
"any" or "invert". default is "any" | "any" | - policy-definitions.statements.conditions.bgp-conditions.match-community-set | Element | Description | Example | |-------------------|----------------------------------------------------------------------------------------------------|----------------| | community-set | name for defined-sets.bgp-defined-sets.community-sets.CommunitySetList that is used in this policy | "community1" | | match-set-options | option for the check:
"any" or "all" or "invert". default is "any" | "invert" | - policy-definitions.statements.conditions.bgp-conditions.match-ext-community-set | Element | Description | Example | |-------------------|---------------------------------------------------------------------------------------|---------------| | ext-community-set | name for defined-sets.bgp-defined-sets.ext-community-sets that is used in this policy | "ecommunity1" | | match-set-options | option for the check:
"any" or "all" or "invert". default is "any" | "invert" | - policy-definitions.statements.conditions.bgp-conditions.match-as-path-set | Element | Description | Example | |--------------------|---------------------------------------------------------------------------------|-----------| | as-path-set | name for defined-sets.bgp-defined-sets.as-path-sets that is used in this policy | "aspath1" | | match-set-options | option for the check:
"any" or "all" or "invert". default is "any" | "invert" | - policy-definitions.statements.conditions.bgp-conditions.match-as-path-length | Element | Description | Example | |----------|----------------------------------------------------------------------------------------------------|---------| | operator | operator to compare the length of AS number in AS_PATH attribute.
"eq","ge","le" can be used.
"eq" means that length of AS number is equal to Value element
"ge" means that length of AS number is equal or greater than the Value element
"le" means that length of AS number is equal or smaller than the Value element| "eq" | | value | value used to compare with the length of AS number in AS_PATH attribute | 2 | - policy-definitions.statements.actions | Element | Description | Example | |-------------------|---------------------------------------------------------------------------------------------------------------|----------------| | route-disposition | stop following policy/statement evaluation and accept/reject the route:
"accept-route" or "reject-route" | "accept-route" | - policy-definitions.statements.actions.bgp-actions | Element | Description | Example | |----------|---------------------------------------------------------------------------------------|---------| | set-med | set-med used to change the med value of the route.
If only numbers have been specified, replace the med value of route.
if number and operater(+ or -) have been specified, adding or subtracting the med value of route. | "-200" | - policy-definitions.statements.actions.bgp-actions.set-community | Element | Description | Example | |-------------|----------------------------------------------------------------------------------|------------| | options | operator to manipulate Community attribute in the route | "ADD" | | communities | communities used to manipulate the route's community accodriong to options below | "65100:20" | - policy-definitions.statements.actions.bgp-actions.set-as-path-prepend | Element | Description | Example | |----------|-------------------------------------------------------------------------------------------------------|---------| | as | AS number to prepend. You can use "last-as" to prepend the leftmost AS number in the aspath attribute.| "65100" | | repeat-n | repeat count to prepend AS | 5 | - Execution condition of Action Action statement is executed when the result of each Condition, including match-set-options is all true. **match-set-options** is defined how to determine the match result, in the condition with multiple evaluation set as follows: | Value | Description | |--------|---------------------------------------------------------------------------| | any | match is true if given value matches any member of the defined set | | all | match is true if given value matches all members of the defined set | | invert | match is true if given value does not match any member of the defined set |
##### Examples - example 1 - This policy definition has prefix-set *ps1* and neighbor-set *ns1* as its condition and routes matches the condition is rejected. ```toml # example 1 [[policy-definitions]] name = "policy1" [[policy-definitions.statements]] name = "statement1" [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps1" [policy-definitions.statements.conditions.match-neighbor-set] neighbor-set = "ns1" [policy-definitions.statements.actions] route-disposition = "reject-route" ``` - example 2 - policy-definition has two statements ```toml # example 2 [[policy-definitions]] name = "policy1" # first statement - (1) [[policy-definitions.statements]] name = "statement1" [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps1" [policy-definitions.statements.conditions.match-neighbor-set] neighbor-set = "ns1" [policy-definitions.statements.actions] route-disposition = "reject-route" # second statement - (2) [[policy-definitions.statements]] name = "statement2" [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps2" [policy-definitions.statements.conditions.match-neighbor-set] neighbor-set = "ns2" [policy-definitions.statements.actions] route-disposition = "reject-route" ``` - if a route matches the condition inside the first statement(1), GoBGP applies its action and quits the policy evaluation. - example 3 - If you want to add other policies, just add policy-definitions block following the first one like this ```toml # example 3 # first policy [[policy-definitions]] name = "policy1" [[policy-definitions.statements]] name = "statement1" [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps1" [policy-definitions.statements.conditions.match-neighbor-set] neighbor-set = "ns1" [policy-definitions.statements.actions] route-disposition = "reject-route" # second policy [[policy-definitions]] name = "policy2" [[policy-definitions.statements]] name = "statement2" [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps2" [policy-definitions.statements.conditions.match-neighbor-set] neighbor-set = "ns2" [policy-definitions.statements.actions] route-disposition = "reject-route" ``` - example 4 - This PolicyDefinition has multiple conditions including BgpConditions as follows: - prefix-set: *ps1* - neighbor-set: *ns1* - community-set: *community1* - ext-community-set: *ecommunity1* - as-path-set: *aspath1* - as-path length: *equal 2* - If a route matches all these conditions, it will be accepted with community "65100:20", next-hop 10.0.0.1, local-pref 110, med subtracted 200, as-path prepended 65005 five times. ```toml # example 4 [[policy-definitions]] name = "policy1" [[policy-definitions.statements]] name = "statement1" [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps1" [policy-definitions.statements.conditions.match-neighbor-set] neighbor-set = "ns1" [policy-definitions.statements.conditions.bgp-conditions.match-community-set] community-set = "community1" [policy-definitions.statements.conditions.bgp-conditions.match-ext-community-set] community-set = "ecommunity1" [policy-definitions.statements.conditions.bgp-conditions.match-as-path-set] community-set = "aspath1" [policy-definitions.statements.conditions.bgp-conditions.as-path-length] operator = "eq" value = 2 [policy-definitions.statements.actions] route-disposition = "accept-route" [policy-definitions.statements.actions.bgp-actions] set-med = "-200" set-next-hop = "10.0.0.1" set-local-pref = 110 [policy-definitions.statements.actions.bgp-actions.set-as-path-prepend] as = "65005" repeat-n = 5 [policy-definitions.statements.actions.bgp-actions.set-community] options = "ADD" [policy-definitions.statements.actions.bgp-actions.set-community.set-community-method] communities-list = ["65100:20"] ``` - example 5 - example of multiple statement ```toml # example 5 [[policy-definitions]] name = "policy1" [[policy-definitions.statements]] # statement without route-disposition continues to the next statement [policy-definitions.statements.actions.bgp-actions] set-med = "+100" [[policy-definitions.statements]] # if matched with "ps1", reject the route and stop evaluating # following statements [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps1" [policy-definitions.statements.actions] route-disposition = "reject-route" [[policy-definitions.statements]] # if matched with "ps2", accept the route and stop evaluating # following statements [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps2" [policy-definitions.statements.actions] route-disposition = "accept-route" [[policy-definitions.statements]] # since this is the last statement, if the route matched with "ps3", # add 10 to MED value and continue to the next policy if exists. # If not, default-policy is applied. [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps3" [policy-definitions.statements.actions.bgp-actions] set-med = "+10" ``` --- ###
4. Attaching policy Here we explain how to attach defined policies to [global rib](#global-attachment) and [neighbor local rib](#rs-attachment). #### 4.1 Attach policy to global rib To attach policies to global rib, add policy name to `global.apply-policy.config`. ```toml [global.apply-policy.config] import-policy-list = ["policy1"] export-policy-list = ["policy2"] default-import-policy = "accept-route" default-export-policy = "accept-route" ``` | Element | Description | Example | |-------------------------|---------------------------------------------------------------------------------------------|----------------| | import-policy | policy-definitions.name for Import policy | "policy1" | | export-policy | policy-definitions.name for Export policy | "policy2" | | default-import-policy | action when the route doesn't match any policy or none of the matched policy specifies `route-disposition`:
"accept-route" or "reject-route". default is "accept-route" | "accept-route" | | default-export-policy | action when the route doesn't match any policy or none of the matched policy specifies `route-disposition`:
"accept-route" or "reject-route". default is "accept-route" | "accept-route" | ####
4.2. Attach policy to route-server-client You can use policies defined above as Import or Export or In policy by attaching them to neighbors which is configured to be route-server client. To attach policies to neighbors, you need to add policy's name to `neighbors.apply-policy` in the neighbor's setting. This example attatches *policy1* to Import policy and *policy2* to Export policy and *policy3* is used as the In policy. ```toml [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.2" peer-as = 65002 [neighbors.route-server.config] route-server-client = true [neighbors.apply-policy.config] import-policy-list = ["policy1"] export-policy-list = ["policy2"] in-policy-list = ["policy3"] default-import-policy = "accept-route" default-export-policy = "accept-route" default-in-policy = "accept-route" ``` neighbors has a section to specify policies and the section's name is apply-policy. The apply-policy has 6 elements. | Element | Description | Example | |-------------------------|---------------------------------------------------------------------------------------------|----------------| | import-policy | policy-definitions.name for Import policy | "policy1" | | export-policy | policy-definitions.name for Export policy | "policy2" | | in-policy | policy-definitions.name for In policy | "policy3" | | default-import-policy | action when the route doesn't match any policy or none of the matched policy specifies `route-disposition`:
"accept-route" or "reject-route". default is "accept-route" | "accept-route" | | default-export-policy | action when the route doesn't match any policy or none of the matched policy specifies `route-disposition`:
"accept-route" or "reject-route". default is "accept-route" | "accept-route" | | default-in-policy | action when the route doesn't match any policy or none of the matched policy specifies `route-disposition`:
"accept-route" or "reject-route". default is "accept-route" | "accept-route" | ## Policy Configuration Example Neighbor 10.0.255.1 advertises 10.33.0.0/16 and 10.3.0.0/16 routes. We define an import policy for neighbor 10.0.255.2 that drops 10.33.0.0/16 route from Neighbor 10.0.255.1. ```toml [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" peer-as = 65001 [neighbors.route-server.config] route-server-client = true [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.2" peer-as = 65002 [neighbors.route-server.config] route-server-client = true [neighbors.apply-policy.config] import-policy-list = ["pd2"] [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.3" peer-as = 65003 [neighbors.route-server.config] route-server-client = true [[defined-sets.prefix-sets]] prefix-set-name = "ps2" [[defined-sets.prefix-sets.prefix-list]] ip-prefix = "10.33.0.0/16" [[defined-sets.prefix-sets.prefix-list]] ip-prefix = "10.50.0.0/16" [[defined-sets.neighbor-sets]] neighbor-set-name = "ns1" [[defined-sets.neighbor-sets.neighbor-info-list]] address = "10.0.255.1" [[policy-definitions]] name = "pd2" [[policy-definitions.statements]] name = "statement1" [policy-definitions.statements.conditions.match-prefix-set] prefix-set = "ps2" match-set-options = "any" [policy-definitions.statements.conditions.match-neighbor-set] neighbor-set = "ns1" match-set-options = "any" [policy-definitions.statements.actions] route-disposition = "reject-route" ``` Neighbor 10.0.255.2 has pd2 policy. The pd2 policy consists of ps2 prefix match and ns1 neighbor match. The ps2 specifies 10.33.0.0 and 10.50.0.0 address. The ps2 specifies the mask with **MASK** keyword. **masklength-range** keyword can specify the range of mask length like ```masklength-range 24..26```. The *ns1* specifies neighbor 10.0.255.1. The pd2 sets multiple condition, This means that only when all match conditions meets, the policy will be applied. The match-prefix-set sets match-set-options to "any". This means that when match to any of prefix-list, the policy will be applied. the policy will be applied to 10.33.0.0/16 or 10.50.0.0 route from neighbor 10.0.255.1. If the match-prefix-set sets match-set-options to "invert", It does not match to any of prefix-list, the policy will be applied. the policy will be applied to other than 10.33.0.0/16 or 10.50.0.0 route from neighbor 10.0.255.1 Let's confirm that 10.0.255.1 neighbor advertises two routes. ```bash $ gobgp neighbor 10.0.255.1 adj-in Network Next Hop AS_PATH Age Attrs 10.3.0.0/16 10.0.255.1 [65001] 00:51:57 [{Origin: 0} {Med: 0}] 10.33.0.0/16 10.0.255.1 [65001] 00:51:57 [{Origin: 0} {Med: 0}] ``` Now let's check out if the policy works as expected. ```bash $ gobgp neighbor 10.0.255.2 local Network Next Hop AS_PATH Age Attrs *> 10.3.0.0/16 10.0.255.1 [65001] 00:49:36 [{Origin: 0} {Med: 0}] $ gobgp neighbor 10.0.255.3 local Network Next Hop AS_PATH Age Attrs *> 10.3.0.0/16 10.0.255.1 [65001] 00:49:38 [{Origin: 0} {Med: 0}] *> 10.33.0.0/16 10.0.255.1 [65001] 00:49:38 [{Origin: 0} {Med: 0}] ``` gobgp-1.29/docs/sources/policy.png000066400000000000000000001471101324612745600172010ustar00rootroot00000000000000‰PNG  IHDRýp²â¢ŽsRGB®Îé pHYs  šœ@IDATxì]`EÛž½^ÒIB ¤ÑK„ž¡W)"ÍÏ*|Ÿþ(v슕¢¨ Eé"$TéB' é½\’«ÿLÈ%{—½²w{›»ËŒ.·3óÎ;3Ïlvvfžy‡Øa0ŒFÀ X®Ñpî/XÜF&+ðDÕó•HJ¿Þб˜ –«°ºfU‰0KÊŽ„úÌX$HOÊh*’¼=V#°Ã'-Tæ~§Õå"ÿžtêÊ,­¿ñ×Ú24*Â7Nƒ€Þse¬^\Èôõ,Þ}ðÔ^²œþó7±k]û·~NyˆdôãÈéšî5€Çá¦x¸r¾{9¥'‡ çÎ]â°¨s3ÿcŸâ;ûw¥bUÙÄ<ξÑá¼9¯¯=Uªë*$Õ5>„üéÖ³™Íäæ|ØNx{O¶6ÜK¢~ïïc—ë§×Æûp°¸\ßG“c"ŽÃ„VudŸÿX7?øñ$˜§f™¯ J5*¥J­’R… #ÈÀ§ÖÝl•\0óåéQ ÅkÃ-(ƒ6)þutž+cÕR©APn±|ϰA}“SÊÅÚoScb¤8(Õêð¢r寍¨>8ÜÀT|k]^ÝíJîðQ’ŠZî³°íɯ9345Qª9AÔz,¬$LžW|ã¯C>*ER=þÏdyÉcúOh^*Ý‘®× ¯¢V9×`¤‰¹šNƒ‰„D[Ýhäiy8UÕWÍÔW[©l9ëÇYè×y¹ 8 š¦Ø~æ»Sò;æKcÉVŒ­ç áT«ä {,¡ï<Û`&“GôYhÝX«½ ¾ðÃóÍËÂsÇGÄ7·"¤ùàŠÎk´>ã¾°ÃW—çl4·™ÅÄž9ã"F˜+o\N[\µÁAžáô–äÖI£Ù¦Žµ³Ðˆñsã¹”e‚å3ú-ßz%“2ÚH %Ô¡î»|¹´§——ª¤ä:wùòõô—ŒäÙ¯æ’|ðúü1÷?Ùx°ÐPÞL„TsWî~µË÷“VÞ©dBÖáüð9šûßoÝš©WÓÌÎs>lïAZkTª¹h •€ë£´^*ýü‹¿õóéúõþ†eä7X•œ7¾Øš×çë»B¸FV˜uX)¯›©T*‡ÕÉ•3ïçËOˆŽVQ.hÂe„‚ŒÛwòòò.äåe]¸–‘û‚6_Ûý`Å ÑUÛéÇš 5ÐPN#¦þüÖÃúWfC…!ªŒn‡ÿ(©˜²Z0W±S'{œ º¬KŸ“+Äåp3›üøÀŸªÿð ‹ “ÿ,‘Ë*ÞU*åãåJåø²êº•Ñ¯PðJ3·5éhºËͼ6/«þ›w!+¯àåû¼I\÷’\9²RÒh€žm•CQ‡¯+À©µ‹JåžAh†ªÑåVj˜^"nÔˆÝMt¿¾©ú*]!›ùšw’6ËÊ:ÅâókutPGÑ0…Äû½&­<ðôÄÈAMþGwá[ž¬múÂÒ5è×èPF Úkú‹Pª…AÌ­/Ë Ç9𷜪‘ !?ó\ަŒJÎTØÅ\qÂüù/ùÌ™3§ºFÀÙ2®ÎÎ‚Üø[MéÁñŠÀQ ¯”×–žue£F¿Ñƒün¤âS¡ç¨Ì§ß‰r¸ÓÝ´gºßÛ'ÖíYà§=)ÿjË®ÿ›²lÛ.r=àµhG¬_>[ú¦Ó²’w›€PÕœ¸. ¤3ÝŸZ¨ú¶I€ˆqsFÉpŠ©k[·¡±±±n@êõªNT‹x cÇî½É3 ëKõí¢û·Hq¦vŒ€\Í ?/!!¡þ5jì‡ÄüÆÍ¿~’\쎞ÜÅd¿¹÷9åê7nœ+¸}ûv6º*àly)¬Döùº=çiMÁš›7–kyú®œ5†ÜAøH‰—/Ÿ Sr)M¥‚eOÕ䇛íôž? Oµ/>2Ò+ dP° }€’õSÝ÷¹ìK.«€£N1:»G(•^f“.Üûf åÄo“ŒsÜ‘v[£¨é¯Ãu¥ÄÆòIxÊ}ô­‡}¢¢€ !FI]œþYc"x3$˜Óeõ¶#©`JoÜxÀ3@R!§#ß?»|¹ÄC&#¿û£W¬XYeôAk”ÔÞpÀöí›®õŽõ¹¨qjŠ †öÚN³4°nØa #À%åU ÇÐÞ]3ÿ÷ƒ—ö“±4®X&~n7]¿zÛùTKuàtö‹€ºôþ×äÒ=Ó¿æû}Gàxvx§íÑÆ]̯Ãûú=ûáö®k£€€#O9vúÊ„c§“QX)8ªñ‰|-ZAùmL¥w§Ç.ZäáêêJ^µª’HÊTˆhíâé[™¬mkô¸3줙2ØðoqG(êôϲCtúò¬3Ÿk;vÔ~;_9†nCSü²¢wyטê p-³qÍMM­Þ–¢Ó —bžû®ÿVš=¢†ßÅèèjC ½1#ª ô^€hm‡/…F‚‰rņ̃®¾”—õ¨Zø_Œ€…T+Ô=-L Ä<î>@xG;‚Rª5®uµ5Ï6ý½àT†úŽæ(àqК vNƒ@¯…k<É›‚´âqä™Z’ñ¥W;ØïµèFUE-íÙ_K6vßgäÔ¹ÉåÙ”·¨E3žU¥îä>ôéDqMÏ1òiÀ nêìæá(ĹœöïÙ~kוx¤u%TÐÄ Í¨¨¨Òd;üGUh\cB{úÕMÄ?>‡¸ß¬’1ãɇ͢)àsmØ ]}”„†çÑŸ¬ù:$’br¾Ç AÏÛÓsº»»û|t¹¸{Í:S¯ph%ã¾²|Æ€ KK+~þıcÿw¬á:uâøsÎã(t¦h9`ú§}³D?Nc¿poí[B.R-Bï[ôÞzm@¡nÇÑħ’¤N"§ó”hŠÈþú{Þ%µJÒLGCŸÃ©¯×FkHB Æn ¿®Q¿B²ºàÒïøw“ëYªd=H%OP£ ©6rk8;#‰XÒm;Ö³7aø§Ók^Èzp=TjuPs Bðáœðvpƒ’Z­órj. b”¡çlÕ¬ç÷¬»¨à‰NÐ+ –nÍð9ŠûúãðáÛЕtøïMçϞ꣹­'ãr1¿vÙoî½RîÕ|~ jÜÛý¬C&Ww$ûñ½ã#@è ¦PÞ·pT /]—Z¨¬çS¯Ëä*kvÁÏ'˘¼on´§1‰BýèÙ»³bb•žŸÙ(¨wÓ¼_løÄi¿Ÿ«ùÜÁzÉë½ú³ø‡JÎÞ ÍTÛM99åÙ+èæX&ñLóœ†Ã¿Ôòú‘žà4$< 5ã0Zºá‡Äx†û®Dp#´îsg@ÎPðå‘KÇÀå†j[}Xʇ;&ðak\¾"×@û>ïöžÆéÓ²ð$Œo¶/˜œ†êž'(¡áÔ”¤’ÇaÎ@¯i/öâß?O«2rµ ~ÏþÓéoÂÛiÏçyг¼1ÝÔ”,Òñ›ð\NÚ‹¸Y:[›’¼;k¸Ó~Œª†€ËÏýå<Õ…»_©ß§ß$¹]péîÓ×Ù6è+MÄn½Œìr5ꃚí½Wd]üüU,â‚{ä|ìõÞ®;ý! õ”'Ôž îÖ>ˆáœ;—+¿U¤êR‘û¤VN»Æçö\ïÎqoöý€Kêýpú_=à¹Ñ´éý.‡ûrcÖÄAf¬*á±ù<ëWÖ¨Æi[)›ß̉ŒÞCitŠ çzOš1£ï ´øGîr™¬Ö;¯(÷] ¯OÖÓΕ8Jöã{ÇF€ûðúÇä´u%^ðì¾]¥R ´á\®»üÚ”eí°GaˆO¥š—òÖ›ëû½¶§q½¿HÆwÙŒØMŸn=–‰äúybQœ„îÍup0¥²è2ê¸ñŸþXÔØé#³kOn)ª0j˜®E¾äMuV*°÷+_hŸboçÝ £É !ZÚ2 òJÏ “»ŠÏ=y9×íþ3€RÖP÷GÅòuáž1Z@;‰´ëN_vë×%äzK4ï}ñóö‡Øö‰Š.4N;¡5¦ðA—bß_u`QãÀ\¦ä†kRðéñÐDGë9(´¶@Хû6Áº|ÑT—(ÎÒÑÐy§š0UÉ|t¦EezƒÕŸŠR‚ïw_¾´N'GSšq¼Ý"€Öº_‹&}Ô©Àž#ÉßÄ9ø麈‰Ï½ÂÉ»|AZoà&ŽX#ÿ»¨ñŒ$¥Ë  º ="šâÌfKÚôÖþ&/ï'ëÛlÐD€üÒÚm;]‡êÑÕÜuõá$g€Ïo[ÚgÀ L‰&WsÃ/¥çd\JßÒ\ ADñ {SNQFÚY`óår{) dÊkôÖ•Fß,ž«ß›ä¸ÔBâ[¥{À|r8êð‘Ÿl‘Ôä'$Y ÷š®z² ÎÂY€³QJŸE©æ¡¥-æ««³{H}µk¡ÌëÇÙF ¯‡JÄS0Ô¾—wŸ¬»eíÙtõdâìæëBMã ÌvµBƒ&ŽÞ;ßXnAm4Þሾ wôë=ÙÎסò«À³±.T1 aF àFÒÙ$Ên;ýˆßx“§ÞÑ—Ôâ— ¡P¹àrç­eo^:ü×&OP9N«£-d>ÃC2µƒhb²º!ŽÑFâ¢q?(‡',ÒæohÆ*šQUHí/^)v ã]s1‡Ùƒ‚»Ç«jK²àH¶$ÜÊŸGFª/àð%ß\þx‚+LèùjÁ9Â'Ê­&mïç8̶”gœ TU>@æÖu4`¤Y›”°6Òç\ÞuÊZÒeÂWÁã×åÝø2h1PÉýq˜í0àÒºòkJ Æegì˜D ’ß{ᆱ‹W³ȘÌëjŽ@òw}ç*e…kaŒ¡ãÿš'Â!f  !Â_¸ÝI+(’¸ÔÛ+«•U5ö8 [`¼¶ÿ“ʪ¼MûL-þ¶ømlH[('ëìöôÍŽ!Da¶ éÅfs ®m™<\–›|Âsa„“pŸð IàvžúšÖÇŽEp§o3°ûùDÌñ ñÑ=›å€S"ÀV§Ï–1„Y?ÝcÖÖËäÚj;FF é£ˆj Vd=ø#à ¸¶:TšºÍ`aÇ,íëʲ]¡JÜé3‹«Im<‘[éÛ|¹Š-«sžòª¼ “µÆŒ#À—z¢£CžÅ8Ì+T|ŽùÇn3Ÿ«Õ2ö;Ô)Õ´ZpÅn Žìƒ•ʳuÅØêôm]¬ßaS6$è+¢q°5,Wîî4qÎì•5êpZó¸ñ˘(íi~ ,i&½BGÝ5S‹1ˆ@Ö‘÷¿MÄ JJUluú܃bNS–Ú‚+¿££‡M3i­Ê—÷ãÖT•°õ7ÔZQ¦¬·²¦¨3ŒPFâ@+ÐÆm¥Š0‘Ϧ ‰|6…×°rLä3Œ ŽqL0‘Ï1ÛÍîJ‰|6lLä³!¸ÆUc"Ÿq|¬ˆÅD>+À³&)&òYƒNÛˆ&ò5BÁü &ò1©™1‘ÏL h‹a"mÈJ€‰| ÙÚÕ`"Ÿ-ŸLä³%ºXwK €‰|-:›yb"›hã¼0FÀD>F`¤R‚‰|T¨°†‰|,€Ü²ÀD>›¶2&òÙ^ÃÊ1‘Ï068Æ1ÀD>Çl7»+5&òÙ°I0‘φàW‰|Æñ±"ù¬Ïš¤˜Èg z8m#˜È×ó7˜ÈÇ<¦fjÄD>3¢-†‰|´!c([D>¶NÙk°È§ª?Z—!Œ°3h ò¡S³L­+uY<î£CL6M»'F•×+`"ЀúçÄ©ÂZ¢P˜¡‘z">Z×Bv%ŠÁTpQG„£,¥þKAL ´qº°O@1Ô™nDäÃGëšÆÉq%Øêôë-òé­ë¸°9eÉÿ r<Ü@ý©qÃã@Í{O€¥Ô>),|*è[q ¼lnË)Ÿ–®Gëª_Hy~^ ~ 21 Ú¹¢Ÿ¢<'L¤¬ìÆUÕtQª”ÞðËÕ*¡B­ð9íãs¹µ|¿DͦÊx.·Ò€4¿/œ÷ VM+S–„oÿ9?¢üÞ‘T˜É“ö4[AO8¦gkGKPg¼ŒË,_ÃX+Ú;Å«Ë(>>ic[†*ªªä/ƒr­ÉCó'…Ÿ¸Rkt8JÚè×À°óiÝši¢Ì“Ý‚ã]»=¾ù¬ ¹.ÏI]ù5¸®ºÐS#¯–j”5R•Z!âòEU®´šˆ«ÅÞÝrýú¿-’¸hZ¼À& '4Õ—W"S¼6ulômZ K”g$½\u÷Ð$‘¢"¾²®:´·§WM¤·§°‡{ °³›è(•)\ù|à¯*…BP©T * ×¼šŸÛ]n–—º\šV{~çSâ"(KÎw;!j?xwØø/oXR&[¤AD¾äUifèî5 èµq1¸n†,A "_uv(ƒ}ˆ|{aÎ¥æÞ.æms|9¸hazgM†ˆ|íí­rÕ¥Ù¼œ“ôªÉ¿:H,¯Œ‘+j:Ö*®\\e!B!zO">Ç•Ç#Jå%ê2E¾ººF©É|ø/ÿÚ…ï„nQ—/¾U-ö9ê4âLPì²öVÇ":O¦³luúvq´nIæ iVâ;SE²ÜY•2àù°0Îÿ.‚!>>@Âãñ=.°ãG—¿X ÐGÁ°¶m‘¸]r• œ-* 9š_¸þî'n}} \%öÝê½d«oïi–¾”ŒÇì8‘¯ÌD"QUÝ£©NrN=/\¾ýÁâ !"ß ±‹—Úb8¡Eˆ|–þ}ñkå­çhÞE#Á™ók£HDäË[Ô( 'BÇVgxa0·øæL™¬|H ‹‹bl€¿0Á/Lоƒƒ\\‡ ÜLe[ ßÏ©þ×ÊÊü÷f?˜ò#qýòµ©÷.QèÈ!Ã?0gPd*«ãˆ|èXãL«•QÀÖô>ˆ\’È´ ‘¯àêvϳ_ÌSVåÎ×.<&áç‡#ÐXuº°lJK¯ý53ˆàƒå±àû€¨E¦ÖÒ,ÏÐHÊk[&—å&ß"¦ˆ|ý=·/ÉFÔ9O”O€‡ÖUˆû„gH&òY¢E©“¿ë;W)+\ [Ú†AQÁøsƒÀÑ)A51˜Ó¹õó‰hY"_á¿Ü þ]¹@U7;ØÅ…X*DxÂ=SîNEøéÞ=ùÆ´t•šàæ+½:öØïZr ymÿ'•Uy›`3™ª'•¶Öo[äh]4%tóÇaÏæyõè/þü”qc%Û†•Œô÷·Y‡@gÖˆ¥Mš$šëïùxñ¿«þ¹µ.ú²ìsvñMõ À°”÷g€qN¾Œ•9]ÅZI…°È—½b.8ÖJàc?èhs?3êÛbùÐàìÖºþoæúßIô¾¾U¼mð€ NuY¦­8™²iÔ´ZY•mFƒ&ÀÇùLd*úÖŽ9ïÿw¸¯ fÑÍ ã% …º¢kì9?¸°º_¤ sòdÑ(OÁôìí3ßøu"êlXs4,òÉ»ù9kkáŒ*dÀ¡~»–EÁù¬Ïš¤ XäSÆv·ryÇš °œ¶¸xÂ,íò#Mãߨ8bF.œ=éç6ãöÄ "ô¾cá}f{GÀAàÅ1£¥»c†øF€¢×3~Œz™ çÌÚ ¢Ú%Ž.éIy-é¼à´ÔïC‹÷ÅÅzx—ßûøÎšˆ/ó”2Ѱȇˆ|½Ù(“Óä-òµXS2`‘¯žÈ×b°ßŒYµÈ‡Õ™îë¤.Zvvô(É7ýû ÛK$-‚N äi™àòMßð`EÚþ_o¯í»íìb«0Ø"ŸHçœ_ãwsÌ®p‘zƉ$S:t°@‹í’ õõ7'Œ“Žöqž½}Æßi‡_ïl»Üi&ùLeÕêˆ|S ÇùŒÂcËH‘ÏÒlZ‘e&‘oV±¥ ÒIw}ó¸I•W~þkuDïó°ÃïéáA'¹ÍdŸ !2&O÷qµíñƒ‰ï„Ø,3’b¶,ò±µ¦ß`‘TC†oÓ¼Vòïç»_íÚ©Sâðx‰»@Àp̨p¹`ËàÁ¢¯ûöñ«¾¾õÔóû3£™Z >Z——w§Bc%ë­K.µ7æÆ´{ÇŒå;Û­‹fcálçç^Uiï=JèÿÄÇKª3nj‰õ#Š2a"(8È>ÀD>zíb/D>4]þà—ø=„ïC®•½Œî ¡¹nëFËü¢›+nlˆŸcHމpLä3ҧV”ÜÙÿÇÖ¡C¤S;v´¯ÏEuYþGFð‹oÿ¹óƯSFš·(ù¨aÃD>j\%ùèµ”=ùa/ûé;FûzõÙ#AÆÏÁuwwçÇŒUß}ëîÆØglUfLä3Y4¥_|á›ýŸôéƵkgF û‰öögG  eÕ­ó¢™.!&ò1(I&ò‘À`÷ùl†·Mˆ|UEü[ë#OÅûz…ü:d°ÈV6Rl… ´²{ €Êì%·~ö¼-òÁD>¨æ^ø¾mõµßÛ<`€di&¤í;º_›6à@|œX™uâû´ƒËº0YZLä£Fs´Èc0‘»ÅD>zM„,òÁ-BäC#ü¬­×ôövÛ1t¨Q˧ôjÅ®4Ú‚}eìX°:wáÍM£á³ùŒà‰¢ò³_­}µ{7éc6¥o¨ZȠϺ¨þ’ÚÔ?”>ø—±=+˜ÈG8&òQã‚C–$òeþ2òÝg‚Ûw?2<^à|+cO@ÜNxnÌh1§ìî»·vÌEÎ9$‘/ã·q/÷p“„¼Ù³§Ã~5R=)3ƒƒ‰Éí<ó÷-ú”*ÞÆa˜Ègc€±zæÀD>zX¶‘ïæïÓㄊòÉ˺wõt´)}CB»/¿A»+ʬ“_#+‚†äè†c"ŸÄRw>Ó_S8{$‚8úW#U-ôá*‡^ß<~U<Ý0Lä£F ù¨qq”PLä£×R-AäCvS”¹çWÿ#fÒn>½šÛFzT@XØ©“¤äÄòoëêÌØ iF10‘$4­¯zxüÓoû÷“xØé>|ŠbÓ âs8`ÓÀRMÉÍwЩ€´Sc"(La"SHÒÖƒ‰|´!37cD¾Š kV¼Õ«§p$+;£û4¢ ½ÛýîÖ‰S˜¨&òQ ˜öÇÔ¹=Ü\½Ÿ ¢ˆuž ôG2µCaÁ¡——Z[+Lä£Fù¨qq”PLä£×Rlùnü65Þ (Ã_îÖÍ1öåу³^Í4o-U—Üy»øö«vÁD>½F@ ªÊ2—l8€1’›^våýª?‘RVòXæñVÙÆD>êfÅD>j\p¨s"À&‘mÏÓä_ùZ´“ ™Kgv‘pçÕSÁÁ¼Âãï,s”z²Õ"V[äË=ùÉìqíˆN^²‡ÆsãóÁ‹]:skon{‘¥ò`"K@ãl¬GùèaÈ&‘/cï¦ ðn#°i îÃðpQ]uÑD´Üšúb" =dºQU™ýÌ»½{Ûóyô¤3s §Æøu5%cs/®÷µT#&òQ#‡‰|Ô¸8J(&òÑk)¶ˆ|È®>Q–±äƒðÞ­bFµ:Eõ¹Nˆ²K?.¢×*ºÒ˜ÈGÂ#ãïW'ÆÂ¯Fd±5¹6ðaúOXQriÃS–Öù,EÎŒt˜ÈgH¶ÁD>Ûà µZEä»÷çÜI^^ü('%ïB}Yyuñ”Âk;->*ùHèJª³f=êB j5·óBB@Vø¸¥ÛB0‘úQÁD>j\%ùèµ[D>q價/wëÚêÞÕ>"˜Ð¾&ïÜ7“èµL“4&ò5`‘sî[ÿZyMØxµ­ßÔ¤–Ýõñò>B¡ðþ¾úY¢ù¨QÃD>j\p¨s"À‘ïþ‰UíåªÚà1p{ktO‡†Š5¹OÚ{ÝížÈWpmÛ¸'ƒƒ5ÎÎ5ö @#buÑeFö‚ÉùŒ€ƒ£ì Lä£×lùªîü52Ù‰Öú®îç8jUÛôÄ·CéµÎ#i¶ˆ|lí¡ô”WåÁª]¦ †K]iü¿Þ"ºé¬–Ÿø:à>A>ȧ¨?{ h®È¬VMWA¬_[î‡7o ¥›É[@äcëCÐ’ê0–†A"ceŠÌGù’S÷[s`’2¶;Lª×6°[ ˜Ú·ÿxÇ@ÎæŒcqЛF·ˆÈwye`Ýttåíú˜ •µU½âXßú!œýÃî„€3f]|‘ïãé J…WþåßÚÐUˆ‰|t£!‰|4ÀbVù˜Å“¤Í""²Z%—u€'é‘T±p• Ûá£,ÛE¢…Vüý…p jQG‰|°í_Þ3ÈÅ¥m‰`ÕŒD[ŠIƒÁjAe†ªˆiÛ¶®ðÆoéf‰|Ôˆa"5.ŽЉ|ôZÊÖD¾¼3ßõïëÕ¦VÀåÒ+˜•ÒĨ¾¤€˜Enû X__PQ[Ù X鿆‰|±š’Œ°Þž½/]8éÉHÐ[æ2ÒÓSª®.êd `ƒ1‘L䣯‡:'¶&ò©Êï÷ƒÆÓØÝ›/ˆ.äyý¦¶#úoò°x‡ê u©Í9õ©þT1‹¥0ž•­§÷‡Ù?¯îwvL£}ö0§¶´S¸»;ËyBeذ3Ú8¢6ŠíêîÆ‘ªeÝm¤©ÅD>‚‹U3‹&òÑÃÓÖD>‰FÖ=ÌÅ…öè–^-ô¤ÇÄžy•vœ>ì~ƒhK×ÙÍv]÷‚µ~s"òî¼ bëNÌç3x]®Ê=ÿîÍí3‡Ôʪ̞!—hjºwvw7[Þ\pÊEÁ©}òj‚²NW¼md‹¬uvs*e]ˆnaLû, ò™Vê ùTN‡ÃU[ä£×d¶&ò)µ!èÅžƒ¼«azƒé:%){ Zˆƒîá!†³²´ßÕ YäCD@D"ü¼|H€4ÞÚºÓG!C  Vt«Ìü÷ë”ïº_¸´¦ß²Œ¤wM~ ©T ßv¿Öˆq½ÁA7š¿×u¹ãwÄhö׋:H¥@®Tжö„‰|:Íɬù˜Å“†6Lä£=QÚD>d8¬R^ëÛM‹©!ppF¦5+Ó€ú›¿ukÚB¬žîî\‰º†ö¬,ƒD>ôÁñ!¼Àë¼&˯z‡ÖËWÁ+ò‘צÿ»¦ÞR“¢:ÿ©‚ä S~½õ¿{ÊU¥VI\àÁ3¬9´FJ^#ªšÎM\ íÑX "ú7œoô³qãÂã:•ŠöÖE‘¯ÌD9EUu€<ÇaBܱ£çÅ‚ƒË·Ã-[Ö¸åý¸5 7pÄ.^jkÔà´ô ùJé§®OÁ¯•vG”‰dˆÈw~-È1­Kó°mD6­%ÕºÒ[B4+d‘ÄGŒÑã]¥%ÃíÔ§€¦z<‰Íÿˆƒ¥ÙcFµMc¶„›@Òà ÌS[™ÛìÙ+¹õç˜ÉŸðzhvf†µú¹¡?¼Ð ¼Ö¡NÝ€—-\\s¥Üè®á|—ã.á¿4o Q©TbWØÙ±æÆÀíäÌŠ/M 8qhf÷hŠkX/R³¸g_qPiÔ¼šªZL‘õ|”\µÖ~ˆ|°Ó·²³¾2%ëÐkÜÎS¸ØÚñÄõ·o‘oÞZ 7]J"2ýÈŸÝ3-ûH¢¶ U"äòÐÜ:K/ën€è«»t­9„´2 ¹šˆMqÄ`ÈÁÚÃîž}4@j5yô¨eιµÿUÕUEèBF­@ë#cà…°dÒ!½è]÷4¼Æ¡FJ—­f &8ü‡BŽ?·øò>ïî*Le*W«„®¬ô½àQ¨N‘4'?òËàdÚ|8  7¬]Ù£#okü’VȲÏJÅ]ÇVÚ /Däk5ëÓáKëÿ¸¶@km€%Vic‘¯4u?ú$·Ôe¯˜ ŽÁÄhäô®ÈW +ŠÖ|¹öueÙh`v§/+Mw‘ò¸ì½;†Äëò®êîõy8–„NsðÇ×ß×ÿÓq°6 ‹ƒ}Ôg©4*íH»©, w‘/¦¼Þ,¤lW[”Š:SmD•\¦ÑÞ4ü¢¾M—ï…×ZxeãˬeÈûåG¾xa¼7Û 8ܺJ…Bä§KlîBFê®Á ‰)ï.špQÁçYßV@ýzÑ °-Ë §÷ù’öЮÙÎ"_«x 2Hä3»-° s`‹|ô°´%‘¯<íÈ P°ð’~Tg"¡¯nå…]÷§Ÿ¨_^ÐïÒq°4,.Çò¡]èh¿G´È‡Hhhd^ßÀk;¼ªàUïhL›ÐÌßÎP® ¼VqÒ3Ó4Šq¹ÜšJ%Ó3êunˆ‰uü<ðBS5ú~}$\/ÍÞž}ÄåµuýþAÖ¯( K„‰|”°0ˆ‰|ÌàhLä³4ó’Ð&ò)ªr†h4,$p”ß8ãJªzG£wµ~ŸEˆèa$AÛß–+€CpM=ÐÈŽ!"2uÿ¼z }m„Wc‡ïé D4Ü](‹¦}¸Åœ¦‘®^”Ëáʪ €¶wp¨ú6¡çˆ8¸^Ä’«‚¾ˆÇ«åð…i9§VÅš›-‰Èg*I«#òA@¬'òAŽ…)`q<óˆ|–*ouD>TŽi°‘oV±i¹Gˆc¤’W †|#vþ&Æ4q«Ì-¤´'«{öáì4 8Z3²¨* YäCý[ðJG:©; ëû᪠ãrùY2ÚLÆTRÇ ¥»FŠzËxmiº6@ÒÝôõëEºA¶òeUW_Æwk—X[’on>Ø"5RØ"5.8Ô9°•E¾ÌC/÷#¸üp.™P©ÑŒ²-ä] Ôã]Ý8Ôèݬó®þhТr£C,öFûÕhvšàT6fog7luú,±È'#Ä7SËËm¾lN$ôÒm–´p~¼þnº’ Ã ÑYm@ëE}tÓÚÈ—ZQ¸!ázÚaa{w»¼B]Í‘Üôë;¿~YgÜ?ü*Ú{iÒY@ä3©Ó$ò±ÇZvશÈGH[ù5% n!±‡ë14 Ál"_ÚÁe]@¨C>¾[Çݸ[QaÃYY/@tÒå]iÎ2P\rhòu»¢â•ÛÞݬ¨P =ƒ ®©*CD>CêÃ)¸ŽqLÞXDäk³üú£…%uuÀfÇë&} TI4ªz~PÍ^G#õ¢jH=žŸ/l·ø Òæ24±øê6´çò#SÚID>l‘Öj\p¨s"` "Ÿ\V8Â¥m¯úNߧדgNä Ð¥5»S……ÀU(Mwˆ´[£_luúùÐÃS%ôL:’—g·²ñ€ËËW¾ËIr^|±WbEúq³¦øÉéŒÜc"Ÿpp”}!€‰|ôÚƒi"_Îù5~•" Ã¨Ï“QIÚ†Ï(ñE—J /Ò+±cJ'æå)kùn‰–”ùPóí5}ÿo„YÅk¥níÝ»5\ßHtC£ùtO’W˜ìô1‘¯2LäÓÃá<˜ÈG¯É˜&ò^Û9‚+ò8J>lJ-tKÚÿ0Gw!^1^zgVV­È?ü¸%ÁD¾Ô¢_Ì Ä÷öç¢iZ¯ã¤¹¿œ ëêê:Žÿî"¹ÔG|r^£ª ÍOÙj”Fˆ‰|dÔ¾ÇD>†5_&ò™MI³ˆ|ŠÊìb¯Ðú©}­~A‡aÛ×ݽ+×´Ò)þäâbPX+¯í4q²ŠGÛ±Eäckzß""Ÿ5™´Ão?ÞK£máH›Þ‘7¥§Ã@}v…jH=Û+¹÷y—7ÆëDèyHD>½˜f^l‘¯$&ÐѺØ"Ÿ lM"òYš¶ÈG‰œi‹|ÅéI.*EMxûØ7O‘U„Ž]}«NJѺvktÓÓë€Ôw›¥ugÈ"ŸÉìÙêô-"òiK¡‚ËßÈJ|Pc Þ &òéÒàÅD>j\%ùèµSD¾ª¢ ¾ª®b¨_ß§)­›x–ñ]ü7¾íz«ÙqU IæŸ\¿¡àw·š^«èJc"Ÿ. M×±•\ ¯æž9Û*¦Ž–\¸XË“xí Šy-K /üK¬-N5ÈâÇD>¸˜õ`"³xÒІ‰|4À¢'j”È÷ éõ_tÛ·÷4ƒF‘|¿ºq׃*h¡^Î*ýÍí;J_rY&´¦ ˜ÈG^è;7ߨ¨,ú#3“"Öy‚θõ£ÎwÔêU¦jåÞiL¢¢¶4¾®NN)Љ|”°€yÐ"Œ±n¹ù¨Áe!ùèŒ,òÁfl2Nä«-¸3\èÒÖè>tŸ“Ë ÷Ïž:}Æéh÷«ªÀû×®)]Âç¿E¯EšKc"_sLÚÊm³ìÅ eerêNŽ"™C)àñ”óΜ©&¼ºà4ÌäŽ4@Üò¬þOï¨ÀGÕÆD>êæÇD>j\p¨s"À‘ ,µ% îÆÿc ¥N3wÿ–Z%Ëû5=Ý©Mô=þ‚Œë°®Ý—Íø 2…;ñAä#CÑyêbŸ-—9ã~ÐgÏž«-TñNõœ»o¹ÞÆîy’6I9âɘ‡‰|f€„EìLä£×LùZN¼2SKލd"‰‹†2æ•.^¬Íª69v¡W;‘Þxïžú\IiQÇ)[Ö3Q$Lä3‚bð¬ý«oTÈÒ?º~]aDÌá¢~ÏÈÐüù0§¬íø5¯Ñ)¼k»ˆD¥ŒÚ:&òQ#‰‰|Ô¸8J(&òÑk)&ˆ|•¹ÉÃy.mt ò+EØø/ohÜ:~9þØq™³YT½^V_L®õœùŒ‹w0#ý&òyšÐ4¿û€% WÞ¼U½óþ}µQ‡‰:UPžƒSE¢NŸE X:ï0âó+•Ò÷á©Õúé0‘Oý˜ÈÇ ˜ôTa"=¼hH$ò)«ŠG¸D›Ýé£<;=yhcVæÊ+—.9 ›¿ pô˜ xuy/8þ½ ØÅD>£ðàßÿù|i¯™³æ=[³ÿáCÒöˆ{±‡^‡aχŽýü6ÝÒ¢©4´ƒ]tgO3?&òQ£‰‰|Ô¸8J(&òÑk)k‰|™Ç>î¨Ñ¨ÜG~~•NÎÈ’hÛñß½°å~V$¼12"¦“?Ó²ÈϰÃGd|÷m½çý½‹Iý˜Ègš!#>º'¹`Ò±càÏ,£;ÛÌÐÖ2"È^óC‡€W×Éw{|Ó9KK!ò MTTæ6[×ÇD>jD1‘êœXKä+»w0/òLÔ7nZˆÜfèò™ŸßºSöÙÍ›{rZ¢pݶ %™'¤úqfú1‘ÏL °XË#€‰|ôÚÀZ"Ÿ¼2¸È·+­©}r ÛFÌ*ö‰ûp²˗¹³Or¸Ž¿Žðãk¼Ý|öuYtùäº1u‰|4D'ñy„ÏžüôÙóeŸ^¿¡tVÿæ´4õ褣ՂÀ˜§{<ù§É-0¦à@v°¹|é…Ü?F–ÅD>2M÷˜Èׄ…#Þa"½V³†ÈWpu»§FYÛ%0þã³ôrÕ•n>£$dì7ý÷åæç½uåŠÃœÆ‡¶‡è¾wõß…U‰í§mšðIªÕ4˜Ä·×IªÚŠòq¯˜ÈG &òQãâ(¡˜ÈG¯¥¬!ò)*$ˆ¼‚,žÚ×/):3$pÖÁÇ/ʈ?P‡zòšìÍ¡Ùâ¯oßVŽLJª®õë³g>³u1‘ÏB„Ñv>H²øT0pá¼³çsŽ$ÊZÚ8:"wÖ©k&;QZäöZ¢:–ÒÂ*7&C§ñ\þƒÌC/÷Ób"Ÿ Ý_LäÓÅûœK‰|å9É"U]u”ÿ —1‰š5èöܹä~ýÇI¬„FÉêJídÔ¹¤D8([~ýö]ïÁKÇtŸ¹Ñº3‰£%ºžÈg¨ÒÝÿùLМ£#Î׉×vÛ»¯fÞé3µl÷˜WS^¾˜,þë¯Ú¿KåÛÚOû-¶Ç“{Íw›9Ä'©&ÿz3¿b"Ÿ aû@ù赃¥D¾ì£ …'y^i_E/Gó¤»?±õ¸ÿˆUÃwVî Ù½§Z¹Ó Sä-áÐûzÉÅ‹uÃþ9\•Áø ø?'£Ùc¶Ê‚‰| -õl¯ìù̉u ŸÅüU¢Ø¾ÿ€lúÉ“²rsNߨÊ!V>2§º{wíæÜÒm/ ¿j?öhmÝ/fصÃÀ$eMIãº>&òQƒ†‰|Ô¸8J(&òÑk)K‰|5¥i#ø®í›Ú§*µO¯©e]Ÿ»ø¦ ÓÄK¯ÞºÜn箚onßV³eÅï¡Lž=w®½¯yXögÀè¯c{Îû{§-×ï©p`‹ÈÇ£ÊÜÙÂÐC¯/áv¶ö'¾35ñôÙYЂ]ÀÓaaœá~mC}}„g9èáDv’òó•?ܽ'¯Ñ€r•ØwkÀˆU[Ai œCÇ®¾Uts— =ñíÐá¤!"_òªÀ43òê5 èµq1¸n†,A "_uv(ƒ}ˆ|{aÎx5Qªv1oƒ˜ãËÁEr­-ùÚ£J#nª¶,®]ôB«Î‰7À°q_Ü„²3îíûo÷o%þß›))ý Ô,  ïh&zgïxð¬»w¯êbq Wàâû+|_ÿÀöûš\§"_ ³©õBË{:riMß?pŠ9mZ̶ÈH„×ÓÇ~†¹üœ‘ônð÷wMÚ|?+®¢¶:¬—§WM¤§§°‡‡»°³›’J~¸òùÀ ^Èôb%Ü«Y sá4Ðíòrp³¢B~¹¤¬öBq¡ØE Ê’ó=Ž‹‚ÇîémNÛ¶&Ƶó%ž‰GщÈWf<UÕ¡ §‰žÖ]¾£unà ‰Óã !ù,íôùµr qêZ]LDä;¿ÖÜ£u³Å(CÄ ‚¡ldýÔêÐP€löCñù¹¾o»=eˤ¿Nœš%÷X¿¶œ1â~~ @B¯é”pÉÙËOÌËSï{˜S}¡¸H º\­sþ5xÆú$[p¬hT¹^´È‡X™tÓÒ‘g«Ó/ Œýð*»éTêm&Ç¿÷%ëËŠ¼áÝ“ŸEÞ,É å?èÆUÕtQª”>ðËÕ*!\cð9íãs¹µ|¿DͦÖð\nò]CÓ‚¦®¾`Î1¸tÆY‘O·Äªì‹/A?4ù\áýQkt:[ZD䃾•õ•)Y‡^㢓 \çBùæ­rÓµ""ÓÌñA[ kò¯H|m:µo¬<èc^ë¡Ìz4HÛ{ÿßè¤k·ãe.F*Õ*q‹kM/ww"@"xðù|8@#Ð@­Ì*ä u¹R¡¸SQ)¿S^Nä×Öˆ]¢Ðí¤Ê¥Ã ÿ¯]hÉQ½±zÛ:Ž­N¿ÃÓô˜µõ²­+d‰~7¿ðºîÓ~A3->aIùõÓÆ}tþÆÆ˜ÎȨ†~œ?"ò9œ¥,#õ1¾Œ[àeÓ©4£…À‘#€ˆ|¥©ûÑT¨¥.{Å\p &æXªÀ‘Ò5ùз¦ˆiíëÊ²Ñ áž¢¦$Á§÷Ìgí¡ž ÛdÀ²lEåA–G‹¯m=T’¤ªªl£QUK@éÁѨ%JÀ-W‚ ‚Ç«æ‰Ú犻Jï¾àvl‰"ò)«òlþ>b«Ó÷”WåAÀì²Ó·eC¶„nôps…®§ò.ý'p÷G(æLÊ»ù×ZÅKA"_K4q«Ïù’S÷[CŒUÆäãý\@IDATv‡Ë;*`©Ùj‡jºD¾´ƒËº€P‡$||×+Z¿T4 ™.·Ê|¹=Õ ù.¯ 4õQfu‘[Å Þj”PÈ=0Q^‘-òÙ°ñ°E>‚k\5¶Èg+bë-òU>81œ'öL´BNJl‘&`X\¶ýž;¦ª«”õïZD{õÐ¥ôµ:"DÁš‘"dï÷ã’­R¢Šm‚‰Èg©þVGäƒ@å˜KóŽ#—$¸´í}Ø´<–` l‘)$[©dãšà‹o_Ý8 BÑJa0Xml‘Ï 48  c‘ïÖŽÙ4*E@‡QŸ';!­¾JlMï×ùZ=Ú, tñKR×U†›™-¶Èg&PX¬åÀù赋|Õù7GpEGñVTz[+-òY‹ NÜÃF%j”5=!tˆ|­9‰|­fǃ==Ø"½Ö CäSË+úŠ=Cñz>=ˆ­–fË"[#}«Á è#ûÆ}‚࢟kFjd‘¯·rXD‹&òi‘`ýùlùRÒ>îÍ“6Ë+¦Dù(aÁtàˆÜNÁ4ÓÌH‡‰|f€¤#‚‰|:p°éÁD>zh›´.ˆà ¥gíÁB½:¾4&ò9~ÚE ¸\I,ÈT»(Œùì¨1pQlŽ€ùD>0Ÿ#ôL±yp-†[Óû˜È×BMLˆ½ÓaÖhÛ^ý!FЉ|FÀÁQö…&òÑk3‰|¨ußÅï=íXš 0‘ ± rkS a8¯‰&àÐZä3!æјÈçØíˆ‰|ôÚÏL"_<ÔzŸ't­¤§K3&ò1"Ö,òm†P˜êô1‘îó‚‰|tcLùƒ’¬h2ôì„GëÚ¥é]rAñùœ±U[ N Gëž…Y†—‹‘"`"Ÿp(£0‘61‘Êfù¨q¼6#‹|ô´ci&ÀD>&PÄ:@ÃѺ!ˆÅ?CòLäÃOBkBÀ "_ÄZè‘~äͰքMk«+&òµžß«jlŠùZϳàð5ÅD>zMh‘òwà ­ëMO;–fLäcE¬ð¥žèëèAþ8x2Ôƒ‰|šNAð9Ø"MИÇD>z(šAäCëùÑÓŠ¥™Dù˜D³ë"­ûÂp^ƒ À‰|€1Œ‰|¡±u&ò1Š0šÎ÷„×xխ˨v¬Ì,0‘Ï,˜°)ˆ|Ú£uMñc"Ÿ)0õã1‘OÖü˜ÈGjD>4ÊGSûøýѺô´ci&ÀD>&PÄ:´D¾ˆ(Œuú­ -LäkUÍÝê+k‚ÈGšÚ'"1‘ϹLäsîöÕ¯Ý%€¶í!6¿¾ÃD>}D°ßnÀD>zMc„Èç5õ‚WRƒFLä£-cÒ˜ÈÇ”­[‰È§Mã!¦®¾ÃD>}DLû1‘Ï4F6‘ÀD>z°!ò¡=ÃKNO#–fLäcÑVªDäÓ"°ÞLÐzH¿˜ÈGì[Lä3 &[a"c¨j·êib"Ÿ –1‘eÀ5;="ª&倂Wä!9Lä#aÖ-&ò™“-„0‘ªˆ|¨%^û›´a"_ìÞa"»x;mn ù´D>TÏ:xÚ³ßj&òµÚ¦o•7@äÁ@&ºË›@ÁD¾&,œóùœ³]MÕŠŠÅ‰|¦PÃñvƒ&òÑk D>k¿Q&ò5BÁî &ò±‹·ÓæFAäCuEÓy ðBçgk&òi‘0ÿùÌÇŠQILä£'‘Yæ/DìÅÎÀD>;hg(‘U«^×à…Öó´ù´H˜û‹‰|æ"Ÿ&òY é¨!^Ùzš0‘O¶¼˜ÈÇÒNž‘O[cý)~LäÓ"cî/&ò™‹ãr˜ÈGR "ÕÔ>TЉ|ôeNù˜Ã²Uk¢ òiñÐïôµá­âùZE3ãJ6 @Aä3Ðéc"Ÿ³?4˜Èçì-l¸~·aT-¼ú4ˆ`"Ÿa¬pŒ!€‰|ôDÈצ†vöë—øôa"Ÿ>",ù1‘% =D>mµÉÖù0‘O‹Šù¿˜Èg>VŒJb"=8õˆ|úyè)ÃÒ6AùlkëSj€È§‚lù´¨˜û‹‰|æ"Ÿ&òY©©ýz˜Èg´–'ÆD>˱Ã)I!ò!©Sð †W;xa"–ÃD>Zp1)Œ‰|ôÐ$ùÚÔAðBûù(@a%ùXÙù31BäC•WÁ뼨lñ£x§u˜Èç´M‹+F‰È‡¦ö÷Á ýíS8Lä£Å©‚x,Õ¦ÃÓô˜µõ2KùálÌG±øçÃkÃû3 ½ÌWæ(’áKÁXÖ-ðBdF§u™ÇWtå_VTä‹xòn|.á 4*µZ)U«5.‡+\n•FéªUhÒÕ\·»b¯Žmz?™ê4¬Ú^AD¾ÒÔý%V”/{Å\p ¦g‹ÌlEQ­OÚ@äC퉦ö×шˆ|®0þž»ˆ*ÏI]ù5¸®ºÐS#¯–j”5R•Z!âòEU®´šˆ«ÅÞÝrýú¿-’¸ â¢];DäSVåÙü}ÄV§ï)¯Ê ‚ˆÛe§_‘—"Ì>¹¢Ÿ¢<'L£–w*EµJî /±F%ªU Áá)8\A‡Ç¯åpE%€ÃO…/Ë[i@šßÀÎÛë Ò‘ýü ¯ ðâwó¯?^³U¼+dÀÖY%³Æ!"Ÿ5éO[pu»gÞ¥ŸâÅDE\¬,Z,ñzvê¤î=&AÞ»·ÐÛÛ¸ººÖ_R©”••ÊÊJ “ÉÀÝ»wc®^»VsýÚuuúž§…E®nÙrðǵã©à1ß»x©/°… ‘/9u…ÉQ2elwP?s¥Vèp˜¤ D>ôÌ€í;”«.Íæåœü WuþÍA@Y£’WuT)k\¥î2´ à‹Ý¾¨‡'Šš2µ¢®\­¬+Ó”^ßÊ/¸°Vȹp’[@ =ê4âLPì²ö"ò]^˜gër±ÕéÛº´õg$½\~/qPÕÆËe%¡ž~½k|Cb…î¾]…®Þ€Ô½à ¤€/p|‘PÔU ”rxÕUºÖTøTÝéRQx{TiÞµÚ´]óÅ„îYOtBÚ.jwØø/oÐ. "_òªÀ4#ê+aÜix=; È߸\7"‹£È "_uv(9¨%2ø÷½/PäDD-µP»˜·AÌñåࢅé1²Ày^2#…GD>´îß⮦ª„“qà…ÁòÒŒ™Šê¢!RŠÀ®ã„þ¡q×6ðíÑŽ›©‚ªµ ²øžYþuÿœÔ¿ä_ÿ(JÙ\Ë·Ùå·#døÆÞ¦Ô3ß@äC³W6팕ظ"Ÿð=CF¾—o\̶±%™'¤Y‰ïLU×–ÏÒ¨!s9~ðòxtʤeN¥”ƒâì   ã„2-y£\©ª+ç =·D¿¸Õ·÷4K_J–F/UΕßܳ¿†¦êÊô¢ÈÞ¡gÔãÑ`×ö¥ ™á¬÷ïmœåÛÁUX?+F¯‘üÞ 7„´Ô¸,ûœ8ûÈ34²œE}ÂÃ9Ï=÷¬ëÔ©SÁ3íà,ظq“òÇŸ~”+”D*áÑkUç©/0¹ú’¿ë;W)+\ åš›FO.(ª3îcpB/Ü)½›ÞZ° VÍìýd¸’"zi6­/Eú,‹)¼ñ—ûÃ?_Ûw¶‹gG"´ßÓÒŽ½‡³õž–)¤HUQ” Ò“–§_þEED>ß#ð«°Ç~?Ð’ËÉkû? §÷7ÁâfR™± ¶:}¹$''P +9 EhÊ3çì×ó”Õùsý»Œ¡s%mCâÐW" -æ‹=8 Ò¯üZ{ÿê68›Ôv—OŸYßD-²ù´ U ¯m™<\–›|Æ¡/|C.F¤ÀNÿåÖÒéxÂâ£Gîž! ÜÎS`u´ˆF@é;ž˜­ªÊúo||<çƒÞ—DDDj[FÃ5 Ø´i“æ­·Þ®©ª©{À÷‹y½%f¶p§O¯Y3s<ø%p¦Bz¡‘Ôý|"æø„Œøˆõ5}ôžÎ=÷å"yUÁôÀîÖŠ\ÛØv"M£VƒüŒãàê‘wª+K3ªy®í¿êòÄÎ-Ñù³ÕéÛ¦×kþDÕùšÛ6­]ù)þÙIoõ4ô¢3’ÁÓ~–ø… ·Y‡jä8DMüF4~É5Q‡®cÏ9ýÕ?)? ~Ìl[c‹µ?€)´±8½Ã%l ò‰­à©»Ÿ‰Ìü-îïž¡^/_¾|Éeß¾½¬uø+8*óçÏ'²²HÖ~ûUÅÃ#¿ßÞ4øãÂk;=ØÄ[䣇ö”ÏëɺèãßX‡”²n‘}Ħl=ãAÒ›GÛw=cÜK—EÑS¾·y‡*‹~p¶Œ|î¤t茭¾R‘Ûë77 :xoïâ^(žM‡-òY‰ö­sÞÞ2æ°»›ÿ¢±/^” ‡ÈÅ+ÄJ­ô’‹]Û‚¾cV&üï¦È§}ÿé÷vÎ;~ã׉cèi±NÚ "Ÿ6ƒÝw‚!Z³ÿ2Hä3°õ‰YeåùÜÛ›†¾Î)¾øÓ÷k¿ :sæ´¤K—.ÌfBC¾,gÍšEÜ¿Ÿ)š6iÔ„ü“o¾µs^ V‰b‹|ôà+©ƒaŠÃôRÙ^q«nmŠÝ'æó—øÏQIä¸Ï„wd6„}ç4Œxú—ˆÑ+ƒ+2“~½²~à Dòf«$Ø"Ÿ…H£—cÊC—Õæ_[=é»vqóöI$î-ËKJ¼À Ç7ŠcžÜá¡®)ýøÊº_ í&V‘V2ùȺnf€Qä|o–,òÝ?±ªýýí£wEô ž‘žž&™9s¦‰‚±íáá~øa½ðàÁîDñÅoÿ2âY4r³u °E>zË•\ÿ1#kù®o7©øêïõùQH³Ç%m»›Q<Û‹‡O'Æÿ÷ºÈ/hÈè»ÛgÌH|‡•Ñ"¶ÈgAÛæœ_ãwg˨]nfŒ[|YÒ¾Û ´Ø.‰OÇA`ì ¥m¿·sÎßi‡_ïl»Üi6a‘œ}ºn_ú)t :ëý¼Xp֭ƪú±`‘ïÞ¾ÿö¨¸¾á¯×_}¥SRb¢ØÍÍ$YÙª*YšxèСàöíÛ¢îÁmenõceÁ ¥ºÌI‡-ò™ƒÒ#™v€ž®"P }gL§²½E>40»òý€ÏÕu¥ïÁѽ8$b6-Ù“ˆÜÁ€)ëÅ}F~P|í]lÌÐb‹|4Ÿ€ô#o†åœùfw—/vŠ»O‚Í—'ÿQÔwÌJ¿Ò»þHÝ9¿¿-ËiÂ"9kX®ü}Ä’õÞ,òÝÜþÔ Ùýƒ¿ýõç.·eË–ñìíŨÿløùùÿ=%?´_öžÙÛ§F_ûÙGàü]çëÁœå¦s·­E>4]~ç·±½üzûÂy»ÝÂ%´ïNÂ3G%òÒû+®m9Çœ#…Û|® ›ùîîy¾OÉõÛ£&­ñè>ô»9"L‚Ãgqbçì–”ß?±©%H#iJT'ðgNIëèôíÈ—vpY—ÊÌcÿ>xPœ@Ñ\ö„>Lþøc›HY'ë~ã§AWÑ6Y[”ùÌG5¿ÄÉ ¦ð3#•͈|hÙ'mÇÜ~A1}†ÎÜ&†tÌ(NË‹@Û- áÙc"Yñ­·®oýŒ­J„‰|f"ûðÔꀒ;ûÿ4m“4°Çdûš#2QïÑ ~þA~ñí?wÞøuÊHâEÓ òÉß NÉê@·³©ÀÕ¢Ì(‘=ùÐGlÑõ_÷;vŒ6l˜¡ú¨¨¨ã¯®®& Ë_dÉ[Lõc"ŸyZ®S©;ld/„UÉ©ëë£Oùv2ðñŸD¶Ú.M•7a.žA`â+w¢2wÉÕŸâžgB§¾LäÓG„¦ôs/®Ûß{Äû  óh ûjÓ¾?Hxæ¨-º½êÖŽyÑL—˜‘¯×ò?@W7 8·þ0 ìi"þœŽ_6*”Àn?®v¾á¯€”'>³™Æ²™>ùÒß­É:ºqÏž= &&¦Y–Žðã? F ÍÞ;ï{¦É}lùú¼’Þkìù™ßr g~öW`ªþóqð2ˆoãZo£Ã\ÛóŒùPÛßÛ>m½w‡~nƒ¦oáë—ÑQüh7Öè…gиÛÂ뿌}Œérc"Ÿ Ds/|ß¶äú®ß¢'¯“tò_ÒöíÕ®/ˆ™½K\}ö{4­ËdiiùêÖíèŽC?eoãï ®y»>«ìñ%øÛIàÿÙn°½‹?ù¸7¼cD>dÇ¡æî_?­_·NO??¸\×£=8J:ZW_DÏÏ<‘/õ×ñïÂåÌî±sö ì—¢F3¯ØÍMõ‹å¥YïÞÚ1w@3+0‘ÏxèË1ïü÷k» Y"íÐc’ÝŽ:T¡Y2èÓoÂW’²{{(}ð¯å6õ4Ó òÕ§œ5œ(“AE€Š„¥y:}\Áà)+Á»zYµ¨½Ö¿¼4¸z¹˜>-Љ|y‡¯˜Hšâã!úÓO?ŠØÆÏÖùùûû¸\!ªË?»ÙUg"?6‰|ÈZ÷O hÀ¤ÇW˜(¿µ:’®/ö‚ŸŸKî  >ø— Æ]NÃÙ9eÃѺ¬ùÐjx¬óê!33i7ßZì˜Hï6„õZòðÔ'ßÖÕ™±)ÂŒL1‘ÏH©;Ÿé×Tf#ö§£OQU±ÿ¤ï„dèõÍã™ß¥Cäk8ZÀiüc×€Xªò¡0)ì–6À—`L›þxÚá7³xÙ°qB$hûÜH`öG =ùŠopUWf¾µuëïR@Àl¬ç‰3fLŸ_µ”‰ÌÙ&ò¹BÚ_’Ê0{Ö,ðEŒ€t9HáQ›¦ Þsc)gäêSg€¸°¶ yŽÖeõƒ2?yÊÃ^zw`ÍP£Ô˜ Ox_ –úv¿»uâ&´c"ŠhZ¿*çܧ‘ã>—Ĭšú¦(m‚8\>ˆšü½´¶4í&¶;Ñ!òÁ£u{£ZÅ÷Çàô~¬±ºÃÝ6›_’¢*ðÌì/Á4c²¶ŠË-ü—~ë†v–Nì÷– ù Ï~ôú“OÎDFFÚ *»ÐûÙªU"¼lâ½ýÿ³Úô[D>2p6ÁŽ?· ¼8ï[0‘ÇÖ}z>¾¼ ü4²7X<ÖðGnjÁ”(ø!|’fÙ!òÝømj<—Ëï2è%ªeBšE²Oq4茚¼¾«3ÞFîÖ–ù(¼÷Ç´¹î>]¼{6#©RH;nú2îÐ}¢0ëŸ7¬Ñ%ò!Ôàf:|žU«÷‚NÆP„d9Ôñ‹ƒWþ³Œ6&Ët\uàÌý|Ý;t~ï ú[‘æÙ‰E¾z«ŒòŠq_~ù…iŒìMŸ§§'X³fP‘{ìSkËÆ‘O¿œþ^pÄ¿ˆïå‚7ýâõãméG<›çÖ‚5Q¡ ø©Àèóòå~0ÎÈ]íªP™Ø$ò¡íyµ…·>0å Ä8³ó ˆAá3yÙ'>Zfm=1‘OAô%UW‘µ$zÊ:ÆHnzYØ•ZìÁÛc™ÇWXe—.‘O ‚·+8vò–ñÑ>’mOæÛ_‚·²Àò%ÀPmz[ÿµÕ}AŸ•³­¹ÒuöBäS=<öÒÒ¥ÿÇ—JÃP ]œõåṄT$tDRŸ¶.A¾ü¸ˆ¯¤ƒ—þ ßf«Í‡ü‹vËÀ-yŸwnº4 ˆàG¹Qw'ÄuhÓtœ6›D¾´½ÏNkÓ¾ŸÈ7ñãü®WüÛ"EuÁD´£ÌjkÁëÒ¢jYMäËúwÅlÿΣ ¯`‹ àh‰ø"7Ð)êynÙ¿^d©ìõD>m^p},¯Ôt§äà =hÝs@|á.XõƯÀæsÔ>ï@†þo ßÂÉC{ ò¡ººêÒØÅ‹[X mk9Î/:ïÃ?kʯÿŸ5¥f“ÈGUήð ¸5ÏñÉ›à«÷·×fC%ÆXؤ•à¸}0ê‹y@ÂãW‹fÁJ«@Ì´Öó‘4[D>dW_Q‘½v„­bp†°Eª…ö{š(¸ôó"ä·Ôa" 9d¯YY™ûLϘ×íõªrö àó¹@|àXgj)Œ*½¹aÐFÀ«.0|Í€D`ÆçáG;@”ˬþmlùîý9w’—_8ŽôµY·ŠßnC_(jЦ^Ûi1Ù ùHJÆß¯Nô  äÖä„’6 $r‘ióS–ÖÛ"ÊËC Tð:µí_jC=Tåë‡`ÅS@ò×yðãwƧdf¬sá(f\S•¸ØÃçŸD>tM]UѸ¹sç:÷¢'Ń‚FûO/˜Ï—œ·˜œÓD>Šª€Áð•ôÁt Ù~l°Å •Ó>Ï©Õ` \NHDT%hÙýñpV žµß<ÖdˆUD>UMÑ\8Xq1™‹“ ˆ¤Þ  ËXMιï&YZ5Lä#!§©)ž1§Õ=H‚à>³ªš²Ç-Ý j ‘O }¨8ö È¼)~mš˜î¼5H~=6mæAµáÖþ>õ5˜RP¢ž < óZ˜È—ypɰnÝ»k‚‚‚¬…Æ!ÓC»ü|¥¬hŠ¥æy[ŠÈGvB8 ^\`§¿Ì¡’±$ n œ eÍC¹Ð<¶Ù®°ÄÅöÐíôÙ òÝ?±ª½Z^ìßi¤Ùeu&AØG‰Õu%OZZ'Läk@.çÜ·þʺÊ0Gµ­oé Mçé߈¤m„÷÷½`Ñ|™¥D>”ÿ¢QàÜöÓmÿÑ–Çœßñ‘€³dp]û7øyÿ%©~Ö¹ç×pÏñk› a°­Å“gºehi"_þpæâ—Z߈HÛ ]ºt¡!¡ ãàFM™jõ³ý;%pŽî«÷€-Èp޵ù/øîÿÙ»ø(Š·ý^o餑@BOèE@„„*Ò¢RìØ°ãßÏ‚]QEEšR¥JMˆôÞ!„„’Jz¹Þ¾™ÀÁå²Wöno¯dæ÷›ìíÔ÷}v3³3óÌ;0,¯ ^Á[‘q‡Ý¼Í€æÛ^Yæ™Ø òUfn“Ðq2Çßû渚ÿŽJLdÀ) ŸŸaîm¿½žÈwëìÚañ'ê‹„_˜æÝŸ”(KÎ3bÂÆ X‡È‡Óunr´íçì÷[vÃ<¡7ð¦õƒ/×Ãïû/f«®„hOòýg®Ã'È"š$.¢nœ+wž$òáYeuY·º¢‚Ïç5j„ÌX•ý€3ŠxšÈG%3²ÉGï}2œ³ СJãHØK‹ år¼¿m‡E[鸴óЙÉÞk™‡ "ŸA]3¬iòh›[ -åò§{¼o¿iÒhnUξ~ÎèÅ‘ÏZˆ3â×˪©)L@¡'ëÅØ àèUý£ûÑiÚ)’2zØ£2x¼…ýo µÒ¹×µ°rƒÎ3c}‘RóÀÈ„¾¼‹sÚ瑯¨ÑI»”©Hž4s™ùýô@TÊ!òÝ•°øÇgàñ¤& t$Ÿ)Íû«¡ÓÁKðÍ/Ϥc ·KgÈg×ákî¾Zá=ëMš4q8?&D=¼úµ?Òm]ý0‘ïxæZï“EºÔ$ô>êïœ-Ê«½} Ì©T@ã7…ÏÃÔÄ(PS¥³6k9t?–_¢m°âfN,V@¿~í`®eùî&òaCbZueÓFMšÙû>üøºÄîô¡Noy…ŽTÃÂãfe°QZt‹þ¢ÜË[ñ×üo6’QFa"ßÉ9q…”‘ Öià,—‘¢ðö¼¤}d‚Sý#2X"’p¡y¼÷?) b`mÙ²|ªûÐè ×©ÂŠN®¤=Uî,‘Ï$LjnV†¶ÿ˜îé^ß@–òhq/ý¿` zŽæÿêoh½ã,øv:H:Ä;š‹åtNùjnì9lØ0;¯XÖÅÕÝÿý W5qÆò¤·ù¨`C܈º!:ØŽ•ƒ«¨²ÁÇë Ý¾sðýü'AÒÖ‰ïAô±¢ÑAì»ãàeŽ:Eä+<øc÷ÐÆT<¾ [j‘OÀqh­‘Ïã@p#> .ƒïF±5¶ˆLx´ÊÒv¸ïrDó4„ȇÐÈKŸÝNš Æû ½Ïñ`"K/> öÕÅçWö¢‹ƒ+D>\×c}!_ÀƒÒÿ¼m¢—ný8ý‡@„:îVÓ½ ¹ýq~Ù ñ‚EŸMIÏVÎÔh?Ï4ù¤mçîÝ»¹}öÊ> žM!Õ¥gVµ +‰7ù,eG³¼€Þ]1šJÆu9†-¡5ßr~3¤]\F€ˆäš.A½¡­»‰|šêÝbZ ¦A7´DÍÁ{-Ô?NÐNÖ¨NbÆÒ ,$ n¢Êÿï ÚG¤"zʲœ!ÑíØûL3{yŠ.©áí*XüúÝ*Ø}NWožNÅ'>ÈÍjqüghãŽ2¢Â¦Y\ªÒ\!ò™Ê‹ †ôcWí£oÀ–ó⢠Ӥoá S¹TW´E0zÙ^XöÎõo¨ùtó$‘cдÄD6âÚµkÇU—ç$úÈ–àY*lPÖ±¥ßïi»*–ÎRtŽ„Óô;¶Ž©¿ž t7‘c4&„5§=ºuZYSF½¶ãvµÑµmuº.—M±w®èÜÚmM‰E]á-¹ªò¬fõ"¼$ÀÝÓû˜|Öy§,òé5U-ƒ#“=²#»8K [ka#òþ§……)áÿ¶ëê>6Ƕ캩]ºCG £SmõhCœÛ\="Ÿ©&´ÿ>íV%¤šî¹bËy?L)¶¤‡-êQ•±ý„ý°þxqŽìN}\(U>gÂÚLyͯؒÚ~'Uja„¹0 Ça;o/‡Åú@cd .Èæ™½í·D>Uñ©H(bïÅñ6Ì,äÁ qÀ@Û±7ù,T„ ôÎãýöhú}*2¸3 Ç£m¨R´;e)êì#¦÷wýG\AˆÎXÖíÄ=m"Ÿºübí`€ÇwiLà„¨(‹ Cî´Ñµmu!¼ûœ ڙϣ%€3¹ÎïL.|n €v[Í‘“½'"¿ ùä?@¾)òwš€‚§gc^¦–ÖiÔ*zÔäO>¿¤¯˜' [×íÅÓ”S½XB£^'å Yb`à Í\bè?ÙÆ,Àòg¾ fíb¯íæ e`Ыh“¿Ìˆ|–*XÜ‹kÔÖGò耑´ƒ—a€E§n±E=ÜNúž{lÈÑÁ>Ãh qȪYí;âT¡43MCD¾Ùkém!¬WÅìn<åŒÅ\I@˜ÃÃeÅuÔÏÉØ{qê í]µçõ´ÿÉ͈|åNj$PiÀýij;Â5BŸ5Ëð;?fNý7‹aâ ö‹ŒX¹ü‘‹fàÑâ·ŽµÞéc"ß‘o+c^Tç\ó.ÓnÕ­L)/ÆkŸ¸?a×!òĈá¶Å]?OéÒvº á>Ëh0жÓp‡ÈWŠê»F·N‹ô˜[?¤±ûŽÇ„? ¿?¤*äqEl9Ü@r€Ã•sÒ[•ôZ‰@D»=°U$sq1bx÷=|òŸÃí½Kuó…Rô"éùØl)Næ‘?ü½®ðêp8„N“)ií:¶^#¶¬÷ç« ™0f%ǃö£Gìj½´º1hm“{é:<ûÀ»0Ř–‡L×ÚÄèCw¼XnŠ£ºR…á2Pøñ°³¿tç—#ÇÈQ¥­f0ˆ7Žºšü…@Ë´H_„.Í“ýùH&΃÷:'€m­cdh|ø Ú¡f8•/õü/¡€NË´Ù?Cs{ùú– |],ó:sÿáZPŸº™K_‚'‘-ŠäWÃlmÒUãSˆoˆÄ§×êAüæ(@ŸÏõݺ…B³¿V?¶NH‚0(6°ùÐonÈÖ‰¢¾)<±¤8žy§•:øl¡ªÞTuÊp)ÌèzO$Q3! ja' VTµª*4¼å£!#µ ŠîˆØ×qϤÿ…,ò9°S'›µÜùcÖ ÿ òøƒ¯ÖQ6ð¦H®©¨ <¢šÌ—„Ÿ ÓáãºÑ“R«®Á?Ywþ½/Ç»•×è`û•ºÓùbEº»˜û¡Ó(ð<‰ŽN‡kgŠÈ7wÜÇç5..»ÿí¶SP0çqx ³ßçú&ÃË/þÊ‹¹.Ï^NùDI5 …œmöpp¡&Üé—‹þÐs¾DäÚ¡Y- bëßD{÷ŸmšO&ÁÉž­áõç~åšyDÔ•T£v×m"Ÿ82I®×)ðÔ4ûukTCÂô@9fŽ¡,-"é4µï4íÁCD>¬sòO#ßù'‘¿Ûá£ßÔ£:ÁKGå\GþFpBʺerx|éÓÍÆHz­Îb”§Ô–u¿“Øj½u9úãYZÎŒÈg/ŸM"êŒûеSDöʱ¿"t«öAÙ;caÚÿ÷ᢀ#]šÃÛOÿ ÊW&·lÖ~/rš‡,òÉ÷”£5lP©h?Ê{Âûѯ’’Ðê8EtU2#òÑÍjJÏ‘oÑÐn8ÅŸN†éhTwˆ>ö·‡÷žXJ4“æ’ –‚¾Fi½Ów§E>qX² ÍB¢]jà§¢*+(“Æó˜=T¢kA¸­æ¹wÛ6ké,òȇù[}‘ÿùz³ ¸Îº=q«ˆKýäÝ¢9\Á-EUÝlŒ¤oÜFÃzà‘0](‚Og@_üídæ ÑzNQ• ˆ,Cõ~Û¬ž ‹|¸´Ï·¯T@ùÿe³~óÈ¿‚áÇíP=s<>¤”™ÇáßóŸ‚´–1ðÉ´ù Ì¯k™Úµ{OYäÃ<–€ÀàâK—.¹¦€Ÿä>}ú´Jm8³žï e$ýâÝPùÖ˜Ò»M-iºŽÜ¿< ;#aÎÔAYDû¿û^QÈ€Þ`Ùò}s/ôÞ/wZäKŒQPYM)^=`Ù¡³PFíóè;íô#áù X0YX‡…Ø¢pÞÙ}4Uª,É4¹\‡—Gho/¹Ý ¶:}§,ò8Ü U%™ÔCn{ª»ߨ…õLbxx€&¢ëˆÞBhb9•o€ƒ,àT]r¸I¶‹jÙÊnÕ">£âôÈTºÃ| ËŠvŸãç@Žö$Oß ¬Žì–¼›ÑÉ~ßáF}h¸ÍyÒ"_$;|ù²Ûtó¥‚Ïœ9«5¦ý^û‚E¾­'Á€¦õkž SÐÁUVÇòË^†õè8ÜŸ§ýŠr»M6õÓå –uüç‘ýþ>T)Üm‘'”]«BmûŽPû<ñN;ýðômï·-¢2GÇ{¿²èœ’/ ¼J ±ÈgÒÛt´®éÞ¡+Ot¥òÖy§;‡*q:‘nTÀF–¾ÑÇúç] +.ù2.B´Ï¯9åÐþ~ø¿U ÛžDÇíÚñYù*¬BÓ•Kq#X妧ï)‹|@…šsôÙa:õÄØËt%ë _Ý-‡nÞn‘­ßÃÇkAñh_˜6%ìNW®y–J°æÉ @ÓôN9´f}.·¬vj·^~wZä»]ï|ui–Ghõ”µPiàãì0÷qÕUÅ—õhíY†,òYh_ÿ–­‘~ýš‘„5˪(D'ݰà4.±ª•¸zN sæÖÀw§Ù{ÇË NËùÒÚŸÒLù * oÛXç:ýÓ×^] Š;Á kÌbªÇ»î øÍ.üý4j”+ST¹Xs‚ȇ%äÇÜøÏ&'›v–utcu™™™ Qk4ñ}^·Û)ZŠáÍD¾ch–{Ö Ñž~ñ!p¸ñ_? æ¡£q·ÏøÈpmפ\D ]²‹ê té”E›È‡ çÉÂNå¤Ñ&¯ÑŒNZÞ¥E:ؽ™AüY lñƒuZ%T—^•Dv{ŠöTƒD>›P±ÅCsŠÈ›2ûÜùE÷‹ÔŠ2p÷ñº;ÿ’Ãοlbå±HdÝ n]Û'jš2Ûê4k™ùì­Rù°M|d§õk#à賿ÀãÖê¡ Gæu5`ÊÞmá5t¼®ÃÛÚLeýó6|1ôS~q¤þŒlöãL˜rÓgçÚÜBä;à¥Å D'D¾øÎÃ@{fnó;ÈÚ÷'8s)ôžÿDí9?M±TxŠÿ»-pßwOÔýHw§E>,`DûGÞÜóž·Uø(p·¹-<û!{£vºz”Ü8BihvpLW‡‘÷j`Ð"ß½B)~¹ñéÔ©Í)"Ÿ48J/”…Ÿ½uÍé™å:BøêMyá@f.Ë¢:O.¥«ƒ«D¾ßvCï@ ÅÛŒèÔ}½íA¬ä ð®­ýòöÊü{¼“W Ç^_JÄÅaÌyŠÈ‡À B`phöÁƒNõwŒaàé‚¶mÛ^£á§yZ¦ê¿ZðÌ/ D»RÞúìQ8îl¹«_…Y9…pfÖ Pbƒ>t\ãPȸ”W]ßD>,_Tlje œ‘'ÞS˜½—öW“7ƒNW64Ê×sîþò¡$òåAÔáÓª™¸…é?²Y|ñó³µVïèª|7=:‘ϸâxm¼øî*dÁ¡Où°rJcІE‹7Ø)~¼?ÿßÿFu{nÏ݇MㇷùrÑçøèGgT|ôý“°†*õ’†È@È}/¢¥±,lЧ^©ÉQZS¿Ów7‘‹ÄìÉÏ´<܆°~•{áo•,:oU§í‘ïd‘íÇo¹qf G¯£5Ф ¸7g¸zt‘Rž¼Á]!ò¡ƒ<8årè=ú>Ç;ý2ĸÇ¼È øé÷—`£32[æ ݯ3à¹CW í`äEð$‘ëÕùé¿×¯_ÏÃSü Ñ­[·dAaG#’G×µ¡â ÞDä+F¶Ï¦ýЦàtŒîVU°™ ص?<Ocƒ>È0–Ã)C0[ÝãŠÝOäCæoc{¬Í:¶DcdêËÜ&BÞY–TÊRUË‘¿œtF:B仃ZL x€¬‚+ÿ:ƒ£Ïç)/8*y©:~øÇœQÆ"ßg롽€Ŷ¶Ø™Ë„Nú£…L+W¿¿›Ç¹ú;1 ÔßL‡'ÿ=ù¨AóŽE='‰| ¼T# =ó÷ß» Oæÿå—_«5‚ؕΠï-D¾JD]›Ž>rä°xù+Ìžc‚lê+±AŸ¿À­…»ç@FÚùú£}±vŠÈ‡ËƦ{Ñqèåx]»!ºœ“ËÕI£5ÎêΑ­é}§ˆ|&ð8’F+¯žXæ5ÌP“\l\sN-×ð$!ëìNdM3"Ÿµ$¦ðzD¾cW¡od°cS•˜mŒ¬é)вû¦¿Þª=ÂÑT.c×ÎÍ@ŽN&›†lõ—þ‘ŽÎØpÁMóE>s‘5¢„…|0»Á½×LJsçÎé†~¿×:¿Íˆ|t²™§u™È§@‹ŽÈšžʳîÏ7`¡yáLýÆ}f= S–í…Ê5ûkO†´[4ZbÈ((¯Ûé»Ó"Ÿ¹@qèŠ+G~U˜‡5„ߘµíôjCP‹¡NÍÈbŒ²Ègn¶:}§ˆ|&é› ™óOñµ cå­K¦ qU+J!ûøRcT—©8«°+D>4mÙu´v×óñyÞ/,ªÝSŸ¾émøÌYYÉ7¨#”£c~_°*7š4§{5x’Èg’"iÒš½E·Ê·mÛf j×wß}WaÇΗ…6qéÃÍ“`©Ñ\²›¯@Ç8ïØø6Ìu§,ú@ésCàñy[ fË ûïüÌapmsM>™sÏ­»‰|&ýãº2ïòVNM™Ã;MY}úšuø=Ox !õíÞ®[¾ÓD> >•ˆØxá¹ôÏÔèåóµ|YøÖÆÝžaÁ=Ô!òm<hÏp : ÇæV;Ì.~ýZÛá'Ö¾ï°ñÂ?Ün=9¦Îùjvž¾{,.­ª=Mä3 kÆýÿ÷®“vØL¥øÎõÂ… žñŸ>~è\§§A±¶ž$òéÐtÖ+K@‰>Šn| f³þc}!ÿ±˜úñ:P`Ã?¶^ À‰_v@/S:6ˆ|¸®Ð¸Þ ¬ñ’si_6òµ^«‚ _kƒ|cÂÛ™+!òY Ö´÷¬å™Û5e91þy‹g¼räg}HëÑó]ÑÐY"ß_‡ bÿ‡Ùó¶êw5¨/Ü„K+_™h-ßé‘·­:¨âP§süýðÄ{«Ay€¶ d5KQ{´.ªlaZŽ€ëÒF¯Úv5ûZñŠ+lâLC&¯N:mÚtGÖä›;Ç‹:-«§ˆ|˜£öörP¡íygV½oÚûÿpZAŠŒ/ œ‘Èà6üs²åÐÑÒû² ïYçcƒÈg’'¶÷kKr/þ­GúLA~}Í<ü³Ž+KôÙW%D> ôµZ- júÝá Ï6ˆõ¢ÛÞR¡ãˆ×'¤Ìºi­[g‰|7J /:dŸ­Ê¾@Lzd¼çÚ¢43ìm¥uG2tehWxæµ¥ ¶Ò-z^Ä[ëLò°uŰ៙ÈÐ9ÉúB²ãÿ€r9Mä3Õ…wd‚ã¾>´þi¿o«å7à|ú纈ö“Þ5éïì•ù(kñÈÚe•Å—Knœû‹"Ö‚Jn›þQ7üÙW®jå ‘‘€Èæ}wt8Î~kõ£5u-:P¤!y³Œ­¥swøûãálŸ$˜ù<²ü‡Œ’8ì¦y‘Ï$l‹ߟ5ò¥;f½ý¶ßN‰VTTÀË/ÏTñ£{þè˜twöê "²t§Ý}òæM‡§é«rVOª|ØðO÷–ðæ³èG£yJ‡—x<¨D;p’p¶ˆ|&aZOذ²º,»0çô—Ÿµ©Lo¼ÛôŠBóKì¯!û£®9Bä£ÀOfˆéñöñ-¯+4J{Ve) ð ƒ^ G6>'‡6ÿ8,¡¯ËÌngˆ|_ý Ý%BȤ: C¸*tËÓ¡ü±0µg+pãYxŽ=°¯¦ÀáN‰ðÎSÈà5ÙÞ@ä3×.ªÿœ–/_Yö¯û]#‰÷m9Jaà¯pv³9Vžø½dhׄâ&´ñàñ,6³Û¶ ÌÆ°a *ŽÉ:rå6‹Ÿ-"ŸIüa”Ðÿõ[^U)*Ù²|oªköÉ? ¥yÇJG-ù•™©Å'ˆ|檶»ðW¼DúéÛ·mýõ|•¿Ù0µ?L™Ð¬L,2%9ýr–¾ÿ4ùdz!@«­¼…Èg.#6n‘6ùfð!rl¦ÖÜöíÛáó/¾µ}üËú’§í\ZZ©û³OŸ¾ ¹ÜåÕËâY½_¶lŒ;N!Mxh kžæÂ³Eä{4^Eg?ô™’ 4#æ’ºÿ÷š7`2Z5xHg8g^&×KáЊ Z„>—‰|æ2`£bñ}ûÂõ3k ϧ}áóƒ4l„gÏâà­é0mûzs]]ýMˆ|vlÜý¹¢°vO>¼á9%:äÁNjïŽÆÄ½=KYlÏçš{‰Iiéùöœ…¾aõGùX¦§ O’öÅïã·7*ó&"Ÿ¥^m¦e|~=¯xo¯^÷«}µãOOO‡iÓ¦´Ù°‰xÃRGWïÙ"òùÊ;Mec#>–¸Æ…ÃèÄËØ&ò™Ë—®bx{Ò¥ƒ?T\Ú?õ沸ò/Qüõi(•e{;>ñ¹+eQå%D>*T,Â~šœ˜úDÆÊG ÷"šwöA‡iؽh „µ=®í¸¥‡™V.‘/™ïlC½žÏ´lž,ÏÛˆ|–X´ž~èÕëù¥;»tíªªªB'ºøÃ‡¦ kõêð™ýˆõ!¼BT´ —Q©€û‡u®%Ü:pp§kö®ÿkÁ´ðø¬‰&)ï8½ó}Þ¿žô¹Ž_§‘CÚ²aJIHüæNÏz•i|Ø,Ïçˆ|–à`6pÓ¾³R¬›¦¹qnƒO1ŸKn‚½K‡ªbº½ØrÄg,ucùþ4Úæ“…Íw¢“ºŽ°\7ëÕy‘ €¶Oz­¨L½ªs×®r_!÷¡)}ÃØqÔˆ›ö›ÒrÔ¯'¨ôb"ŒM"òzª <úò!oÈçð8’!Ú9š¨+rÃHG;ITlje‰Cè^peGá™]ùÌi|x§Ø®ß(ª+òv´ztûë´w0!ò9N†Oâ ï0iô‘^¨¸1Wç ¬þìS+ iŒ–6éõdò£vÐP—VR:D¾MÇ 2ßy²U ø éÆXÞH䣒Oõߪä}Û±]UF@IDATc'ÕÚµk½ö£V­VäI“U/¾ôj~`›ñãZŽ\`Ó|3•®tÂØ"òÑ‘É[ÓâC³Jk ’Oìi±ážøŸºzbÉÕôßG)Õ ¯ÙD MÉÍðu~7¥Z«\Þ鹃o2ac‚²"Hˆ|Ö±ާúcz½4êòÁùWÒ– WhT^³³¦ŽÄx]èວT'·Ï*DÛ=&´»äh ßÐ!òÉ‚QØ|'Ã"øoqn òQ•J. m1³ãSûÛnjMB䳆Œð˜ûž/l3uϘªŠ›«·|ßY‘{q³ÔìGݺ¶¶þØM^tcÿîcÒ|Ðç™î–‚‘O"WCgl¾ÓÝ2yCùÞLä£Â“ábZ0xÕÚÍÿÆÇÇ«°I[ªtl†ám…Ó¦OWõ0°ªH){¯õôƒ¯Çtee–ˆ-"›xº«®ÿsbHQùœ5aÌÃëïî’ÅT.ÞÛŽÈpŸŠ"Û¼¼gé°ª£ÿ¼¬Ö(ËMѽ–œ†¾_q>cΕ˜ûg>„NÃLcC Bäse¼½L_ˆ£ÚÏ8ü÷óù{ѨßÓ¡ð¹ÖMWî[9¾œ+ }AØjiùÚr9PƒÍw: ½Oeóv"˜˜Ýö‰ƒoò÷ô…gætîÜ¥fëÖ­TI݆g>úè#]Ll¬rãæ½c‡þ’Òî±ÍÞõ…íV|«p|(P¨ Ò‘ÔíKî"Ÿµz“Y?àÓyWwmÚ4¯½êê‰?ŒØ*©'œ²ºŽo}K½{ñƒ5÷ã¶Óÿ’žÅuú<‘Ï8mÇý~°ÍcÛVVæ-ÀSH‡×?«bûŒgüØ6K³éÛdUqîÑ5-Æ.MM~ôŸmÖdöpx‹øØåaX«Þˆ|ÖÀÀ¶ú›=¶ÿ¡¬[ð“&?~³U«ÖòU«VBÁ˜yʪ1™¬‰‰QóÃÂ’–cn3ý¿÷ñÇe7"=pÑô>¶…;Ö\n#òY«;¢ýØŠNÏ|'¸ùà‰gvxòï¯[*3/0°eÅOQ•x¦aówíU¹—¶lHüuj‡i[ÿrçú=lùê™n¤†°ÂàæC:?“¶²hQ|ö¯¼ƒßMÓÉ §5ný 4ï"ê©®ŸYBYÔúˆN“öÔ㥠Ó;WfíÂ˧ìwôÍð㜩à} lvw&ºÙ v­¾Gy]è°xcÂZ?(ð´=ù «&¤ Ô9O#zqÇÑ£GëŸxbº¤wïÞ •â]×Ü7`çÎÆïøA~%ó d6I[Œý5¾Ïë5V£ÓêkŽÏK(pE;ãhŽÎÉ“¹R†¯äíú A§¾‡äÅÌ|µ ¹G5ëØvܲC6Ò¸5*kó+É5¹ÞЩ*»5McLì2Eß‹Ñ:ñÅÍ àê±%5eù'x¨^ѸçÌ…‘Æ{lAÃ1ÊOΉsûLk³Œ¢êE…á/Iäç•]Û·ðæî÷ÇÞÊyz²Ñ Iì<•Õÿ@ 8°½eyu5(kŠ ªø2ò—4åEgUè’DÁ79qº,æ¾î¼´lªcµ®üS+ƒoMêÚ:–p:ŠŸ<®¬\û&·Z˜E|¸¸³×¶à‚箂3'2iSž)ˆ« O‹r3¾îª«¼ÚC&‚d½F™X]UÅE³[‰T‡fô©ÔP]UÅS(ä|µFÃð*i@p®#ÈTêħã{»b˜’‰ÉrŽÿØeªNQ¼•éìŒCÂ}­`øáÏÆn•e»A0m Exá‘þLëÏÂÈéñf.ãÆy¬×çX n¯«®ïïZy­¢¼«Ñ ‘ÈB”ÁQÉI`´µ¿(€ÃGm¶VUu•µÕÚªÒLMUÉŽººH"ßâ e|YÔ¾ˆö“zz@f©ùñÝÕÕ.Eá×,㘼g«Ó‡®3óãøB½ˆIá™*ËÔ@j«n¶0êµmÁ mÞ˜ƒ^#1ê5"D,r¸|-—'Rsù|—'.ãr…™F÷‚ 0æjtŽzb]ÓýÏ.=@Qpü2J»×Fúu(î,êôo4”Nuõ¥œGœî0î@É{$4qŸüh[¯Š*¿±_ª.¾$SVÈ ª_ÖH. IK¢“ä‘ÉXlóuH§Oó9è@ΙX»”±å´Õ©w‹è<%Â[?öLZ£Y[YéÙµÍÕe9 zmu#ÔNËŒÀ ápŒhÊ–S Fn‡Ï—óÅa’èäìÈŽOÜ`ê4<“ L_ÙêôÙšÞozyÝøžÉ“WŸd(&Ê Šî¨NÿÇTö Í Âƒ NoÎ̇‘ºCä[ŽTpëTš·AT{˜Óí¼îÄ8:Xa"_yæ<ê¬Ëýr*¤¡Ìî!ö8+•›òÝ9Zw7*èi…¼µí˜Èˆâ½r†ÉUëð y¦„ùú"•. _ж1øÌHÏÕÇà+ù\ÕÓ_ó‹|ôž¬ÙѺ[PÎaôr“Ôl @,ò±r¨Ã‹|¸À Aûé?A‡ s*²d‘9ý§$b‘Ïég‰ ;àµ}kÎíù¬UÜÐÉE¾†þ0¤¿ùL¾¸F ^ɹ`Š:ÅLKl/AY'îÍìnjH¨;`k”RKäs§"¤l§Žr™Ì§žFD>·žŒæ”„nÊäËù܉OK,òÑ{\wˆ|ÑwrÙZ×gÝ"=Mü75[ùØêô ‘ÏCïª"ŸijK§!D>ÚIËp½ÂX mÉ}<!òÑ{€fD>œÑV§O¯`’š1‘1(vA6ˆ|2„L/äñv„ÈwÇÿ"ŸãX1œ’ù\oÇ«A¾E)„ÈG A„ÈÇÊ  D>¼7¯íá~ì‘ï6Žÿ%D>DZb8%!òÑÔŒÈgÊh…ÅOˆ|&€Ø¾"Ûˆûi}6ˆ|æSû~ª½uµ‘Ï:6$Æÿ0#ò™”³2ÅOˆ|&€üõÊÖš>!òyßdÙé"Ÿ÷=#"‘‘Ï 0V‚-ˆ|8U:òí‘Ã7fŽùÌÀ`ó'!ò±‰¶×e…Èש\¼¹©MBä£ÿ"}ÌÉAˆ|ô`´ òáÌxûÞä‡àâ<!òyþø…Vˆ|–£|¬+!òÑ}â„ÈG1ÆÒ"#PR­ë"#ÐÒ/„ùècFrP `…ÈGÕé"~6ƒ‘Ï&<îŒ$D>zèRùpx]ÿ!äÍ–y ‘ã Gˆ|ž@Ýë¤ òáó´“ßç‡ê:¬!ò9 IèPù°VyÈç"ßßÜv„ÈgBÂ_¯f_xnU‘ùÜ /­Âñ—=Þ›¯µÈEˆ|€[ïE€ùè= "Ÿ©K?!ò™aùJˆ|,î¯ÕQùÌMïš«Mˆ|æh8ö›ùÉñT„ÈGR "Ÿ©ªu}S¹²ˆ!ò±¶?WeAäã#]#ÿÑ-!òY"bïžùì!ä¶xBäc Zl +ùÆwJ$D¾;@°}!D>¶÷Óú,ˆ|½‘šx›^…º„ÈGŠÍ Bä³ ;# ‘ºVˆ|¸|vÄ¿ÈÅ7„Èwöÿ"û˜ûeD>*Ö¾_êmO)B䳇‰÷'¬ùL*š­ë"Ÿ ½"Ÿ¿>Yj½luú„ÈG õB‘ÞC±AäÃmG~òä ‘à Gˆ|ž@Ýë4#òÅ#õ"?fEMB䳌`B䳎;£‘º6ˆ|¸ ä/ ßßç‘Ï3¸û]­fD>ÌÚÇÓxF+J"Ÿ`¬"ŸUhÜAˆ|Œ#lbñ"ãÐ:V !ò9†Ie3"Ÿ­©}\ !òÙÁ²^4!òÕƒ„­B䣇´ "Ÿ© ;ëú„Èg„í+!ò±¸ŸÖw‡È‡-nõA~‡ŸªI[-Bä£ ÉàÃØ!òaÍN"À‘½ëÿZø°ªDt;"Ÿ€ü$º Òã(òU6ô!D>à(ïB€ùè=;D>\^öãýáêŠ\lª›8– D>–÷×êîùðQºøÚ–#D>[èPÇ"5.n%D>zÛ!ò™ Ãëú]M7äÊ.„ÈÇ.Þ~[Û"_O¤ ½NŸù辄ÈG1ÆÒ"cPš„—ÿZK£;Þ0$¿ÙA€ùØÁÙïk¹¼vz7¤$¶ºuÉŽ²„Èg zÑ„ÈW¶‘ÒùpÕÈ‘ß<ÜŠ^é$5"(’2@Yp|"‚á¢.„ÈWrçß8@ä3pZQ|a”é†\ýBäó¿gZG#½VÞªH}Cˆ|Ô¸P/D€ùè=ˆ|¦¯ê5Ux·q,#@ˆ|,îÕŸß ] Ò-Íý‘Ï,’"Ÿ lÝ"=¤$òáBsѶ=#Ù¶G_&R"(6ð2ŠNþÖ‡'íG0àízö!òÙCÈ2žù,aížùÜõ1ž88½êÚ¾T·Õ@ ¦D€ù(a!tPU\ïÇ“6:‚ò £v!òÙ…È"!òYÂÞ-!òÑÃÚA"*Ô˜'l»]Ss+•^ $µ«"Ÿ«6ðü*E G¯ªê\1fäâ}úÄ™!@ˆ|f`Ÿ~€ãD>NW^@L±Q«L.ÍÞà÷À4@ ‘ÏOúͯuæðù|q@¥ƒ*"Ÿƒ@‘džG€ùè=D¾&Ei0O$;Rxp6ÝMK"K@ûk5Õ…§RùÒFifGëÚS•ùì!T?žùêcÂJ!òу™‘¯¶`,&]Y~=…^-$µ+"Ÿ+葼 S”¦6îœfv´®=T‘ÏB–ñ„Èg‰k÷„Èç6¨kÖ o=,M¯ªHU«5n«ˆ\Bä«‹¹£@ÁÑŸ£Œz]TÓ_Ÿ6;Z×^ „Èg!ËxBä³D„µ{Bä£5"_TçÉ¥±¼–ÏáðËnîx£=½šHjg D>g‘#ùàÖ¹uýxâ }bi€ñÎѺ„Ègñ^"Ÿ äÖ¯ Cä3íÑG˃{«ò¦ú50 P9>K:7½¼n|ÏäÉ«ñ™ÍĹmu^ª8¬Å&šÕ`"¶Ñß \Ç7á!¤èräU Ba?SùÊ3·”¹ Vî—SkV±EfvAT׳Þ!òÉQI…vJk‚ŽÖ Di²$‘IéÕ7þ{ ýþÁNDWæ—œZÑL-/5jä2£N)Ó´bž@\ÃåÉä¡D. o[Ýý…\<òˆ4*ÅD>]M¡ÛÛ#¶:ýPMMaÒß+;ýªÂӢ܌/»i+ó[¸Õm jmÐjÃz½Ä¨Ñ‹ŒZ½Ãç"ÒOÍðU\¿ŒÃgrô²‹BYÌÕè^/ Kè‹ÿ¡<îjJrzµü¾è.ÏàVp‚È× Á*D xx.>0Läs±’Ý0‘ïxæ¥3yïäÑ¥&}æÊ\(Ãg²Ò%òaÅâ~yüìÂîÍŠN¯‹ê8Ñ•,—q’—çòó3>n//>w¿[™¢W)ã J] °I B&^ ˆÃ 䣾^ÈÑUVôÕ¥½Bk,½z@PtìG/@r‹+”\äqÂ÷' <˜úö —…b¸Lä;9'ÎÞG™Ëµ²Õé»,(Óäìù Yeö¿£ ܪþºJysir¸2°O¸HÜ2Z$n¢&À•€€| ôr­P_£äÚ@í-E„2«¢µ*³üAåùUÖÆ)n€ä&°/0ª÷ß-†Ï;Ï´¼Ž–wcÏ;=9ñÅð¤U8&òÿ*îªù1‘¯ý’—áœiIŒ&òÉs›0ØGà‘Ïf•;Y{lÊ{’>Ž9™ß_³a"_¬œ48JÏî/>µ4uúØVXYSÆÍÙúBoUÍÅIºêŠ„±Ú1MEA)ÉBqb›‡Ë ²'—A¥UNecåÅÒÆ;oô,ùo§øÂB•@¾>(nðºÄ;Ò>Ú«Æåø;D>üqåÖÑ>[þà„”.£âbe×öÉnî~¬ÖP4ôÚ˜ðIm¸A}; D °UsÕÕ IñÊ‹—ü¹jrÙÕ?‹ÄÒVßµxxÕVO.Ü!ò•"Ý®ÑÑnZ¶æ&+âR?ñØtÊ­3kCO-¾ÿÕ¬¦î—ô2¾–8¿ob§sSÅMßë) NiŠ;|º¸ÕIÏòj?b_ëÊïtâqiËß7$™q-í­ô“Kî›ä§è:Üx£SVô JHÙkª‚ùLHÔ½"_]<È#à ‘#ÕqJº^SÝ[QYäêR˜]€q;}rqÿËÙýjF@?Éô¤Ç¶Û5. jJ;ÜáÛÍO'žÍmú^/a§ÓS%ÍôK04Êûäì²Î§—>8[3¥S–¯¥e«Ó¯%ò± ^:µ¤ï3×ÓgíÅ/Qò®qÒæ?’âŽM ¹Mœ€nÑ0'EÜáÀ$qèÈðqyG¾Þ^æw*rßý‚vGåÙ»ßkn#7qà§YN”O,ò9ÉâˆE>z¸Ó±È‡ˆ|á¦Òñö=dÙózî®·»˜Â˜¾âiüÓËN¼žþÖÞá¡ÛgL7û¶ŸXÜ,˜éªê•‡û >M yÛò–‹EòãÊþwî®Û²6½ÌúVEb‘¯Þã¡pqÝ”^×ôß)h©y¾]ú#ÒÚ—(Áý/‘¹”‚H)4ýð~a‡Ã“Å÷K&\ù{rúù#1kÜ-®:'-U M3/Ü "Ÿyv¿ýÍ ‘¯Áìxð¦—Xä£÷4œ!ò™jàK#ÒknO5Ý3yÅܪó+zmæD•½ÝvÓiÜ'ˆ„=cò?°W ´Ù8* éÇ=šUäm]qrq×/1É›I}m•E,òÙBÇFž†B£ê·•‡$ÌíÛæÏáRaŒg^"“˜üP1$þ4PÒò÷!!ÞÕÏN.êü-ÞnbŠgêªE'c‰µJ3/Xä3GƒáßÄ"À:^±Èç8V4SÖZä3ÏÓ- [ø4câ÷¹?†*9ÿûÆ&ïwOLÚ2F*iÆD±.—þp+NûC“Ä)!C27Œß–³ûýD— u b‘Ï,“àµó‹«S× [ÁÄv¤¡5³LâÑûÀ¡]Ú#²€>!2ÿž´ýêÎÿµbJ |"–ŒÕ$åƒæešù̃©~78"Áu"šš¤“„¹3"Ÿ³58"ÊA"ßdL&»ëâÏ=c4êåžßøn  ?nÌ:ÏÕò²?D£{IÄ„6\Ç}˭ΈÊA³ïúIÐILÉÅåëÝ9Ck’Xä3!áà[‘Ê;6÷ïÈI-[¯.ÅÍ&ý%Î Žû¸gtÙå5fþ5½;râ±ÐÉXGƒ¢;ªÍË#D>s4îý&D¾{X_þ€³D>ŒŒH$ž8$½øÂ†TW‘ÂÓå—ÖX"é(¼ç¯Ý[Ó+bR[nÛÍc¤Jù…/OÿÞеt¾ÎÖ(Å­D¾+ÿ<שô⪵ sû†Ä¼Ø™ïm_T/Dø¸VÜV«†J+r÷.e‚4‚NÄJƤQÕå`!ò9Iæy‘Þ3p–ÈgªE—®­ÎO5Ý;sÅ„½+O\Ø'¢S‹%C¤<©k»¦œ‘Á™<’V¡´õa±ªèÊ»gVôÚ™2ÉCˆ|Ž „Òäý÷MLÙå-&þ<@6¬¹wÍÙÑ! k4´^7BPziÃ_çWŒl'¹Õh|:+%"iLše"Bä³Däö=!òQãâ+¡„ÈGïI¹BäÃ55î6#Yúì^}ë¼^Í·ScK¡gëú_@¯ˆÄĈݹ{ÊùìåÅAÇãƒVysæ©e}Ÿ³—Þ™xBäs5<¥Ÿjþ–Øÿõ€ñäð¾$£M¢òÌW×Mëጄ7þÙˆUÓãÅËü„Èg‰ƒ÷„ÈÇ ˜ôŠ"D>zxÑH]ȇó6j3´[ú¼™ö í6 ð/où«¬kdPó…ƒ|cxOÞ•´cœP¯/œqî!S$q)ˆùìÀ‡-½¼ze¼iãç;ÙIíÝÑ²Ž‘ÐjùC’šÂ}?_ÝövkºÒVœJE'b¥Qå#D>*T¦¥Â6£¤Žu0”­ë PÌ'#D>z˜Ò=Z—ªtlé[ü¤Š³viÍàÂ'4KB\+¡/,½ÚÒE-ƒ¶[ÆHTò¬.®›ÚÓVZºq„Èg1üå˜ê»Ñ3:ȆúÖ”¾5µ°AŸø9}¤e×þZX~c?-ó :Eqj@dÇ4ª² ‘ B䣯…„ú'®ùLˆÆ÷I×)+SL÷Ž\/¬šÐÏÀ©ýBÇP_›Ò·¦Ÿ(6mÁ ©)úï{lEÐZ:o ÷I"ßåµÃ^· Ll<³‹ÏNQ½F·ä„>zmç‹_PÅS…žX‚NÔ6múà—'¨âi„" °HRÏ"@ˆ|ôðw•ȇkk>èóL£Ñ Äu©o¡–—ý¦Åâ%L›Ñu¤~w¦ Nm ‘·•Þ<üá|Ì©bÂ"Ÿ3ÿzº›N_òX‹%J}}ªˆJÅø¯úŠ8Aº>ç– AoVtfu*O´ÏÚ¡/„Èg‰Øí{B䣯ÅWB ‘Þ“r•ÈgªM Ý[•–jº·u-<óӗѯtt‰²•Ìgãbÿ¯‡ÍOº²zä&” D> ñ´~Uɾ/â?é-å{ç>| ±iq>Ð^fmUnª88>ÍZ:B䳆 á„ÈLjÎAˆ|Îáæ@.J"Ÿ)¶ø©­)L5Ý[»ž_9¶?Hô£ŸiÏÖI®ÖDq[8t6››"SVe¾Wzik «"‚™ëÆN· Ù‚"Ö‚ð—qèÐf¢ë{ßxÓ–Vø@!½ºúþÈÎS3¬¥#D>jd‘_ %D>zOŠ "®16å­ƒÈògG[¼#¼=OQqê³ÄïúIñ ÆŸ¬C4ß’ãàûo»ª'!òY ˆ¿¤4Õ×f&ÎëG‹äfQŒÏÜÆ}Ô[¬S”<|-ý˦ք¾¾ë÷qøâ+‘Æ—[KCˆ|ÔÈ"5.$Ô?`‚ȇ‘ Žéªâ dÇó3¾~ÀRW7=5> s¤8°gŒµ$~ûfw±¶¦t$ÞQæ Šù ‘ïæÁÏ ÇÅù®.ËÈ BäíxåWÿ|ÑZaªâ ©Bidšµxšá„ÈG0’Üs"=ì™ ò™jF¥«Ë³)YüØ®¾FqmfÌ[ÝÄà c‚T‹|4‰Sxæ·çM9s%D>3Ô°½fmMÞÓ1¯usëyôfUzÅÏèg:´5eC ŽýI%NYžØô¾4ª8S!ò™¨{%D¾ºxøÚ!òÑ{bLùp­Á-Ú«UUô£’ kÃÔQ²öá4Ò§ŠöÛ°è—: ÑI„cŠÏþ⬒„Èg†\Îö·FÞß° ä†äðdÄä6œ¢Ó‹·ÔûZÚqhûŒ´ùÐo.ZÆ™ß"Ÿ9 ÿ&D>†u¼8Bäs+š)mùpY )³nr8¼ê«[_kkY¶NcjÔ3í={–¹¥P,Ü I dP‚1ÿ補­ŽùÌÓës'‡OlÓà^$ Aø„ÖB­¦tœå^Ðʬíýðö3˜("%,Ä"5,>Jˆ|ôSD>S­|IXZõÍCuFû×÷}ÕD¯V7 êg•†dÊî—×ðI­%Z}á£Î*Gˆ|wÃg8ë•ÊÁãœÅÒ§óI“ÃÑš‘Ht}ó ÝÌÑÖô…%Úíô ‘ϵ{¿ ‘ïä—ÿ#À‘Ï„”8¢mºFQRg]¿Á$E¹B䣇/“D>\s\¿Žuª–ækØCÙ°Ða‰¬Pñ–vï۞ȭÊI«3âè“b‹ÈÇ–á„PMMaRþ¤£˜Òxýƒú¶dµ§í#ž¯†ö„¦<ê÷·F{–—ý‹´*“˜n½öŽáüpº©¼]m›9Ò¤‡Ýcœ ò±õ!hRÇ#W‰|‘¿¡WЉ|Ç3·Ø}ÿmà¤KMB.éÁ®,eøL“D>¬t@x3-Ox°èÄ’>íÇn†Ät5Ц²®ìíZó¶vãÔ§‰¨bÛáèçoøžŽÃD¾“sâ éäq&­W7ðxû‡®²º}`/öö{¾ö|žbµÃÇ ÚÂsQó`¡Œ#AxŠß¨Ñ†\Ùׯ*ÏNEÛfÒðo{Žùì!äBj”¦‘£u©ñ‘PBä£÷ ˜&òáÚ#ÛM×kªúª5MõõnAýãXÙ›ï­í4Æ,$Œ Tåÿ÷Ek|OÇù ‘/)]Šülƒ²Šz®Ü*ʲœÒ¤0v– „“à©z¾2å»á»âïᣒõp@]e!-†6šlP ¥ÂdUIË›;ßJFÛeªð¶ a(o ‘r´.5,$ÔO`šÈ‡ajÜí™[ž ïæÎ×:„Ê$q³ Ú£[Úp{y;õ'qUåYDH[û>GIÆ!/´–”éý`Tùó¥™ë¶™›¸ýô¢~–\ØäY=½¾¼¥¤M+yÞ î u¿.äðwÑëðhùZX®¾›T;`fñ[ðüV],y­a¸û_u7å_•T“w8•/ M«+#w„ÈÇŒ¤6 D>z(3Mä3ÕΗ6J¯.|À"äË_ˆ|Wäë8ÜÏuDÞñ9í:Ù¾©=÷ÞhÐ&ªÊ®¾yuËóÿËÞñÖ¡€˜Ž$=²:ÝZ)Fž2IÒ"„c-ž±pî` ª;q³j|BAÔ[^þ ”¾Éw¥âCMñëÝKêÃÿP­2Q§¨ lúÀ׎êNˆ|ÔH"5.¾Jˆ|ôžÓD>Sí;¥•_Ù5ÛÈÑEŠ[¸Ùxš´ÓdDŽWÊ¿‘dÂÈÑ+ƒD>Ó zª{ò%ÈÿˆüïÈáNÿ1äë}  079#^÷ѵ5}kòŽá·Äj§z]¤ 1 6yø,Fùe°¥æ‚ýoÀ«oýsUpÃàÞ "Œ ƒJjÔëCâ|rÜŠpõ‚1‘ïøWqWëEÔh?ý'h¿äe8W?Š„P"€‰|òÜæ”q$ЭÜ!òmB•”;YQlÊ{’>Ž9™ß_³a"_G•k:pÌäà+ró”§´Ó7^šYçè­N{œúåþçuêªzkþ½Ž+‘7àrp¸ÇïP™üäwâ›Ç·¢xWœÑ,3úÍ‘‡£È"Ö†¶ýg³~ï^3‹¯÷Ó ÓKy²ÚI‚zqL´åÇ@:ž¾Ø€½ÜPátKã¬Ü\1øõbž0p—,´‰ÎÑr̈|vòˆkÔPwºÃN_ŽÆD¾ÙkÑ–-WÜìn<åŒÅ\I@˜·Å• H^k˜ùœíô* àHƒp˜ÈwdäÛWÖ˜Õ9×á%U±4ÀÈ2åýí—íZ _h§±†<)곌«[A¥á­jÑ,K4%—û N2 …[ÆÑ¸O‘J¹ÈŸ@þwÜé»ÛÕ  ø¾l_@“.«‡ýz¿(ŽTjÔë$¼÷wú`´èCÑÔ½Õ'æˆànHÓ  øÿ+ÆúÌE½wˆ|xbb/Etƒ ÂùP§ïbg}jÌÍgñZ]HF‹ öMò Å1‘oÚÐØ—–Ó5{×”ˆÄŸfÙO{;OqÜPS3ÀÑôN§óvëÆÅ}–^oõƒ²ÕØ%G©08¾ {#ƒ¦f+Š»FO3 ·mräñÀfò‹‘¿|mï¯îr«QÁ¯#ßUÕiQÒø•ûíð±@F­^Äea¤w×çq­È¡OŒ–×9Wš7KŠeB䣇©»ˆ|XŠÀ„þ{y¡b÷¯qúH;ÍásÑhˆGÖ´CD>\'îè× ? ù(äg#_ÛᣫÅR6aÖavœ±ÈÇðÔ¹V Áîu.Ô—ëßöàEAWôÈ.Z¾Òh<* }ð¹|8Yú<¯,ªUÔ­ô^xò#x ¤ÃŽù¨¡"D>j\|%”ùè=)wù°aMåWtn^ÐG€|¤ÖWk€Ëå)è=!†ˆ|˜˜gi¬ÖOûk„®"®¤çðøJ}Ö•"Ê[®-„ºŸ©a0B–d%oô’ r$Gb®}(ð€…Ð+|®Žîú1±Ègå12L,ò1¢Se‹|NÁæH&‡-ò™ G&É jÛ›A_h§1&h ŠæÐy´'‹²Èw‰`µÃÇò±Õé;e‘Ëç)ô@w;Ã>اÑש%1øIxWP‡ÞW?"h $ÖIY'Õîåä:@‡Õý6©#õ‘:Á½ÐGäCªãi0ç&òÕ”±õ?伜~˜ÓŒÈç¬v ŽÈ‡€rÈ7™Öl¢8,YeÔ9F½•©QgŸe>h§±È·û,.æ²Ñrþb‘ϤtE\ê'7L7_yü[ÚÚØ9\ü½„*˜WqøÞmí/ŒŠš +BÇÃc¢$,êßEÌ÷ƒšÖI§Óœ„Un~×q…šü4¹ °ÇÀ¯#¾!ùêAR@ŽÖ¥Æ…„ú'î°ÈgBª–Á/—©²ñn3w:ïo§±öª¬ š“½èN$\)›­QJÓËëÆ÷¤+(G/¹ ÌªpˆéO·lËôåšßágE}3»­d`fÄËðiĸ_d²y`Ê-‡5e«XÙ¾§ºZFú’lSÍn¸"Ÿ@%EºB䣇«;‰|X®Ht ·QîvÞÞNcý•Ë”|Aˆ#¶QêÀÅ ‘¯N¹–7luú¦£u-ë·yÏã…^Q^*sm Öf u#•½+eu­Þ©!½d̳Øíg5¹‹ª«åЉ/Ð-Æ "Ý*|2=ƒD¾ºëB>‰†ï ‰|HjWÚ†ÛGëúžêNIìN"ˆcžWe³3@óævc¡¼R¡…6£=@ÃD>”½—áNÇV§ï”’°fYŠ e,u«XD4}TöÌ(Ù ÙzµU™+4gá»Â™ð†Êýkù&!gÊäBqøÓ½£WBäs)'Ò"Ÿ 1“…ù˜Á‘¢ÚD>\†@yºzmòEýyo;mPê@S!‰ìöÔe©“„!"_2©nîîR£Šd0Ì)"_lÊìsç–ôéÊUÀÖñºXçcªµ0¡`-„òâàASˆäÊ©:-TŠà8:|ç"+ ÷Ð7ŒP}0O÷Àóï…:öËŒÈgoî­ÁùˆE>ÇÞ!oLeFä#ùx@î²Ègª:¢ý£¯ïûŸ·Uø(p6œ·µÓXçꣅÀ–eÇt¥Mº¾CäÃ$Êk¸,w9¶FúNù¤ÁQz~pàÙêƒNÝ€P¹þ:]o?,R쀟{a¹Šý«¥8_¡ ,ª3=V-ÎKˆ|…úŽùêcBBüwù0jQ'–q…ÂÅYW,È:‡¿·´ÓXúªÿòt\CÐnç4a'[¾SD> W²§j_í¯&vàc§–êýùz?(Ã͵"Ÿ›&Å3‡!òÑÃÒÝD>, O¸§b÷ —céaÀFêòÍ9*YdGZ¦ÒMr"ß$"ÛOØRºá Ç`±ÞTC¸Þúý¢RÒyƒ3º"5j„ÈG‹¯„"½'ån"–&(ºïÚâå5F#ËëŸô p[jù™bЕ«U-GþrÒ™J‘ïj1=^,à‰%Y•»o8ƒ£ÏçÁSûèERÇÿÑ©C]‘ϯ!ò¹\ÛE"Ÿm|\ˆuŠÈ‡ëk>ô›‹F ”×q;ÝõÜ—µtÍeµ@±ÆÙØ"ò±5½ï‘Ï×deɪK,1CMµzǵdÍe@ØhH$tJ 3"Ÿ½ü ŽÈ‡qeË:Æ‚Xä³÷R¹+ÞŒÈçlÄ"%røh]úÜ!SQ|~ôŠ[ËÎÛ4kJëOWÌÚ/ùëŠ!$q¤S3² b‘Ïìh6dÎ?Õ ŒÊLg‰ºf…ùÐO¼k¡xå%cTÇ'þpVlBä£FŽù¨q!¡þ‰€»‰|&Ôâ~¶²bÇuŽêš»­ó™jôŽkÑ’szž8ð@BêÛ^?%ÍÖHßi"~¤AÑÕ‚€Ø…ùßsmdæï‡ÃRþzF+ÛÚ¸Û3·Îä|BBäs;’“e‘àlù°D¡q½€ÆK æh0äkƒJ…óOjCšý†ÞS©›šùêâM{ýo9Z×7ª¯[šÊµHè'·øxÆ[‹ÏéC›?2ß•‘=B䣯ÅWB ‘Þ“bƒÈg’(¶Ç[KÊ·äèU9 c´_´øœŽ+Kômãi&Ìð•ùÌÑ@¿µZ- Lø.û•½ b½èÆûûU|iøú„”Y7-  uKˆ|´à¢—˜ùèáÅ`jBäc̺E9Mä3‘<ºRÔìëì—öø}[­Î­†‚oë¢ZO{פ¿³WBä£@®Õ¸¿–©2«JÊþÁf·ý×Õœ(‚ò­9êø~_媖„ÈGà´T؆b\[."D>jpY%D>z c‹|(‡VÎ\#ò™¤j=þï•ꜚ’õ™~½ïú¬ …@Öä—Ø^s[:ÔWBä£ÀEf ïûöõw÷+t•ÖmãSdõ™ ƒV93Óä’€¤Ãúº¼cù¨=!òQãBBý¶ˆ|&ôðq»!±½~ãý*|,¸?ºâÕ— òS¥%-Fþþ«/éçD>s@[]xŒÏ _ž5ý_…?¸þV†ÊXÅý¯ÝÔÍÿ˜ëÍÂoBäcdR3"=Ù"ò™KÕbø¼óBiܼÌ)Ûþf\ þ 7ßÛ¯nÔrâÓáÍ´æz;û›ùl ×zü–oTWª³ ¾;ÁØ6ªb5ªtãcùöë ƒ~šÅTÅ„ÈG$!òQãâ+¡„ÈGïI±Iä3—¬Í¤—èŠô§r?>ä7l~½\ W¦ý«¶þ°YÿsÌõuå7!òÙ@OóÇtš9£pÁyÙ–lƒ¤>U}¤ðúPXüÈgð¶¦'D>¦¤(‡ù(@a'ˆù܆³ËD>sɰQ±fƒ~|¡t}vaþ·Ç}~†ð\zø¨Öt˜¶}½¹®®þ&D>;6îþ\Q£Ö'_{5MY±ûºÔÞ‰{—ÇþQ}Ÿk>tî%&¥%D>j4 ‘_ %D>zOŠm"Ÿ¹t˜›Ôô¾&þr®¢à§Szó8_ú—(N´ZúBØÛù‰ÃŸ3-;!ò9€hâÀO³Bâ=‘5m;”ocl–Åš™K‚i¸4j#„µ=®í¸¥‡™+ùvI„ÈG(!òQãBBý¶‰|–(bÓ¾q½?‘÷ùaÞÕwû\ǯWháò#›•¢¨¨ÍŸ<ùª¥~¾tïsD>Kpñ‰FMûÎJÉ~a—¦lóUŸÚRs¬.ߤ ˆéöbË?œ±Ôå{BäcpRó"=ìHâ“øÂÛ=6úÚû*òçŸÔù«¿dm¦!sòVyptÊ“ÉnØAC]ZI ‘.B䣯ÅWB ‘Þ“ò‘ÏRJl¸'!eî¨âeW®¢öO‰ÏñfWs¼Î¥®QK¥Ë;?uòM¼Ñ]ò"MdñTl·×GÝZpáÊå ›º*ïÜÇ×…²_Ü­ºùþ¡Â°Ö&´»ä(MUi%'D>ZpÑKLˆ|ôðb05!ò1fÝ¢%òÕ-úö]xÒˆªÖc·ŽS_4ü‰;Tùi6Ž¡’Äz8.:«Ëœ´U.wžÙiúÁ¯­§f&†ùœÀ1æ¾ç “Ý?F“ «ÏõY£(ßî]ëüÕ‡òÑWãŸòšŒŠÝ-G­Ò|Ðç™N¨I+ !òQÃEˆ|Ô¸øJ(!òÑ{Rž$òQIŠ÷¶#2ܧ’À®/£µòªko¦«uÞ1PSœ+óƒÿR~sæJì}o<”4iM•L‡±Eäã3-¸•ò*âR?¹ Y‰g,oçC/Ó×MI¿öjÆçÅK·$|›*Æ0VÝ‚ðÖw÷£]7TÒàäÙÉOþƒMÀ²âîùQe{©P®Î¥› ÆiÅR0$7µ?†êE³×‚‹Û=O¹ùï,6å¶$w"p<$X2>Œ›Cí|³¿…µi ÕHEý'Àéš½kJž9µŸÖõI¬N/>û×ÀÜŸ½uvóÊáMf÷5z¸‡+à¹^8Í´·P0ÿ¤ºdÕe­(¸Õ§É¯[ïÎé|šâ1–œ­N¿öhÝäÉ«O2&¹‚ÚŽûý ¼hV™ýï(·ªŸ®RÞBÚ6\)m.·‰›‡€¨i pч/ù@!`{Ëú ÐU[¤eV9¨2+4Šó¥*ù©[~€ä&‡”Õë|Ð[zØ«'ÿÔÊà›;g᩺ {iI<]º :ÌXœˆy$ts’ô®!püÇ.SuŠâ¨”<×J"¹ë"`äôx3·EÝ0Ïßáöºêúþz(鯗Wu5êôaÓ`¥´mG)ò‚„^€Ã•òA_­ÁÞ ¯ÑjUYÕÕJŽö–\ ÜBCú ¡°é¾ˆö“zz@f‰êñÝEÓûKQø5Ë8&ïÙêô¡ëÌü8¾ÐýD>gÀ©*<-ÊÍøº«¶êf ¯º­Ô­ jM„Q¯—5z‘Q«rø\-GÀSs|WÀ/ãpÄ™\½ì‚ 0æjtŽ2q ®3²ÛËsvù节ã—Q:‡ˆ|öÊ#ñæð MDˆ|æ°ô›túnº[Dgöˆ|ÎjgmKÏ®m®.ËIÐk«õ™‘§®^ÊÑó+Á ¨âðùr¾8¬@œÙñ‰L†ç¬Ìöò±Õéû-‘ÏÀæñAÑÕIãÿ8€Â°'Ž @ðr òy¹†u"Ÿ3šâAòØŠ)ö~áØ"ò±e†7TSS˜àOÆÇ” a‘ÏÇ4ó qÑì—°ÿ=ð(°Èç©I•ë‹|Ö±!14 a‘F©$i-Ä"ŸÇ^,òyLv/¯Øíù¼\‰Ç–E>¶¦÷o'¤©s¼NfùèùŒFN§= B$·”¯ÙœvàÔ¨@»Ê‘o½ب¸X_"Ÿ={“‚vŽf˜Ý§œ±˜Kˆ|ŽÆ\:3‹|̱÷ÍÞwk’Jù:ôp«þ^úþ{QÍÉ“AQbµî‹+]ÄØÕ9·ÖX‘5 H¸{¸c‘æžn—ÊÖôþ‹|îT…”M…€‹GërL¦14c;ªò½. 5ÜyiiUçÏŸ¿u,íæ[Ö"_÷ÖAJg»ï»µ:•:ÝÖâ<ž±aÇËø}ß{ü\¶ë2`‹|ÿßÞµ@Eq¤ëêžžT„ˆ$FÝDÃSˆøE³Dw71Ѹn4Iô†sî‰1½{Ä}e5×Ýøˆ›M|ÄUñ- ¢ËÃ’=f!_`xÍôãÖß= Ã03À Œ<ª83Uõ×ÿÿSýõOUwÕ_U«º÷¾ë×E4(¸ëMßí;ò)Hb—ÀÇNô°@Q¢#UZ¥BÝï$gw­n9ò5Ù;…D½Î{•IZ,×0ty·Ã„a¥:©iª´êÖ#ù:á:» w9ò¹«ÓWù®v;¤{y…:Û‘oì sÕ•e¿õ`…V7âУ7ð)b5 O=ñ¯ÿ2Ärå%™xIÏàF(aýÂwÿó‡ omöå ?;D!®zð€àµeåe£>¯ÕúªSº°ÏòVŒK˜þ._[µt—–Þ~&+wÞ*UZ¯ÔCË Y^Ïíú¡$?‹Œ!x™N!lÄá^zÓ„¸çf¬|Å÷Å™©'ºâ4p䳬6I» pä»|óX[[Ê:]5m,=}öìf[ F®Ø®ëŸý×ó*š¾£fLw¿Î.H¾è ?g^‡ekø1‹—uÄÞã“]ÿðö'¢À…a›Gj†ÊŽî5ÿ£Ý_Iûض÷ÆÛï˜Psÿx87 ê° qqGF ¤Öìȸ@΃°uóº1 ùðŽ|]¾s«»Öéw‹ùºñý¹´#*ÄsúðdÈÐ%s ‡…O½”­¹·©ýÐß òxñÒøšâŒ2{rþžÜŠ£YW>„òð˜ØJwö­y9”—wE¿íz@Ó×Ú¤§C#?Ø›ßù'’ùZß"7QºdG¾öΕLþIXÌ<.-MáÓOCïQTš>)q+ÛP½ ž@'ßâPUwwB>ÈG\t;pvf{í=2!é]¦öá:µhìÖŒüKöì]ÀönýØ9@Û°þĹëË­uµ3ßmväkg}{ ›»vä³¶—®8òu²mèµpäkƒ³ãÅZ†?:%jŠŸ‰fJi–æ Æ³ž_È4m¹P¬”Ë1âBý†?3ÇŸ3<¬¥×‰Yˆ‰|~î\¥Ã÷ÑŠë,XàAû<±HfcдÉa° [«àéÁlóõÕ,Ê:_-/ÉÿiOþ·­:ƒŽ|†GîúêŒ÷Ž|]rMœÀ„œÅóäÅÅÅe–Ÿ•¯?x-ûLŠrdÝí !CéðYÚXøÌ‚]-+eßÞQÚ­ÊÜáãQ0ýÏ¢†ûù…Æ Sløê}cAK]rìÝÏG½()/OÅyè¶ïV’†ÿ_V¬þùolÉ´Ž| ÞöÕµwq¹ëh]w5XÄ‘ï1Ù§‹Ž|jÍ£¬ —gÿyËŸ+ÕÁ“ŸWC„9@3Žùõ2…æMÑJû(‘Fæ&nØs²dÿΕåb@ÄX™Fk¶þ4ˆ¯*[ y õ™ç –§¦¦6æfÚÕH³…@¯3RÓ!¶ ð6”•}1åÔ©œûw¼ÿ#¼ïCðP¡ï@ ($¹ Ó¾‰#_§AÙCj5MÞñÔ3 ü[V›Gç?¸Ý’†}{.(¢`œ26•e }{ߟܳ¡D4J:é!¥/š8ÈR§bï'3/îJ£(AP{Iÿ4«fb{OHHSž¥-ÅÚ™&Ž|íªÇ²¹«Ó—ùz,J¤âhÔxÖÊ z5.ð¤JÇh ÉIDAT‹6Ï×Ûky´Ýt ÄÕÿúøšòV#ë « ÞRÓ"P:ÿ åUö¶²°[}|8ÿŠkS’xϦ Iô*À‘_+Gë:ă¡%ùùùT^^mùIÝy¾\¼º3µ\y,ç£ðWVÁ‘½‹FCÌ.¢ùs‹àÌ{9øGK9dÕ›,ìØ¾½ Hô1K»#ß@WYçG>,ÙÔ6:§¥m)âÈ×6F=š£³ùƒ!¢áoŸ0¡TÇ\º~êj…#b÷F_eY L¤†‹¡U% ÄB½^zÃgh¡DžÓ—K•¼%¯’VÑÈü0¢P:=–ù`ðêÒúv­o¨ò ´®Å@3hôÔ…Ž|ðÖ ÷“«A ¶"’S¢Õ÷¯bÇ;9Ôqš¤?, òß» [Ô†½›¥Et«Á¿‚ôÐÀn¨Í%–‘#{ÇOÂR-ùIºç à.G>wuú=ù^VSØ‘¯»­ûu©é~[:oêÆ—_*ûûªÐ}ÝTWù'°!)É㣷b/äq W áŠ3çC‹ß+ü+³ «•ƒÁD‡‰b ŽWå˜äŽœ¹µß¥ãŸækË£@1¥òø&àé—ŠJN¿Oh]ˆ ¢aÏõuâÍl¡Ê$¨ƒg>ÿü\¼dOÛ\ BÆPqôDÎ$Š4[Ð\&§2Š©2œRLP"Ú·w¦ÊË÷¥ÃÆËÁ|4zóËÈt„®¤`Ý3n|†¬‘C«¿¸Rz(q¶”µaï2þ6 tØöɺ××e44•¶;q©_`dÿ—ýíÁ_$´®Ã€V{€M›}¡Ú}¿:ÌØÂ8;,Ý~þ>¡Ó†{ K¨~pñ£E’£ÖG¼yíÓ»W>ñ&´.Ä€VUrµ÷mz·yûZx3[{ïóØ“þ²äI¶ðCdOåf4\ŠçÿÌPzØA6ÉäÈ›™¥ sr ÃQ–ÈD½kjùöß\Ó×¢X¿¥[r*ofeUA3B11¢å[Rò¨Æ ë·/K~’&HXج=D(Ô ÏË¿î„áê’€O²ïÚ§ƒ4¾9yoã¶ò×.{Çüáq“¾f¹†I kuü²Ãg/ovdïáØûŸµðþÂ2ÿÀ2Öº:˜÷ÀüÊ[NW㡹ƒÞªŽ±»«ÓWjs±Šó cýˆ?„æ ”{ÐþØ¢¯äœÜkáà]ÏT–ì…QHeù\s§ßL³ìô§„‡`_lêôÕ4]Ø(apã!h!;ûÂ¥xÅán<^mÊÞq ¦¦¡S ñúÄQìhXó2‘Ó~þSU¶S©ДkšÞ+@1nh”Óöä–(å$&ØDÀÂÞm–c"Ø›!dÎö»ãÿîàÏf_KtxÜDÜy7J÷³ÃŸ¾~뺴V¾-{ÙØø©ÿ#ÖÕ¼i9ˆh¨/ûòóþŽìþ_ŒÙÛ¯S²þžF¼üõÚ‡²òM ð`ƒßÀá³cQ„䜎÷ ÏÊJ³;Å”†‡PÛâqt)"nÄ•“2‚@W!àŒ½Ãƒ2Ø{zúÛgëElÞYäúŽœÝ·ï@@®ÔÝ|_¯oärXNçè·a9.wÈãHÞ‘–#9RFèLÚkïØkPÄ>°NÛ;Ô™Ø|gÞ¹Þ©KaíWG®ªÛ à­ò6Šx!ü1l½©ÛTŒT„ Ð{ïP‰J‚A€ @ ‚A€ @ ‚A€ @ ‚A€ @ ½²¤©ßÜN»4¼”(±ËÚP¨¢©’Á>Ì{‡Ìë‰m°Ø$)›à}ÄðzÿktDâì·ä#{-ÖúÛ”$D‚@×#7ÌQÛØ07˜zÕ¾sk4˨„s­öÙ·W3bïö!tw#@¼÷ÝxÏü=ÊÞy¼ †Ü­4íMœþ¹s—&iÆò*‡ãØû%ç´)‚€3‚|„µ,Þˆ¿}¿$ö½jS˃Ÿ¬9í剽ÛC†Ð݃éô݃sOÿܾÉÎü~*8øÙ€€¡c5ž~+” «6²ó6¿5ÁWÉw4žóÕ‘õÁ£‚ƒbŸí{òwTžðº ^ÉcûL¥—‡A¿˜¹ÐÙß$öî,rDÎUÈæ<®"ØÇäÕ4_ú÷ýû‹Ì—})â……WÕåÅ™XÉùà¨Ò㦿Ë×V­Ä‡äö‡!Q/-½ýLVî {ú±¤—§xTÝ[ün„ÃB¤mMméÑié¿dfå®™ðÞv/!ç“£RUù4nÛw¢àð£ô㚘=¿? ïS‘Cù76íÍ¿)ÑÉAÀ)8´ïÄW²m|–8(Ÿ oKFNv–r|ò/£ëÞþD8¼u´ˆÔ •3ÜkþG»¿jqâðB°gï‰/-Su§(ƒøУaTGg†1 êqÄÞ%èÈ—‹7}ìëâ”`êgAxLl%_«Ç‡üˆÒ0$ZS'¼ÉÛ554Žh4çÕ7ˆK[zªë„Õ±±aBÎÚņ:ŽŸÄqÆ¤Ò Ón¥‘'þ6a:?šÆ®>ZWùY÷ŠnµšŠÄÞÔÝ¿Q wø…Lšt¡ØP–’m [ö5uöÒêÒkx}èð!P¨‘’^6>šqÿ¤7±wòí¤Ów ¿>'mذÙ3¦Ïš1#iÖ|HSqÛ|¨ˆ"þ£²äaͽV\¿`ÁÚç‰E2P š69l‹cÐ䉄0 =¾ZjýÚµkÕ”nð2YVƒ~•9Uå$éÛó[c¤©¾òÎï7—zr²žc¤Ii{P¡™Ó¦¼6}ÆŒY S§/úìÕ¥ÑÔ²t)J;¢U™O¸ƒ©¯ŸE ÷ó Æ›U_½oluünË_5Oœ¥«¡kîm‚2Г?Ú{à°±#•iµã7oæ{o‰É9‡€b¿ÎI©>ˆ…îUè3**f-NóÕrëg¯»QÃW•­P(dÔgž+XûëçfÚÕH³…@¯3RÓ!vDDUÝÅSÑ©sy˸¼³G6k<½WhµÚ5£ü ¯Œ_´OiOݤæÁ%jÁˆ‡W öU5M@ž‚€sPèÇ*ÃN}EEFm~Sók¾í=yeDÁþ8<}%…Ô(Sèû[>¯<¹gC‰0hÔ4™Ê¢ôE›¦̬­¢°ÌÆ*zf2Œ~}ÝŽšã{7ÝÔ : ì=p€î bï­`#' ¾ õmÑ4]¢|`î2x°vâ©sW—˸ÐU‹ˆWÎâ–È”Î$8A¢ ßKv¾”ÎYùôÏ:óá¹sçÒVî½yWaÎ9và Øûc9gˆ½+¨Øˆ#Ÿ+èõAY–6æä„Û¿ty©C«J,y„z½ô†ÏÐB 8ó…Oµ,µ•¶­g\Bâ»Qð¤«=³ÿH^4â?уoÎôƒǠɃᲟJuí´2[5"´¾ˆró.ûAoyõ8oÊBÝ*B0 ­Ýç 7Ô K{b†-—ÙTèÖFÄâ´tÚÞxì ¨©¾“Ĩêž>›·™Ø{{À$<Ž oúŽÐ!e­úöƒ‡îr’’<^rb·pÕÑ0è -~±ã@!ÚBÏâY±€?jÖ |mõºº:ÃjjTfl¿ µ² 2}Ç"K’Whêâ­y É<¢D£7¿ŒL—Êñ4sçŠÙ×…C«¿È-µ–³ÎSAñÇd…RöEì„ô ¼¥;êêV7Ô7¾ 4bï€ ® @:}WÐ#²­(\}ò7ÍNL¦‚˜˜‘/Î,SZÍW¼æ´²A¸Šõ(ïPß<σÕƒbéM^¦¶g^Äð䂇÷¶f&ôñáüËÍy’"tÅ^Û’ÌÙòj¥‘ñȾšf Øi Þ½R-p!@ ÔQˬG €n®îL-WôŒêy §â³´e(vüPö I†Ø»5t$ßAH§ßAÀú:;]ìb@qñ)ÞˆV7Íi?…xý´Qª ¥[r¤uü4£ùQÖ#šZçÖ8󷞢•5-¦Äy[6¤u!ñ¿Sêä«Eë-Ë:‰ B€ÕäÉü-Fõmª¸v1{2¥Õ­oY(¢¡¾ê—Ÿ½¼èÖömÐC·Òã'³?°Øo‚Ø; E‚³´÷ÖYýD®#&д߯ê1cñ iÖ£ðíF&-+‹ñ+*RYë‰ÏÝÿ&?÷ Ò°k¥éƒäQAd©^»¡%Œ‰ÖO߸ÏÇ‹RS7JsòΪ;=]ó”•bï΢Iä, ¾%$Ý£%-„ýîø¿•JS¨AŸ—ÝWÉ“˜ Л öÞ›îæã»2¼ÿø°'¿ì"T??ØqOËû±Ô« pQ%'t[ˆ½wÛ[Ó£*öÿÝH£)uÓ2IEND®B`‚gobgp-1.29/docs/sources/policy.svg000066400000000000000000000231441324612745600172140ustar00rootroot00000000000000 A B C Adj-IN A B C Adj-OUT RIB Import Policy Export Policy gobgp-1.29/docs/sources/policy_component.svg000066400000000000000000000336541324612745600213050ustar00rootroot00000000000000 Condition Action Statement Statement Statement Statement Policy … Condition - prefix-set-name - neighbor-set-name - community-set-name - ext-community-set-name - as-path-set-name - as-path-length - rpki-validation-result Action - accept/reject - add/remove community/ ext-community - prepend as-path - manipulate MED Prefix Sets prefix-set … Defined Sets Neighbor Sets neighbor-set … Community Sets community-set … ExtCommunity Sets ext-community-set … AsPath Sets as-path-set … gobgp-1.29/docs/sources/route-reflector.md000066400000000000000000000045411324612745600206370ustar00rootroot00000000000000# Route Reflector This page explains how to set up GoBGP as a route reflector. ## Prerequisites Assumed you finished [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md). ## Configuration Configure `RouteReflector.RouteReflectorConfig` section to enable route reflector functionality. The configuration below configures two route reflector clients and two normal iBGP peers. ```toml [global.config] router-id = "192.168.0.1" as = 65000 [[neighbors]] [neighbors.config] neighbor-address = "192.168.10.2" peer-as = 65000 [neighbors.route-reflector.config] route-reflector-client = true route-reflector-cluster-id = "192.168.0.1" [[neighbors]] [neighbors.config] neighbor-address = "192.168.10.3" peer-as = 65000 [neighbors.route-reflector.config] route-reflector-client = true route-reflector-cluster-id = "192.168.0.1" [[neighbors]] [neighbors.config] neighbor-address = "192.168.10.4" peer-as = 65000 [[neighbors]] [neighbors.config] neighbor-address = "192.168.10.5" peer-as = 65000 ``` ## Check route reflector behavior Let's check adj-rib-out of a route reflector client. ```bash $ gobgp neighbor 192.168.10.2 adj-out Network Next Hop AS_PATH Attrs 10.0.2.0/24 192.168.10.3 [{Origin: i} {Med: 0} {LocalPref: 100} {Originator: 192.168.0.3} {ClusterList: [192.168.0.1]}] 10.0.3.0/24 192.168.10.4 [{Origin: i} {Med: 0} {LocalPref: 100} {Originator: 192.168.0.4} {ClusterList: [192.168.0.1]}] 10.0.4.0/24 192.168.10.5 [{Origin: i} {Med: 0} {LocalPref: 100} {Originator: 192.168.0.5} {ClusterList: [192.168.0.1]}] ``` You can see the routes from other iBGP peers are reflected. Also Originator and ClusterList path attributes are added. For the normal iBGP peer's adj-rib-out ```bash $ gobgp neighbor 192.168.10.4 adj-out Network Next Hop AS_PATH Attrs 10.0.1.0/24 192.168.10.2 [{Origin: i} {Med: 0} {LocalPref: 100}] 10.0.2.0/24 192.168.10.3 [{Origin: i} {Med: 0} {LocalPref: 100}] ``` Only the routes from route reflector clients are advertised via GoBGP. Originator and ClusterList path attributes are not added. gobgp-1.29/docs/sources/route-server.md000066400000000000000000000044031324612745600201550ustar00rootroot00000000000000# Route Server This page explains how to set up GoBGP as a [route server](https://tools.ietf.org/html/rfc7947) ## Prerequisites Assumed that you finished [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md). ## Configuration This example uses the following simple configuration file, `gobgpd.conf`. There are three changes from the configuration file used in [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md) * Peers are configured as route server clients (of course!). * GoBGP doesn't try to connect to peers. It only listens and accepts. * MD5 passwords are enabled. ```toml [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.1" peer-as = 65001 auth-password = "hoge1" [neighbors.transport.config] passive-mode = true [neighbors.route-server.config] route-server-client = true [[neighbors]] [neighbors.config] neighbor-address = "10.0.255.2" peer-as = 65002 auth-password = "hoge2" [neighbors.transport.config] passive-mode = true [neighbors.route-server.config] route-server-client = true ``` ## Starting GoBGP Let's start gobgpd: ```bash $ sudo -E gobgpd -f gobgpd.conf {"level":"info","msg":"Peer 10.0.255.1 is added","time":"2015-04-06T22:55:57+09:00"} {"level":"info","msg":"Peer 10.0.255.2 is added","time":"2015-04-06T22:55:57+09:00"} ``` GoBGP implements multiple RIBs, that is, each peer has own local RIB. Let's check respectively. ```bash $ gobgp neighbor 10.0.255.1 local Network Next Hop AS_PATH Age Attrs *> 10.3.0.0/24 10.0.255.2 [65002] 00:05:50 [{Origin: 0} {Med: 0}] *> 192.168.2.0/24 10.0.255.2 [65002] 00:05:50 [{Origin: 0} {Med: 0}] ``` ```bash $ gobgp neighbor 10.0.255.2 local Network Next Hop AS_PATH Age Attrs *> 10.3.0.0/16 10.0.255.1 [65001] 00:06:12 [{Origin: 0} {Med: 0}] *> 10.3.0.1/32 10.0.255.1 [65001] 00:06:12 [{Origin: 0} {Med: 0}] ``` Of course, you can also look at the adjacent rib-in and rib-out of each peer as done in [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md). gobgp-1.29/docs/sources/rpki.md000066400000000000000000000141621324612745600164630ustar00rootroot00000000000000# RPKI This page explains how to use a Resource Public Key Infrastructure (RPKI) server to do Origin AS Validation. ## Prerequisites Assume you finished [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md). ## Contents - [Configuration](#section0) - [Validation](#section1) - [Policy with validation results](#section2) - [Force Re-validation](#section3) - [Monitoring validation](#section4) ##
Configuration You need to add **[RpkiServers]** section to your configuration file. We use the following file. Note that this is for route server setup but RPKI can be used with non route server setup. ```toml [global.config] as = 64512 router-id = "10.0.255.254" [[neighbors]] [neighbors.config] peer-as = 65001 neighbor-address = "10.0.255.1" [neighbors.route-server.config] route-server-client = true [[neighbors]] [neighbors.config] peer-as = 65002 neighbor-address = "10.0.255.2" [neighbors.route-server.config] route-server-client = true [[rpki-servers]] [rpki-servers.config] address = "210.173.170.254" port = 323 ``` ## Validation You can verify whether gobgpd successfully connects to the RPKI server and get the ROA (Route Origin Authorization) information in the following way: ```bash $ gobgp rpki server Session State Uptime #IPv4/IPv6 records 210.173.170.254:323 Up 00:03:06 14823/2168 ``` ```bash $ gobgp rpki table 210.173.170.254|head -n4 Network Maxlen AS 2.0.0.0/12 16 3215 2.0.0.0/16 16 3215 2.1.0.0/16 16 3215 ``` By default, IPv4's ROA information is shown. You can see IPv6's like: ```bash $ gobgp rpki -a ipv6 table 210.173.170.254|head -n4 fujita@ubuntu:~$ gobgp rpki -a ipv6|head -n3 Network Maxlen AS 2001:608::/32 32 5539 2001:610::/32 48 1103 2001:610:240::/42 42 3333 ``` We configure the peer 10.0.255.1 to send three routes: 1. 2.0.0.0/12 (Origin AS: 3215) 2. 2.1.0.0/16 (Origin AS: 65001) 3. 192.186.1.0/24 (Origin AS: 65001) From the above ROA information, the first is valid. the second is invalid (the origin should be 3215 too). the third is a private IPv4 address so it should not be in the ROA. Let's check out the adjacent rib-in of the peer: ```bash $ gobgp neighbor 10.0.255.1 adj-in Network Next Hop AS_PATH Age Attrs V 2.0.0.0/12 10.0.255.1 3215 00:08:39 [{Origin: i}] I 2.1.0.0/16 10.0.255.1 65001 00:08:39 [{Origin: i}] N 192.168.1.0/24 10.0.255.1 65001 00:08:39 [{Origin: i}] ``` As you can see, the first is marked as "V" (Valid), the second as "I" (Invalid), and the third as "N" (Not Found). ## Policy with validation results The validation result can be used as [Policy's condition](https://github.com/osrg/gobgp/blob/master/docs/sources/policy.md). You can do any actions (e.g., drop the route, adding some extended community attribute, etc) according to the validation result. As an example, this section shows how to drop an invalid route. Currently, all the routes from peer 10.0.255.1 are included in peer 10.0.255.2's local RIB. ```bash $ gobgp neighbor 10.0.255.2 local Network Next Hop AS_PATH Age Attrs V*> 2.0.0.0/12 10.0.255.1 3215 00:23:47 [{Origin: i}] I*> 2.1.0.0/16 10.0.255.1 65001 00:23:47 [{Origin: i}] N*> 192.168.1.0/24 10.0.255.1 65001 00:23:47 [{Origin: i}] ``` We add a policy to the above configuration. ```toml [global.config] as = 64512 router-id = "10.0.255.254" [[neighbors]] [neighbors.config] peer-as = 65001 neighbor-address = "10.0.255.1" [neighbors.route-server.config] route-server-client = true [[neighbors]] [neighbors.config] peer-as = 65002 neighbor-address = "10.0.255.2" [neighbors.route-server.config] route-server-client = true [neighbors.apply-policy-config] import-policy-list = ["AS65002-IMPORT-RPKI"] [[rpki-servers]] [rpki-servers.config] address = "210.173.170.254" port = 323 [[policy-definitions]] name = "AS65002-IMPORT-RPKI" [[policy-definitions.statements]] name = "statement1" [policy-definitions.statements.conditions.bgp-conditions] rpki-validation-result = "invalid" [policy-definitions.statements.conditions.actions.route-disposition] reject-route = true ``` The value for **RpkiValidationResult** are defined as below. | Validation Result | Value | |-------------------|-----------------| | Not Found | "not-found" | | Valid | "valid" | | Invalid | "invalid" | With the new configuration, the IMPORT policy rejects the invalid 2.1.0.0/16. ```bash $ gobgp neighbor 10.0.255.2 local Network Next Hop AS_PATH Age Attrs V*> 2.0.0.0/12 10.0.255.1 3215 00:00:21 [{Origin: i}] N*> 192.168.1.0/24 10.0.255.1 65001 00:00:21 [{Origin: i}] ``` ### Detailed Information about validation You can get the detailed information about announced routes. ```bash $ gobgp neighbor 10.0.255.1 adj-in 2.1.0.0/16 validation Target Prefix: 2.1.0.0/16, AS: 65001 This route is invalid reason: as No VRP ASN matches the route origin ASN. Matched VRPs: No Entry Unmatched AS VRPs: Network AS MaxLen 2.0.0.0/12 3215 16 2.1.0.0/16 3215 16 Unmatched Length VRPs: No Entry ``` From this, we can notice that 2.1.0.0/16 (Origin AS: 65001) is invalid due to its origin AS, the origin AS should be 3215. ## Force Re-validation Validation is executed every time bgp update messages arrive. The changes of ROAs doesn't trigger off validation. The following command enables you to validate all the routes. ```bash $ gobgp rpki validate ``` gobgp-1.29/docs/sources/rs-policy.png000066400000000000000000002121321324612745600176200ustar00rootroot00000000000000‰PNG  IHDRG|ÅsRGB®Îé pHYs  šœ@IDATxì]|EW“»ô@Bè!´PzïADªT¥)`TJPT@QQ© " Ÿ(½7é-¡C¨I @z»\.W¾7—\²w¹~»{—d'¿Íîô7ÿÝÛ}3óŸ7<à‡‡‡‡‡‡‡†7qf™ì¥ ”JÓ~ÜP3…Ç‹PS’•ÙK^ilYøˆ¹ÁâÇÇcŠeWÃüþ‘²â0+¯4^ó6mÔÂÂä+©:mØy Â+{©ÞÙu,r••¥Éß Ëfó󮟹p=\ë5ˆÃÄ0¾…›ÿôÕgÒ ËïñêtqVÂÊ‚p%\¼%àñ \<œ†Xpþò€þo`Ph^µy›®?+m¨¶Ã´üðp®Ó–ÿѤeg³ŠÒ¼?Ì¿[Šr^¤KÄ‚ÍC$¿éx©Ãа1®ìoÛñšX­ÓÉèí®XwôÔµ):¿-gê³#äçÇž»pµÉO pì;ÑoÂW)÷öý ÊüÎÆd“ù{û† Ç~bä{†mu£´µ{¿Ö’­b Ë »¸ª[ôîx]¸¿T½èàÉ+†ùuñæÎb~>~[¯|[Í%´1Žocz×Hé+}Aøðëi¯fŸïe¶ª±œ*µÊÃX¸™0\Ôf|(iõâx°åjv%ÞÄe©ÔeM´… æp ¥B¢ë{8VËææAŽ\5yíÉü̆·®GSïýaþÝBÉ¥雫P<?²_«úQœ.êÏÞåEUŒH¹™rÁd¨Ÿ»ªSªùÁÆË±ÿ;Ѷ×àñ)·w¾4¥As•êÿF*Rß°â¹q·²e™rå8+“–H¦PóÃŒãP"©Mß ›j£#1õ Ù# ‹JÈRŽ®ÆØí×{‰ùccIx¿LºqJµ8xì+­zšÌÁEpp”[ÎÄ(ïQ>%Þ`ÌÅQ’_>JQïÆLöQ‹‹â® \þeªAz0n@³î%Ã)Ùy7óù1^OsTŒÔÏ3[246…·{ì+ÍhúféÄUSJVj4„™GW7›d´JW l6`\wQÁÜ bDxpÄÖk±F£Í…–ÇS7ˆðhìï¯JM½%ˆˆXgû”™:EE'©àKŠ›63Æ!PF¨â#œà^¡ÆI&OÌã¹)4±8öÙÓ"‡Ò ¢‘á5l{¿ñ ?½YHýf/°l7ž"W•ðüý²ð帰*¥oŠÒ剜—ËŒÉø8Eó-†Ó64øøžå°hÊJÿƪ&MRU<^¤NÛ0V}ɰˆ=RÁþ/ # Tò“ ïVçÀ=¨®žš—ñB/MtŸÖo–§ïe^E•VB5䪄!JYƇÅkÀ×C:[Å V¨Õ 2«c[{‹ 4yUê”#MÊÃ_LµæT¼šL·2ßõ•¡=/'‚ZUUÀ‡gA~üUÎí Æ®ø»«%æFyˆà“ ýâ¾þí@’©²é @¯Ná[ðh² ôÔÕÂ!Pþ4{©WFä±7ós³ûaëñw ™žÑæõ<ÿZ²z_š)Dôói¼|Þ3‰›èL>›ç,Û]Ì2U€…ð–•“ÌÛtÎ÷3*¼}çªb¥¼ˆ÷q5 úcQÖó1±ˆ¯‰[³uk¬£Ó:Ý“šçf+mÀ HÎkˆ@ëáÔƒ˜3†ÁZ¿B- »ÑBÚÒOö•i_ûeÜ=6K­·à#ÅTê&8uhAï[,¹“l¬À‹?­ MI¬™‰ðßQ%IrÅX:SaÍNþü¹þ þè^wæ¼=‡Né²mì8mÕ®¼È©Åi0¸g8ŽŽ]³é¹Ôhx>{hÿvðæ­[X¬¨¨àð‰ÿ¾1Lƒ<\Ú]©šV+˜¿UëP@<Ò©êb¶B8G^J¶éÇn°F–ôôˆR‘7R©TvÎS(GƽPœÞߦÊ(q§ï^ÆDßKLL¼œ˜øôò͘„wtõ2yÎTˆG𠘬Ž+›C \ кk߈ē;2s³ÒWâïyszVîÊã‘I©=:·\k ˆ’ùTÚwÉ·ãdbfß®­#Œå³%L©ð7ú Šz½ô ñ%[Ê-HK}CšÊ­†i*Ä™ŠåÂíC ïiÔrjN_[ìçÁ&x²­z œžùoªB–¹P©T Pç4'oY›9ûó…i±ÛŠË(¾Jˆ½9þ©ö[•xùiâËËF¿ƒÅÉõ¯àÏ—¥QFgWRv"Š‘~B€3«§§)ü‚‡Sò4tSZŠ‹GÙŠ=䊇kŒ|ãõÑâ+©HÐR,3…H.­Ö»íkºwΗV\T\›& jѾØ_p¶e´¼Xó4Œ5é× Ñ±Ø!7²ØÃìÕ™˜¼{ÌÖÀ•Î!P~hֵϷ K]h®ÅYrþäžÃÿ¤¦iÞµ„¥|©2XøjÓ©ùl½ŠSóæÉxö=5¼²TatÔ€šÆð:_-¨9uì°ªcÇŽÕ#FŒn×¹ÇZꨙz3ö!4,‹óÛ€À P¤” ÐåàA^zŸ¾Ÿ6¢~SŒñd[ œØ‘—õÂveƒ/ |Ÿ¬Qˆu’áyÑ^‰¾’ëw_?KI¡wy}î¶¨í²¦°è °§T)Gú/*|·ýÔ-YözÓl÷“T?S1köÊØžnÔ¢ ­äÝ©k×®Þàá?[/ÊIÂ9P‰=ÿ*®Þ t ÿ¸ØÏ]qp؃@Çiø‰ zÅUüDÃû÷ïï!®P}µL2j»xlXUÖ"âŠT(KÖS¨ªæúTš@ÍGzÎØ›Õÿ¾PX¸>[aAŸ>ýßèÕ«×ø^½ºïÖ£÷ôV­Û¤‰Aã[œUöð)5 ôŠŽ‹ŽŽÖ߉Qɳ&— páâuªŸ»væËFõ£~\¢ ™£Y†×œr¤ijàîÞYÔìĆ]»¶mcTjµ/ÌiKÂkO6òŒâåýW©JŠŸT“lu\¡m«#Õ*‹Ïdnf)¢Çû‹›B£Š<|æKþˆ@ßã"åð'5`a¤"J®§ŠsóK´Áh€¹¯ªÑ Œw'¯Êþ – ­¤ Z0}ñ .Y²h¹d4ÆÒ­K§›ËmÑÍ{'ò‘ ‡ùÐí³æÿ‘±Ç—ߦíEêMÒå7wŽ@(Â\ã®ÿwd*n†;ÙåoŽƒíä²s8‚À†»Â#=zôX§V«õ¸e*áøñÓËÃúW^‡ØÂ©i|p¸ùüýwÑÕÛb~MÔQé /Þ"Ç÷ÑÏ·üŒÇ^ .ȇï ~›¶{teòz¾šù;Fd°‡´8m/¼hÓiûŠT¨yaSGŒ.°]`}ûEjrûì“_þFã2]×û·¯&.A@ðìÖWT$*yñÞ©X£áÿT*UÑŒ™@ࣸyïúßü"Ÿ„'«}ÞgëZÎÙ]ÄGJ–‰ÎÑuã’­'cI™­ú½1—L®­uÖ>“‚îKÞ‡ýÓ)rŒÞ’,ا³¾…ìÈzÏ yö÷,¸ö=ïDDâÀz» ¦Ø*8™RìØ¶ÅµÆÁÕ7lß=áôÕï¸{ç7€RÖ™Ú†@OÁyªß®]þû+»ûÇ,ª¥šEßoúß3#à%!_']Ç×)˜Ë kÕõóe­÷O_¨KOxE6%»v¾¶ •ˆÂ|Íp¬®ãDòµnÝB#q÷\'ÃŽ U±Ð®œ¥ŒFS tìÒ ž‡ÛŠØ^ ¢?Ž1ÌGUI\—ð3Ã4œßFJpcU°ûhäïb‰Ù\%ù!Ÿ²JRkH±o•¢c÷¿Ü‹öåÁñDz˜Öø]#Ï›&%V;b£TV%Œh)kŽ ‚„z xð"M¾mÇÉ[X9JºÐ~/êLÌ¥mßÝoÛ>V¬.6Ö¬P ¢?‰z¼¥dBJmØsýŒÑH'þFœ(Š‘ªqe˜Æ`þ¶o-žÉ—!xéýÀï'ñ~¼éJŸ*¨¥ňø©|3 s)w}φ3yz61\Jí (±Ñ¡J¸ÌŽ `ÄÈ£·Xõ7ªé¨y9å¨$†ø\;9Oo$jÕ2"RVÊ]qpppp8ƒ±ú¯#§oŒv²T\õe2×zç{5šNy,«7™k‡‡‡@@€|«N µö2Ю eêªõ²Ð® Î@€|OkVL¬®?Áƒ cƒBØé;‚¦¯d£% ;‚@Ðø‚0WÈù9™pfyGàiºpa cpr‘Å-ŒÞ2.CÀ9ˆ@ £}/ð… `¡2Þqƒœ£¸0†1hă;ÿ3f ×9OBé©U,ô šÁ{†óÅ>ÃÞ»ýHZ»¿_Ö£#¸0f1Èzp¢m~Nb >*F·X±çbuä¨B½a­5üüêDPßà®Oš MýoiõüÌx?.Œ9 2Ÿž­œ½K *Åßö<$\' ô¬ÝížÇ¨ê øávnv*?þÄÂÆD.Œ9 ®®k7D‘ÿÂüÌ ÷½W©áµù8¾N)n@©=ru«ÑÊìÄØ€XºÁ¦r$ªPwhó:ƒH¥Kx®ëx°ûݰԇû¥ Êßd].‡€Ó éÿSµ€Fƒ3œ.I9?4cðC³›M¦…8g=½ýë÷÷­;híUë³p)é@€ åˆM Ð>ñ'_£® 2@‡ä»;CË|+¹–%¤J…Ü­,5¨´´Eì…²¦Ó)/›ÊrseÙ€@åöÞ¿ÖÿÚ…KÊ!À!PNê>36=¾œ6Ÿkv)D þÈíWPìR«)b¯˜Rˆ{©Ù³b­|x÷Ln©o×€ò„@¢Ø;ˆÖ—]yÏ‘¶„$›ls 8lñpµÎ³ÉçXFàÞ¶1áX¥Õ²9r”>þ"7zAçݳ²¬Gû?h_¸ ¶29—ŒCÀùDDœ¯Ý{É=ç Rþ$¸½¶ýluÕò×rG[¬Éõ l¤p´.¿í(Rî·Æ\Ú…]¶ç6žƒMåHôpÿô`ãbp¡L" V*Ä)“upesЊ@DD`Òí´öi•¯ ¦V)=°y,›y) €òz=Ø=¥YYh ×6•#ŽÍ=qÖ"À²­EŠKç*p„l'Ý Ží$àK{µ!»´ßAN~öàÙìaÍÕD!›Ë])!»ÜÝò²Ð`Ží¤»È²íž#dÛ c9B¶cø•ÛÜ!»ÜÞúÒÛpŽí´{Dz텞#dÛ‹œ£ù8B¶£–Óü!»œÞøÒÜlŽí´»Ç²í…ž#dÛ‹œ+æãÙ®xW8™888B6÷ ”68B¶“îGÈvð¥½ZŽ]Úï '?‡{p„lö°æj¢ŽMŽå®Ž]înyYh0GÈvÒ]äÙvϲí†Î±Œ!Û1üÊmnŽ]no}ém8GÈvÚ½ãÙöBϲíEÎÑ|!ÛQËi~Ž]No|in6GÈvÚÝãÙöBϲíEÎó YJg!ûWë´¹ª´'g¥)7þWKžþ°–R–^U Vz¹ñ”¾<Ê[ü<%ð3å¿$J¼°Bè-ñº›+ô=*©Üä|õžßÜp%e‰²SS’ŸBòYK`ù4¬C}!›$üp H´„ìÁK eF.x•µ°WÀ}Õa¸ŽíÊ!ms†ó÷„z5a ¤{ˆA¾÷3DÆ‚ÛG¿A{"OY ûý]½º |ÏÞ… ÎÀšî:ËjGвãw¿o¯€Æ5àõŠ>JÒý9x•+€ªOtRh´Ê}™ {w-øÿs.aSI¦Ôº¼<¤ÞØP)=î\ˆ"ãY-Ræ/¥P£öÁ‰†/ÌÌÓÒUøj}7\êÁ· s©Ž®ú„üðø ëi¥º!t OÙ9“jÓ]¬­åíHÕ¦v¤´#ªfÊ%=-÷ ;RM±#z(áôÛÖ6u¹ŽT!!û”ù™™v-Û Uf†fÒ”³(BÈŽw Å(öäW5sîï}d/†Ups½$îU¹©[ÏÊ••!r_,u€‰Òë¡»²²üŽ%&öÝ÷ìy§ÿÎ}+ȹüÓc™G?ªµu°Bhs £®FÏ…„ì;XI©TŽD÷O®3øí0,£Hžt{§Oò©Ïgçå¦ š r ’Xz6 ±ÁêãKš5‘c{llèg7n®ˆþ±þKuPóÅ ßøë´ EÑ’ÔBv…˵ü—r¡BŽi§iÚŽBÒP1w+Ê‘¿TAÞ#G–!d÷¯àƒœ#kR[.ÏÆåµ#E`²’]õ¨†ÉË…rTÃâÈ‘Ň²ûûÖ´öªmÉïï˜ÐJ“xécu¾ÛõZSéHÝÚØwh¡÷OŒ­ê?(fð`·U­[3:Âl(éHÝ8Àã÷¶­jy¾Œ"©_ãN/¯j˜Ži¿•²Ÿ·®cvÚi1Y-ìZ S¼Úù(V+¶PYbäú€èUÍVðŸX¿¶exhÜÁÒñuê0¦Šãïæß4o.~2d°ÛÔà ^/N~~ìö†®ãrÒâÙxÎB¶á1á¿ó×ð®Ig–˜ß°^½«ýûIéV4Q•MÁnHx‹hÚTt½I`nÂ'Ñ«ÂV¦Dï·ÄY°©c‰m°ºb¢v)¹±bÊ\Øñ›ŽòsfÞ/‡‡#:¬øq¦lÔý"„7ŸBg+ëbÍB6בҿ#VZÈN˜;¤ü(Gg£¡¢diÚU Ù·ÿÔïå©Å‡'T¯ØãñàÁ’×kÖ¦F‹ôŸ’>?T’‡‡‰#û÷“4æg¾·¹û¾Ø“Kj”LÉLg!Û \ïþÒ~¦[RÔŠCÝ»{¾ß ÐÖ•gVTáp’œ»½1 ¿tp_§„C³ö=;ó]‡ 5S€ ²µ„l3EqQ4#@Ùÿ;ë\æ&ÑSK²¹ŽTÉÛe¥…l-!»dîòÂŽ…ìôø‹’è5-¾òJ»·äBŸ>ß¶h!&‹‚\ÁÕÇÕÞgz÷–~×,¬FfÔÚ=·¶ |… ¹8 ÙfP&K£×¶\\Y9ñîÀ’öfR;?ÊG‘Ö·mëþ}³°Š©—ÞûßR2ÅȳƒÍˆ®V(!d£L®@È®èjØ0!ŽmUÙ,XÈæ:RÆï„„lã…”±PBÈÆ&YAȞҌɦ“…Ïÿysg·Wîàw®‘¯/“ÕÙ]ö„Úµgúô–øe<øòÞÚ– É÷¹´969G:B6#%lîôy°0Ð…¾}¤dˆ¯´¸‰uêç6làýâÒÏÇØ†,-ø0)'!dcù!›I)eëÙ” s—„j.½q\GÊ^äÊo>BÈÆÖ“ÝÌ9F Ù¤tbÞž¹ êWû§Kg ±MäÊ.ÌÏn )‡ ÿ÷g&$BÈF,h}—³©1v£W7_ò"íùˆ³½{»»úc „O›4áýÙ¡d^]·3æøÂZÆÒ8Ʋ£Ç²ãÂT¨«²¹Ž”ù;̲KâãlB6ᦦ^Y½wIxSÿ9¹¶VDL÷êÕKš‘“Úmzý#ËxAì'Ñî8B¶Hï®kÿ~A~ß´7Þ2UUZY©²ªUKiöõÍ[®¬ ¤³t²+¿_Õy–+\x””|„«Oµ5¦Â\s8–Bv‹áô¬õÐÅ\;œwä:ø5ûÎö'47'‹+²¹Ž”¹;TG!»ãg°yøw0ÃrÎKñø¸uø þA9G™“™„l²`àáž)‘KÃÃܦիWê>rä»,9šùxÖ}òGï•de¨9¬í‰ãÙ¨ÝþcHi^ò¸3½{IK㈑AsàÍ~DÓ&^iç¾ÛC[ï€NB¶—^òyÐ¥þ{°Ì$¢5þVWð‚fÞ×7Pg‰í.†|4¶·|òèmø¼¸‚ÿÏÓPyáVØ)‚¨F&  ds)랺Ù^È|‘c‡,ƒ™ÖÕÌnª;ñ ™¸þ ð†Ê~ðÒñÚé'd»[©ç–oÃUÎÊ™ ”Ú™!îBq¸{7·zîüv±›»~á8Öú%p„l Äþ‡êŵ¥;»v‘úˆÉ®eÃ}€+ìZû{Wºc¸ÙQ[ZK3![3¶ ˆ¼ 3*!óm‘ƒé´DYk6–…V…°îM€¼H4æê, „l¡”k¦‚èÖøüýß ƒ¹ö°wðø¯<›ßé^¨ˆZâc€3 Ù\GÊú§ƒ.B6v¢Ôï q®FŽøÆY/ó)Ò@4ãWø¥sC¨^ÅT–jt!›Œ°$ŸølÍÔ:!ç5iâËÑ,e&^Œ#HÇ{ö€|ÀÍßz˜©3S kQlj¢´²É¼¥ìêº_–6oæÖ²BÖÀb«¢ÿuê$qW¤ ½³mxW¶ê´¥²ÝÑ   £÷`–-y™LÛèXPÉ:î™kÆmK !Û׬š ’ó÷`ù¼¿€ÑÕ0ÖÞŸ ÷Áë«°éÍ.à;¼“u›:‹Íu¤¬½«ô§CåÖ¿Ò¤L˜úæ0„þl/1=c„ŸÃjAùÃÀªÕ;Î d?þ«ßÔÚQƒ/ÃÂÊLï_‚¤Ý]:K5éç>:òI=Ûïžñ!»—˜ÃÇ4ôö¨2½ο¿µú¡dcÀ?:tªŸ_Y’™xݪ¯~ ú>&ÙÞR€ÿT–#[|ôkdßG”4±úž‰•ˆ•&BvãšßOÉÑë°jéN`d—µwLGÌÝ ¿ h•Þêm½Õ`g²¹Ž”µwµ8Ý„ìJ¸Úü×é } s¦®…žÅ59çjÄ÷°´V4ýjv¢¬`›ý`÷”fü¬çS÷â̈ÀÁу²éZë¡-¤ mÚ¸ÉïüýKÆóHK†5MD‰áÙFbÔo•éq  ü<—]×-(zW’<ß=Ùá‘: ÙTÄ+zœù$ÏÒaJ‡OÁº³¨ÐtM”3Ýe(mÖºÒ@Ȧ¶¥U]€/Gƒôß‹°~õ!¦Æ±u­ŽøÖul5>xlêÑ:ƒÍu¤l2è"dSk®ŽfçÖMÉXXüÁFhOcózÀW°°¢'tø~h* –8˺[ƒ?Žüœ¤1Žˆ¤“mˆyUœÕ<‹ Rô3˜Ó3zÆ3íï0^{žSÏ|¢¬•6g‰mØž.> ÒÍ'áwB†6ŒgÒŸ“üq?ÂM‚¡îÂ7¬›Ž°[Ù\GÊ>ôé"dÖ^· ÀÏS@r.¾[°U»uaFý¯-…÷pá@ß•“Aêf“Zo­Xô²m2>Ìǫ٤,»Ÿ[µ”(³ÆÑa߯ܲãN}S-?/³ÛG 2òh»ÚƒXYÎ ­/È»»Õ¡å°4²KÀT;àä"p¿üYÊi¸'*cÑOaΙÅàN”4[]i dkSÿÀ'$h$Co"¤hci˜{ýXZ#—Œ±~:‚*Û„l®#EEßúkºÙÆjlŠßûå@rø¬þf{›_r•0ü—i õ“Ì|[„lb[þdæïíÚÙ0n^vW­"•Â×ÍÂEòÛ›¿vE­œq¥Et‡ Ùò;͘Q¿>ŸprÊ‹û 4T$—¥õ#¶.\¹ÍMð¥wh¸½ ËqtqÒ0Qˆ2F”2¢œÙãJ !ÛXÛ ÉÐ~„MÈÑÆÒÐöÊW°Àß:âÆÄ6MGPe`“Íu¤¨È»Öu¤á.F¾Ï?`ýÚ#ÀøðȘ04) ¦¬GÞ“¯§}X°EÈN¹øý[#ƒƒÁžv j_óœ–kr:¾2¯±vvÃ)Ê5!› ‘ç¡’Pš¬ƒ:p¯‹²úãèÑ;õêñÒ£~Rhã„lc"´Å—ÞÎÙ ÙqVÍÚÀi˜(_¨„}‡äkw¢”ÙëJ!ÛX G‘*ÍÝIÚX:ˆ\™ØWÌ94Á&!›ëHÙçé&d“¤kàÍ¿Ÿ€ß·;»7ÆJÖ#öÁž$ÃÇÞI Û±AÈNy|ÜS‘ýbÌgY¹¤D¿­¥ÑG–÷ÏÃö*c|äˆüåšõû€AÕªiÊÓ¨‘îay»n]±"'e0!êéÂl93EÈ6&C&[f‚tÃqøuávú{…Déú•¯]³Á½ ”q¥m¬­!)ºC(Ô˜¹V²´±4Ž„û4ÄN YmdÏtµn¶Ù\GÊþ޹_L²©Ïîz`+àOï ^?îƒÍLL»`hì B¯æàÖÎl²ŽÍ;¬FMMÍr2j¤{Ðê7²–1Ç„èÂl=—kB¶87aô¤:µëÛz3ØL_ÇË êx{AÌþéí©—IB¶1y·Xùx|¿6ý´Ÿ¾^!Q¶ˆÒõ*_hä±L8[ ÙÆ1ÜV‡Ðq?Á„4m,=aÄ. ±OCìÔØ;aO½Ú<²¹Ž”ý)‚=S„lcψN ÕY;=¼‘ÎéabŒØÃÑN÷:¬-[pŒÍ—½1µ^Ýr÷#£Gck‡dÛ½â¹Ü²ý¬ŽP£èŽËÛË«›Q·®‡ ùîH{ÚÏ4!Û˜LhE›ÿåðš¿¶l=ã8ixÅ>¨ü*[Dé"Ê®´² ÛNlµ,{$Õ+Bó׿ZÈÄ ±KCFŒˆ:[„l®#eGŠÜg& ÙÆž£)}@Ô·TþäøõþspØî ±Fì»`Ä>ŽiBöÃ=3›x ø^m+:8ÄEGcPÆø±&'iX^nsà"޶^¦í±›{¶Ã€ªUÙ”ÕŠæ°›¤oÕª¼\yf;Wzx,!0£?Þ¾ïþ ›psR»IÃ[P¹ÂQ£M‹QÙ"J—¥z­/Í„lÃ6›-?N©:ø>3Œ·ÅOìР=š/Ét±OC—cƒÍu¤éHÑu¯m-çãÁ nW‚§¯ƒÕÉ™`÷VÄþ×΋ðëb´Fì‚Ñå˜&d+ãÏ ›^¯n¹áÞ—¦~~PU*ÆíÒÊ0ι%d{(’{ö©RÙá…5 ë¥ô ¶l¡ëUqÙ£¿X¬‰?¾ ¾žŒVxØ"deá0î•pÅÈúÈû¶“†‰R5 •«€Q¶ŒÕaoXi'd¶›ØnY;¤ xmÌ0Œ·ÆOìÏ;4hÆØ¥¡Ó±AÈv^G*øk©ï ¼^:‘Nø¬.ËÑŽ„lÃÆðp¿Ÿˆ7pz¸4ýƒ}ÓÃÄî×f´ÿõÉPðèŠöÀètL²5ùÝúU©b·Rh_[]ç™%ò©^M’Ÿr·³=m)—„lBBÎÊÍëZ‰í•ìRàw1ÔCÜ€ßÏyÖïûT®,È~v¡­­›„lc²áòoqŸpø ¬ŽK±¾WH”©Ñ+à·q] Ò‚×é'—B¶!Þ„4M8B¸­Ë([7û$vgˆýb‡†Ø£¡Û±AÈvZGªu/ày Vµ ðhV0 j0êu¤#E d‹m(<»>Kqzm–µú-|eoÎOÝ™Ø3—Öž8& Ù‰‘ë”*¥oŽž°ê\è™%íî$çet·ƒrIÈ~qeMu7±2Àå£*ý€WɈ"ߨX³©©=7ØRžÎ•Ý=òÓ[ZJgÏ6!Û°~âßø¸µ¬ :}+È‚±4Ô0¢D Xëú7‡êß·m› j9®~M!Û°„<½þ]¾È„iÖnöIìÍ»3_Œ)±Cãtg!Ûy)^ŸæF óÞ@šrFJ7doGŠ”É&!Û° hÁ~| ·r‡.—À§†ñÆüöl‚l¬ÇÃì#d'ßú·m§ÀJù<2|Æ¢sµg¶}@d)dÁiOÎÚ<=S. Ù²„ë!u¼¼5,>3ÚªxÛ›¨²*ð9¡;ˆÒUkježÍ3éÎ d‚GöNÜñ!¸× €-æÀW …aŠb?Qž:|?µ«õ6Lsœ Y\²þUY!dë·ªÀGHÔ¦ƒÄšÍ>‰bofÚéÆà*@¦ ÙNëHI;¯¾á°QÁ}à5ïaìö0foGŠÆ6!Û wœF¾›U…¯}ïÆSýön‚L-Úk& ÙüÜá=*²kõÑŸYw\µV×Û'7ùÚfWèžYîÁ[óàX™Æ.Bv~Îó0?_–‰j!Àkmz×¥¯•M¦7Y(î'—£È­Jo©ì•Fz…>‰Ÿtmò‘ñ^!QššÍ†%õª@³mïƒ;“R—%B¶±»X´ÙgœéÍ>Ét±3CìÍ @»3ÆÊ¡+ŒiB¶³:RЯ›éÑdúÀwOÑÞŽ]÷ÚÑr6ù $i¸ñ‡0?À:ï› ¢L1éÊ!ÛVÚÍ>'lö¹p„QÓNGü> ·!!öf¨qL\3MÈvNG ¹‰ ¸‰yJ |Bà9§èHGÊ„l `E—þ¸¾õWäϽH‡wІ׫ExAìyý~jÊÆ&ÈX“„ì¼ü¼šìn¤îšÏ,¹¿M}}$|yªÍ³#¥‘M éŒZ)b¯€-N*o²N™EÇ{¥©^mšƒkAýˆª ù¯¯s¸8ô¨VdÄÙ4'ëlB¶˜èñÁˆS‹@š)ƒÑ-çÀX]|“÷a¦ý̉„ªœêÐ|.‹„lcé6ûæ0gt+Þ‡ Íx4 ñ_<Ûäxµ—'›Ê™‹­Må䀿ð%М¨'7¯s¸"‘:/7™* ž\Æ<®@È6”+Ë3_€$> Þéø¼Úòc—%‡Qÿ¡ÒD”§ò☠dÃN·Ùç$]ÿ¸j£‘ÇuBu²ýˆ±ôN³ƒí”ŽT?nâ£HÐ\; š*‚Îá)ÚÓ‘"R;“MEMw]mmý‚ÜÃká«6A[:6AÖ•MïÙvBvnòu‰ÇSóY$có\ø™Õ~ëÕ*o[ï‹„ì›XG:DG!<™ºÓC2ºQ#bÙùÕ‹+‚òDÞUÿjöæŽ*­§'b˜E§Ñ(=}Å,¾¿ûá’\ªT)Q IÅ€S§A3¦Qq\!—@}è~ì9‘H•›j“ú`!Ûª{ãh«Ñ¢3\\îæÃ§ù*Ð\]¢4±å!;2²ÙªÏX=é ¨{ù!¸ ëiÆâé #›}~¡)N¯ýÕµ¨È¶#t–OÊš¶z¤f!ìñ4íÁÇÕ䚯R€§²^yäeD~jä¬;ŠýžQ’Ÿ½ÏÏ/H§.N¯ÖåScqå³X™SÕŽ4^s}n¢æÐQWš/€×®8Ž×ß¿»7`{N"ªSbO„ËR z¡Al5xG¥’?7àýAú5_£!qJ~_ÃW榇£”Éx¤à¡»7Åø„…l?5‘Kî­Ò2r­=ðÞ*Uô)Þõi¹G¦¯…þž ^ö¼ˆ]/:œR ¼Ç/¡ãðï Ëã‘¶6kxå!êžÙ Â[Ì¿ñ€ £³å!›7ZÛ{`«J›ê!¼Šô8u „†sðEÇnA¾PI—A…4T ¯ƒN—’•3s¡*~°6üãëŸÚkô#Ñ6ùe¦V)Ó†a89+ Ï$ñ“£þã‰Ä^ÏÉ'ý'(ˆçóˆz¤-ÃðëCzÞø5J܌ՎTÇîúÜļ{ ¾Tðãи ÐŽ¼w ]UÂSÜšçºæÏn>È_Ü™!Oº‹wš|‹µ˜ì´äë]€§ïâxµR^Óe€4†`¯;(éÁ7G¾OS °ð^“û¬­C{ÏIž…³Óã°¡D[ö" GœQm# Žü.&€Óü7<³(?µ=¸ˆ$96¹HÁ!x}f/T ÄعÔF1§ó‚ç¶à™Å24*EŽ¿§\Ñ™ŽvXU†‹?³ØùµF¥UŽìžÒ,/óykÚU¸Â²;¦-¸‡–3å•8¼Õ ©z¯§â‡Ç*2rÔ‰x0ጇÉGZ­Qæ…Ê“î|‰×æ•#>?++ŸtYp!½õùX%oÈ Ä üU¢õœ–K°[ûFÐ gГ¯ä+äq]å©÷­zh´¢ôxÝÕ‚hÂç)àùÏeÓép5Y•Ç/,”beôáëãVB.r^Æ¥É@‚Ëv×F¾QûúV`&Y~îÆÃä“·`;ZË5úƒ)$d“Ö˜TÍTAK”5¯­i)QN}½Š¼¶õJDÙ°ì_P\¼1¿Nƒñ¸Õ¤·VÁ˜ßq_/Ýø®]¥êgÚþ!lÑ)öBöÐå Åçç\q¨É«¡Aa’¯o¼h2…AÄ£•G³×‘ÂÓËÀ¶‘[}¬_úé·Êð5.HÍœWÈç«ýêõŸSwàO7l©åٽݚøÝï<Æ·æ>Fƒ=‚šz5xý÷ F 1ûßÒêêH;gɹú3›O4ÕÂçN–t¯I~N Å´è4QV«YLXœ€(E†ŽŒEâ‡‡×Æ“_ðo…žhwd^¯"¥T9¾Rx7¯UZþiÍC¤á ²²YRŽxƒÚQDÕ]"D%£¼'äô­šƒìueÊ|~Õö³¾©Üj*ù¨[í.zT‹y€ÌºÜ¿€¬0ù¹¬7f5«å¸2}.QÊ}£#LY>¡2³á½¾_Šӟƒ{X0 ±ßùx 5Œú>~þ+,2VR!!ûÆ9M9z¾BãCêwŸÁÛØ{UŠ…ÆãµÉTØšƒè$ Öo¡™ùŽa%Ù󙺋ÚĶ ÓŽBȶF9J{Û6ŽÈc³#%ÅQ£¢Qd r†(J¯ °¨‘Ž”@êkó³MÙ¨™SŒH«P1òdzÕ#Ø(lºÄMd5ËwAö[=aÜkmá%ÇpõÚW“VÂ'›gͧĦª‹²£0Àœrt¸ZçÙµŠ2Yq!õ É~©T1Ž¡V”RðÌ’>O í̆M:¹ å&‡Ewsû躲¸Sg1a†ÅÄ ¨¿ ¢åâA8H«ðøç|üǤ#å“ Rñ57犯Ô¶¥mË™·gZ£ÁäAJ+ÊòšP—Ú-;^·¾–јB¦T Ý+5ɲ¥HW#d_EÜb÷ ‡÷ÐÈcQ¯öïáŽ}Òe!È$ØÒBãi%"ˆIÌ€þÇoªJ®éL²g®‡Î؉‰øÚßCÂÿu ”†ÔO‡ÂøVuŠ9V{æÂW¹ðßÌ_A–oQmvHÛ3ÛAÈf³#ƒºhÇãmj˜GcVm‘ޔĻ*Ž¡Úæ\‰}> bȆµ‡‰“zÀS]K~Ÿ»}`Õ[+A–fÓÛPWgÛ Ù’ †9r•’•Õ¢¥á™%Êvrl~fí d“î yrˆ"¶žxTÇãk<´£L+GDëû&x´ö¬Ôö¼pgªÆ†ÁæÒÝçþÕ´4¢\1ë:öÑç Q½e[Š è?®åjË¢å{™¼z’“8T.÷«ÑA«Y[[—„lk‹¶9QzˆòÓ>>!ÊaçÁq\zþUÇy šlk›ßÃbP¹HžÇËéʲ¯<† "$ã ¹žª±fé…í¾„äÕCùÞ@x•Q²´@Ïí@cœÏR rö&ÈU“þƒŽi Ùìu¤›Ø®¶RšÛ‡@MÞzï‹í ÑëÇž"ޱäìéHÑœm![ϵ€n!4僣˺8rÞölB»iâô°,›á/S²½‘ÁœÐ6u¥ã™ÍDåHÍ’¦ÝgXÁ<Èèç.¦r÷–À3•<ÐFÊ(/±ÂU-dþ?h‚3è•𨆠yyÚåŽßͲ3¡;ŒÖŒNÃz¸ú÷ཻOánÄv¨ÕNùÛUÅLL[Èf­#U‚›ˆæ>~ß ò~Ð{_ìõ…8}\u²}ÄI¿«|öv¤¬*œ…D÷žÌür;5„÷ ƒ›¦ªüg6ü„¿ñ½ÓÖ‚Ln÷/ÅTéÅáLZÈör“$ÞÉpàEW,¦é«RðÌáo¦e¨rø’Û¦b<Æ Ù˰¤¿ðÈ3^bÁ’DSq.î[³]LLvaý0çüû–àhþ;a¢¾» ¹§ÿ óÚô3‘–ÞàGYY¸àÖÝ`èÊr®`!›(9DÙ!JQ~,I}e)lö‘–Π7æ±Æâ’‘G“-à ˉ|k Œ*Ž)¸rU ÙÇn¤ª~°eâö)v©+ð)™ÿä¢R4ñížðİíTEoP®š ÓÎ߃¸å;M¿,¨yì¹fÚB6k)Cnb¾´ñFPÙgØ!­‰{3†IHo#)g[ÈŽ{ 0y5È›…À'ߌ-Ù£7Djï'ðeº NÍZ8XfKŸI ÙB¡ûãûØñeÒ•àÓºà3KÚ=#=WäðØV,J£…ljí²MˆÇùjP0úð„‡ê÷æ”°Xb¢¨-šcE4™‚0jÀ#ƒs »ýÏr”ÒJ%¦¢,Uël ÙD¹A%‡()[ˆÒcI^]üïàgöôød2“ú½.µñ³ŸDzÃíÏáÍÈûúDsW´S`Áh.! W½ÄãºñV™½‹Y6‚¬o3˜úá ¸o>uA,!i/{&î¿ ‰k96•gª> !ÛTj8!dÛ4¼ÎJG Gáyu+PåÍ…Cz~=OêQмÐÿbóÕÓK„ÇÞŽ‘Å™²ñµ;égÈ­[¯|LõPK@ö÷Gð ŽL_³rñA»cÊB64›/¹s/+‹ÁyµÒñÌ,dfjÜýêÄ’k[œ²-Ï숌~õ™²ÞúÁ–}b‰÷¹ã‰‰ýë1µ9ßñ@er’Lj|—Ö‚jÌZ#Ì¡¡6øïE¢°BÇqçm­EKÈŽÞ†ùþ¶×ïƒß¡ÖwãL/ϵ¿D4Qjº/šÀ#ÊN‰n~_†¾Þ¯| ]ᦵb©‹5*ÀU´‰Ò¤‚'œ·FÜú^»:ÓB­ìEBöoŸÎ$Z°žP3þLL‡&A¾píY*´´ETáu ïØÞ_4Ü6劵 i{ñvØê# #;—Xn‹(Ž¥%„ìœIúÄ %’ŽTÊ™oHGJÊØ»i[êwŒRØLH'õ‡ãMÄ1loGŠHTHÈþ/qrˤ Â)Û*³Ÿ®5™ËLD*Òcqš,ÈVn|ö˜IZ"ŠLÿùÌ|c9lÀéáú‹FÐoà´D¥%!;ÞæÉ=‘_ð•Ïî^ؤ‰W‰"i (ÏlJ^ÄËrÄ :Ï»gk³ Ùw0ÎQÐã˜æQ¥=Ü?=˜`íµBtò`B‚“+ÖÖâÚén¥§£ùa†­KøI«œEÈV S¬ß—‹CÞ'n~«]`3ÈÄ8zÔRøäQD½ñ=ØÌ[ìÑ¢Rs ù¤î°.ú¼yýQñKÓÕÙ;/AÄì>g0lECŠá-Bàš-€=Ix{ä†×‚O¿[’ìnMY„´= ÉÛ„ÄMÈÜÖä±6 Ó„l"GaGÊZ‘Êdº¢ŽTÃWmîH@œAÈÆÑR Äj4ºeëû¦me™»adzøç·aÊÙ»ðäÛ]ösõŒÕÁ!›ÔU¹íŒ+×ÓRÝm~¹´‡xñ¼Ý½nH<ýi}ïØ ›Ê‘]„lÒ°Àfÿ;’ blBÙ^ôXÌ·-.N n~fÆïYÆŠªÈâ 4ø—“—#—ÀgŽìãëêÓ_ÃÌK zÒjȳ…4Œ£'pŸÐÛDŽ]oèÄw5Böº#ðf%_Ø¥BƸ´>pfÿ’+tt²žqó²¬-i/Y=ŽÆÛâ'ämBâ&dnBê¶%¯¹´L²IÝ\G À‘Ž”¹ûÇT!ROY­]R½‹Øßr¤ž†Õ wÉœބĵ‡é›f’MV{»Ic.$;¸<×à\ ïÑ„DyŽ[Å#öˆb!Ûb5l*G…1• °é°47Ïkÿ>-µ µL5ͪpÒ\ÿð‘B\½“Yk⦠c›M”´|-z wO ï寔lֆ׬ÊŸÀäÄĽ¿Ñú^!Qʼ¥µé84ÛÖÞy ãï%ìùäJ„ì ÷Á §Ò†¼Ý6m; áh†à†·²bç…(F¸/ÝêM3`§5y,¥!$nBæ^€¤nBî¦Ã1MÈ&2r)\âî`GŠMB6±¯õ.ÚÙÊʃ»çÂ:ž3´(Ÿ…ÓÃãþüÒÐÆ-\& Ù¤Ír· ûþˆ‰E#ÉåÓ‘ý£¶ÇÅOÞ'ìA \²u@)|CþXûà¡ÍÆ¡tùKóùôË—¨ ðRk÷_Ž&Ñlwl²gl€¼£7 vÿ\˜F”Û%6ž#¬6Èÿ˜·œ‚Ä…Û­ïVñƒ¨û‰Ð§š")üÚðåð:©Á•ÙËvÁ~žp’XÆM0Ã+xY7¥Fì»é©þ v_Œ#g_(!s÷AR7’»s ÉÛQÇ4!›ÈÇu¤ëH Ù"d»ZhÇ(7!.ÿý¡v3bR=-ŽLÏ|§‡BÖ¾+ŽwΘ$d“û‡úw[\,OÁ¸½#Zॽ#‰‰Àˆãƒ»~j`ûºª˜ d³9rTHȶ®±†©jöYqôjZ*D3m°bð{7Z–ïQe£½¢°i!›(-¾“ˆ2c¯Ì¦òõ ƒ¬Ÿß‚q+öAÚû¬ë¶©QəМ”9¦ ¬½ý&>zéø–¦d´%œ²;¸|yôÖdqÜ6WëXäi§#Ö€ ‡åöü3Çv²»5rRw‡ðþ»ë —½YsvXÈÖÉÆu¤ìïH Ù°MF—lù½x¸óÇ{ð!Tëî]çá q\w»äÈ9q“®RÍ•c»…l]iUZOOtKîï}fޝK]öο>|(“K«l±·evXȶX›Ê‘Ý„lÒ 2úÁ÷¬¾nÁõ ÛBµˆ« ˆq0\©§®ùÊŠmöVÌ!{Å^P­Ø©+QyéÞ¸x› {å6•oDGHýb¼9odþ~ÒòKuñpWž5±÷'ýaÜÃ%ò·ßø^sBö¦ÿàuœF»õn?xŒJœWø5œØÝüJ32[ŒÈÈödSXÑŽû±o óÐöL.!}ÛëØ dÙ¸Ž”ý)‚„쥅› ¯œ Ó ‘šÔË„›Ò â··æÿ 2ä,Úí˜$dë„Rø„ü¶ìÎÝr7;’› Ÿ'ðª¶{o¯ W8³©ÙMÈÖU¥×âÍûŸ?×åÅ-ºq#WàUíï 0\ïºn#*) ¶CæÃa,Q^˜–tFH| Œ{w=äà*/³®2Níy¡ò+Xš‘„ÃÛÃÚ›O`Ò—Cá.zÓÍff>’‡2wmHU«C(š+xBˆ¥¦ª&v\ÈvÏ’!’lÿa*ádÇòZ•`É$\ GÈßö86ÙD.®#eGÊžûjkbGë0n‚Œvµ´› ÛšßÖô¸ú3ºgLÿð7ݰkÒ€IB¶®=µoÚ™KhåÉ}yëVžØ£âöŠ Úm ³Ü²u Ù—MèUeݬ+‘6í-¦Ë_ÚΗSRˆF­¨ÒëK‡¸$L²ÿ½š¿B·¨¬¥…-œ½q¸å¤1?‚ 9Nf®»zëIr„«¹îxºÁƒ>ßÀÛ˜ÉÃlF†#QÑ©-qƒ¤ÏGL£ÝŒƒp_äE™«-_ç݇»8ñÓ¦ê&do4¨i÷fŸl²u²s)¶Ÿ™$d“M·ž*¹ ²íRÚ–cñH¸ŠDíìfšMZC–°+}C¾ŸýF¹1[óR.‡küZM_kÛÕO]® Ù:(jÙòË™ä”̽ññº 2yVã¤üØsçeÐp‘­›õÂ$!ûÈu€7Ù¤ž0i*+†u3í'Ód¯·iƒ¿ÁUUflA7©Q/ yGD¦!í`ÍÓ˜ˆ—™–Ñ\ù¸/ÔØvõa. Ê^­‚iåè2ñbW½ Ó˜œŽÐÉcxÖmöù6.½¶u³O6Ù:y¹Ž” ÛÏL²u› ;ZÆ6A¶]RÛr|?Î6© óÉôp¼«æ™&dëZR{Àš]×Ò2²'$è‚ÊôyÎÕ«r±GÀÿ‚šO°ñŽèÃR® Ù:(ȇžW¥íÜ)/ÉʲѬÕ÷¨^æ«6³×áyX¦Ù¸ A¥dX[˜F”Ý=bû¼q&\ëÝ>è½ä8òbÔ}<®eÉ¡a\JÕçõÓà¦Pdüz˜Ñ ìöÀ‘#ïΡP4î…Æð»56®»-p{¯GÃ$²Ý;"–¬…lö©TÃ>²ÙgÚ¨aÄ9@ÈÖÉÃu¤tHØvf‚mÍ&ȶIi_êµSàr —NX‰›ZÓ>¡n?![×í÷­j«¹ÏŸ/Óß7ÒÞóII°ãÉÓ¼J}¾ýN×~{ÏåšM­á°ÍçrEGÆŸ;o’—AM_Ú®¯¥¦ÂÜkWóÜû€Ù™ d“¹û>_‚¼O¼ÿÛ óäa:Ú`© ü`Ÿk]>ëºätºpH=—»ÇFü te…T‚ßñú]<º0–Ïs€{.?,½BUHýc:C‰ua[Oƒ’Øm!ö[ˆ–å,Qnö¹-ŸŸžµÁúÍ>Ù"dë„å:R:$l;ÓMÈ&v²ˆ½,b7ËÒ&ȶIj_êÍ3áß@/XC¶*I·’þÌ![ך†Ãþ<+ç»_^rû6î1P6™™xáb/ ÑBGgF˜B¨T²© T¼qÞÁ/“×?|HûPj=l_gåçà“ÿåòÜuî¶ë·¦¾\- JH[TFvÌ»¶(°¦[Ó]GC«Â’Ž ·.á毈›Ð^~\°¤ŸÄÝ]dàc<Æ?ˎÊøpRWï‰[Ž+éJð®<õ*´×Bì¶8c:B'Ÿá7ûœ‹«×´›}b\ ¼ Ó³EȦÖËu¤¨h°ã›hËHFìeY» 2Rnû6Jİ•L£ýA‹Ï.„lj»}ÚÌútùÝhù¸¥FYt³®\É{¡ä]mÀiªp$Œê)GÁaÃâQÐØkaI6›ª!¤ðCóašø‰äf')Ø$dSÁu¤¨hX¾¦‹à Ç'tƒ¡ön‚lYRÇSìû5­ ‹:†Âms¥±EȦÊ@F=•’ [‡œ:C¶×( îAf&ŒC>•[Hw*„ö§À!Ûâp"7¤Í¬•@¬ð¦³LRÖ­-_Q¾¸ùÍ•þý„ ||è.ž•òÆž;—¿56NYÌîÖtÛ4"„ìäè]bP)þ¶Ð?´Ôê»q®5BaAf›¢ÝGÂn4¶øÙJ¤åGÐ-2p Bñø ÂGbã-´ë!CÀŸUñ‚ú?¼Aj‚|ôpâÈèèŒUh( ãŽìi×iôLʰb­ˆ~›œIµéêÎÿÞl¯yvvõé^½$Müüè,šõ²º9’#K~­Æ›ÇÇJ}*Ѳ˜®W×µ¢ÈˆÿýæÌ5-Mf†²9ð\{ôœ¤Ý×Ì]Ãkóq|ƒ¬yópÅCÌúV«ÚûJ:ììÜY"à³=Àíøz™É†ÈøÜÊó+6™Ýhô΃z‘z"W·­ÌN܈ÅÄ:XTQv6‘vÈBv‘ÄF.p jŸGµ6ӛ஋¥lgc2•6ñüùžç/âN:Û‚nňÀÅ!ÛÈm(Aþpõ¿ÛSk Ùdz(+¯³Ðˆ¬c+H]é ¨KÙŽC˜Ô î”Uň´•mB6©“êHO\S±Ñ§mRÞ-ÅÛ‘ŽÔÙ¤de•×ÿœH·bDð¢›M½¥õšMB6#771T¹oæ¹ÔŒ»ƒOR"sitqÙ¸'Ò¾} ö«¿ˆnň)<ØT޶m„ºÃ·ô¬Újz¯cÇå§J ‰M¦T‚72w&$Ý®òÚ¦¡~Õ”æÚÈÅ9Ž@p%ˆBqsRÒß³´Kè‰RDÜ"<àÁôhêL¬c+zfp$@8n<«7¥†iÊ”s!Û@®#eˆç·¶ ÙT™Èw¡Æ›'GŸx™œ²s§RYʦØîãTZ»C‡r}C?»ŸŒÔÓî8B¶Húç¨[.“žü/·ÝÈ'£2®êÈži÷î•­Œy~®ÆèCoúVkØY: Ù®Š§µr hQi9ÊQ!![g!› ó’{0ÄÚ²ìHGꚎÇ7º¼>"xDÙ)YX¶•#g²uXëÎ\GJ‡„ñ3]„l㥗ÎP¶ Ù†(‘ºÎöÉâ‰Ïô>~<']Gœ4Lèb~²X Gjå¹^µ?¯?îˆÝËZjGȶ„Æ×úÛå g¿òãØèžÇŽçÄË\k§¢°ýþ葺̓¹)µ"L¿>“é#: ÙVÜ—NòéëðW¦ÚA…„l*ù„Œ-d°“±l2…GÌh!dm i9rhŠ«ÑÊôÈ‘³Ù:¬©g®#EECÿš.B¶~©¥Ûç B¶!bDA ~ʵØÛpÏž\²½”+»££U]ÍÑTj9³áøƒŒŒéÚÏ!›ÍiµÌðñÿÕ5†ÉsåVS_O¼üz¤\ô{ƒÝ{äßÞ¹£t…¹Z²¢®õÁC²÷¯Þˆñ óZãqv2‰ƒ®l¦,dëÊ/mgOˆúß9haDî½FÙƒŒÄ9$ˆQÏ¥Ô‚ª~!þ-DBxÙ!2©qåúš Ù–ðã:RÆbÂB¶ñšÊZ¨ã²-!â.õÔ4˜ve¾¬BÓpusÎO÷î¹Ä·*71=ðÚ§d·îÆx·˜6°áÈm'©ñL\s²m@•™«,DZsÿ¾ºî®Ýò“™ÊÕG|µfçKí&¨lŽ1JÈ6õw™ó4ôÝÛ£_z‡~8årÔƒ»dÅÄû²‘1cÏž“7سG¾íeΖ žËº7tQÜLÉË…3@ûú•’ Í ÙºŠwáåé¯  áLHÞsðXb¬,Üó­~ÿ²=¥FÚí „lcøs)c¨paTœIȦÊA½®ÙéÃgµ&_í¹Gð<\ˆ”9·Ó"v„œáö={áû÷gzón´¤ñˆ¡ ¦\úBKç`IŽíШiýJf¥6Sg\½UñÿSŒ;w.÷ìË—@§­—8¤¸âî]uÃ={³Û:œ¶;]ùkõþ«:„N¾¸4°é0§XªäÙúÚg¹‡Û‹Tñ34Ä![—ˆ(®ŸãA'÷è,/Â7Òs„­RCvõʾrä*„l½@ñp©08B6å¡(¼t6!»¤D!d©?Ùœ¼ê]v¥åoh¶ÿ€lè§rÉþœL;n—ðÎÆ„îÙ“óæù‹ObÜkÎk0ãîà^_=`ºnÃò™ d3½l™Úo7ŸàQá“O£:ë:1ê·Š/£6¾ê&K™›ŸW¹m@€|PÕ*žÍüýyõ½½¡’DbQ4òp<ÈÊ‚ûx~žwàyB~Rž\ u÷=© l²5dà¯çÉÃë î¢GµˆàY4 ùꢼ–ï r@¿±°Z"‚; Ú%ü†C¿äwA,澇ÇaªÑe=?ã±M ;7­ Õb¸õ tÒ…•Õ³MF q׉Ša£ýj÷^B¦"âîþ=®-?ñÊ{rENã¡5j¨&ש#i[±"Ðeˆt¤p[½öá#ÙóÜÜ|ð¨ôGP§y¿Ñi9Øàp£MʼnûSÅ»#ϱ‘¬ ²}fø3F=lˆyF”œ6ÞEþÙÀû@Ùw  d󘊲yŒâž ÈvÔÎ í¨åœMÇÙÎZ°˜¦g€lþ†¯TÒˆ7º‚±ÈÕù¥ „’SÚÝV¯@è/ám ²]‡R(cœ9JÔA5ÈþÏ€ùw o wß0@¶¥Å ÛÒ&žâÎe5Èöô·©ˆÕoíX8*'"ïE2{DÎ Kô)2½‡BfBQ®,ò¾L<5Œ²=µe=¿^ -ï6f€ly·lµc€lþ¦Édûaì>äVüR¡?åÉÒ‰ä¦DÎ$µÈ™Z¹§%µCȹA襛²­X‰Š ÛÒì mi9…0@¶œZ£éÂÙü•È&ŒBgŽ(#Z‚£ƒdÍghÖˆf• ÈöȈ7"!ȶjªXu`)šÕcäf 0@¶¥Á ÛÒ&r a€l9µFÒ…²í6í,£¥®Rv% ÌÅËSÈœžx_y%²2âH²­˜‹²­Fú`ÈvÔÆ í¨åœMÇÙÎZ°˜¦g€lþ†Ïd§b,ÍöÐÒšÜe–†LÞ¯?¡$š5¢çlz°CJŒoŒLËjÀÙdb€l£¸'ˆ²-íÌÙ–6ñôÈöôfõ³j@6ɈYZ#ùYÈÏ"?Üy>²"?IWS„{’ d{Rk¯º0@¶¼Û›²åÝ>²Õ޲ù›ÆMb@Ù$O3N3ç ÏË{Æ‹]ÊÇ‘$dÛµp³ ÛÒà mi9…0@¶œZ£éÂÙüeÈ&£Èt¤G=$ÂUCÞ PžÄòñFôÀÙd^b€l^³HÈÙ–6f€lK›È)„²åÔEHÈÔX„¢c?hð"”£àqä‘B \Ád[±d[1ŒôÁ í¨ ÛQË9›Ž²µ`1MÏÙü oÈæÄ,­‘¤ÑÈC‘{ WD¶G•ònr‚ ÍYÂìÊÙfqß#d[Úš²-mâé! íé-ÌêgÕf€l’ÊŽòÛ‘O!îèCd{TodOØÓâ ÛÓZ´øÔ‡²åÝÖ -ïö‘­v Íß4f€l:ˆÜYM6H…qo#OÍ“¡mü½‘Ëå=[»XR#!ȶf*^X`€lKË3@¶¥MäÂÙrj"¤ dó7– ›„h{ý%dòCd‹úaääyBñJÇŠ|÷líb1sÄÙÖL mÕ4ÒF0@¶¥} ÛÒ&r a€l9µFÒ…²E5–½¥5æFN¹Y#.óéxó2r.ÀìêÏÕ‘ ÀO oŠ‚7 ]Ðn|b€lGÍÙŽZÎÙt í¬‹izÈæox@6 ÚewA™ äHØ„àýbd8ñQs <†L»âò‰²óMQð†² ÚÃO milȶ´‰§‡0@¶§·0«ŸU ð²I–fŽZ!Ó Ñàçk¾ £i_EŽà‰·ÀñÈxtd{tóztå [ÞÍËÙònÙjÇÙüMÃÈ&Á8dšªMfÔŸiÙlY8÷Hi—"OàL®x#Šc€l ±[YX€²-›²-m"§È–Sk!] ›¿±xÙœ µ¥µ÷Q€f‡r8Ažë4 „\Â$Žfhi`µß$ÌxËÙæÉf€ì|S¸÷†²-íÍÙ–6‘SdË©5Š. -º±ø@ÙO`.„Zb'·{¿ ù9š…¢pÚÕV€ »€9þ{`€ìÿláæ;ÈvÔà í¨åœMÇÙÎZ°˜¦g€lþ†·È&a¾™#Z*û9“ìídŽš'goÄÙV,ÉÙV #}0d[Ú˜²-mâé! íé-ÌêgÕVÙ$Ù¹= •EîŽ<›P4ÊüŠLŽ"‰xñF£ŠÏ_È.>míi5e€ly·(dË»}d«dó7@6'¼oÚä=ŒÇ+mÓOÌ{rù …F!#[9b€l!¦d2î´d[Z›²-m"§È–Sk!] ›¿±l²)·´Fƒ›ÁÈtDˆº…¿#Œ„| Ù‚ ÛÂ$\ds–pó•²- ÎÙ–6‘SdË©5Š. íPcq ì×1õzä»äò%¦!ìѿȾô Íg c€l+†‘>˜²µ1d;j9gÓ1@¶³,¦é ›¿ám²)ÁIdÂA¦íûŽÐuLtÙÛZbȶbȶbéƒ ÛÒÆ miOQº±‚AIÑ»^Äòº±LÑE=º½ß7áôo•3¯VÖ§'"7'@^¸´b4 ˆdœHôVú$ªË\*ÝøFXÃá1_Þ™Ñ °n³²ý­âˆrQؤ"ŸwB©lLÛ™ÎV£¼Š-q€lõö©eü…õÚµº¶/Z8¬¯(»F©ÈV €,;eûêu™´‘C¶”•¥ƒ‡§G$Þ:PE—t·²AŸjE·‚ðL‹àï[n"¼S•š€{>¡‘7ÂêôºZ©Mšl+…Š [Ÿ›èJÝ98r¥Þ.Ë+öø’ðøóëšëÓ¶ÍÑ%·ÌÑ¥û—Ï,QCTõi­J€ã Pk‚ ';²u)•jÈLËJŒ;£‹;¹Tu÷ð,¥Ú7ì(µÛý"j,ßá›Ór, ûaBüˆ·ðAhnLJ +Ív ÊdÇaeùþñ½0¼4òßNCƒi«#oD~™@Ú(ÍwÜH9OxHL¯žÓ¡Öå@Q¬Oqè+¨]½ñuÚqi‹î5„0[ž—Ȧ¾‚>vŠ Ñ`èζ µÒbO4}F]zBmo¥6×?´jvéJÍUZÿR•O€BéJ•²3“ñ7.ôÙé9É.d&ß?—{í÷MÚÞšoMÐa¥&hgèÝ–i::VNF @ö‰iŠìàHç­¸!ƒÒßÝ“;êÓô×g¥F•¨ØB_º~ÿRUž‚€ðê P(ìèIçnÑ1èuiwcwݸk;£b¯ý3ììüÆ•_Øï•Û­©ÒaÊU’)LÊdO$µmÒÙƒáJà„çS ›êË78z Ã颊–h‚iÏ"޼ùäe bTîñ— Ê3wŒ;… ŽRØF)n}Ù›Ù88¢™S[ó~£/«Kƶµ¸<@öqÔÛÖàhk¹6*Ë¡n7w}Y1ñòß/åd<ìåãª*SõiuéªOûDTi*M ©h¯§võ#A¢Ô‡×Cb¯ïz.æÊÖÖ1gyß?ºøºÂ7le¹æc·„EuJy,UxóÙÔ·&¹J »¿–®*ˆòi66.Ô:c˸2_¡yÅZûï#³Sï÷ «Ð<§jÃ×üË=Ѽ¼]ûÛ”üà2Ü8¹"ûú‰Ùx|émŸðßÖ콚~ …qƒrtkí(òVWhòÝ@ÕWŽnüöòºaÓâŽ~êå­®P¯ýDmåúý //ׄ9R¾e£:ûe¥?„ó{§÷¸v`F÷û'–m/ÕdÄ”R ÅKR°›3•Æzü•àÙü±„f¦§*NÿÜ©ç­mïí,Õ¹[×qç|wý^Ò‘y5*Ôî ß<é׬ûÜÊÙ ×~<9·ÉÂ[{§—5—cÏî·€ Ù4ãCÈÈ~r+dGˆGûóžÃ+ ŒGæ=» ÈXqdG ”uZŒõN›Ð£3«‡ìØc‹Jœœ×|fZÌ‘E;Mê6î¼o•†$™72.ÙAƒg§¨»¿èS¥îË£wõϙŦ=ŠvçÄ‹º¹säÎÁ‘¹]%}Ž92/âÂ’V¨½r?i?x«ÃNÓ|4þ%$-ÓZæˆa‚25ž‡.ãNûF6z­Åƒcó7Ÿúù¹>Öä]Îíîîÿþïš­Þ®þìȾ®^s¨†…n{ƒÚO}¨znÔA­wNÆ'ç6›pq³=ð·E>bòÙör€lû’ aÅC6Í}‡Ì/i樲7²â;Oíf@!‡q1Ùœ%,®nñÍú »Ù*m “d™q‘“‡ìÄèÃÚ“ó[|©O‰ÚqØN¿Ï}©Vªó±Ó…jå@ÜÐÔaè6ßúÏN®rñŸgWtíì…˜‡lV>½°ÝÝÃ+3Û½²Þ¿Æ“o(iÖFnäZžuÈ·Lµö­on}wÓÝ}ß•‘RGæ![°u« $áæ›¤  ô-äú&aBnM—ÔLå¿À‡÷‘ÕÈjÿç^üà.P#U¢-ÿº|纭YËl%]YW²ûÍ€FÊÞpð·ý ëé¹¾†§Ô}Œ[ç­šÒMçžÑûo­ßÁpòMTÝJ¼y°5¼‘©Í}øîóp³o+°éû«Ñ»°wì"hkšXn÷ÛNAHƒwààG« ¡5ÝLþAå[\ZñÌ$Gó±–Ž²Í,sneg ÙéÛÙæ[gŒÌª•ë½ìUû©bÍ^œÃeS§®dW+ñ>*P ™ëgn2ž=f^B~z"<»ý4|ã«ÉßqfO§á(°ùº A1KkÖðF¦ÙÀ‡kþãì 45jÈ>t¦ŸϘf —ûU{¡ôg«a½J ª % Á%z¹Íú á­âJ@v€’ãáÕÓ`Œp Ü'y>´ƒgêP:Äî;W²kÙ4ýë‹y‘ ‡„×jûžÒ9Ý ?µ·R í^ûK«Tút9µ¤£Õ¨#š2@¶‰ÕÈ‘bƃs_·~yµ/ ë)õä›ÊÐÒõÊ_ûãÚÑär5 ;Ät_ô…€Ï~…e+öÏWr‰ž®ÈgŒZ½_lý T*/Ûƒ£<@v&–;ùk;åïÅx!þލk‚|ÐN~ýyv.Œ9|l:àRzƒ~ÞHP½ _¼µhVJ6´å$„Îþ–¿þ<jmŸZÎyȤ¼ Ù¬¯dñ|!W²½;® ¨3tðrßï``~!2¸‰yª7‚65¡|™Pȱ¥Ra²/ÿÒu¤_PÙ'ê ÿ [º¥8:ܶÍË¿úf'Ý~ÿÚ¶ªËYwwNÓ¹ M ®„SKÔë8É'´¬ÕY|9Ûݦn-{/ײ3zžÿµO;›‚…ùF'ðÛBÆ.„e¸¤"  ×€ .•}·åÐøÑáv(M~8. Ó2[$tpÔ3¡Ã†mœÁ”_Ì^\|?yÓþ ‹ê3g8h^‚éÿTF¡Ó¡Ëðå:XöJ[îÓÚ¾¨Âd³¾¢Ð_ÀÁ=,z|$ÃÈW~€…¯"wÓÀûÕ`V½ÊùI/ãQA6Õr7 ûÊÆ ôéñ#Ûô_ëëå…ô ¯M^˜ã“xqイ{Ç\‚…a€ì¼äÚºþê—©Öt¸g½5yõ£S“[¼´Ø7#öôÔäØS~æmÿçHÈÆNEÕ¿ D ˜ ‹]¶{ʳm%Œ}cDýqæþñhšV–Ùø@îh†nª€ E.eGVÞ(? o˜Ÿ =²õxD±ª]àûÁ Ý~ æ|½¢ìˆKMËï/‡%]AÄÐg„íºKL¯öŸB I3Ëœõfð( ;"`áhð½yÞù“Ñ—˜M¤éû=|]¹Ôý²h¦Ü ÈÎH}è•rëÀ·MºÎÔjü] ?•Π"s®XûEEÉŠm‚onÿºÈ¤¼â f‰=¾$<+éöø¦ÝgûòZÉC#*·RUÛkoüù&í¢rŠ\ È6Wä»A ~¦>Tìú ̽•…².þɯPié.X´b,hÛÕ6×ÐúóêÐc³‘ÿ±.•ófoiMÞ(?ÓÔY°ÒW £À³ù6nšàÀoJðÅࢹÿƒJ6D%‹2.G,€ù­jB…ñ/›ã”¹OêðDë`&ÍðÉHöý_Pfæ_°töpðíÚD\) ©ÐSÌ‘jÊÚ;„VÔÌyÈ®Z Vž¾#„êÑ€ößå»`)¡…¦s…\Zx ü~¨S ª}ÖÛþr„ÃeºÍú ǬïJ@¶¹¸¡fí‹ðݧ«E{7ÏNôó‹_Ã8Ü8ðõ>.Gò8È&غäè1ÍzüäÑÿÔpÚÀÒP¯ÃDÕ£ók¿Ýf Š= ûÖžoÊé3ŸŠj9Æå¯µ™­eñèãÕ›òN¼´þMgr5 Û\/ýö6h*G@£†ïÁW:¹„4ÏËö@Øçk`Ùä~Я¸AÙ ¡^ŽüP³_Eh·emÍá—QBó$Ù¥ƒà†·¤‰YnèÔ¼`èeŠZž³r/}_W( õ§¶aZž;Ù¬¯0µ¼¸{W²ùJ®‹ÿ%ÓvëI˜ûÍ÷-³ö™ƒ2ôÐgÁ(ðõÓòif=Ì]€ìØç ­X»—·_u%žOU ö6äèj?àeV]w~å; È~tñ7«7åE˜œâB5Z¼¡Ò§Å?OŽÀä\güƒ¿>-n‡m[g<|$µ®ƒ€·òñÝ xôsöÁÀæúl8ƒ«—†YþÈ<ÎÆóQŒ«l €.jÖÈ´œfÕ`Þ‰F_K¦Á6ï `èE8Ú¦° "; Ÿ†úC«™ƒ…/G˜ëN@6ë+L-/¿ûf8ñ5ÚßÁ¢Ÿ¶ä#ÄEö|#!î)Ø_¼=ÜÈN¸¾Ã_—3 fÛw}ÄkX4SÐöþZí&ø¤ÜÚGG79LÅMøDï?Gm¸ÌÿÃ-áÆ„>¾¡Ùd„âþÉå‚—]ÌÕ“ m^Žÿ¥q ½7Wt©=^:Ço8М ?| Jâò’è÷áÃP%%ê´‰2z±¦Ù#¡Dø¤#ÈÖð~@hf$ÇyÈž1öáÉú×ÀSbÒg‘"Þ_‹ $-&­YòS£ò†çqÇœÃËîd³¾Âñ¾‚Þ )Ù|ïZ»: ˜Ðü–î„¥¿î·»Ñ/ Aaäìv<¼»øuЖD`¸#ä@ö>qF¿à ލXdÓD6æ•«Ïh|ãŸO«8Z‰b ÈŽ;¾¬K٨Άâ4kĽ(U¿¦ÎN{Ðv1pab®R²Íõ¡ƎÏÁý¿¯šÇ;û|í>¨ºOƒCùi¯ ›–»d Š,+ÑRM 1pokiMôÌѽép•óÝ$æ».nöˆô}AÑ-£ Â˜E0—ÀÒê J„üÓŸÚm$v9´ w²Y_áx_Aí% Ûô]àî'è…3¿?l‚åR,“_0ô6‰€àå¹RÅ_ÝÈÎÉLèÙxˆd8âkíž4{T©~ï¤k;_t´Äb ÈÎÍzÔ¿JƒÅîÅ¡—% ¬*ø‡V›GÛó¾[R²Í ->9wOíðz«¡»y¼£Ï4óÐöc˜Õ²Tûi„c`àÿƒ’÷“ Ýƒ`ƒzXÑd=.€(˜ÍyÈþqì6À 9mg'8±hj–‡¨? M‹MoMžüÒòSãÈr„µ|í†;Èf}…ã}µ‹”€l¾vïÛ”ˆ¤åáŸ]¹xæsçýšÈ»Á˜V£,Ô[14w„¾\¯T‡õëAJž‡ìT‘ùDùFÈæ›šcØ1dZzLÈ>rò¿iVyÿ^?{D¾Z¦½ÚòáÐð¥oá+Á Ø$€8ù¥¡#òSã,¹Íú gú jc©Ù|ïшgAõ\(ýÁJXxù8íü€‘?0ò FþÁœ%©Ùi1G{Uk2 Å“‚KÕ¦ÝkÊ[¹çX:{9øãB²SnîoY¦ÆsîÔÕ¡ J™¨tµŽŠìô„ŽŽ¬¥ÔÍZÞÕðkmûç 9t¦ôœ-¬É ¯5>-­Ö¿Z;BäɱÝ?ìË)}ž‡ìD‘y‘çëKÈÍ҉ƙ¥7>Îÿàì‘#DŽϖ†€o/´éò•s xòCƒþh¦Ðrù§q¹Íú €¢ØWÐûõnwP·¨•Fχ¹ñÉŽûL#ÿ_ëÃÂÉèŒü‚¹‚¤dçèRž*]í{6WÔ°ðó(ÕU›žpYô¬9i. »h4†>£:DtúkÂ^ówîï¯DÚƒeeäBô­lXõ‡Î96 hO‹xßÀ2 ö 1Dïø´FÕç§Ò³`"@öÄø;¿ß^š‡ +Û<|Õ^z‹xÚ¶»é#Ðè ~bÂÔÄò)M}…¨™aËœ,Cb-*³ûÁÁ¥êXFº2Dow3@IDATÆï,U3¢J;åõ“ËŸÆÛéb«M€ìÓ*ˆýеYŒý‘€Íä¢"uÞꀢR 0ué õJ¢Çh¹Ö ªFùÀ'øBG¶…:ZÒU;x§Þ=DË7¢È€l>ÅðkÖ¾ ÚßÁ¼·~6ârøÄxýƒÒ ü‚¼p‹v)ÔWîA¿Á` WžÃVïŲ)¹9îˆþ¨]Dÿ蛲)c¢ÙCa[N.¼» š=÷—@Ó„JÏ‚~bû$¿3ä†üÐÐÀÖ•$5 Û­}…J!híGé­€ 0%´ïâ3_pß·¨£}µ·;Ùæï#ö5.— ƒF=¿…/Íãm=›‚L~ÀlÉŠ“öæ%*µÊVÐèPJ’ù;^¾dg$Vzt{¿èž¾X²ãŽÎ+3&z_>,CÊ×GdÞÞÐ×^‰ŠOj Wg¾œcWgw²ùêPàç7A»p,™üTà“1kù1¼x÷ŒÜýhÃtm5`tö…3“û‚躹^øLƒ#š9âz³Úxƒœ€,ŠLÙ\B\64Ô­?í¿${ÄåAàéEo€o\2ŒzØ'ù›!¿3“ú/ù¡)TríÖ¾çô" Q_Ýô1åh_AUr7 ÛÜŒ´lþÃPüÒ@Û®SáCóx¾gr“¿/¡‡ óåá|˜c€ìœÌGõñ¸(éß ™¿³Þ* n>ŠÌˆ?¹\tïS,Ùé1§ªàn-ƒó/®øâ.fáþÒLXüû?™ðÏY=d™eã¡„rfaR=„VCv–èUôÂdóÙ {3P|˜˜o6²Ù[ ÀR’¹|‡‰Ðñâxw½ið+Ò)¢å<žc`·f°È4#Ù”M¯Ç#×¢$‡ñFæ€ìÇÙ öh FŽUуa.Q/ Z!‡}’Ÿò7óúyJ¢Ù}©Ù…ÙW@Nl¡¾û c±; .%˜w[ hPƒOs­$ÍÕѾ‚´) @¶¹4jã9l¾h­®/~o˜Ç›>;r²iz!÷Ò²õ5©½ÜN2{g©þ%¢¼3Þ¬âv[ðèÒ©GžüMƒdg§Ý«\²¶HSÄÝ?¸š ›gÃzä5û²aÁº øh‹Ù÷¢Â±=åâT1JàyrÙY‰eH*›$¯=^Ÿö†@Üf»|õ>þc/z| -\ƒÉæÆsÇœ¦§CGµ î/§M3sÍeaº´ÖEヸŒø®4{T«<ÌßsÞñÙ#Ê7ÿ°Ï[Öû¤åò3Cþfº ß>}\&5 »0û ÐåŸÔW`?aì/véà³Yip6£ åTZ7 Ž< ¯ðÇåaÚ)™ž ýû|¯´äã'GAæËËV˜”€l½.­b@¸èo^[ê ‹“Ù;KJEÔÒæê’EC @¶d¡°Ö±/eÈÕW ,ù~GÈ… ~ êãsàš›T#oÙ^Þ*Ã3ëpN@8¹ËC¶PÆvo<,ôõ…°tÇY(0Œ‡š6Øv¾Ûü hêTš£m¹Ã—aP»'þÃqÒy€l1²¹¤t59¼^0MCþ¼pãÁu¥> •ÊCvfzªBŸ•iZ5Ý»ï¥J†UóR€AôàH Ù’v†XWV¥S¥‘d+@ˆÎ±çàæ¿%"UÐQŸ ¸SÊHÁ¥•ðt#U-îÞÈ)ð,õƒ—ÖI·È ‚‘ùù€ì‰v¿Zâ¹Y4ÖÃo6i ª¥BD¿ï`Ñÿ>‚õªBæØÅµö0ÌÁíúšáÏ‘zÝ;°Ç<Ã<@6ÕWìn5ÊjòçÈ4·EƒÕ‹È¢‰Ù¸ç§àK•— íÔy¢,@¿‚p¶ çû|{ Ìó×Àkï¾—ÈŸÌûËa ‚WKÀcHl¥wEœ û€€übÕ¥Då ³¯µ<×Hùß?¥Ò 4PCmÓÿ$\Æ8- æ.q¤¯ ¢ »ÕìâbÞïaœùõv‘ºV³¡·€Žþxe&|8z¤Ì;H˜A®Xê}åÀ!ÈV ³‘È&¯ˆÜ±J[˵™PÙj,ODFü)­——2WáEPt7“ ßY¥Ú †\ÑÓ<@6õãI­HJØ’•zp´ 'àêÇÈ«ë¿vø#‰«¸Á òÁχB Ü‘6$ÊFÁ÷2á½íî)Õ¾¹Yñ¢f;Œ€ì‹hÆ`­ÚPTÈø¥Pù»kGÎ%Ñ3ƒ*uúæôk Sï€…ËÆ€¯+1/ÛOÃ`tO°Díú¹Ç+hZîí†|¹à”"!dÏ !½Zò€ûÃ`ø³Ý§ð d Š“KŒÜaŸ¸¤¹ÈGÑÌg-kBy:~Dˆ®n•!@vÚª¢Ê,ľ¼½¡kÓ‘¥æ¿ÏÈç>Ý2 Q!ŽôT@ ›úî»6 ,5m=”™Ð݈¿³!暨 øS¶`h†Î†/q«ÿ¸ ÑðŒ3‡ »F+ó\­3µõœÙ±„ÕüãÄBY¾³¸›+z6$}ë(tpte!OG^†|¹I=8ŠÄÒh›Ù·È3þUáˆù&Kªt^°_ãë/èÇÄ›ã¯ÒPZ6e4ðq«˜¼ÏÞG–ë4Vûåäd<58rí–Áí\Ålš¡s Öü­° ïÕ/¸Ð{~v(qê½òÚñð7_  ûØMHå‹¶å^@Þ'PÞB,ícmpì9èÛ)·×‡<×Ð"¹è:ìs’|qyí—vµ ‡Ž‰8ËU©ó—ÐEt ±ñŸÑ=·!蟙L)œcšÒäîò0q¢ïQŸïè=§¯ŠÏ“É}ŸË=Sº\c|nNvíœQ¿M˜­û¨Û œ›™ çÜT¤··!ñÒ¦WN^ß…ÿÏ/ì¥ÛÎh/ƒzÊ{F[È®†vÎÎL|¯5‘iÖß²mç¾d'„"^í¶±Q;tJ_þÆ«×Ï»ÆS;åITÑ–óqiôO0F¹sÑŸùõrÝÃ3 ñC­K§)Pó3¾»Æ+Ö‰ê‡} Î_ ÖéwËŠMeŽÎðõ/Îê?¶%ÊæÙ8ÿ½Í{¦ð\E®Áàë­(„Y#TL¹û5žš«=8R3êWèsósäÉÈô; ùOdc'Bƒ#odºJAôq%†œ¬VèÁ¶Á©9µrT¾%Ö†ÕyyEÅÖoÛú:Á×ûT½ù±Ç™Jý7 ·Ec÷oôYR6Âë¿gT v{_è}9ÖXŒ;¥Ñ.7G§H¹}°×Ñj ZBn®>r²©é‡Üù¿á¯ÜxåðÎî ¼\Bñ);Ï‚Jñ8“Q¢[иÊÝ–ªmZ¦–ãßuM€lEÿÿVB¨Ð^Lóò4Ò N‚˜#Me›{ûgE?\¸ë²•ÞðAïaøƒ„ëpáj’^£‚$lC±P»æ"ÓŠÁ¹xîÞ#lsÒÇÏÝÓ•ž{©ü•>¥/Óo•1L¡À8槤Ÿ_üÉÂçÇaïmÈŒ;õ ª¢l!îÞËÁëò°Ó ñ†0“É$e° &¼š—ÙZ™qê8P$ƒáŒ„f%è×:϶Þh/+©¸g…g2Ú–äôYÉ(@ËÍ ôŒLé8æž+øi \å’pkcjß¼67¶u2€ÿÝ®%ô_Xþ×XÜq ,¿*áyH.ùôÆoãŒç mäSÊ Ù\v‡ñ†Ö®ÿåÄ^óÙV‡>ËvAY}¨Û×›3¿ü´?@‡õ GÁkè&`ÎÚ XŠK™&?âü)……F•ƒèïñïÜËóí‹K"ä–©-ñ„ö‰—~& ¢S?=›››-´s”§`¡ =|¹ ÓÂÑUÛ.¾0ªQ~g>•Õ¸u2¶™÷Œ‚ .h0è!ô‰n «´ŸDÁt÷ÒÆŸst¤¬$,ýRs¨ƒ¸#·Ì2“øñ”»`+$½Ó ún; u†Ï…)ËÆ‚––Üœ%´ëÃ`ç¼°/¯WgBÈþËÆ÷ÖÖÈ6U["*à‰—–â˃/ìþéßBîìü”û¡æ‘.L†ï¬ë^J𱄻§ÒgðÊç¿AòÒ]6§õ•=¾+,¿õzâ—·¶œŸÎ Ù\Oâ ùZsb¯Ö37AßðØŠ/þQºÀÂ6þ‹àÕÿAò¸®ðÊsõ¸‰)ÑÍÁ‡wàØ„e‘+á«,µ‡l<™¹Ðú Àó_Ä>¢{^_Ñ»•F÷ÑÂÜ~êEò¤}Îþ'aöqäÁѾ‚Ê’ƒ‡l®Î'o¼»Ò;5„ø¿L¿%è×ñ° qB«py8=UÂ_ )=dㆣؤ8t7Éì¥ê'ÆË1çÜ` ‚B,DnŠ…L³E„)˧‚S ùÁ.»¡/öêÈ¿"k“¢w½(6gu@¹Iqh„çv ‹ôÁ_ ¼Ø^}ñÚµ¥ªs“aœ:¹pÐM§m§$\¥Ú?ÿˉӠ¨\ üÚ,ȺéΕ+ ¬ààútXï†/AÒ!õ!¾3ÞJšErñýæ"Ðí<7¶~£¬í ã*†GŒÜÒ†¼ö#düsš uìúrkX‚ç/ ˆI(ø龜lôÓdþfäzÈVqZg•¬yÈþhtGôñaà¶ÕÄv"á›òÉ/ƒ¢ÁöòÁƒ}õs†Ã¨ƒ—àÖôõGÚ)IX´Ô²åÝWdÅé`ÒJ÷|ç9ÚWPKÊÁCö­û¸ÎlP>øæU8lï û똒˜{Æ.‚t½ËÃRyȦzáv¶ó© W$œ³µg=ëñî|gI‹Ôø«MHäMëñÇHá!Ûƒ#‡=ç!;È•tçŒL²C²±¶JMи»;I}8ß¶ui°m+Í뺼 ¸ÖqÿÆe™'ÇÆE¶‡ìBΜ-ðpþpH k1Ê/yN!p{\§É0c×$ÐÐ1ŽPûÚ°dóIxÓþåHzž4„7:¾¯„;Ú•÷,øÂç!ûEÐA…~ˆpÇžCsfWî¼>2[Õ„·>ïc¸ Ö‡ÀÚÚž¼Vi!ìå6gÛg䬠#²±Lwõš #>wÏ,XS:ÓWPY…é!û!ÎÅâ´ôR¡0ûç7ŒùWŸ–‡Wƒ1½§Ãb\®ñy_×:8µ¯ˆxÙ”géæo½ºöU ²½UöÆ_öµ°*!ãw–t¾c/ÂÃNkýCEmðmÕL\„;gŽTW7®Ä,æê­ Þsu{š˜4ž&›tÿ<±“Äná';& {ÉÈ´’&¿ ¯Xóm¯­ÖM€ƒM#ᣎŸAæÕ{Òüñ”¢Å =¾šñ1’“€lnæˆòâG3ñ—˜¬AªZ”+$ÛÛp÷ÃȨ_>üöU=ËHeh{,‚· ÄM`n!å ‘‘J°¾‚pŽ÷ÔŽ…ÈNA@5«ýµ°bõ[°BÈ;e.CËó†Áˆýàö·\ç0AJ@6m°Qiƒo$D1¯N±z޽¾3”¾ÛäRiwŽ‚d“¡J6¼;öÚ?*½N63no¿Ûg×ê½}ÿçö‚(ð÷ƒ`@§Žiïv…¯?ÇšýöÏ`{TY˜ŠçŒeg\G¨qøyÇ9È¥uMKqå9(-¯5GöFvŠÐ1cctgæ÷ãØ-6£XľѶf<ƒmêÜáðØô¦òÞ&7¹ Ômçè½Ô€lÒ‹õE±¯ õˆ¹ÆmÕÈÿ–£ï¥«Y2¦ÀåácûÓV×,K È6ÖUé¿éæ™_ a?¿3–v]Ú\Ü&{çì:Š|Fð2ªié²M“óÞ»spÄ«€@ò‡¤Ö†žŒ¾°Qˆ¸ÇÉÐ4ùõcËtþe›ÓÎ(ÑT€ìmˆÂ<2†w„A÷rTlZÙý_ÂåB`nÛO ãA’iŒ°{ܲ¾%# *½±À¸uœdÓ’D9%a†n#×GEæ€ì}aP²â±Fäç…Fèéwî²7a½(%¬ˆ›ÀÜŸ"¨›ÀÝÎ’Ô€lÒõÎõdCw²É¿Öèg+% vn|ÿ?¿g¤‹£Ô¼:¤àòðÀU»áúøâþOͤd“R%ëõùãö™µŠ=Ž‹!Å]Û+#>Ñ•Ú}xË‘êK@6g(e@©•׎.…WáÒõëƒ[п]Îê¦;ä ÃÝ€ìøCÚëÈèÝ F 7æ+®´ÿÑo`i€V=õ:‘9‘H3O”ƒek ÒÉ @¶)Þˆ«žCKk¦€ì6CÕô,ˆBP´(\ùw¡å_5¬$¿/œB®¸˜ûYu#¸;ƒ@ÞÎÔ€lN7ÖW8ÞW Ý È&¿ZèÇ(w’Yû6|̵¡+®´<<¦3.o”MG[–Mu-Ótt¬Ò'àrÌå-®¨z‘ËãÚñ¥é^š‡–R©²R²Ý9s”Èv¬Ý*>;sû£ØSüà²cáT—öÿîå[âgG«àNÙˆ—.“!;¦q‹G9(¶WϳßÃèKoÓs_@:Ή¢ŸFÀº‡©Ðâû¿ŒÞPE¥56ÅqÁûð¦÷ ôjê!{Ãx ϶[U:DøR€q9b¤#0èÏßß3ž*-´hÁrênù¼õÆ|È °·[ÈÙyб¾Âñ¾‚Lè.ÙäOëÓ_!óR4œG?[ã Píêw«OKˆø4¼:õwHC7"“xÙ¦ á ~É…ýß» €Œ”Xˆ½²MQ¶Å8Q…¦¶sÀC¶irÞ{wŽd“æ4û/Ïü3;&Iè•×F…˜tÿ"ÄÝÜ•[¹Ótr¤é¹ }ÁÒí4ýd|°ö]ÇÀÀB+xî;˜„Gkì{ákÈsìN·gT*¿áàèUٴó1²9ØÙ¡™#ýû!(ù(ÚMxÖµÕ{ úu¡ã?è¡é‘ÃÀƒu+ÁÇè{&ƒ@ߎ;Ù¤ë+ï+È~îdwòìá0š€ÔT¶4¢#ÜêÞ †~² Òÿup[J@6WçªÝ—mNN¸’A+ʼnÎíþ&Kå_bMxÍ®Érª·;G²9ƒUh?iyÌ•­£ƒ3.Ðïgw•¡ (½ °T=‘s$î5 ú¶C&nµÿrËÇÆÃû$U@­81 Þ»|Nôý2g2 ,qâ˰Ëèܹ¾'”(0'Öo®!›ÿ#߯0j£jÈ¢iÑè +!$1ùq¡ã>îÆÃ1:þCHgeèÄòÊ0uî†#ð·Xr ›Ó‰õœ%äy%?Z[ñd\B6‚,µ–ïu‡‹êÁè·—@úiP-’²Ñ´…]Pîû3ÿL*6;³3SÀÍ“+ †þäÌ;PlÙœÑB+µIS–|ó‘h.‡¢uM¸{ b®lÓaG¿ÌÍ¥dßGp4¤+”€Y{&Ágt“68rwO†1G.Ã¥aèMWhÚèt1"¶ð+¼Žiü„¦Ë“ãÃqYˆž="@6Ž›ó^ü4,ç2²wEÏ×Y¢á.GŒ“b9ÂZùö.èØaŸîdsz³¾‚³„ø«Ô€l:yõËCÅk*.º9ï8²<,5 ›«IÕ.ó6<º&5öê?\G_Omû4Så_ò·R áçµãT¬ٜ٪¼°xÁƒèCÉ÷.ýÍyäÕ€S!‡þž® «ú9uôÎTRJ@vjöô§ì +Ž~-üÇÝ™ú˜¦­Z²ÿœ#þ>wÞY*ܯ žî½ô~2ô¼J™æ'àžoÄ%=8"@öž‹Ð!ÄöÐöy.#[×oh9â Üœ3 FI¹aMî°OŠ:ìÓ]€lNoÖWp–w•Í‚L~´øA§©xéïÁþ:áZŽñs,5 ›« õÕÚRõÞ?¼~T:9…ôdŠ¿sîœßU¾ã—ß9[Ïb ÈæŒ÷øåiøþ¿ÇxôËsõèÂ]FâÕÚþr¤ÆÙL*@6î¬‚ŽŸ£«~ŸþN00W[×Fˆ#Z9-ß÷§¬fžÔnª•pó¥’²58 Ê.õ6Ô@ó+ Û¾O~[þÆã=¾êCè¸1Š»R–ûÔçÂ&:ì3KçÊœóòrÍiÃú Îâ®R²…‚,NSǤqSÆ6Ä~=]Ü»¨n³HçÙ\Ö5{­Ú}ê‘óû¦gsažv¥ÿ×NÓ„G~æìÇ?Ù¦X²M_Žš½–@?ëÛ­á‘àìG1§§³BjöoZoGï¥dëðß¶ËÈHJ‡g¾• ,¤ÞëAÊÌ¡ðÊ÷Bâì¿…ù5©\~Á¼‡! ÅÞUÌ“½aE§s†,x6êa& U( z|Wû.Vï=ùm!ÿ-B±IVôtI0ö9ûÜ;v±°Ã>ÝÈ6­ë+L­!ì^ @¶˜C…iéœÔò1ðGɘGG•$ Øæ@¶iJ5ùᥳ2ïߤï-Ï£ãOÈÒe¥œ¨=àÏÍr­ÐWèï4 ÛT‰*/Ìû8îÚÎxôàòퟦå¸û>;+ö®ê¡ ¯þa¥vïÈW~„Ÿ5½¦Cæ­pôØTøˆÀÑr Âá¶óW“rà¢ýmÁ¦íÄó¿¡»@ýmá( ´ PoÖš? ^,fßiãÑk‹çӥߖÂXްV<ìó}ܽf<ìeÖä(Ü€lS=X_aj ÷ß§âü&ú2u²;´üõmøY«†Õ´<Œÿ‡6ß]w²Më\ªÑ¾e›¼¹ouÿŒÌ4릙ÈôþîÅMpãÔªÔòO}>ÖU*{@¶©!i÷VPT—Q'þ~/óὓ¦QEöž¶3ÄŒíßZÙèÉ&Ø:ÏCöLÔ‚À:ØZRã²³´Ö ¿NRû·±}THºü¾ÛºOÁ@òÛÂ$‡+wØç™[p]+Ø´·;Ù¦¶a}…©5ìß»­ÏÍW¿Ÿ,ò—e¿t÷JàòðL¶œºê\ƒõdw²MkO³žÞšÀÕûW¿œFÇkx¥$\…C¿HªÔúõ°¨N‚væ ©7d›Y©jǯ.û–m:zçÏ3ãhE£hÓÎ¥²î;Wýåõﻲ&®d#[3 5Œ¾ü ®&ogêOx¢£áùÈ2¶oÍó½Ë E2ÛcoæˆÔÊž€_®SúâŒ%´F•Âañä~ð<ùk±&S˜á ÿß'0Ï´úwÞY]¤p7 ÛÔ&¬¯0µ†í{W²KÂŽAOAOGA¶­©kb7}Ÿ£ ’Ï[EÕw²ÍkõꎯRÝ>¸ï—>E}€”w¶/è© ¯ö~µæ7¯«3ÏR²mN%:£,_Úfcã"@­ ä‹s&ììŠ:gÜ?÷ͳ#÷)KÔp&«BK{hݰl:02jÀ†¦®öiD€ìø‹Ô£[k§‚!ou…&ß ”×섎nüKȶ,¥h…¸mj¿rúþîÿÖ¾X`è±}L£eŸ¶-x:÷d/¤IewŽ\ È67HTŸ»üË6½sY7DøÓJ‰üI¯K<}6ýï\•î zÒ?üµö ׎5žýF›yiKüÈïÚ¨¼—ÜÞÒ͈´C^Ä%(×Âd›Û–õæaÏö,àn@¶¹>¾A95_ÛýlN®~ß®å/¤¡›sY>xÛ‚§2½ü#¾¨3pó ©”d€l;–­Õï÷íåZ Ù³ªWòÙ]_eÓ¬Œ\‰ÎLû{NÓôë'W¨Þoã+ÁåšIæ–ÀÕ€l¹ÚT¬^y€lD<Ó÷@¦å0>‚7âÒÙe¿‚óÓÈCvêÀ%ôäka²ùlÊú >«<s% Ûz)E+¦0Ùæ¢RÔÀ#RÞükó¬Æt‚‚œéò¡99;–<—¦)Q{LÝW7»|)Í´î mj +÷Õ{.9Röɱ¯ü»ð⮥]ÓÒ“l-›[ÉDÂ`°á€(wÛ‚v uðÄú#ÿ#õŒ‘+ÙšÆíYç²Cò &œÐrd¾í¥4ÃÚYè”$Ù™ÏÉ-¡½ŒL3U@²í²IΨ0Ù|öc}ŸUÐåBT×tŒ±· 5æý61Iü™ÑÐÂd››Kãëo¨7âÀ'ªàJãq#RÚ•ÃóôäPQND®ö­î—~v÷×7J4ÚµæË¿î’Z?)Ùî\VK®ÿÚá?¤6å_ºÉȸ'íy)éÑ¥›g5ʼ¸¦,^ ÚQ·m~ÛôSÿûðFhÞ/Ö¸i½;ì!•‡lwèîæ2¾Ãò†!˜•[ Ÿi ½Ðm^B¦’9½‰œ%(ûTùm?p4sÙâûìÙBŒÇú K+Iå!Û²$O q‡l!V©ÕoÝ?% ï‚‹›ç4I— ÖöÊ‘…¹›fÖÍLˆ=½®zß /Tlón´ú8+ÃЩJûI×DTÇ)QÈæ7Ÿ › ­ò[‘i€dJbðF\:¾¥5_Œü-'”¨ƒj ÍYÃä:qbÉçÖ™„HvËúŠ‚¦e€ì‚ö §Âd[jó8¤bë·ïÖúï‹•ïÇ;—vK>ôLj ò#TtïòØ2÷ÉÔ³;§\ ©Ù£g½aû'W, C•éΙ#IÙÖìQ©í{wŒ>Ú_¡ ûè_ã¯ü9³vú­ÓkÀ‡ú%?¸¸E?“f¯î^Û¾¢ÂÓSž®÷ÚÖ_©3¶¦/ wŸLÙ¦…NÃ:¶Ei(oÄ%ãeÆÈ=È…ÓƒqšÒU.€lkÕg}…5˰ðÂd[k5Ðù›UºÏo{ÿö¡Åÿ›ûdú¾_úgÐTRSŽ^Ñç7Âæ¦þcÄm½—ò㯟ì^¥ã—W¤.Û<)Ù¦?æåyÔ3NCîÀ í¸°v`óãÿûpÜ‘oÔ.W³{NÕÆƒµa嚀——·Kê›™ún]“{íè’ôŒä˜l•_øÊ*ÏÏ\âJo b%@öÄø;o2ó°ae¸.6ÿ¢*ŸÈ&ßEi&u8÷‘ D$"š9úÊx'üÍÍG&_b4¦Œ]ýó)‘àÁ7Èî9Èa²dKʝ F!@vôÆ×£í4н¦‘ÆsíˆyFt ›úŠl9Öˆo ºgfbôáù7ÿ÷Á€K: Vû†h"ÒT¬Û×Û7°ŒKÔ¦•—G1'áÚ±Ÿ³ný 8õ»ê9³Áà»\R€ƒ™ ûÄ´ .ÝÂçN'>A•úÕ¾÷ëïÒd±Ç—„Ç_öBn棗õºÔÒáše–©ÞÉ?¤t=E`x5Ðø—´[œS^šÊŒ¹úOVÌÕmÙYi¼U¾á»|ª­®ÒuáAÙËû•»v]ÖB5ÔW+¥ÖÁÄ ¤ùÁ³Xö7Èu‘iàr9 YìŒ}AѸ³È½‘ oTÀ·á'PaÎO`¸Ç“('8 ¯×?¤ê3S »U¨TÜú †?4÷ìû Ââ¹æ«Ò^a…/Ì $øÖ¸¥r@ÉZºBV×Xüå Ã¥Çë£OOè Ò)JG¶÷.ÙQV Â"q`£±«fVz$£Ÿ¢ÄØ3p÷òæÔø[ÔÞJM¼Â'dMɺ½þ(Óì»™¸AàÌšþÕÒoí¡¯ÿ$WçÎÁHå!ÛYc<8³.øþéͲÓbÛ¢ézÙ™Ée1O¥_p…L¥ÊTšä`ENv:àÁ°=›•ùÈ++å¾V© ˆ÷VúÞ¥f—_é†Ë=5é‚ܖ͘‡lþ7ÄÆàˆм4ù=¢­þ´ÖY,-ÆGç"“cÈÏ‘ÿDΧ2PcÆP(ÅÄ> »SXP‰ZÝ…HÛÏÏEžÞW™D²i©Éã‰Ùx|ˆ!EÇ+;Wëöõ²!Z½ ³Q!âe¤Xfzª"öÈìrÙI7CuY©~YIþ†”x!¥yù„¤©+¦—o8ônD½¾²û½&ýÝAîq€ì…³eP§,·ŽÙÙ:±ô-@€lE°¶N½¥ gŽ<¦`JÁO{Qr"r(ò7Èb—å0‰çÈV¼(¨N-ã/¬×âÿàaAÒ…(ÄúŠB4¾›Š&@¶bdÙ)?¤3}ìÈÈ"šV7h*C\ä‰Ùx¶šµ¾Ü¡úy9”Š%*R`²ù›ËÌC¶¹á³~@®‰ü¯y¤ÀgÂù"7D^Á—†yÈæ³ +L 0Ù–Ö—ƒ‡lK­Xgæ!›³»Š²óÍo.3Ù|B'óÙMFËv;‘y§ä™‡l>³ÃbÕ¥\ú%hµ$QÀÌCvsäâ!ÛR3B`²Ù{à˜‡l‡ÌF‰ ~`<=8@ì/LÇ1ðóÍk\ŒœxP;Õ¬hçÑÁÌC¶£Íë>ÙŽjè©é˜‡lOmY‰ëå [bä‘=‡lsÅZbÀäWCÍ#<¿…2¿#7µ&Ë"üÐÈæØ£H k‹¼™b ù<"`v>1@v¾) Þ0@vA{¸ñ‰²-ÍÙ–6ñôÈöôfõ³j+€l¼\GN¶š`ƽˆ\ÊDæm¼Ÿ‹œnf~Ë»´f.ä©Ï í©-ëùõb€ly·1dË»}d«dó7@¶5¼‘i&4 ´™=•@î‹L3J¶È”ÍÙ¶ÌÅâ à miuȶ´‰œB [N­Q„ta€lþƲȶ…72Íè;|Žìü&òjäȶÈbpÄÙVÍÅÙVM#md[Ú—²-m"§È–Sk!] [Tc ™9¢ o"oE~y2±íaŽtÈ„O2ds–0»2@¶™AÜ÷ÈÙŽÚš²µœ³é ÛY Óô Íßð<€lÚ†O€l !ògô>òä«Bäɶád ›³„Ù•²Í â¾Gȶ´5d[ÚÄÓC ÛÓ[˜ÕϪxÙBg¸<Ƀ¶²¹'m.žïZlAÙ Í÷:°°¢`È–w+1@¶¼ÛG¶Ú1@6Óð²…⸠_š1êɸÀ1@¶‹1·Z€²-ÍÍÙ–6‘SdË©5Š. ÍßX<€l±3Gäôñüܟå/Å"ô†Ð$ÃÙd^b€l^³HÈÙ–6f€lK›È)„²åÔEHÈÔX~(Uù¸ i€§QN‹¼ ùds§ÄK ¥¥µÖËÙdb€l£¸'ˆ²µ3d;j9gÓ1@¶³,¦é ›¿áÍÙÍQŠF´›LѬ Šh°ó 2'ÒYå/­1@¶s1@¶ÃHÌÙ–6f€lK›xzd{z ³úYµ€ [ Þ¨fZye^æz¼’ß#0 ¡üÁ‘aO‘a€lOiÉâWÈ–w›3@¶¼ÛG¶Ú1@6Ó˜²Åàh m&²é,Ó||~¹²=:†Õ‘ Ûž©X¼»-ÀÙ–g€lK›È)„²åÔEHÈæo,@¶%Z à—,ZŸº Ï+ ŠÏ4@oÎ÷Hƒª£ÈO2@6ŸyŒa mÕ4ÒF0@¶¥} ÛÒ&r a€l9µFÒ…²í6Vm”ˆEŽ·+ ðÊ,FNâ‘¥Ù¤WCyẫŒþŽ ÛÜ,yÏ mÅ0Ò3@¶£6f€lG-çl:ÈvÖ‚Å4=dó7¼ ûI”2k„rƒ‘¿çÏÑ8ÀúãF[‰7 6⎠ÛÔ$&÷ mb ÷Þ2@¶¥½ ÛÒ&žÂÙžÞ¬~V-`ÈŠ7¢3Ô6"ßµšéãlt­Æ† EÑ`¬ îuó¶#ç1Ñ í1MYì*ÂÙònrÈ–wûÈV;Èæo@¶™#5æ2™¶ïÛ¢‹yy -!ŒKA¾„`'߆U!ÁެGD'¦€WûO¾¤<¢>žZ ȶlYȶ´‰œB [N­Q„ta€lþÆÊdWÃXÂ]à—Ê%,Ñ ä³ù!Öohõ6²½™YZZ«Ô·<´ž•çÄÄ¥‚òÌÈ?t×NÍ ÛŽ¤Šf€lKË2@¶¥MäÂÙrj"¤ dÛl¬Æ{™œ9Z#ÚÍö²½Y#.= zhÀó`åºW—Û!Vâ‹o0dZÛ3@¶£¦g€lG-çl:ÈvÖ‚Å4=dó7| »Æîç—È¥­ûiÈ;óCìßLC{N!÷å É‘«Æ³ÖìçXÄ%BµS*hÙÑ>1@¶}I$ÁÙ–†e€lK›xzˆ½iWÖ?()z׋®ÌåÅ,àŒòÙ0{;ÕhCƒ1´…Ñ[ÙHDîRnÇCy2ÅÙӔŮ" -ï&g€ly·lµc€lþ¦½ª`L}dP[£fAŽ×Y°ž‹áÓ‘mHë­€C©YPÁJÌÙE£9 Û² ÛÒ&r a€l9µFÒ…²ùëÏãFO×—16ƒ_ÂJ³F4Èɱ!c-êgŒ méPZ^òQÁßéºâ±ƒ‹²y_Ù2@¶e“0@¶¥MäÂÙrj"¤ dó7Vj&TÆ:ÆÃEbí®ZlMÀNx&ÆÏB&07/yàVB 4æ,Î ]h­ÏÙŽšž²µœ³é ÛY Óô Íßð:=D` ãahP39Ýš€€ðÙ(Ó¹Ÿl6€Z— šßuáñ˜0È.MÉÙ–íÄÙ–6ñô¥+Ȳº±LÑE=º½ß7áôo•3¯VÖ§'–ÐT9Á È ƒ" r¼“ 9Þ‰ÞJŸDU`™ëA¥ßk8¡‘7ÂêôºZ© íÖ•- [Ÿ›èJÝ98r¥Þ.Ë+öø’ðøóëšgëbÚæè’[æfè‚Õe2´µBþÕCµJ¥·—Ÿ ”A>›©‡œÔldAÿ 3+ýüe]Ì…#ª»Gf*•Á¼ôÁÛý"ê,ßá›Ór, ûaBüˆ··c6¬ ×]f\g4ù7¨ P€½ñ Kš¿‰ü ²ÃhIÞ}‡‘ǧ §š ©àZ©8z'b¸GŽÝsº_eow ©‰ds_ú 26²£7¾mÇð÷šFB˜‰ÎdÇa…p²·è †îl›P+-öXsótÐ'¥ÔöÒ(s5•‚²ý›…ªT%5Þþj…—¿ ¼µJÐ'ë 7 ãÒõ9™—e¦]ÙûhýF­B­LQj«¼"v†>Ñí`™¦£i§­lˆÙ'¦U(²ƒ#·:à†¬I_|wwNî¨Óß韓‘åß´”>ôéHÿÀÖå@‰“D E€=É) EŒ/R6¤ì¿[7yÏݨ¤Ýû†YR× Ô”ø=¨b‡5U:L¹J2…Iù€ì‰¤¶Mz8s0\C ­M)ˆüû$ÔW{CtF®Ñ£ùW‘Vq$2©]A71“mÈÿGΧ{ÓáêÔ=°ÿÃ09?ÐCoLÙBGä!»Ðc·¾‚^=dãà(×Îkó~£gùbq6` û8ÚÄÖàhk¹6ÇXèts×—¯ýõR¶îA/e°Zô|9u`»j>­Êw€šô³×ÇS»R?h¤Ì›I!Éûî>—¼ãN뻦{Çž™uÝÛ»üÊrÍÇn ‹ê”ÂÉÖ5Mý¸µ]ѪÙýµ£ÍÆÆE Â"Іˆ¤Qq§V‡Þ;öýÈìÔû}—Ê ïåü\%ðR¹öÿ;ãê#ˆ_s9;á×K8÷¾­ ®÷mÍÞ«÷HZ9™ ;þâ5äèÖÚ£¨·ºB“ïO—·#Z´£Ë ‡O3pÞèa*¼…51ÿJƒa­‘{¹°–43´™:O=—o™¨ñÍ (óÍFøsÖPx®e$sqžv½êÖC‡I0ÕnÝ&Nôj–6¤ª]9‰Šk_Aæ<1¿E]RôJ¼½kü¥¾u&tšMñxª: :]³±¢6E³w£# Ó—× j’úèÈ»¹ų́°^Õ½Â{WWûÖ w©Jô\›¸íÄÿr1-yo´R¾>¢ÖàÙ…9›tln“þ¸¬ö3Vô¦«*«tUFòQ]Ý<ºRd÷ȺTäÁ¹õAч&OÈN‹ïÞ»”×ÞGUÒ×¥e˜f¦ ò6S?Üx5êî´£3ίqß7¸áäš½Ùk*ëŽ{Ù±îЫ0ËHH…ú5"`6Ž ,s¡N4RÜÛÅúÑW'¹ 苼‚Ë;QÕNÞ? œ^½ààh7çiWƒ#ûD²;…!æHˆ´ýüJ÷¾‚Ì$],GÈÆÁ‘Îök¤èxec§àjÝ~:a[Îõ±—× kœ’°ïS…ÆP¡Ìǵá/ÕP(¼¤™û |Cž­Dì§” 1?žèqwù7ÝãÎ,Ø^ºÁ˜)¥rÁõF™£G{ÈÎLOUœ^ö\Ï›;Æï î^¢[ÝCý}*~ÙZÒ‘¹ýC»EBí½}ü*ÏhS93óÄÇÖ]xkïtz3*, l;¸C¬ô¶÷áOÔÁ|šf‹n!ÿ+~t6Û»|ù†ÀñëqFÜ_´G„q€l•!@v”@Y§ÅX_á´ =:¹zÈŽ=¶¨Ä‰E f&?Úµ¨ÂÔæQuÿíï[¢w”d#óFV†h ü§-Ôu ð ë_¾ãý_üsê秦=ŠvçÄ 0Ùæ-cã9æÈ¼ˆsË›üa‰ý$j}7ÿ Ÿ·ôQ…Û[fµ‘¡Qˆa‚à¡Îþ—}K ¬Òâþ©Ù›O-ë@;¡ÜBÌCvA3ÿ¸êháÌ7[€©ùëêyRð*ö¨‚XÚ‚QôAò 'B€ì†U!á‰rpg±héÍcI®²Y_Qð•c² Úƒžäè!ûÜÊnÏß98ekÈ‹eÚ×=ÔOÚ¥*H5[di‘‚!Ê`(;¡©ºæ–žZï )o]XÓ~ÓÍ]S+”’î‰yÈhÛó¿ôi}h¾6è@IDATÚߥÆÖ©^kkO_W¯¹ TÃBÌËÇʼÝXUkÛKZƒ&îƒã‹þßÞuÀGQtñw5w—N -Б"%ôTˆ‚4A@AŠ‚€ DT@ªRD:HWéE@"½‡Ð›zIo×Ë÷Þ%›\’ËåÚ^.ÉÎï··³SÞÌüwoöíÌÞÔ_wk_~äï\rl È$d矑!d矲§¸ùøÂå®·ñ7iÊè§¥¸l®#Å‹0£#BvŸÖÿqG¸Š¨cÁÈ–dâ‹ÒÙ„mM³ˆsTÏš|6¥áúŠÜpq²scâN²Ÿœ•F® ù^¥»óãk»{xVœÖB,‰rWºB$UýàµïÈ¿iR)æê¯»¯mèÖÕÕà,d[òå5-Ç(‘ klêâUvØëBµq7çäu÷”ùv*Ýæþ?c÷>=1¯<›uä,dgG÷U24¨[.g5^‘ÒBS_lÚ¬"ó4]Ô¨0ªl; þUÊ€ íÝYr^§ðbï\`!›ë+Ì?eœ…ló¸äʾ…lZ(poÏÀ²Ï®u#zK¥5Kä_­HQúýZ‚Z;{HÕpofäê¦ÓȤ›Ž³m]?ruãïÀ;uH݈÷¥^Í$¶ ÁµQhk‚j/©8­Y©gQ¿ìŒþwk;³ÛAÈv-.,-&¸mHO»À•à#·ˆ!d“RR,W‡VªÑr~#÷ˆÙçïq9I /¸tóIÑZcÙVáK„l\HaUZq}…eÀl$d[VDbÝÁB6½#œº»ìȺÕVv–¢}"·FWV»$ 'Vн{cCÓ_ØVœ F‘!dßú£í·‚2ºîµ÷öÑügaq¥ûÖ–Yßçå¹_ޏr޶°àãìz~³jâf¯O;Ô…Ôícá Êg¦nHYYˆ»Ÿ8é Zާ7ñJ¿LÿÅÑ£K¸ÏšqDÉ4¼¨øÝ…ÍõEå‰r]; šMô‹W‘K÷~Õ¸D¹OºÇšðÓt_­íÝdêø¸Î×V½þ§<é%­vºãÙy@¹2äGåÓ§}p®“¬}æ‘Ê}ƒË áUYüÄ\[¾ãÁ?Ó*;»¦!; ѳw ŽÐ§Ô&n­#B6in|®pd+å7<>gÙThŸV‰£ZõÓTFÒ6)ç„l®¯Èÿ‘âÙ¹1*HB6-¸·{øÅ _7õX‡å"w‹B³$!>I]ßê·¶w^L+C'=]GÈ6ƒèåÕ-?ã—оÕðæ` ›PX-ù¯ô}kYìÍ ›Ÿ_XàÌv8“Ýo„{ÃiâÉ8³ŽÎ–õÎ,¿ÇsÊ} ª¤+G&„ìÏ0ÝJ<’s¦gñšF©>¨Tâ‰MåH‘^ü²ß¸ç[fÑ!_Àñ±+¡]f€zÐ<‚à púëyO 4!›ë+¬{pœEȶ :7‡pÚØËº’ &Õ‡¿@ïf“a¥Ò Šm´»uaÞ–rkË}Ý•3=–à°9Ž'äCß»xˆ‚D-nni?ÃfùdàÙ9ºþû»oêùqƒjíì.+Œ#F9š¥Þ«Á/7>ÄûÙÅy«Rc8mèÔ™„ìêå!§¥DC—ÁŽ…{¡\Î6¸Ãu‡pxóð˜#“ä6õŸ º†€©‘6¬ó‡x,pqÝŸcy]~ _š*š~žpéÒýì †D š3wa.½l\\G«ŠÛxÊMÛ ;P±¡²gU¦ü9™Íõùžï,B6®ñ’€Ïg«á®_V îãë7úÞ~±žJÇkå\B6°<:ùõ²€5KURx¿ü3€åã~Mµ¶„IyÞª°¨µoôsï, !; CŠòøË³ª­zS&ô)<#“&˜õ–ÃvÒúþïî~²Ùv:›íïêoû€÷´-°nà ãþJvÔŠ,8bÔêÂðíßÓ@$âgWŽ~ÞeqšpÒ»éÛ"d²ÉÞÔ.<,m•ÀNeqeœVƒð«4sõ@Å’péeRvåH(í² ºö¾ÅM+¶*cÜ—¡Äâý°~ÔÛàí#µ¼kyA²¹¾Â¶;ëLBviP÷j %§oµ‘rÙ³­bNN=x1„=‡±? ‘€Ÿµ¥¹b ‚}kÛÛ#D•<^«0¹Iá㊘Ãø¸¹mõ5oÊÔ)&ÿwèËy$s‹`WÓù&=‰øŸ3ZM¤®˜Ë«ðusÏúNrFõ–Qí×NR=$ö¼±åýö cI®öŒ ÿ±+`Yœf©›ÄX qªlÞ© ñ4£/ãæ® |eYKø§õ€[XÀ0ê VM!›ë+l*Ø dOê «B•áKaIl2X¡ŽØ^oksL\M/ÞƒY8+­lå·µ+ ÙŠÔx~ÒÓ£?Ín#•foPkñb#]ÉnÕx>-Ëú=88z”3äs„lDñÅ¥Õ¥pHîóÊsÛͧ&ãIñiY|ÛJïï1ÖчǙ„ìœu™7Ä@P·9°ôa\ÁtzS·@ðÚX¹a,HÛ“¥¢<\| 4©–®©qÁ>’³bÒy$wIpêϰEêñcWC(S  .þ{B˜kÓsTü¾ë²gaÅÒƒlç*ÿóþ ~mõTúüë-z»šÍõö=Î"d›–N¶x¿ë’jå N¿…0—ù0Mã ÿŒíP÷ØuX´ð#â–=V;W²ïl÷CIm¿’´HQv•f¶–jÒžr† ŽOÊóÈEß”þº€Ì”u4«T#9ÀQ‘Î$d›Ã|å(ðÀýÁ^kó5,¤ÑsiØ ›¿Ê/ÜkY·&y—B#[r5Oë 7(U9Ð+ªBs±C²‘gôò1>dJ+ë‘Ïâóž:#ðËžà¹>ÖšÉçŠ3™´Õ †áï³8EâB6×WØ÷D8‹³t¾`.Ž|–óƒ¦ï̆osƳ}½`TÛ{–ÏÒ†UØ(ÍqB6YÀV§<SeAh‘þø'ôÅe=¡Âäf¢øÿ6üàèÝ(ö„ì‡ÇæjI¡e‡×/25Kíx0¤ž á¿­£-¥Ë/ÎÙ„ìœåñQËØ6$•Ë@H£Iðʸ­;†DÏ­°¿H½ûµµ¬”ÍÙõ<%p+¨d:ñòø-\¾lÁz2²]Qå\e…ìàRp]«ƒßl6îõM«Á¥$3¼#ÓÌ]B€O$h$C¯#R´i›þ÷æÀ¬JÐàÇ åá}·Å¹’Íõ¶Ü™ìiIÈÎ._ˆ8¡öËÇ CÛ€qñÄÄœñl]¯9 [NÂêi½@†#ž6;W²Ÿ_ZøQ‰UÝ‚Æi3N¶f(=à5^¯¬kü€·53ËémìުÄìø{›G ­Ë'NNqqe‡ÕiRãß&C`îÜf\Æ {¾©¿'´«÷9|Ív]÷_ïÏVÁºÏ»ƒßÈ·ò'GÑf³>éSjC—B=µJŸŸd©:‘íºZ#¿F9XqSÚqapO§¿ü”"A#ÚŸHÑD޶¦GÒtý¾Aš­™ÐÖˆ+ Ù\_áÈf7/N#Ãò‘ ÓëáÝ÷~‚ì–ðçX~ÈØWxvj˜Îí³µLW²ãîÿã¥Ny1 ÜØFóLmm_A¥§åýÔÞ¤ç‡'8R‡bMÈ&þ€6-þír#XEþthwÊK£GH<罸ºb¸½õb‹³>Ôéáz)Ÿ‡ÕýíÊ)Ûôé Å°fP(|ÕÓ:2ð‹D¨_£|ºr´ó, Æg×Í9P.YÈ.0ÇXÈÆ‘˜iJ¨ÇðˆÐîJ$òŠåW1"Cwi _þ+‰$_z{ãß cDxWÌÉ<ìü6q!›ë+ìï+èù`ƒó¹C^¬²9|ˆ¶†úäŒwÖ5ÙW©÷µußw›çÿ•W¹® d?>:u`‰°*@Ö¿sòjf„£Õo¾^#oüàÈ7vOvkBöËËkÃüÞ 6§Q#æI-Õÿ51*†=hfË™MBvÎzø¢ªñÏtárôþ' Ï9“8týß+õ˜ ¿um g´Ž LÓ|É hðq¸üÝ6¨”ˆSVëFÁ ÙÕÉ‘ÌÏæÂ=²]±¨ñØüçYDòЀޥ‡1ù+G”vB·¬ AcVÂR"KS˜3]Ÿy0H¡†¾+ðkßÓõËU„l®¯°¿¯ ç† B¶¹ç±$êkFFŒ²Ø¸}¹dv‡‘]%´¯´¦gs(=0Ô±Å"® dk51}J¬íÀ?Ìn¨ 4#•ê]Cý·Ý¦~Š5![£{Þ¿TßZÅîÁ¡§VRÙ<‚}àÁ¾‘­íyŠÙ&dç¬SiÜKýßï@ú8Fµþ z䌷÷šFÚM_ZÕ„ê¿·ž ‡opª©õŽI %ò·-îQÔG¥áòÔMЯ‚?ì§$Ê¿},\Á“[²™ö h ëpKƒ^¯’A$ó€¸Ê¦—ß™HÒ‹†‚ ­€· ûÁ1Rüçk å•h˜¹üT¸JçW²uñ® ds}@aì+ÈæÐ´=„6ˆ’-"램ܩÈ~ÙQÂ= ëÌ@»Jd_ÉŽmB¶Nêס¢=›ã-s—÷µÄÿí`©<ñf[{ZÅ!Ûå7Þ†ëñ}Ú…¸T#i#é Ÿù7‡ŠóÊ|ªæ&lˆÿVj”ö4Éæ<ârž ôó0<ù盚Ußþñ¶-ˆûbOæ—-¾Qe¸Ÿ_"[â_G«Î{¿ÉÛ3`þàŸaøêÑF‹Ô¶ˆ€:ã`,.~{ÿTéÛV—˜ {6…¿†±3ûB?&ÿÄÍF+Ô/ñ: sõ9ƒ9–ŽÊÑ3´Ž}:| ôÄ•a‘÷^ämïÈ\]‰,S޲‹ ì³!ùωð³¹t–ÂȤÀ©[0oÉàKÆiަE{Î…š(ð”Ó„æÄõ¸\Þ¾‚à$Bö“]£žä€6çå349ãÃÎsª  "Ž-Gÿƒ1]à?[¥“ý$²£4w0HhWÉY.ƒM}…ÆY29/.®,mÐhü¤µ '#ÞìÙÝÞoTIŸÖ„qïu@ï\³•¶H„ìÈÙ•œú¡ëÊѵ@ìýÀBûÌF Y›œ\ß»¥{i³%eŽ+ñ=Ì+Õ.OňRz‰^ƒeÀožÕ²2²ìóm_QúôLs[‹q%!Û\ÝZàëpû Ýv–}¶j˜K“WXÈœ†dà#Hò¦.¶:šJïI_´kÔ•ș³$ÇÙ¦íz«!¬Æ=¨V+ ‘q)ÖóŽDš&Ž\ýˆLÍ„[sž³jþ}–ÒË…[g:¶ Ù\_‘u·ìí+H‚«ÙYµÍò‘ ¢p´E„ÆM׬‹0®&ÍŠÌÇGv“<ÅÐñç@Fö”œéØ$dÇ^û«¹wóòž³†¹òi¸»¾ß¼BÊ€6%-8áÑI›{úbIÈ~yaYE¡ŸT+*‰=¾ ÜPTŒúË%h¥kàÿL»fPË«YY‰^˜ÜØÊªe&s5!;³`OÇú¸2e4HW‚Õ´bÌ$*Oo«)ð¿§ 0âßoAZÊ'Ïd#ÖD7›ºýþ¯¬±˜¸"M ÙLñ“pC\rbÐZõºÅ s*É\ÚŽáÐéÖc˜„«Þ$¸Y¬ÝWÁÔ @.ÃÓeóo0ën„lÓFâ”ÅÜ5¼*IÑz}ˉDMdj"U¹ÚT~N?Z.»ö(¬Å‘5ÏÐz9csÍ6!›ë+²î“½}Ip5!;«ÖY>²I4ô ð™»Öá>ƒ¸þ5o×>¼ŸCV¢© 2%†c“mà+jÓýbݹùûÚ/­î/PÄGãkÁ;WN«ÙEÈÖ¤=«"}­„LÛÁâÛ<Ç[( v¾ý¶ÁÕ Ø­üÆÆL„…i¯² Ô„0'ÎogžuEûÉiSå…zÉÞ‡¡Àÿ¦7øLÙëiÊ+«uY¾wgA«óÿÁwDæ®Z6+Ü_l4LJƒ×ÚÕ…Õ9ó»#!›©ãüÁ@1*GÏ5zû—"™šHÕ¸êí;"Y3òMÏd:`Ñ^X–ƽÚXÞ†Å4Ÿ­~¶ Ù\_‘uGŠB_1¨ˆpñDiäÀ­ÉkÕ&ÙGB;IŸ­F{I%í]ÎB-o›„l½R䊽BÝýýFèKkûIõ𛨠d³­}†í]€‡Ýì=_^]RÓ_LÀ±êøá ì:Øãä•ðÂõ†„p=ÛX–ÊØË%kÙ<ßsõ°Þ¹ÊB¶µ5ܬĨ°-ØfNÆMMº óöMI=»Ÿšôšð¡ÍŸZ¸Ì]»kœÈY¿ B6Kßš9K3ÍXÈ6‹+xÖ*Õ¨ÓYgìÒœ #Rõ/Ã@J$ëi['8³m;òý°¶nCÒ§MŽoƒ¬dNñ±m!›ë+²n“½}Ip……쬚ZöFE¸Ñr…q«á7²]dšž¶½õ¾AKÛRÜ'‘UÇ–…l¥<•§KSù{°=rTÞot%Uýùz‘í³#vXÈ>ŠÅ-Á£6•kÎ9™¶–«ˆ0¤*Ãñ¸…Ó;Q‰OÎJý›)r¥Ì#À ÐùdlW –Ìñfˆ‡½©7ò¨Õ#øìùd“ͬ”ðHïšUk|‰@¯NzH„5«™ù™„ìð|×µÆã¾Y´B„u‚-%¤B™~ó`åÁ¯aPýª » jm? Kp¹¾¤™Íß¹o¸ ÇQRVåŽÍ$dS{ lµYÈÆ5?"sõûi ìó;«Õƒ˜=G‘«‰d=~5,íI>Dƒ“·qÚN2y=¬ÆMlËÇmH‘oM^Bö)+Ò¿û”õ·"]f®¯È„Âè±§¯ ŒDÈÆÕjúìÒr]=Ÿü®qä—õ1ó)ï>§ÕÐvÑb\Ø1ÂÏt¸]Nc´6û×OŒ^窜³2Ù—P®¥Õj¶XÙ–²±QRž€¯çñyìâXHÞoéïz½Íc€„lêÇ“¬ÄŸ6ÑHú <ãA«z7á‡Ñ±=rD0Ž¡a•zMJÏÛ›z»°¨Îü; nb¬A¾?o>.A`Û½&,o¬df9º—pÊB÷ OD…ˆ9\£QÝøR±^¥ˆµi´ÃÙ™¸šx ¯Cp—Y°ä‹õPuÕ?°bÝ9‹ó‚ÆV%¨Sð·ûMŠu+¯9B6SA$¡k«À:º~Ë„Ú&’5’­¥Dº^´ªŽü~ÅÕA'¼“ý‹Üþœ˜ÓB6×W˜Þ{ú Ê_ЄlÓ6Ÿq}ßÊA=\}9‡ì ½ ?/г0s¦.ÈkÛ ÙªØ;2¾‡@Ëv­ ÍûÍ¿Ñô:›W«ÙAȦ·;)TVMnVÅhÄ¥b ØR®$丹éiˆ}1ÜÁB¶G¯V`ö~NíÛqµÙçî8CiD¼o ÃéµMí뀎¶ÉBÌqßÅÿ f‡“0GºðS’Š×|Ü…] ÓÿV­(Îä0¦ÍvîqNøÖDÓ›¦Çkô™2(&ÔTÐ+ÍÞjŠwž+$}…Ð[¬Ó)âmîÆl$d¿´,ÝXg8²Y„SÒ/ÖB‹Ý ÍÏCARÕ’Óe¤(¡dè4ø¯xÏ,_UF?=»Jqú¿mF‰ô,æ~fWâÜ|1¾Ý43‚ñzÓ4äÇ#ó¹5^@ïÁØŸ)$Ï,íª×ëÊÑ¥%!S´Š„v˜[<ôZÚ­wÖv9GÊ™ÿÉ[(£tS"ðxˆî7jÜÁD¸ýžÐ㮇_Ð6“pó^O§WëÌÇ93”US‡ÿ›k1­GN?ýs¥~—Å’’rFåu­VÆUÒiä¨ÒèOä•&#¼Œ·êV/ wòJ‡û}•À´²vm*—«ÃÑkø<ð w¡/ݽ‰ó¬Úú{BÜÃ_aŽiy¦~"dóú[?=iš×U~œ“ ù Â;’/¬Zo¹f4=wähp_̹ÿ ÞO(AÝŠ“œ¿$àvÇñ~êñ0ðéLÿ÷ ¿J qñ÷ŒCßôåÆX«L?…Ñu‘OE‘Wéš×Œï+žeðõ€ÚƒÁxŸ'2>³8ü…~|fyøÌ é™%?ߠн–rn)ÊÍù²vlérè®™:¼1îø~3à->h„1”oþé2eòËu¦ÕÎËÿߪšøÿÎc<¾Q¬rW0UNÌi): "lÄc))G3<9Ý1óMÔX9|‘Oàê’µßÛÔfüS«Jã Rôi–¦y­’’o¢º£Ê™©Â Ê@Þ¥›ÆÛ”;û¸’³¡?¡(ñ!ò…h­ï[©°ø•[ˆ!z•ŽçW­ãþ*oÌ ®ŒU.5öèúî/ÉBv~N‰Þ-¡)òŽžå%xÚVB ΟäoK8Z®†öSAî/ƒõ{&ªN3a=n<ˆó:=KõqG Ùæêë%…¤ƒ3Â3û/PŸ½V|"oc(ŽÚ X‹S™ÞRs%ÛV¥ ¼š5¨“Êå2,dÓWá©\‘¹JI|ƒ¤Õ»/»œ;Ê|HÂÚqûR…¥¯Ð¥¨„ºäg]4)/é=HoÒUðÀ—sú (㚇×x0ñ¤F‚DgJä7½–¢¡EiŠÊe(ÂÆ8R(è:#žœãp¿4À•i ܳqÞ¨·àÀ—`…-3ä\/8» ”ˆ@¾bdÞÓïñßè¹µô2òÃgÖ»z÷_#­­Ä«+ÛâSŸ&Å“UWXžY}*ÂË®e ¶šLP>tÒM´ç{Ë ¸ô\“£²H!:Ç/xìÆƒprp)ĹÎXŠÜ†ÇZ‘WÅ FÃO±¥RtË.AóH“ËZ:UºyÖ† )7Ì”\ZHÓ9c’ <§ j&“S‚ô 5_êS!ÕaîHÈNC°;…ƒ\ÀƒWæW ÀæÏap¯Ÿ`0,7­·s:=K8eXÈ&%³À>¢,²MënËF»¦ùLýË€æàex>w|T£<(ÿøwû|G,ƒndQmA±êØ&ds}EöÛG_á[OW®É›¾ÚÔúˆ¹ä* Ü19÷f¤ÛÃÓ~ma0Ž É›àwb}Ø´-ð–¼PÜ5»É-pÿ÷5V~+^Ç×J¯;±áy‰ŠÉ+‚ g[9bÊ¡³]²ÅÞ·âÙŸWà &¨×Á29q²Ljx¾cK™¥ß‡–éÓiY)Ò`Kü&óK²9ŧü/ RÉc§+!DþðPFEó3ásÜ •™÷ÍV›Ñ]àÅø04n¤!IÛlšlì¼pg Ùv6)W¶®€aöHÜõjaþ ÉÓô¿ƒq7ÁÍð­H£c q¶-ds}EÖí/ì}}D[ \°qò¯‰0=«eÙ}Ÿ¾ ÷»5¿\ò‹÷²Ç9óŠM Ù|‘ä>Ý/¶»¿ß¨ýÊ›ñ ‘¬ô}[±(Œ²mmc®ô~A-¨'gò¤s%prÀÊøoàwy¼•RUðoìlXà¢éUt.¦Ü¶²r™ÉÜÅBöè• Fû$þþ >Éky=SiÜbäáÐŽ0ôßAq„Ö°àÜÝB¶£M>ƒOÊÔM @¥hÈÇá‘%ydSiÉ0øäômx8w¨,¥µ7Žm Ù\_‘ugìí+HBA[È&å•%®†»²ù3˜$Zþ@úêp£C=ø””©,}:²e!ÛxÇ´ÒÊûI,ŽÙf=îü~£Z*î'$þÕ¢³jlÏ Ùù våÈ‘Z ö~or$ 2¡AkP+ïÛ< ™C’µ—8üoïØ#p_—÷;"Q}¾ ”f§+­-̦tÉG§‰glÊ„‰3 Ùùgd,dçŸÒÆÓ6ƒfû)x¾ñ3ø¨N°u/_\¡u»WsÑs6(q› §» B6­B)0G„lšJtv®DLXò·ÂÜáÜ*ôˆ¤=û²/^,;hqUŽ]Õ5!d[“Ÿ,dÛô§çúŠ,Xíí+HYÈÆS~D²mÝŠã¬jYå›±TQáΪQð)Yö&‰¼Ø´:|‹ ”÷_X“ö4„ìÜÔìbÈBö¥ìAù_I|‚/$ydU"©y¥pß÷›6A šç©âòm§Ø<AÈöÍ«Õö„»lD+—ÜàóHÈV眗ʷÞB©ï©äO»Hªøå›ÖY .(·ÁûÏ·¿ ´U„¾'šúÖ@²þ%\ÄMho²4õWý ø9•|ê™0°éDZrh“3²oí¬™¶ç“ÑÿóµPyÞ pj÷²`7è–€øåÃ`‘®ó©C¶èÕ£! ‰Ûãº| "f€„¶Á(JŽÙ >ÿ¼Œ@ÚÓÖ»¸ õÓå l]>›þ>DÙ"ƒÈÚDÚþn+lö•BɾmY_Ñj¾zDÈNZÕ|dÞ¡\_|Vú B6ƒý;z-)?eqº¶üÄæ§jó¾C–cîõ±ðdÑP^±Tú’jË9²bÑÉq4MŽÇ´õcAZ¡dVœk|DÈ~‚ŒbÛ\¹æ£/Üý³Ÿ„HÙ|‰k^Éîö~#ÄðýB_ï+R¯ù)æ¹Î dóÌió“®9²‹M(ˆøe"’#ž0Äî\À° {»•'a¥üoX"? ”®WŒ¨}È»ž@˜dë²\Ê[„ìÕÿ€Íý'}×>°Wøc"œnZ ¾î4 ”÷ˆâï$W ÙføñPÔ¯ _áÞl62´DÚ&ò6‘¸‰Ìí$¸mB6Õ“ë+ë+Â"d¯ù4;Î@Ìô>0˜ŒŸR]lud«ˆl!‘M$²ä,Ç&!›Ø}<¤F¾rVu­–ã.ï7ªpÊñ'J®ä!«+ÏrBW*Gv²©ý ‡ü›tì±H§p /›eÈí¿ë?­@ìwоÜ“ëÏÓ`@£Ži_tƒAh¸Í¡ùÇÃÓàp­ ðcûo@ñ$Î9í)j„ì¸àe(*FUÊÂK‡ÁGP"ò6‘¸‰ÌM¤nGd1yÙ&dS9\_;gƾâèV¤ñïÀ vµûú'[Hhi*Hr²‘ä Ç&!›êÇç•Øÿç]4\PjÅ’M@¼Þ+Aèåu9qÿ{p+ôyh˜,B;({ßúä •½¨¸G>G-dcç׉A»ÿœdÜÈé #Rw«×à3$y+ˆìíg…쌊q}…ý}Aè* Ùd—h2Ú' k ÃÆtÿØx®vN‚o^$Á™ñ¸r“l'±ël·mZ±G•ÕÏ—\)vjÜú/ñèc^…ãö˜âa‹ß ÙùŠw¥rd7!›ZA£"¯ÀåÏæ\pµì|qsYÅH9ñL_¹ËÂ-öê*Bö]$K¿¤é–µàËí_ØG¶¶×çÁŒ˜d8ñÎ,PØ»íEa'dS»Ç¬D^nÿAÛ€X‹=épðôëÁ0·rPéÛç B6Õ‹ë+ìï+?W²ÉÙ% ­£¿î ש\6ÙHÂÑÔ ¨Ô_ûúwû œ²IÈfÚ]µÇº}Ê{IŠœ)(NîÅ¢H•È3`k©ÚÝœH¡wAW*Gv²™fV ýn}â‘G%C,.îÙ¼ ‘W…ß|ÊÖwë1’Ç±í¦€W™|` üÃöýã&©‘³aÒ§Ùg>(õv¬©*Ì„l¶wâ:P<…‹´ýÛx“üeÃápå2ð#‘¾‰üm«s!›©×W0H¸ß;Ù#B³èØ®!ÙJZý)ŒºtþíFìêGÙ&d´„],­2ÿÙìóN¢‘³¬ãò5± ˆÝzÛP®Áˆ_‘Vl Ù h%‚Û¦‰}—?þ攃lF¢{ŸÓ.¿‚¤ˆ'êJ¡3â‘°MÈ~…º*‘¤+•†_ŽÍ€®BÕÏôÿ~cÎßÛH¼´™vY˜ ÙhùZuó ܤm?hûWaNdo"}ù›Hà¶8W²™úp}ƒ„íg6 ÙÏÐÜéP\¨ñZ LG[FÇl¯}9ÈfÒ‚!ðÑ?×à)nVk3‰mB6ÓªªaËvÊo$¤&ýË’©o¦ 79?ýþ¬RèYz[ÙFƒñóÚ~W¬ Ù lÕÂÖý–rþUrâá¢M\3è pl„\êóÚtêè™öÛsf“MKe;|r?l¸0 ÖÛS?GòT Íî‰0|ÿ%x{¢—| ŸÐ¶ŽàgO^"}Ë<`#­"2¸µÎU„l¦>\_Á aÛ™-BvNš FŨb ˜¿r$ì³­Vާ&ÛIßöÿB[JkѦ’-Ù&d3u¡¾ZVªÉäèÏ#äd²(»Ô‹/ ~ï}UP‡Ÿæ9ÚÎbMÈfÀ£‡Ç³TóÉÑé‡çÕºë:]¼ö^Ý{ì&©1˜±EÈ–ãu§é @#8»¯Ìc‡ Ì´ÁÒ9¤(~ ƒ×GÀ«™Ûmëô,ÉuEœ­„ì_ÿÍ~ÜÞ§#†Òv®¨£¹2þœ?kõ°÷“_q×t›m›“˜#ÌB6#‰ë+$l;³AȦ(œŽ•ûKaÕïŸÝüIÛZ’;5ÙPB[JWä¿N[·5In)y…8FÈf¤Öîµñ$h<οX|Ù&ŽÉ_Îôñÿà³ciRß:Óýø§ökB¶é ¯Ýký)¾^vèÁgGmøn5•àÞ~ùõXx:óœªdõŸ;£¦l²iilØLP$ÉáèÕŸØ%[ƒA§ú²ð#ø`þnH\¼ßºN¯°²7íÆ!·÷Ô¼Ø8©e ж¥Ùó%|—(‡ãcW\kÅG®«Ù¦­àú S4¬ó;›Mv†È/òà\¨±ÜºZ°—Šl)è ÌÛ iGZgàÔ„lÓ—o8ö+\¥¬L9íªå¡¦¥³ïüÍI•>ÑYwÀn— ZÛºBEÈ6mTÕ.k¦$G<ÙtÓe| ÓòÙòëRÕp÷à ©¯‚ÛO~ÄV9ÉÅ¡¢^sAù0.\ü¾&r´;¸¸+.;ÿù8)§nåÏÃ)L„ì ÿ÷§KÓ> í=ÜoªÃö 0W¯ENZô¡Â³T/W²MëÁõ¦h¸ÖökaÔr£É‰Chwè'×–žwiѦÚVúp:ÚXÂU¯ù:W²M+Q6dhŒg™V£ï û[¡‰+Zc £!vÛÝԠֳƚ¶Ù±'d›‚G«·ü«õüäñ´ÓÊ´«v®-6è~²„}ÿÓä<•ôogjÔÎ&d?ÏË÷áÆ±™0ŽHÑn]fÆv…çcÃ`Ðg«Aƒ6<3#Ìx ![®Ϲ»@3(½ßʹ››Å¦ "ƒãv c®â.êø’±ˆ·+ Ù¦àú S4ò÷;‹Ýïa,Hq³3;&Á´üKvm ²­ÃмGÂ¥–Jw!Û´4ê)”Ü|oèßi´½FQpÊIð`ìQ¹_à£JÖêâ´ÑoŽãé¨Úé‡;¨]¼ÝkB~ÓInå(Õ—ØMÚÅÄë5{˜ìÌrIÈFî€d@yg *éz2°5¸Ìx¢ oW+«-¥/,„ìàR°jf?xkx'pËUD ?8Õ„ŸpÓÏ<عšmzﹾ Ë~g²|пUFíŸ Ÿ‘½!Ë¥LìÔ÷àÚçÝ [YøÓR \EÈÎY‡Úýÿ ~ >}oðAEaWhóô›ÝþRÊ|ëM®þÎòK9ÛêÈ5„l‹ÃàŽTÖ\Þfc_–±ÚÇ\œ#a×6të*»:§öžBiuGDXÞûcþÑІ‘¯õÛÕÔÙ6ˆ{k§¿¶çÓ@ÿϺA“yƒÜkt"Ÿ:ÛÝx„^Œ†?PnìS0®¼7Ô\ð”íÕ ì°T0u¶·ÔÛÏAÜf tÄ}ñ~ÌWFx8¿YÚЪù¦³1×WäXäò望žüŽ)-mçQvÖ¨7±‡cJç_÷HQõèr?cm,Œvx;xRÍÙ5NKx"¼µýÍ ²ÿºÕW½)æñ]úÚvJsTOSàjóàXï˺}÷QŸëTwqi“þÚÔkPh´³»’sä…lK Æ)¨½^[޼Þa+¤F¢…±Bäh*-z|„2éÈÓ‡uŸ q¶bDP°AÈ.DçYÕÂFÈγ!…$¢ Ù9¡áúŠœˆä¾v6!;w …/ÄÕ„lS„<ýµ¯õþ§ê©—/®´Ø¨5à2ÑÂä”÷áfØ_ YùZß²¡±……+•#‡-d[¡öÿ6FxUh2òNŸ½Ê”3…ƒá¯Shà霋ɉŸ_¯Öu]OúXj#ç\ !Û¹-/iEÈÎÙZ®¯È‰w®&dç¬Ì·Œ®Î'Þ…èľ{Ó´IvúÎ)–õkâß Û¡ ª|[¯ÿ!»7–ͯ¢!;„êôûó°oÙvCï:˜ŒÛnhhTÆ]í™v=t»Øô‹®¯Vß¹uçý½iêçyòCóÀn0)l±ÛîèovýS!ÒU ù(r Û#FÎ$d³‹Žk¥B¶kQa¯´‚$d›k×W˜CÀY„lóÒ ghA²s¢%‘y½0UâQïs\À“öbÕ5-Tt'G¦î}ô·üÙ¼Ë^ÿ¤[í¾["Ø®„lWN«%7øðì_lƒDòË5ñ²Nÿsï©ïð×^k»Eù|Y”[<@´¢îF—¿äÃÏ>(Y{Àÿê~°‡+ð`ËB¶+ê^Ô˰ÕBvQÇ#³}N°)Ë‚‡ë+rƒÃ†…ìÜ¥ÅçXȶ™:ýþ8Ð`TØ‹ùQ·®…n“» ×wvÐ_mµI)¿ ü£VÏïµýÂ%‹]8 ÙÖ<5iHÃn0äÔ‚Ò¯èúrñÍHT’Òp(-›˜ú¿<®ºÕ}gšáe‰ê~p±K•7fügCsJʲÍÃDzÍãÂV¨;²Íµë+²£Â²³ãAWIÈÎ]›ô 6ãŸÖõ?¡¢ÒäÚ&?wTAv„ Â%y×;mO}6çò­’Õûôl8äÜ ãŒEATÆIeºräˆUBv^x·›ô8äãkýEÚšãM:}Ùþò¸¿î‚+6õSÜM\¢¯¼Ön«2ù@ò† v³:ÔÿððêŒóª/î:8B¶ë°¦’Ü…W«¹¾"/d¸ð‚&dçu<<Ä@ûoVíº¦]j„b*(r4© -¨Øvzµö߇«m¶¤=sì?©â”oö¨Òéû»l—S>„lWL(%”–ü"äÓË+r6Ì•×7·j®H¼8N+O«[¢ke]©µ¥^€‡Ëœá4± ˆÛqW³á–\ó"M#’”ù½bË)«i ÔÖz¦Æ>]ßýåcˆ=iÁF‡Qj‰í þ¤nðò£åÐBÄ|PG| ÿÜx⢖˜º›O†" l×’b¨5½?ø¾Hé‘P—0¯\ÿ>®OÝE)Œ,&¿H‚ÇѯÀšéäž>•ß¾öÞš³„IA¸âØWÎOoï2<Ù5ê>z-­/7¾;To i¿€”ÏW ‰û¿†3O€oQ C+ßiw_À:l¦ŽÚš‡ëáS¹ƒ÷kï­=“G<ëÁ‰OÎJ£}1@£|>Dè'–”î_KR²g ¸œEãõV׋f^ä¸-fÃMÚ徇äžÔ³ÞBWðŠ,URÍ3¤EήäÔeê®TŽ|<|ƒû5vüˆ¥Fº*îÅ¥Õ¥^^^óŽF÷¢¯^®*çÕ¸¬Ò·c%/Ïz¥x’ª~ *-Ë·*¤9«pSy? ’#ž¨’"kŒ&yûEH¼ëm®ÒmÅiÒìÝÁõ |áÜÜʱa!Û•Ê‘¨dõžªõXà6;Š[º»1×wø&=8ZQ£J’Ô O½.͓ǩybišÈÃ'Mè˜X¦ÑÇÉö„%9îww×§õãïí“NCÜã( ”­ÒåçÀÒuz8íK­F¥¾‚0ÂÍÜŠa%z±…Y•Û¹DÍ.~Õ»ÿéîíSÊSy/Î/Ô$E—ШR= êT/ƒVãÁy¤ñÅÞôŽ“{Wjù´Lý>…â}͆r”ÿ¸šóî2CÈ.PΑµÍ¡N¹0tÌÖ¶‡KÇ!PÈh{s‡ÿƒÆ9²/®¯°©"ŸN¦U+= C+ivƒ `]é(ôŽÙ¨Ð':³!Ä#á\GÀ ÙE ®yù!`¥…ìüÄpñ.C€³í2¨‹VA6XÈ.Z çZS˜x!ö)ëÔ/Á †+ën¥…lWV©°”õw`Û‰— Ke‹R=9 ÙEénº°-6XÈva­¸¢8, à" ÙjPl£¬´]lñɻ᮳wŠg g!»xÞw‡[mƒ…l‡Ëâp8ðð":;E'Ä&¬´m“Ì⑘×éî®á ‹G[‹~+]É9bÙEU®…Ž"@„ìZŽ áòs¸BCÈv!&.)Š Ù®TŽ\WHn8BvnL¸óp„ló¸p¡î‹GÈvß{ãÖ5ãÙn}{¸Ê™G€#d›Ç…õPŽm7Ä!ÛnèËÈ²Ã¯ØææÙÅöÖÞ†s„ì»w!Û^è9B¶½È9š#d;Š`1Íϲ‹é/ÌÍæÙv÷8B¶½Ðs„l{‘sÇ|®äq„lw|¸:q¸'!Û=ï W«¼àÙycÃj „lWnÂ*8œð¼ Bv|\ìcˆ¥M‹9Ç!À!À!7DÈ~²kÔ“¼Sp1–P¤Æó5©E”†/ôÑç“ cƒ2m¦F=ØØÕ©Fc]©©bïôÀÜÜ>¨¹Á ¿òÛ OK½Jè¹0v1€2îqÊ=qœ+$!ÛÿÅÅ•¥ãïGÔ :Kü+½¬Òqæ=.Œ] ¯h„ûaÎ9›8(©ÒÝçÎö5Õ‰÷ÿG9ybï¨zŸþ Ã^çÂXÄ@ "Ëäëlº[ù$æåÏF4•ù6FåÏûðÐsa¬c°7g<Œ«?ð» ·v¿H;TQWþqóåè‚©‰û–ZX1ê>q¢wɘIPPlZxøn¹“¦¾"òh“Ì(.Ì%\ÎÀœ;qK B9*–@s0}ñw©Â¯¾ùL4‡Kv %F/¤Y3ãPp€L=}OÄåðì­â®Š ø,4ÀgAl¡Á2¡vOÄ©KÝ,$qyKʽËÛÁè<\IÈv^­9Iîƒg0dÔF €WîS1®&€™È«l…VÛ:¯¸ G…îiDDòõë×_]ˆx<±@êÀêv¸’sävç*Tð4iÿÆOjEM_©hœNXE/OOµ’ aOÄÌsïòBA[ðµ,ØF"µ¼Ii_éO/½ 3M÷,A³%,´QО£—æPæM›%èÒýÒ%‘ê~)rýøfÍ7. HAbîUtŒö€HžR ¨Õgò!ÔZ}Øñ“qAv'+œó7D|õÿYb®ÝMºöî(Nx<Õ‚ä@Õ­{Ïn5q¥·öÄŠ= óU-<ø§Ü›C sÜ´š+PæÊ°€?)=ÒÁ¥e‚êôÐñ…Ñ¦Ö ëãg(÷Œƒ@EÑû¥*7©™>N“Ž\oÞ¨:ˆ›&ýAÛܸé e•• _f(îU¶á(½Ù»·G‹(»¯Ì0wÀ€¾oùÁéâ„Щ]ý¥éþ¬zèÀK&›^1P¼ÔxÏÒ€¿LgÌÿýæsw3‚¸S1E@ß/.=pàÀ ¦‡ÁÎ?¿g˹V×V«U‡EÇýðÉ'þiÇV^1ÃTaíƒ=Nr¯Cå^‹J¨Ü§QO‚’÷ åþ Ú å~6z2i2”{ó_ å^‹ò¢cä‡48êi…rψçÎÅîÅS oº;6YÌWFmÝqxÛê¯btÞe§»c ºNb¾:êœ Û·eñ _L«¶Ð©`÷‘³KVq+7*=,÷oÝ2Ú6 6ˆ&| e6IOÁ‡é¿¾¨Kz2‹®qÌ'ñPÄù cÆŒQ9´c*£ ¹š×9»DœuQôODDøª no[õe,¾°ŒN"€û”Gš˜ ìY¹«bƒ€V/ >‚<ž[·n=1=¾ê³¯àT–¾V÷Àt0øð×Å‹ñbA¦ëª%uÝG.=‘ÇürÊ=ƒwv œr䜹RòA ¤§xyf½N–éç<™øIáwæ‚'ñÚA~1Ÿ—¡$¡Á9&ÒÌYÆã+™àÈÏ–]Ö1Æsúˆt™ÓjÌóX@g­^l02ÅûH´Ëóâ‚éôºlüÊÏ9SÊÉ<ŒŠOÔº)OµžÙд’mÓþK»MÓ“ŸSîs"Â]³@f‡ÇvAœ|Kp/UK蘉xW…éÁàk&6W·T”Ì6\÷³öEz£ !_ͤ¡³^‘h1òõÑ</s&ÏS$ÌTÈLÓ“_À‡´œaÜuñD@ÈWGŸ;wŽwöìY¾é1fõñLBö¥ovÏ7blTQg\‘1N¹Ï‰wÍ6œrÄ6œ|7@à߇šƒ ÷‚w{ûv¦Jþ©I ñþƒ®µzqððî­“¿ÅÀ¯+x¨Sû’_È7<¢3ãÌ(²8‹–îR5|䉅sý V1>óÁ`ä¢bm0=L!i0õu̳Cá'ðn›Æ3~N¹gàήB€ëÄ\…4W‡@" Õ‹‚›5kªkÚ´©A¨UW°‰ùª(âvDM;0™™f‹|¡9Oit·=a^Z}ê{õ°XuUb†•R”ÂaÍšíÓ}7°>®˜ã\qF@£uyóÍÞ:uú0ëxëð·[ @6í>¼±8CÏÂI o¶oøSÖuºSîs"Â]³§±0'ß,™˜ŒX_`f:ÆtÀݬ˜"h Fø6~|±Œ¶×@gTU²'âó£†RxõÇŽŸŽjH~åi%í‡û_”mºŒºÄNµ9ɱæî•°tp'Óµj©qãM£|î§X"€ Mýb¶$%%­Î:âWÇÄ%¯ƒ£¡îEäùt`Ô@Óoj¡ä]'ÈEã¿|¿©q?=8N¹gàήB€ù8tUy\9.BÀt+’u4CVGÆ,Z´È£^½x]hh8³¢?[m‘x]âçŸE–ÒdËã‰Û8ƒÂ3Õ“r¤à.‹4xÿáö!– èѪËTq©³beò0¢ue¨9o˹;°è®GȆþÊt>œ Tµz2{1’r¯B{ZÌ×<)÷ÇN^hϬŠ$Išc«Žƒ^SŸÁ—”û޵Äug®;ý”Âê7o}ÙC¯®_Î[7jç‘‹Ùì/‘Ý%ˆ‰>ļ¹í¹3‡‡‡@C€”#š"£#¼Oóà"Ö<®9EÓçwÕà†¥©¹¤Ü=ž§îEÊ}~i,ÁFʽ¥x.®ø çCV| àZÊ!P4ðø¨•憄bc#²h6–kU‘Fà‘ƒLDÄ -K O_Ui1¥üܨ§%tŠW§¯û͵¶!p|õZ2mü>®5œkj‘@€Sî‹ÄmäÁ!À!À!À!À!À!À!À!À!À!À!À!À!À!À!À!À!À!À!À!À!À!À!À!À!À!Ptà˜ùE÷Þ:½e¦Kó 7€HÈ?Ö¤’tЂÍÑÙãò¾ª9q§·OÄÌdz;TQW>TkÆ+Ø7#–òÒõ›/G终Çઙ¸$Zl¦™>/ºŒ¯pÒÜ„ÖLtžA [¶ýW„ ™¥Ì9¯óÌÈEppp38Bv1»áì4—­¡í©ûòŸ¿ß4Ýf‰I.Éhç€îÖÅFàÄŒ× IE2 /ûþgYmÔé ÁÏ4[:¶nÐãð‰Ëý²b,ûôúô=Ô˜í?tZÕëd©Q§ÓXÎÉÅr؈ Ê}ƒ7Þ)Ny¾@ gÏ^æ7ìøÎ'é×Z¼¾$@›G¦vNm¬0—œC ;ŒM­ì¡Ü‡@>”ñ§½^¦L…6 ”îa’Ÿy¨:Èøó;Ä^Ù–—÷Ø4íQP­ @:¾ß|în~ù‹xiío/uCB«4«Ó, qûгí•ÁåãÈk”ûyäÍ'ØøÙ€ªŽ–÷£Ëë3"=–ûå°näÈÔ¸<кRüoSÖ1ZžE8N4hÞòX¯ Öê…Á´Á)}Å­ÖžX³Ñ U…ÑîØà/³sÿ‰Ýæ ~‹ûÏçàN]Þ? i3 }÷nsr‚KË>Ø´;âD“öoü$R+jŠ„šgÿ;?œ‘Û ÃjUMõ±GÏÍa ãYÄ×=ü}Û¶«u¿Ð°ëÀHQÌ­Ctý*Aë‹§ò·íü….-é+ƒâè%ã¯<|ôÌヘïݼ0ö‘VU£ü+å*Ì~˜dË)Ç[Æ_xèè™éMÃz¿á‘ðd¬t>µ† }2…Ò‡tØÚ#îîL>è’Ÿ<×±ZLqœ+¶dSî«Um›¢Ò{$¤¤…ªÒâÊx†r?*çö4Ö"ÖãŸÝs¯ê½±¬ÀKÍãçF¬ŽKgœrdL\¢ü`¦Ç˜t /*­‰øõ]3q:=?SïêкÁ¦ÌL5øuz N­îKyÔ ½Ñè[^rþ{)?Þ¥}£éÏÀ#•§M Ójy€£(“-nC ßÐ<¼ðÅÚ‹$¯(9ž^#ÍÙžM›%èÒý˜pl»_Š\?¾Y³Fã óS*¥| B+—k"1ŸQ92''Y®ŸÖ¬YýiþmCË$[Fe캨üOÆ-t1÷Ñêu¸mƒ 8ňÐáœ)l)÷{ÃÞï Iz>ä_EJzæ‡Q^Ê}ëI+½ô'Vìá 9ÐWµ|ëþóéi‹öy4Ý<ãojTQ÷ñbÚÊ„sÅnZ­Ø?öpâQÀÇ]»¾Ó±ã›]z7iÞê²_²$IÌ×FÑH…þúï™Lݲ²& º õÔ =ŽQšTœ2·…A,Ì6ÍFi 77Ÿ£39Fކ/Œ¦ëX¹`ZÓ·Þ™O~rï(ŒŠUÃ3Ú0 Ùð¦isÓc ï¯Z/®ÿÎÛ»½ývX·Ö8"&ŒûoWzk аRB\£7{÷ãh…ùÊ s  áû–œžFÚÕ_šîÏùËÏÜ´–bê›Èñ—ñæÎš5KÄó.3*=—”½ùûë¨þ$Òõ‹íLc8*¢BܯŠüå¼yi1܇€Y,(÷8¢”þ›(÷:‰6'H£TÕPáÇ”BiƤÉPî³ÉÉPîõ'f I•kumµZuØÃ8Í:Ff£ýë;†ëð(ç¡2³6“’;'Ì>tÅ ®­ö!/WO‹‰y~(9!v vvÆ—#Ij,íHÆftr¥dêé«vE\X>\~¡ÏªÎ æÂKu¯|KF9‚ ¥Ë_¦šËÈ‘µúºL&›îã)ž¸|òð$U†²Ä¼° ÑSI¶¯Ž~gömãÔO¾e¹u%]S¿yëq8*iro^Rå>jÂÁˆP’³¤MhGOAëY+@µ.hð©&ûf¬¦‘åƒwx}ñCn©çŒ)a¿`øYòpŽC g’s6#Ð¥Š¦røæóѦÿÍxUßžÕ=-¤ÙLã’+/¡¦ÙjLG#AÍš™f±è¿8-D‚šqLå„„ï–yFÌù–2jª›>tÖÚBP®_Ù§î_zþPë`ãÄKû7í·XD¡ˆó5Q'ΜoweÓ—è M”UJ«W$v¦³¯Æ 5õø²¡K μœ¡¿ð0è=K{§ÞÊ;¡mÙö˜í%E£9¤Ï„Ö(-ü4ãö[ÏEOœ¡Ü.Ý·ŒžUÊ=ìuóÖQ¤tÅ¥ñ‡uì>ôJ²Q¬fv½´s+óf¹(.¶ À|˜ƒ¦rMt.’¼ÅዘŠNàoÙ·èmJܬcOHÑ!WBÆ‹L÷YøÍ&G°ån†ýáù+”råx¥ç“9Ç!§q+ðËÔ͘ÃAø†8eÓ¦M £·ô¹ýu{#«£ò-¼t­Néi„Ð?CŽ(ã¥ï#Ölb–©{Tl”ÉÅ¡ô-*г]ç[N!N5íÀd]Fý#_hÎÖº[‡žÐÔ¹>õ½ŒS_éWyÿF¢F©½öÒpœä^Þ2¾j×Ê“9¿š^+U|ú‹âúÚÚnN9²1.½ô1„¼Á¸¸{Õ q™Zm˜—-“ÒS ›Îœ*Á\3g¡Xaú¾æ:rïºÃæäø¡ÊÃ'®ôcòŸÛ2ïN–5üP„–ãò‘MÁ´Óì9”§•´î|‘‘€Í¤á.±S-A`N;2¾ ÛŠœÌk”S®ËTOC9"¾!jxû@ŸÌeú<žÁ +5)§m ,ƒëÁ„pgëpµr#ͽÄgyV 5°lç¹"gò#«}œÏ˜K{òry8¬Bàèѣ«W¯ FÆÚxYÔ«rg%½h‘Gu¼=z ÊL9ov}³£J-”'½\M©KÉ´Ó÷E\ 'qsáH¼.ñóÏ¢zõâu¡¡á9?­†#ïY ¼g9åt8¢‚gB\åg/íW$ªàܹ(®±Ùb’ù…po@âÿYÿÄ™Ë8sFC¯ÇV“•¯¤Üw¬%®;sÝiã 22Y!LˆÞB£˜´]HÈ[½ß3½¦QänÈG|qàÇS¦+hI¹Ò¶\›¡³wÑB£«1ð» ~·v=¡ ™fîÁˆÈ é1Ü/‡@:\§Æ= …T7k¦3ýº}’OÑXÂï~·æu46)É0Ó@µ ôÕþóÐ¥5îWS®F…¶•ûöG BïyÝ›¿LH ê4#'¯{-U ·„¿0>-ìÖ™SŽØÅ—“Î6ô…Ú¢å}4‚ˆkÕôI ƒÄ½o9q‡íb‹«ü†­Ûm”hU- ˆwIO˜³÷ÈY£•ìâŠ×îÂ…@ƒ>áÁâûû0µæ2ñì¹+F{GLwæ hÔ“s…œ¦»P™iÀ©3Œ;³@ä‰3y^lÈçdr°‰OZ‚øv‰|>?ÑCh8=©w…Á]Î]a³HNv!EàÿˆeüО.„IEND®B`‚gobgp-1.29/docs/sources/rs-policy.svg000066400000000000000000000434141324612745600176400ustar00rootroot00000000000000 A A A B B B C C C Adj-IN Loc-RIB Adj-OUT In Policy Import Policy Export Policy gobgp-1.29/docs/sources/ttl-security.md000066400000000000000000000167361324612745600201770ustar00rootroot00000000000000# TTL Security This page explains how to configure TTL Security in accordance with [RFC3682](https://tools.ietf.org/html/rfc3682): The Generalized TTL Security Mechanism (GTSM). ## Prerequisites Assume you finished [Getting Started](https://github.com/osrg/gobgp/blob/master/docs/sources/getting-started.md). ## Contents - [Configuration](#section0) - [Verification](#section1) ## Configuration If the BGP neighbor "10.0.0.2" is directly connected and the "malicious" BGP router is 2 hops away, you can block the connection from the malicious BGP router with `ttl-min >= 254` in `[neighbors.ttl-security.config]` section. If specify `ttl-min = 255`, this allows only directly connected neighbor, and `ttl-min = 254` allows also the neighbor on 1 hop away. ```toml [global.config] router-id = "10.0.0.1" [[neighbors]] [neighbors.config] neighbor-address = "10.0.0.2" [neighbors.ttl-security.config] enabled = true ttl-min = 255 ``` **NOTE:** TTL Security feature is mututally exclusive with [eBGP Multihop](https://github.com/osrg/gobgp/blob/master/docs/sources/ebgp-multihop.md). These features cannot be configured for the same neighbor. ## Verification With TTL Security configuration, GoBGP will set TTL of all BGP messages to 255 and set the minimal acceptable TTL to the given `ttl-min` value. Then, with the above configuration, only directly connected neighbor "10.0.0.2" is acceptable and the malicious BGP router will be blocked. For the connection from the proper neighbor: ``` $ gobgpd -f gobgpd.toml {"level":"info","msg":"gobgpd started","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Topic":"Config","level":"info","msg":"Finished reading the config file","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"level":"info","msg":"Peer 10.0.0.2 is added","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Topic":"Peer","level":"info","msg":"Add a peer configuration for:10.0.0.2","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Key":"10.0.0.2","State":"BGP_FSM_OPENCONFIRM","Topic":"Peer","level":"info","msg":"Peer Up","time":"YYYY-MM-DDTHH:mm:ss+09:00"} ...(snip)... ``` ``` $ tcpdump -i ethXX tcp -v tcpdump: listening on ethXX, link-type EN10MB (Ethernet), capture size 262144 bytes hh:mm:ss IP (tos 0x0, ttl 255, id 51126, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.2.xxx > 10.0.0.1.bgp: Flags [S], cksum 0x7df2 (correct), seq 889149897, win 29200, options [mss 1460,sackOK,TS val 4431487 ecr 0,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 255, id 0, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.1.bgp > 10.0.0.2.xxx: Flags [S.], cksum 0x8382 (incorrect -> 0x12ac), seq 2886345048, ack 889149898, win 28960, options [mss 1460,sackOK,TS val 4431487 ecr 4431487,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 255, id 51127, offset 0, flags [DF], proto TCP (6), length 52) 10.0.0.2.xxx > 10.0.0.1.bgp: Flags [.], cksum 0x837a (incorrect -> 0xb260), ack 1, win 58, options [nop,nop,TS val 4431487 ecr 4431487], length 0 hh:mm:ss IP (tos 0x0, ttl 255, id 51128, offset 0, flags [DF], proto TCP (6), length 103) 10.0.0.2.xxx > 10.0.0.1.bgp: Flags [P.], cksum 0x83ad (incorrect -> 0x8860), seq 1:52, ack 1, win 58, options [nop,nop,TS val 4431487 ecr 4431487], length 51: BGP Open Message (1), length: 51 Version 4, my AS 65002, Holdtime 90s, ID 2.2.2.2 Optional parameters, length: 22 Option Capabilities Advertisement (2), length: 20 Route Refresh (2), length: 0 Multiprotocol Extensions (1), length: 4 AFI IPv4 (1), SAFI Unicast (1) Multiprotocol Extensions (1), length: 4 AFI IPv6 (2), SAFI Unicast (1) 32-Bit AS Number (65), length: 4 4 Byte AS 65002 hh:mm:ss IP (tos 0x0, ttl 255, id 48934, offset 0, flags [DF], proto TCP (6), length 52) 10.0.0.1.bgp > 10.0.0.2.xxx: Flags [.], cksum 0x837a (incorrect -> 0xb22e), ack 52, win 57, options [nop,nop,TS val 4431487 ecr 4431487], length 0 hh:mm:ss IP (tos 0x0, ttl 255, id 48935, offset 0, flags [DF], proto TCP (6), length 103) 10.0.0.1.bgp > 10.0.0.2.xxx: Flags [P.], cksum 0x83ad (incorrect -> 0x8b31), seq 1:52, ack 52, win 57, options [nop,nop,TS val 4431487 ecr 4431487], length 51: BGP Open Message (1), length: 51 Version 4, my AS 65001, Holdtime 90s, ID 1.1.1.1 Optional parameters, length: 22 Option Capabilities Advertisement (2), length: 20 Route Refresh (2), length: 0 Multiprotocol Extensions (1), length: 4 AFI IPv4 (1), SAFI Unicast (1) Multiprotocol Extensions (1), length: 4 AFI IPv6 (2), SAFI Unicast (1) 32-Bit AS Number (65), length: 4 4 Byte AS 65001 hh:mm:ss IP (tos 0x0, ttl 255, id 51129, offset 0, flags [DF], proto TCP (6), length 52) 10.0.0.2.xxx > 10.0.0.1.bgp: Flags [.], cksum 0x837a (incorrect -> 0xb1fa), ack 52, win 58, options [nop,nop,TS val 4431487 ecr 4431487], length 0 hh:mm:ss IP (tos 0x0, ttl 255, id 51131, offset 0, flags [DF], proto TCP (6), length 52) 10.0.0.2.xxx > 10.0.0.1.bgp: Flags [.], cksum 0x837a (incorrect -> 0xb1ca), ack 71, win 58, options [nop,nop,TS val 4431497 ecr 4431487], length 0 ...(snip)... ``` For the connection from the malicious BGP router: ``` $ gobgpd -f gobgpd.toml {"level":"info","msg":"gobgpd started","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Topic":"Config","level":"info","msg":"Finished reading the config file","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"level":"info","msg":"Peer 10.0.0.2 is added","time":"YYYY-MM-DDTHH:mm:ss+09:00"} {"Topic":"Peer","level":"info","msg":"Add a peer configuration for:10.0.0.2","time":"YYYY-MM-DDTHH:mm:ss+09:00"} ...(No connection)... ``` ``` $ tcpdump -i ethXX tcp -v tcpdump: listening on ethXX, link-type EN10MB (Ethernet), capture size 262144 bytes hh:mm:ss IP (tos 0x0, ttl 253, id 396, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.2.xxx > 10.0.0.1.bgp: Flags [S], cksum 0xf680 (correct), seq 1704340403, win 29200, options [mss 1460,sackOK,TS val 4270655 ecr 0,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 255, id 0, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.1.bgp > 10.0.0.2.xxx: Flags [S.], cksum 0x8382 (incorrect -> 0x1e1a), seq 2916417775, ack 1704340404, win 28960, options [mss 1460,sackOK,TS val 4270656 ecr 4270655,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 253, id 397, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.2.xxx > 10.0.0.1.bgp: Flags [S], cksum 0x8382 (incorrect -> 0xf586), seq 1704340403, win 29200, options [mss 1460,sackOK,TS val 4270905 ecr 0,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 255, id 0, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.1.bgp > 10.0.0.2.xxx: Flags [S.], cksum 0x8382 (incorrect -> 0x1d21), seq 2916417775, ack 1704340404, win 28960, options [mss 1460,sackOK,TS val 4270905 ecr 4270655,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 255, id 0, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.1.bgp > 10.0.0.2.xxx: Flags [S.], cksum 0x8382 (incorrect -> 0x1c27), seq 2916417775, ack 1704340404, win 28960, options [mss 1460,sackOK,TS val 4271155 ecr 4270655,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 253, id 398, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.2.xxx > 10.0.0.1.bgp: Flags [S], cksum 0x8382 (incorrect -> 0xf391), seq 1704340403, win 29200, options [mss 1460,sackOK,TS val 4271406 ecr 0,nop,wscale 9], length 0 hh:mm:ss IP (tos 0x0, ttl 255, id 0, offset 0, flags [DF], proto TCP (6), length 60) 10.0.0.1.bgp > 10.0.0.2.xxx: Flags [S.], cksum 0x8382 (incorrect -> 0x1b2c), seq 2916417775, ack 1704340404, win 28960, options [mss 1460,sackOK,TS val 4271406 ecr 4270655,nop,wscale 9], length 0 ...(snip)... ``` gobgp-1.29/docs/sources/unnumbered-bgp.md000066400000000000000000000063101324612745600204240ustar00rootroot00000000000000# Unnumbered BGP BGP is not only for the Internet. Due to proven scalability and configuration flexibility, large data center operators are using BGP for thier data center networking [[ietf-rtgwg-bgp-routing-large-dc](https://tools.ietf.org/html/rfc7938)]. In typical case, the topology of the network is CLOS network which can offer multiple ECMP for ToR switches. Each ToR switches run BGP daemon and peer to uplink switches connected with P2P link. In this case, since all switches are operated by single administrator and trusted, we can skip tedius neighbor configurations like specifing neighbor address or neighbor AS number by using unnumberd BGP feature. Unnumbered BGP utilizes IPv6 link local address to automatically decide who to connect. Also, when using unnumberd BGP, you don't need to specify neighbor AS number. GoBGP will accept any AS number in the neighbor's open message. ## Prerequisites To use unnumbered BGP feature, be sure the link between two BGP daemons is P2P and IPv6 is enabled on interfaces connected to the link. Also, check neighbor's IPv6 link local address is on the linux's neighbor table. ```bash $ ip -6 neigh show fe80::42:acff:fe11:5 dev eth0 lladdr 02:42:ac:11:00:05 REACHABLE ``` If neighbor's address doesn't exist, easiest way to fill the table is `ping6`. Try the command below ```bash $ ping6 -c 1 ff02::1%eth0 PING ff02::1%eth0 (ff02::1%eth0): 56 data bytes 64 bytes from fe80::42:acff:fe11:5%eth0: icmp_seq=0 ttl=64 time=0.312 ms --- ff02::1%eth0 ping statistics --- 1 packets transmitted, 1 packets received, 0% packet loss round-trip min/avg/max/stddev = 0.312/0.312/0.312/0.000 ms ``` More reliable method is to run [radvd](http://www.litech.org/radvd/) or [zebra](http://www.nongnu.org/quagga/) to periodically send router advertisement. ## Configuration via configuration file ```toml [global.config] as = 64512 router-id = "192.168.255.1" [[neighbors]] [neighbors.config] neighbor-interface = "eth0" ``` ## Configuration via CLI ```bash $ gobgp global as 64512 router-id 192.168.255.1 $ gobgp neighbor add interface eth0 $ gobgp neighbor eth0 BGP neighbor is fe80::42:acff:fe11:3%eth0, remote AS 65001 BGP version 4, remote router ID 192.168.0.2 BGP state = BGP_FSM_ESTABLISHED, up for 00:00:07 BGP OutQ = 0, Flops = 0 Hold time is 90, keepalive interval is 30 seconds Configured hold time is 90, keepalive interval is 30 seconds Neighbor capabilities: multi-protocol: ipv4-unicast: advertised and received ipv6-unicast: advertised and received route-refresh: advertised and received extended-nexthop: advertised and received Local: nlri: ipv4-unicast, nexthop: ipv6 Remote: nlri: ipv4-unicast, nexthop: ipv6 four-octet-as: advertised and received Message statistics: Sent Rcvd Opens: 1 1 Notifications: 0 0 Updates: 1 0 Keepalives: 1 1 Route Refresh: 0 0 Discarded: 0 0 Total: 3 2 Route statistics: Advertised: 1 Received: 0 Accepted: 0 ``` gobgp-1.29/docs/sources/zebra-multipath.md000066400000000000000000000160111324612745600206210ustar00rootroot00000000000000# Equal Cost Multipath Routing with Zebra This page explains how GoBGP handles Equal Cost Multipath (ECMP) routes with Zebra daemon included in [Quagga](http://www.nongnu.org/quagga/) or [FRRouting](https://frrouting.org/). ## Prerequisites Assume you finished [Getting Started](getting-started.md) and [FIB manipulation](zebra.md). ## Contents - [Configuration](#configuration) - [Verification](#verification) ## Configuration **Note:** Before constructing your environment, please confirm your Zebra is built with "--enable-multipath=ARG" configure option. The APT packaged Quagga on Ubuntu 16.04 is configured with this option as following. ```bash $ /usr/lib/quagga/zebra --version zebra version 0.99.24.1 Copyright 1996-2005 Kunihiro Ishiguro, et al. configured with: --build=x86_64-linux-gnu ...(snip)... --enable-multipath=64 ...(snip)... ``` Here supposes the following topology and demonstrates two ECMP routes which advertised from R2 and R3 are installed to R1's Kernel routing table via Zebra. ```text R1: GoBGP + Zebra R2: GoBGP R3: GoBGP +-------------+ +-------------+ | R1 | .1/24 .2/24 | R2 | | ID: 1.1.1.1 |---------------------| ID: 2.2.2.2 | | AS: 65000 | 192.168.12.0/24 | AS: 65000 | +-------------+ +-------------+ | .1/24 | | 192.168.13.0/24 | | .3/24 +-------------+ | R3 | | ID: 3.3.3.3 | | AS: 65000 | +-------------+ ``` To enables ECMP features at GoBGP on R1, please confirm "use-multiple-paths" option is configured as following. With this option, GoBGP will redistribute BGP multipath routes to Zebra and Zebra will install them into Kernel routing table. ```toml # gobgpd.toml on R1 [global.config] as = 65000 router-id = "1.1.1.1" [global.use-multiple-paths.config] enabled = true [[neighbors]] [neighbors.config] neighbor-address = "192.168.12.2" peer-as = 65000 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv6-unicast" [[neighbors]] [neighbors.config] neighbor-address = "192.168.13.3" peer-as = 65000 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv6-unicast" [zebra.config] enabled = true url = "unix:/var/run/quagga/zserv.api" redistribute-route-type-list = ["connect"] version = 2 ``` ```toml # gobgpd.toml on R2 [global.config] as = 65000 router-id = "2.2.2.2" [[neighbors]] [neighbors.config] neighbor-address = "192.168.12.1" peer-as = 65000 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv6-unicast" ``` ```toml # gobgpd.toml on R3 [global.config] as = 65000 router-id = "3.3.3.3" [[neighbors]] [neighbors.config] neighbor-address = "192.168.13.1" peer-as = 65000 [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv4-unicast" [[neighbors.afi-safis]] [neighbors.afi-safis.config] afi-safi-name = "ipv6-unicast" ``` ## Verification When connections established between each routers, all routers have only "connected" routes from Zebra on R1. ```bash R1> gobgp global rib -a ipv4 Network Next Hop AS_PATH Age Attrs *> 1.1.1.1/32 0.0.0.0 00:00:00 [{Origin: i} {Med: 0}] *> 192.168.12.0/24 0.0.0.0 00:00:00 [{Origin: i} {Med: 0}] *> 192.168.13.0/24 0.0.0.0 00:00:00 [{Origin: i} {Med: 0}] R2> gobgp global rib -a ipv4 Network Next Hop AS_PATH Age Attrs *> 1.1.1.1/32 192.168.12.1 00:00:00 [{Origin: i} {Med: 0} {LocalPref: 100}] *> 192.168.12.0/24 192.168.12.1 00:00:00 [{Origin: i} {Med: 0} {LocalPref: 100}] *> 192.168.13.0/24 192.168.12.1 00:00:00 [{Origin: i} {Med: 0} {LocalPref: 100}] ``` And only these routes are installed on R1's Kernel routing table. ```bash R1> ip route 192.168.12.0/24 dev r1-eth1 proto kernel scope link src 192.168.12.1 192.168.13.0/24 dev r1-eth2 proto kernel scope link src 192.168.13.1 ``` Then, let's add new routes destinated to "10.23.1.0/24" on R2 and R3 routes. These routes should be treated as Multipath routes which have the same cost. ```bash R2> gobgp global rib -a ipv4 add 10.23.1.0/24 R2> gobgp global rib -a ipv4 Network Next Hop AS_PATH Age Attrs *> 1.1.1.1/32 192.168.12.1 00:10:00 [{Origin: i} {Med: 0} {LocalPref: 100}] *> 10.23.1.0/24 0.0.0.0 00:00:00 [{Origin: ?}] *> 192.168.12.0/24 192.168.12.1 00:10:00 [{Origin: i} {Med: 0} {LocalPref: 100}] *> 192.168.13.0/24 192.168.12.1 00:10:00 [{Origin: i} {Med: 0} {LocalPref: 100}] R3> gobgp global rib -a ipv4 add 10.23.1.0/24 R3> gobgp global rib -a ipv4 Network Next Hop AS_PATH Age Attrs *> 1.1.1.1/32 192.168.13.1 00:10:00 [{Origin: i} {Med: 0} {LocalPref: 100}] *> 10.23.1.0/24 0.0.0.0 00:00:00 [{Origin: ?}] *> 192.168.12.0/24 192.168.13.1 00:10:00 [{Origin: i} {Med: 0} {LocalPref: 100}] *> 192.168.13.0/24 192.168.13.1 00:10:00 [{Origin: i} {Med: 0} {LocalPref: 100}] ``` GoBGP on R1 will receive these routes and install them into R1's Kernel routing table via Zebra. The following shows that traffic to "10.23.1.0/24" will be forwarded through the interface r1-eth1 (nexthop is R2) or the interface r1-eth2 (nexthop is R3) with the same weight. ```bash R1> gobgp global rib -a ipv4 Network Next Hop AS_PATH Age Attrs *> 1.1.1.1/32 0.0.0.0 00:15:00 [{Origin: i} {Med: 0}] *> 10.23.1.0/24 192.168.12.2 00:05:00 [{Origin: ?} {LocalPref: 100}] * 10.23.1.0/24 192.168.13.3 00:05:00 [{Origin: ?} {LocalPref: 100}] *> 192.168.12.0/24 0.0.0.0 00:15:00 [{Origin: i} {Med: 0}] *> 192.168.13.0/24 0.0.0.0 00:15:00 [{Origin: i} {Med: 0}] R1> ip route 10.23.1.0/24 proto zebra nexthop via 192.168.12.2 dev r1-eth1 weight 1 nexthop via 192.168.13.3 dev r1-eth2 weight 1 192.168.12.0/24 dev r1-eth1 proto kernel scope link src 192.168.12.1 192.168.13.0/24 dev r1-eth2 proto kernel scope link src 192.168.13.1 ``` gobgp-1.29/docs/sources/zebra.md000066400000000000000000000046711324612745600166250ustar00rootroot00000000000000# FIB manipulation This page explains how to perform FIB manipulation; kernel routing table updates, interface lookups, and redistribution of routes between different routing protocols. GoBGP uses zebra included in [Quagga](http://www.nongnu.org/quagga/) or [FRRouting](https://frrouting.org/). ## Prerequisites Assume you finished [Getting Started](getting-started.md) and installing Quagga or FRRouting on the same host with GoBGP. **Note:** For the integration with FRRouting, ONLY version 3.0.x is supported, because the API (using Zebra protocol) of FRRouging is updated so fast and its backward compatibility is not been kept. ## Contents - [Configuration](#configuration) - [Check routes from zebra](#check-routes-from-zebra) ## Configuration You need to enable the zebra feature in the Global configuration as follows. ```toml [zebra] [zebra.config] enabled = true url = "unix:/var/run/quagga/zserv.api" redistribute-route-type-list = ["connect"] version = 2 ``` - `url` specifies the path to the unix domain socket or the TCP port for connecting to Zebra API. If omitted, GoBGP will use `"unix:/var/run/quagga/zserv.api"` by the default. Please note that with FRRouting, the path to the unix domain socket would be like `"unix:/var/run/frr/zserv.api"`. To specify the TCP port, `url` value would be like `"tcp:192.168.24.1:2600"`. - `redistribute-route-type-list` specifies which route types you want to receive from Zebra daemon. For example, with `["connect"]`, GoBGP will receive the connected routes and redistribute them. - `version` specifies Zebra API version. `2` is the version used by Quagga on Ubuntu 16.04 LTS. To enable the Next-Hop Tracking features, please specify `3` or later. For connecting to FRRouting, please specify `4`. ## Check Routes from zebra Zebra has 3 connected routes in this example's environment. - 172.16.1.100/30 - 172.16.6.100/30 - 192.168.31.0/24 Let's check these routes with GoBGP cli. ```bash $ gobgp global rib Network Next Hop AS_PATH Age Attrs *> 172.16.1.100/30 0.0.0.0 00:00:02 [{Origin: i} {Med: 1}] *> 172.16.6.100/30 0.0.0.0 00:00:02 [{Origin: i} {Med: 1}] *> 192.168.31.0/24 0.0.0.0 00:00:02 [{Origin: i} {Med: 1}] ``` You can see connected routes stored in the GoBGP global rib. gobgp-1.29/gobgp/000077500000000000000000000000001324612745600136535ustar00rootroot00000000000000gobgp-1.29/gobgp/cmd/000077500000000000000000000000001324612745600144165ustar00rootroot00000000000000gobgp-1.29/gobgp/cmd/bmp.go000066400000000000000000000050201324612745600155200ustar00rootroot00000000000000// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "fmt" "net" "strconv" "github.com/spf13/cobra" "github.com/osrg/gobgp/config" "github.com/osrg/gobgp/packet/bmp" ) func modBmpServer(cmdType string, args []string) error { if len(args) < 1 { return fmt.Errorf("usage: gobgp bmp %s [:] [{pre|post|both|local-rib|all}]", cmdType) } var address string port := uint32(bmp.BMP_DEFAULT_PORT) if host, p, err := net.SplitHostPort(args[0]); err != nil { ip := net.ParseIP(args[0]) if ip == nil { return nil } address = args[0] } else { address = host // Note: BmpServerConfig.Port is uint32 type, but the TCP/UDP port is // 16-bit length. pn, _ := strconv.ParseUint(p, 10, 16) port = uint32(pn) } var err error switch cmdType { case CMD_ADD: policyType := config.BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY if len(args) > 1 { switch args[1] { case "pre": case "post": policyType = config.BMP_ROUTE_MONITORING_POLICY_TYPE_POST_POLICY case "both": policyType = config.BMP_ROUTE_MONITORING_POLICY_TYPE_BOTH case "local-rib": policyType = config.BMP_ROUTE_MONITORING_POLICY_TYPE_LOCAL_RIB case "all": policyType = config.BMP_ROUTE_MONITORING_POLICY_TYPE_ALL default: return fmt.Errorf("invalid bmp policy type. valid type is {pre|post|both|local-rib|all}") } } err = client.AddBMP(&config.BmpServerConfig{ Address: address, Port: port, RouteMonitoringPolicy: policyType, }) case CMD_DEL: err = client.DeleteBMP(&config.BmpServerConfig{ Address: address, Port: port, }) } return err } func NewBmpCmd() *cobra.Command { bmpCmd := &cobra.Command{ Use: CMD_BMP, } for _, w := range []string{CMD_ADD, CMD_DEL} { subcmd := &cobra.Command{ Use: w, Run: func(cmd *cobra.Command, args []string) { err := modBmpServer(cmd.Use, args) if err != nil { exitWithError(err) } }, } bmpCmd.AddCommand(subcmd) } return bmpCmd } gobgp-1.29/gobgp/cmd/common.go000066400000000000000000000214561324612745600162450ustar00rootroot00000000000000// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "bytes" "encoding/json" "fmt" "net" "os" "sort" "strconv" "strings" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials" cli "github.com/osrg/gobgp/client" "github.com/osrg/gobgp/config" "github.com/osrg/gobgp/packet/bgp" "github.com/osrg/gobgp/table" ) const ( CMD_GLOBAL = "global" CMD_NEIGHBOR = "neighbor" CMD_POLICY = "policy" CMD_RIB = "rib" CMD_ADD = "add" CMD_DEL = "del" CMD_ALL = "all" CMD_SET = "set" CMD_LOCAL = "local" CMD_ADJ_IN = "adj-in" CMD_ADJ_OUT = "adj-out" CMD_RESET = "reset" CMD_SOFT_RESET = "softreset" CMD_SOFT_RESET_IN = "softresetin" CMD_SOFT_RESET_OUT = "softresetout" CMD_SHUTDOWN = "shutdown" CMD_ENABLE = "enable" CMD_DISABLE = "disable" CMD_PREFIX = "prefix" CMD_ASPATH = "as-path" CMD_COMMUNITY = "community" CMD_EXTCOMMUNITY = "ext-community" CMD_IMPORT = "import" CMD_EXPORT = "export" CMD_IN = "in" CMD_MONITOR = "monitor" CMD_MRT = "mrt" CMD_DUMP = "dump" CMD_INJECT = "inject" CMD_RPKI = "rpki" CMD_RPKI_TABLE = "table" CMD_RPKI_SERVER = "server" CMD_VRF = "vrf" CMD_ACCEPTED = "accepted" CMD_REJECTED = "rejected" CMD_STATEMENT = "statement" CMD_CONDITION = "condition" CMD_ACTION = "action" CMD_UPDATE = "update" CMD_ROTATE = "rotate" CMD_BMP = "bmp" CMD_LARGECOMMUNITY = "large-community" CMD_SUMMARY = "summary" CMD_VALIDATION = "validation" ) var subOpts struct { AddressFamily string `short:"a" long:"address-family" description:"specifying an address family"` } var neighborsOpts struct { Reason string `short:"r" long:"reason" description:"specifying communication field on Cease NOTIFICATION message with Administrative Shutdown subcode"` Transport string `short:"t" long:"transport" description:"specifying a transport protocol"` } var conditionOpts struct { Prefix string `long:"prefix" description:"specifying a prefix set name of policy"` Neighbor string `long:"neighbor" description:"specifying a neighbor set name of policy"` AsPath string `long:"aspath" description:"specifying an as set name of policy"` Community string `long:"community" description:"specifying a community set name of policy"` ExtCommunity string `long:"extcommunity" description:"specifying a extended community set name of policy"` AsPathLength string `long:"aspath-len" description:"specifying an as path length of policy (,)"` } var actionOpts struct { RouteAction string `long:"route-action" description:"specifying a route action of policy (accept | reject)"` CommunityAction string `long:"community" description:"specifying a community action of policy"` MedAction string `long:"med" description:"specifying a med action of policy"` AsPathPrependAction string `long:"as-prepend" description:"specifying a as-prepend action of policy"` NexthopAction string `long:"next-hop" description:"specifying a next-hop action of policy"` } var mrtOpts struct { OutputDir string FileFormat string Filename string `long:"filename" description:"MRT file name"` RecordCount int64 `long:"count" description:"Number of records to inject"` RecordSkip int64 `long:"skip" description:"Number of records to skip before injecting"` QueueSize int `long:"batch-size" description:"Maximum number of updates to keep queued"` Best bool `long:"only-best" description:"only keep best path routes"` SkipV4 bool `long:"no-ipv4" description:"Skip importing IPv4 routes"` SkipV6 bool `long:"no-ipv4" description:"Skip importing IPv6 routes"` NextHop net.IP `long:"nexthop" description:"Rewrite nexthop"` } func formatTimedelta(d int64) string { u := uint64(d) neg := d < 0 if neg { u = -u } secs := u % 60 u /= 60 mins := u % 60 u /= 60 hours := u % 24 days := u / 24 if days == 0 { return fmt.Sprintf("%02d:%02d:%02d", hours, mins, secs) } else { return fmt.Sprintf("%dd ", days) + fmt.Sprintf("%02d:%02d:%02d", hours, mins, secs) } } func cidr2prefix(cidr string) string { _, n, err := net.ParseCIDR(cidr) if err != nil { return cidr } var buffer bytes.Buffer for i := 0; i < len(n.IP); i++ { buffer.WriteString(fmt.Sprintf("%08b", n.IP[i])) } ones, _ := n.Mask.Size() return buffer.String()[:ones] } func extractReserved(args, keys []string) map[string][]string { m := make(map[string][]string, len(keys)) var k string isReserved := func(s string) bool { for _, r := range keys { if s == r { return true } } return false } for _, arg := range args { if isReserved(arg) { k = arg m[k] = make([]string, 0, 1) } else { m[k] = append(m[k], arg) } } return m } type neighbors []*config.Neighbor func (n neighbors) Len() int { return len(n) } func (n neighbors) Swap(i, j int) { n[i], n[j] = n[j], n[i] } func (n neighbors) Less(i, j int) bool { p1 := n[i].State.NeighborAddress p2 := n[j].State.NeighborAddress p1Isv4 := !strings.Contains(p1, ":") p2Isv4 := !strings.Contains(p2, ":") if p1Isv4 != p2Isv4 { if p1Isv4 { return true } return false } addrlen := 128 if p1Isv4 { addrlen = 32 } strings := sort.StringSlice{cidr2prefix(fmt.Sprintf("%s/%d", p1, addrlen)), cidr2prefix(fmt.Sprintf("%s/%d", p2, addrlen))} return strings.Less(0, 1) } type capabilities []bgp.ParameterCapabilityInterface func (c capabilities) Len() int { return len(c) } func (c capabilities) Swap(i, j int) { c[i], c[j] = c[j], c[i] } func (c capabilities) Less(i, j int) bool { return c[i].Code() < c[j].Code() } type vrfs []*table.Vrf func (v vrfs) Len() int { return len(v) } func (v vrfs) Swap(i, j int) { v[i], v[j] = v[j], v[i] } func (v vrfs) Less(i, j int) bool { return v[i].Name < v[j].Name } func newClient() *cli.Client { var grpcOpts []grpc.DialOption if globalOpts.TLS { var creds credentials.TransportCredentials if globalOpts.CaFile == "" { creds = credentials.NewClientTLSFromCert(nil, "") } else { var err error creds, err = credentials.NewClientTLSFromFile(globalOpts.CaFile, "") if err != nil { exitWithError(err) } } grpcOpts = []grpc.DialOption{ grpc.WithTimeout(time.Second), grpc.WithBlock(), grpc.WithTransportCredentials(creds), } } target := net.JoinHostPort(globalOpts.Host, strconv.Itoa(globalOpts.Port)) client, err := cli.New(target, grpcOpts...) if err != nil { exitWithError(err) } return client } func addr2AddressFamily(a net.IP) bgp.RouteFamily { if a.To4() != nil { return bgp.RF_IPv4_UC } else if a.To16() != nil { return bgp.RF_IPv6_UC } return bgp.RouteFamily(0) } func checkAddressFamily(def bgp.RouteFamily) (bgp.RouteFamily, error) { var rf bgp.RouteFamily var e error switch subOpts.AddressFamily { case "ipv4", "v4", "4": rf = bgp.RF_IPv4_UC case "ipv6", "v6", "6": rf = bgp.RF_IPv6_UC case "ipv4-l3vpn", "vpnv4", "vpn-ipv4": rf = bgp.RF_IPv4_VPN case "ipv6-l3vpn", "vpnv6", "vpn-ipv6": rf = bgp.RF_IPv6_VPN case "ipv4-labeled", "ipv4-labelled", "ipv4-mpls": rf = bgp.RF_IPv4_MPLS case "ipv6-labeled", "ipv6-labelled", "ipv6-mpls": rf = bgp.RF_IPv6_MPLS case "evpn": rf = bgp.RF_EVPN case "encap", "ipv4-encap": rf = bgp.RF_IPv4_ENCAP case "ipv6-encap": rf = bgp.RF_IPv6_ENCAP case "rtc": rf = bgp.RF_RTC_UC case "ipv4-flowspec", "ipv4-flow", "flow4": rf = bgp.RF_FS_IPv4_UC case "ipv6-flowspec", "ipv6-flow", "flow6": rf = bgp.RF_FS_IPv6_UC case "ipv4-l3vpn-flowspec", "ipv4vpn-flowspec", "flowvpn4": rf = bgp.RF_FS_IPv4_VPN case "ipv6-l3vpn-flowspec", "ipv6vpn-flowspec", "flowvpn6": rf = bgp.RF_FS_IPv6_VPN case "l2vpn-flowspec": rf = bgp.RF_FS_L2_VPN case "opaque": rf = bgp.RF_OPAQUE case "": rf = def default: e = fmt.Errorf("unsupported address family: %s", subOpts.AddressFamily) } return rf, e } func printError(err error) { if globalOpts.Json { j, _ := json.Marshal(struct { Error string `json:"error"` }{Error: err.Error()}) fmt.Println(string(j)) } else { fmt.Println(err) } } func exitWithError(err error) { printError(err) os.Exit(1) } gobgp-1.29/gobgp/cmd/common_test.go000066400000000000000000000022311324612745600172720ustar00rootroot00000000000000// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "fmt" "github.com/stretchr/testify/assert" "strings" "testing" ) func Test_ExtractReserved(t *testing.T) { assert := assert.New(t) args := strings.Split("10 rt 100:100 med 10 nexthop 10.0.0.1 aigp metric 10 local-pref 100", " ") keys := []string{"rt", "med", "nexthop", "aigp", "local-pref"} m := extractReserved(args, keys) fmt.Println(m) assert.True(len(m["rt"]) == 1) assert.True(len(m["med"]) == 1) assert.True(len(m["nexthop"]) == 1) assert.True(len(m["aigp"]) == 2) assert.True(len(m["local-pref"]) == 1) } gobgp-1.29/gobgp/cmd/global.go000066400000000000000000001304241324612745600162110ustar00rootroot00000000000000// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "encoding/json" "fmt" "net" "regexp" "sort" "strconv" "strings" "time" "github.com/spf13/cobra" "github.com/osrg/gobgp/config" "github.com/osrg/gobgp/packet/bgp" "github.com/osrg/gobgp/table" ) type ExtCommType int const ( ACCEPT ExtCommType = iota DISCARD RATE REDIRECT MARK ACTION RT ENCAP ESI_LABEL ROUTER_MAC DEFAULT_GATEWAY VALID NOT_FOUND INVALID ) var ExtCommNameMap = map[ExtCommType]string{ ACCEPT: "accept", DISCARD: "discard", RATE: "rate-limit", REDIRECT: "redirect", MARK: "mark", ACTION: "action", RT: "rt", ENCAP: "encap", ESI_LABEL: "esi-label", ROUTER_MAC: "router-mac", DEFAULT_GATEWAY: "default-gateway", VALID: "valid", NOT_FOUND: "not-found", INVALID: "invalid", } var ExtCommValueMap = map[string]ExtCommType{ ExtCommNameMap[ACCEPT]: ACCEPT, ExtCommNameMap[DISCARD]: DISCARD, ExtCommNameMap[RATE]: RATE, ExtCommNameMap[REDIRECT]: REDIRECT, ExtCommNameMap[MARK]: MARK, ExtCommNameMap[ACTION]: ACTION, ExtCommNameMap[RT]: RT, ExtCommNameMap[ENCAP]: ENCAP, ExtCommNameMap[ESI_LABEL]: ESI_LABEL, ExtCommNameMap[ROUTER_MAC]: ROUTER_MAC, ExtCommNameMap[DEFAULT_GATEWAY]: DEFAULT_GATEWAY, ExtCommNameMap[VALID]: VALID, ExtCommNameMap[NOT_FOUND]: NOT_FOUND, ExtCommNameMap[INVALID]: INVALID, } func rateLimitParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { exp := regexp.MustCompile(fmt.Sprintf("^(%s|(%s) (\\d+)(\\.(\\d+))?)( as (\\d+))?$", ExtCommNameMap[DISCARD], ExtCommNameMap[RATE])) elems := exp.FindStringSubmatch(strings.Join(args, " ")) if len(elems) != 8 { return nil, fmt.Errorf("invalid rate-limit") } var rate float32 var as uint64 if elems[2] == ExtCommNameMap[RATE] { f, err := strconv.ParseFloat(elems[3]+elems[4], 32) if err != nil { return nil, err } rate = float32(f) } if elems[7] != "" { var err error as, err = strconv.ParseUint(elems[7], 10, 16) if err != nil { return nil, err } } return []bgp.ExtendedCommunityInterface{bgp.NewTrafficRateExtended(uint16(as), rate)}, nil } func redirectParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { if len(args) < 2 || args[0] != ExtCommNameMap[REDIRECT] { return nil, fmt.Errorf("invalid redirect") } rt, err := bgp.ParseRouteTarget(strings.Join(args[1:], " ")) if err != nil { return nil, err } switch rt.(type) { case *bgp.TwoOctetAsSpecificExtended: r := rt.(*bgp.TwoOctetAsSpecificExtended) return []bgp.ExtendedCommunityInterface{bgp.NewRedirectTwoOctetAsSpecificExtended(r.AS, r.LocalAdmin)}, nil case *bgp.IPv4AddressSpecificExtended: r := rt.(*bgp.IPv4AddressSpecificExtended) return []bgp.ExtendedCommunityInterface{bgp.NewRedirectIPv4AddressSpecificExtended(r.IPv4.String(), r.LocalAdmin)}, nil case *bgp.FourOctetAsSpecificExtended: r := rt.(*bgp.FourOctetAsSpecificExtended) return []bgp.ExtendedCommunityInterface{bgp.NewRedirectFourOctetAsSpecificExtended(r.AS, r.LocalAdmin)}, nil case *bgp.IPv6AddressSpecificExtended: r := rt.(*bgp.IPv6AddressSpecificExtended) return []bgp.ExtendedCommunityInterface{bgp.NewRedirectIPv6AddressSpecificExtended(r.IPv6.String(), r.LocalAdmin)}, nil } return nil, fmt.Errorf("invalid redirect") } func markParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { if len(args) < 2 || args[0] != ExtCommNameMap[MARK] { return nil, fmt.Errorf("invalid mark") } dscp, err := strconv.ParseUint(args[1], 10, 8) if err != nil { return nil, fmt.Errorf("invalid mark") } return []bgp.ExtendedCommunityInterface{bgp.NewTrafficRemarkExtended(uint8(dscp))}, nil } func actionParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { if len(args) < 2 || args[0] != ExtCommNameMap[ACTION] { return nil, fmt.Errorf("invalid action") } sample := false terminal := false switch args[1] { case "sample": sample = true case "terminal": terminal = true case "terminal-sample", "sample-terminal": sample = true terminal = true default: return nil, fmt.Errorf("invalid action") } return []bgp.ExtendedCommunityInterface{bgp.NewTrafficActionExtended(terminal, sample)}, nil } func rtParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { if len(args) < 2 || args[0] != ExtCommNameMap[RT] { return nil, fmt.Errorf("invalid rt") } exts := make([]bgp.ExtendedCommunityInterface, 0, len(args[1:])) for _, arg := range args[1:] { rt, err := bgp.ParseRouteTarget(arg) if err != nil { return nil, err } exts = append(exts, rt) } return exts, nil } func encapParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { if len(args) < 2 || args[0] != ExtCommNameMap[ENCAP] { return nil, fmt.Errorf("invalid encap") } var typ bgp.TunnelType switch args[1] { case "l2tpv3": typ = bgp.TUNNEL_TYPE_L2TP3 case "gre": typ = bgp.TUNNEL_TYPE_GRE case "ip-in-ip": typ = bgp.TUNNEL_TYPE_IP_IN_IP case "vxlan": typ = bgp.TUNNEL_TYPE_VXLAN case "nvgre": typ = bgp.TUNNEL_TYPE_NVGRE case "mpls": typ = bgp.TUNNEL_TYPE_MPLS case "mpls-in-gre": typ = bgp.TUNNEL_TYPE_MPLS_IN_GRE case "vxlan-gre": typ = bgp.TUNNEL_TYPE_VXLAN_GRE default: return nil, fmt.Errorf("invalid encap type") } return []bgp.ExtendedCommunityInterface{bgp.NewEncapExtended(typ)}, nil } func esiLabelParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { if len(args) < 2 || args[0] != ExtCommNameMap[ESI_LABEL] { return nil, fmt.Errorf("invalid esi-label") } label, err := strconv.ParseUint(args[1], 10, 32) if err != nil { return nil, err } isSingleActive := false if len(args) > 2 { switch args[2] { case "single-active": isSingleActive = true case "all-active": // isSingleActive = false default: return nil, fmt.Errorf("invalid esi-label") } } o := &bgp.ESILabelExtended{ Label: uint32(label), IsSingleActive: isSingleActive, } return []bgp.ExtendedCommunityInterface{o}, nil } func routerMacParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { if len(args) < 2 || args[0] != ExtCommNameMap[ROUTER_MAC] { return nil, fmt.Errorf("invalid router's mac") } hw, err := net.ParseMAC(args[1]) if err != nil { return nil, err } o := &bgp.RouterMacExtended{Mac: hw} return []bgp.ExtendedCommunityInterface{o}, nil } func defaultGatewayParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { if len(args) < 1 || args[0] != ExtCommNameMap[DEFAULT_GATEWAY] { return nil, fmt.Errorf("invalid default-gateway") } return []bgp.ExtendedCommunityInterface{bgp.NewDefaultGatewayExtended()}, nil } func validationParser(args []string) ([]bgp.ExtendedCommunityInterface, error) { if len(args) < 1 { return nil, fmt.Errorf("invalid validation state") } var typ bgp.ValidationState switch args[0] { case "valid": typ = bgp.VALIDATION_STATE_VALID case "not-found": typ = bgp.VALIDATION_STATE_NOT_FOUND case "invalid": typ = bgp.VALIDATION_STATE_INVALID default: return nil, fmt.Errorf("invalid validation state") } return []bgp.ExtendedCommunityInterface{bgp.NewValidationExtended(typ)}, nil } var ExtCommParserMap = map[ExtCommType]func([]string) ([]bgp.ExtendedCommunityInterface, error){ ACCEPT: nil, DISCARD: rateLimitParser, RATE: rateLimitParser, REDIRECT: redirectParser, MARK: markParser, ACTION: actionParser, RT: rtParser, ENCAP: encapParser, ESI_LABEL: esiLabelParser, ROUTER_MAC: routerMacParser, DEFAULT_GATEWAY: defaultGatewayParser, VALID: validationParser, NOT_FOUND: validationParser, INVALID: validationParser, } func ParseExtendedCommunities(args []string) ([]bgp.ExtendedCommunityInterface, error) { idxs := make([]struct { t ExtCommType i int }, 0, len(ExtCommNameMap)) for idx, v := range args { if t, ok := ExtCommValueMap[v]; ok { idxs = append(idxs, struct { t ExtCommType i int }{t, idx}) } } exts := make([]bgp.ExtendedCommunityInterface, 0, len(idxs)) for i, idx := range idxs { var a []string f := ExtCommParserMap[idx.t] if i < len(idxs)-1 { a = args[:idxs[i+1].i-idx.i] args = args[(idxs[i+1].i - idx.i):] } else { a = args args = nil } if f == nil { continue } ext, err := f(a) if err != nil { return nil, err } exts = append(exts, ext...) } if len(args) > 0 { return nil, fmt.Errorf("failed to parse %v", args) } return exts, nil } func ParseFlowSpecArgs(rf bgp.RouteFamily, args []string) (bgp.AddrPrefixInterface, []string, error) { // Format: // match ... [then ...] [rd ] [rt ...] req := 3 // match [ ...] if len(args) < req { return nil, nil, fmt.Errorf("%d args required at least, but got %d", req, len(args)) } m := extractReserved(args, []string{"match", "then", "rd", "rt"}) if len(m["match"]) == 0 { return nil, nil, fmt.Errorf("specify filtering rules with keyword 'match'") } var rd bgp.RouteDistinguisherInterface extcomms := m["then"] switch rf { case bgp.RF_FS_IPv4_VPN, bgp.RF_FS_IPv6_VPN, bgp.RF_FS_L2_VPN: if len(m["rd"]) == 0 { return nil, nil, fmt.Errorf("specify rd") } var err error if rd, err = bgp.ParseRouteDistinguisher(m["rd"][0]); err != nil { return nil, nil, fmt.Errorf("invalid rd: %s", m["rd"][0]) } if len(m["rt"]) > 0 { extcomms = append(extcomms, "rt") extcomms = append(extcomms, m["rt"]...) } default: if len(m["rd"]) > 0 { return nil, nil, fmt.Errorf("cannot specify rd for %s", rf.String()) } if len(m["rt"]) > 0 { return nil, nil, fmt.Errorf("cannot specify rt for %s", rf.String()) } } rules, err := bgp.ParseFlowSpecComponents(rf, strings.Join(m["match"], " ")) if err != nil { return nil, nil, err } var nlri bgp.AddrPrefixInterface switch rf { case bgp.RF_FS_IPv4_UC: nlri = bgp.NewFlowSpecIPv4Unicast(rules) case bgp.RF_FS_IPv6_UC: nlri = bgp.NewFlowSpecIPv6Unicast(rules) case bgp.RF_FS_IPv4_VPN: nlri = bgp.NewFlowSpecIPv4VPN(rd, rules) case bgp.RF_FS_IPv6_VPN: nlri = bgp.NewFlowSpecIPv6VPN(rd, rules) case bgp.RF_FS_L2_VPN: nlri = bgp.NewFlowSpecL2VPN(rd, rules) default: return nil, nil, fmt.Errorf("invalid route family") } return nlri, extcomms, nil } func ParseEvpnEthernetAutoDiscoveryArgs(args []string) (bgp.AddrPrefixInterface, []string, error) { // Format: // esi etag label
] [med ] [local-pref ] [community ] [aigp metric ] [large-community ]`, err, cmdstr, //
modtype, ) helpErrMap[bgp.RF_IPv4_UC] = fmt.Errorf(ucHelpMsgFmt, "ipv4") helpErrMap[bgp.RF_IPv6_UC] = fmt.Errorf(ucHelpMsgFmt, "ipv6") fsHelpMsgFmt := fmt.Sprintf(`error: %s usage: %s rib -a %%s %s%%s match then %%s%%s%%s : { %s | %s | %s [as ] | %s | %s | %s { sample | terminal | sample-terminal } }... : xxx:yyy, xxx.xxx.xxx.xxx:yyy, xxxx::xxxx:yyy, xxx.xxx:yyy`, err, cmdstr, //
modtype, // "" or " rd " // "" or " [rt ]" // // ExtCommNameMap[ACCEPT], ExtCommNameMap[DISCARD], ExtCommNameMap[RATE], ExtCommNameMap[REDIRECT], ExtCommNameMap[MARK], ExtCommNameMap[ACTION], ) baseFsMatchExpr := fmt.Sprintf(` : { %s [] | %s [] | %s ... | %s ... | %s ... | %s ... | %s ... | %s ... | %s ... | %s ... | %s ... | %s ... %%s}... : [&] [<|<=|>|>=|==|!=] : %s : [&] [=|!|!=] : dont-fragment, is-fragment, first-fragment, last-fragment, not-a-fragment : [&] [=|!|!=] : %s%%s : [&] [<|<=|>|>=|==|!=] `, bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_DST_PREFIX], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_SRC_PREFIX], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_IP_PROTO], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_FRAGMENT], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_TCP_FLAG], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_PORT], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_DST_PORT], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_SRC_PORT], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_ICMP_TYPE], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_ICMP_CODE], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_PKT_LEN], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_DSCP], // ipProtocols, tcpFlags, // ) ipv4FsMatchExpr := fmt.Sprintf(baseFsMatchExpr, "", "") ipv6FsMatchExpr := fmt.Sprintf(baseFsMatchExpr, fmt.Sprintf(`| %s ... `, bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_LABEL]), "") l2vpnFsMatchExpr := fmt.Sprintf(baseFsMatchExpr, fmt.Sprintf(`| %s ... | %s | %s | %s ... | %s ... | %s ... | %s ... | %s ... | %s ... | %s ... | %s ... | %s ... `, bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_LABEL], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_DST_MAC], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_SRC_MAC], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_ETHERNET_TYPE], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_LLC_DSAP], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_LLC_SSAP], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_LLC_CONTROL], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_SNAP], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_VID], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_COS], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_INNER_VID], bgp.FlowSpecNameMap[bgp.FLOW_SPEC_TYPE_INNER_COS]), fmt.Sprintf(` : [&] [<|<=|>|>=|==|!=] : %s`, etherTypes)) helpErrMap[bgp.RF_FS_IPv4_UC] = fmt.Errorf(fsHelpMsgFmt, "ipv4-flowspec", "", "", "", ipv4FsMatchExpr) helpErrMap[bgp.RF_FS_IPv6_UC] = fmt.Errorf(fsHelpMsgFmt, "ipv6-flowspec", "", "", "", ipv6FsMatchExpr) helpErrMap[bgp.RF_FS_IPv4_VPN] = fmt.Errorf(fsHelpMsgFmt, "ipv4-l3vpn-flowspec", " rd ", " [rt ]", rdHelpMsgFmt, ipv4FsMatchExpr) helpErrMap[bgp.RF_FS_IPv6_VPN] = fmt.Errorf(fsHelpMsgFmt, "ipv6-l3vpn-flowspec", " rd ", " [rt ]", rdHelpMsgFmt, ipv6FsMatchExpr) helpErrMap[bgp.RF_FS_L2_VPN] = fmt.Errorf(fsHelpMsgFmt, "l2vpn-flowspec", " rd ", " [rt ]", rdHelpMsgFmt, l2vpnFsMatchExpr) helpErrMap[bgp.RF_EVPN] = fmt.Errorf(`error: %s usage: %s rib %s { a-d | macadv | multicast | esi | prefix } -a evpn : esi etag label Check Please check if Golang and Docker is installed correctly and make sure the $GOPATH is defined. ```shell $ go version go version go1.5.1 linux/amd64 $ echo $GOPATH /home/yokoi-h/work $ sudo docker version Client: Version: 1.9.0 API version: 1.21 Go version: go1.4.2 Git commit: 76d6bc9 Built: Tue Nov 3 17:43:42 UTC 2015 OS/Arch: linux/amd64 Server: Version: 1.9.0 API version: 1.21 Go version: go1.4.2 Git commit: 76d6bc9 Built: Tue Nov 3 17:43:42 UTC 2015 OS/Arch: linux/amd64 ``` ## Set up dependencies Execute the following commands inside the VM to install the dependencies: 1. Install pip and [pipework](https://github.com/jpetazzo/pipework). ```shell $ sudo apt-get update $ sudo apt-get install git python-pip python-dev iputils-arping bridge-utils lv $ sudo wget https://raw.github.com/jpetazzo/pipework/master/pipework -O /usr/local/bin/pipework $ sudo chmod 755 /usr/local/bin/pipework ``` 2. Get docker images. Download docker images pertaining to GoBGP testing. ```shell $ sudo docker pull golang:1.7 $ sudo docker pull osrg/gobgp $ sudo docker pull osrg/quagga $ sudo docker pull osrg/quagga:v1.0 $ sudo docker pull osrg/exabgp ``` 3. Clone GoBGP and install python libraries. ```shell $ mkdir -p $GOPATH/src/github.com/osrg $ cd $GOPATH/src/github.com/osrg $ git clone https://github.com/osrg/gobgp.git $ cd ./gobgp/test $ sudo pip install -r pip-requires.txt ``` ## Install local source code You need to install local source code into GoBGP docker container. You also need this operation at every modification to the source code. ```shell $ cd $GOPATH/src/github.com/osrg/gobgp $ sudo fab -f ./test/lib/base.py make_gobgp_ctn --set tag=gobgp ``` ## Run test 1. Run all test. You can run all scenario tests with run_all_tests.sh. If all tests passed, you can see "all tests passed successfully" at the end of the test. ```shell $ cd $GOPATH/src/github.com/osrg/gobgp/test/scenario_test $ ./run_all_tests.sh ... OK all tests passed successfully ``` 2. Run each test. You can run scenario tests individually with each test file. See `test/scenario_test/*.py`, for the individual test files. ```shell $ cd $GOPATH/src/github.com/osrg/gobgp/test/scenario_test $ sudo -E PYTHONPATH=$GOBGP/test python .py ... OK ``` ## Clean up A lot of containers, networks temporary files are created during the test. Let's clean up. ```shell $ sudo docker rm -f $(sudo docker ps -a -q -f "label=gobgp-test") $ sudo docker network prune -f --filter "label=gobgp-test" $ sudo rm -rf /tmp/gobgp ``` gobgp-1.29/test/scenario_test/addpath_test.py000066400000000000000000000232771324612745600214270ustar00rootroot00000000000000# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import time import unittest import nose from fabric.api import local from lib import base from lib.base import ( BGP_FSM_ESTABLISHED, assert_several_times, ) from lib.gobgp import GoBGPContainer from lib.exabgp import ExaBGPContainer from lib.noseplugin import OptionParser, parser_option class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g2 = GoBGPContainer(name='g2', asn=65000, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g3 = GoBGPContainer(name='g3', asn=65000, router_id='192.168.0.3', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) e1 = ExaBGPContainer(name='e1', asn=65000, router_id='192.168.0.4') ctns = [g1, g2, g3, e1] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) g1.add_peer(e1, addpath=True) e1.add_peer(g1, addpath=True) g1.add_peer(g2, addpath=False, is_rr_client=True) g2.add_peer(g1, addpath=False) g1.add_peer(g3, addpath=True, is_rr_client=True) g3.add_peer(g1, addpath=True) cls.g1 = g1 cls.g2 = g2 cls.g3 = g3 cls.e1 = e1 # test each neighbor state is turned establish def test_00_neighbor_established(self): self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g2) self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.e1) # prepare routes with path_id (no error check) def test_01_prepare_add_paths_routes(self): self.e1.add_route(route='192.168.100.0/24', identifier=10, aspath=[100, 200, 300]) self.e1.add_route(route='192.168.100.0/24', identifier=20, aspath=[100, 200]) self.e1.add_route(route='192.168.100.0/24', identifier=30, aspath=[100]) # test three routes are installed to the rib due to add-path feature def test_02_check_g1_global_rib(self): def f(): rib = self.g1.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 3) assert_several_times(f) # test only the best path is advertised to g2 def test_03_check_g2_global_rib(self): def f(): rib = self.g2.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 1) self.assertEqual(rib[0]['paths'][0]['aspath'], [100]) assert_several_times(f) # test three routes are advertised to g3 def test_04_check_g3_global_rib(self): def f(): rib = self.g3.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 3) assert_several_times(f) # withdraw a route with path_id (no error check) def test_05_withdraw_route_with_path_id(self): self.e1.del_route(route='192.168.100.0/24', identifier=30) # test the withdrawn route is removed from the rib def test_06_check_g1_global_rib(self): def f(): rib = self.g1.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 2) for path in rib[0]['paths']: self.assertIn(path['aspath'], ([100, 200, 300], [100, 200])) assert_several_times(f) # test the best path is replaced due to the removal from g1 rib def test_07_check_g2_global_rib(self): def f(): rib = self.g2.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 1) self.assertEqual(rib[0]['paths'][0]['aspath'], [100, 200]) assert_several_times(f) # test the withdrawn route is removed from the rib of g3 def test_08_check_g3_global_rib(self): def f(): rib = self.g3.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 2) for path in rib[0]['paths']: self.assertIn(path['aspath'], ([100, 200, 300], [100, 200])) assert_several_times(f) # install a route with path_id via GoBGP CLI (no error check) def test_09_install_add_paths_route_via_cli(self): # identifier is duplicated with the identifier of the route from e1 self.g1.add_route(route='192.168.100.0/24', identifier=10, local_pref=500) # test the route from CLI is installed to the rib def test_10_check_g1_global_rib(self): def f(): rib = self.g1.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 3) for path in rib[0]['paths']: self.assertIn(path['aspath'], ([100, 200, 300], [100, 200], [])) if not path['aspath']: # path['aspath'] == [] self.assertEqual(path['local-pref'], 500) assert_several_times(f) # test the best path is replaced due to the CLI route from g1 rib def test_11_check_g2_global_rib(self): def f(): rib = self.g2.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 1) self.assertEqual(rib[0]['paths'][0]['aspath'], []) assert_several_times(f) # test the route from CLI is advertised from g1 def test_12_check_g3_global_rib(self): def f(): rib = self.g3.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 3) for path in rib[0]['paths']: self.assertIn(path['aspath'], ([100, 200, 300], [100, 200], [])) if not path['aspath']: # path['aspath'] == [] self.assertEqual(path['local-pref'], 500) assert_several_times(f) # remove non-existing route with path_id via GoBGP CLI (no error check) def test_13_remove_non_existing_add_paths_route_via_cli(self): # specify locally non-existing identifier which has the same value # with the identifier of the route from e1 self.g1.del_route(route='192.168.100.0/24', identifier=20) # test none of route is removed by non-existing path_id via CLI def test_14_check_g1_global_rib(self): def f(): rib = self.g1.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 3) for path in rib[0]['paths']: self.assertIn(path['aspath'], ([100, 200, 300], [100, 200], [])) if not path['aspath']: # path['aspath'] == [] self.assertEqual(path['local-pref'], 500) assert_several_times(f) # remove route with path_id via GoBGP CLI (no error check) def test_15_remove_add_paths_route_via_cli(self): self.g1.del_route(route='192.168.100.0/24', identifier=10) # test the route is removed from the rib via CLI def test_16_check_g1_global_rib(self): def f(): rib = self.g1.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 2) for path in rib[0]['paths']: self.assertIn(path['aspath'], ([100, 200, 300], [100, 200])) assert_several_times(f) # test the best path is replaced the removal from g1 rib def test_17_check_g2_global_rib(self): def f(): rib = self.g2.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 1) self.assertEqual(rib[0]['paths'][0]['aspath'], [100, 200]) assert_several_times(f) # test the removed route from CLI is withdrawn by g1 def test_18_check_g3_global_rib(self): def f(): rib = self.g3.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 2) for path in rib[0]['paths']: self.assertIn(path['aspath'], ([100, 200, 300], [100, 200])) assert_several_times(f) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/aspath_test.py000066400000000000000000000121121324612745600212640ustar00rootroot00000000000000# Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( BGP_FSM_ESTABLISHED, assert_several_times, ) from lib.gobgp import GoBGPContainer from lib.quagga import QuaggaBGPContainer class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65001, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) q1 = QuaggaBGPContainer(name='q1', asn=65002, router_id='192.168.0.2') g2 = GoBGPContainer(name='g2', asn=65001, router_id='192.168.0.3', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) ctns = [g1, g2, q1] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) g1.add_peer(q1) q1.add_peer(g1) q1.add_peer(g2) g2.add_peer(q1) g1.add_route('10.0.0.0/24') cls.g1 = g1 cls.g2 = g2 cls.q1 = q1 cls.ctns = {n.name: n for n in ctns} # test each neighbor state is turned establish def test_01_neighbor_established(self): self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.q1) self.q1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g2) def test_02_check_reject_as_loop(self): def f(): self.assertEqual(len(self.g2.get_global_rib()), 0) assert_several_times(f) def test_03_update_peer(self): self.g2.update_peer(self.q1, allow_as_in=10) self.q1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g2) def test_04_check_accept_as_loop(self): def f(): self.assertEqual(len(self.g2.get_global_rib()), 1) assert_several_times(f) def test_05_check_remove_private_as_peer_all(self): g3 = GoBGPContainer(name='g3', asn=100, router_id='192.168.0.4', ctn_image_name=parser_option.gobgp_image, log_level=parser_option.gobgp_log_level) g4 = GoBGPContainer(name='g4', asn=200, router_id='192.168.0.5', ctn_image_name=parser_option.gobgp_image, log_level=parser_option.gobgp_log_level) time.sleep(max(ctn.run() for ctn in [g3, g4])) self.ctns['g3'] = g3 self.ctns['g4'] = g4 self.g2.add_peer(g3) g3.add_peer(self.g2) g3.add_peer(g4, remove_private_as='all') g4.add_peer(g3) self.g2.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g3) g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g4) def f(): rib = g4.get_global_rib() self.assertEqual(len(rib), 1) self.assertEqual(len(rib[0]['paths']), 1) self.assertEqual(rib[0]['paths'][0]['aspath'], [100]) assert_several_times(f) def test_06_check_remove_private_as_peer_replace(self): g3 = self.ctns['g3'] g4 = self.ctns['g4'] g3.update_peer(g4, remove_private_as='replace') g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g4) def f(): rib = g4.get_global_rib() self.assertEqual(rib[0]['paths'][0]['aspath'], [100, 100, 100, 100]) assert_several_times(f) def test_07_check_replace_peer_as(self): g5 = GoBGPContainer(name='g5', asn=100, router_id='192.168.0.6', ctn_image_name=parser_option.gobgp_image, log_level=parser_option.gobgp_log_level) time.sleep(g5.run()) g4 = self.ctns['g4'] g4.add_peer(g5, replace_peer_as=True) g5.add_peer(g4) g4.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g5) def f(): rib = g5.get_global_rib() self.assertEqual(rib[0]['paths'][0]['aspath'], [200, 200, 200, 200, 200]) assert_several_times(f) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/bgp_confederation_test.py000066400000000000000000000143071324612745600234640ustar00rootroot00000000000000# Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer from lib.quagga import QuaggaBGPContainer class GoBGPTestBase(unittest.TestCase): def _check_global_rib_first(self, q, prefix, aspath): route = q.get_global_rib(prefix)[0] self.assertListEqual(aspath, route['aspath']) @classmethod def setUpClass(cls): # +-----Confederation(AS30)-----+ # AS21 AS20 | +-AS65002-+ +-AS65001-+ | AS10 # +----+ +----+ | | +-----+ | | +-----+ | | +----+ # | q3 |---| q2 |--+-+-| g1 |-+-----+-| q11 |-+-+--| q1 | # +----+ +----+ | | +-----+ | | +-----+ | | +----+ # | | | | | | | | # | | | | | | | | # | | | | | | | | # | | +-----+ | | +-----+ | | # | | | q22 | | | | q12 | | | # | | +-----+ | | +-----+ | | # | +---------+ +---------+ | # +-----------------------------+ gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix bgp_conf_1 = {'global': {'confederation': {'config': { 'enabled': True, 'identifier': 30, 'member-as-list': [65002]}}}} bgp_conf_2 = {'global': {'confederation': {'config': { 'enabled': True, 'identifier': 30, 'member-as-list': [65001]}}}} g1 = GoBGPContainer(name='g1', asn=65002, router_id='192.168.2.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, bgp_config=bgp_conf_2) q1 = QuaggaBGPContainer(name='q1', asn=10, router_id='1.1.1.1') q2 = QuaggaBGPContainer(name='q2', asn=20, router_id='2.2.2.2') q3 = QuaggaBGPContainer(name='q3', asn=21, router_id='3.3.3.3') q11 = QuaggaBGPContainer(name='q11', asn=65001, router_id='192.168.1.1', bgpd_config=bgp_conf_1) q12 = QuaggaBGPContainer(name='q12', asn=65001, router_id='192.168.1.2', bgpd_config=bgp_conf_1) q22 = QuaggaBGPContainer(name='q22', asn=65002, router_id='192.168.2.2', bgpd_config=bgp_conf_2) ctns = [g1, q1, q2, q3, q11, q12, q22] cls.initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(cls.initial_wait_time) q1.add_peer(q11, remote_as=30) q11.add_peer(q1) q11.add_peer(q12) q12.add_peer(q11) g1.add_peer(q11) q11.add_peer(g1) g1.add_peer(q22) q22.add_peer(g1) g1.add_peer(q2) q2.add_peer(g1, remote_as=30) q3.add_peer(q2) q2.add_peer(q3) cls.gobgp = g1 cls.quaggas = {'q1': q1, 'q2': q2, 'q3': q3, 'q11': q11, 'q12': q12, 'q22': q22} # test each neighbor state is turned establish def test_01_neighbor_established(self): self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.quaggas['q11']) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.quaggas['q22']) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.quaggas['q2']) self.quaggas['q11'].wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.quaggas['q1']) self.quaggas['q11'].wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.quaggas['q12']) self.quaggas['q2'].wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.quaggas['q3']) def test_02_route_advertise(self): self.quaggas['q3'].add_route('10.0.0.0/24') time.sleep(self.initial_wait_time) routes = [] for i in range(60): routes = self.quaggas['q1'].get_global_rib('10.0.0.0/24') if len(routes) > 0: break time.sleep(1) self.failIf(len(routes) == 0) # Confirm AS_PATH in confederation is removed self._check_global_rib_first(self.quaggas['q1'], '10.0.0.0/24', [30, 20, 21]) # Confirm AS_PATH in confederation is not removed self._check_global_rib_first(self.quaggas['q11'], '10.0.0.0/24', [65002, 20, 21]) self._check_global_rib_first(self.quaggas['q22'], '10.0.0.0/24', [20, 21]) def test_03_best_path(self): self.quaggas['q1'].add_route('10.0.0.0/24') routes = [] for i in range(60): routes = self.gobgp.get_global_rib('10.0.0.0/24') print(routes) if len(routes) == 1: if len(routes[0]['paths']) == 2: break time.sleep(1) self.failIf(len(routes) != 1) self.failIf(len(routes[0]['paths']) != 2) # In g1, there are two routes to 10.0.0.0/24 # confirm the route from q1 is selected as the best path # because it has shorter AS_PATH. # (AS_CONFED_* segments in AS_PATH is not counted) paths = routes[0]['paths'] self.assertTrue(paths[0]['aspath'], [65001, 10]) # confirm the new best path is advertised self._check_global_rib_first(self.quaggas['q22'], '10.0.0.0/24', [65001, 10]) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/bgp_malformed_msg_handling_test.py000066400000000000000000000073141324612745600253240ustar00rootroot00000000000000# Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer from lib.exabgp import ExaBGPContainer class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) e1 = ExaBGPContainer(name='e1', asn=65001, router_id='192.168.0.2') ctns = [g1, e1] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) g1.add_peer(e1, treat_as_withdraw=True) e1.add_peer(g1) cls.g1 = g1 cls.e1 = e1 # test each neighbor state is turned establish def test_01_neighbor_established(self): self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.e1) def test_02_attribute_discard(self): # Malformed attribute 'AGGREGATOR' should be discard, but the session should not be disconnected. self.e1.add_route('10.0.0.0/24', attribute='0x07 0xc0 0x0000006400') # Confirm the session is not disconnected for _ in range(5): state = self.g1.get_neighbor_state(self.e1) self.assertTrue(BGP_FSM_ESTABLISHED, state) time.sleep(1) # Confirm the path is added dests = self.g1.get_global_rib() self.assertTrue(len(dests) == 1) routes = dests[0]['paths'] self.assertTrue(len(routes) == 1) # Confirm the attribute 'AGGREGATOR(type=7)' is discarded for d in routes[0]['attrs']: self.assertFalse(d['type'] == 7) self.e1.del_route('10.0.0.0/24') def test_03_treat_as_withdraw(self): # Malformed attribute 'MULTI_EXIT_DESC' should be treated as withdraw, # but the session should not be disconnected. self.e1.add_route('20.0.0.0/24', attribute='0x04 0x80 0x00000064') self.e1.add_route('30.0.0.0/24', attribute='0x04 0x80 0x00000064') # Malformed self.e1.add_route('30.0.0.0/24', attribute='0x04 0x80 0x0000000064') # Confirm the session is not disconnected for _ in range(5): state = self.g1.get_neighbor_state(self.e1) self.assertTrue(BGP_FSM_ESTABLISHED, state) time.sleep(1) # Confirm the number of path in RIB is only one dests = self.g1.get_global_rib() self.assertTrue(len(dests) == 1) self.assertTrue(dests[0]['paths'][0]['nlri']['prefix'] == '20.0.0.0/24') if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/bgp_router_test.py000066400000000000000000000377751324612745600222020ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import json import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( BGP_FSM_IDLE, BGP_FSM_ACTIVE, BGP_FSM_ESTABLISHED, BGP_ATTR_TYPE_MULTI_EXIT_DISC, BGP_ATTR_TYPE_LOCAL_PREF, wait_for_completion, assert_several_times, ) from lib.gobgp import ( GoBGPContainer, extract_path_attribute, ) from lib.quagga import QuaggaBGPContainer from lib.exabgp import ExaBGPContainer class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) q1 = QuaggaBGPContainer(name='q1', asn=65001, router_id='192.168.0.2') q2 = QuaggaBGPContainer(name='q2', asn=65002, router_id='192.168.0.3') q3 = QuaggaBGPContainer(name='q3', asn=65003, router_id='192.168.0.4') qs = [q1, q2, q3] ctns = [g1, q1, q2, q3] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) for q in qs: g1.add_peer(q, passwd='passwd') q.add_peer(g1, passwd='passwd', passive=True) # advertise a route from q1, q2, q3 for idx, q in enumerate(qs): route = '10.0.{0}.0/24'.format(idx + 1) q.add_route(route) cls.gobgp = g1 cls.quaggas = {'q1': q1, 'q2': q2, 'q3': q3} # test each neighbor state is turned establish def test_01_neighbor_established(self): for q in self.quaggas.itervalues(): self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q) def test_02_check_gobgp_global_rib(self): for q in self.quaggas.itervalues(): # paths expected to exist in gobgp's global rib routes = q.routes.keys() timeout = 120 interval = 1 count = 0 while True: # gobgp's global rib state = self.gobgp.get_neighbor_state(q) self.assertEqual(state, BGP_FSM_ESTABLISHED) global_rib = [p['prefix'] for p in self.gobgp.get_global_rib()] for p in global_rib: if p in routes: routes.remove(p) if len(routes) == 0: break time.sleep(interval) count += interval if count >= timeout: raise Exception('timeout') # check gobgp properly add it's own asn to aspath def test_03_check_gobgp_adj_out_rib(self): for q in self.quaggas.itervalues(): for path in self.gobgp.get_adj_rib_out(q): asns = path['aspath'] self.assertTrue(self.gobgp.asn in asns) # check routes are properly advertised to all BGP speaker def test_04_check_quagga_global_rib(self): interval = 1 timeout = int(120 / interval) for q in self.quaggas.itervalues(): done = False for _ in range(timeout): if done: break global_rib = q.get_global_rib() global_rib = [p['prefix'] for p in global_rib] if len(global_rib) < len(self.quaggas): time.sleep(interval) continue self.assertTrue(len(global_rib) == len(self.quaggas)) for c in self.quaggas.itervalues(): for r in c.routes: self.assertTrue(r in global_rib) done = True if done: continue # should not reach here raise AssertionError def test_05_add_quagga(self): q4 = QuaggaBGPContainer(name='q4', asn=65004, router_id='192.168.0.5') self.quaggas['q4'] = q4 initial_wait_time = q4.run() time.sleep(initial_wait_time) self.gobgp.add_peer(q4) q4.add_peer(self.gobgp) q4.add_route('10.0.4.0/24') self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q4) def test_06_check_global_rib(self): self.test_02_check_gobgp_global_rib() self.test_04_check_quagga_global_rib() def test_07_stop_one_quagga(self): g1 = self.gobgp q4 = self.quaggas['q4'] q4.stop() self.gobgp.wait_for(expected_state=BGP_FSM_ACTIVE, peer=q4) g1.del_peer(q4) del self.quaggas['q4'] # check gobgp properly send withdrawal message with q4's route def test_08_check_global_rib(self): self.test_02_check_gobgp_global_rib() self.test_04_check_quagga_global_rib() def test_09_add_distant_relative(self): q1 = self.quaggas['q1'] q2 = self.quaggas['q2'] q3 = self.quaggas['q3'] q5 = QuaggaBGPContainer(name='q5', asn=65005, router_id='192.168.0.6') initial_wait_time = q5.run() time.sleep(initial_wait_time) for q in [q2, q3]: q5.add_peer(q) q.add_peer(q5) med200 = {'name': 'med200', 'type': 'permit', 'match': '0.0.0.0/0', 'med': 200} q2.add_policy(med200, self.gobgp, 'out') med100 = {'name': 'med100', 'type': 'permit', 'match': '0.0.0.0/0', 'med': 100} q3.add_policy(med100, self.gobgp, 'out') q5.add_route('10.0.6.0/24') self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q2) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q3) q2.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q5) q3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q5) timeout = 120 interval = 1 count = 0 while True: paths = self.gobgp.get_adj_rib_out(q1, '10.0.6.0/24') if len(paths) > 0: path = paths[0] print "{0}'s nexthop is {1}".format(path['nlri']['prefix'], path['nexthop']) n_addrs = [i[1].split('/')[0] for i in self.gobgp.ip_addrs] if path['nexthop'] in n_addrs: break time.sleep(interval) count += interval if count >= timeout: raise Exception('timeout') def test_10_originate_path(self): self.gobgp.add_route('10.10.0.0/24') dst = self.gobgp.get_global_rib('10.10.0.0/24') self.assertTrue(len(dst) == 1) self.assertTrue(len(dst[0]['paths']) == 1) path = dst[0]['paths'][0] self.assertTrue(path['nexthop'] == '0.0.0.0') self.assertTrue(len(path['aspath']) == 0) def test_11_check_adj_rib_out(self): for q in self.quaggas.itervalues(): paths = self.gobgp.get_adj_rib_out(q, '10.10.0.0/24') self.assertTrue(len(paths) == 1) path = paths[0] peer_info = self.gobgp.peers[q] local_addr = peer_info['local_addr'].split('/')[0] self.assertTrue(path['nexthop'] == local_addr) self.assertTrue(path['aspath'] == [self.gobgp.asn]) def test_12_disable_peer(self): q1 = self.quaggas['q1'] self.gobgp.disable_peer(q1) self.gobgp.wait_for(expected_state=BGP_FSM_IDLE, peer=q1) time.sleep(3) for route in q1.routes.iterkeys(): dst = self.gobgp.get_global_rib(route) self.assertTrue(len(dst) == 0) for q in self.quaggas.itervalues(): if q is q1: continue paths = self.gobgp.get_adj_rib_out(q, route) self.assertTrue(len(paths) == 0) def test_13_enable_peer(self): q1 = self.quaggas['q1'] self.gobgp.enable_peer(q1) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q1) def test_14_check_adj_rib_out(self): self.test_11_check_adj_rib_out() def test_15_check_active_connection(self): g1 = self.gobgp g2 = GoBGPContainer(name='g2', asn=65000, router_id='192.168.0.7', ctn_image_name=self.gobgp.image, log_level=parser_option.gobgp_log_level) time.sleep(g2.run()) self.quaggas['g2'] = g2 g2.add_peer(g1, passive=True) g1.add_peer(g2) g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g2) def test_16_check_local_pref_and_med_handling(self): g1 = self.gobgp g1.add_route('10.20.0.0/24', local_pref=1000, med=2000) # iBGP peer g2 = self.quaggas['g2'] paths = g2.get_global_rib('10.20.0.0/24') self.assertTrue(len(paths) == 1) self.assertTrue(len(paths[0]['paths']) == 1) path = paths[0]['paths'][0] local_pref = extract_path_attribute(path, BGP_ATTR_TYPE_LOCAL_PREF) self.assertTrue(local_pref['value'] == 1000) med = extract_path_attribute(path, BGP_ATTR_TYPE_MULTI_EXIT_DISC) self.assertTrue(med['metric'] == 2000) # eBGP peer q1 = self.quaggas['q1'] paths = q1.get_global_rib('10.20.0.0/24') self.assertTrue(len(paths) == 1) path = paths[0] local_pref = extract_path_attribute(path, BGP_ATTR_TYPE_LOCAL_PREF) # local_pref's default value is 100 self.assertTrue(local_pref['value'] == 100) med = extract_path_attribute(path, BGP_ATTR_TYPE_MULTI_EXIT_DISC) self.assertTrue(med['metric'] == 2000) def test_17_check_shutdown(self): g1 = self.gobgp q1 = self.quaggas['q1'] q2 = self.quaggas['q2'] q3 = self.quaggas['q3'] q2.add_route('20.0.0.0/24') q3.add_route('20.0.0.0/24') self.test_01_neighbor_established() self.test_02_check_gobgp_global_rib() paths = q1.get_global_rib('20.0.0.0/24') self.assertEqual(len(paths), 1) n_addrs = [i[1].split('/')[0] for i in self.gobgp.ip_addrs] self.assertIn(paths[0]['nexthop'], n_addrs) q3.stop() self.gobgp.wait_for(expected_state=BGP_FSM_ACTIVE, peer=q3) paths = q1.get_global_rib('20.0.0.0/24') self.assertEqual(len(paths), 1) self.assertIn(paths[0]['nexthop'], n_addrs) g1.del_peer(q3) del self.quaggas['q3'] def test_18_check_withdrawal(self): g1 = self.gobgp q1 = self.quaggas['q1'] q2 = self.quaggas['q2'] g1.add_route('30.0.0.0/24') q1.add_route('30.0.0.0/24') self.test_01_neighbor_established() self.test_02_check_gobgp_global_rib() paths = g1.get_adj_rib_out(q1, '30.0.0.0/24') self.assertEqual(len(paths), 1) self.assertNotIn('source-id', paths[0]) paths = g1.get_adj_rib_out(q2, '30.0.0.0/24') self.assertEqual(len(paths), 1) self.assertNotIn('source-id', paths[0]) g1.local('gobgp global rib del 30.0.0.0/24') def f(): paths = g1.get_adj_rib_out(q1, '30.0.0.0/24') self.assertEqual(len(paths), 0) paths = g1.get_adj_rib_out(q2, '30.0.0.0/24') self.assertEqual(len(paths), 1) self.assertEqual(paths[0]['source-id'], '192.168.0.2') assert_several_times(f) def test_19_check_grpc_add_neighbor(self): g1 = self.gobgp e1 = ExaBGPContainer(name='e1', asn=65000, router_id='192.168.0.7') time.sleep(e1.run()) e1.add_peer(g1) self.quaggas['e1'] = e1 n = e1.peers[g1]['local_addr'].split('/')[0] g1.local('gobgp n add {0} as 65000'.format(n)) g1.add_peer(e1, reload_config=False) g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=e1) def test_20_check_grpc_del_neighbor(self): g1 = self.gobgp e1 = self.quaggas['e1'] n = e1.peers[g1]['local_addr'].split('/')[0] g1.local('gobgp n del {0}'.format(n)) g1.del_peer(e1, reload_config=False) def test_21_check_withdrawal_2(self): g1 = self.gobgp g2 = self.quaggas['g2'] prefix = '40.10.0.0/24' g1.add_route(prefix) wait_for_completion(lambda: len(g1.get_global_rib(prefix)) == 1) wait_for_completion(lambda: len(g2.get_global_rib(prefix)) == 1) r = g2.local('gobgp monitor global rib -j', stream=True, tty=False) g1.local('gobgp global rib del 40.10.0.0/24') del g1.routes[prefix] wait_for_completion(lambda: len(g1.get_global_rib(prefix)) == 0) wait_for_completion(lambda: len(g2.get_global_rib(prefix)) == 0) ret = json.loads(r.next()) self.assertTrue(ret[0]['nlri']['prefix'] == prefix) self.assertTrue('withdrawal' in ret[0]) def test_22_check_cli_sorted(self): g1 = self.gobgp cnt = 0 def next_prefix(): for i in range(100, 105): for j in range(100, 105): yield '{0}.{1}.0.0/24'.format(i, j) for p in next_prefix(): g1.local('gobgp global rib add {0}'.format(p)) cnt += 1 cnt2 = 0 g = next_prefix() n = g.next() for path in g1.local("gobgp global rib", capture=True).split('\n')[1:]: if [elem for elem in path.split(' ') if elem != ''][1] == n: try: cnt2 += 1 n = g.next() except StopIteration: break self.assertTrue(cnt == cnt2) def test_23_check_withdrawal3(self): gobgp_ctn_image_name = parser_option.gobgp_image g1 = self.gobgp g3 = GoBGPContainer(name='g3', asn=65006, router_id='192.168.0.8', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g4 = GoBGPContainer(name='g4', asn=65007, router_id='192.168.0.9', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) initial_wait_time = max(ctn.run() for ctn in [g3, g4]) time.sleep(initial_wait_time) self.quaggas = {'g3': g3, 'g4': g4} g3.local('gobgp global rib add 50.0.0.0/24') g1.add_peer(g3, passive=True) g3.add_peer(g1) g1.add_peer(g4, passive=True) g4.add_peer(g1) self.test_01_neighbor_established() self.test_02_check_gobgp_global_rib() g4.local('gobgp global rib add 50.0.0.0/24 med 10') paths = g1.get_adj_rib_out(g3, '50.0.0.0/24') self.assertTrue(len(paths) == 0) paths = g1.get_adj_rib_out(g4, '50.0.0.0/24') self.assertTrue(len(paths) == 1) self.assertTrue(paths[0]['source-id'] == '192.168.0.8') g3.local('gobgp global rib del 50.0.0.0/24') paths = g1.get_adj_rib_out(g3, '50.0.0.0/24') self.assertTrue(len(paths) == 1) self.assertTrue(paths[0]['source-id'] == '192.168.0.9') paths = g1.get_adj_rib_out(g4, '50.0.0.0/24') self.assertTrue(len(paths) == 0) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/bgp_unnumbered_test.py000066400000000000000000000061151324612745600230060ustar00rootroot00000000000000# Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import unittest from fabric.api import local from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer import sys import os import time import nose from lib.noseplugin import OptionParser, parser_option from itertools import combinations class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g2 = GoBGPContainer(name='g2', asn=65001, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) ctns = [g1, g2] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time + 2) done = False def f(ifname, ctn): out = ctn.local('ip -6 n', capture=True) l = [line for line in out.split('\n') if ifname in line] if len(l) == 0: return False elif len(l) > 1: raise Exception('not p2p link') return 'REACHABLE' in l[0] for i in range(20): g1.local('ping6 -c 1 ff02::1%eth0') g2.local('ping6 -c 1 ff02::1%eth0') if f('eth0', g1) and f('eth0', g2): done = True break time.sleep(1) if not done: raise Exception('timeout') for a, b in combinations(ctns, 2): a.add_peer(b, interface='eth0') b.add_peer(a, interface='eth0') cls.g1 = g1 cls.g2 = g2 # test each neighbor state is turned establish def test_01_neighbor_established(self): self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g2) def test_02_add_ipv4_route(self): self.g1.add_route('10.0.0.0/24') time.sleep(1) rib = self.g2.get_global_rib(rf='ipv4') self.assertTrue(len(rib) == 1) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/bgp_zebra_nht_test.py000066400000000000000000000212411324612745600226130ustar00rootroot00000000000000# Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import time import unittest from fabric.api import local from fabric.state import env import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( Bridge, BGP_FSM_ESTABLISHED, ) from lib.gobgp import GoBGPContainer from lib.quagga import QuaggaOSPFContainer def try_local(command, f=local, ok_ret_codes=None, **kwargs): ok_ret_codes = ok_ret_codes or [] orig_ok_ret_codes = list(env.ok_ret_codes) try: env.ok_ret_codes.extend(ok_ret_codes) return f(command, **kwargs) finally: env.ok_ret_codes = orig_ok_ret_codes def wait_for(f, timeout=120): interval = 1 count = 0 while True: if f(): return time.sleep(interval) count += interval if count >= timeout: raise Exception('timeout') def get_ifname_with_prefix(prefix, f=local): command = ( "ip addr show to %s" " | head -n1 | cut -d'@' -f1 | cut -d' ' -f2") % prefix return f(command, capture=True) class ZebraNHTTest(unittest.TestCase): """ Test case for Next-Hop Tracking with Zebra integration. """ # R1: GoBGP # R2: GoBGP + Zebra + OSPFd # R3: Zebra + OSPFd # R4: Zebra + OSPFd # # +----+ # | R3 |... has loopback 10.3.1.1/32 # +----+ # / | # / | # / +----+ # / | R4 | # / +----+ # +----+ | # | R2 |------+ # +----+ # | 192.168.0.2/24 # | # | 192.168.0.0/24 # | # | 192.168.0.1/24 # +----+ # | R1 | # +----+ @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix local("echo 'start %s'" % cls.__name__, capture=True) cls.r1 = GoBGPContainer( name='r1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, zebra=False) cls.r2 = GoBGPContainer( name='r2', asn=65000, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, zebra=True, zapi_version=3, ospfd_config={ 'networks': { '192.168.23.0/24': '0.0.0.0', '192.168.24.0/24': '0.0.0.0', }, }) cls.r3 = QuaggaOSPFContainer( name='r3', zebra_config={ 'interfaces': { 'lo': [ 'ip address 10.3.1.1/32', ], }, }, ospfd_config={ 'networks': { '10.3.1.1/32': '0.0.0.0', '192.168.23.0/24': '0.0.0.0', '192.168.34.0/24': '0.0.0.0', }, }) cls.r4 = QuaggaOSPFContainer( name='r4', ospfd_config={ 'networks': { '192.168.34.0/24': '0.0.0.0', '192.168.24.0/24': '0.0.0.0', }, }) wait_time = max(ctn.run() for ctn in [cls.r1, cls.r2, cls.r3, cls.r4]) time.sleep(wait_time) cls.br_r1_r2 = Bridge(name='br_r1_r2', subnet='192.168.12.0/24') [cls.br_r1_r2.addif(ctn) for ctn in (cls.r1, cls.r2)] cls.br_r2_r3 = Bridge(name='br_r2_r3', subnet='192.168.23.0/24') [cls.br_r2_r3.addif(ctn) for ctn in (cls.r2, cls.r3)] cls.br_r2_r4 = Bridge(name='br_r2_r4', subnet='192.168.24.0/24') [cls.br_r2_r4.addif(ctn) for ctn in (cls.r2, cls.r4)] cls.br_r3_r4 = Bridge(name='br_r3_r4', subnet='192.168.34.0/24') [cls.br_r3_r4.addif(ctn) for ctn in (cls.r3, cls.r4)] def test_01_BGP_neighbor_established(self): """ Test to start BGP connection up between r1-r2. """ self.r1.add_peer(self.r2, bridge=self.br_r1_r2.name) self.r2.add_peer(self.r1, bridge=self.br_r1_r2.name) self.r1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.r2) def test_02_OSPF_established(self): """ Test to start OSPF connection up between r2-r3 and receive the route to r3's loopback '10.3.1.1'. """ def _f(): return try_local( "vtysh -c 'show ip ospf route'" " | grep '10.3.1.1/32'", f=self.r2.local, ok_ret_codes=[1], # for the empty case with "grep" command capture=True) wait_for(f=_f) def test_03_add_ipv4_route(self): """ Test to add IPv4 route to '10.3.1.0/24' whose nexthop is r3's loopback '10.3.1.1'. Also, test to receive the initial MED/Metric. """ # MED/Metric = 10(r2 to r3) + 10(r3-ethX to r3-lo) med = 20 def _f_r2(): return try_local( "gobgp global rib -a ipv4 10.3.1.0/24" " | grep 'Med: %d'" % med, f=self.r2.local, ok_ret_codes=[1], # for the empty case with "grep" command capture=True) def _f_r1(): return try_local( "gobgp global rib -a ipv4 10.3.1.0/24" " | grep 'Med: %d'" % med, f=self.r1.local, ok_ret_codes=[1], # for the empty case with "grep" command capture=True) self.r2.local( 'gobgp global rib add -a ipv4 10.3.1.0/24 nexthop 10.3.1.1') wait_for(f=_f_r2) wait_for(f=_f_r1) def test_04_link_r2_r3_down(self): """ Test to update MED to the nexthop if the Metric to that nexthop is changed by the link down. If the link r2-r3 goes down, MED/Metric should be increased. """ # MED/Metric = 10(r2 to r4) + 10(r4 to r3) + 10(r3-ethX to r3-lo) med = 30 def _f_r2(): return try_local( "gobgp global rib -a ipv4 10.3.1.0/24" " | grep 'Med: %d'" % med, f=self.r2.local, ok_ret_codes=[1], # for the empty case with "grep" command capture=True) def _f_r1(): return try_local( "gobgp global rib -a ipv4 10.3.1.0/24" " | grep 'Med: %d'" % med, f=self.r1.local, ok_ret_codes=[1], # for the empty case with "grep" command capture=True) ifname = get_ifname_with_prefix('192.168.23.3/24', f=self.r3.local) self.r3.local('ip link set %s down' % ifname) wait_for(f=_f_r2) wait_for(f=_f_r1) def test_05_link_r2_r3_restore(self): """ Test to update MED to the nexthop if the Metric to that nexthop is changed by the link up again. If the link r2-r3 goes up again, MED/Metric should be update with the initial value. """ # MED/Metric = 10(r2 to r3) + 10(r3-ethX to r3-lo) med = 20 def _f_r2(): return try_local( "gobgp global rib -a ipv4 10.3.1.0/24" " | grep 'Med: %d'" % med, f=self.r2.local, ok_ret_codes=[1], # for the empty case with "grep" command capture=True) def _f_r1(): return try_local( "gobgp global rib -a ipv4 10.3.1.0/24" " | grep 'Med: %d'" % med, f=self.r1.local, ok_ret_codes=[1], # for the empty case with "grep" command capture=True) ifname = get_ifname_with_prefix('192.168.23.3/24', f=self.r3.local) self.r3.local('ip link set %s up' % ifname) wait_for(f=_f_r2) wait_for(f=_f_r1) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/bgp_zebra_test.py000066400000000000000000000260161324612745600217470ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import from __future__ import print_function import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( Bridge, BGP_FSM_ESTABLISHED, ) from lib.gobgp import GoBGPContainer from lib.quagga import QuaggaBGPContainer class GoBGPTestBase(unittest.TestCase): # The same topology for IPv4 and IPv6 environment. # o1: Quagga (not router) # g1: GoBGP # q1: Quagga (Zebra + BGPd) # o2: Quagga (not router) # # +----+ +----+ +----+ +----+ # | o1 | | g1 |===(BGP)===| q1 | | o2 | # +----+ +----+ +----+ +----+ # | | | | | | # +--(br01)--+ +---(br02)----+ +--(br03)--+ @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix # preparing the container for ipv4 g1_v4 = GoBGPContainer(name='g1_v4', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, zebra=True) q1_v4 = QuaggaBGPContainer(name='q1_v4', asn=65001, router_id='192.168.0.2', zebra=True) o1_v4 = QuaggaBGPContainer(name='o1_v4', asn=65002, router_id='192.168.0.3') o2_v4 = QuaggaBGPContainer(name='o2_v4', asn=65002, router_id='192.168.0.4') # preparing the container for ipv6 g1_v6 = GoBGPContainer(name='g1_v6', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, zebra=True) q1_v6 = QuaggaBGPContainer(name='q1_v6', asn=65001, router_id='192.168.0.2', zebra=True) o1_v6 = QuaggaBGPContainer(name='o1_v6', asn=65002, router_id='192.168.0.3') o2_v6 = QuaggaBGPContainer(name='o2_v6', asn=65002, router_id='192.168.0.4') # preparing the bridge for ipv4 br01_v4 = Bridge(name='br01_v4', subnet='192.168.10.0/24') br02_v4 = Bridge(name='br02_v4', subnet='192.168.20.0/24') br03_v4 = Bridge(name='br03_v4', subnet='192.168.30.0/24') # preparing the bridge for ipv6 br01_v6 = Bridge(name='br01_v6', subnet='2001:10::/32') br02_v6 = Bridge(name='br02_v6', subnet='2001:20::/32') br03_v6 = Bridge(name='br03_v6', subnet='2001:30::/32') cls.ctns = { 'ipv4': [g1_v4, q1_v4, o1_v4, o2_v4], 'ipv6': [g1_v6, q1_v6, o1_v6, o2_v6], } cls.gobgps = { 'ipv4': g1_v4, 'ipv6': g1_v6, } cls.quaggas = { 'ipv4': q1_v4, 'ipv6': q1_v6, } cls.others = { 'ipv4': [o1_v4, o2_v4], 'ipv6': [o1_v6, o2_v6], } cls.bridges = { 'br01_v4': br01_v4, 'br02_v4': br02_v4, 'br03_v4': br03_v4, 'br01_v6': br01_v6, 'br02_v6': br02_v6, 'br03_v6': br03_v6, } """ No.1 start up ipv4 containers and check state each neighbor is established in ipv4 environment """ def test_01_check_neighbor_established(self): g1 = self.gobgps['ipv4'] q1 = self.quaggas['ipv4'] o1 = self.others['ipv4'][0] o2 = self.others['ipv4'][1] # start up containers of ipv4 environment initial_wait_time = max(ctn.run() for ctn in self.ctns['ipv4']) time.sleep(initial_wait_time) # make ipv4 bridge and set ip to each container [self.bridges['br01_v4'].addif(ctn) for ctn in [o1, g1]] [self.bridges['br02_v4'].addif(ctn) for ctn in [g1, q1]] [self.bridges['br03_v4'].addif(ctn) for ctn in [q1, o2]] g1.add_peer(q1, bridge=self.bridges['br02_v4'].name) q1.add_peer(g1, bridge=self.bridges['br02_v4'].name) g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q1) """ No.2 check whether the ping is reachable in container that have previously beyond the GoBGP in ipv4 environment """ def test_02_check_reachability_between_o1_and_o2(self): g1 = self.gobgps['ipv4'] q1 = self.quaggas['ipv4'] o1 = self.others['ipv4'][0] o2 = self.others['ipv4'][1] # set o1's default gateway as g1 g1_addr = g1.ip_addrs[1][1].split('/')[0] o1.add_static_route(self.bridges['br03_v4'].subnet, g1_addr) # set o2's default gateway as q1 q1_addr = q1.ip_addrs[2][1].split('/')[0] o2.add_static_route(self.bridges['br01_v4'].subnet, q1_addr) # test reachability between o1 and o2 addrs = [e[1] for e in o2.ip_addrs if 'br03_v4' in e[2]] self.assertTrue(len(addrs) == 1) o2_addr = addrs[0] o1.get_reachability(o2_addr) """ No.4 start up ipv6 containers and check state each neighbor is established in ipv6 environment """ def test_04_check_neighbor_established_v6(self): g1 = self.gobgps['ipv6'] q1 = self.quaggas['ipv6'] o1 = self.others['ipv6'][0] o2 = self.others['ipv6'][1] # start up containers of ipv6 environment initial_wait_time = max(ctn.run() for ctn in self.ctns['ipv6']) time.sleep(initial_wait_time) # make ipv6 bridge and set ip to each container [self.bridges['br01_v6'].addif(ctn) for ctn in [o1, g1]] [self.bridges['br02_v6'].addif(ctn) for ctn in [g1, q1]] [self.bridges['br03_v6'].addif(ctn) for ctn in [q1, o2]] g1.add_peer(q1, bridge=self.bridges['br02_v6'].name) q1.add_peer(g1, bridge=self.bridges['br02_v6'].name) g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q1) """ No.5 check whether the ping is reachable in container that have previously beyond the GoBGP in ipv6 environment """ def test_05_check_reachability_between_o1_and_o2(self): g1 = self.gobgps['ipv6'] q1 = self.quaggas['ipv6'] o1 = self.others['ipv6'][0] o2 = self.others['ipv6'][1] # set o1's default gateway as g1 g1_addr = g1.ip_addrs[1][1].split('/')[0] o1.add_static_route(self.bridges['br03_v6'].subnet, g1_addr) # set o2's default gateway as q1 q1_addr = q1.ip_addrs[2][1].split('/')[0] o2.add_static_route(self.bridges['br01_v6'].subnet, q1_addr) # test reachability between o1 and o2 addrs = [e[1] for e in o2.ip_addrs if 'br03_v6' in e[2]] self.assertTrue(len(addrs) == 1) o2_addr = addrs[0] o1.get_reachability(o2_addr) def test_07_mpath_test_setup(self): g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=parser_option.gobgp_image, log_level=parser_option.gobgp_log_level, config_format=parser_option.config_format, zebra=True) g2 = GoBGPContainer(name='g2', asn=65001, router_id='192.168.0.2', ctn_image_name=parser_option.gobgp_image) g3 = GoBGPContainer(name='g3', asn=65001, router_id='192.168.0.3', ctn_image_name=parser_option.gobgp_image) g4 = GoBGPContainer(name='g4', asn=65000, router_id='192.168.0.4', ctn_image_name=parser_option.gobgp_image) g5 = GoBGPContainer(name='g5', asn=65000, router_id='192.168.0.5', ctn_image_name=parser_option.gobgp_image) ctns = [g1, g2, g3, g4, g5] for ctn in ctns: self.ctns[ctn.name] = ctn initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) # advertise same prefix g2.add_route('10.0.10.0/24') g3.add_route('10.0.10.0/24') g4.add_route('10.0.10.0/24') g5.add_route('10.0.10.0/24') for g in [g2, g3, g4, g5]: g1.add_peer(g) g.add_peer(g1) def test_08_mpath_test_check_neighbor_established(self): g1 = self.ctns['g1'] g2 = self.ctns['g2'] g3 = self.ctns['g3'] g4 = self.ctns['g4'] g5 = self.ctns['g5'] for g in [g2, g3, g4, g5]: g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g) def test_09_mpath_test_check_mpath_injected(self): g1 = self.ctns['g1'] g2 = self.ctns['g2'] g3 = self.ctns['g3'] g4 = self.ctns['g4'] g5 = self.ctns['g5'] def nexthops(): n = [] for line in g1.local('ip route show 10.0.10.0/24', capture=True).split('\n'): line = line.strip() if 'via' in line: n.append(line.split(' ')[2].strip()) return n def validate_nexthops(peers): interval = 1 count = 0 timeout = 30 while True: valid = False nhs = nexthops() if len(nhs) == len(peers): valid = True for peer in peers: if g1.peers[peer]['neigh_addr'].split('/')[0] not in nhs: valid = False break if valid: return time.sleep(interval) count += interval if count >= timeout: raise Exception(nhs) validate_nexthops([g4, g5]) g4.local('gobgp g ri del 10.0.10.0/24') validate_nexthops([g5]) g4.local('gobgp g ri add 10.0.10.0/24 local-pref 200') validate_nexthops([g4]) g4.local('gobgp g ri del 10.0.10.0/24') g5.local('gobgp g ri del 10.0.10.0/24') validate_nexthops([g2, g3]) g3.local('gobgp g ri del 10.0.10.0/24') validate_nexthops([g2]) g3.local('gobgp g ri add 10.0.10.0/24 med 10') validate_nexthops([g2]) g2.local('gobgp g ri add 10.0.10.0/24 med 20') validate_nexthops([g3]) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print("docker not found") sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/ci-scripts/000077500000000000000000000000001324612745600204565ustar00rootroot00000000000000gobgp-1.29/test/scenario_test/ci-scripts/build_embeded_go.py000066400000000000000000000012351324612745600242620ustar00rootroot00000000000000import sys import os from subprocess import call def cut(filename, out): with open(filename, 'r') as f: flag = False codes = [] for line in f.readlines(): if line.strip() == '```go': flag = True elif line.strip() == '```': with open(out, 'w') as g: g.write("".join(codes)) return elif flag: codes.append(line) if __name__ == '__main__': filename = sys.argv[1] out = 'hoge.go' cut(filename, out) ret = call(['go', 'build', '-o', 'hoge', out]) os.remove(out) os.remove('hoge') sys.exit(ret) gobgp-1.29/test/scenario_test/ci-scripts/jenkins-build-script.sh000066400000000000000000000027141324612745600250560ustar00rootroot00000000000000#!/usr/bin/env sh # renew GOPATH rm -rf /usr/local/jenkins/{bin,pkg,src} mkdir /usr/local/jenkins/{bin,pkg,src} mkdir -p /usr/local/jenkins/src/github.com/osrg/ export GOBGP_IMAGE=gobgp export GOPATH=/usr/local/jenkins export GOROOT=/usr/local/go export GOBGP=/usr/local/jenkins/src/github.com/osrg/gobgp export WS=`pwd` # clear docker.log if [ "${BUILD_TAG}" != "" ]; then sudo sh -c ": > /var/log/upstart/docker.log" fi rm -f ${WS}/nosetest*.xml cp -r ../workspace $GOBGP pwd cd $GOBGP ls -al git log | head -20 sudo docker rmi $(sudo docker images | grep "^" | awk '{print $3}') sudo docker rm -f $(sudo docker ps -a -q) for link in $(ip li | awk '/(_br|veth)/{sub(":","", $2); print $2}') do sudo ip li set down $link sudo ip li del $link done sudo docker rmi $GOBGP_IMAGE sudo fab -f $GOBGP/test/lib/base.py make_gobgp_ctn:tag=$GOBGP_IMAGE [ "$?" != 0 ] && exit "$?" cd $GOBGP/gobgpd $GOROOT/bin/go get -v cd $GOBGP/test/scenario_test ./run_all_tests.sh if [ "${BUILD_TAG}" != "" ]; then cd ${WS} mkdir jenkins-log-${BUILD_NUMBER} sudo cp *.xml jenkins-log-${BUILD_NUMBER}/ sudo cp /var/log/upstart/docker.log jenkins-log-${BUILD_NUMBER}/docker.log sudo chown -R jenkins:jenkins jenkins-log-${BUILD_NUMBER} tar cvzf jenkins-log-${BUILD_NUMBER}.tar.gz jenkins-log-${BUILD_NUMBER} s3cmd put jenkins-log-${BUILD_NUMBER}.tar.gz s3://gobgp/jenkins/ rm -rf jenkins-log-${BUILD_NUMBER} jenkins-log-${BUILD_NUMBER}.tar.gz fi gobgp-1.29/test/scenario_test/evpn_test.py000066400000000000000000000122211324612745600207550ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import from itertools import combinations import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( BGP_FSM_ESTABLISHED, BGP_ATTR_TYPE_EXTENDED_COMMUNITIES, ) from lib.gobgp import GoBGPContainer def get_mac_mobility_sequence(pattr): for ecs in [ p['value'] for p in pattr if 'type' in p and p['type'] == BGP_ATTR_TYPE_EXTENDED_COMMUNITIES]: for ec in [e for e in ecs if 'type' in e and e['type'] == 6]: if ec['subtype'] == 0: if 'sequence' not in ec: return 0 else: return ec['sequence'] return -1 class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g2 = GoBGPContainer(name='g2', asn=65000, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) ctns = [g1, g2] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) g1.local("gobgp vrf add vrf1 rd 10:10 rt both 10:10") g2.local("gobgp vrf add vrf1 rd 10:10 rt both 10:10") for a, b in combinations(ctns, 2): a.add_peer(b, vpn=True, passwd='evpn') b.add_peer(a, vpn=True, passwd='evpn') cls.g1 = g1 cls.g2 = g2 # test each neighbor state is turned establish def test_01_neighbor_established(self): self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g2) def test_02_add_evpn_route(self): self.g1.local('gobgp global rib add ' '-a evpn macadv 11:22:33:44:55:66 10.0.0.1 esi AS 1 1 1 etag 1000 label 1000 ' 'rd 10:10 rt 10:10') grib = self.g1.get_global_rib(rf='evpn') self.assertTrue(len(grib) == 1) dst = grib[0] self.assertTrue(len(dst['paths']) == 1) path = dst['paths'][0] self.assertTrue(path['nexthop'] == '0.0.0.0') interval = 1 timeout = int(30 / interval) done = False for _ in range(timeout): if done: break grib = self.g2.get_global_rib(rf='evpn') if len(grib) < 1: time.sleep(interval) continue self.assertTrue(len(grib) == 1) dst = grib[0] self.assertTrue(len(dst['paths']) == 1) path = dst['paths'][0] n_addrs = [i[1].split('/')[0] for i in self.g1.ip_addrs] self.assertTrue(path['nexthop'] in n_addrs) done = True def test_03_check_mac_mobility(self): self.g2.local('gobgp global rib add ' '-a evpn macadv 11:22:33:44:55:66 10.0.0.1 esi AS 2 1 1 etag 1000 label 1000 ' 'rd 10:20 rt 10:10') time.sleep(3) grib = self.g1.get_global_rib(rf='evpn') self.assertTrue(len(grib) == 1) dst = grib[0] self.assertTrue(len(dst['paths']) == 1) path = dst['paths'][0] n_addrs = [i[1].split('/')[0] for i in self.g2.ip_addrs] self.assertTrue(path['nexthop'] in n_addrs) self.assertTrue(get_mac_mobility_sequence(path['attrs']) == 0) def test_04_check_mac_mobility_again(self): self.g1.local('gobgp global rib add ' '-a evpn macadv 11:22:33:44:55:66 10.0.0.1 esi AS 3 1 1 etag 1000 label 1000 ' 'rd 10:20 rt 10:10') time.sleep(3) grib = self.g2.get_global_rib(rf='evpn') self.assertTrue(len(grib) == 1) dst = grib[0] self.assertTrue(len(dst['paths']) == 1) path = dst['paths'][0] n_addrs = [i[1].split('/')[0] for i in self.g1.ip_addrs] self.assertTrue(path['nexthop'] in n_addrs) self.assertTrue(get_mac_mobility_sequence(path['attrs']) == 1) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/flow_spec_test.py000066400000000000000000000355041324612745600217770ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer from lib.exabgp import ExaBGPContainer from lib.yabgp import YABGPContainer class FlowSpecTest(unittest.TestCase): """ Test case for Flow Specification. """ # +------------+ +------------+ # | G1(GoBGP) |---(iBGP)---| E1(ExaBGP) | # | 172.17.0.2 | | 172.17.0.3 | # +------------+ +------------+ # | # (iBGP) # | # +------------+ # | Y1(YABGP) | # | 172.17.0.4 | # +------------+ @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix cls.g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) cls.e1 = ExaBGPContainer(name='e1', asn=65000, router_id='192.168.0.2') cls.y1 = YABGPContainer(name='y1', asn=65000, router_id='192.168.0.3') ctns = [cls.g1, cls.e1, cls.y1] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) # Add FlowSpec routes into GoBGP. cls.g1.add_route( route='ipv4/all', rf='ipv4-flowspec', matchs=[ 'destination 11.1.0.0/24', 'source 11.2.0.0/24', "protocol '==tcp &=udp icmp >igmp >=egp 9090 >=8180 <9190 <=8081 !=9091 &!443'", 'destination-port 80', 'source-port 8080', 'icmp-type 0', 'icmp-code 2', "tcp-flags '==S &=SA A !F !=U =!R'", 'packet-length 100', 'dscp 12', 'fragment dont-fragment is-fragment+first-fragment', ], thens=['discard']) cls.g1.add_route( route='ipv6/dst/src/label', # others are tested on IPv4 rf='ipv6-flowspec', matchs=[ 'destination 2001:1::/64 10', 'source 2001:2::/64 15', 'label 12', ], thens=['discard']) cls.g1.add_peer(cls.e1, flowspec=True) cls.e1.add_peer(cls.g1, flowspec=True) cls.g1.add_peer(cls.y1, flowspec=True) cls.y1.add_peer(cls.g1, flowspec=True) cls.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=cls.e1) cls.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=cls.y1) # Add FlowSpec routes into ExaBGP. cls.e1.add_route( route='ipv4/all', rf='ipv4-flowspec', matchs=[ 'destination 12.1.0.0/24', 'source 12.2.0.0/24', 'protocol =tcp', 'port >=80', 'destination-port >5000', 'source-port 8080', 'icmp-type <1', 'icmp-code <=2', "tcp-flags FIN", 'packet-length >100&<200', 'dscp 12', 'fragment dont-fragment', ], thens=['discard']) cls.e1.add_route( route='ipv6/dst/src/protocol/label', # others are tested on IPv4 rf='ipv6-flowspec', matchs=[ 'destination 2002:1::/64/10', 'source 2002:2::/64/15', 'next-header udp', 'flow-label >100', ], thens=['discard']) # Add FlowSpec routes into YABGP. cls.y1.add_route( route='ipv4/all', rf='ipv4-flowspec', matchs=[ 'destination 13.1.0.0/24', 'source 13.2.0.0/24', "protocol =6|=17", # "port", # not seem to be supported 'destination-port =80', 'source-port <8080|>9090', 'icmp-type >=1', 'icmp-code <2', # "tcp-flags", # not seem to be supported via REST API 'packet-length <=100', 'dscp =12', # "fragment", # not seem to be supported via REST API ], thens=['traffic-rate:0:0']) # 'discard' # IPv6 FlowSpec: not supported with YABGP v0.4.0 # cls.y1.add_route( # route='ipv6/dst/src/label', # others are tested on IPv4 # rf='ipv6-flowspec', # matchs=[ # 'destination 2003:1::/64/10', # 'source 2003:2::/64/15', # 'label 12', # ], # thens=['traffic-rate:0:0']) # 'discard' def test_01_ipv4_exabgp_adj_rib_in(self): rib = self.e1.get_adj_rib_in(self.g1, rf='ipv4-flowspec') self.assertEqual(1, len(rib)) nlri = list(rib)[0] # advertised from G1(GoBGP) _exp_fmt = ( # INPUTS: # 'destination 11.1.0.0/24', "destination-ipv4 11.1.0.0/24 " # 'source 11.2.0.0/24', "source-ipv4 11.2.0.0/24 " # "protocol '==tcp &=udp icmp >igmp >=egp igmp >=egp 9090 >=8180 <9190 <=8081 !=9091 &!443'", "port [ =80&=90 =8080 >9090 >=8180 <9190 <=8081 !=9091&!=443 ] " # 'destination-port 80', "destination-port =80 " # 'source-port 8080', "source-port =8080 " # 'icmp-type 0', "icmp-type =echo-reply " # 'icmp-code 2', "icmp-code =2 " # "tcp-flags '==S &=SA A !F !=U =!R'", "tcp-flags [ =syn&=%s ack !fin !=urgent !=rst ] " # 'packet-length 100', "packet-length =100 " # 'dscp 12', "dscp =12 " # 'fragment dont-fragment is-fragment+first-fragment', "fragment [ dont-fragment is-fragment+first-fragment ]" ) # Note: Considers variants of SYN + ACK expected_list = (_exp_fmt % 'syn+ack', _exp_fmt % 'ack+syn') self.assertIn(nlri, expected_list) def test_02_ipv6_exabgp_adj_rib_in(self): rib = self.e1.get_adj_rib_in(self.g1, rf='ipv6-flowspec') self.assertEqual(1, len(rib)) nlri = list(rib)[0] # advertised from G1(GoBGP) expected = ( # INPUTS: # 'destination 2001:1::/64 10', "destination-ipv6 2001:1::/64/10 " # 'source 2001:2::/64 15', "source-ipv6 2001:2::/64/15 " # 'label 12', "flow-label =12" ) self.assertEqual(expected, nlri) def test_03_ipv4_yabgp_adj_rib_in(self): rib = self.y1.get_adj_rib_in(peer=self.g1, rf='flowspec') self.assertEqual(1, len(rib)) nlri = list(rib)[0] # advertised from G1(GoBGP) expected = ( # INPUTS: # 'destination 11.1.0.0/24', '{"1": "11.1.0.0/24",' # 'source 11.2.0.0/24', ' "2": "11.2.0.0/24",' # "protocol '==tcp &=udp icmp >igmp >=egp 2|>=8|<94|<=46|><47",' # "port '==80 &=90 8080 >9090 >=8180 <9190 <=8081 !=9091 &!443'", ' "4": "=80&=90|=8080|>9090|>=8180|<9190|<=8081|><9091&><443",' # 'destination-port 80', ' "5": "=80",' # 'source-port 8080', ' "6": "=8080",' # 'icmp-type 0', ' "7": "=0",' # 'icmp-code 2', ' "8": "=2",' # "tcp-flags '==S &=SA A !F !=U =!R'", ' "9": "=2&=18|16|>1|>=32|>=4",' # 'packet-length 100', ' "10": "=100",' # 'dscp 12', ' "11": "=12",' # 'fragment dont-fragment is-fragment+first-fragment', ' "12": "1|6"}' ) self.assertEqual(expected, nlri) def test_04_ipv6_yabgp_adj_rib_in(self): # IPv6 FlowSpec: not supported with YABGP v0.4.0 pass def test_05_ipv4_gobgp_global_rib(self): rib = self.g1.get_global_rib(rf='ipv4-flowspec') self.assertEqual(3, len(rib)) output_nlri_list = [r['prefix'] for r in rib] nlri_g1 = ( # INPUTS: # 'destination 11.1.0.0/24', "[destination: 11.1.0.0/24]" # 'source 11.2.0.0/24', "[source: 11.2.0.0/24]" # "protocol '==tcp &=udp icmp >igmp >=egp igmp >=egp 9090 >=8180 <9190 <=8081 !=9091 &!443'", "[port: ==80&==90 ==8080 >9090 >=8180 <9190 <=8081 !=9091&!=443]" # 'destination-port 80', "[destination-port: ==80]" # 'source-port 8080', "[source-port: ==8080]" # 'icmp-type 0', "[icmp-type: ==0]" # 'icmp-code 2', "[icmp-code: ==2]" # "tcp-flags '==S &=SA A !F !=U =!R'", "[tcp-flags: =S&=SA A !F !=U !=R]" # 'packet-length 100', "[packet-length: ==100]" # 'dscp 12', "[dscp: ==12]" # 'fragment dont-fragment is-fragment+first-fragment', "[fragment: dont-fragment is-fragment+first-fragment]" ) nlri_e1 = ( # INPUTS: # 'destination 12.1.0.0/24', '[destination: 12.1.0.0/24]' # 'source 12.2.0.0/24', '[source: 12.2.0.0/24]' # 'protocol =tcp', '[protocol: ==tcp]' # 'port >=80', '[port: >=80]' # 'destination-port >5000', '[destination-port: >5000]' # 'source-port 8080', '[source-port: ==8080]' # 'icmp-type <1', '[icmp-type: <1]' # 'icmp-code <=2', '[icmp-code: <=2]' # "tcp-flags FIN", '[tcp-flags: F]' # 'packet-length >100&<200', '[packet-length: >100&<200]' # 'dscp 12', '[dscp: ==12]' # 'fragment dont-fragment', '[fragment: dont-fragment]' ) nlri_y1 = ( # INPUTS: # 'destination 13.1.0.0/24', "[destination: 13.1.0.0/24]" # 'source 13.2.0.0/24', "[source: 13.2.0.0/24]" # "protocol =6|=17", "[protocol: ==tcp ==udp]" # 'destination-port =80', "[destination-port: ==80]" # 'source-port <8080|>9090', "[source-port: <8080 >9090]" # 'icmp-type >=1', "[icmp-type: >=1]" # 'icmp-code <2', "[icmp-code: <2]" # 'packet-length <=100', "[packet-length: <=100]" # 'dscp =12', "[dscp: ==12]" ) for nlri in [nlri_g1, nlri_e1, nlri_y1]: self.assertIn(nlri, output_nlri_list) def test_06_ipv6_gobgp_global_rib(self): rib = self.g1.get_global_rib(rf='ipv6-flowspec') import json self.assertEqual(2, len(rib), json.dumps(rib)) output_nlri_list = [r['prefix'] for r in rib] nlri_g1 = ( # INPUTS: # 'destination 2001:1::/64 10', "[destination: 2001:1::/64/10]" # 'source 2001:2::/64 15', "[source: 2001:2::/64/15]" # 'label 12', "[label: ==12]" ) nlri_e1 = ( # INPUTS: # 'destination 2002:1::/64/10', '[destination: 2002:1::/64/10]' # 'source 2002:2::/64/15', '[source: 2002:2::/64/15]' # 'next-header udp', '[protocol: ==udp]' # 'flow-label >100', '[label: >100]' ) for nlri in [nlri_g1, nlri_e1]: self.assertIn(nlri, output_nlri_list) def test_07_ipv4_exabgp_delete_route(self): # Delete a route on E1(ExaBGP) self.e1.del_route(route='ipv4/all') time.sleep(1) # Test if the route is deleted or not rib = self.g1.get_adj_rib_in(peer=self.e1, rf='ipv4-flowspec') self.assertEqual(0, len(rib)) def test_08_ipv6_exabgp_delete_route(self): # Delete a route on E1(ExaBGP) self.e1.del_route(route='ipv6/dst/src/protocol/label') time.sleep(1) # Test if the route is deleted or not rib = self.g1.get_adj_rib_in(peer=self.e1, rf='ipv6-flowspec') self.assertEqual(0, len(rib)) def test_09_ipv4_yabgp_delete_route(self): # Delete a route on Y1(YABGP) self.y1.del_route(route='ipv4/all') time.sleep(1) # Test if the route is deleted or not rib = self.g1.get_adj_rib_in(peer=self.y1, rf='ipv4-flowspec') self.assertEqual(0, len(rib)) def test_10_ipv6_yabgp_delete_route(self): # IPv6 FlowSpec: not supported with YABGP v0.4.0 pass def test_11_ipv4_gobgp_delete_route(self): # Delete a route on G1(GoBGP) self.g1.del_route(route='ipv4/all') time.sleep(1) # Test if the route is deleted or not rib_e1 = self.e1.get_adj_rib_in(peer=self.g1, rf='ipv4-flowspec') self.assertEqual(0, len(rib_e1)) rib_y1 = self.y1.get_adj_rib_in(peer=self.g1, rf='ipv4-flowspec') self.assertEqual(0, len(rib_y1)) def test_12_ipv6_gobgp_delete_route(self): # Delete a route on G1(GoBGP) self.g1.del_route(route='ipv6/dst/src/label') time.sleep(1) # Test if the route is deleted or not rib_e1 = self.e1.get_adj_rib_in(peer=self.g1, rf='ipv6-flowspec') self.assertEqual(0, len(rib_e1)) rib_y1 = self.y1.get_adj_rib_in(peer=self.g1, rf='ipv6-flowspec') self.assertEqual(0, len(rib_y1)) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/global_policy_test.py000066400000000000000000000252461324612745600226370ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( BGP_FSM_IDLE, BGP_FSM_ESTABLISHED, BGP_ATTR_TYPE_COMMUNITIES, ) from lib.gobgp import GoBGPContainer from lib.exabgp import ExaBGPContainer def community_exists(path, com): a, b = com.split(':') com = (int(a) << 16) + int(b) for a in path['attrs']: if a['type'] == BGP_ATTR_TYPE_COMMUNITIES and com in a['communities']: return True return False class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format=parser_option.config_format) q1 = ExaBGPContainer(name='q1', asn=65001, router_id='192.168.0.2') q2 = ExaBGPContainer(name='q2', asn=65002, router_id='192.168.0.3') q3 = ExaBGPContainer(name='q3', asn=65003, router_id='192.168.0.4') qs = [q1, q2, q3] ctns = [g1, q1, q2, q3] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) g1.local('gobgp global policy export add default reject') for q in qs: g1.add_peer(q) q.add_peer(g1) # advertise a route from q1, q2, q3 for idx, q in enumerate(qs): route = '10.0.{0}.0/24'.format(idx + 1) q.add_route(route) cls.gobgp = g1 cls.quaggas = {'q1': q1, 'q2': q2, 'q3': q3} # test each neighbor state is turned establish def test_01_neighbor_established(self): for q in self.quaggas.itervalues(): self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q) def test_02_check_adj_rib_out(self): for q in self.quaggas.itervalues(): self.assertTrue(len(self.gobgp.get_adj_rib_out(q)) == 0) def test_03_add_peer(self): q = ExaBGPContainer(name='q4', asn=65004, router_id='192.168.0.5') time.sleep(q.run()) self.gobgp.add_peer(q) q.add_peer(self.gobgp) q.add_route('10.10.0.0/24') self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q) self.quaggas['q4'] = q for q in self.quaggas.itervalues(): self.assertTrue(len(self.gobgp.get_adj_rib_out(q)) == 0) def test_04_disable_peer(self): q3 = self.quaggas['q3'] self.gobgp.disable_peer(q3) self.gobgp.wait_for(expected_state=BGP_FSM_IDLE, peer=q3) for q in self.quaggas.itervalues(): if q.name == 'q3': continue self.assertTrue(len(self.gobgp.get_adj_rib_out(q)) == 0) def test_05_enable_peer(self): q3 = self.quaggas['q3'] self.gobgp.enable_peer(q3) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q3) for q in self.quaggas.itervalues(): self.assertTrue(len(self.gobgp.get_adj_rib_out(q)) == 0) def test_06_disable_peer2(self): q3 = self.quaggas['q3'] # advertise a route which was also advertised by q1 # this route will be best for g1 because q3's router-id is larger # than q1 q3.add_route('10.0.1.0/24') time.sleep(3) # then disable q3 self.gobgp.disable_peer(q3) self.gobgp.wait_for(expected_state=BGP_FSM_IDLE, peer=q3) for q in self.quaggas.itervalues(): if q.name == 'q3': continue self.assertTrue(len(self.gobgp.get_adj_rib_out(q)) == 0) def test_07_adv_to_one_peer(self): self.gobgp.local('gobgp policy neighbor add ns0 {0}'.format(self.gobgp.peers[self.quaggas['q1']]['neigh_addr'].split('/')[0])) self.gobgp.local('gobgp policy statement add st0') self.gobgp.local('gobgp policy statement st0 add condition neighbor ns0') self.gobgp.local('gobgp policy statement st0 add action accept') self.gobgp.local('gobgp policy add p0 st0') self.gobgp.local('gobgp global policy export add p0 default reject') for q in self.quaggas.itervalues(): self.gobgp.softreset(q, type='out') def test_08_check_adj_rib_out(self): for q in self.quaggas.itervalues(): if q.name == 'q3': continue paths = self.gobgp.get_adj_rib_out(q) if q == self.quaggas['q1']: self.assertTrue(len(paths) == 2) else: self.assertTrue(len(paths) == 0) def test_09_change_global_policy(self): self.gobgp.local('gobgp policy statement st0 add action community add 65100:10') self.gobgp.local('gobgp global policy export set p0 default accept') for q in self.quaggas.itervalues(): self.gobgp.softreset(q, type='out') def test_10_check_adj_rib_out(self): for q in self.quaggas.itervalues(): if q.name == 'q3': continue paths = self.gobgp.get_adj_rib_out(q) if q != self.quaggas['q3']: self.assertTrue(len(paths) == 2) for path in paths: if q == self.quaggas['q1']: self.assertTrue(community_exists(path, '65100:10')) else: self.assertFalse(community_exists(path, '65100:10')) def test_11_add_ibgp_peer(self): q = ExaBGPContainer(name='q5', asn=65000, router_id='192.168.0.6') time.sleep(q.run()) self.quaggas['q5'] = q self.gobgp.add_peer(q) q.add_peer(self.gobgp) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q) def test_12_add_local_pref_policy(self): self.gobgp.local('gobgp policy statement st1 add action accept') self.gobgp.local('gobgp policy statement st1 add action local-pref 300') self.gobgp.local('gobgp policy add p1 st1') self.gobgp.local('gobgp global policy export set p1 default reject') for q in self.quaggas.itervalues(): self.gobgp.softreset(q, type='out') def test_13_check_adj_rib_out(self): q1 = self.quaggas['q1'] for path in self.gobgp.get_adj_rib_out(q1): self.assertTrue(path['local-pref'] is None) q5 = self.quaggas['q5'] for path in self.gobgp.get_adj_rib_out(q5): self.assertTrue(path['local-pref'] == 300) def test_14_route_type_condition_local(self): self.gobgp.local('gobgp policy statement st2 add action accept') self.gobgp.local('gobgp policy statement st2 add condition route-type local') self.gobgp.local('gobgp policy add p2 st2') self.gobgp.local('gobgp global policy export set p2 default reject') for q in self.quaggas.itervalues(): self.gobgp.softreset(q, type='out') q1 = self.quaggas['q1'] self.assertTrue(len(self.gobgp.get_adj_rib_out(q1)) == 0) self.gobgp.add_route('10.20.0.0/24') time.sleep(1) self.assertTrue(len(self.gobgp.get_adj_rib_out(q1)) == 1) self.assertTrue(self.gobgp.get_adj_rib_out(q1)[0]['nlri']['prefix'] == u'10.20.0.0/24') def test_15_route_type_condition_internal(self): self.gobgp.local('gobgp policy statement st2 set condition route-type internal') for q in self.quaggas.itervalues(): self.gobgp.softreset(q, type='out') q1 = self.quaggas['q1'] self.assertTrue(len(self.gobgp.get_adj_rib_out(q1)) == 0) q5 = self.quaggas['q5'] q5.add_route('10.30.0.0/24') time.sleep(1) self.assertTrue(len(self.gobgp.get_adj_rib_out(q1)) == 1) self.assertTrue(self.gobgp.get_adj_rib_out(q1)[0]['nlri']['prefix'] == u'10.30.0.0/24') def test_16_route_type_condition_external(self): self.gobgp.local('gobgp policy statement st2 set condition route-type external') for q in self.quaggas.itervalues(): self.gobgp.softreset(q, type='out') q1 = self.quaggas['q1'] num1 = len(self.gobgp.get_adj_rib_out(q1)) self.gobgp.add_route('10.40.0.0/24') time.sleep(1) num2 = len(self.gobgp.get_adj_rib_out(q1)) self.assertTrue(num1 == num2) q5 = self.quaggas['q5'] q5.add_route('10.50.0.0/24') time.sleep(1) num3 = len(self.gobgp.get_adj_rib_out(q1)) self.assertTrue(num1 == num3) q2 = self.quaggas['q2'] q2.add_route('10.60.0.0/24') time.sleep(1) num4 = len(self.gobgp.get_adj_rib_out(q1)) self.assertTrue(num1 + 1 == num4) def test_17_multi_statement(self): self.gobgp.local('gobgp policy statement st3 add action med set 100') self.gobgp.local('gobgp policy statement st4 add action local-pref 100') self.gobgp.local('gobgp policy add p3 st3 st4') self.gobgp.local('gobgp global policy import set p3 default accept') self.gobgp.add_route('10.70.0.0/24') time.sleep(1) rib = self.gobgp.get_global_rib('10.70.0.0/24') self.assertTrue(len(rib) == 1) self.assertTrue(len(rib[0]['paths']) == 1) path = rib[0]['paths'][0] self.assertTrue(path['med'] == 100) self.assertTrue(path['local-pref'] == 100) def test_18_reject_policy(self): self.gobgp.local('gobgp global policy import set default reject') self.gobgp.local('gobgp neighbor all softresetin') time.sleep(1) # self-generated routes remain since softresetin doesn't re-evaluate # them for v in self.gobgp.get_global_rib(): for p in v['paths']: self.assertTrue(p['nexthop'] == '0.0.0.0') if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/graceful_restart_test.py000066400000000000000000000143001324612745600233410ustar00rootroot00000000000000# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( BGP_FSM_IDLE, BGP_FSM_ACTIVE, BGP_FSM_ESTABLISHED, ) from lib.gobgp import GoBGPContainer class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g2 = GoBGPContainer(name='g2', asn=65001, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) ctns = [g1, g2] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) g1.add_route('10.10.10.0/24') g1.add_route('10.10.20.0/24') g1.add_peer(g2, graceful_restart=True) g2.add_peer(g1, graceful_restart=True) cls.bgpds = {'g1': g1, 'g2': g2} # test each neighbor state is turned establish def test_01_neighbor_established(self): g1 = self.bgpds['g1'] g2 = self.bgpds['g2'] g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g2) def test_02_graceful_restart(self): g1 = self.bgpds['g1'] g2 = self.bgpds['g2'] g1.graceful_restart() g2.wait_for(expected_state=BGP_FSM_ACTIVE, peer=g1) self.assertTrue(len(g2.get_global_rib('10.10.20.0/24')) == 1) self.assertTrue(len(g2.get_global_rib('10.10.10.0/24')) == 1) for d in g2.get_global_rib(): for p in d['paths']: self.assertTrue(p['stale']) g1.routes = {} g1._start_gobgp(graceful_restart=True) time.sleep(3) g1.add_route('10.10.20.0/24') def test_03_neighbor_established(self): g1 = self.bgpds['g1'] g2 = self.bgpds['g2'] g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g2) time.sleep(1) self.assertTrue(len(g2.get_global_rib('10.10.20.0/24')) == 1) self.assertTrue(len(g2.get_global_rib('10.10.10.0/24')) == 0) for d in g2.get_global_rib(): for p in d['paths']: self.assertFalse(p.get('stale', False)) def test_04_add_non_graceful_restart_enabled_peer(self): g1 = self.bgpds['g1'] # g2 = self.bgpds['g2'] gobgp_ctn_image_name = parser_option.gobgp_image g3 = GoBGPContainer(name='g3', asn=65002, router_id='192.168.0.3', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) self.bgpds['g3'] = g3 time.sleep(g3.run()) g3.add_route('10.10.30.0/24') g1.add_peer(g3) g3.add_peer(g1) g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g3) time.sleep(1) self.assertTrue(len(g3.get_global_rib('10.10.20.0/24')) == 1) def test_05_graceful_restart(self): g1 = self.bgpds['g1'] g2 = self.bgpds['g2'] g3 = self.bgpds['g3'] g1.graceful_restart() g2.wait_for(expected_state=BGP_FSM_ACTIVE, peer=g1) self.assertTrue(len(g2.get_global_rib('10.10.20.0/24')) == 1) self.assertTrue(len(g2.get_global_rib('10.10.30.0/24')) == 1) for d in g2.get_global_rib(): for p in d['paths']: self.assertTrue(p['stale']) self.assertTrue(len(g3.get_global_rib('10.10.20.0/24')) == 0) self.assertTrue(len(g3.get_global_rib('10.10.30.0/24')) == 1) def test_06_test_restart_timer_expire(self): time.sleep(25) g2 = self.bgpds['g2'] self.assertTrue(len(g2.get_global_rib()) == 0) def test_07_multineighbor_established(self): g1 = self.bgpds['g1'] g2 = self.bgpds['g2'] g3 = self.bgpds['g3'] g1._start_gobgp() g1.del_peer(g2) g1.del_peer(g3) g2.del_peer(g1) g3.del_peer(g1) g1.add_peer(g2, graceful_restart=True, llgr=True) g1.add_peer(g3, graceful_restart=True, llgr=True) g2.add_peer(g1, graceful_restart=True, llgr=True) g3.add_peer(g1, graceful_restart=True, llgr=True) g2.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g1) g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g1) def test_08_multineighbor_graceful_restart(self): g1 = self.bgpds['g1'] g2 = self.bgpds['g2'] g3 = self.bgpds['g3'] g1.graceful_restart() g2.wait_for(expected_state=BGP_FSM_ACTIVE, peer=g1) g3.wait_for(expected_state=BGP_FSM_ACTIVE, peer=g1) g1._start_gobgp(graceful_restart=True) count = 0 while ((g1.get_neighbor_state(g2) != BGP_FSM_ESTABLISHED) or (g1.get_neighbor_state(g3) != BGP_FSM_ESTABLISHED)): count += 1 # assert connections are not refused self.assertTrue(g1.get_neighbor_state(g2) != BGP_FSM_IDLE) self.assertTrue(g1.get_neighbor_state(g3) != BGP_FSM_IDLE) if count > 120: raise Exception('timeout') time.sleep(1) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/ibgp_router_test.py000066400000000000000000000250151324612745600223330ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import from itertools import combinations import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( BGP_FSM_IDLE, BGP_FSM_ESTABLISHED, ) from lib.base import wait_for_completion from lib.gobgp import GoBGPContainer from lib.quagga import QuaggaBGPContainer class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) q1 = QuaggaBGPContainer(name='q1', asn=65000, router_id='192.168.0.2') q2 = QuaggaBGPContainer(name='q2', asn=65000, router_id='192.168.0.3') qs = [q1, q2] ctns = [g1, q1, q2] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) # ibgp peer. loop topology for a, b in combinations(ctns, 2): a.add_peer(b) b.add_peer(a) # advertise a route from q1, q2 for idx, c in enumerate(qs): route = '10.0.{0}.0/24'.format(idx + 1) c.add_route(route) cls.gobgp = g1 cls.quaggas = {'q1': q1, 'q2': q2} # test each neighbor state is turned establish def test_01_neighbor_established(self): for q in self.quaggas.itervalues(): self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q) def test_02_check_gobgp_global_rib(self): for q in self.quaggas.itervalues(): # paths expected to exist in gobgp's global rib routes = q.routes.keys() timeout = 120 interval = 1 count = 0 while True: # gobgp's global rib state = self.gobgp.get_neighbor_state(q) self.assertEqual(state, BGP_FSM_ESTABLISHED) global_rib = [p['prefix'] for p in self.gobgp.get_global_rib()] for p in global_rib: if p in routes: routes.remove(p) if len(routes) == 0: break time.sleep(interval) count += interval if count >= timeout: raise Exception('timeout') def test_03_check_gobgp_adj_rib_out(self): for q in self.quaggas.itervalues(): paths = self.gobgp.get_adj_rib_out(q) # bgp speaker mustn't forward iBGP routes to iBGP peers self.assertTrue(len(paths) == 0) def test_04_originate_path(self): self.gobgp.add_route('10.10.0.0/24') dst = self.gobgp.get_global_rib('10.10.0.0/24') self.assertTrue(len(dst) == 1) self.assertTrue(len(dst[0]['paths']) == 1) path = dst[0]['paths'][0] self.assertTrue(path['nexthop'] == '0.0.0.0') self.assertTrue(len(path['aspath']) == 0) def test_05_check_gobgp_adj_rib_out(self): for q in self.quaggas.itervalues(): paths = self.gobgp.get_adj_rib_out(q) self.assertTrue(len(paths) == len(self.gobgp.routes)) path = paths[0] self.assertTrue(path['nlri']['prefix'] == '10.10.0.0/24') peer_info = self.gobgp.peers[q] local_addr = peer_info['local_addr'].split('/')[0] self.assertTrue(path['nexthop'] == local_addr) self.assertTrue(len(path['aspath']) == 0) # check routes are properly advertised to all BGP speaker def test_06_check_quagga_global_rib(self): interval = 1 timeout = int(120 / interval) for q in self.quaggas.itervalues(): done = False for _ in range(timeout): if done: break global_rib = q.get_global_rib() # quagga's global_rib must have two routes at least, # a self-generated route and a gobgp-generated route if len(global_rib) < len(q.routes) + len(self.gobgp.routes): time.sleep(interval) continue peer_info = self.gobgp.peers[q] local_addr = peer_info['local_addr'].split('/')[0] for r in self.gobgp.routes: self.assertTrue(r in (p['prefix'] for p in global_rib)) for rr in global_rib: if rr['prefix'] == r: self.assertTrue(rr['nexthop'] == local_addr) for r in q.routes.keys(): self.assertTrue(r in (p['prefix'] for p in global_rib)) for rr in global_rib: if rr['prefix'] == r: self.assertTrue(rr['nexthop'] == '0.0.0.0') done = True if done: continue # should not reach here raise AssertionError def test_07_add_ebgp_peer(self): q3 = QuaggaBGPContainer(name='q3', asn=65001, router_id='192.168.0.4') self.quaggas['q3'] = q3 initial_wait_time = q3.run() time.sleep(initial_wait_time) self.gobgp.add_peer(q3) q3.add_peer(self.gobgp) q3.add_route('10.0.3.0/24') self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q3) def test_08_check_global_rib(self): self.test_02_check_gobgp_global_rib() def test_09_check_gobgp_ebgp_adj_rib_out(self): q1 = self.quaggas['q1'] q2 = self.quaggas['q2'] q3 = self.quaggas['q3'] paths = self.gobgp.get_adj_rib_out(q3) total_len = len(q1.routes) + len(q2.routes) + len(self.gobgp.routes) assert len(paths) == total_len for path in paths: peer_info = self.gobgp.peers[q3] local_addr = peer_info['local_addr'].split('/')[0] self.assertTrue(path['nexthop'] == local_addr) self.assertTrue(path['aspath'] == [self.gobgp.asn]) def test_10_check_gobgp_ibgp_adj_rib_out(self): q1 = self.quaggas['q1'] q3 = self.quaggas['q3'] peer_info = self.gobgp.peers[q3] neigh_addr = peer_info['neigh_addr'].split('/')[0] for prefix in q3.routes.iterkeys(): paths = self.gobgp.get_adj_rib_out(q1, prefix) self.assertTrue(len(paths) == 1) path = paths[0] # bgp router mustn't change nexthop of routes from eBGP peers # which are sent to iBGP peers self.assertTrue(path['nexthop'] == neigh_addr) # bgp router mustn't change aspath of routes from eBGP peers # which are sent to iBGP peers self.assertTrue(path['aspath'] == [q3.asn]) # disable ebgp peer, check ebgp routes are removed def test_11_disable_ebgp_peer(self): q3 = self.quaggas['q3'] self.gobgp.disable_peer(q3) del self.quaggas['q3'] self.gobgp.wait_for(expected_state=BGP_FSM_IDLE, peer=q3) for route in q3.routes.iterkeys(): dst = self.gobgp.get_global_rib(route) self.assertTrue(len(dst) == 0) for q in self.quaggas.itervalues(): paths = self.gobgp.get_adj_rib_out(q) # only gobgp's locally generated routes must exists print paths self.assertTrue(len(paths) == len(self.gobgp.routes)) def test_12_disable_ibgp_peer(self): q1 = self.quaggas['q1'] self.gobgp.disable_peer(q1) self.gobgp.wait_for(expected_state=BGP_FSM_IDLE, peer=q1) for route in q1.routes.iterkeys(): dst = self.gobgp.get_global_rib(route) self.assertTrue(len(dst) == 0) def test_13_enable_ibgp_peer(self): q1 = self.quaggas['q1'] self.gobgp.enable_peer(q1) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q1) def test_14_check_gobgp_adj_rib_out(self): for q in self.quaggas.itervalues(): paths = self.gobgp.get_adj_rib_out(q) # only gobgp's locally generated routes must exists self.assertTrue(len(paths) == len(self.gobgp.routes)) def test_15_add_ebgp_peer(self): q4 = QuaggaBGPContainer(name='q4', asn=65001, router_id='192.168.0.5') self.quaggas['q4'] = q4 initial_wait_time = q4.run() time.sleep(initial_wait_time) self.gobgp.add_peer(q4) q4.add_peer(self.gobgp) prefix = '10.0.4.0/24' q4.add_route(prefix) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q4) q1 = self.quaggas['q1'] q2 = self.quaggas['q2'] for q in [q1, q2]: def _f(): return prefix in [p['nlri']['prefix'] for p in self.gobgp.get_adj_rib_out(q)] wait_for_completion(_f) def f(): return len(q2.get_global_rib(prefix)) == 1 wait_for_completion(f) def test_16_add_best_path_from_ibgp(self): q1 = self.quaggas['q1'] q2 = self.quaggas['q2'] prefix = '10.0.4.0/24' q1.add_route(prefix) def f1(): l = self.gobgp.get_global_rib(prefix) return len(l) == 1 and len(l[0]['paths']) == 2 wait_for_completion(f1) def f2(): return prefix not in [p['nlri']['prefix'] for p in self.gobgp.get_adj_rib_out(q2)] wait_for_completion(f2) def f3(): l = q2.get_global_rib(prefix) # route from ibgp so aspath should be empty return len(l) == 1 and len(l[0]['aspath']) == 0 wait_for_completion(f3) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/long_lived_graceful_restart_test.py000066400000000000000000000144171324612745600255540ustar00rootroot00000000000000# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import from itertools import chain import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( BGP_FSM_ACTIVE, BGP_FSM_ESTABLISHED, ) from lib.gobgp import GoBGPContainer class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g2 = GoBGPContainer(name='g2', asn=65001, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g3 = GoBGPContainer(name='g3', asn=65002, router_id='192.168.0.3', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g4 = GoBGPContainer(name='g4', asn=65003, router_id='192.168.0.4', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) ctns = [g1, g2, g3, g4] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) g1.add_peer(g2, graceful_restart=True, llgr=True) g2.add_peer(g1, graceful_restart=True, llgr=True) g1.add_peer(g3, graceful_restart=True, llgr=True) g3.add_peer(g1, graceful_restart=True, llgr=True) g1.add_peer(g4, graceful_restart=True) g4.add_peer(g1, graceful_restart=True) cls.bgpds = {'g1': g1, 'g2': g2, 'g3': g3, 'g4': g4} # test each neighbor state is turned establish def test_01_neighbor_established(self): g1 = self.bgpds['g1'] g2 = self.bgpds['g2'] g3 = self.bgpds['g3'] g4 = self.bgpds['g4'] g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g2) g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g3) g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g4) def test_02_graceful_restart(self): g1 = self.bgpds['g1'] g2 = self.bgpds['g2'] g3 = self.bgpds['g3'] g4 = self.bgpds['g4'] g2.local('gobgp global rib add 10.0.0.0/24') g2.local('gobgp global rib add 10.10.0.0/24 community no-llgr') time.sleep(1) g2.graceful_restart() g1.wait_for(expected_state=BGP_FSM_ACTIVE, peer=g2) time.sleep(1) self.assertTrue(len(g1.get_global_rib('10.0.0.0/24')) == 1) # 10.10.0.0/24 is announced with no-llgr community # must not exist in the rib self.assertTrue(len(g1.get_global_rib('10.10.0.0/24')) == 0) for d in g1.get_global_rib(): for p in d['paths']: self.assertTrue(p['stale']) self.assertTrue(len(g3.get_global_rib('10.0.0.0/24')) == 1) # check llgr-stale community is added to 10.0.0.0/24 r = g3.get_global_rib('10.0.0.0/24')[0]['paths'][0] comms = list(chain.from_iterable([attr['communities'] for attr in r['attrs'] if attr['type'] == 8])) self.assertTrue(0xffff0006 in comms) # g4 is not llgr capable, llgr-stale route must be # withdrawn self.assertTrue(len(g4.get_global_rib('10.0.0.0/24')) == 0) g2._start_gobgp(graceful_restart=True) time.sleep(2) g2.local('gobgp global rib add 10.0.0.0/24') g2.local('gobgp global rib add 10.10.0.0/24') def test_03_neighbor_established(self): g1 = self.bgpds['g1'] g2 = self.bgpds['g2'] # g3 = self.bgpds['g3'] # g4 = self.bgpds['g4'] g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g2) time.sleep(1) self.assertTrue(len(g1.get_global_rib('10.0.0.0/24')) == 1) self.assertTrue(len(g1.get_global_rib('10.10.0.0/24')) == 1) for d in g1.get_global_rib(): for p in d['paths']: self.assertFalse(p.get('stale', False)) def test_04_llgr_stale_route_depreferenced(self): g1 = self.bgpds['g1'] g2 = self.bgpds['g2'] g3 = self.bgpds['g3'] g4 = self.bgpds['g4'] g4.local('gobgp global rib add 10.0.0.0/24 med 100') time.sleep(1) # check g2's path is chosen as best and advertised rib = g3.get_global_rib('10.0.0.0/24') self.assertTrue(len(rib) == 1) self.assertTrue(g2.asn in rib[0]['paths'][0]['aspath']) g2.graceful_restart() g1.wait_for(expected_state=BGP_FSM_ACTIVE, peer=g2) time.sleep(1) # llgr_stale route depreference must happend # check g4's path is chosen as best and advertised rib = g3.get_global_rib('10.0.0.0/24') self.assertTrue(len(rib) == 1) self.assertTrue(g4.asn in rib[0]['paths'][0]['aspath']) # if no candidate exists, llgr_stale route will be chosen as best rib = g3.get_global_rib('10.10.0.0/24') self.assertTrue(len(rib) == 1) self.assertTrue(g2.asn in rib[0]['paths'][0]['aspath']) def test_05_llgr_restart_timer_expire(self): time.sleep(35) g3 = self.bgpds['g3'] rib = g3.get_global_rib('10.10.0.0/24') self.assertTrue(len(rib) == 0) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/route_reflector_test.py000066400000000000000000000104241324612745600232130ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer from lib.quagga import QuaggaBGPContainer def wait_for(f, timeout=120): interval = 1 count = 0 while True: if f(): return time.sleep(interval) count += interval if count >= timeout: raise Exception('timeout') class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) q1 = QuaggaBGPContainer(name='q1', asn=65000, router_id='192.168.0.2') q2 = QuaggaBGPContainer(name='q2', asn=65000, router_id='192.168.0.3') q3 = QuaggaBGPContainer(name='q3', asn=65000, router_id='192.168.0.4') q4 = QuaggaBGPContainer(name='q4', asn=65000, router_id='192.168.0.5') qs = [q1, q2, q3, q4] ctns = [g1, q1, q2, q3, q4] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) # g1 as a route reflector g1.add_peer(q1, is_rr_client=True) q1.add_peer(g1) g1.add_peer(q2, is_rr_client=True) q2.add_peer(g1) g1.add_peer(q3) q3.add_peer(g1) g1.add_peer(q4) q4.add_peer(g1) # advertise a route from q1, q2 for idx, c in enumerate(qs): route = '10.0.{0}.0/24'.format(idx + 1) c.add_route(route) cls.gobgp = g1 cls.quaggas = {'q1': q1, 'q2': q2, 'q3': q3, 'q4': q4} # test each neighbor state is turned establish def test_01_neighbor_established(self): for q in self.quaggas.itervalues(): self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q) def test_02_check_gobgp_global_rib(self): for q in self.quaggas.itervalues(): # paths expected to exist in gobgp's global rib def f(): state = self.gobgp.get_neighbor_state(q) self.assertEqual(state, BGP_FSM_ESTABLISHED) routes = q.routes.keys() global_rib = [p['prefix'] for p in self.gobgp.get_global_rib()] for p in global_rib: if p in routes: routes.remove(p) return len(routes) == 0 wait_for(f) def test_03_check_gobgp_adj_rib_out(self): for q in self.quaggas.itervalues(): paths = [p['nlri']['prefix'] for p in self.gobgp.get_adj_rib_out(q)] for qq in self.quaggas.itervalues(): if q == qq: continue if self.gobgp.peers[q]['is_rr_client']: for p in qq.routes.keys(): self.assertTrue(p in paths) else: for p in qq.routes.keys(): if self.gobgp.peers[qq]['is_rr_client']: self.assertTrue(p in paths) else: self.assertFalse(p in paths) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/route_server_as2_test.py000066400000000000000000000073601324612745600233060ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import unittest import sys import time from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( BGP_FSM_IDLE, BGP_FSM_ESTABLISHED, ) from lib.gobgp import GoBGPContainer from lib.exabgp import ExaBGPContainer class GoBGPTestBase(unittest.TestCase): wait_per_retry = 5 retry_limit = 10 @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) rs_clients = [ ExaBGPContainer(name='q{0}'.format(i + 1), asn=(65001 + i), router_id='192.168.0.{0}'.format(i + 2)) for i in range(4)] ctns = [g1] + rs_clients initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) for i, rs_client in enumerate(rs_clients): g1.add_peer(rs_client, is_rs_client=True) as2 = False if i > 1: as2 = True rs_client.add_peer(g1, as2=as2) # advertise a route from route-server-clients for idx, rs_client in enumerate(rs_clients): route = '10.0.{0}.0/24'.format(idx + 1) rs_client.add_route(route) if idx < 2: route = '10.0.10.0/24' rs_client.add_route(route) cls.gobgp = g1 cls.quaggas = {x.name: x for x in rs_clients} # test each neighbor state is turned establish def test_01_neighbor_established(self): for q in self.quaggas.itervalues(): self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q) def test_02_check_gobgp_local_rib(self): for rs_client in self.quaggas.itervalues(): done = False for _ in range(self.retry_limit): if done: break state = self.gobgp.get_neighbor_state(rs_client) self.assertEqual(state, BGP_FSM_ESTABLISHED) local_rib = self.gobgp.get_local_rib(rs_client) local_rib = [p['prefix'] for p in local_rib] if len(local_rib) < (len(self.quaggas) - 1): time.sleep(self.wait_per_retry) continue self.assertTrue(len(local_rib) == 4) done = True if done: continue # should not reach here raise AssertionError def test_03_stop_q2_and_check_neighbor_status(self): q2 = self.quaggas['q2'] q2.remove() self.gobgp.wait_for(expected_state=BGP_FSM_IDLE, peer=q2) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/route_server_ipv4_v6_test.py000066400000000000000000000143761324612745600241230ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer from lib.quagga import QuaggaBGPContainer class GoBGPIPv6Test(unittest.TestCase): wait_per_retry = 5 retry_limit = 15 @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65002, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) q1 = QuaggaBGPContainer(name='q1', asn=65003, router_id='192.168.0.3') q2 = QuaggaBGPContainer(name='q2', asn=65004, router_id='192.168.0.4') q3 = QuaggaBGPContainer(name='q3', asn=65005, router_id='192.168.0.5') q4 = QuaggaBGPContainer(name='q4', asn=65006, router_id='192.168.0.6') ctns = [g1, q1, q2, q3, q4] v4 = [q1, q2] v6 = [q3, q4] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) for ctn in v4: g1.add_peer(ctn, is_rs_client=True) ctn.add_peer(g1) for ctn in v6: g1.add_peer(ctn, is_rs_client=True, v6=True) ctn.add_peer(g1, v6=True) for idx, q in enumerate(v4): route = '10.0.{0}.0/24'.format(idx + 1) q.add_route(route) for idx, q in enumerate(v6): route = '2001:{0}::/96'.format(idx + 1) q.add_route(route, rf='ipv6') cls.gobgp = g1 cls.quaggas = {'q1': q1, 'q2': q2, 'q3': q3, 'q4': q4} cls.ipv4s = {'q1': q1, 'q2': q2} cls.ipv6s = {'q3': q3, 'q4': q4} def check_gobgp_local_rib(self, ctns, rf): for rs_client in ctns.itervalues(): done = False for _ in range(self.retry_limit): if done: break state = self.gobgp.get_neighbor_state(rs_client) self.assertEqual(state, BGP_FSM_ESTABLISHED) local_rib = self.gobgp.get_local_rib(rs_client, rf=rf) local_rib = [p['prefix'] for p in local_rib] if len(local_rib) < (len(ctns) - 1): time.sleep(self.wait_per_retry) continue self.assertTrue(len(local_rib) == (len(ctns) - 1)) for c in ctns.itervalues(): if rs_client != c: for r in c.routes: self.assertTrue(r in local_rib) done = True if done: continue # should not reach here raise AssertionError def check_rs_client_rib(self, ctns, rf): for rs_client in ctns.itervalues(): done = False for _ in range(self.retry_limit): if done: break global_rib = rs_client.get_global_rib(rf=rf) global_rib = [p['prefix'] for p in global_rib] if len(global_rib) < len(ctns): time.sleep(self.wait_per_retry) continue self.assertTrue(len(global_rib) == len(ctns)) for c in ctns.itervalues(): for r in c.routes: self.assertTrue(r in global_rib) done = True if done: continue # should not reach here raise AssertionError # test each neighbor state is turned establish def test_01_neighbor_established(self): for q in self.quaggas.itervalues(): self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q) def test_02_check_ipv4_peer_rib(self): self.check_gobgp_local_rib(self.ipv4s, 'ipv4') self.check_rs_client_rib(self.ipv4s, 'ipv4') def test_03_check_ipv6_peer_rib(self): self.check_gobgp_local_rib(self.ipv6s, 'ipv6') self.check_rs_client_rib(self.ipv6s, 'ipv6') def test_04_add_in_policy_to_reject_all(self): for q in self.gobgp.peers.itervalues(): self.gobgp.local('gobgp neighbor {0} policy in set default reject'.format(q['neigh_addr'].split('/')[0])) def test_05_check_ipv4_peer_rib(self): self.check_gobgp_local_rib(self.ipv4s, 'ipv4') self.check_rs_client_rib(self.ipv4s, 'ipv4') def test_06_check_ipv6_peer_rib(self): self.check_gobgp_local_rib(self.ipv6s, 'ipv6') self.check_rs_client_rib(self.ipv6s, 'ipv6') def test_07_add_in_policy_to_reject_all(self): self.gobgp.local('gobgp neighbor all softresetin') time.sleep(1) def test_08_check_rib(self): for q in self.ipv4s.itervalues(): self.assertTrue(all(p['filtered'] for p in self.gobgp.get_adj_rib_in(q))) self.assertTrue(len(self.gobgp.get_adj_rib_out(q)) == 0) self.assertTrue(len(q.get_global_rib()) == len(q.routes)) for q in self.ipv6s.itervalues(): self.assertTrue(all(p['filtered'] for p in self.gobgp.get_adj_rib_in(q, rf='ipv6'))) self.assertTrue(len(self.gobgp.get_adj_rib_out(q, rf='ipv6')) == 0) self.assertTrue(len(q.get_global_rib(rf='ipv6')) == len(q.routes)) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/route_server_malformed_test.py000066400000000000000000000357401324612745600245720ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest import inspect from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer from lib.exabgp import ExaBGPContainer counter = 1 _SCENARIOS = {} def register_scenario(cls): global counter _SCENARIOS[counter] = cls counter += 1 def lookup_scenario(name): for value in _SCENARIOS.values(): if value.__name__ == name: return value return None def wait_for(f, timeout=120): interval = 1 count = 0 while True: if f(): return time.sleep(interval) count += interval if count >= timeout: raise Exception('timeout') @register_scenario class MalformedMpReachNlri(object): """ No.1 malformaed mp-reach-nlri """ @staticmethod def boot(env): gobgp_ctn_image_name = env.parser_option.gobgp_image log_level = env.parser_option.gobgp_log_level g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=log_level) e1 = ExaBGPContainer(name='e1', asn=65001, router_id='192.168.0.2') e2 = ExaBGPContainer(name='e2', asn=65001, router_id='192.168.0.2') ctns = [g1, e1, e2] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) for q in [e1, e2]: g1.add_peer(q, is_rs_client=True) q.add_peer(g1) env.g1 = g1 env.e1 = e1 env.e2 = e2 @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 for c in [e1, e2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) # advertise malformed MP_REACH_NLRI e1.add_route('10.7.0.17/32', attribute='0x0e 0x60 0x11223344') @staticmethod def check(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 def f(): for line in e1.log().split('\n'): if 'UPDATE message error / Attribute Flags Error / 0x600E0411223344' in line: return True return False wait_for(f) # check e2 is still established g1.wait_for(BGP_FSM_ESTABLISHED, e2) @staticmethod def executor(env): lookup_scenario("MalformedMpReachNlri").boot(env) lookup_scenario("MalformedMpReachNlri").setup(env) lookup_scenario("MalformedMpReachNlri").check(env) @register_scenario class MalformedMpUnReachNlri(object): """ No.2 malformaed mp-unreach-nlri """ @staticmethod def boot(env): lookup_scenario("MalformedMpReachNlri").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 for c in [e1, e2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) # advertise malformed MP_UNREACH_NLRI e1.add_route('10.7.0.17/32', attribute='0x0f 0x60 0x11223344') @staticmethod def check(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 def f(): for line in e1.log().split('\n'): if 'UPDATE message error / Attribute Flags Error / 0x600F0411223344' in line: return True return False wait_for(f) # check e2 is still established g1.wait_for(BGP_FSM_ESTABLISHED, e2) @staticmethod def executor(env): lookup_scenario("MalformedMpUnReachNlri").boot(env) lookup_scenario("MalformedMpUnReachNlri").setup(env) lookup_scenario("MalformedMpUnReachNlri").check(env) @register_scenario class MalformedAsPath(object): """ No.3 malformaed as-path """ @staticmethod def boot(env): lookup_scenario("MalformedMpReachNlri").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 for c in [e1, e2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) # advertise malformed AS_PATH # Send the attribute to the length and number of aspath is inconsistent # Attribute Type 0x02 (AS_PATH) # Attribute Flag 0x40 (well-known transitive) # Attribute Value 0x02020000ffdc ( # segment type = 02 # segment length = 02 -> # correct value = 01 # as number = 65500 ) e1.add_route('10.7.0.17/32', attribute='0x02 0x60 0x11223344') @staticmethod def check(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 def f(): for line in e1.log().split('\n'): if 'UPDATE message error / Attribute Flags Error / 0x60020411223344' in line: return True return False wait_for(f) # check e2 is still established g1.wait_for(BGP_FSM_ESTABLISHED, e2) @staticmethod def executor(env): lookup_scenario("MalformedAsPath").boot(env) lookup_scenario("MalformedAsPath").setup(env) lookup_scenario("MalformedAsPath").check(env) @register_scenario class MalformedAs4Path(object): """ No.4 malformaed as4-path """ @staticmethod def boot(env): lookup_scenario("MalformedMpReachNlri").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 for c in [e1, e2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) # advertise malformed AS4_PATH e1.add_route('10.7.0.17/32', attribute='0x11 0x60 0x11223344') @staticmethod def check(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 def f(): for line in e1.log().split('\n'): if 'UPDATE message error / Attribute Flags Error / 0x60110411223344' in line: return True return False wait_for(f) # check e2 is still established g1.wait_for(BGP_FSM_ESTABLISHED, e2) @staticmethod def executor(env): lookup_scenario("MalformedAs4Path").boot(env) lookup_scenario("MalformedAs4Path").setup(env) lookup_scenario("MalformedAs4Path").check(env) @register_scenario class MalformedNexthop(object): """ No.5 malformaed nexthop """ @staticmethod def boot(env): lookup_scenario("MalformedMpReachNlri").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 for c in [e1, e2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) # advertise malformed NEXT_HOP # 0x0e: MP_REACH_NLRI # 0x60: Optional, Transitive # 0x01: AFI(IPv4) # 0x01: SAFI(unicast) # 0x10: Length of Next Hop Address # 0xffffff00: Network address of Next Hop # 0x00: Reserved e1.add_route('10.7.0.17/32', attribute='0x0e 0x60 0x010110ffffff0000') @staticmethod def check(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 def f(): for line in e1.log().split('\n'): if 'UPDATE message error / Attribute Flags Error / 0x600E08010110FFFFFF0000' in line: return True return False wait_for(f) # check e2 is still established g1.wait_for(BGP_FSM_ESTABLISHED, e2) @staticmethod def executor(env): lookup_scenario("MalformedNexthop").boot(env) lookup_scenario("MalformedNexthop").setup(env) lookup_scenario("MalformedNexthop").check(env) @register_scenario class MalformedRouteFamily(object): """ No.6 malformaed route family """ @staticmethod def boot(env): lookup_scenario("MalformedMpReachNlri").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 for c in [e1, e2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) # advertise malformed ROUTE_FAMILY # 0x0e: MP_REACH_NLRI # 0x60: Optional, Transitive # 0x01: AFI(IPv4) # 0x01: SAFI(unicast) # 0x10: Length of Next Hop Address # 0xffffff00: Network address of Next Hop # 0x00: Reserved e1.add_route('10.7.0.17/32', attribute='0x0e 0x60 0x0002011020010db800000000000000000000000100') @staticmethod def check(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 def f(): for line in e1.log().split('\n'): if 'UPDATE message error / Attribute Flags Error / 0x600E150002011020010DB800000000000000000000000100' in line: return True return False wait_for(f) # check e2 is still established g1.wait_for(BGP_FSM_ESTABLISHED, e2) @staticmethod def executor(env): lookup_scenario("MalformedRouteFamily").boot(env) lookup_scenario("MalformedRouteFamily").setup(env) lookup_scenario("MalformedRouteFamily").check(env) @register_scenario class MalformedAsPathSegmentLengthInvalid(object): """ No.7 malformaed aspath segment length invalid """ @staticmethod def boot(env): lookup_scenario("MalformedMpReachNlri").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 for c in [e1, e2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) # advertise malformed AS_PATH SEGMENT LENGTH # Send the attribute to the length and number of aspath is inconsistent # Attribute Type 0x02 (AS_PATH) # Attribute Flag 0x40 (well-known transitive) # Attribute Value 0x02020000ffdc ( # segment type = 02 # segment length = 02 -> # correct value = 01 # as number = 65500 ) e1.add_route('10.7.0.17/32', attribute='0x02 0x40 0x0202ffdc') @staticmethod def check(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 def f(): for line in e1.log().split('\n'): if 'UPDATE message error / Malformed AS_PATH / 0x4002040202FFDC' in line: return True return False wait_for(f) # check e2 is still established g1.wait_for(BGP_FSM_ESTABLISHED, e2) @staticmethod def executor(env): lookup_scenario("MalformedAsPathSegmentLengthInvalid").boot(env) lookup_scenario("MalformedAsPathSegmentLengthInvalid").setup(env) lookup_scenario("MalformedAsPathSegmentLengthInvalid").check(env) @register_scenario class MalformedNexthopLoopbackAddr(object): """ No.8 malformaed nexthop loopback addr """ @staticmethod def boot(env): lookup_scenario("MalformedMpReachNlri").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 for c in [e1, e2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) # Malformed Invalid NEXT_HOP Attribute # Send the attribute of invalid nexthop # next-hop 127.0.0.1 -> # correct value = other than loopback and 0.0.0.0 address e1.add_route('10.7.0.17/32', nexthop='127.0.0.1') @staticmethod def check(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 def f(): for line in e1.log().split('\n'): if 'UPDATE message error / Invalid NEXT_HOP Attribute / 0x4003047F000001' in line: return True return False wait_for(f) # check e2 is still established g1.wait_for(BGP_FSM_ESTABLISHED, e2) @staticmethod def executor(env): lookup_scenario("MalformedNexthopLoopbackAddr").boot(env) lookup_scenario("MalformedNexthopLoopbackAddr").setup(env) lookup_scenario("MalformedNexthopLoopbackAddr").check(env) @register_scenario class MalformedOriginType(object): """ No.9 malformaed origin type """ @staticmethod def boot(env): lookup_scenario("MalformedMpReachNlri").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 for c in [e1, e2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) # Invalid ORIGIN Attribute # Send the attribute of origin type 4 # Attribute Type 0x01 (Origin) # Attribute Flag 0x40 (well-known transitive) # Attribute Value 0x04 ( # origin type = 04 -> # correct value = 01 or 02 or 03 ) e1.add_route('10.7.0.17/32', attribute='0x1 0x40 0x04') @staticmethod def check(env): g1 = env.g1 e1 = env.e1 e2 = env.e2 def f(): for line in e1.log().split('\n'): if 'UPDATE message error / Invalid ORIGIN Attribute / 0x40010104' in line: return True return False wait_for(f) # check e2 is still established g1.wait_for(BGP_FSM_ESTABLISHED, e2) @staticmethod def executor(env): lookup_scenario("MalformedOriginType").boot(env) lookup_scenario("MalformedOriginType").setup(env) lookup_scenario("MalformedOriginType").check(env) class TestGoBGPBase(unittest.TestCase): wait_per_retry = 5 retry_limit = 10 @classmethod def setUpClass(cls): idx = parser_option.test_index base.TEST_PREFIX = parser_option.test_prefix cls.parser_option = parser_option cls.executors = [] if idx == 0: print 'unset test-index. run all test sequential' for _, v in _SCENARIOS.items(): for k, m in inspect.getmembers(v, inspect.isfunction): if k == 'executor': cls.executor = m cls.executors.append(cls.executor) elif idx not in _SCENARIOS: print 'invalid test-index. # of scenarios: {0}'.format(len(_SCENARIOS)) sys.exit(1) else: for k, m in inspect.getmembers(_SCENARIOS[idx], inspect.isfunction): if k == 'executor': cls.executor = m cls.executors.append(cls.executor) def test(self): for e in self.executors: yield e if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/route_server_policy_grpc_test.py000066400000000000000000003253101324612745600251310ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest import inspect from fabric.api import local import nose from nose.tools import ( assert_true, assert_false, ) from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( Bridge, BGP_FSM_ESTABLISHED, BGP_ATTR_TYPE_COMMUNITIES, BGP_ATTR_TYPE_EXTENDED_COMMUNITIES, ) from lib.gobgp import GoBGPContainer from lib.quagga import QuaggaBGPContainer from lib.exabgp import ExaBGPContainer counter = 1 _SCENARIOS = {} def register_scenario(cls): global counter _SCENARIOS[counter] = cls counter += 1 def lookup_scenario(name): for value in _SCENARIOS.values(): if value.__name__ == name: return value return None def wait_for(f, timeout=120): interval = 1 count = 0 while True: if f(): return time.sleep(interval) count += interval if count >= timeout: raise Exception('timeout') @register_scenario class ImportPolicy(object): """ No.1 import-policy test -------------------------------- e1 ->(192.168.2.0/24)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): gobgp_ctn_image_name = env.parser_option.gobgp_image log_level = env.parser_option.gobgp_log_level g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=log_level) e1 = ExaBGPContainer(name='e1', asn=65001, router_id='192.168.0.2') q1 = QuaggaBGPContainer(name='q1', asn=65002, router_id='192.168.0.3') q2 = QuaggaBGPContainer(name='q2', asn=65003, router_id='192.168.0.4') ctns = [g1, e1, q1, q2] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) for q in [e1, q1, q2]: g1.add_peer(q, is_rs_client=True) q.add_peer(g1) env.g1 = g1 env.e1 = e1 env.q1 = q1 env.q2 = q2 @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.0.0/16 16..24') g1.local('gobgp policy neighbor add ns0 {0}'.format(g1.peers[e1]['neigh_addr'].split('/')[0])) g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add condition neighbor ns0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('192.168.2.0/24') # this will pass e1.add_route('192.168.2.0/15') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1)) == 2) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1)) == 2) wait_for(lambda: len(env.q1.get_global_rib()) == 2) wait_for(lambda: len(env.g1.get_local_rib(env.q2)) == 1) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2)) == 1) wait_for(lambda: len(env.q2.get_global_rib()) == 1) @staticmethod def executor(env): lookup_scenario("ImportPolicy").boot(env) lookup_scenario("ImportPolicy").setup(env) lookup_scenario("ImportPolicy").check(env) @register_scenario class ExportPolicy(object): """ No.2 export-policy test -------------------------------- e1 ->(192.168.2.0/24)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | -> q2-rib ->x q2-adj-rib-out | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.0.0/16 16..24') g1.local('gobgp policy neighbor add ns0 {0}'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add condition neighbor ns0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('192.168.2.0/24') # this will pass e1.add_route('192.168.2.0/15') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 2) wait_for(lambda: len(q1.get_global_rib()) == 2) wait_for(lambda: len(g1.get_local_rib(q2)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def executor(env): lookup_scenario("ExportPolicy").boot(env) lookup_scenario("ExportPolicy").setup(env) lookup_scenario("ExportPolicy").check(env) @register_scenario class ImportPolicyUpdate(object): """ No.3 import-policy update test r1:192.168.2.0/24 r2:192.168.20.0/24 r3:192.168.200.0/24 ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------------------- | update gobgp.conf | V ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.20.0/24') g1.local('gobgp policy prefix add ps0 192.168.200.0/24') g1.local('gobgp policy neighbor add ns0 {0}'.format(g1.peers[e1]['neigh_addr'].split('/')[0])) g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add condition neighbor ns0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.2.0/24') e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 3) wait_for(lambda: len(q1.get_global_rib()) == 3) wait_for(lambda: len(g1.get_local_rib(q2)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 # q1 = env.q1 # q2 = env.q2 g1.local('gobgp policy prefix del ps0 192.168.200.0/24') g1.softreset(e1) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 3) wait_for(lambda: len(q1.get_global_rib()) == 3) wait_for(lambda: len(g1.get_local_rib(q2)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 2) wait_for(lambda: len(q2.get_global_rib()) == 2) @staticmethod def executor(env): lookup_scenario("ImportPolicyUpdate").boot(env) lookup_scenario("ImportPolicyUpdate").setup(env) lookup_scenario("ImportPolicyUpdate").check(env) lookup_scenario("ImportPolicyUpdate").setup2(env) lookup_scenario("ImportPolicyUpdate").check2(env) @register_scenario class ExportPolicyUpdate(object): """ No.4 export-policy update test r1:192.168.2.0 r2:192.168.20.0 r3:192.168.200.0 ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r2,r3)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------------------- | update gobgp.conf | V ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r2,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.20.0/24') g1.local('gobgp policy prefix add ps0 192.168.200.0/24') g1.local('gobgp policy neighbor add ns0 {0}'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add condition neighbor ns0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.2.0/24') e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 3) wait_for(lambda: len(q1.get_global_rib()) == 3) wait_for(lambda: len(g1.get_local_rib(q2)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix del ps0 192.168.200.0/24') # we need hard reset to flush q2's local rib g1.reset(e1) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 3) wait_for(lambda: len(q1.get_global_rib()) == 3) wait_for(lambda: len(g1.get_local_rib(q2)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 2) wait_for(lambda: len(q2.get_global_rib()) == 2) @staticmethod def executor(env): lookup_scenario("ExportPolicyUpdate").boot(env) lookup_scenario("ExportPolicyUpdate").setup(env) lookup_scenario("ExportPolicyUpdate").check(env) lookup_scenario("ExportPolicyUpdate").setup2(env) lookup_scenario("ExportPolicyUpdate").check2(env) @register_scenario class ExportPolicyUpdateRouteRefresh(object): """ export-policy update and route refresh handling test r1:192.168.2.0 r2:192.168.20.0 r3:192.168.200.0 ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r2,r3)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------------------- | update gobgp.conf q2 sends route refresh | V ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r2,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): lookup_scenario("ExportPolicyUpdate").boot(env) @staticmethod def setup(env): lookup_scenario("ExportPolicyUpdate").setup(env) @staticmethod def check(env): lookup_scenario("ExportPolicyUpdate").check(env) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix del ps0 192.168.200.0/24') q2.send_route_refresh() for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check2(env): lookup_scenario("ExportPolicyUpdate").check2(env) @staticmethod def executor(env): lookup_scenario("ExportPolicyUpdateRouteRefresh").boot(env) lookup_scenario("ExportPolicyUpdateRouteRefresh").setup(env) lookup_scenario("ExportPolicyUpdateRouteRefresh").check(env) lookup_scenario("ExportPolicyUpdateRouteRefresh").setup2(env) lookup_scenario("ExportPolicyUpdateRouteRefresh").check2(env) @register_scenario class ImportPolicyIPV6(object): """ No.5 IPv6 import-policy test r1=2001::/64 r2=2001::/63 ------------------------------------------------- e1 ->(r1,r2)-> | ->(r1,r2)-> q1-rib ->(r1,r2)-> q1-adj-rib-out | ->(r1,r2)-> q1 | | | ->(r2) -> q2-rib ->(r2) -> q2-adj-rib-out | ->(r2)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): gobgp_ctn_image_name = env.parser_option.gobgp_image log_level = env.parser_option.gobgp_log_level g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=log_level) e1 = ExaBGPContainer(name='e1', asn=65001, router_id='192.168.0.2') q1 = QuaggaBGPContainer(name='q1', asn=65002, router_id='192.168.0.3') q2 = QuaggaBGPContainer(name='q2', asn=65003, router_id='192.168.0.4') ctns = [g1, e1, q1, q2] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) br01 = Bridge(name='br01', subnet='2001::/96') [br01.addif(ctn) for ctn in ctns] for q in [e1, q1, q2]: g1.add_peer(q, is_rs_client=True, bridge=br01.name) q.add_peer(g1, bridge=br01.name) env.g1 = g1 env.e1 = e1 env.q1 = q1 env.q2 = q2 @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 2001::/32 64..128') g1.local('gobgp policy neighbor add ns0 {0}'.format(g1.peers[e1]['neigh_addr'].split('/')[0])) g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add condition neighbor ns0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('2001::/64', rf='ipv6') # this will pass e1.add_route('2001::/63', rf='ipv6') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 2) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 1) @staticmethod def executor(env): lookup_scenario("ImportPolicyIPV6").boot(env) lookup_scenario("ImportPolicyIPV6").setup(env) lookup_scenario("ImportPolicyIPV6").check(env) @register_scenario class ExportPolicyIPV6(object): """ No.6 IPv6 export-policy test r1=2001::/64 r2=2001::/63 ------------------------------------------------- e1 ->(r1,r2)-> | ->(r1,r2)-> q1-rib ->(r1,r2)-> q1-adj-rib-out | ->(r1,r2)-> q1 | | | ->(r1,r2)-> q2-rib ->(r2) -> q2-adj-rib-out | ->(r2)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicyIPV6').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 2001::/32 64..128') g1.local('gobgp policy neighbor add ns0 {0}'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add condition neighbor ns0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('2001::/64', rf='ipv6') # this will pass e1.add_route('2001::/63', rf='ipv6') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 2) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 1) @staticmethod def executor(env): lookup_scenario("ExportPolicyIPV6").boot(env) lookup_scenario("ExportPolicyIPV6").setup(env) lookup_scenario("ExportPolicyIPV6").check(env) @register_scenario class ImportPolicyIPV6Update(object): """ No.7 IPv6 import-policy update test r1=2001:0:10:2::/64 r2=2001:0:10:20::/64 r3=2001:0:10:200::/64 ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------------------- | update gobgp.conf | V ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicyIPV6').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 2001:0:10:2::/64') g1.local('gobgp policy prefix add ps0 2001:0:10:20::/64') g1.local('gobgp policy neighbor add ns0 {0}'.format(g1.peers[e1]['neigh_addr'].split('/')[0])) g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add condition neighbor ns0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('2001:0:10:2::/64', rf='ipv6') e1.add_route('2001:0:10:20::/64', rf='ipv6') e1.add_route('2001:0:10:200::/64', rf='ipv6') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 1) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 # q1 = env.q1 # q2 = env.q2 g1.local('gobgp policy prefix del ps0 2001:0:10:20::/64') g1.softreset(e1, rf='ipv6') @staticmethod def check2(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 2) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 2) @staticmethod def executor(env): lookup_scenario("ImportPolicyIPV6Update").boot(env) lookup_scenario("ImportPolicyIPV6Update").setup(env) lookup_scenario("ImportPolicyIPV6Update").check(env) lookup_scenario("ImportPolicyIPV6Update").setup2(env) lookup_scenario("ImportPolicyIPV6Update").check2(env) @register_scenario class ExportPolicyIPv6Update(object): """ No.8 IPv6 export-policy update test r1=2001:0:10:2::/64 r2=2001:0:10:20::/64 r3=2001:0:10:200::/64 ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r2,r3)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------------------- | update gobgp.conf | V ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r2,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicyIPV6').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 2001:0:10:2::/64') g1.local('gobgp policy prefix add ps0 2001:0:10:20::/64') g1.local('gobgp policy neighbor add ns0 {0}'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add condition neighbor ns0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('2001:0:10:2::/64', rf='ipv6') e1.add_route('2001:0:10:20::/64', rf='ipv6') e1.add_route('2001:0:10:200::/64', rf='ipv6') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 1) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix del ps0 2001:0:10:20::/64') g1.reset(e1) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check2(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 2) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 2) @staticmethod def executor(env): lookup_scenario("ExportPolicyIPv6Update").boot(env) lookup_scenario("ExportPolicyIPv6Update").setup(env) lookup_scenario("ExportPolicyIPv6Update").check(env) lookup_scenario("ExportPolicyIPv6Update").setup2(env) lookup_scenario("ExportPolicyIPv6Update").check2(env) @register_scenario class ImportPolicyAsPathLengthCondition(object): """ No.9 aspath length condition import-policy test -------------------------------- e1 ->(aspath_length=10)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition as-path-length 10 ge') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('192.168.100.0/24', aspath=range(e1.asn, e1.asn - 10, -1)) # this will pass e1.add_route('192.168.200.0/24', aspath=range(e1.asn, e1.asn - 8, -1)) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 2) wait_for(lambda: len(q1.get_global_rib()) == 2) wait_for(lambda: len(g1.get_local_rib(q2)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathLengthCondition").boot(env) lookup_scenario("ImportPolicyAsPathLengthCondition").setup(env) lookup_scenario("ImportPolicyAsPathLengthCondition").check(env) @register_scenario class ImportPolicyAsPathCondition(object): """ No.10 aspath from condition import-policy test -------------------------------- e1 ->(aspath=[65100,...])-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy as-path add as0 ^{0}'.format(e1.asn)) g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition as-path as0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('192.168.100.0/24', aspath=range(e1.asn, e1.asn - 10, -1)) # this will pass e1.add_route('192.168.200.0/24', aspath=range(e1.asn - 1, e1.asn - 10, -1)) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): # same check function as previous No.1 scenario lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathCondition").boot(env) lookup_scenario("ImportPolicyAsPathCondition").setup(env) lookup_scenario("ImportPolicyAsPathCondition").check(env) @register_scenario class ImportPolicyAsPathAnyCondition(object): """ No.11 aspath any condition import-policy test -------------------------------- e1 ->(aspath=[...65098,...])-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy as-path add as0 65098') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition as-path as0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('192.168.100.0/24', aspath=[65000, 65098, 65010]) # this will pass e1.add_route('192.168.200.0/24', aspath=[65000, 65100, 65010]) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): # same check function as previous No.1 scenario lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathAnyCondition").boot(env) lookup_scenario("ImportPolicyAsPathAnyCondition").setup(env) lookup_scenario("ImportPolicyAsPathAnyCondition").check(env) @register_scenario class ImportPolicyAsPathOriginCondition(object): """ No.12 aspath origin condition import-policy test -------------------------------- e1 ->(aspath=[...,65090])-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy as-path add as0 65090$') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition as-path as0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('192.168.100.0/24', aspath=[65000, 65098, 65090]) # this will pass e1.add_route('192.168.200.0/24', aspath=[65000, 65100, 65010]) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): # same check function as previous No.1 scenario lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathOriginCondition").boot(env) lookup_scenario("ImportPolicyAsPathOriginCondition").setup(env) lookup_scenario("ImportPolicyAsPathOriginCondition").check(env) @register_scenario class ImportPolicyAsPathOnlyCondition(object): """ No.13 aspath only condition import-policy test -------------------------------- e1 -> (aspath=[65100]) -> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy as-path add as0 ^65100$') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition as-path as0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('192.168.100.0/24', aspath=[65100]) # this will pass e1.add_route('192.168.200.0/24', aspath=[65000, 65100, 65010]) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): # same check function as previous No.1 scenario lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathOnlyCondition").boot(env) lookup_scenario("ImportPolicyAsPathOnlyCondition").setup(env) lookup_scenario("ImportPolicyAsPathOnlyCondition").check(env) @register_scenario class ImportPolicyAsPathMismatchCondition(object): """ No.14 aspath condition mismatch import-policy test ------------------------------- exabgp ->(aspath=[...,65090])->| -> q1-rib -> q1-adj-rib-out | --> q1 | | | -> q2-rib -> q2-adj-rib-out | --> q2 ------------------------------- This case check if policy passes the path to e1 because of condition mismatch. """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('192.168.100.0/24', aspath=[65100, 65090]) # this will pass e1.add_route('192.168.200.0/24', aspath=[65000, 65100, 65010]) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 2) wait_for(lambda: len(q1.get_global_rib()) == 2) wait_for(lambda: len(g1.get_local_rib(q2)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 2) wait_for(lambda: len(q2.get_global_rib()) == 2) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathMismatchCondition").boot(env) lookup_scenario("ImportPolicyAsPathMismatchCondition").setup(env) lookup_scenario("ImportPolicyAsPathMismatchCondition").check(env) @register_scenario class ImportPolicyCommunityCondition(object): """ No.15 community condition import-policy test -------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('192.168.100.0/24', community=['65100:10']) # this will pass e1.add_route('192.168.200.0/24', community=['65100:20']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityCondition").boot(env) lookup_scenario("ImportPolicyCommunityCondition").setup(env) lookup_scenario("ImportPolicyCommunityCondition").check(env) @register_scenario class ImportPolicyCommunityRegexp(object): """ No.16 community condition regexp import-policy test -------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 6[0-9]+:[0-9]+') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('192.168.100.0/24', community=['65100:10']) # this will pass e1.add_route('192.168.200.0/24', community=['55100:20']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityRegexp").boot(env) lookup_scenario("ImportPolicyCommunityRegexp").setup(env) lookup_scenario("ImportPolicyCommunityRegexp").check(env) def community_exists(path, com): a, b = com.split(':') com = (int(a) << 16) + int(b) for a in path['attrs']: if a['type'] == BGP_ATTR_TYPE_COMMUNITIES and com in a['communities']: return True return False @register_scenario class ImportPolicyCommunityAction(object): """ No.17 community add action import-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=65100:10,65100:20)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action community add 65100:20') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', community=['65100:10']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 1) wait_for(lambda: len(q1.get_global_rib()) == 1) wait_for(lambda: len(g1.get_local_rib(q2)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) path = g1.get_adj_rib_out(q2)[0] assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityAction").boot(env) lookup_scenario("ImportPolicyCommunityAction").setup(env) lookup_scenario("ImportPolicyCommunityAction").check(env) lookup_scenario("ImportPolicyCommunityAction").check2(env) @register_scenario class ImportPolicyCommunityReplace(object): """ No.18 community replace action import-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=65100:20)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action community replace 65100:20') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', community=['65100:10']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) path = g1.get_adj_rib_out(q2)[0] assert_false(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityReplace").boot(env) lookup_scenario("ImportPolicyCommunityReplace").setup(env) lookup_scenario("ImportPolicyCommunityReplace").check(env) lookup_scenario("ImportPolicyCommunityReplace").check2(env) @register_scenario class ImportPolicyCommunityRemove(object): """ No.19 community remove action import-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=null)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action community remove 65100:10 65100:20') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', community=['65100:10']) e1.add_route('192.168.110.0/24', community=['65100:10', '65100:20']) e1.add_route('192.168.120.0/24', community=['65100:10', '65100:30']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 3) wait_for(lambda: len(q1.get_global_rib()) == 3) wait_for(lambda: len(g1.get_local_rib(q2)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 3) wait_for(lambda: len(q2.get_global_rib()) == 3) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) if path['nlri']['prefix'] == '192.168.110.0/24': assert_true(community_exists(path, '65100:20')) if path['nlri']['prefix'] == '192.168.120.0/24': assert_true(community_exists(path, '65100:30')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_false(community_exists(path, '65100:10')) if path['nlri']['prefix'] == '192.168.110.0/24': assert_false(community_exists(path, '65100:20')) if path['nlri']['prefix'] == '192.168.120.0/24': assert_true(community_exists(path, '65100:30')) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityRemove").boot(env) lookup_scenario("ImportPolicyCommunityRemove").setup(env) lookup_scenario("ImportPolicyCommunityRemove").check(env) lookup_scenario("ImportPolicyCommunityRemove").check2(env) @register_scenario class ImportPolicyCommunityNull(object): """ No.20 community null action import-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=null)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action community replace') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', community=['65100:10']) e1.add_route('192.168.110.0/24', community=['65100:10', '65100:20']) e1.add_route('192.168.120.0/24', community=['65100:10', '65100:30']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityRemove').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) if path['nlri']['prefix'] == '192.168.110.0/24': assert_true(community_exists(path, '65100:20')) if path['nlri']['prefix'] == '192.168.120.0/24': assert_true(community_exists(path, '65100:30')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_false(community_exists(path, '65100:10')) if path['nlri']['prefix'] == '192.168.110.0/24': assert_false(community_exists(path, '65100:20')) if path['nlri']['prefix'] == '192.168.120.0/24': assert_false(community_exists(path, '65100:30')) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityNull").boot(env) lookup_scenario("ImportPolicyCommunityNull").setup(env) lookup_scenario("ImportPolicyCommunityNull").check(env) lookup_scenario("ImportPolicyCommunityNull").check2(env) @register_scenario class ExportPolicyCommunityAdd(object): """ No.21 community add action export-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=null)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action community add 65100:20') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', community=['65100:10']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) local_rib = g1.get_local_rib(q2) for path in local_rib[0]['paths']: assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) @staticmethod def executor(env): lookup_scenario("ExportPolicyCommunityAdd").boot(env) lookup_scenario("ExportPolicyCommunityAdd").setup(env) lookup_scenario("ExportPolicyCommunityAdd").check(env) lookup_scenario("ExportPolicyCommunityAdd").check2(env) @register_scenario class ExportPolicyCommunityReplace(object): """ No.22 community replace action export-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=65100:20)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action community replace 65100:20') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', community=['65100:10']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) local_rib = g1.get_local_rib(q2) for path in local_rib[0]['paths']: assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_false(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) @staticmethod def executor(env): lookup_scenario("ExportPolicyCommunityReplace").boot(env) lookup_scenario("ExportPolicyCommunityReplace").setup(env) lookup_scenario("ExportPolicyCommunityReplace").check(env) lookup_scenario("ExportPolicyCommunityReplace").check2(env) @register_scenario class ExportPolicyCommunityRemove(object): """ No.23 community replace action export-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=null)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action community remove 65100:20 65100:30') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', community=['65100:10', '65100:20', '65100:30']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) assert_true(community_exists(path, '65100:30')) local_rib = g1.get_local_rib(q2) for path in local_rib[0]['paths']: assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) assert_true(community_exists(path, '65100:30')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) assert_false(community_exists(path, '65100:30')) @staticmethod def executor(env): lookup_scenario("ExportPolicyCommunityRemove").boot(env) lookup_scenario("ExportPolicyCommunityRemove").setup(env) lookup_scenario("ExportPolicyCommunityRemove").check(env) lookup_scenario("ExportPolicyCommunityRemove").check2(env) @register_scenario class ExportPolicyCommunityNull(object): """ No.24 community null action export-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=null)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action community replace') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', community=['65100:10', '65100:20', '65100:30']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) assert_true(community_exists(path, '65100:30')) local_rib = g1.get_local_rib(q2) for path in local_rib[0]['paths']: assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) assert_true(community_exists(path, '65100:30')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_false(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) assert_false(community_exists(path, '65100:30')) @staticmethod def executor(env): lookup_scenario("ExportPolicyCommunityNull").boot(env) lookup_scenario("ExportPolicyCommunityNull").setup(env) lookup_scenario("ExportPolicyCommunityNull").check(env) lookup_scenario("ExportPolicyCommunityNull").check2(env) def metric(path): for a in path['attrs']: if 'metric' in a: return a['metric'] return -1 @register_scenario class ImportPolicyMedReplace(object): """ No.25 med replace action import-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action med set 100') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 100) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 100) @staticmethod def executor(env): lookup_scenario("ImportPolicyMedReplace").boot(env) lookup_scenario("ImportPolicyMedReplace").setup(env) lookup_scenario("ImportPolicyMedReplace").check(env) lookup_scenario("ImportPolicyMedReplace").check2(env) @register_scenario class ImportPolicyMedAdd(object): """ No.26 med add action import-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=300+100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action med add 100') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 400) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 400) @staticmethod def executor(env): lookup_scenario("ImportPolicyMedAdd").boot(env) lookup_scenario("ImportPolicyMedAdd").setup(env) lookup_scenario("ImportPolicyMedAdd").check(env) lookup_scenario("ImportPolicyMedAdd").check2(env) @register_scenario class ImportPolicyMedSub(object): """ No.27 med subtract action import-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=300-100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action med sub 100') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 200) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 200) @staticmethod def executor(env): lookup_scenario("ImportPolicyMedSub").boot(env) lookup_scenario("ImportPolicyMedSub").setup(env) lookup_scenario("ImportPolicyMedSub").check(env) lookup_scenario("ImportPolicyMedSub").check2(env) @register_scenario class ExportPolicyMedReplace(object): """ No.28 med replace action export-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action med set 100') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 300) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 100) @staticmethod def executor(env): lookup_scenario("ExportPolicyMedReplace").boot(env) lookup_scenario("ExportPolicyMedReplace").setup(env) lookup_scenario("ExportPolicyMedReplace").check(env) lookup_scenario("ExportPolicyMedReplace").check2(env) @register_scenario class ExportPolicyMedAdd(object): """ No.29 med add action export-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=300+100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action med add 100') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 300) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 400) @staticmethod def executor(env): lookup_scenario("ExportPolicyMedAdd").boot(env) lookup_scenario("ExportPolicyMedAdd").setup(env) lookup_scenario("ExportPolicyMedAdd").check(env) lookup_scenario("ExportPolicyMedAdd").check2(env) @register_scenario class ExportPolicyMedSub(object): """ No.30 med subtract action export-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=300-100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy statement add st0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action med sub 100') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 300) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 200) @staticmethod def executor(env): lookup_scenario("ExportPolicyMedSub").boot(env) lookup_scenario("ExportPolicyMedSub").setup(env) lookup_scenario("ExportPolicyMedSub").check(env) lookup_scenario("ExportPolicyMedSub").check2(env) @register_scenario class InPolicyReject(object): """ No.31 in-policy reject test ---------------- e1 ->r1(community=65100:10) -> x | -> q1-rib -> | -> r2 --> q1 r2(192.168.10.0/24) -> o | | | -> q2-rib -> | -> r2 --> q2 ---------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy in add policy0'.format(g1.peers[e1]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', community=['65100:10']) e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_adj_rib_in(e1)) == 2) wait_for(lambda: len(g1.get_local_rib(q1)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 1) wait_for(lambda: len(q1.get_global_rib()) == 1) wait_for(lambda: len(g1.get_local_rib(q2)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def executor(env): lookup_scenario("InPolicyReject").boot(env) lookup_scenario("InPolicyReject").setup(env) lookup_scenario("InPolicyReject").check(env) @register_scenario class InPolicyAccept(object): """ No.32 in-policy accept test ---------------- e1 ->r1(community=65100:10) -> x | -> q1-rib -> | -> r2 --> q1 r2(192.168.10.0/24) -> o | | | -> q2-rib -> | -> r2 --> q2 ---------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy community add cs0 65100:10') g1.local('gobgp policy statement st0 add condition community cs0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy in add policy0 default reject'.format(g1.peers[e1]['neigh_addr'].split('/')[0])) e1.add_route('192.168.100.0/24', community=['65100:10']) e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('InPolicyReject').check(env) @staticmethod def executor(env): lookup_scenario("InPolicyAccept").boot(env) lookup_scenario("InPolicyAccept").setup(env) lookup_scenario("InPolicyAccept").check(env) @register_scenario class InPolicyUpdate(object): """ No.35 in-policy update test r1:192.168.2.0 r2:192.168.20.0 r3:192.168.200.0 ------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q1 | | | q2 | | ->(r1)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------- | update distribute policy | V ------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2)-> rib ->(r1,r2)-> adj-rib-out | ->(r1,r2)-> q1 | | | q2 | | ->(r1,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.20.0/24') g1.local('gobgp policy prefix add ps0 192.168.200.0/24') g1.local('gobgp policy neighbor add ns0 {0}'.format(g1.peers[e1]['neigh_addr'].split('/')[0])) g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add condition neighbor ns0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy in add policy0'.format(g1.peers[e1]['neigh_addr'].split('/')[0])) e1.add_route('192.168.2.0/24') e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_adj_rib_in(e1)) == 3) wait_for(lambda: len(g1.get_local_rib(q1)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 1) wait_for(lambda: len(q1.get_global_rib()) == 1) wait_for(lambda: len(g1.get_local_rib(q2)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 # q1 = env.q1 # q2 = env.q2 g1.clear_policy() g1.local('gobgp policy prefix del ps0 192.168.200.0/24') g1.softreset(e1) @staticmethod def check2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_adj_rib_in(e1)) == 3) wait_for(lambda: len(g1.get_local_rib(q1)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 2) wait_for(lambda: len(q1.get_global_rib()) == 2) wait_for(lambda: len(g1.get_local_rib(q2)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 2) wait_for(lambda: len(q2.get_global_rib()) == 2) @staticmethod def executor(env): lookup_scenario("InPolicyUpdate").boot(env) lookup_scenario("InPolicyUpdate").setup(env) lookup_scenario("InPolicyUpdate").check(env) lookup_scenario("InPolicyUpdate").setup2(env) lookup_scenario("InPolicyUpdate").check2(env) @register_scenario class ExportPolicyAsPathPrepend(object): """ No.37 aspath prepend action export -------------------------------- e1 ->(aspath=[65001])-> | -> p1-rib -> p1-adj-rib-out | -> p1 | | | -> p2-rib -> p2-adj-rib-out | -> p2 | apply action | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.20.0/24') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action as-prepend 65005 5') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_adj_rib_in(e1)) == 2) wait_for(lambda: len(g1.get_local_rib(q1)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 2) wait_for(lambda: len(q1.get_global_rib()) == 2) wait_for(lambda: len(g1.get_local_rib(q2)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 2) wait_for(lambda: len(q2.get_global_rib()) == 2) @staticmethod def check2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_adj_rib_out(q1, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_local_rib(q2, prefix='192.168.20.0/24')[0]['paths'][0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == ([65005] * 5) + [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) @staticmethod def executor(env): lookup_scenario("ExportPolicyAsPathPrepend").boot(env) lookup_scenario("ExportPolicyAsPathPrepend").setup(env) lookup_scenario("ExportPolicyAsPathPrepend").check(env) lookup_scenario("ExportPolicyAsPathPrepend").check2(env) @register_scenario class ImportPolicyAsPathPrependLastAS(object): """ No.38 aspath prepend action lastas import -------------------------------- e1 ->(aspath=[65001])-> | -> p1-rib -> p1-adj-rib-out | -> p1 | | | -> p2-rib -> p2-adj-rib-out | -> p2 | apply action | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.20.0/24') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action as-prepend last-as 5') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ExportPolicyAsPathPrepend').check(env) @staticmethod def check2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_adj_rib_out(q1, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_local_rib(q2, prefix='192.168.20.0/24')[0]['paths'][0] assert_true(path['aspath'] == ([e1.asn] * 5) + [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == ([e1.asn] * 5) + [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathPrependLastAS").boot(env) lookup_scenario("ImportPolicyAsPathPrependLastAS").setup(env) lookup_scenario("ImportPolicyAsPathPrependLastAS").check(env) lookup_scenario("ImportPolicyAsPathPrependLastAS").check2(env) @register_scenario class ExportPolicyAsPathPrependLastAS(object): """ No.39 aspath prepend action lastas export -------------------------------- e1 ->(aspath=[65001])-> | -> p1-rib -> p1-adj-rib-out | -> p1 | | | -> p2-rib -> p2-adj-rib-out | -> p2 | apply action | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.20.0/24') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action as-prepend last-as 5') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ExportPolicyAsPathPrepend').check(env) @staticmethod def check2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_adj_rib_out(q1, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_local_rib(q2, prefix='192.168.20.0/24')[0]['paths'][0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == ([e1.asn] * 5) + [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) @staticmethod def executor(env): lookup_scenario("ExportPolicyAsPathPrependLastAS").boot(env) lookup_scenario("ExportPolicyAsPathPrependLastAS").setup(env) lookup_scenario("ExportPolicyAsPathPrependLastAS").check(env) lookup_scenario("ExportPolicyAsPathPrependLastAS").check2(env) @register_scenario class ImportPolicyExCommunityOriginCondition(object): """ No.40 extended community origin condition import -------------------------------- e1 ->(extcommunity=origin:65001.65100:200)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy ext-community add es0 soo:65001.65100:200') g1.local('gobgp policy statement st0 add condition ext-community es0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.20.0/24', extendedcommunity='origin:{0}:200'.format((65001 << 16) + 65100)) e1.add_route('192.168.200.0/24', extendedcommunity='origin:{0}:100'.format((65001 << 16) + 65200)) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyExCommunityOriginCondition").boot(env) lookup_scenario("ImportPolicyExCommunityOriginCondition").setup(env) lookup_scenario("ImportPolicyExCommunityOriginCondition").check(env) @register_scenario class ImportPolicyExCommunityTargetCondition(object): """ No.41 extended community origin condition import -------------------------------- e1 ->(extcommunity=target:65010:320)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy ext-community add es0 rt:6[0-9]+:3[0-9]+') g1.local('gobgp policy statement st0 add condition ext-community es0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.20.0/24', extendedcommunity='target:65010:320') e1.add_route('192.168.200.0/24', extendedcommunity='target:55000:320') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyExCommunityTargetCondition").boot(env) lookup_scenario("ImportPolicyExCommunityTargetCondition").setup(env) lookup_scenario("ImportPolicyExCommunityTargetCondition").check(env) @register_scenario class InPolicyPrefixCondition(object): """ No.42 prefix only condition accept in ----------------- e1 ->r1(192.168.100.0/24) -> o | -> q1-rib -> | -> r2 --> q1 r2(192.168.10.0/24) -> x | | | -> q2-rib -> | -> r2 --> q2 ----------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.10.0/24') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add action reject') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy in add policy0'.format(g1.peers[e1]['neigh_addr'].split('/')[0])) # this will be blocked e1.add_route('192.168.100.0/24') # this will pass e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('InPolicyReject').check(env) @staticmethod def executor(env): lookup_scenario("InPolicyPrefixCondition").boot(env) lookup_scenario("InPolicyPrefixCondition").setup(env) lookup_scenario("InPolicyPrefixCondition").check(env) def ext_community_exists(path, extcomm): typ = extcomm.split(':')[0] value = ':'.join(extcomm.split(':')[1:]) for a in path['attrs']: if a['type'] == BGP_ATTR_TYPE_EXTENDED_COMMUNITIES: for c in a['value']: if typ == 'RT' and c['type'] == 0 and c['subtype'] == 2 and c['value'] == value: return True return False @register_scenario class ImportPolicyExCommunityAdd(object): """ No.43 extended community add action import-policy test --------------------------------- e1 ->(extcommunity=none) ->| -> q1-rib -> q1-adj-rib-out | --> q1 | | | -> q2-rib -> q2-adj-rib-out | --> q2 | add ext-community | --------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.10.0/24') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action ext-community add rt:65000:1') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_false(ext_community_exists(path, 'RT:65000:1')) path = g1.get_adj_rib_out(q2)[0] assert_true(ext_community_exists(path, 'RT:65000:1')) @staticmethod def executor(env): lookup_scenario("ImportPolicyExCommunityAdd").boot(env) lookup_scenario("ImportPolicyExCommunityAdd").setup(env) lookup_scenario("ImportPolicyExCommunityAdd").check(env) lookup_scenario("ImportPolicyExCommunityAdd").check2(env) @register_scenario class ImportPolicyExCommunityAdd2(object): """ No.44 extended community add action import-policy test -------------------------------- e1 ->(extcommunity=RT:65000:1) -> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | -> q2-rib -> q2-adj-rib-out | --> q2 | add ext-community | -------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.10.0/24') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action ext-community add rt:65100:100') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.10.0/24', extendedcommunity='target:65000:1') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_true(ext_community_exists(path, 'RT:65000:1')) assert_false(ext_community_exists(path, 'RT:65100:100')) path = g1.get_local_rib(q2)[0]['paths'][0] assert_true(ext_community_exists(path, 'RT:65000:1')) assert_true(ext_community_exists(path, 'RT:65100:100')) path = g1.get_adj_rib_out(q2)[0] assert_true(ext_community_exists(path, 'RT:65000:1')) assert_true(ext_community_exists(path, 'RT:65100:100')) @staticmethod def executor(env): lookup_scenario("ImportPolicyExCommunityAdd2").boot(env) lookup_scenario("ImportPolicyExCommunityAdd2").setup(env) lookup_scenario("ImportPolicyExCommunityAdd2").check(env) lookup_scenario("ImportPolicyExCommunityAdd2").check2(env) @register_scenario class ImportPolicyExCommunityMultipleAdd(object): """ No.45 extended community add action multiple import-policy test --------------------------------------- exabgp ->(extcommunity=none) ->| -> peer1-rib -> peer1-adj-rib-out | --> peer1 | | | -> peer2-rib -> peer2-adj-rib-out | --> peer2 | add ext-community | --------------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.10.0/24') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action ext-community add rt:65100:100 rt:100:100') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy import add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_false(ext_community_exists(path, 'RT:65100:100')) assert_false(ext_community_exists(path, 'RT:100:100')) path = g1.get_local_rib(q2)[0]['paths'][0] assert_true(ext_community_exists(path, 'RT:65100:100')) assert_true(ext_community_exists(path, 'RT:100:100')) path = g1.get_adj_rib_out(q2)[0] assert_true(ext_community_exists(path, 'RT:65100:100')) assert_true(ext_community_exists(path, 'RT:100:100')) @staticmethod def executor(env): lookup_scenario("ImportPolicyExCommunityMultipleAdd").boot(env) lookup_scenario("ImportPolicyExCommunityMultipleAdd").setup(env) lookup_scenario("ImportPolicyExCommunityMultipleAdd").check(env) lookup_scenario("ImportPolicyExCommunityMultipleAdd").check2(env) @register_scenario class ExportPolicyExCommunityAdd(object): """ No.46 extended comunity add action export-policy test ------------------------------------ e1 ->(extcommunity=none) ->| -> q1-rib -> q1-adj-rib-out | --> q1 | | | -> q2-rib -> q2-adj-rib-out | --> q2 | add ext-community | ------------------------------------ """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.local('gobgp policy prefix add ps0 192.168.10.0/24') g1.local('gobgp policy statement st0 add condition prefix ps0') g1.local('gobgp policy statement st0 add action accept') g1.local('gobgp policy statement st0 add action ext-community add rt:65000:1') g1.local('gobgp policy add policy0 st0') g1.local('gobgp neighbor {0} policy export add policy0'.format(g1.peers[q2]['neigh_addr'].split('/')[0])) e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_false(ext_community_exists(path, 'RT:65000:1')) path = g1.get_local_rib(q2)[0]['paths'][0] assert_false(ext_community_exists(path, 'RT:65000:1')) path = g1.get_adj_rib_out(q2)[0] assert_true(ext_community_exists(path, 'RT:65000:1')) @staticmethod def executor(env): lookup_scenario("ExportPolicyExCommunityAdd").boot(env) lookup_scenario("ExportPolicyExCommunityAdd").setup(env) lookup_scenario("ExportPolicyExCommunityAdd").check(env) lookup_scenario("ExportPolicyExCommunityAdd").check2(env) class TestGoBGPBase(unittest.TestCase): wait_per_retry = 5 retry_limit = 10 @classmethod def setUpClass(cls): idx = parser_option.test_index base.TEST_PREFIX = parser_option.test_prefix cls.parser_option = parser_option cls.executors = [] if idx == 0: print 'unset test-index. run all test sequential' for _, v in _SCENARIOS.items(): for k, m in inspect.getmembers(v, inspect.isfunction): if k == 'executor': cls.executor = m cls.executors.append(cls.executor) elif idx not in _SCENARIOS: print 'invalid test-index. # of scenarios: {0}'.format(len(_SCENARIOS)) sys.exit(1) else: for k, m in inspect.getmembers(_SCENARIOS[idx], inspect.isfunction): if k == 'executor': cls.executor = m cls.executors.append(cls.executor) def test(self): for e in self.executors: yield e if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/route_server_policy_test.py000066400000000000000000003655451324612745600241340ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest import inspect from fabric.api import local import nose from nose.tools import ( assert_true, assert_false, ) from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( Bridge, BGP_FSM_ESTABLISHED, BGP_ATTR_TYPE_COMMUNITIES, BGP_ATTR_TYPE_EXTENDED_COMMUNITIES, ) from lib.gobgp import GoBGPContainer from lib.quagga import QuaggaBGPContainer from lib.exabgp import ExaBGPContainer counter = 1 _SCENARIOS = {} def register_scenario(cls): global counter _SCENARIOS[counter] = cls counter += 1 def lookup_scenario(name): for value in _SCENARIOS.values(): if value.__name__ == name: return value return None def wait_for(f, timeout=120): interval = 1 count = 0 while True: if f(): return time.sleep(interval) count += interval if count >= timeout: raise Exception('timeout') @register_scenario class ImportPolicy(object): """ No.1 import-policy test -------------------------------- e1 ->(192.168.2.0/24)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): gobgp_ctn_image_name = env.parser_option.gobgp_image log_level = env.parser_option.gobgp_log_level g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=log_level) e1 = ExaBGPContainer(name='e1', asn=65001, router_id='192.168.0.2') q1 = QuaggaBGPContainer(name='q1', asn=65002, router_id='192.168.0.3') q2 = QuaggaBGPContainer(name='q2', asn=65003, router_id='192.168.0.4') ctns = [g1, e1, q1, q2] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) for q in [e1, q1, q2]: g1.add_peer(q, is_rs_client=True) q.add_peer(g1) env.g1 = g1 env.e1 = e1 env.q1 = q1 env.q2 = q2 @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.0.0/16', 'masklength-range': '16..24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[e1]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') # this will be blocked e1.add_route('192.168.2.0/24') # this will pass e1.add_route('192.168.2.0/15') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1)) == 2) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1)) == 2) wait_for(lambda: len(env.q1.get_global_rib()) == 2) wait_for(lambda: len(env.g1.get_local_rib(env.q2)) == 1) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2)) == 1) wait_for(lambda: len(env.q2.get_global_rib()) == 1) @staticmethod def executor(env): lookup_scenario("ImportPolicy").boot(env) lookup_scenario("ImportPolicy").setup(env) lookup_scenario("ImportPolicy").check(env) @register_scenario class ExportPolicy(object): """ No.2 export-policy test -------------------------------- e1 ->(192.168.2.0/24)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | -> q2-rib ->x q2-adj-rib-out | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.0.0/16', 'masklength-range': '16..24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[q2]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') # this will be blocked e1.add_route('192.168.2.0/24') # this will pass e1.add_route('192.168.2.0/15') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 2) wait_for(lambda: len(q1.get_global_rib()) == 2) wait_for(lambda: len(g1.get_local_rib(q2)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def executor(env): lookup_scenario("ExportPolicy").boot(env) lookup_scenario("ExportPolicy").setup(env) lookup_scenario("ExportPolicy").check(env) @register_scenario class ImportPolicyUpdate(object): """ No.3 import-policy update test r1:192.168.2.0/24 r2:192.168.20.0/24 r3:192.168.200.0/24 ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------------------- | update gobgp.conf | V ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.20.0/24'} p1 = {'ip-prefix': '192.168.200.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0, p1]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[e1]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.2.0/24') e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 3) wait_for(lambda: len(q1.get_global_rib()) == 3) wait_for(lambda: len(g1.get_local_rib(q2)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 # q1 = env.q1 q2 = env.q2 g1.clear_policy() p0 = {'ip-prefix': '192.168.20.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[e1]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') g1.softreset(e1) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 3) wait_for(lambda: len(q1.get_global_rib()) == 3) wait_for(lambda: len(g1.get_local_rib(q2)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 2) wait_for(lambda: len(q2.get_global_rib()) == 2) @staticmethod def executor(env): lookup_scenario("ImportPolicyUpdate").boot(env) lookup_scenario("ImportPolicyUpdate").setup(env) lookup_scenario("ImportPolicyUpdate").check(env) lookup_scenario("ImportPolicyUpdate").setup2(env) lookup_scenario("ImportPolicyUpdate").check2(env) @register_scenario class ExportPolicyUpdate(object): """ No.4 export-policy update test r1:192.168.2.0 r2:192.168.20.0 r3:192.168.200.0 ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r2,r3)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------------------- | update gobgp.conf | V ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r2,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.20.0/24'} p1 = {'ip-prefix': '192.168.200.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0, p1]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[q2]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('192.168.2.0/24') e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 3) wait_for(lambda: len(q1.get_global_rib()) == 3) wait_for(lambda: len(g1.get_local_rib(q2)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 g1.clear_policy() p0 = {'ip-prefix': '192.168.20.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[q2]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') # we need hard reset to flush q2's local rib g1.reset(e1) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 3) wait_for(lambda: len(q1.get_global_rib()) == 3) wait_for(lambda: len(g1.get_local_rib(q2)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 2) wait_for(lambda: len(q2.get_global_rib()) == 2) @staticmethod def executor(env): lookup_scenario("ExportPolicyUpdate").boot(env) lookup_scenario("ExportPolicyUpdate").setup(env) lookup_scenario("ExportPolicyUpdate").check(env) lookup_scenario("ExportPolicyUpdate").setup2(env) lookup_scenario("ExportPolicyUpdate").check2(env) @register_scenario class ImportPolicyIPV6(object): """ No.5 IPv6 import-policy test r1=2001::/64 r2=2001::/63 ------------------------------------------------- e1 ->(r1,r2)-> | ->(r1,r2)-> q1-rib ->(r1,r2)-> q1-adj-rib-out | ->(r1,r2)-> q1 | | | ->(r2) -> q2-rib ->(r2) -> q2-adj-rib-out | ->(r2)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): gobgp_ctn_image_name = env.parser_option.gobgp_image log_level = env.parser_option.gobgp_log_level g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=log_level) e1 = ExaBGPContainer(name='e1', asn=65001, router_id='192.168.0.2') q1 = QuaggaBGPContainer(name='q1', asn=65002, router_id='192.168.0.3') q2 = QuaggaBGPContainer(name='q2', asn=65003, router_id='192.168.0.4') ctns = [g1, e1, q1, q2] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) br01 = Bridge(name='br01', subnet='2001::/96') [br01.addif(ctn) for ctn in ctns] for q in [e1, q1, q2]: g1.add_peer(q, is_rs_client=True, bridge=br01.name) q.add_peer(g1, bridge=br01.name) env.g1 = g1 env.e1 = e1 env.q1 = q1 env.q2 = q2 @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '2001::/32', 'masklength-range': '64..128'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[e1]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') # this will be blocked e1.add_route('2001::/64', rf='ipv6') # this will pass e1.add_route('2001::/63', rf='ipv6') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 2) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 1) @staticmethod def executor(env): lookup_scenario("ImportPolicyIPV6").boot(env) lookup_scenario("ImportPolicyIPV6").setup(env) lookup_scenario("ImportPolicyIPV6").check(env) @register_scenario class ExportPolicyIPV6(object): """ No.6 IPv6 export-policy test r1=2001::/64 r2=2001::/63 ------------------------------------------------- e1 ->(r1,r2)-> | ->(r1,r2)-> q1-rib ->(r1,r2)-> q1-adj-rib-out | ->(r1,r2)-> q1 | | | ->(r1,r2)-> q2-rib ->(r2) -> q2-adj-rib-out | ->(r2)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicyIPV6').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '2001::/32', 'masklength-range': '64..128'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[q2]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') # this will be blocked e1.add_route('2001::/64', rf='ipv6') # this will pass e1.add_route('2001::/63', rf='ipv6') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 2) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 1) @staticmethod def executor(env): lookup_scenario("ExportPolicyIPV6").boot(env) lookup_scenario("ExportPolicyIPV6").setup(env) lookup_scenario("ExportPolicyIPV6").check(env) @register_scenario class ImportPolicyIPV6Update(object): """ No.7 IPv6 import-policy update test r1=2001:0:10:2::/64 r2=2001:0:10:20::/64 r3=2001:0:10:200::/64 ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------------------- | update gobgp.conf | V ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicyIPV6').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '2001:0:10:2::/64'} p1 = {'ip-prefix': '2001:0:10:20::/64'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0, p1]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[e1]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('2001:0:10:2::/64', rf='ipv6') e1.add_route('2001:0:10:20::/64', rf='ipv6') e1.add_route('2001:0:10:200::/64', rf='ipv6') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 1) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 # q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '2001:0:10:2::/64'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[e1]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': { 'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') g1.softreset(e1, rf='ipv6') @staticmethod def check2(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 2) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 2) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 2) @staticmethod def executor(env): lookup_scenario("ImportPolicyIPV6Update").boot(env) lookup_scenario("ImportPolicyIPV6Update").setup(env) lookup_scenario("ImportPolicyIPV6Update").check(env) lookup_scenario("ImportPolicyIPV6Update").setup2(env) lookup_scenario("ImportPolicyIPV6Update").check2(env) @register_scenario class ExportPolicyIPv6Update(object): """ No.8 IPv6 export-policy update test r1=2001:0:10:2::/64 r2=2001:0:10:20::/64 r3=2001:0:10:200::/64 ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r2,r3)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------------------- | update gobgp.conf | V ------------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2,r3)-> rib ->(r1,r2,r3)-> adj-rib-out | ->(r1,r2,r3)-> q1 | | | q2 | | ->(r1,r2,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicyIPV6').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '2001:0:10:2::/64'} p1 = {'ip-prefix': '2001:0:10:20::/64'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0, p1]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[q2]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('2001:0:10:2::/64', rf='ipv6') e1.add_route('2001:0:10:20::/64', rf='ipv6') e1.add_route('2001:0:10:200::/64', rf='ipv6') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 1) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 1) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '2001:0:10:2::/64'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[q2]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') g1.reset(e1) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check2(env): wait_for(lambda: len(env.g1.get_local_rib(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q1, rf='ipv6')) == 3) wait_for(lambda: len(env.q1.get_global_rib(rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_local_rib(env.q2, rf='ipv6')) == 3) wait_for(lambda: len(env.g1.get_adj_rib_out(env.q2, rf='ipv6')) == 2) wait_for(lambda: len(env.q2.get_global_rib(rf='ipv6')) == 2) @staticmethod def executor(env): lookup_scenario("ExportPolicyIPv6Update").boot(env) lookup_scenario("ExportPolicyIPv6Update").setup(env) lookup_scenario("ExportPolicyIPv6Update").check(env) lookup_scenario("ExportPolicyIPv6Update").setup2(env) lookup_scenario("ExportPolicyIPv6Update").check2(env) @register_scenario class ImportPolicyAsPathLengthCondition(object): """ No.9 aspath length condition import-policy test -------------------------------- e1 ->(aspath_length=10)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'as-path-length': {'operator': 'ge', 'value': 10}}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') # this will be blocked e1.add_route('192.168.100.0/24', aspath=range(e1.asn, e1.asn - 10, -1)) # this will pass e1.add_route('192.168.200.0/24', aspath=range(e1.asn, e1.asn - 8, -1)) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 2) wait_for(lambda: len(q1.get_global_rib()) == 2) wait_for(lambda: len(g1.get_local_rib(q2)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathLengthCondition").boot(env) lookup_scenario("ImportPolicyAsPathLengthCondition").setup(env) lookup_scenario("ImportPolicyAsPathLengthCondition").check(env) @register_scenario class ImportPolicyAsPathCondition(object): """ No.10 aspath from condition import-policy test -------------------------------- e1 ->(aspath=[65100,...])-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 as0 = {'as-path-sets': [{'as-path-set-name': 'as0', 'as-path-list': ['^{0}'.format(e1.asn)]}]} g1.set_bgp_defined_set(as0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-as-path-set': {'as-path-set': 'as0'}}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') # this will be blocked e1.add_route('192.168.100.0/24', aspath=range(e1.asn, e1.asn - 10, -1)) # this will pass e1.add_route('192.168.200.0/24', aspath=range(e1.asn - 1, e1.asn - 10, -1)) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): # same check function as previous No.1 scenario lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathCondition").boot(env) lookup_scenario("ImportPolicyAsPathCondition").setup(env) lookup_scenario("ImportPolicyAsPathCondition").check(env) @register_scenario class ImportPolicyAsPathAnyCondition(object): """ No.11 aspath any condition import-policy test -------------------------------- e1 ->(aspath=[...65098,...])-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 as0 = {'as-path-sets': [{'as-path-set-name': 'as0', 'as-path-list': ['65098']}]} g1.set_bgp_defined_set(as0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-as-path-set': {'as-path-set': 'as0'}}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') # this will be blocked e1.add_route('192.168.100.0/24', aspath=[65000, 65098, 65010]) # this will pass e1.add_route('192.168.200.0/24', aspath=[65000, 65100, 65010]) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): # same check function as previous No.1 scenario lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathAnyCondition").boot(env) lookup_scenario("ImportPolicyAsPathAnyCondition").setup(env) lookup_scenario("ImportPolicyAsPathAnyCondition").check(env) @register_scenario class ImportPolicyAsPathOriginCondition(object): """ No.12 aspath origin condition import-policy test -------------------------------- e1 ->(aspath=[...,65090])-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 as0 = {'as-path-sets': [{'as-path-set-name': 'as0', 'as-path-list': ['65090$']}]} g1.set_bgp_defined_set(as0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-as-path-set': {'as-path-set': 'as0'}}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') # this will be blocked e1.add_route('192.168.100.0/24', aspath=[65000, 65098, 65090]) # this will pass e1.add_route('192.168.200.0/24', aspath=[65000, 65100, 65010]) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): # same check function as previous No.1 scenario lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathOriginCondition").boot(env) lookup_scenario("ImportPolicyAsPathOriginCondition").setup(env) lookup_scenario("ImportPolicyAsPathOriginCondition").check(env) @register_scenario class ImportPolicyAsPathOnlyCondition(object): """ No.13 aspath only condition import-policy test -------------------------------- e1 -> (aspath=[65100]) -> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 as0 = {'as-path-sets': [{'as-path-set-name': 'as0', 'as-path-list': ['^65100$']}]} g1.set_bgp_defined_set(as0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-as-path-set': {'as-path-set': 'as0'}}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') # this will be blocked e1.add_route('192.168.100.0/24', aspath=[65100]) # this will pass e1.add_route('192.168.200.0/24', aspath=[65000, 65100, 65010]) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): # same check function as previous No.1 scenario lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathOnlyCondition").boot(env) lookup_scenario("ImportPolicyAsPathOnlyCondition").setup(env) lookup_scenario("ImportPolicyAsPathOnlyCondition").check(env) @register_scenario class ImportPolicyAsPathMismatchCondition(object): """ No.14 aspath condition mismatch import-policy test ------------------------------- exabgp ->(aspath=[...,65090])->| -> q1-rib -> q1-adj-rib-out | --> q1 | | | -> q2-rib -> q2-adj-rib-out | --> q2 ------------------------------- This case check if policy passes the path to e1 because of condition mismatch. """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') # this will be blocked e1.add_route('192.168.100.0/24', aspath=[65100, 65090]) # this will pass e1.add_route('192.168.200.0/24', aspath=[65000, 65100, 65010]) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 2) wait_for(lambda: len(q1.get_global_rib()) == 2) wait_for(lambda: len(g1.get_local_rib(q2)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 2) wait_for(lambda: len(q2.get_global_rib()) == 2) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathMismatchCondition").boot(env) lookup_scenario("ImportPolicyAsPathMismatchCondition").setup(env) lookup_scenario("ImportPolicyAsPathMismatchCondition").check(env) @register_scenario class ImportPolicyCommunityCondition(object): """ No.15 community condition import-policy test -------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') # this will be blocked e1.add_route('192.168.100.0/24', community=['65100:10']) # this will pass e1.add_route('192.168.200.0/24', community=['65100:20']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityCondition").boot(env) lookup_scenario("ImportPolicyCommunityCondition").setup(env) lookup_scenario("ImportPolicyCommunityCondition").check(env) @register_scenario class ImportPolicyCommunityRegexp(object): """ No.16 community condition regexp import-policy test -------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['6[0-9]+:[0-9]+']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') # this will be blocked e1.add_route('192.168.100.0/24', community=['65100:10']) # this will pass e1.add_route('192.168.200.0/24', community=['55100:20']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityRegexp").boot(env) lookup_scenario("ImportPolicyCommunityRegexp").setup(env) lookup_scenario("ImportPolicyCommunityRegexp").check(env) def community_exists(path, com): a, b = com.split(':') com = (int(a) << 16) + int(b) for a in path['attrs']: if a['type'] == BGP_ATTR_TYPE_COMMUNITIES and com in a['communities']: return True return False @register_scenario class ImportPolicyCommunityAction(object): """ No.17 community add action import-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=65100:10,65100:20)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0', 'match-set-options': 'any'}}}, 'actions': {'route-disposition': 'accept-route', 'bgp-actions': { 'set-community': { 'options': 'add', 'set-community-method': {'communities-list': ['65100:20']}}}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.100.0/24', community=['65100:10']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 1) wait_for(lambda: len(q1.get_global_rib()) == 1) wait_for(lambda: len(g1.get_local_rib(q2)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) path = g1.get_adj_rib_out(q2)[0] assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityAction").boot(env) lookup_scenario("ImportPolicyCommunityAction").setup(env) lookup_scenario("ImportPolicyCommunityAction").check(env) lookup_scenario("ImportPolicyCommunityAction").check2(env) @register_scenario class ImportPolicyCommunityReplace(object): """ No.18 community replace action import-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=65100:20)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'accept-route', 'bgp-actions': { 'set-community': { 'options': 'REPLACE', 'set-community-method': {'communities-list': ['65100:20']}}}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.100.0/24', community=['65100:10']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) path = g1.get_adj_rib_out(q2)[0] assert_false(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityReplace").boot(env) lookup_scenario("ImportPolicyCommunityReplace").setup(env) lookup_scenario("ImportPolicyCommunityReplace").check(env) lookup_scenario("ImportPolicyCommunityReplace").check2(env) @register_scenario class ImportPolicyCommunityRemove(object): """ No.19 community remove action import-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=null)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'accept-route', 'bgp-actions': { 'set-community': { 'options': 'REMOVE', 'set-community-method': {'communities-list': ['65100:10', '65100:20']}}}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.100.0/24', community=['65100:10']) e1.add_route('192.168.110.0/24', community=['65100:10', '65100:20']) e1.add_route('192.168.120.0/24', community=['65100:10', '65100:30']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_local_rib(q1)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 3) wait_for(lambda: len(q1.get_global_rib()) == 3) wait_for(lambda: len(g1.get_local_rib(q2)) == 3) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 3) wait_for(lambda: len(q2.get_global_rib()) == 3) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) if path['nlri']['prefix'] == '192.168.110.0/24': assert_true(community_exists(path, '65100:20')) if path['nlri']['prefix'] == '192.168.120.0/24': assert_true(community_exists(path, '65100:30')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_false(community_exists(path, '65100:10')) if path['nlri']['prefix'] == '192.168.110.0/24': assert_false(community_exists(path, '65100:20')) if path['nlri']['prefix'] == '192.168.120.0/24': assert_true(community_exists(path, '65100:30')) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityRemove").boot(env) lookup_scenario("ImportPolicyCommunityRemove").setup(env) lookup_scenario("ImportPolicyCommunityRemove").check(env) lookup_scenario("ImportPolicyCommunityRemove").check2(env) @register_scenario class ImportPolicyCommunityNull(object): """ No.20 community null action import-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=null)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'accept-route', 'bgp-actions': { 'set-community': { 'options': 'REPLACE', 'set-community-method': {'communities-list': []}}}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.100.0/24', community=['65100:10']) e1.add_route('192.168.110.0/24', community=['65100:10', '65100:20']) e1.add_route('192.168.120.0/24', community=['65100:10', '65100:30']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityRemove').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) if path['nlri']['prefix'] == '192.168.110.0/24': assert_true(community_exists(path, '65100:20')) if path['nlri']['prefix'] == '192.168.120.0/24': assert_true(community_exists(path, '65100:30')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_false(community_exists(path, '65100:10')) if path['nlri']['prefix'] == '192.168.110.0/24': assert_false(community_exists(path, '65100:20')) if path['nlri']['prefix'] == '192.168.120.0/24': assert_false(community_exists(path, '65100:30')) @staticmethod def executor(env): lookup_scenario("ImportPolicyCommunityNull").boot(env) lookup_scenario("ImportPolicyCommunityNull").setup(env) lookup_scenario("ImportPolicyCommunityNull").check(env) lookup_scenario("ImportPolicyCommunityNull").check2(env) @register_scenario class ExportPolicyCommunityAdd(object): """ No.21 community add action export-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=null)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'accept-route', 'bgp-actions': { 'set-community': { 'options': 'add', 'set-community-method': {'communities-list': ['65100:20']}}}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('192.168.100.0/24', community=['65100:10']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) local_rib = g1.get_local_rib(q2) for path in local_rib[0]['paths']: assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) @staticmethod def executor(env): lookup_scenario("ExportPolicyCommunityAdd").boot(env) lookup_scenario("ExportPolicyCommunityAdd").setup(env) lookup_scenario("ExportPolicyCommunityAdd").check(env) lookup_scenario("ExportPolicyCommunityAdd").check2(env) @register_scenario class ExportPolicyCommunityReplace(object): """ No.22 community replace action export-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=65100:20)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'accept-route', 'bgp-actions': { 'set-community': { 'options': 'REPLACE', 'set-community-method': {'communities-list': ['65100:20']}}}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('192.168.100.0/24', community=['65100:10']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) local_rib = g1.get_local_rib(q2) for path in local_rib[0]['paths']: assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_false(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) @staticmethod def executor(env): lookup_scenario("ExportPolicyCommunityReplace").boot(env) lookup_scenario("ExportPolicyCommunityReplace").setup(env) lookup_scenario("ExportPolicyCommunityReplace").check(env) lookup_scenario("ExportPolicyCommunityReplace").check2(env) @register_scenario class ExportPolicyCommunityRemove(object): """ No.23 community replace action export-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=null)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'accept-route', 'bgp-actions': { 'set-community': { 'options': 'REMOVE', 'set-community-method': {'communities-list': ['65100:20', '65100:30']}}}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('192.168.100.0/24', community=['65100:10', '65100:20', '65100:30']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) assert_true(community_exists(path, '65100:30')) local_rib = g1.get_local_rib(q2) for path in local_rib[0]['paths']: assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) assert_true(community_exists(path, '65100:30')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) assert_false(community_exists(path, '65100:30')) @staticmethod def executor(env): lookup_scenario("ExportPolicyCommunityRemove").boot(env) lookup_scenario("ExportPolicyCommunityRemove").setup(env) lookup_scenario("ExportPolicyCommunityRemove").check(env) lookup_scenario("ExportPolicyCommunityRemove").check2(env) @register_scenario class ExportPolicyCommunityNull(object): """ No.24 community null action export-policy test ------------------------------- e1 ->(community=65100:10)-> | -> q1-rib -> q1-adj-rib-out | ->(community=65100:10)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(community=null)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'accept-route', 'bgp-actions': { 'set-community': { 'options': 'REPLACE', 'set-community-method': {'communities-list': []}}}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('192.168.100.0/24', community=['65100:10', '65100:20', '65100:30']) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) for path in adj_out: assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) assert_true(community_exists(path, '65100:30')) local_rib = g1.get_local_rib(q2) for path in local_rib[0]['paths']: assert_true(community_exists(path, '65100:10')) assert_true(community_exists(path, '65100:20')) assert_true(community_exists(path, '65100:30')) adj_out = g1.get_adj_rib_out(q2) for path in adj_out: assert_false(community_exists(path, '65100:10')) assert_false(community_exists(path, '65100:20')) assert_false(community_exists(path, '65100:30')) @staticmethod def executor(env): lookup_scenario("ExportPolicyCommunityNull").boot(env) lookup_scenario("ExportPolicyCommunityNull").setup(env) lookup_scenario("ExportPolicyCommunityNull").check(env) lookup_scenario("ExportPolicyCommunityNull").check2(env) def metric(path): for a in path['attrs']: if 'metric' in a: return a['metric'] return -1 @register_scenario class ImportPolicyMedReplace(object): """ No.25 med replace action import-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 st0 = {'name': 'st0', 'actions': {'route-disposition': 'accept-route', 'bgp-actions': {'set-med': '100'}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 100) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 100) @staticmethod def executor(env): lookup_scenario("ImportPolicyMedReplace").boot(env) lookup_scenario("ImportPolicyMedReplace").setup(env) lookup_scenario("ImportPolicyMedReplace").check(env) lookup_scenario("ImportPolicyMedReplace").check2(env) @register_scenario class ImportPolicyMedAdd(object): """ No.26 med add action import-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=300+100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 st0 = {'name': 'st0', 'actions': {'route-disposition': 'accept-route', 'bgp-actions': {'set-med': '+100'}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 400) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 400) @staticmethod def executor(env): lookup_scenario("ImportPolicyMedAdd").boot(env) lookup_scenario("ImportPolicyMedAdd").setup(env) lookup_scenario("ImportPolicyMedAdd").check(env) lookup_scenario("ImportPolicyMedAdd").check2(env) @register_scenario class ImportPolicyMedSub(object): """ No.27 med subtract action import-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=300-100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 st0 = {'name': 'st0', 'actions': {'route-disposition': 'accept-route', 'bgp-actions': {'set-med': '-100'}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 200) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 200) @staticmethod def executor(env): lookup_scenario("ImportPolicyMedSub").boot(env) lookup_scenario("ImportPolicyMedSub").setup(env) lookup_scenario("ImportPolicyMedSub").check(env) lookup_scenario("ImportPolicyMedSub").check2(env) @register_scenario class ExportPolicyMedReplace(object): """ No.28 med replace action export-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 st0 = {'name': 'st0', 'actions': {'route-disposition': 'accept-route', 'bgp-actions': {'set-med': '100'}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 300) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 100) @staticmethod def executor(env): lookup_scenario("ExportPolicyMedReplace").boot(env) lookup_scenario("ExportPolicyMedReplace").setup(env) lookup_scenario("ExportPolicyMedReplace").check(env) lookup_scenario("ExportPolicyMedReplace").check2(env) @register_scenario class ExportPolicyMedAdd(object): """ No.29 med add action export-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=300+100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 st0 = {'name': 'st0', 'actions': {'route-disposition': 'accept-route', 'bgp-actions': {'set-med': '+100'}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 300) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 400) @staticmethod def executor(env): lookup_scenario("ExportPolicyMedAdd").boot(env) lookup_scenario("ExportPolicyMedAdd").setup(env) lookup_scenario("ExportPolicyMedAdd").check(env) lookup_scenario("ExportPolicyMedAdd").check2(env) @register_scenario class ExportPolicyMedSub(object): """ No.30 med subtract action export-policy test ------------------------------- e1 ->(med=300)-> | -> q1-rib -> q1-adj-rib-out | ->(med=300)-> q1 | | | -> q2-rib -> q2-adj-rib-out | ->(med=300-100)-> q2 | apply action | ------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 st0 = {'name': 'st0', 'actions': {'route-disposition': 'accept-route', 'bgp-actions': {'set-med': '-100'}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('192.168.100.0/24', med=300) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 q1 = env.q1 q2 = env.q2 adj_out = g1.get_adj_rib_out(q1) assert_true(metric(adj_out[0]) == 300) local_rib = g1.get_local_rib(q2) assert_true(metric(local_rib[0]['paths'][0]) == 300) adj_out = g1.get_adj_rib_out(q2) assert_true(metric(adj_out[0]) == 200) @staticmethod def executor(env): lookup_scenario("ExportPolicyMedSub").boot(env) lookup_scenario("ExportPolicyMedSub").setup(env) lookup_scenario("ExportPolicyMedSub").check(env) lookup_scenario("ExportPolicyMedSub").check2(env) @register_scenario class InPolicyReject(object): """ No.31 in-policy reject test ---------------- e1 ->r1(community=65100:10) -> x | -> q1-rib -> | -> r2 --> q1 r2(192.168.10.0/24) -> o | | | -> q2-rib -> | -> r2 --> q2 ---------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, e1, 'in') e1.add_route('192.168.100.0/24', community=['65100:10']) e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_adj_rib_in(e1)) == 2) wait_for(lambda: len(g1.get_local_rib(q1)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 1) wait_for(lambda: len(q1.get_global_rib()) == 1) wait_for(lambda: len(g1.get_local_rib(q2)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def executor(env): lookup_scenario("InPolicyReject").boot(env) lookup_scenario("InPolicyReject").setup(env) lookup_scenario("InPolicyReject").check(env) @register_scenario class InPolicyAccept(object): """ No.32 in-policy accept test ---------------- e1 ->r1(community=65100:10) -> x | -> q1-rib -> | -> r2 --> q1 r2(192.168.10.0/24) -> o | | | -> q2-rib -> | -> r2 --> q2 ---------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 cs0 = {'community-sets': [{'community-set-name': 'cs0', 'community-list': ['65100:10']}]} g1.set_bgp_defined_set(cs0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-community-set': {'community-set': 'cs0'}}}, 'actions': {'route-disposition': 'accept-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, e1, 'in', 'reject') e1.add_route('192.168.100.0/24', community=['65100:10']) e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('InPolicyReject').check(env) @staticmethod def executor(env): lookup_scenario("InPolicyAccept").boot(env) lookup_scenario("InPolicyAccept").setup(env) lookup_scenario("InPolicyAccept").check(env) @register_scenario class InPolicyUpdate(object): """ No.35 in-policy update test r1:192.168.2.0 r2:192.168.20.0 r3:192.168.200.0 ------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q1 | | | q2 | | ->(r1)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------- | update distribute policy | V ------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2)-> rib ->(r1,r2)-> adj-rib-out | ->(r1,r2)-> q1 | | | q2 | | ->(r1,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.20.0/24'} p1 = {'ip-prefix': '192.168.200.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0, p1]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[e1]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, e1, 'in') e1.add_route('192.168.2.0/24') e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_adj_rib_in(e1)) == 3) wait_for(lambda: len(g1.get_local_rib(q1)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 1) wait_for(lambda: len(q1.get_global_rib()) == 1) wait_for(lambda: len(g1.get_local_rib(q2)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 # q1 = env.q1 # q2 = env.q2 g1.clear_policy() p0 = {'ip-prefix': '192.168.20.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[e1]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, e1, 'in') g1.softreset(e1) @staticmethod def check2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_adj_rib_in(e1)) == 3) wait_for(lambda: len(g1.get_local_rib(q1)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 2) wait_for(lambda: len(q1.get_global_rib()) == 2) wait_for(lambda: len(g1.get_local_rib(q2)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 2) wait_for(lambda: len(q2.get_global_rib()) == 2) @staticmethod def executor(env): lookup_scenario("InPolicyUpdate").boot(env) lookup_scenario("InPolicyUpdate").setup(env) lookup_scenario("InPolicyUpdate").check(env) lookup_scenario("InPolicyUpdate").setup2(env) lookup_scenario("InPolicyUpdate").check2(env) @register_scenario class ExportPolicyAsPathPrepend(object): """ No.37 aspath prepend action export -------------------------------- e1 ->(aspath=[65001])-> | -> p1-rib -> p1-adj-rib-out | -> p1 | | | -> p2-rib -> p2-adj-rib-out | -> p2 | apply action | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.20.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) st0 = {'name': 'st0', 'conditions': {'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}}, 'actions': {'route-disposition': 'accept-route', 'bgp-actions': {'set-as-path-prepend': {'repeat-n': 5, 'as': "65005"}}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_adj_rib_in(e1)) == 2) wait_for(lambda: len(g1.get_local_rib(q1)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 2) wait_for(lambda: len(q1.get_global_rib()) == 2) wait_for(lambda: len(g1.get_local_rib(q2)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 2) wait_for(lambda: len(q2.get_global_rib()) == 2) @staticmethod def check2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_adj_rib_out(q1, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_local_rib(q2, prefix='192.168.20.0/24')[0]['paths'][0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == ([65005] * 5) + [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) @staticmethod def executor(env): lookup_scenario("ExportPolicyAsPathPrepend").boot(env) lookup_scenario("ExportPolicyAsPathPrepend").setup(env) lookup_scenario("ExportPolicyAsPathPrepend").check(env) lookup_scenario("ExportPolicyAsPathPrepend").check2(env) @register_scenario class ImportPolicyAsPathPrependLastAS(object): """ No.38 aspath prepend action lastas import -------------------------------- e1 ->(aspath=[65001])-> | -> p1-rib -> p1-adj-rib-out | -> p1 | | | -> p2-rib -> p2-adj-rib-out | -> p2 | apply action | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.20.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) st0 = {'name': 'st0', 'conditions': {'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}}, 'actions': {'route-disposition': 'accept-route', 'bgp-actions': {'set-as-path-prepend': {'repeat-n': 5, 'as': "last-as"}}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ExportPolicyAsPathPrepend').check(env) @staticmethod def check2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_adj_rib_out(q1, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_local_rib(q2, prefix='192.168.20.0/24')[0]['paths'][0] assert_true(path['aspath'] == ([e1.asn] * 5) + [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == ([e1.asn] * 5) + [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) @staticmethod def executor(env): lookup_scenario("ImportPolicyAsPathPrependLastAS").boot(env) lookup_scenario("ImportPolicyAsPathPrependLastAS").setup(env) lookup_scenario("ImportPolicyAsPathPrependLastAS").check(env) lookup_scenario("ImportPolicyAsPathPrependLastAS").check2(env) @register_scenario class ExportPolicyAsPathPrependLastAS(object): """ No.39 aspath prepend action lastas export -------------------------------- e1 ->(aspath=[65001])-> | -> p1-rib -> p1-adj-rib-out | -> p1 | | | -> p2-rib -> p2-adj-rib-out | -> p2 | apply action | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.20.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) st0 = {'name': 'st0', 'conditions': {'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}}, 'actions': {'route-disposition': 'accept-route', 'bgp-actions': {'set-as-path-prepend': {'repeat-n': 5, 'as': "last-as"}}}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ExportPolicyAsPathPrepend').check(env) @staticmethod def check2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_adj_rib_out(q1, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_local_rib(q2, prefix='192.168.20.0/24')[0]['paths'][0] assert_true(path['aspath'] == [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.20.0/24')[0] assert_true(path['aspath'] == ([e1.asn] * 5) + [e1.asn]) path = g1.get_adj_rib_out(q2, prefix='192.168.200.0/24')[0] assert_true(path['aspath'] == [e1.asn]) @staticmethod def executor(env): lookup_scenario("ExportPolicyAsPathPrependLastAS").boot(env) lookup_scenario("ExportPolicyAsPathPrependLastAS").setup(env) lookup_scenario("ExportPolicyAsPathPrependLastAS").check(env) lookup_scenario("ExportPolicyAsPathPrependLastAS").check2(env) @register_scenario class ImportPolicyExCommunityOriginCondition(object): """ No.40 extended community origin condition import -------------------------------- e1 ->(extcommunity=origin:65001.65100:200)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario("ImportPolicy").boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 es0 = {'ext-community-sets': [{'ext-community-set-name': 'es0', 'ext-community-list': ['SoO:65001.65100:200']}]} g1.set_bgp_defined_set(es0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-ext-community-set': {'ext-community-set': 'es0'}}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.20.0/24', extendedcommunity='origin:{0}:200'.format((65001 << 16) + 65100)) e1.add_route('192.168.200.0/24', extendedcommunity='origin:{0}:100'.format((65001 << 16) + 65200)) for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyExCommunityOriginCondition").boot(env) lookup_scenario("ImportPolicyExCommunityOriginCondition").setup(env) lookup_scenario("ImportPolicyExCommunityOriginCondition").check(env) @register_scenario class ImportPolicyExCommunityTargetCondition(object): """ No.41 extended community origin condition import -------------------------------- e1 ->(extcommunity=target:65010:320)-> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | ->x q2-rib | -------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 es0 = {'ext-community-sets': [{'ext-community-set-name': 'es0', 'ext-community-list': ['RT:6[0-9]+:3[0-9]+']}]} g1.set_bgp_defined_set(es0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-ext-community-set': {'ext-community-set': 'es0'}}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.20.0/24', extendedcommunity='target:65010:320') e1.add_route('192.168.200.0/24', extendedcommunity='target:55000:320') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario("ImportPolicy").check(env) @staticmethod def executor(env): lookup_scenario("ImportPolicyExCommunityTargetCondition").boot(env) lookup_scenario("ImportPolicyExCommunityTargetCondition").setup(env) lookup_scenario("ImportPolicyExCommunityTargetCondition").check(env) @register_scenario class InPolicyPrefixCondition(object): """ No.42 prefix only condition accept in ----------------- e1 ->r1(192.168.100.0/24) -> o | -> q1-rib -> | -> r2 --> q1 r2(192.168.10.0/24) -> x | | | -> q2-rib -> | -> r2 --> q2 ----------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.10.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) st0 = {'name': 'st0', 'conditions': {'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, e1, 'in') # this will be blocked e1.add_route('192.168.100.0/24') # this will pass e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('InPolicyReject').check(env) @staticmethod def executor(env): lookup_scenario("InPolicyPrefixCondition").boot(env) lookup_scenario("InPolicyPrefixCondition").setup(env) lookup_scenario("InPolicyPrefixCondition").check(env) def ext_community_exists(path, extcomm): typ = extcomm.split(':')[0] value = ':'.join(extcomm.split(':')[1:]) for a in path['attrs']: if a['type'] == BGP_ATTR_TYPE_EXTENDED_COMMUNITIES: for c in a['value']: if typ == 'RT' and c['type'] == 0 and c['subtype'] == 2 and c['value'] == value: return True return False @register_scenario class ImportPolicyExCommunityAdd(object): """ No.43 extended community add action import-policy test --------------------------------- e1 ->(extcommunity=none) ->| -> q1-rib -> q1-adj-rib-out | --> q1 | | | -> q2-rib -> q2-adj-rib-out | --> q2 | add ext-community | --------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.10.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) st0 = { 'name': 'st0', 'conditions': { 'match-prefix-set': { 'prefix-set': ps0['prefix-set-name'] } }, 'actions': { 'route-disposition': 'accept-route', 'bgp-actions': { 'set-ext-community': { 'options': 'add', 'set-ext-community-method': { 'communities-list': ['rt:65000:1'], } }, } } } policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_false(ext_community_exists(path, 'RT:65000:1')) path = g1.get_adj_rib_out(q2)[0] assert_true(ext_community_exists(path, 'RT:65000:1')) @staticmethod def executor(env): lookup_scenario("ImportPolicyExCommunityAdd").boot(env) lookup_scenario("ImportPolicyExCommunityAdd").setup(env) lookup_scenario("ImportPolicyExCommunityAdd").check(env) lookup_scenario("ImportPolicyExCommunityAdd").check2(env) @register_scenario class ImportPolicyExCommunityAdd2(object): """ No.44 extended community add action import-policy test -------------------------------- e1 ->(extcommunity=RT:65000:1) -> | -> q1-rib -> q1-adj-rib-out | --> q1 | | | -> q2-rib -> q2-adj-rib-out | --> q2 | add ext-community | -------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.10.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) st0 = { 'name': 'st0', 'conditions': { 'match-prefix-set': { 'prefix-set': ps0['prefix-set-name'] } }, 'actions': { 'route-disposition': 'accept-route', 'bgp-actions': { 'set-ext-community': { 'options': 'add', 'set-ext-community-method': { 'communities-list': ['rt:65100:100'], } }, } } } policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.10.0/24', extendedcommunity='target:65000:1') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_true(ext_community_exists(path, 'RT:65000:1')) assert_false(ext_community_exists(path, 'RT:65100:100')) path = g1.get_local_rib(q2)[0]['paths'][0] assert_true(ext_community_exists(path, 'RT:65000:1')) assert_true(ext_community_exists(path, 'RT:65100:100')) path = g1.get_adj_rib_out(q2)[0] assert_true(ext_community_exists(path, 'RT:65000:1')) assert_true(ext_community_exists(path, 'RT:65100:100')) @staticmethod def executor(env): lookup_scenario("ImportPolicyExCommunityAdd2").boot(env) lookup_scenario("ImportPolicyExCommunityAdd2").setup(env) lookup_scenario("ImportPolicyExCommunityAdd2").check(env) lookup_scenario("ImportPolicyExCommunityAdd2").check2(env) @register_scenario class ImportPolicyExCommunityMultipleAdd(object): """ No.45 extended community add action multiple import-policy test --------------------------------------- exabgp ->(extcommunity=none) ->| -> peer1-rib -> peer1-adj-rib-out | --> peer1 | | | -> peer2-rib -> peer2-adj-rib-out | --> peer2 | add ext-community | --------------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.10.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) st0 = { 'name': 'st0', 'conditions': { 'match-prefix-set': { 'prefix-set': ps0['prefix-set-name'] } }, 'actions': { 'route-disposition': 'accept-route', 'bgp-actions': { 'set-ext-community': { 'options': 'add', 'set-ext-community-method': { 'communities-list': ['rt:65100:100', 'rt:100:100'], } }, } } } policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'import') e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_false(ext_community_exists(path, 'RT:65100:100')) assert_false(ext_community_exists(path, 'RT:100:100')) path = g1.get_local_rib(q2)[0]['paths'][0] assert_true(ext_community_exists(path, 'RT:65100:100')) assert_true(ext_community_exists(path, 'RT:100:100')) path = g1.get_adj_rib_out(q2)[0] assert_true(ext_community_exists(path, 'RT:65100:100')) assert_true(ext_community_exists(path, 'RT:100:100')) @staticmethod def executor(env): lookup_scenario("ImportPolicyExCommunityMultipleAdd").boot(env) lookup_scenario("ImportPolicyExCommunityMultipleAdd").setup(env) lookup_scenario("ImportPolicyExCommunityMultipleAdd").check(env) lookup_scenario("ImportPolicyExCommunityMultipleAdd").check2(env) @register_scenario class ExportPolicyExCommunityAdd(object): """ No.46 extended comunity add action export-policy test ------------------------------------ e1 ->(extcommunity=none) ->| -> q1-rib -> q1-adj-rib-out | --> q1 | | | -> q2-rib -> q2-adj-rib-out | --> q2 | add ext-community | ------------------------------------ """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.10.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) st0 = { 'name': 'st0', 'conditions': { 'match-prefix-set': { 'prefix-set': ps0['prefix-set-name'] } }, 'actions': { 'route-disposition': 'accept-route', 'bgp-actions': { 'set-ext-community': { 'options': 'add', 'set-ext-community-method': { 'communities-list': ['rt:65000:1'], } }, } } } policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, q2, 'export') e1.add_route('192.168.10.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): lookup_scenario('ImportPolicyCommunityAction').check(env) @staticmethod def check2(env): g1 = env.g1 # e1 = env.e1 q1 = env.q1 q2 = env.q2 path = g1.get_adj_rib_out(q1)[0] assert_false(ext_community_exists(path, 'RT:65000:1')) path = g1.get_local_rib(q2)[0]['paths'][0] assert_false(ext_community_exists(path, 'RT:65000:1')) path = g1.get_adj_rib_out(q2)[0] assert_true(ext_community_exists(path, 'RT:65000:1')) @staticmethod def executor(env): lookup_scenario("ExportPolicyExCommunityAdd").boot(env) lookup_scenario("ExportPolicyExCommunityAdd").setup(env) lookup_scenario("ExportPolicyExCommunityAdd").check(env) lookup_scenario("ExportPolicyExCommunityAdd").check2(env) @register_scenario class InPolicyUpdate2(object): """ No.47 in-policy update test r1:192.168.2.0 r2:192.168.20.0 r3:192.168.200.0 ------------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1,r2)-> rib ->(r1,r2)-> adj-rib-out | ->(r1,r2)-> q1 | | | q2 | | ->(r1,r3)-> rib ->(r1,r3)-> adj-rib-out | ->(r1,r3)-> q2 ------------------------------------------- | update distribute policy | V ------------------------------------- | q1 | e1 ->(r1,r2,r3)-> | ->(r1)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q1 | | | q2 | | ->(r1)-> rib ->(r1)-> adj-rib-out | ->(r1)-> q2 ------------------------------------- """ @staticmethod def boot(env): lookup_scenario('ImportPolicy').boot(env) @staticmethod def setup(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 p0 = {'ip-prefix': '192.168.20.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[e1]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, e1, 'in') e1.add_route('192.168.2.0/24') e1.add_route('192.168.20.0/24') e1.add_route('192.168.200.0/24') for c in [e1, q1, q2]: g1.wait_for(BGP_FSM_ESTABLISHED, c) @staticmethod def check(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_adj_rib_in(e1)) == 3) wait_for(lambda: len(g1.get_local_rib(q1)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 2) wait_for(lambda: len(q1.get_global_rib()) == 2) wait_for(lambda: len(g1.get_local_rib(q2)) == 2) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 2) wait_for(lambda: len(q2.get_global_rib()) == 2) @staticmethod def setup2(env): g1 = env.g1 e1 = env.e1 # q1 = env.q1 # q2 = env.q2 g1.clear_policy() p0 = {'ip-prefix': '192.168.20.0/24'} p1 = {'ip-prefix': '192.168.200.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p0, p1]} g1.set_prefix_set(ps0) ns0 = {'neighbor-set-name': 'ns0', 'neighbor-info-list': [g1.peers[e1]['neigh_addr'].split('/')[0]]} g1.set_neighbor_set(ns0) st0 = {'name': 'st0', 'conditions': { 'match-prefix-set': {'prefix-set': ps0['prefix-set-name']}, 'match-neighbor-set': {'neighbor-set': ns0['neighbor-set-name']}}, 'actions': {'route-disposition': 'reject-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, e1, 'in') g1.softreset(e1) @staticmethod def check2(env): g1 = env.g1 e1 = env.e1 q1 = env.q1 q2 = env.q2 wait_for(lambda: len(g1.get_adj_rib_in(e1)) == 3) wait_for(lambda: len(g1.get_local_rib(q1)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q1)) == 1) wait_for(lambda: len(q1.get_global_rib()) == 1) wait_for(lambda: len(g1.get_local_rib(q2)) == 1) wait_for(lambda: len(g1.get_adj_rib_out(q2)) == 1) wait_for(lambda: len(q2.get_global_rib()) == 1) @staticmethod def executor(env): lookup_scenario("InPolicyUpdate2").boot(env) lookup_scenario("InPolicyUpdate2").setup(env) lookup_scenario("InPolicyUpdate2").check(env) lookup_scenario("InPolicyUpdate2").setup2(env) lookup_scenario("InPolicyUpdate2").check2(env) @register_scenario class InPolicyRejectImplicitWithdraw(object): """ No.48 in-policy reject test g2 (asn: 65002) g3 (asn: 65003) g2's in-policy only accepts routes with origin asn 65002 r1:192.168.10.0/24 1. r1 | g1(rs) v ---------------- g3 - g2 ->(r1(aspath=[65002]))-> o | -> g4-rib -> | -> r1(aspath=[65002]) --> g4 ---------------- 2. g3 also sends prefix r1 (the prefix from g2 is still the best for the prefix) r1 r1 | | g1(rs) v v ---------------- g3 - g2 ->(r1(aspath=[65002]))-> o | -> g4-rib -> | -> r1(aspath=[65002]) --> g4 ---------------- 3. g2 withdraws r1, then the path from g3 becomes the best (implicit withdrawal happens). Since g2's in-policy only accepts routes with origin asn 2, rs must send withdrawal to g4. r1 r1 | x g1(rs) v ---------------- g3 - g2 ->(r1(aspath=[65002,65003]))-> x | -> g4-rib -> | -> r1(withdrawal) --> g4 ---------------- """ @staticmethod def boot(env): gobgp_ctn_image_name = env.parser_option.gobgp_image log_level = env.parser_option.gobgp_log_level g1 = GoBGPContainer(name='g1', asn=65001, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=log_level) g2 = GoBGPContainer(name='g2', asn=65002, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=log_level) g3 = GoBGPContainer(name='g3', asn=65003, router_id='192.168.0.3', ctn_image_name=gobgp_ctn_image_name, log_level=log_level) g4 = GoBGPContainer(name='g4', asn=65004, router_id='192.168.0.4', ctn_image_name=gobgp_ctn_image_name, log_level=log_level) ctns = [g1, g2, g3, g4] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) for cli in [g2, g4]: g1.add_peer(cli, is_rs_client=True) cli.add_peer(g1) g3.add_peer(g2) g2.add_peer(g3) env.g1 = g1 env.g2 = g2 env.g3 = g3 env.g4 = g4 @staticmethod def setup(env): g1 = env.g1 g2 = env.g2 # g3 = env.g3 g4 = env.g4 as0 = {'as-path-sets': [{'as-path-set-name': 'as0', 'as-path-list': ['_65002$']}]} g1.set_bgp_defined_set(as0) st0 = {'name': 'st0', 'conditions': {'bgp-conditions': {'match-as-path-set': {'as-path-set': 'as0'}}}, 'actions': {'route-disposition': 'accept-route'}} policy = {'name': 'policy0', 'statements': [st0]} g1.add_policy(policy, g2, 'in', 'reject') g2.add_route('192.168.0.0/24') for c in [g2, g4]: g1.wait_for(BGP_FSM_ESTABLISHED, c) g2.wait_for(BGP_FSM_ESTABLISHED, g1) @staticmethod def check(env): g1 = env.g1 # g2 = env.g2 g4 = env.g4 wait_for(lambda: len(g1.get_local_rib(g4)) == 1) wait_for(lambda: len(g1.get_local_rib(g4)[0]['paths']) == 1) wait_for(lambda: len(g4.get_global_rib()) == 1) wait_for(lambda: len(g4.get_global_rib()[0]['paths']) == 1) @staticmethod def setup2(env): env.g3.add_route('192.168.0.0/24') @staticmethod def check2(env): g1 = env.g1 g2 = env.g2 g4 = env.g4 wait_for(lambda: len(g2.get_global_rib()) == 1) wait_for(lambda: len(g2.get_global_rib()[0]['paths']) == 2) wait_for(lambda: len(g1.get_local_rib(g4)) == 1) wait_for(lambda: len(g1.get_local_rib(g4)[0]['paths']) == 1) wait_for(lambda: len(g1.get_adj_rib_in(g2)) == 1) wait_for(lambda: g1.get_neighbor(g2)['state'].get('adj-table', {}).get('accepted', 0) == 1) wait_for(lambda: len(g4.get_global_rib()) == 1) wait_for(lambda: len(g4.get_global_rib()[0]['paths']) == 1) @staticmethod def setup3(env): env.g2.local('gobgp global rib del 192.168.0.00/24') @staticmethod def check3(env): g1 = env.g1 g2 = env.g2 g4 = env.g4 wait_for(lambda: len(g2.get_global_rib()) == 1) wait_for(lambda: len(g2.get_global_rib()[0]['paths']) == 1) wait_for(lambda: len(g1.get_adj_rib_in(g2)) == 1) wait_for(lambda: g1.get_neighbor(g2)['state'].get('adj-table', {}).get('accepted', 0) == 0) wait_for(lambda: len(g1.get_local_rib(g4)) == 0) wait_for(lambda: len(g4.get_global_rib()) == 0) @staticmethod def executor(env): lookup_scenario("InPolicyRejectImplicitWithdraw").boot(env) lookup_scenario("InPolicyRejectImplicitWithdraw").setup(env) lookup_scenario("InPolicyRejectImplicitWithdraw").check(env) lookup_scenario("InPolicyRejectImplicitWithdraw").setup2(env) lookup_scenario("InPolicyRejectImplicitWithdraw").check2(env) lookup_scenario("InPolicyRejectImplicitWithdraw").setup3(env) lookup_scenario("InPolicyRejectImplicitWithdraw").check3(env) class TestGoBGPBase(unittest.TestCase): wait_per_retry = 5 retry_limit = 10 @classmethod def setUpClass(cls): idx = parser_option.test_index base.TEST_PREFIX = parser_option.test_prefix cls.parser_option = parser_option cls.executors = [] if idx == 0: print 'unset test-index. run all test sequential' for _, v in _SCENARIOS.items(): for k, m in inspect.getmembers(v, inspect.isfunction): if k == 'executor': cls.executor = m cls.executors.append(cls.executor) elif idx not in _SCENARIOS: print 'invalid test-index. # of scenarios: {0}'.format(len(_SCENARIOS)) sys.exit(1) else: for k, m in inspect.getmembers(_SCENARIOS[idx], inspect.isfunction): if k == 'executor': cls.executor = m cls.executors.append(cls.executor) def test(self): for e in self.executors: yield e if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/route_server_softreset_test.py000066400000000000000000000111471324612745600246350ustar00rootroot00000000000000# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer class GoBGPTestBase(unittest.TestCase): wait_per_retry = 5 retry_limit = 15 @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g2 = GoBGPContainer(name='g2', asn=65001, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g3 = GoBGPContainer(name='g3', asn=65002, router_id='192.168.0.3', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g4 = GoBGPContainer(name='g4', asn=65003, router_id='192.168.0.4', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) ctns = [g1, g2, g3, g4] # advertise a route from route-server-clients cls.clients = {} for cli in [g2, g3, g4]: cls.clients[cli.name] = cli initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) for cli in cls.clients.itervalues(): g1.add_peer(cli, is_rs_client=True, passwd='passwd', passive=True, prefix_limit=10) cli.add_peer(g1, passwd='passwd') cls.gobgp = g1 # test each neighbor state is turned establish def test_01_neighbor_established(self): for cli in self.clients.itervalues(): self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=cli) def test_02_softresetin_test1(self): g1 = self.gobgp g2 = self.clients['g2'] g3 = self.clients['g3'] p1 = {'ip-prefix': '10.0.10.0/24'} p2 = {'ip-prefix': '10.0.20.0/24'} ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p1, p2]} g1.set_prefix_set(ps0) st0 = {'conditions': {'match-prefix-set': {'prefix-set': 'ps0'}}, 'actions': {'route-disposition': 'accept-route'}} pol0 = {'name': 'pol0', 'statements': [st0]} _filename = g1.add_policy(pol0, g3, 'in', 'reject') g3.add_route('10.0.10.0/24') g3.add_route('10.0.20.0/24') time.sleep(1) num = g2.get_neighbor(g1)['state']['messages']['received']['update'] ps0 = {'prefix-set-name': 'ps0', 'prefix-list': [p1]} g1.set_prefix_set(ps0) g1.create_config() # this will cause g1 to do softresetin for all neighbors (policy is changed) g1.reload_config() time.sleep(1) num2 = g2.get_neighbor(g1)['state']['messages']['received']['update'] self.assertTrue((num + 1) == num2) g3.softreset(g1, type='out') time.sleep(1) num3 = g2.get_neighbor(g1)['state']['messages']['received']['update'] self.assertTrue(num2 == num3) def test_03_softresetin_test2(self): g1 = self.gobgp g2 = self.clients['g2'] g2.add_route('10.0.10.0/24') time.sleep(1) num = g2.get_neighbor(g1)['state']['messages']['received']['update'] time.sleep(3) g1.local('gobgp n all softresetin') time.sleep(3) num1 = g2.get_neighbor(g1)['state']['messages']['received']['update'] self.assertTrue(num == num1) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/route_server_test.py000066400000000000000000000213321324612745600225340ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( BGP_FSM_ACTIVE, BGP_FSM_ESTABLISHED, ) from lib.gobgp import GoBGPContainer from lib.quagga import QuaggaBGPContainer class GoBGPTestBase(unittest.TestCase): wait_per_retry = 5 retry_limit = 15 @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) rs_clients = [ QuaggaBGPContainer(name='q{0}'.format(i + 1), asn=(65001 + i), router_id='192.168.0.{0}'.format(i + 2)) for i in range(3)] ctns = [g1] + rs_clients q1 = rs_clients[0] q2 = rs_clients[1] q3 = rs_clients[2] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) for rs_client in rs_clients: g1.add_peer(rs_client, is_rs_client=True, passwd='passwd', passive=True, prefix_limit=10) rs_client.add_peer(g1, passwd='passwd') # advertise a route from route-server-clients routes = [] for idx, rs_client in enumerate(rs_clients): route = '10.0.{0}.0/24'.format(idx + 1) rs_client.add_route(route) routes.append(route) cls.gobgp = g1 cls.quaggas = {'q1': q1, 'q2': q2, 'q3': q3} def check_gobgp_local_rib(self): for rs_client in self.quaggas.itervalues(): done = False for _ in range(self.retry_limit): if done: break local_rib = self.gobgp.get_local_rib(rs_client) local_rib = [p['prefix'] for p in local_rib] state = self.gobgp.get_neighbor_state(rs_client) self.assertEqual(state, BGP_FSM_ESTABLISHED) if len(local_rib) < (len(self.quaggas) - 1): time.sleep(self.wait_per_retry) continue self.assertTrue(len(local_rib) == (len(self.quaggas) - 1)) for c in self.quaggas.itervalues(): if rs_client != c: for r in c.routes: self.assertTrue(r in local_rib) done = True if done: continue # should not reach here raise AssertionError def check_rs_client_rib(self): for rs_client in self.quaggas.itervalues(): done = False for _ in range(self.retry_limit): if done: break global_rib = rs_client.get_global_rib() global_rib = [p['prefix'] for p in global_rib] if len(global_rib) < len(self.quaggas): time.sleep(self.wait_per_retry) continue self.assertTrue(len(global_rib) == len(self.quaggas)) for c in self.quaggas.itervalues(): for r in c.routes: self.assertTrue(r in global_rib) done = True if done: continue # should not reach here raise AssertionError # test each neighbor state is turned establish def test_01_neighbor_established(self): for q in self.quaggas.itervalues(): self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q) # check advertised routes are stored in route-server's local-rib def test_02_check_gobgp_local_rib(self): self.check_gobgp_local_rib() # check gobgp's global rib. when configured as route-server, global rib # must be empty def test_03_check_gobgp_global_rib(self): self.assertTrue(len(self.gobgp.get_global_rib()) == 0) # check routes are properly advertised to route-server-client def test_04_check_rs_clients_rib(self): self.check_rs_client_rib() # check if quagga that is appended can establish connection with gobgp def test_05_add_rs_client(self): q4 = QuaggaBGPContainer(name='q4', asn=65004, router_id='192.168.0.5') self.quaggas['q4'] = q4 initial_wait_time = q4.run() time.sleep(initial_wait_time) self.gobgp.add_peer(q4, is_rs_client=True) q4.add_peer(self.gobgp) route = '10.0.4.0/24' q4.add_route(route) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q4) # check advertised routes are stored in gobgp's local-rib def test_05_check_gobgp_local_rib(self): self.check_gobgp_local_rib() # check routes are properly advertised to quagga def test_06_check_rs_clients_rib(self): self.check_rs_client_rib() def test_07_stop_one_rs_client(self): q4 = self.quaggas['q4'] q4.stop() self.gobgp.wait_for(expected_state=BGP_FSM_ACTIVE, peer=q4) del self.quaggas['q4'] # check a route advertised from q4 is deleted from gobgp's local-rib def test_08_check_gobgp_local_rib(self): self.check_gobgp_local_rib() # check whether gobgp properly sent withdrawal message with q4's route def test_09_check_rs_clients_rib(self): self.check_rs_client_rib() @unittest.skip("med shouldn't work with different AS peers by default") def test_10_add_distant_relative(self): q1 = self.quaggas['q1'] q2 = self.quaggas['q2'] q3 = self.quaggas['q3'] q5 = QuaggaBGPContainer(name='q5', asn=65005, router_id='192.168.0.6') initial_wait_time = q5.run() time.sleep(initial_wait_time) for q in [q2, q3]: q5.add_peer(q) q.add_peer(q5) med200 = {'name': 'med200', 'type': 'permit', 'match': '0.0.0.0/0', 'med': 200, 'priority': 10} q2.add_policy(med200, self.gobgp, 'out') med100 = {'name': 'med100', 'type': 'permit', 'match': '0.0.0.0/0', 'med': 100, 'priority': 10} q3.add_policy(med100, self.gobgp, 'out') q5.add_route('10.0.6.0/24') q2.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q5) q3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q5) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q2) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=q3) def check_nexthop(target_prefix, expected_nexthop): is_done = False for _ in range(self.retry_limit): if is_done: break time.sleep(self.wait_per_retry) for path in q1.get_global_rib(): if path['prefix'] == target_prefix: print "{0}'s nexthop is {1}".format(path['prefix'], path['nexthop']) n_addrs = [i[1].split('/')[0] for i in expected_nexthop.ip_addrs] if path['nexthop'] in n_addrs: is_done = True break return is_done done = check_nexthop('10.0.6.0/24', q3) self.assertTrue(done) med300 = {'name': 'med300', 'type': 'permit', 'match': '0.0.0.0/0', 'med': 300, 'priority': 5} q3.add_policy(med300, self.gobgp, 'out') time.sleep(self.wait_per_retry) done = check_nexthop('10.0.6.0/24', q2) self.assertTrue(done) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/route_server_test2.py000066400000000000000000000072421324612745600226220ustar00rootroot00000000000000# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer from lib.exabgp import ExaBGPContainer class GoBGPTestBase(unittest.TestCase): wait_per_retry = 5 retry_limit = 15 @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g2 = GoBGPContainer(name='g2', asn=65001, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name) e1 = ExaBGPContainer(name='e1', asn=65002, router_id='192.168.0.3') ctns = [g1, g2, e1] cls.clients = {cli.name: cli for cli in (g2, e1)} initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) for cli in cls.clients.values(): # Omit "passwd" to avoid a issue on ExaBGP version 4.0.5: # https://github.com/Exa-Networks/exabgp/issues/766 g1.add_peer(cli, is_rs_client=True, passive=True, prefix_limit=10) cli.add_peer(g1) # advertise a route from route-server-clients g2.add_route('10.0.0.0/24') e1.add_route('10.0.1.0/24') cls.gobgp = g1 # test each neighbor state is turned establish def test_01_neighbor_established(self): for cli in self.clients.values(): self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=cli) def test_02_add_neighbor(self): e2 = ExaBGPContainer(name='e2', asn=65001, router_id='192.168.0.4') time.sleep(e2.run()) self.gobgp.add_peer(e2, is_rs_client=True) e2.add_peer(self.gobgp) self.gobgp.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=e2) self.clients[e2.name] = e2 def test_03_check_neighbor_rib(self): rib = self.gobgp.get_local_rib(self.clients['e2']) self.assertTrue(len(rib) == 1) self.assertTrue(len(rib[0]['paths']) == 1) path = rib[0]['paths'][0] self.assertTrue(65001 not in path['aspath']) def test_04_withdraw_path(self): self.clients['g2'].local('gobgp global rib del 10.0.0.0/24') time.sleep(1) info = self.gobgp.get_neighbor(self.clients['g2'])['state']['adj-table'] self.assertTrue(info['advertised'] == 1) self.assertTrue('accepted' not in info) # means info['accepted'] == 0 self.assertTrue('received' not in info) # means info['received'] == 0 if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/rtc_test.py000066400000000000000000000215001324612745600205750ustar00rootroot00000000000000# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import from itertools import combinations import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g2 = GoBGPContainer(name='g2', asn=65000, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) ctns = {ctn.name: ctn for ctn in [g1, g2]} initial_wait_time = max(ctn.run() for ctn in ctns.values()) time.sleep(initial_wait_time) g1.local("gobgp vrf add vrf1 rd 100:100 rt both 100:100") g1.local("gobgp vrf add vrf2 rd 200:200 rt both 200:200") g2.local("gobgp vrf add vrf1 rd 100:100 rt both 100:100") g2.local("gobgp vrf add vrf3 rd 300:300 rt both 300:300") g1.local("gobgp vrf vrf1 rib add 10.0.0.0/24") g1.local("gobgp vrf vrf2 rib add 10.0.0.0/24") g2.local("gobgp vrf vrf1 rib add 20.0.0.0/24") g2.local("gobgp vrf vrf3 rib add 20.0.0.0/24") for a, b in combinations(ctns.values(), 2): a.add_peer(b, vpn=True, passwd='rtc', graceful_restart=True) b.add_peer(a, vpn=True, passwd='rtc', graceful_restart=True) cls.g1 = g1 cls.g2 = g2 cls.ctns = ctns # test each neighbor state is turned establish def test_01_neighbor_established(self): self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g2) def test_02_check_gobgp_adj_rib_out(self): time.sleep(2) self.assertTrue(len(self.g1.get_adj_rib_out(self.g2, rf='ipv4-l3vpn')) == 1) self.assertTrue(len(self.g2.get_adj_rib_out(self.g1, rf='ipv4-l3vpn')) == 1) def test_03_add_vrf(self): self.g1.local("gobgp vrf add vrf3 rd 300:300 rt both 300:300") time.sleep(2) self.assertTrue(len(self.g1.get_adj_rib_out(self.g2, rf='rtc')) == 3) self.assertTrue(len(self.g1.get_adj_rib_in(self.g2, rf='ipv4-l3vpn')) == 2) def test_04_del_vrf(self): self.g1.local("gobgp vrf del vrf1") time.sleep(2) self.assertTrue(len(self.g1.get_adj_rib_in(self.g2, rf='ipv4-l3vpn')) == 1) self.assertTrue(len(self.g1.get_adj_rib_out(self.g2, rf='rtc')) == 2) def test_05_rr_setup(self): gobgp_ctn_image_name = parser_option.gobgp_image g3 = GoBGPContainer(name='g3', asn=65000, router_id='192.168.0.3', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g4 = GoBGPContainer(name='g4', asn=65000, router_id='192.168.0.4', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) g5 = GoBGPContainer(name='g5', asn=65000, router_id='192.168.0.5', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level) time.sleep(max(ctn.run() for ctn in [g3, g4, g5])) g3.add_peer(g4, vpn=True, is_rr_client=True) g4.add_peer(g3, vpn=True) g3.add_peer(g5, vpn=True, is_rr_client=True) g5.add_peer(g3, vpn=True) for v in [g3, g4, g5]: self.ctns[v.name] = v def test_06_neighbor_established(self): g3 = self.ctns['g3'] g4 = self.ctns['g4'] g5 = self.ctns['g5'] g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g4) g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g5) def test_07_rr_test(self): g3 = self.ctns['g3'] g4 = self.ctns['g4'] g5 = self.ctns['g5'] g4.local("gobgp vrf add vrf1 rd 100:100 rt both 100:100") time.sleep(1) g5.local("gobgp vrf add vrf1 rd 100:100 rt both 100:100") time.sleep(1) def check_rtc(client): rib = g3.get_adj_rib_out(client, rf='rtc') self.assertTrue(len(rib) == 1) path = rib[0] self.assertTrue(path['nexthop'] == g3.peers[client]['local_addr'].split('/')[0]) ids = [attr['value'] for attr in path['attrs'] if attr['type'] == base.BGP_ATTR_TYPE_ORIGINATOR_ID] self.assertTrue(len(ids) == 1) self.assertTrue(ids[0] == g3.router_id) check_rtc(g4) check_rtc(g5) g4.local("gobgp vrf vrf1 rib add 40.0.0.0/24") g5.local("gobgp vrf vrf1 rib add 50.0.0.0/24") time.sleep(1) def check_ipv4_l3vpn(client): rib = g3.get_adj_rib_out(client, rf='ipv4-l3vpn') self.assertTrue(len(rib) == 1) path = rib[0] self.assertTrue(path['nexthop'] != g3.peers[client]['local_addr'].split('/')[0]) ids = [attr['value'] for attr in path['attrs'] if attr['type'] == base.BGP_ATTR_TYPE_ORIGINATOR_ID] self.assertTrue(len(ids) == 1) self.assertTrue(ids[0] != client.router_id) check_ipv4_l3vpn(g4) check_ipv4_l3vpn(g5) def test_08_rr_setup2(self): g1 = self.ctns['g1'] g2 = self.ctns['g2'] g3 = self.ctns['g3'] g1.local("gobgp vrf del vrf2") g1.local("gobgp vrf del vrf3") g2.local("gobgp vrf del vrf1") g2.local("gobgp vrf del vrf3") g3.add_peer(g1, vpn=True) g1.add_peer(g3, vpn=True) g3.add_peer(g2, vpn=True) g2.add_peer(g3, vpn=True) g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g1) g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=g2) def test_09_rr_test2(self): g1 = self.ctns['g1'] g2 = self.ctns['g2'] g3 = self.ctns['g3'] g4 = self.ctns['g4'] g5 = self.ctns['g5'] self.assertTrue(len(g1.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 0) self.assertTrue(len(g2.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 0) g1.local("gobgp vrf add vrf1 rd 100:100 rt both 100:100") g1.local("gobgp vrf vrf1 rib add 10.0.0.0/24") time.sleep(1) self.assertTrue(len(g1.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 2) self.assertTrue(len(g2.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 0) self.assertTrue(len(g3.get_adj_rib_in(g1, rf='ipv4-l3vpn')) == 1) self.assertTrue(len(g3.get_adj_rib_in(g2, rf='ipv4-l3vpn')) == 0) self.assertTrue(len(g4.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 2) self.assertTrue(len(g5.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 2) g2.local("gobgp vrf add vrf2 rd 200:200 rt both 200:200") g2.local("gobgp vrf vrf2 rib add 20.0.0.0/24") time.sleep(1) self.assertTrue(len(g1.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 2) self.assertTrue(len(g2.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 0) self.assertTrue(len(g3.get_adj_rib_in(g1, rf='ipv4-l3vpn')) == 1) self.assertTrue(len(g3.get_adj_rib_in(g2, rf='ipv4-l3vpn')) == 0) self.assertTrue(len(g4.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 2) self.assertTrue(len(g5.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 2) g4.local("gobgp vrf add vrf2 rd 200:200 rt both 200:200") time.sleep(1) self.assertTrue(len(g1.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 2) self.assertTrue(len(g2.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 0) self.assertTrue(len(g3.get_adj_rib_in(g1, rf='ipv4-l3vpn')) == 1) self.assertTrue(len(g3.get_adj_rib_in(g2, rf='ipv4-l3vpn')) == 1) self.assertTrue(len(g4.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 3) self.assertTrue(len(g5.get_adj_rib_in(g3, rf='ipv4-l3vpn')) == 2) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/run_all_tests.sh000077500000000000000000000114461324612745600216210ustar00rootroot00000000000000#!/bin/bash set +e if [ ! -v GOROOT ]; then if which go > /dev/null; then GOROOT=`dirname $(dirname $(which go))` else echo 'set $GOROOT' exit 1 fi fi if [ ! -v GOPATH ]; then echo 'set $GOPATH' exit 1 fi if [ ! -v GOBGP ]; then GOBGP=$GOPATH/src/github.com/osrg/gobgp fi if [ ! -v GOBGP_IMAGE ]; then GOBGP_IMAGE=gobgp fi if [ ! -v WS ]; then WS=`pwd` fi cd $GOBGP/test/scenario_test PIDS=() export PYTHONPATH=$GOBGP/test:$PYTHONPATH # route server test python route_server_test.py --gobgp-image $GOBGP_IMAGE --test-prefix rs -s -x --with-xunit --xunit-file=${WS}/nosetest.xml & PIDS=("${PIDS[@]}" $!) # route server ipv4 ipv6 test python route_server_ipv4_v6_test.py --gobgp-image $GOBGP_IMAGE --test-prefix v6 -s -x --with-xunit --xunit-file=${WS}/nosetest_ip.xml & PIDS=("${PIDS[@]}" $!) # bgp router test python bgp_router_test.py --gobgp-image $GOBGP_IMAGE --test-prefix bgp -s -x --with-xunit --xunit-file=${WS}/nosetest_bgp.xml & PIDS=("${PIDS[@]}" $!) # ibgp router test python ibgp_router_test.py --gobgp-image $GOBGP_IMAGE --test-prefix ibgp -s -x --with-xunit --xunit-file=${WS}/nosetest_ibgp.xml & PIDS=("${PIDS[@]}" $!) # evpn router test python evpn_test.py --gobgp-image $GOBGP_IMAGE --test-prefix evpn -s -x --with-xunit --xunit-file=${WS}/nosetest_evpn.xml & PIDS=("${PIDS[@]}" $!) # flowspec test python flow_spec_test.py --gobgp-image $GOBGP_IMAGE --test-prefix flow -s -x --with-xunit --xunit-file=${WS}/nosetest_flow.xml & PIDS=("${PIDS[@]}" $!) # route reflector test python route_reflector_test.py --gobgp-image $GOBGP_IMAGE --test-prefix rr -s -x --with-xunit --xunit-file=${WS}/nosetest_rr.xml & PIDS=("${PIDS[@]}" $!) # zebra test python bgp_zebra_test.py --gobgp-image $GOBGP_IMAGE --test-prefix zebra -s -x --with-xunit --xunit-file=${WS}/nosetest_zebra.xml & PIDS=("${PIDS[@]}" $!) # global policy test python global_policy_test.py --gobgp-image $GOBGP_IMAGE --test-prefix gpol -s -x --with-xunit --xunit-file=${WS}/nosetest_global_policy.xml & PIDS=("${PIDS[@]}" $!) # route server as2 test python route_server_as2_test.py --gobgp-image $GOBGP_IMAGE --test-prefix as2 -s -x --with-xunit --xunit-file=${WS}/nosetest_rs_as2.xml & PIDS=("${PIDS[@]}" $!) # graceful restart test python graceful_restart_test.py --gobgp-image $GOBGP_IMAGE --test-prefix gr -s -x --with-xunit --xunit-file=${WS}/nosetest_rs_gr.xml & PIDS=("${PIDS[@]}" $!) # bgp unnumbered test python bgp_unnumbered_test.py --gobgp-image $GOBGP_IMAGE --test-prefix un -s -x --with-xunit --xunit-file=${WS}/nosetest_rs_un.xml & PIDS=("${PIDS[@]}" $!) for (( i = 0; i < ${#PIDS[@]}; ++i )) do wait ${PIDS[$i]} if [ $? != 0 ]; then exit 1 fi done PIDS=() # route server malformed message test NUM=$(python route_server_malformed_test.py --test-index -1 -s 2> /dev/null | awk '/invalid/{print $NF}') PARALLEL_NUM=10 for (( i = 1; i < $(( $NUM + 1)); ++i )) do python route_server_malformed_test.py --gobgp-image $GOBGP_IMAGE --test-prefix mal$i --test-index $i -s -x --gobgp-log-level debug --with-xunit --xunit-file=${WS}/nosetest_malform${i}.xml & PIDS=("${PIDS[@]}" $!) sleep 3 done for (( i = 0; i < ${#PIDS[@]}; ++i )) do wait ${PIDS[$i]} if [ $? != 0 ]; then exit 1 fi done # route server policy test NUM=$(python route_server_policy_test.py --test-index -1 -s 2> /dev/null | awk '/invalid/{print $NF}') PARALLEL_NUM=25 for (( i = 0; i < $(( NUM / PARALLEL_NUM + 1)); ++i )) do PIDS=() for (( j = $((PARALLEL_NUM * $i + 1)); j < $((PARALLEL_NUM * ($i+1) + 1)); ++j)) do python route_server_policy_test.py --gobgp-image $GOBGP_IMAGE --test-prefix p$j --test-index $j -s -x --gobgp-log-level debug --with-xunit --xunit-file=${WS}/nosetest_policy${j}.xml & PIDS=("${PIDS[@]}" $!) if [ $j -eq $NUM ]; then break fi sleep 3 done for (( j = 0; j < ${#PIDS[@]}; ++j )) do wait ${PIDS[$j]} if [ $? != 0 ]; then exit 1 fi done done # route server policy grpc test NUM=$(python route_server_policy_grpc_test.py --test-index -1 -s 2> /dev/null | awk '/invalid/{print $NF}') PARALLEL_NUM=25 for (( i = 0; i < $(( NUM / PARALLEL_NUM + 1)); ++i )) do PIDS=() for (( j = $((PARALLEL_NUM * $i + 1)); j < $((PARALLEL_NUM * ($i+1) + 1)); ++j)) do python route_server_policy_grpc_test.py --gobgp-image $GOBGP_IMAGE --test-prefix pg$j --test-index $j -s -x --gobgp-log-level debug --with-xunit --xunit-file=${WS}/nosetest_policy_grpc${j}.xml & PIDS=("${PIDS[@]}" $!) if [ $j -eq $NUM ]; then break fi sleep 3 done for (( j = 0; j < ${#PIDS[@]}; ++j )) do wait ${PIDS[$j]} if [ $? != 0 ]; then exit 1 fi done done echo 'all tests passed successfully' exit 0 gobgp-1.29/test/scenario_test/vrf_neighbor_test.py000066400000000000000000000150071324612745600224640ustar00rootroot00000000000000# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65001, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') g2 = GoBGPContainer(name='g2', asn=65002, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') g3 = GoBGPContainer(name='g3', asn=65003, router_id='192.168.0.3', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') g4 = GoBGPContainer(name='g4', asn=65004, router_id='192.168.0.4', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') g5 = GoBGPContainer(name='g5', asn=65005, router_id='192.168.0.5', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') g6 = GoBGPContainer(name='g6', asn=65006, router_id='192.168.0.6', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') g7 = GoBGPContainer(name='g7', asn=65007, router_id='192.168.0.7', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') ctns = [g1, g2, g3, g4, g5, g6, g7] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) g4.local("gobgp vrf add red rd 10:10 rt both 10:10") g4.local("gobgp vrf add blue rd 20:20 rt both 20:20") g5.local("gobgp vrf add red rd 10:10 rt both 10:10") g5.local("gobgp vrf add blue rd 20:20 rt both 20:20") g1.add_peer(g4) g4.add_peer(g1, vrf='red') g2.add_peer(g4) g4.add_peer(g2, vrf='red') g3.add_peer(g4) g4.add_peer(g3, vrf='blue') g4.add_peer(g5, vpn=True) g5.add_peer(g4, vpn=True) g5.add_peer(g6, vrf='red') g6.add_peer(g5) g5.add_peer(g7, vrf='blue') g7.add_peer(g5) cls.g1 = g1 cls.g2 = g2 cls.g3 = g3 cls.g4 = g4 cls.g5 = g5 cls.g6 = g6 cls.g7 = g7 # test each neighbor state is turned establish def test_01_neighbor_established(self): self.g4.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g1) self.g4.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g2) self.g4.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g3) self.g4.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g5) self.g5.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g6) self.g5.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g7) def test_02_inject_from_vrf_red(self): self.g1.local('gobgp global rib add 10.0.0.0/24') time.sleep(1) dst = self.g2.get_global_rib('10.0.0.0/24') self.assertTrue(len(dst) == 1) self.assertTrue(len(dst[0]['paths']) == 1) path = dst[0]['paths'][0] self.assertTrue([self.g4.asn, self.g1.asn] == path['aspath']) dst = self.g3.get_global_rib('10.0.0.0/24') self.assertTrue(len(dst) == 0) dst = self.g4.get_global_rib(rf='vpnv4') self.assertTrue(len(dst) == 1) self.assertTrue(len(dst[0]['paths']) == 1) path = dst[0]['paths'][0] self.assertTrue([self.g1.asn] == path['aspath']) dst = self.g6.get_global_rib('10.0.0.0/24') self.assertTrue(len(dst) == 1) self.assertTrue(len(dst[0]['paths']) == 1) path = dst[0]['paths'][0] self.assertTrue([self.g5.asn, self.g4.asn, self.g1.asn] == path['aspath']) dst = self.g7.get_global_rib('10.0.0.0/24') self.assertTrue(len(dst) == 0) def test_03_inject_from_vrf_blue(self): self.g3.local('gobgp global rib add 10.0.0.0/24') time.sleep(1) dst = self.g2.get_global_rib('10.0.0.0/24') self.assertTrue(len(dst) == 1) self.assertTrue(len(dst[0]['paths']) == 1) path = dst[0]['paths'][0] self.assertTrue([self.g4.asn, self.g1.asn] == path['aspath']) dst = self.g4.get_global_rib(rf='vpnv4') self.assertTrue(len(dst) == 2) dst = self.g6.get_global_rib('10.0.0.0/24') self.assertTrue(len(dst) == 1) self.assertTrue(len(dst[0]['paths']) == 1) path = dst[0]['paths'][0] self.assertTrue([self.g5.asn, self.g4.asn, self.g1.asn] == path['aspath']) dst = self.g7.get_global_rib('10.0.0.0/24') self.assertTrue(len(dst) == 1) self.assertTrue(len(dst[0]['paths']) == 1) path = dst[0]['paths'][0] self.assertTrue([self.g5.asn, self.g4.asn, self.g3.asn] == path['aspath']) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/vrf_neighbor_test2.py000066400000000000000000000136551324612745600225550ustar00rootroot00000000000000# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import ( BGP_FSM_ACTIVE, BGP_FSM_ESTABLISHED, wait_for_completion, ) from lib.gobgp import GoBGPContainer class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65001, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') g2 = GoBGPContainer(name='g2', asn=65001, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') g3 = GoBGPContainer(name='g3', asn=65001, router_id='192.168.0.3', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') g4 = GoBGPContainer(name='g4', asn=65001, router_id='192.168.0.4', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') g5 = GoBGPContainer(name='g5', asn=65001, router_id='192.168.0.5', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, config_format='yaml') ctns = [g1, g2, g3, g4, g5] initial_wait_time = max(ctn.run() for ctn in ctns) time.sleep(initial_wait_time) g3.local("gobgp vrf add red rd 10:10 rt both 10:10") g3.local("gobgp vrf add blue rd 20:20 rt both 20:20") g1.add_peer(g3, graceful_restart=True, llgr=True) g3.add_peer(g1, vrf='red', is_rr_client=True, graceful_restart=True, llgr=True) g2.add_peer(g3, graceful_restart=True, llgr=True) g3.add_peer(g2, vrf='red', is_rr_client=True, graceful_restart=True, llgr=True) g4.add_peer(g3, graceful_restart=True, llgr=True) g3.add_peer(g4, vrf='blue', is_rr_client=True, graceful_restart=True, llgr=True) g5.add_peer(g3, graceful_restart=True, llgr=True) g3.add_peer(g5, vrf='blue', is_rr_client=True, graceful_restart=True, llgr=True) cls.g1 = g1 cls.g2 = g2 cls.g3 = g3 cls.g4 = g4 cls.g5 = g5 # test each neighbor state is turned establish def test_01_neighbor_established(self): self.g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g1) self.g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g2) self.g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g4) self.g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g5) def test_02_add_routes(self): self.g1.local("gobgp global rib add 10.0.0.0/24") self.g4.local("gobgp global rib add 10.0.0.0/24") wait_for_completion(lambda: len(self.g3.get_global_rib(rf="vpnv4")) == 2) wait_for_completion(lambda: len(self.g2.get_global_rib()) == 1) wait_for_completion(lambda: len(self.g5.get_global_rib()) == 1) def test_03_disable(self): self.g3.disable_peer(self.g1) wait_for_completion(lambda: len(self.g3.get_global_rib(rf="vpnv4")) == 1) wait_for_completion(lambda: len(self.g2.get_global_rib()) == 0) wait_for_completion(lambda: len(self.g5.get_global_rib()) == 1) self.g3.enable_peer(self.g1) self.g3.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g1) def test_04_softreset_in(self): self.g3.softreset(self.g1) wait_for_completion(lambda: len(self.g3.get_global_rib()) == 0) wait_for_completion(lambda: len(self.g3.get_global_rib(rf="vpnv4")) == 2) wait_for_completion(lambda: len(self.g2.get_global_rib()) == 1) wait_for_completion(lambda: len(self.g5.get_global_rib()) == 1) def test_05_softreset_out(self): self.g3.softreset(self.g2, type='out') wait_for_completion(lambda: len(self.g3.get_global_rib()) == 0) wait_for_completion(lambda: len(self.g3.get_global_rib(rf="vpnv4")) == 2) wait_for_completion(lambda: len(self.g2.get_global_rib()) == 1) wait_for_completion(lambda: len(self.g5.get_global_rib()) == 1) def test_06_graceful_restart(self): self.g1.graceful_restart() self.g3.wait_for(expected_state=BGP_FSM_ACTIVE, peer=self.g1) wait_for_completion(lambda: len(self.g3.get_global_rib(rf="vpnv4")) == 2) wait_for_completion(lambda: len(self.g2.get_global_rib()) == 1) wait_for_completion(lambda: len(self.g3.get_global_rib(rf="vpnv4")) == 1) wait_for_completion(lambda: len(self.g2.get_global_rib()) == 0) if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/test/scenario_test/zapi_v3_test.py000066400000000000000000000074071324612745600213720ustar00rootroot00000000000000# Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import sys import time import unittest from fabric.api import local import nose from lib.noseplugin import OptionParser, parser_option from lib import base from lib.base import BGP_FSM_ESTABLISHED from lib.gobgp import GoBGPContainer class GoBGPTestBase(unittest.TestCase): @classmethod def setUpClass(cls): gobgp_ctn_image_name = parser_option.gobgp_image base.TEST_PREFIX = parser_option.test_prefix g1 = GoBGPContainer(name='g1', asn=65000, router_id='192.168.0.1', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, zebra=True, zapi_version=3) g2 = GoBGPContainer(name='g2', asn=65001, router_id='192.168.0.2', ctn_image_name=gobgp_ctn_image_name, log_level=parser_option.gobgp_log_level, zebra=True, zapi_version=3) initial_wait_time = max(ctn.run() for ctn in [g1, g2]) time.sleep(initial_wait_time) g1.add_peer(g2, vpn=True) g2.add_peer(g1, vpn=True) cls.g1 = g1 cls.g2 = g2 def test_01_neighbor_established(self): self.g1.wait_for(expected_state=BGP_FSM_ESTABLISHED, peer=self.g2) def test_02_create_vrf(self): self.g1.local('ip netns add ns01') self.g1.local('ip netns add ns02') self.g2.local('ip netns add ns01') self.g2.local('ip netns add ns02') self.g1.local('ip netns exec ns01 ip li set up dev lo') self.g1.local('ip netns exec ns02 ip li set up dev lo') self.g2.local('ip netns exec ns01 ip li set up dev lo') self.g2.local('ip netns exec ns02 ip li set up dev lo') self.g1.local("vtysh -c 'enable' -c 'conf t' -c 'vrf 1 netns ns01'") self.g1.local("vtysh -c 'enable' -c 'conf t' -c 'vrf 2 netns ns02'") self.g2.local("vtysh -c 'enable' -c 'conf t' -c 'vrf 1 netns ns01'") self.g2.local("vtysh -c 'enable' -c 'conf t' -c 'vrf 2 netns ns02'") self.g1.local("gobgp vrf add vrf01 id 1 rd 1:1 rt both 1:1") self.g1.local("gobgp vrf add vrf02 id 2 rd 2:2 rt both 2:2") self.g2.local("gobgp vrf add vrf01 id 1 rd 1:1 rt both 1:1") self.g2.local("gobgp vrf add vrf02 id 2 rd 2:2 rt both 2:2") self.g1.local("gobgp vrf vrf01 rib add 10.0.0.0/24 nexthop 127.0.0.1") self.g1.local("gobgp vrf vrf02 rib add 20.0.0.0/24 nexthop 127.0.0.1") time.sleep(2) lines = self.g2.local("ip netns exec ns01 ip r", capture=True).split('\n') self.assertTrue(len(lines) == 1) self.assertTrue(lines[0].split(' ')[0] == '10.0.0.0/24') lines = self.g2.local("ip netns exec ns02 ip r", capture=True).split('\n') self.assertTrue(len(lines) == 1) self.assertTrue(lines[0].split(' ')[0] == '20.0.0.0/24') if __name__ == '__main__': output = local("which docker 2>&1 > /dev/null ; echo $?", capture=True) if int(output) is not 0: print "docker not found" sys.exit(1) nose.main(argv=sys.argv, addplugins=[OptionParser()], defaultTest=sys.argv[0]) gobgp-1.29/tools/000077500000000000000000000000001324612745600137155ustar00rootroot00000000000000gobgp-1.29/tools/completion/000077500000000000000000000000001324612745600160665ustar00rootroot00000000000000gobgp-1.29/tools/completion/README.md000066400000000000000000000132601324612745600173470ustar00rootroot00000000000000# Completion This page explains completion for gobgp client. ## Bash completion The described how to use and how to customize of bash completion. ### How to use 1. install bash-completion as follows: ``` % sudo apt-get install bash-completion ``` 1. add gobgp's path to PATH environment variable If you run 'go get github.com/osrg/gobgp/gobgp', gobgp command is installed in $GOPATH/bin. ``` % export PATH=$PATH:$GOPATH/bin ``` 1. load completion file ``` % source $GOPATH/src/github.com/osrg/gobgp/tools/completion/gobgp-completion.bash ``` You can use tab completion for gobgp after loading gobgp-completion.bash. ### How to customize In order to customize the bash completion, please follow steps below: 1. generate bash completion file Generate the bash completion file by using binary of gobgp client. This generating function uses [cobra bash completion](https://github.com/spf13/cobra#generating-bash-completions-for-your-command) internally. ``` % gobgp --gen-cmpl --bash-cmpl-file= ``` The following function is generated if added the "gobgp neighbor" command to gobgp client. ``` _gobgp_neighbor() { last_command="gobgp_neighbor" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } ``` 1. copy the generated functions Copy the above function to **gogbp-static-completion.bash**. 1. implement a dynamic completion If you want to add dynamic completion, you need to implement that part yourself. For example, if you want to add the neighbor address after the "gobgp neighbor" command dynamically, you can achieve it by implementing the specific internal command in **gobgp-dynamic-completion.bash**. 1. implement command to get a list of the neighbor address You need to add the processing function of the following like below to **gobgp-dynamic-completion.bash**. ``` # Get bgp neighbors by using gobgp command. __gobgp_q_neighbor() { local neighbors=( $(__gobgp_q $url $port --quiet neighbor) ) case "${neighbors[*]}" in "grpc: timed out"* | "rpc error:"* ) req_faild="True" return ;; esac for n in ${neighbors[*]}; do commands+=($n) done searched="True" } ``` 1. add a call to "__gobgp_q_neighbor" You can call the above functions by implementing as follows to **gobgp-static-completion.bash**: ``` _gobgp_neighbor() { last_command="gobgp_neighbor" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() # Implement call processing to here __gobgp_q_neighbor } ``` 1. implement the handle processing If you want to add the completion following the "gobgp neighbor \" command, you need to add a handler for _gobgp_neighbor_addr() like below to "__handle_gobgp_command()" function in **gobgp-dynamic-completion.bash**. ``` case "${last_command}" in # Control after dynamic completion of bgp neighbor command gobgp_neighbor ) next_command="_${last_command}_addr" ;; esac ``` "next_command" variable above indicates a function to be called after "gobgp neighbor", and the function name of next command is supposed to be "_gobgp_neighbor_addr" in the example above. Therefore the actual "gobgp_neighbor_addr" function needs to be implemented in **gobgp-dynamic-completion.bash**. ``` _gobgp_neighbor_addr() { last_command="gobgp_neighbor_addr" commands=() commands+=("local") commands+=("adj-in") commands+=("adj-out") commands+=("reset") commands+=("softreset") commands+=("softresetin") commands+=("softresetout") commands+=("shutdown") commands+=("enable") commands+=("disable") commands+=("policy") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } ``` 1. delete the generated bash completion file. ## Zsh completion The described how to use of bash completion. ### How to use zsh completion for gobgp works by adding the path of gobgp zsh completion directory to $fpath and enabling zsh completion like below: ``` % vi ~/.zshrc GOBGP_COMP=$GOPATH/src/github.com/osrg/gobgp/tools/completion/zsh fpath=($GOBGP_COMP $fpath) autoload -Uz compinit compinit ```gobgp-1.29/tools/completion/gobgp-completion.bash000066400000000000000000000130511324612745600221720ustar00rootroot00000000000000#!/bin/bash . `dirname $BASH_SOURCE`/gobgp-static-completion.bash . `dirname $BASH_SOURCE`/gobgp-dynamic-completion.bash __debug() { if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then echo "$*" >> "${BASH_COMP_DEBUG_FILE}" fi } # Homebrew on Macs have version 1.3 of bash-completion which doesn't include # _init_completion. This is a very minimal version of that function. __my_init_completion() { COMPREPLY=() _get_comp_words_by_ref cur prev words cword } __index_of_word() { local w word=$1 shift index=0 for w in "$@"; do [[ $w = "$word" ]] && return index=$((index+1)) done index=-1 } __contains_word() { local w word=$1; shift for w in "$@"; do [[ $w = "$word" ]] && return done return 1 } __handle_reply() { __debug "${FUNCNAME}" case $cur in -*) if [[ $(type -t compopt) = "builtin" ]]; then compopt -o nospace fi local allflags if [ ${#must_have_one_flag[@]} -ne 0 ]; then allflags=("${must_have_one_flag[@]}") else allflags=("${flags[*]} ${two_word_flags[*]}") fi COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) if [[ $(type -t compopt) = "builtin" ]]; then [[ $COMPREPLY == *= ]] || compopt +o nospace fi return 0; ;; esac # check if we are handling a flag with special work handling local index __index_of_word "${prev}" "${flags_with_completion[@]}" if [[ ${index} -ge 0 ]]; then ${flags_completion[${index}]} return fi # we are parsing a flag and don't have a special handler, no completion if [[ ${cur} != "${words[cword]}" ]]; then return fi local completions if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then completions=("${must_have_one_flag[@]}") elif [[ ${#must_have_one_noun[@]} -ne 0 ]]; then completions=("${must_have_one_noun[@]}") else completions=("${commands[@]}") fi COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) if [[ ${#COMPREPLY[@]} -eq 0 ]]; then declare -F __custom_func >/dev/null && __custom_func fi } # The arguments should be in the form "ext1|ext2|extn" __handle_filename_extension_flag() { local ext="$1" _filedir "@(${ext})" } __handle_subdirs_in_dir_flag() { local dir="$1" pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 } __handle_flag() { __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" # if a command required a flag, and we found it, unset must_have_one_flag() local flagname=${words[c]} # if the word contained an = if [[ ${words[c]} == *"="* ]]; then flagname=${flagname%=*} # strip everything after the = flagname="${flagname}=" # but put the = back fi __debug "${FUNCNAME}: looking for ${flagname}" if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then must_have_one_flag=() fi # skip the argument to a two word flag if __contains_word "${words[c]}" "${two_word_flags[@]}"; then c=$((c+1)) # if we are looking for a flags value, don't show commands if [[ $c -eq $cword ]]; then commands=() fi fi if [ ${words[(c-1)]} == "-u" ]; then url="-u ${words[(c)]}" fi if [ ${words[(c-1)]} == "-p" ]; then port="-p ${words[(c)]}" fi # skip the flag itself c=$((c+1)) } __handle_noun() { __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then must_have_one_noun=() fi nouns+=("${words[c]}") c=$((c+1)) } __handle_command() { __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" # echo "${FUNCNAME}: c is $c words[c] is ${words[c]} searched is ${searched} through ${through}" next_command="" through="False" __handle_gobgp_command searched="False" if [[ ${through} == "False" ]]; then if [[ -n ${last_command} ]]; then next_command="_${last_command}_${words[c]}" else next_command="_${words[c]}" fi fi c=$((c+1)) __debug "${FUNCNAME}: looking for ${next_command}" # echo "${FUNCNAME}: looking for ${next_command} searched is ${searched} through ${through}" declare -F $next_command >/dev/null && $next_command if [[ ${req_faild} == "True" ]]; then next_command="__gobgp_null" fi } __handle_word() { if [[ $c -ge $cword ]]; then __handle_reply return fi __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}" if [[ "${words[c]}" == -* ]]; then __handle_flag elif __contains_word "${words[c]}" "${commands[@]}"; then __handle_command else __handle_noun fi __handle_word } __start_gobgp() { local cur prev words cword if declare -F _init_completion >/dev/null 2>&1; then _init_completion -s || return else __my_init_completion || return fi local c=0 local flags=() local two_word_flags=() local flags_with_completion=() local flags_completion=() local commands=("gobgp") local must_have_one_flag=() local must_have_one_noun=() local last_command local nouns=() req_faild="False" searched="False" through="False" __handle_word } if [[ $(type -t compopt) = "builtin" ]]; then complete -F __start_gobgp gobgp else complete -o nospace -F __start_gobgp gobgp fi # ex: ts=4 sw=4 et filetype=sh gobgp-1.29/tools/completion/gobgp-dynamic-completion.bash000066400000000000000000001053651324612745600236260ustar00rootroot00000000000000#!/bin/bash __gobgp_q() { gobgp 2>/dev/null "$@" } # Get bgp neighbors use gobgp command. __gobgp_q_neighbor() { local neighbors=( $(__gobgp_q $url $port --quiet neighbor) ) case "${neighbors[*]}" in "grpc: timed out"* | "rpc error:"* ) req_faild="True" return ;; esac for n in ${neighbors[*]}; do commands+=($n) done searched="True" } # Get gobgp configration of vrfs use gobgp command. __gobgp_q_vrf() { local vrfs=( $(__gobgp_q $url $port --quiet vrf) ) case "${vrfs[*]}" in "grpc: timed out"* | "rpc error:"* ) req_faild="True" return ;; esac for n in ${vrfs[*]}; do commands+=($n) done searched="True" } # Get gobgp configration of policies use gobgp command. __gobgp_q_policy() { local parg=$1 local policies=( $(__gobgp_q $url $port --quiet policy $parg) ) case "${policies[*]}" in "grpc: timed out"* | "rpc error:"* ) req_faild="True" return ;; esac for ps in ${policies[*]}; do commands+=($ps) done searched="True" } # Get gobgp configration of policiy statements use gobgp command. __gobgp_q_statement() { local statements=( $(__gobgp_q $url $port --quiet policy statement ) ) case "${statements[*]}" in "grpc: timed out"* | "rpc error:"* ) req_faild="True" return ;; esac for sts in ${statements[*]}; do commands+=($sts) done searched="True" } # Handler for controlling obtained when the dynamic complement. # This function checks the last command to control the next operation. __handle_gobgp_command() { if [[ ${searched} == "True" ]]; then case "${last_command}" in # Control after dynamic complement of bgp neighbor command gobgp_neighbor ) next_command="_${last_command}_addr" ;; # Control after dynamic complement of bgp policy command gobgp_policy_prefix_* | gobgp_policy_neighbor_* | gobgp_policy_as-path_* | gobgp_policy_community_* | gobgp_policy_ext-community_* ) next_command="__gobgp_null" ;; gobgp_policy_del | gobgp_policy_set ) next_command="__gobgp_null" ;; gobgp_policy_statement ) if [[ ${words[c]} == "del" || ${words[c]} == "add" ]]; then return fi next_command="_gobgp_policy_statement_sname" ;; gobgp_policy_statement_del ) next_command="__gobgp_null" ;; *_condition_prefix | *_condition_neighbor | *_condition_as-path | *_condition_community | *_ext-condition_community ) next_command="__gobgp_null" ;; # Control after dynamic complement of bgp vrf command gobgp_vrf ) if [[ ${words[c]} == "del" || ${words[c]} == "add" ]]; then return fi next_command="_global_vrf_vname" ;; gobgp_vrf_del ) next_command="__gobgp_null" ;; # Control after dynamic complement of bgp mrt command gobgp_mrt_dump_rib_neighbor ) next_command="__gobgp_null" ;; gobgp_monitor_neighbor ) next_command="__gobgp_null" ;; esac through="True" fi } __gobgp_null() { last_command="gobgp_null" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_local() { last_command="gobgp_neighbor_addr_local" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_adj-in() { last_command="gobgp_neighbor_addr_adj-in" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_adj-out() { last_command="gobgp_neighbor_addr_adj-out" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_reset() { last_command="gobgp_neighbor_addr_reset" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_softreset() { last_command="gobgp_neighbor_addr_softreset" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_softresetin() { last_command="gobgp_neighbor_addr_softresetin" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_softresetout() { last_command="gobgp_neighbor_addr_softresetout" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_shutdown() { last_command="gobgp_neighbor_addr_shutdown" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_enable() { last_command="gobgp_neighbor_addr_enable" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_disable() { last_command="gobgp_neighbor_addr_disable" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_in_add() { last_command="gobgp_neighbor_addr_policy_in_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_in_del() { last_command="gobgp_neighbor_addr_policy_in_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_in_set() { last_command="gobgp_neighbor_addr_policy_in_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_in() { last_command="gobgp_neighbor_addr_policy_in" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_import_add() { last_command="gobgp_neighbor_addr_policy_import_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_import_del() { last_command="gobgp_neighbor_addr_policy_import_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_import_set() { last_command="gobgp_neighbor_addr_policy_import_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_import() { last_command="gobgp_neighbor_addr_policy_import" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_export_add() { last_command="gobgp_neighbor_addr_policy_export_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_export_del() { last_command="gobgp_neighbor_addr_policy_export_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_export_set() { last_command="gobgp_neighbor_addr_policy_export_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy_export() { last_command="gobgp_neighbor_addr_policy_export" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr_policy() { last_command="gobgp_neighbor_addr_policy" commands=() commands+=("in") commands+=("import") commands+=("export") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor_addr() { last_command="gobgp_neighbor_addr" commands=() commands+=("local") commands+=("adj-in") commands+=("adj-out") commands+=("reset") commands+=("softreset") commands+=("softresetin") commands+=("softresetout") commands+=("shutdown") commands+=("enable") commands+=("disable") commands+=("policy") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _global_vrf_vname_rib_del() { last_command="global_vrf_vname_rib_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _global_vrf_vname_rib() { last_command="global_vrf_vname_rib" commands=() commands+=("del") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _global_vrf_vname() { last_command="global_vrf_vname" commands=() commands+=("rib") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_condition_prefix() { last_command="gobgp_policy_statement_sname_ope_condition_prefix" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "prefix" } _gobgp_policy_statement_sname_ope_condition_neighbor() { last_command="gobgp_policy_statement_sname_ope_condition_neighbor" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "neighbor" } _gobgp_policy_statement_sname_ope_condition_as-path() { last_command="gobgp_policy_statement_sname_ope_condition_as-path" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "as-path" } _gobgp_policy_statement_sname_ope_condition_community() { last_command="gobgp_policy_statement_sname_ope_condition_community" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "community" } _gobgp_policy_statement_sname_ope_condition_ext-community() { last_command="gobgp_policy_statement_sname_ope_condition_ext-community" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "ext-community" } _gobgp_policy_statement_sname_ope_condition_as-path-length() { last_command="gobgp_policy_statement_sname_ope_condition_as-path-length" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_condition_rpki_valid() { last_command="gobgp_policy_statement_sname_ope_condition_rpki_valid" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_condition_rpki_invalid() { last_command="gobgp_policy_statement_sname_ope_condition_rpki_invalid" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_condition_rpki_not-found() { last_command="gobgp_policy_statement_sname_ope_condition_rpki_not-found" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_condition_rpki() { last_command="gobgp_policy_statement_sname_ope_condition_rpki" commands=() commands+=("valid") commands+=("invalid") commands+=("not-found") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_condition() { last_command="gobgp_policy_statement_sname_ope_condition" commands=() commands+=("prefix") commands+=("neighbor") commands+=("as-path") commands+=("community") commands+=("ext-community") commands+=("as-path-length") commands+=("rpki") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action_reject() { last_command="gobgp_policy_statement_sname_ope_action_reject" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action_accept() { last_command="gobgp_policy_statement_sname_ope_action_accept" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action_communities_add() { last_command="gobgp_policy_statement_sname_ope_action_communities_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action_communities_remove() { last_command="gobgp_policy_statement_sname_ope_action_communities_remove" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action_communities_replace() { last_command="gobgp_policy_statement_sname_ope_action_communities_replace" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action_communities() { last_command="gobgp_policy_statement_sname_ope_action_communities" commands=() commands+=("add") commands+=("remove") commands+=("replace") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action_community() { _gobgp_policy_statement_sname_ope_action_communities } _gobgp_policy_statement_sname_ope_action_ext-community() { _gobgp_policy_statement_sname_ope_action_communities } _gobgp_policy_statement_sname_ope_action_med_add() { last_command="gobgp_policy_statement_sname_ope_action_med_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action_med_sub() { last_command="gobgp_policy_statement_sname_ope_action_med_sub" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action_med_set() { last_command="gobgp_policy_statement_sname_ope_action_med_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action_med() { last_command="gobgp_policy_statement_sname_ope_action_med" commands=() commands+=("add") commands+=("sub") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action_as-prepend() { last_command="gobgp_policy_statement_sname_ope_action_as-prepend" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope_action() { last_command="gobgp_policy_statement_sname_ope_action" commands=() commands+=("reject") commands+=("accept") commands+=("community") commands+=("ext-community") commands+=("med") commands+=("as-prepend") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_ope() { last_command="gobgp_policy_statement_sname_ope" commands=() commands+=("condition") commands+=("action") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_sname_add() { _gobgp_policy_statement_sname_ope } _gobgp_policy_statement_sname_del() { _gobgp_policy_statement_sname_ope } _gobgp_policy_statement_sname_set() { _gobgp_policy_statement_sname_ope } _gobgp_policy_statement_sname() { last_command="gobgp_policy_statement_sname" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() }gobgp-1.29/tools/completion/gobgp-static-completion.bash000066400000000000000000001157671324612745600235000ustar00rootroot00000000000000#!/bin/bash _gobgp_global_rib_add() { last_command="gobgp_global_rib_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_rib_del() { last_command="gobgp_global_rib_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_rib() { last_command="gobgp_global_rib" commands=() commands+=("add") commands+=("del") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_in_add() { last_command="gobgp_global_policy_in_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_in_del() { last_command="gobgp_global_policy_in_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_in_set() { last_command="gobgp_global_policy_in_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_in() { last_command="gobgp_global_policy_in" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_import_add() { last_command="gobgp_global_policy_import_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_import_del() { last_command="gobgp_global_policy_import_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_import_set() { last_command="gobgp_global_policy_import_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_import() { last_command="gobgp_global_policy_import" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_export_add() { last_command="gobgp_global_policy_export_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_export_del() { last_command="gobgp_global_policy_export_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_export_set() { last_command="gobgp_global_policy_export_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy_export() { last_command="gobgp_global_policy_export" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global_policy() { last_command="gobgp_global_policy" commands=() commands+=("in") commands+=("import") commands+=("export") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_global() { last_command="gobgp_global" commands=() commands+=("rib") commands+=("policy") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_neighbor() { last_command="gobgp_neighbor" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--transport=") two_word_flags+=("-t") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_neighbor } _gobgp_vrf_add() { last_command="gobgp_vrf_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_vrf_del() { last_command="gobgp_vrf_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_vrf } _gobgp_vrf() { last_command="gobgp_vrf" commands=() commands+=("add") commands+=("del") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_vrf } _gobgp_policy_prefix_add() { last_command="gobgp_policy_prefix_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_prefix_del() { last_command="gobgp_policy_prefix_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "prefix" } _gobgp_policy_prefix_set() { last_command="gobgp_policy_prefix_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "prefix" } _gobgp_policy_prefix() { last_command="gobgp_policy_prefix" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_neighbor_add() { last_command="gobgp_policy_neighbor_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_neighbor_del() { last_command="gobgp_policy_neighbor_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "neighbor" } _gobgp_policy_neighbor_set() { last_command="gobgp_policy_neighbor_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "neighbor" } _gobgp_policy_neighbor() { last_command="gobgp_policy_neighbor" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_as-path_add() { last_command="gobgp_policy_as-path_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_as-path_del() { last_command="gobgp_policy_as-path_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "as-path" } _gobgp_policy_as-path_set() { last_command="gobgp_policy_as-path_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "as-path" } _gobgp_policy_as-path() { last_command="gobgp_policy_as-path" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_community_add() { last_command="gobgp_policy_community_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_community_del() { last_command="gobgp_policy_community_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "community" } _gobgp_policy_community_set() { last_command="gobgp_policy_community_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "community" } _gobgp_policy_community() { last_command="gobgp_policy_community" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_ext-community_add() { last_command="gobgp_policy_ext-community_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_ext-community_del() { last_command="gobgp_policy_ext-community_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "ext-community" } _gobgp_policy_ext-community_set() { last_command="gobgp_policy_ext-community_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "ext-community" } _gobgp_policy_ext-community() { last_command="gobgp_policy_ext-community" commands=() commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_add() { last_command="gobgp_policy_statement_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_statement_del() { last_command="gobgp_policy_statement_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_statement } _gobgp_policy_statement() { last_command="gobgp_policy_statement" commands=() commands+=("add") commands+=("del") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_statement } _gobgp_policy_add() { last_command="gobgp_policy_add" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_policy_del() { last_command="gobgp_policy_del" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "" } _gobgp_policy_set() { last_command="gobgp_policy_set" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_policy "" } _gobgp_policy() { last_command="gobgp_policy" commands=() commands+=("prefix") commands+=("neighbor") commands+=("as-path") commands+=("community") commands+=("ext-community") commands+=("statement") commands+=("add") commands+=("del") commands+=("set") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_monitor_global_rib() { last_command="gobgp_monitor_global_rib" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_monitor_global() { last_command="gobgp_monitor_global" commands=() commands+=("rib") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_monitor_neighbor() { last_command="gobgp_monitor_neighbor" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_neighbor } _gobgp_monitor() { last_command="gobgp_monitor" commands=() commands+=("global") commands+=("neighbor") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_mrt_dump_rib_global() { last_command="gobgp_mrt_dump_rib_global" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--format=") two_word_flags+=("-f") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--outdir=") two_word_flags+=("-o") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_mrt_dump_rib_neighbor() { last_command="gobgp_mrt_dump_rib_neighbor" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--format=") two_word_flags+=("-f") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--outdir=") two_word_flags+=("-o") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() __gobgp_q_neighbor } _gobgp_mrt_dump_rib() { last_command="gobgp_mrt_dump_rib" commands=() commands+=("global") commands+=("neighbor") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--format=") two_word_flags+=("-f") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--outdir=") two_word_flags+=("-o") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_mrt_dump() { last_command="gobgp_mrt_dump" commands=() commands+=("rib") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--format=") two_word_flags+=("-f") flags+=("--outdir=") two_word_flags+=("-o") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_mrt_inject_global() { last_command="gobgp_mrt_inject_global" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_mrt_inject() { last_command="gobgp_mrt_inject" commands=() commands+=("global") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_mrt_update_enable() { last_command="gobgp_mrt_update_enable" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_mrt_update_disable() { last_command="gobgp_mrt_update_disable" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_mrt_update_reset() { last_command="gobgp_mrt_update_reset" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_mrt_update_rotate() { last_command="gobgp_mrt_update_rotate" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_mrt_update() { last_command="gobgp_mrt_update" commands=() commands+=("enable") commands+=("disable") commands+=("reset") commands+=("rotate") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_mrt() { last_command="gobgp_mrt" commands=() commands+=("dump") commands+=("inject") commands+=("update") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_rpki_enable() { last_command="gobgp_rpki_enable" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_rpki_server() { last_command="gobgp_rpki_server" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_rpki_table() { last_command="gobgp_rpki_table" commands=() flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--address-family=") two_word_flags+=("-a") flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp_rpki() { last_command="gobgp_rpki" commands=() commands+=("enable") commands+=("server") commands+=("table") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() } _gobgp() { url="" port="" q_type="" last_command="gobgp" commands=() commands+=("global") commands+=("neighbor") commands+=("vrf") commands+=("policy") commands+=("monitor") commands+=("mrt") commands+=("rpki") flags=() two_word_flags=() flags_with_completion=() flags_completion=() flags+=("--bash-cmpl-file=") flags+=("--debug") flags+=("-d") flags+=("--gen-cmpl") flags+=("-c") flags+=("--host=") two_word_flags+=("-u") flags+=("--json") flags+=("-j") flags+=("--port=") two_word_flags+=("-p") flags+=("--quiet") flags+=("-q") must_have_one_flag=() must_have_one_noun=() }gobgp-1.29/tools/completion/zsh/000077500000000000000000000000001324612745600166725ustar00rootroot00000000000000gobgp-1.29/tools/completion/zsh/_gobgp000066400000000000000000000034761324612745600200640ustar00rootroot00000000000000#compdef gobgp __af(){ _arguments \ '-a[address family]::(ipv4 ipv6 evpn encap rtc)' } __global(){ local -a _global_arguments _global_arguments=( "rib" ) _arguments : \ '*:: :->command' if (( CURRENT == 1 )); then _describe -t commands "global command" _global_arguments return fi case "$words[1]" in rib) __af ;; esac } __neighbor(){ : ${(A)_neighbors::=${=${$(gobgp -u ${${opt_args[-u]}:-127.0.0.1} -q neighbor)//\:/\\:}}} _arguments : \ '*:: :->command' if (( CURRENT == 1 )); then _describe -t commands "neighbor selection" _neighbors return fi local -a _neighbor_arguments _neighbor_arguments=( "local" "adj-in" "adj-out" "reset" "softreset" "softresetin" "softresetout" "shutdown" "enable" "disable" "policy" ) _arguments : \ '*:: :->command' if (( CURRENT == 1 )); then _describe -t commands "neighbor command" _neighbor_arguments return fi case "$words[1]" in local) ;& adj-in) ;& adj-out) ;& reset) ;& softreset) ;& softresetin) ;& softresetout) __af ;; esac } local -a _gobgp_arguments _gobgp_arguments=( "global" "neighbor" ) _arguments : \ '-u[specifying an url (127.0.0.1)]::' \ '-p[specifying a port]::' \ '-d[use debug]' \ '-q[use quiet]' \ '-j[use json format to output format]' \ '-h[Show this help message]' \ '*:: :->command' if (( CURRENT == 1 )); then _describe -t commands "gobgp command" _gobgp_arguments return fi case "$words[1]" in global) __global ;; neighbor) __neighbor ;; esac gobgp-1.29/tools/config/000077500000000000000000000000001324612745600151625ustar00rootroot00000000000000gobgp-1.29/tools/config/example_toml.go000066400000000000000000000075241324612745600202070ustar00rootroot00000000000000package main import ( "bytes" "fmt" "github.com/BurntSushi/toml" "github.com/osrg/gobgp/config" ) func main() { b := config.Bgp{ Global: config.Global{ Config: config.GlobalConfig{ As: 12332, RouterId: "10.0.0.1", }, }, Neighbors: []config.Neighbor{ config.Neighbor{ Config: config.NeighborConfig{ PeerAs: 12333, AuthPassword: "apple", NeighborAddress: "192.168.177.33", }, AfiSafis: []config.AfiSafi{ config.AfiSafi{ Config: config.AfiSafiConfig{ AfiSafiName: "ipv4-unicast", }, }, config.AfiSafi{ Config: config.AfiSafiConfig{ AfiSafiName: "ipv6-unicast", }, }, }, ApplyPolicy: config.ApplyPolicy{ Config: config.ApplyPolicyConfig{ ImportPolicyList: []string{"pd1"}, DefaultImportPolicy: config.DEFAULT_POLICY_TYPE_ACCEPT_ROUTE, }, }, }, config.Neighbor{ Config: config.NeighborConfig{ PeerAs: 12334, AuthPassword: "orange", NeighborAddress: "192.168.177.32", }, }, config.Neighbor{ Config: config.NeighborConfig{ PeerAs: 12335, AuthPassword: "grape", NeighborAddress: "192.168.177.34", }, }, }, } var buffer bytes.Buffer encoder := toml.NewEncoder(&buffer) err := encoder.Encode(b) if err != nil { panic(err) } err = encoder.Encode(policy()) if err != nil { panic(err) } fmt.Printf("%v\n", buffer.String()) } func policy() config.RoutingPolicy { ps := config.PrefixSet{ PrefixSetName: "ps1", PrefixList: []config.Prefix{ config.Prefix{ IpPrefix: "10.3.192.0/21", MasklengthRange: "21..24", }}, } ns := config.NeighborSet{ NeighborSetName: "ns1", NeighborInfoList: []string{"10.0.0.2"}, } cs := config.CommunitySet{ CommunitySetName: "community1", CommunityList: []string{"65100:10"}, } ecs := config.ExtCommunitySet{ ExtCommunitySetName: "ecommunity1", ExtCommunityList: []string{"RT:65001:200"}, } as := config.AsPathSet{ AsPathSetName: "aspath1", AsPathList: []string{"^65100"}, } bds := config.BgpDefinedSets{ CommunitySets: []config.CommunitySet{cs}, ExtCommunitySets: []config.ExtCommunitySet{ecs}, AsPathSets: []config.AsPathSet{as}, } ds := config.DefinedSets{ PrefixSets: []config.PrefixSet{ps}, NeighborSets: []config.NeighborSet{ns}, BgpDefinedSets: bds, } al := config.AsPathLength{ Operator: "eq", Value: 2, } s := config.Statement{ Name: "statement1", Conditions: config.Conditions{ MatchPrefixSet: config.MatchPrefixSet{ PrefixSet: "ps1", MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY, }, MatchNeighborSet: config.MatchNeighborSet{ NeighborSet: "ns1", MatchSetOptions: config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY, }, BgpConditions: config.BgpConditions{ MatchCommunitySet: config.MatchCommunitySet{ CommunitySet: "community1", MatchSetOptions: config.MATCH_SET_OPTIONS_TYPE_ANY, }, MatchExtCommunitySet: config.MatchExtCommunitySet{ ExtCommunitySet: "ecommunity1", MatchSetOptions: config.MATCH_SET_OPTIONS_TYPE_ANY, }, MatchAsPathSet: config.MatchAsPathSet{ AsPathSet: "aspath1", MatchSetOptions: config.MATCH_SET_OPTIONS_TYPE_ANY, }, AsPathLength: al, }, }, Actions: config.Actions{ RouteDisposition: "reject-route", BgpActions: config.BgpActions{ SetCommunity: config.SetCommunity{ SetCommunityMethod: config.SetCommunityMethod{ CommunitiesList: []string{"65100:20"}, }, Options: "ADD", }, SetMed: "-200", }, }, } pd := config.PolicyDefinition{ Name: "pd1", Statements: []config.Statement{s}, } p := config.RoutingPolicy{ DefinedSets: ds, PolicyDefinitions: []config.PolicyDefinition{pd}, } return p } gobgp-1.29/tools/grep_avoided_functions.sh000077500000000000000000000010721324612745600207740ustar00rootroot00000000000000#!/usr/bin/env bash # List of functions which should not be used with remarkable reasons FUNCS=( # On a 32-bit platform, int type is not big enough to convert into uint32 type. # strconv.Atoi() should be replaced by strconv.ParseUint() or # strconv.ParseInt(). 'strconv\.Atoi' ) SCRIPT_DIR=`dirname $0` RESULT=0 for FUNC in ${FUNCS[@]} do for GO_PKG in $(go list github.com/osrg/gobgp/... | grep -v '/vendor/') do grep ${FUNC} -r ${GOPATH}/src/${GO_PKG} if [ $? -ne 1 ] then RESULT=1 fi done done exit $RESULTgobgp-1.29/tools/grpc/000077500000000000000000000000001324612745600146505ustar00rootroot00000000000000gobgp-1.29/tools/grpc/cpp/000077500000000000000000000000001324612745600154325ustar00rootroot00000000000000gobgp-1.29/tools/grpc/cpp/Makefile000066400000000000000000000050161324612745600170740ustar00rootroot00000000000000HOST_SYSTEM = $(shell uname | cut -f 1 -d_) SYSTEM ?= $(HOST_SYSTEM) CXX = g++ CPPFLAGS += -I/usr/local/include -pthread CXXFLAGS += -std=c++11 ifeq ($(SYSTEM),Darwin) LDFLAGS += -L/usr/local/lib `pkg-config --libs grpc++` \ -lgrpc++_reflection \ -lprotobuf -lpthread -ldl else LDFLAGS += -L/usr/local/lib `pkg-config --libs grpc++` \ -Wl,--no-as-needed -lgrpc++_reflection -Wl,--as-needed \ -lprotobuf -lpthread -ldl endif PROTOC = protoc GRPC_CPP_PLUGIN = grpc_cpp_plugin GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)` PROTOS_PATH = . vpath %.proto $(PROTOS_PATH) all: system-check gobgp_api_client gobgp_api_client: gobgp_api_client.pb.o gobgp_api_client.grpc.pb.o gobgp_api_client.o $(CXX) $^ $(LDFLAGS) -L. -lgobgp -o $@ %.grpc.pb.cc: %.proto $(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $< %.pb.cc: %.proto $(PROTOC) -I $(PROTOS_PATH) --cpp_out=. $< clean: rm -f *.o *.pb.cc *.pb.h gobgp_api_client # The following is to test your system and ensure a smoother experience. # They are by no means necessary to actually compile a grpc-enabled software. PROTOC_CMD = which $(PROTOC) PROTOC_CHECK_CMD = $(PROTOC) --version | grep -q libprotoc.3 PLUGIN_CHECK_CMD = which $(GRPC_CPP_PLUGIN) HAS_PROTOC = $(shell $(PROTOC_CMD) > /dev/null && echo true || echo false) ifeq ($(HAS_PROTOC),true) HAS_VALID_PROTOC = $(shell $(PROTOC_CHECK_CMD) 2> /dev/null && echo true || echo false) endif HAS_PLUGIN = $(shell $(PLUGIN_CHECK_CMD) > /dev/null && echo true || echo false) SYSTEM_OK = false ifeq ($(HAS_VALID_PROTOC),true) ifeq ($(HAS_PLUGIN),true) SYSTEM_OK = true endif endif system-check: ifneq ($(HAS_VALID_PROTOC),true) @echo " DEPENDENCY ERROR" @echo @echo "You don't have protoc 3.0.0 installed in your path." @echo "Please install Google protocol buffers 3.0.0 and its compiler." @echo "You can find it here:" @echo @echo " https://github.com/google/protobuf/releases/tag/v3.0.0-alpha-1" @echo @echo "Here is what I get when trying to evaluate your version of protoc:" @echo -$(PROTOC) --version @echo @echo endif ifneq ($(HAS_PLUGIN),true) @echo " DEPENDENCY ERROR" @echo @echo "You don't have the grpc c++ protobuf plugin installed in your path." @echo "Please install grpc. You can find it here:" @echo @echo " https://github.com/grpc/grpc" @echo @echo "Here is what I get when trying to detect if you have the plugin:" @echo -which $(GRPC_CPP_PLUGIN) @echo @echo endif ifneq ($(SYSTEM_OK),true) @false endif gobgp-1.29/tools/grpc/cpp/build.sh000066400000000000000000000004741324612745600170720ustar00rootroot00000000000000#!/bin/bash GOBGP_PATH=${GOPATH}/src/github.com/osrg/gobgp cd ${GOBGP_PATH}/gobgp/lib go build -buildmode=c-shared -o libgobgp.so *.go cd ${GOBGP_PATH}/tools/grpc/cpp ln -s ${GOBGP_PATH}/gobgp/lib/libgobgp.h ln -s ${GOBGP_PATH}/gobgp/lib/libgobgp.so ln -s ${GOBGP_PATH}/api/gobgp.proto gobgp_api_client.proto make gobgp-1.29/tools/grpc/cpp/gobgp_api_client.cc000066400000000000000000000054551324612745600212370ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "gobgp_api_client.grpc.pb.h" extern "C" { // Gobgp library #include "libgobgp.h" } using grpc::Channel; using grpc::ClientContext; using grpc::Status; using gobgpapi::GobgpApi; class GrpcClient { public: GrpcClient(std::shared_ptr channel) : stub_(GobgpApi::NewStub(channel)) {} std::string GetNeighbor() { gobgpapi::GetNeighborRequest request; ClientContext context; gobgpapi::GetNeighborResponse response; grpc::Status status = stub_->GetNeighbor(&context, request, &response); if (status.ok()) { std::stringstream buffer; for (int i=0; i < response.peers_size(); i++) { gobgpapi::PeerConf peer_conf = response.peers(i).conf(); gobgpapi::PeerState peer_info = response.peers(i).info(); gobgpapi::Timers peer_timers = response.peers(i).timers(); buffer << "BGP neighbor is: " << peer_conf.neighbor_address() << ", remote AS: " << peer_conf.peer_as() << "\n" << "\tBGP version: 4, remote route ID " << peer_conf.id() << "\n" << "\tBGP state = " << peer_info.bgp_state() << ", up for " << peer_timers.state().uptime() << "\n" << "\tBGP OutQ = " << peer_info.out_q() << ", Flops = " << peer_info.flops() << "\n" << "\tHold time is " << peer_timers.state().hold_time() << ", keepalive interval is " << peer_timers.state().keepalive_interval() << "seconds\n" << "\tConfigured hold time is " << peer_timers.config().hold_time() << "\n"; } return buffer.str(); } else { std::stringstream buffer; buffer << status.error_code() << "\n" << status.error_message() << "\n" << status.error_details() << "\n"; return buffer.str(); } } private: std::unique_ptr stub_; }; int main(int argc, char** argv) { if(argc < 2) { std::cout << "Usage: ./gobgp_api_client [gobgp address]\n"; return 1; } std::string addr = argv[1]; GrpcClient gobgp_client(grpc::CreateChannel(addr + ":50051", grpc::InsecureChannelCredentials())); std::string reply = gobgp_client.GetNeighbor(); std::cout << reply; return 0; } gobgp-1.29/tools/grpc/java/000077500000000000000000000000001324612745600155715ustar00rootroot00000000000000gobgp-1.29/tools/grpc/java/src/000077500000000000000000000000001324612745600163605ustar00rootroot00000000000000gobgp-1.29/tools/grpc/java/src/gobgp/000077500000000000000000000000001324612745600174565ustar00rootroot00000000000000gobgp-1.29/tools/grpc/java/src/gobgp/example/000077500000000000000000000000001324612745600211115ustar00rootroot00000000000000gobgp-1.29/tools/grpc/java/src/gobgp/example/GobgpSampleClient.java000066400000000000000000000032421324612745600253140ustar00rootroot00000000000000package gobgp.example; import gobgpapi.Gobgp; import gobgpapi.GobgpApiGrpc; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import java.util.List; public class GobgpSampleClient { private final GobgpApiGrpc.GobgpApiBlockingStub blockingStub; public GobgpSampleClient(String host, int port) { ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext(true).build(); this.blockingStub = GobgpApiGrpc.newBlockingStub(channel); } public void getNeighbors(){ Gobgp.GetNeighborRequest request = Gobgp.GetNeighborRequest.newBuilder().build(); for(Gobgp.Peer peer: this.blockingStub.getNeighbor(request).getPeersList()) { Gobgp.PeerConf conf = peer.getConf(); Gobgp.PeerState state = peer.getInfo(); Gobgp.Timers timer = peer.getTimers(); System.out.printf("BGP neighbor is %s, remote AS %d\n", conf.getNeighborAddress(), conf.getPeerAs()); System.out.printf("\tBGP version 4, remote router ID %s\n", conf.getId()); System.out.printf("\tBGP state = %s, up for %d\n", state.getBgpState(), timer.getState().getUptime()); System.out.printf("\tBGP OutQ = %d, Flops = %d\n", state.getOutQ(), state.getFlops()); System.out.printf("\tHold time is %d, keepalive interval is %d seconds\n", timer.getState().getHoldTime(), timer.getState().getKeepaliveInterval()); System.out.printf("\tConfigured hold time is %d\n", timer.getConfig().getHoldTime()); } } public static void main(String args[]){ new GobgpSampleClient(args[0], 50051).getNeighbors(); } } gobgp-1.29/tools/grpc/nodejs/000077500000000000000000000000001324612745600161325ustar00rootroot00000000000000gobgp-1.29/tools/grpc/nodejs/get_neighbors.js000066400000000000000000000020321324612745600213040ustar00rootroot00000000000000var grpc = require('grpc'); var api = grpc.load('gobgp.proto').gobgpapi; var stub = new api.GobgpApi('localhost:50051', grpc.credentials.createInsecure()); stub.getNeighbor({}, function(err, neighbor) { neighbor.peers.forEach(function(peer) { if(peer.info.bgp_state == 'BGP_FSM_ESTABLISHED') { var date = new Date(Number(peer.timers.state.uptime)*1000); var holdtime = peer.timers.state.negotiated_hold_time; var keepalive = peer.timers.state.keepalive_interval; } console.log('BGP neighbor:', peer.conf.neighbor_address, ', remote AS:', peer.conf.peer_as); console.log("\tBGP version 4, remote router ID:", peer.conf.id); console.log("\tBGP state:", peer.info.bgp_state, ', uptime:', date); console.log("\tBGP OutQ:", peer.info.out_q, ', Flops:', peer.info.flops); console.log("\tHold time:", holdtime, ', keepalive interval:', keepalive, 'seconds'); console.log("\tConfigured hold time:", peer.timers.config.hold_time); }); }); gobgp-1.29/tools/grpc/python/000077500000000000000000000000001324612745600161715ustar00rootroot00000000000000gobgp-1.29/tools/grpc/python/get_neighbor.py000066400000000000000000000022621324612745600212010ustar00rootroot00000000000000import gobgp_pb2_grpc import gobgp_pb2 import sys import grpc from grpc.framework.interfaces.face.face import ExpirationError _TIMEOUT_SECONDS = 1 def run(gobgpd_addr): channel = grpc.insecure_channel(gobgpd_addr + ':50051') stub = gobgp_pb2_grpc.GobgpApiStub(channel) try: peers = stub.GetNeighbor(gobgp_pb2.GetNeighborRequest()).peers for peer in peers: print("BGP neighbor is %s, remote AS %d" % (peer.conf.neighbor_address, peer.conf.peer_as)) print(" BGP version 4, remote router ID %s" % (peer.conf.id)) print(" BGP state = %s, up for %s" % (peer.info.bgp_state, peer.timers.state.uptime)) print(" BGP OutQ = %d, Flops = %d" % (peer.info.out_q, peer.info.flops)) print(" Hold time is %d, keepalive interval is %d seconds" % (peer.timers.state.negotiated_hold_time, peer.timers.state.keepalive_interval)) print(" Configured hold time is %d, keepalive interval is %d seconds" % (peer.timers.config.hold_time, peer.timers.config.keepalive_interval)) except ExpirationError, e: print str(e) sys.exit(-1) if __name__ == '__main__': gobgp = sys.argv[1] run(gobgp) gobgp-1.29/tools/grpc/ruby/000077500000000000000000000000001324612745600156315ustar00rootroot00000000000000gobgp-1.29/tools/grpc/ruby/get_neighbors.rb000066400000000000000000000012751324612745600210020ustar00rootroot00000000000000require 'gobgp_pb' require 'gobgp_services_pb' host = ARGV[0] stub = Gobgpapi::GobgpApi::Stub.new("#{host}:50051", :this_channel_is_insecure) arg = Gobgpapi::GetNeighborRequest.new() stub.get_neighbor(arg).peers.each do |n| puts "BGP neighbor is #{n.conf.neighbor_address}, remote AS #{n.conf.peer_as}" puts "\tBGP version 4, remote route ID #{n.conf.id}" puts "\tBGP state = #{n.info.bgp_state}, up for #{n.timers.state.uptime}" puts "\tBGP OutQ = #{n.info.out_q}, Flops = #{n.info.flops}" puts "\tHold time is #{n.timers.state.hold_time}, keepalive interval is #{n.timers.state.keepalive_interval} seconds" puts "\tConfigured hold time is #{n.timers.config.hold_time}" end gobgp-1.29/tools/pyang_plugins/000077500000000000000000000000001324612745600165745ustar00rootroot00000000000000gobgp-1.29/tools/pyang_plugins/README.rst000066400000000000000000000022271324612745600202660ustar00rootroot00000000000000What's this ? ============= This is a pyang plugin to generate config/bgp_configs.go from openconfig yang files (see https://github.com/openconfig/public). Prerequisites ============= Please confirm $GOPATH is configured before the following steps. How to use ========== Set the environment variables for this tool:: $ GOBGP_PATH=$GOPATH/src/github.com/osrg/gobgp/ Clone the required resources by using Git:: $ cd $HOME $ git clone https://github.com/osrg/public $ git clone https://github.com/YangModels/yang $ git clone https://github.com/mbj4668/pyang Setup environments for pyang:: $ cd $HOME/pyang $ source ./env.sh Generate config/bgp_configs.go from yang files:: $ PYTHONPATH=. ./bin/pyang \ --plugindir $GOBGP_PATH/tools/pyang_plugins \ -p $HOME/yang/standard/ietf/RFC \ -p $HOME/public/release/models \ -p $HOME/public/release/models/bgp \ -p $HOME/public/release/models/policy \ -f golang \ $HOME/public/release/models/bgp/openconfig-bgp.yang \ $HOME/public/release/models/policy/openconfig-routing-policy.yang \ $GOBGP_PATH/tools/pyang_plugins/gobgp.yang \ | gofmt > $GOBGP_PATH/config/bgp_configs.go gobgp-1.29/tools/pyang_plugins/bgpyang2golang.py000066400000000000000000000640521324612745600220560ustar00rootroot00000000000000# Copyright (C) 2014,2015 Nippon Telegraph and Telephone Corporation. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import print_function import sys from pyang import plugin from collections import namedtuple _COPYRIGHT_NOTICE = """ // DO NOT EDIT // generated by pyang using OpenConfig https://github.com/openconfig/public // // Copyright (C) 2014-2016 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. """ EQUAL_TYPE_LEAF = 0 EQUAL_TYPE_ARRAY = 1 EQUAL_TYPE_MAP = 2 EQUAL_TYPE_CONTAINER = 3 def pyang_plugin_init(): plugin.register_plugin(GolangPlugin()) class GolangPlugin(plugin.PyangPlugin): def __init__(self, name=None): super(GolangPlugin, self).__init__(name=name) self.multiple_modules = True def add_output_format(self, fmts): fmts['golang'] = self def emit(self, ctx, modules, fd): ctx.golang_identity_map = {} ctx.golang_typedef_map = {} ctx.golang_struct_def = [] ctx.golang_struct_names = {} ctx.emitted_type_names = {} ctx.prefix_rel = {} ctx.module_deps = [] for m in modules: check_module_deps(ctx, m) # visit yang statements visit_modules(ctx) # emit bgp_configs emit_go(ctx, fd) def visit_modules(ctx): # visit typedef and identity for mod in ctx.module_deps: visit_typedef(ctx, mod) visit_identity(ctx, mod) # visit container for mod in ctx.module_deps: visit_children(ctx, mod, mod.i_children) def emit_go(ctx, fd): ctx.golang_struct_def.reverse() done = set() # emit generate_header(ctx, fd) generate_common_functions(ctx, fd) for mod in ctx.module_deps: if mod not in _module_excluded: emit_typedef(ctx, mod, fd) emit_identity(ctx, mod, fd) for struct in ctx.golang_struct_def: struct_name = struct.uniq_name if struct_name in done: continue emit_class_def(ctx, struct, struct_name, struct.module_prefix, fd) done.add(struct_name) def check_module_deps(ctx, mod): own_prefix = mod.i_prefix for k, v in mod.i_prefixes.items(): mod = ctx.get_module(v[0]) if mod is None: continue if mod.i_prefix != own_prefix: check_module_deps(ctx, mod) ctx.prefix_rel[mod.i_prefix] = k if (mod not in ctx.module_deps and mod.i_modulename not in _module_excluded): ctx.module_deps.append(mod) def dig_leafref(type_obj): reftype = type_obj.i_type_spec.i_target_node.search_one('type') if is_leafref(reftype): return dig_leafref(reftype) else: return reftype def emit_class_def(ctx, stmt, struct_name, prefix, fd): if len(stmt.i_children) == 1 and is_list(stmt.i_children[0]): return print('// struct for container %s:%s.' % (prefix, stmt.arg), file=fd) emit_description(stmt, fd) print('type %s struct {' % convert_to_golang(struct_name), file=fd) equal_elems = [] for child in stmt.i_children: if child.path in _path_exclude: continue container_or_list_name = child.uniq_name val_name_go = convert_to_golang(child.arg) child_prefix = get_orig_prefix(child.i_orig_module) tag_name = child.uniq_name.lower() equal_type = EQUAL_TYPE_LEAF equal_data = None print('// original -> %s:%s' % (child_prefix, container_or_list_name), file=fd) # case leaf if is_leaf(child): type_obj = child.search_one('type') type_name = type_obj.arg # case identityref if is_identityref(type_obj): emit_type_name = convert_to_golang(type_obj.search_one('base').arg.split(':')[-1]) # case leafref elif is_leafref(type_obj): if type_obj.search_one('path').arg.startswith('../config'): continue t = dig_leafref(type_obj) if is_translation_required(t): print('// %s:%s\'s original type is %s.' % (child_prefix, container_or_list_name, t.arg), file=fd) emit_type_name = translate_type(t.arg) elif is_identityref(t): emit_type_name = convert_to_golang(t.search_one('base').arg.split(':')[-1]) else: emit_type_name = t.arg # case embeded enumeration elif is_enum(type_obj): emit_type_name = val_name_go # case translation required elif is_translation_required(type_obj): print('// %s:%s\'s original type is %s.' % (child_prefix, container_or_list_name, type_name), file=fd) emit_type_name = translate_type(type_name) # case other primitives elif is_builtin_type(type_obj): emit_type_name = type_name # default else: base_module = type_obj.i_orig_module.i_prefix t = lookup_typedef(ctx, base_module, type_name) # print(t.golang_name, file=sys.stderr) emit_type_name = t.golang_name # case 'case' if is_case(child): continue if is_choice(child) and is_enum_choice(child): emit_type_name = val_name_go # case leaflist if is_leaflist(child): type_obj = child.search_one('type') type_name = type_obj.arg val_name_go = val_name_go + 'List' tag_name += '-list' equal_type = EQUAL_TYPE_ARRAY # case leafref if is_leafref(type_obj): t = dig_leafref(type_obj) emit_type_name = '[]' + t.arg # case identityref elif is_identityref(type_obj): emit_type_name = '[]' + convert_to_golang(type_obj.search_one('base').arg.split(':')[-1]) # case translation required elif is_translation_required(type_obj): print('// original type is list of %s' % type_obj.arg, file=fd) emit_type_name = '[]'+translate_type(type_name) # case other primitives elif is_builtin_type(type_obj): emit_type_name = '[]' + type_name # default else: base_module = type_obj.i_orig_module.i_prefix t = lookup_typedef(ctx, base_module, type_name) emit_type_name = '[]' + t.golang_name # case container elif is_container(child) or (is_choice(child) and not is_enum_choice(child)): key = child_prefix + ':' + container_or_list_name t = ctx.golang_struct_names[key] val_name_go = t.golang_name if len(t.i_children) == 1 and is_list(t.i_children[0]): l = t.i_children[0] emit_type_name = '[]' + l.golang_name equal_type = EQUAL_TYPE_MAP equal_data = l.search_one('key').arg leaf = l.search_one('leaf').search_one('type') if leaf.arg == 'leafref' and leaf.search_one('path').arg.startswith('../config'): equal_data = 'config.' + equal_data else: emit_type_name = t.golang_name equal_type = EQUAL_TYPE_CONTAINER # case list elif is_list(child): key = child_prefix + ':' + container_or_list_name t = ctx.golang_struct_names[key] val_name_go = val_name_go + 'List' tag_name += '-list' emit_type_name = '[]' + t.golang_name equal_type = EQUAL_TYPE_MAP equal_data = child.search_one('key').arg if is_container(child): name = emit_type_name if name.startswith(convert_to_golang(struct_name)) and name.endswith("Config"): tag_name = 'config' val_name_go = 'Config' elif name.startswith(convert_to_golang(struct_name)) and name.endswith("State"): tag_name = 'state' val_name_go = 'State' emit_description(child, fd=fd) print(' {0}\t{1} `mapstructure:"{2}" json:"{2},omitempty"`'.format(val_name_go, emit_type_name, tag_name), file=fd) equal_elems.append((val_name_go, emit_type_name, equal_type, equal_data)) print('}', file=fd) if not struct_name.endswith('state'): print('func (lhs *{0}) Equal(rhs *{0}) bool {{'.format(convert_to_golang(struct_name)), file=fd) print('if lhs == nil || rhs == nil {', file=fd) print('return false', file=fd) print('}', file=fd) for val_name, type_name, typ, elem in equal_elems: if val_name == 'State': continue if typ == EQUAL_TYPE_LEAF: if type_name == '[]byte': print('if bytes.Compare(lhs.{0}, rhs.{0}) != 0 {{'.format(val_name), file=fd) else: print('if lhs.{0} != rhs.{0} {{'.format(val_name), file=fd) print('return false', file=fd) print('}', file=fd) elif typ == EQUAL_TYPE_CONTAINER: print('if !lhs.{0}.Equal(&(rhs.{0})) {{'.format(val_name), file=fd) print('return false', file=fd) print('}', file=fd) elif typ == EQUAL_TYPE_ARRAY: print('if len(lhs.{0}) != len(rhs.{0}) {{'.format(val_name), file=fd) print('return false', file=fd) print('}', file=fd) print('for idx, l := range lhs.{0} {{'.format(val_name), file=fd) if type_name == '[][]byte': print('if bytes.Compare(l, rhs.{0}[idx]) != 0 {{'.format(val_name), file=fd) else: print('if l != rhs.{0}[idx] {{'.format(val_name), file=fd) print('return false', file=fd) print('}', file=fd) print('}', file=fd) elif typ == EQUAL_TYPE_MAP: print('if len(lhs.{0}) != len(rhs.{0}) {{'.format(val_name), file=fd) print('return false', file=fd) print('}', file=fd) print('{', file=fd) print('lmap := make(map[string]*{0})'.format(type_name[2:]), file=fd) print('for i, l := range lhs.{0} {{'.format(val_name), file=fd) print('lmap[mapkey(i, string({0}))] = &lhs.{1}[i]'.format(' + '.join('l.{0}'.format(convert_to_golang(v)) for v in elem.split(' ')), val_name), file=fd) print('}', file=fd) print('for i, r := range rhs.{0} {{'.format(val_name), file=fd) print('if l, y := lmap[mapkey(i, string({0}))]; !y {{'.format('+'.join('r.{0}'.format(convert_to_golang(v)) for v in elem.split(' '))), file=fd) print('return false', file=fd) print('} else if !r.Equal(l) {', file=fd) print('return false', file=fd) print('}', file=fd) print('}', file=fd) print('}', file=fd) else: sys.stderr.write("invalid equal type %s", typ) print('return true', file=fd) print('}', file=fd) def get_orig_prefix(mod): orig = mod.i_orig_module if orig: get_orig_prefix(orig) else: return mod.i_prefix def get_path(c): path = '' if c.parent is not None: p = '' if hasattr(c, 'i_module'): mod = c.i_module prefix = mod.search_one('prefix') if prefix: p = prefix.arg + ":" path = get_path(c.parent) + "/" + p + c.arg return path # define container embedded enums def define_enum(ctx, mod, c): prefix = mod.i_prefix c.path = get_path(c) c.golang_name = convert_to_golang(c.arg) if prefix in ctx.golang_typedef_map: ctx.golang_typedef_map[prefix][c.arg] = c else: ctx.golang_typedef_map[prefix] = {c.arg: c} def visit_children(ctx, mod, children): for c in children: if is_case(c): prefix = get_orig_prefix(c.parent.i_orig_module) c.i_orig_module = c.parent.i_orig_module else: prefix = get_orig_prefix(c.i_orig_module) c.uniq_name = c.arg if c.arg == 'config': c.uniq_name = c.parent.uniq_name + '-config' elif c.arg == 'state': c.uniq_name = c.parent.uniq_name + '-state' elif c.arg == 'graceful-restart' and prefix == 'bgp-mp': c.uniq_name = 'mp-graceful-restart' if is_leaf(c) and is_enum(c.search_one('type')): define_enum(ctx, mod, c) elif is_list(c) or is_container(c) or is_choice(c): c.golang_name = convert_to_golang(c.uniq_name) if is_choice(c): picks = pickup_choice(c) c.i_children = picks if is_enum_choice(c): define_enum(ctx, mod, c) continue prefix_name = prefix + ':' + c.uniq_name if prefix_name in ctx.golang_struct_names: ext_c = ctx.golang_struct_names.get(prefix_name) ext_c_child_count = len(getattr(ext_c, "i_children")) current_c_child_count = len(getattr(c, "i_children")) if ext_c_child_count < current_c_child_count: c.module_prefix = prefix ctx.golang_struct_names[prefix_name] = c idx = ctx.golang_struct_def.index(ext_c) ctx.golang_struct_def[idx] = c else: c.module_prefix = prefix ctx.golang_struct_names[prefix_name] = c ctx.golang_struct_def.append(c) c.path = get_path(c) # print(c.path, file=sys.stderr) if hasattr(c, 'i_children'): visit_children(ctx, mod, c.i_children) def pickup_choice(c): element = [] for child in c.i_children: if is_case(child): element = element + child.i_children return element def get_type_spec(stmt): for s in stmt.substmts: if hasattr(s, 'i_type_spec'): return s.i_type_spec.name return None def visit_typedef(ctx, mod): prefix = mod.i_prefix child_map = {} for stmt in mod.substmts: if is_typedef(stmt): stmt.path = get_path(stmt) # print('stmt.path = "%s"' % stmt.path, file=sys.stderr) name = stmt.arg stmt.golang_name = convert_to_golang(name) # print('stmt.golang_name = "%s"' % stmt.golang_name, file=sys.stderr) child_map[name] = stmt ctx.golang_typedef_map[prefix] = child_map # print('ctx.golang_typedef_map["%s"] = %s' % (prefix, child_map), file=sys.stderr) prefix_rel = ctx.prefix_rel[prefix] ctx.golang_typedef_map[prefix_rel] = child_map # print('ctx.golang_typedef_map["%s"] = %s' % (prefix_rel, child_map)), file=sys.stderr) def visit_identity(ctx, mod): prefix = mod.i_prefix child_map = {} for stmt in mod.substmts: if is_identity(stmt): name = stmt.arg stmt.golang_name = convert_to_golang(name) # print('stmt.golang_name = "%s"' % stmt.golang_name, file=sys.stderr) child_map[name] = stmt base = stmt.search_one('base') if base: base_name = base.arg if ':' in base_name: base_prefix, base_name = base_name.split(':', 1) if base_prefix in ctx.golang_identity_map: ctx.golang_identity_map[base_prefix][base_name].substmts.append(stmt) else: child_map[base_name].substmts.append(stmt) ctx.golang_identity_map[prefix] = child_map # print('ctx.golang_identity_map["%s"] = %s\n' % (prefix, child_map), file=sys.stderr) prefix_rel = ctx.prefix_rel[prefix] ctx.golang_identity_map[prefix_rel] = child_map # print('ctx.golang_identity_map["%s"] = %s\n' % (prefix_rel, child_map), file=sys.stderr) def lookup_identity(ctx, default_prefix, identity_name): result = lookup(ctx.golang_identity_map, default_prefix, identity_name) return result def lookup_typedef(ctx, default_prefix, type_name): result = lookup(ctx.golang_typedef_map, default_prefix, type_name) return result def lookup(basemap, default_prefix, key): if ':' in key: pref, name = key.split(':') else: pref = default_prefix name = key if pref in basemap: return basemap[pref].get(name, None) else: return key def emit_description(stmt, fd): desc = stmt.search_one('description') if desc is None: return None desc_words = desc.arg if desc.arg.endswith('.') else desc.arg + '.' print('// %s' % desc_words.replace('\n', '\n// '), file=fd) def emit_enum(prefix, name, stmt, substmts, fd): type_name_org = name type_name = stmt.golang_name print('// typedef for identity %s:%s.' % (prefix, type_name_org), file=fd) emit_description(stmt, fd) print('type %s string' % type_name, file=fd) const_prefix = convert_const_prefix(type_name_org) print('const (', file=fd) m = {} if is_choice(stmt) and is_enum_choice(stmt): n = namedtuple('Statement', ['arg']) n.arg = 'none' substmts = [n] + substmts for sub in substmts: enum_name = '%s_%s' % (const_prefix, convert_const_prefix(sub.arg)) m[sub.arg.lower()] = enum_name print(' %s %s = "%s"' % (enum_name, type_name, sub.arg.lower()), file=fd) print(')\n', file=fd) print('var %sToIntMap = map[%s]int {' % (type_name, type_name), file=fd) for i, sub in enumerate(substmts): enum_name = '%s_%s' % (const_prefix, convert_const_prefix(sub.arg)) print(' %s: %d,' % (enum_name, i), file=fd) print('}\n', file=fd) print('func (v %s) ToInt() int {' % type_name, file=fd) print('i, ok := %sToIntMap[v]' % type_name, file=fd) print('if !ok {', file=fd) print('return -1', file=fd) print('}', file=fd) print('return i', file=fd) print('}', file=fd) print('var IntTo%sMap = map[int]%s {' % (type_name, type_name), file=fd) for i, sub in enumerate(substmts): enum_name = '%s_%s' % (const_prefix, convert_const_prefix(sub.arg)) print(' %d: %s,' % (i, enum_name), file=fd) print('}\n', file=fd) print('func (v %s) Validate() error {' % type_name, file=fd) print('if _, ok := %sToIntMap[v]; !ok {' % type_name, file=fd) print('return fmt.Errorf("invalid %s: %%s", v)' % type_name, file=fd) print('}', file=fd) print('return nil', file=fd) print('}\n', file=fd) if stmt.search_one('default'): default = stmt.search_one('default') print('func (v %s) Default() %s {' % (type_name, type_name), file=fd) print('return %s' % m[default.arg.lower()], file=fd) print('}\n', file=fd) print('func (v %s) DefaultAsNeeded() %s {' % (type_name, type_name), file=fd) print(' if string(v) == "" {', file=fd) print(' return v.Default()', file=fd) print('}', file=fd) print(' return v', file=fd) print('}', file=fd) def emit_typedef(ctx, mod, fd): prefix = mod.i_prefix t_map = ctx.golang_typedef_map[prefix] for name, stmt in t_map.items(): if stmt.path in _typedef_exclude: continue # skip identityref type because currently skip identity if get_type_spec(stmt) == 'identityref': continue type_name_org = name type_name = stmt.golang_name if type_name in ctx.emitted_type_names: print("warning %s: %s has already been emitted from %s." % (prefix + ":" + type_name_org, type_name_org, ctx.emitted_type_names[type_name]), file=sys.stderr) continue ctx.emitted_type_names[type_name] = prefix + ":" + type_name_org t = stmt.search_one('type') if not t and is_choice(stmt): emit_enum(prefix, type_name_org, stmt, stmt.i_children, fd) elif is_enum(t): emit_enum(prefix, type_name_org, stmt, t.substmts, fd) elif is_union(t): print('// typedef for typedef %s:%s.' % (prefix, type_name_org), file=fd) emit_description(t, fd) print('type %s string' % type_name, file=fd) else: if is_leafref(t): t = dig_leafref(t) print('// typedef for typedef %s:%s.' % (prefix, type_name_org), file=fd) if is_builtin_type(t): emit_description(t, fd) print('type %s %s' % (type_name, t.arg), file=fd) elif is_translation_required(t): print('// %s:%s\'s original type is %s.' % (prefix, name, t.arg), file=fd) emit_description(t, fd) print('type %s %s' % (type_name, translate_type(t.arg)), file=fd) else: m = ctx.golang_typedef_map for k in t.arg.split(':'): m = m[k] emit_description(t, fd) print('type %s %s' % (type_name, m.golang_name), file=fd) def emit_identity(ctx, mod, fd): prefix = mod.i_prefix i_map = ctx.golang_identity_map[prefix] for name, stmt in i_map.items(): enums = stmt.search('identity') if len(enums) > 0: emit_enum(prefix, name, stmt, enums, fd) def is_reference(s): return s.arg in ['leafref', 'identityref'] def is_leafref(s): return s.arg in ['leafref'] def is_identityref(s): return s.arg in ['identityref'] def is_enum(s): return s.arg in ['enumeration'] def is_union(s): return s.arg in ['union'] def is_typedef(s): return s.keyword in ['typedef'] def is_identity(s): return s.keyword in ['identity'] def is_leaf(s): return s.keyword in ['leaf'] def is_leaflist(s): return s.keyword in ['leaf-list'] def is_list(s): return s.keyword in ['list'] def is_container(s): return s.keyword in ['container'] def is_case(s): return s.keyword in ['case'] def is_choice(s): return s.keyword in ['choice'] def is_enum_choice(s): return all(e.search_one('type').arg in _type_enum_case for e in s.i_children) _type_enum_case = [ 'empty', ] def is_builtin_type(t): return t.arg in _type_builtin def is_translation_required(t): return t.arg in list(_type_translation_map.keys()) _type_translation_map = { 'union': 'string', 'decimal64': 'float64', 'boolean': 'bool', 'empty': 'bool', 'inet:ip-address': 'string', 'inet:ip-prefix': 'string', 'inet:ipv4-address': 'string', 'inet:as-number': 'uint32', 'bgp-set-community-option-type': 'string', 'inet:port-number': 'uint16', 'yang:timeticks': 'int64', 'ptypes:install-protocol-type': 'string', 'binary': '[]byte', 'route-family': 'bgp.RouteFamily', 'bgp-capability': 'bgp.ParameterCapabilityInterface', 'bgp-open-message': '*bgp.BGPMessage', } _type_builtin = [ "union", "int8", "int16", "int32", "int64", "string", "uint8", "uint16", "uint32", "uint64", ] _module_excluded = [ "ietf-inet-types", "ietf-yang-types", ] _path_exclude = [ "/rpol:routing-policy/rpol:defined-sets/rpol:neighbor-sets/rpol:neighbor-set/rpol:neighbor", "/rpol:routing-policy/rpol:defined-sets/bgp-pol:bgp-defined-sets/bgp-pol:community-sets/bgp-pol:community-set/bgp-pol:community-member", "/rpol:routing-policy/rpol:defined-sets/bgp-pol:bgp-defined-sets/bgp-pol:ext-community-sets/bgp-pol:ext-community-set/bgp-pol:ext-community-member", "/rpol:routing-policy/rpol:defined-sets/bgp-pol:bgp-defined-sets/bgp-pol:as-path-sets/bgp-pol:as-path-set/bgp-pol:as-path-set-member", ] _typedef_exclude = [ "/gobgp:bgp-capability", "/gobgp:bgp-open-message", ] def generate_header(ctx, fd): print(_COPYRIGHT_NOTICE, file=fd) print('package config', file=fd) print('', file=fd) print('import (', file=fd) print('"fmt"', file=fd) print('', file=fd) print('"github.com/osrg/gobgp/packet/bgp"', file=fd) print(')', file=fd) print('', file=fd) def generate_common_functions(ctx, fd): print('func mapkey(index int, name string) string {', file=fd) print('if name != "" {', file=fd) print('return name', file=fd) print('}', file=fd) print('return fmt.Sprintf("%v", index)', file=fd) print('}', file=fd) def translate_type(key): if key in _type_translation_map.keys(): return _type_translation_map[key] else: return key # 'hoge-hoge' -> 'HogeHoge' def convert_to_golang(type_string): a = type_string.split('.') a = [x.capitalize() for x in a] # XXX locale sensitive return '.'.join(''.join(t.capitalize() for t in x.split('-')) for x in a) # 'hoge-hoge' -> 'HOGE_HOGE' def convert_const_prefix(type_string): return type_string.replace('-', '_').upper() def chop_suf(s, suf): if not s.endswith(suf): return s return s[:-len(suf)] gobgp-1.29/tools/pyang_plugins/gobgp.yang000066400000000000000000000726651324612745600205720ustar00rootroot00000000000000module gobgp { yang-version "1"; // namespace namespace "https://github.com/osrg/gobgp"; prefix "gobgp"; // import some basic types import openconfig-bgp { prefix bgp; } import openconfig-bgp-types { prefix bgp-types; } import openconfig-bgp-multiprotocol { prefix bgp-mp; } import openconfig-routing-policy {prefix rpol; } import openconfig-policy-types {prefix ptypes; } import openconfig-bgp-policy {prefix bgp-pol; } import ietf-inet-types { prefix inet; } import ietf-yang-types { prefix yang; } // meta organization "GoBGP"; contact "GoBGP http://osrg.github.io/gobgp/"; description "This module contains definitions for GoBGP-specific configuration. It augments bgp modules with GoBGP-specific options."; revision "2015-08-10" { description "Updated model to augment base bgp modules"; reference "TBD"; } typedef bmp-route-monitoring-policy-type { type enumeration { enum PRE-POLICY { value 0; description "send pre-policy routes"; } enum POST-POLICY { value 1; description "send post-policy routes"; } enum BOTH { value 2; description "!OBSOLETED! send both pre and post-policy routes"; } enum LOCAL-RIB { value 3; description "send local rib routes"; } enum ALL { value 4; description "send pre-policy, post-policy, and local rib routes"; } } } identity eq { base ptypes:attribute-comparison; } identity ge { base ptypes:attribute-comparison; } identity le { base ptypes:attribute-comparison; } identity IPV4-MULTICAST { base bgp-types:afi-safi-type; description "IPv4 multicast (AFI,SAFI = 1,2)"; reference "RFC4760"; } identity IPV6-MULTICAST { base bgp-types:afi-safi-type; description "IPv4 multicast (AFI,SAFI = 1,2)"; reference "RFC4760"; } identity RTC { base bgp-types:afi-safi-type; description "Route target membership (AFI,SAFI = 1,132)"; reference "RFC4684"; } identity IPV4-ENCAP { base bgp-types:afi-safi-type; description "Encapsulation (AFI,SAFI = 1,7)"; reference "RFC5512"; } identity IPV6-ENCAP { base bgp-types:afi-safi-type; description "Encapsulation (AFI,SAFI = 2,7)"; reference "RFC5512"; } identity IPV4-FLOWSPEC { base bgp-types:afi-safi-type; description "IPv4 flowspec (AFI,SAFI = 1,133)"; reference "RFC5575"; } identity L3VPN-IPV4-FLOWSPEC { base bgp-types:afi-safi-type; description "L3VPN IPv4 flowspec (AFI,SAFI = 1,134)"; reference "RFC5575"; } identity IPV6-FLOWSPEC { base bgp-types:afi-safi-type; description "IPv6 flowspec (AFI,SAFI = 1,133)"; reference "RFC5575"; } identity L3VPN-IPV6-FLOWSPEC { base bgp-types:afi-safi-type; description "L3VPN IPv6 flowspec (AFI,SAFI = 1,134)"; reference "RFC5575"; } identity L2VPN-FLOWSPEC { base bgp-types:afi-safi-type; description "L2VPN flowspec (AFI,SAFI = 25,134)"; reference "draft-ietf-idr-flowspec-l2vpn-03"; } identity OPAQUE { base bgp-types:afi-safi-type; description "Opaque (AFI,SAFI = 16397,241)"; reference "https://tools.ietf.org/html/draft-lapukhov-bgp-opaque-signaling-01"; } grouping gobgp-message-counter { description "Counters for all BGPMessage types"; leaf OPEN { type uint64; description "Number of BGP open messages announcing, withdrawing or modifying paths exchanged."; } leaf REFRESH { type uint64; description "Number of BGP Route-Refresh messages indicating an error condition has occurred exchanged."; } leaf KEEPALIVE { type uint64; description "Number of BGP Keepalive messages indicating an error condition has occurred exchanged."; } leaf DYNAMIC-CAP { type uint64; description "Number of BGP dynamic-cap messages indicating an error condition has occurred exchanged."; } leaf WITHDRAW-UPDATE { type uint32; description "Number of updates subjected to treat-as-withdraw treatment."; } leaf WITHDRAW-PREFIX { type uint32; description "Number of prefixes subjected to treat-as-withdraw treatment."; } leaf DISCARDED { type uint64; description "Number of discarded messages indicating an error condition has occurred exchanged."; } leaf TOTAL { type uint64; description "Number of total messages indicating an error condition has occurred exchanged."; } } grouping gobgp-adjacent-table { container adj-table { leaf ADVERTISED { type uint32; } leaf FILTERED { type uint32; } leaf RECEIVED { type uint32; } leaf ACCEPTED { type uint32; } } } grouping gobgp-timer { description "additional timer"; leaf idle-hold-time-after-reset { type decimal64 { fraction-digits 2; } default 30; description "Time interval in seconds that a BGP session will be in idle state after neighbor reset operation."; } } grouping gobgp-neighbor-timer { description "additional timer"; leaf downtime { type yang:timeticks; description "This timer determines the amount of time since the BGP last transitioned out of the Established state"; } leaf update-recv-time { type int64; description "The number of seconds elasped since January 1, 1970 UTC last time the BGP session received an UPDATE message"; } } grouping gobgp-in-policy { description "additional policy"; leaf-list in-policy { type leafref { path "/rpol:routing-policy/rpol:policy-definitions/" + "rpol:policy-definition/rpol:name"; //require-instance true; } description "list of policy names in sequence to be applied on sending a routing update in the current context, e.g., for the current other route server clients."; } leaf default-in-policy { type rpol:default-policy-type; default REJECT-ROUTE; description "explicitly set a default policy if no policy definition in the in-policy chain is satisfied."; } } grouping gobgp-route-server-config { description "Configuration parameter specifying whether the neighbor is route server client or not."; leaf route-server-client { type boolean; default "false"; description "Configure the neighbor as a route server client."; } } grouping gobgp-route-server-config-set { description "set of configurations for route server client."; container route-server { description "Configure the local router as a route server"; container config { description "Configuration parameters relating to route server client(s) used for the BGP neighbor"; uses gobgp-route-server-config; } container state { config false; description "State information relating to route server client(s) used for the BGP neighbor"; uses gobgp-route-server-config; } } } typedef rpki-validation-result-type { type enumeration { enum NONE { description "RPKI disabled"; } enum NOT-FOUND { description "If the origin AS, prefix, maximum prefix length does not exist in the range of ROA"; } enum VALID { description "If the origin AS, prefix, maximum prefix length is exist in the range of ROA"; } enum INVALID { description "if the origin AS is different when prefix, maximum prefix length is exist in the range of ROA"; } } description "indicate the validation result of RPKI based on ROA"; } grouping gobgp-rpki-validation-result { description "additional rpki"; leaf rpki-validation-result { type rpki-validation-result-type; default NOT-FOUND; description "specify the validation result of RPKI based on ROA as conditions"; } } grouping gobgp-rpki-server-messages-sent { description "additional RPKI sent messages"; leaf serial-query { type int64; description "Number of serial query message sent to RPKI server"; } leaf reset-query { type int64; description "Number of reset query message sent to RPKI server"; } leaf error { type int64; description "Number of error message sent to RPKI server"; } } grouping gobgp-rpki-server-messages-received { description "additional RPKI receive messages"; leaf serial-notify { type int64; description "Number of serial notify message received from RPKI server"; } leaf cache-reset { type int64; description "Number of cache reset message received from RPKI server"; } leaf cache-response { type int64; description "Number of cache response message received from RPKI server"; } leaf ipv4-prefix { type int64; description "Number of ipv4 prefix message received from RPKI server"; } leaf ipv6-prefix { type int64; description "Number of ipv6 prefix message received from RPKI server"; } leaf end-of-data { type int64; description "Number of end of data message received from RPKI server"; } leaf error { type int64; description "Number of error message received from RPKI server"; } } grouping gobgp-rpki-server-messages { description "additional RPKI messages"; container rpki-sent { description "Counters for transmission RPKI Message types"; uses gobgp-rpki-server-messages-sent; } container rpki-received { description "Counters for reception RPKI Message types"; uses gobgp-rpki-server-messages-received; } } grouping gobgp-rpki-server-state { description "additional RPKI state"; leaf up { type boolean; } leaf serial-number { type uint32; } leaf records-v4 { type uint32; } leaf records-v6 { type uint32; } leaf prefixes-v4 { type uint32; } leaf prefixes-v6 { type uint32; } leaf uptime { type int64; description "This timer determines the amount of time since the RPKI last transitioned in of the Established state"; } leaf downtime { type int64; description "This timer determines the amount of time since the RPKI last transitioned out of the Established state"; } leaf last-pdu-recv-time { type int64; description "last time the received an pdu message from RPKI server"; } container rpki-messages { description "Counters for transmission and reception RPKI Message types"; uses gobgp-rpki-server-messages; } } grouping gobgp-rpki-server-config { description "additional RPKI config"; leaf address { type inet:ip-address; description "Reference to the address of the RPKI server used as a key in the RPKI server list"; } leaf port { type uint32; description "Reference to the port of the RPKI server"; } leaf refresh-time { type int64; description "Check interval for a configured RPKI server."; } leaf hold-time { type int64; description "Specify the length of time in seconds that the session between the router and RPKI server is to be considered operational without any activity"; } leaf record-lifetime { type int64; description "Indicate the expiration date of the route validation recode received from RPKI server"; } leaf preference { type uint8; description "RPKI server has a static preference. Higher the preference values indicates a higher priority RPKI server"; } } grouping gobgp-rpki-server-set { description "additional RPKI configuration and state"; container config { description "Configuration parameters relating to RPKI server"; uses gobgp-rpki-server-config; } container state { description "State information relating to RPKI server"; uses gobgp-rpki-server-state; } } grouping gobgp-rpki-servers { description "additional RPKI structure"; container rpki-servers { list rpki-server { key "address"; description "List of RPKI servers configured on the local system"; leaf address { type leafref { path "../config/address"; } } uses gobgp-rpki-server-set; } } } grouping gobgp-bmp-server-config { description "additional BMP config"; leaf address { type inet:ip-address; description "Reference to the address of the BMP server used as a key in the BMP server list"; } leaf port { type uint32; description "Reference to the port of the BMP server"; } leaf route-monitoring-policy { type bmp-route-monitoring-policy-type; default PRE-POLICY; } leaf statistics-timeout { type uint16; description "Interval seconds of statistics messages sent to BMP server"; } leaf route-mirroring-enabled { type boolean; description "Enable feature for mirroring of received BGP messages mainly for debugging purpose"; } } grouping gobgp-bmp-server-set { description "additional BMP configuration and state"; container config { description "Configuration parameters relating to BMP server"; uses gobgp-bmp-server-config; } container state { description "Configuration parameters relating to BMP server"; uses gobgp-bmp-server-config; } } grouping gobgp-bmp-servers { description "BGP Monitoring Protocol servers"; container bmp-servers { list bmp-server { key "address"; description "List of BMP servers configured on the local system"; leaf address { type leafref { path "../config/address"; } } uses gobgp-bmp-server-set; } } } grouping long-lived-graceful-restart { container long-lived-graceful-restart { container config { leaf enabled { type boolean; } leaf restart-time { type uint32; } } container state { leaf enabled { type boolean; } leaf received { type boolean; } leaf advertised { type boolean; } leaf peer-restart-time { type uint32; } leaf peer-restart-timer-expired { type boolean; } } } } grouping gobgp-ttl-security-config { description "Configuration parameters for TTL Security"; leaf enabled { type boolean; default "false"; description "Enable features for TTL Security"; } leaf ttl-min { type uint8; description "Reference to the port of the BMP server"; } } grouping gobgp-ttl-security-config-set { description "set of configurations for Generalized TTL Security Mechanism (GTSM)"; container ttl-security { description "Configure TTL Security feature"; container config { description "Configuration parameters for TTL Security"; uses gobgp-ttl-security-config; } container state { config false; description "State information for TTL Security"; uses gobgp-ttl-security-config; } } } // augment statements augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:state/bgp:messages/bgp:sent" { description "additional counters"; uses gobgp-message-counter; } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:state/bgp:messages/bgp:received" { description "additional counters"; uses gobgp-message-counter; } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:state" { description "additional counters"; uses gobgp-adjacent-table; } typedef bgp-capability { type uint8; } typedef bgp-open-message { type uint8; } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:state" { leaf-list remote-capability { type bgp-capability; } leaf-list local-capability { type bgp-capability; } leaf received-open-message { type bgp-open-message; } } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:config" { description "additional state elements"; leaf admin-down { type boolean; description "The config of administrative operation. If state, indicates the neighbor is disabled by the administrator"; } } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:state" { description "additional state elements"; leaf admin-down { type boolean; description "The state of administrative operation. If the state is true, it indicates the neighbor is disabled by the administrator"; } leaf admin-state { type enumeration { enum UP { description "admin state is up"; } enum DOWN { description "admin state is down"; } enum PFX_CT { description "prefix counter over limit"; } } } leaf established-count { type uint32; description "The number of how many the peer became established state"; } leaf flops { type uint32; description "The number of flip-flops"; } } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:config" { leaf neighbor-interface { type string; } leaf vrf { type string; } } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:state" { leaf neighbor-interface { type string; } leaf vrf { type string; } } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:state" { leaf remote-router-id { type string; } } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:transport/bgp:config" { leaf remote-port { type inet:port-number; } leaf ttl { description "TTL value for BGP packets"; type uint8; } } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:timers/bgp:config" { description "additional timer"; uses gobgp-timer; } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:timers/bgp:state" { description "additional timers"; uses gobgp-timer; uses gobgp-neighbor-timer; } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:afi-safis/bgp:afi-safi/bgp:graceful-restart/bgp:state" { description "additional graceful-restart status"; leaf end-of-rib-received { type boolean; } leaf end-of-rib-sent { type boolean; } } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:graceful-restart/bgp:config" { description "additional graceful-restart status"; leaf deferral-time { type uint16; } leaf notification-enabled { type boolean; } leaf long-lived-enabled { type boolean; } } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor/bgp:graceful-restart/bgp:state" { description "additional graceful-restart status"; leaf deferral-time { type uint16; } leaf notification-enabled { type boolean; } leaf long-lived-enabled { type boolean; } } augment "/bgp:bgp/bgp:peer-groups/bgp:peer-group" { description "route server configuration for peer-group"; uses gobgp-route-server-config-set; } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor" { description "route server configuration for neighbor"; uses gobgp-route-server-config-set; } augment "/bgp:bgp/bgp:peer-groups/bgp:peer-group" { description "TTL Security configuration for peer-group"; uses gobgp-ttl-security-config-set; } augment "/bgp:bgp/bgp:neighbors/bgp:neighbor" { description "TTL Security configuration for neighbor"; uses gobgp-ttl-security-config-set; } augment "/bgp:bgp/bgp:global/bgp:apply-policy/bgp:config" { description "addtional policy"; uses gobgp-in-policy; } augment "/bgp:bgp/bgp:global/bgp:apply-policy/bgp:state" { description "additional policy"; uses gobgp-in-policy; } augment "/rpol:routing-policy/rpol:policy-definitions/" + "rpol:policy-definition/rpol:statements/rpol:statement/" + "rpol:actions/bgp-pol:bgp-actions/bgp-pol:set-as-path-prepend" { description "as number used for aspath prepend"; leaf as { type union { type inet:as-number; type string { pattern "last-as"; } } description "autonomous system number or 'last-as' which means the leftmost as number in the AS-path to be prepended"; } } augment "/rpol:routing-policy/rpol:defined-sets/rpol:neighbor-sets/rpol:neighbor-set" { description "alternative for the existing neighbor element"; leaf-list neighbor-info { description "neighbor ip address or prefix"; type inet:ip-address; } } augment "/rpol:routing-policy/rpol:defined-sets/" + "bgp-pol:bgp-defined-sets/bgp-pol:community-sets/bgp-pol:community-set" { description "alternative for the existing community-member"; leaf-list community { description "community set member"; type string; } } augment "/rpol:routing-policy/rpol:defined-sets/" + "bgp-pol:bgp-defined-sets/bgp-pol:ext-community-sets/bgp-pol:ext-community-set" { description "alternative for the existing ext-community-member"; leaf-list ext-community { type string; description "extended community set member"; } } augment "/rpol:routing-policy/rpol:defined-sets/" + "bgp-pol:bgp-defined-sets" { container large-community-sets { list large-community-set { key "large-community-set-name"; leaf large-community-set-name { type string; } leaf-list large-community { type string; description "extended community set member"; } } } } augment "/rpol:routing-policy/rpol:defined-sets/" + "bgp-pol:bgp-defined-sets/bgp-pol:as-path-sets/bgp-pol:as-path-set" { description "alternative for the existing as-path-set-member"; leaf-list as-path { type string; description "AS path expression"; } } augment "/rpol:routing-policy/rpol:policy-definitions/" + "rpol:policy-definition/rpol:statements/rpol:statement/" + "rpol:conditions/bgp-pol:bgp-conditions" { description "additional rpki condition"; uses gobgp-rpki-validation-result; } deviation "/rpol:routing-policy/rpol:policy-definitions/" + "rpol:policy-definition/rpol:statements/rpol:statement/" + "rpol:conditions/bgp-pol:bgp-conditions/bgp-pol:route-type" { description "alternative route-type condition"; deviate replace { type enumeration { enum NONE { description "route type is none"; } enum INTERNAL { description "route type is internal"; } enum EXTERNAL { description "route type is external"; } enum LOCAL { description "route type is local"; } } } } augment "/rpol:routing-policy/rpol:policy-definitions/" + "rpol:policy-definition/rpol:statements/rpol:statement/" + "rpol:conditions/bgp-pol:bgp-conditions" { container match-large-community-set { leaf large-community-set { type string; } uses rpol:match-set-options-group; } } augment "/rpol:routing-policy/rpol:policy-definitions/" + "rpol:policy-definition/rpol:statements/rpol:statement/" + "rpol:actions/bgp-pol:bgp-actions" { container set-large-community { container set-large-community-method { leaf-list communities { type string; } } leaf options { type bgp-pol:bgp-set-community-option-type; } } } augment "/bgp:bgp" { description "additional rpki configuration and state"; uses gobgp-rpki-servers; } augment "/bgp:bgp" { description "additional bmp configuration"; uses gobgp-bmp-servers; } typedef mrt-type { type enumeration { enum UPDATES { description "RPKI disabled"; } enum TABLE { description "If the origin AS, prefix, maximum prefix length is exist in the range of ROA"; } } } grouping gobgp-mrt-set { container config { leaf dump-type { type mrt-type; } leaf file-name { type string; description "Configures a file name to be written"; } leaf table-name { type string; description "specify the table name with route server setup"; } leaf dump-interval { type uint64; } leaf rotation-interval { type uint64; } } } grouping gobgp-mrt { description "additional mrt configuration"; container mrt-dump { list mrt { key "file-name"; leaf file-name { type leafref { path "../config/file-name"; } } uses gobgp-mrt-set; } } } augment "/bgp:bgp" { description "additional mrt configuration"; uses gobgp-mrt; } grouping zebra-config { leaf enabled { type boolean; description "Configure enabling to connect to zebra."; } leaf url { type string; description "Configure url for zebra."; } leaf-list redistribute-route-type { type identityref { base ptypes:install-protocol-type; } } leaf version { type uint8; description "Configure version of zebra protocol. Default is 2. Supported up to 3."; } leaf nexthop-trigger-enable { type boolean; } leaf nexthop-trigger-delay { type uint8; } } grouping zebra-set { container zebra { container config { uses zebra-config; } container state { uses zebra-config; } } } augment "/bgp:bgp" { uses zebra-set; } grouping collector-config { leaf url { type string; } leaf db-name { type string; } leaf table-dump-interval { type uint64; } } grouping collector-set { container collector { container config { uses collector-config; } container state { uses collector-config; } } } augment "/bgp:bgp" { uses collector-set; } grouping listen-config { leaf port { type int32; } leaf-list local-address { type string; } } augment "/bgp:bgp/bgp:global/bgp:config" { uses listen-config; } augment "/bgp:bgp/bgp:global/bgp:state" { uses listen-config; } grouping dynamic-neighbors { container dynamic-neighbors { list dynamic-neighbor { key "prefix"; leaf prefix { type leafref { path "../config/prefix"; } } container config { uses bgp-global-dynamic-neighbor-config; } container state { config false; uses bgp-global-dynamic-neighbor-config; } } } } grouping bgp-global-dynamic-neighbor-config { description "A dynamic neighbor belongs to a peer group. This configuration structure was taken from the latest openconfig."; leaf prefix { type string; } leaf peer-group { type string; } } augment "/bgp:bgp" { uses dynamic-neighbors; } grouping disable-best-path-selection-config { leaf disable-best-path-selection { type boolean; description "Disables best path selection process."; } } augment "/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:config" { uses disable-best-path-selection-config; } augment "/bgp:bgp/bgp:global/bgp:route-selection-options/bgp:state" { uses disable-best-path-selection-config; } augment "/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi" { uses bgp-mp:all-afi-safi-common; } grouping route-target-membership-config { leaf deferral-time { type uint16; } } grouping afi-safi-state { leaf family { description "Address family value of AFI-SAFI pair translated from afi-safi-name."; type route-family; } } augment "/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi/bgp:state" { uses afi-safi-state; } augment "/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi" { container route-target-membership { container config { uses route-target-membership-config; } container state { uses route-target-membership-config; } } uses long-lived-graceful-restart; } augment "/bgp:bgp/bgp:global/bgp:afi-safis/bgp:afi-safi" { container add-paths { description "add-paths configuration options related to a particular AFI-SAFI."; container config { uses bgp:bgp-neighbor-add-paths_config; } container state { uses bgp:bgp-neighbor-add-paths_config; } } } } gobgp-1.29/tools/route-server/000077500000000000000000000000001324612745600163575ustar00rootroot00000000000000gobgp-1.29/tools/route-server/README.md000066400000000000000000000047361324612745600176500ustar00rootroot00000000000000Route Server testing env ======================== Preparation ----------- Set up Ubuntu 14.04 Server Edition Virtual Machine environment. We tested this with Fusion on Mac OS X and VirtualBox on Windows 8. Setup ----- Open a terminal and execute the following commands: ``` % sudo apt-get install -y --force-yes wget % wget https://raw.githubusercontent.com/osrg/gobgp/master/tools/route-server/route-server-docker.sh % chmod +x route-server-docker.sh % ./route-server-docker.sh install ``` All necessary software will be installed. You can find all configuration files (for Quagga and gobgp) at /usr/local/gobgp. Let's make sure: ``` fujita@ubuntu:~$ find /usr/local/gobgp|sort /usr/local/gobgp /usr/local/gobgp/gobgpd.conf /usr/local/gobgp/q1 /usr/local/gobgp/q1/bgpd.conf /usr/local/gobgp/q2 /usr/local/gobgp/q2/bgpd.conf /usr/local/gobgp/q3 /usr/local/gobgp/q3/bgpd.conf /usr/local/gobgp/q4 /usr/local/gobgp/q4/bgpd.conf /usr/local/gobgp/q5 /usr/local/gobgp/q5/bgpd.conf /usr/local/gobgp/q6 /usr/local/gobgp/q6/bgpd.conf /usr/local/gobgp/q7 /usr/local/gobgp/q7/bgpd.conf /usr/local/gobgp/q8 /usr/local/gobgp/q8/bgpd.conf ``` Before going to playing with this environment, you need to log out and log in again. Start ----- ``` % ./route-server-docker.sh start ``` Eight containers for Quagga and one container for gobgp started. Now ready to start gobgp so let's go into the container for gobgp: ``` % docker exec -it gobgp bash ``` You are supposed to get a console like the following: ``` root@ec36881de971:~# ``` ``` root@e7cb66415e2f:~# go run gobgp/bgpd.go -f /mnt/gobgpd.conf INFO[0000] Peer 10.0.0.1 is added INFO[0000] Peer 10.0.0.2 is added INFO[0000] Peer 10.0.0.3 is added INFO[0000] Peer 10.0.0.4 is added INFO[0000] Peer 10.0.0.5 is added INFO[0000] Peer 10.0.0.6 is added INFO[0000] Peer 10.0.0.7 is added INFO[0000] Peer 10.0.0.8 is added ``` After some time, you'll see more messages on the console (it means Quagga bgpd connected to gobgp). On a different console, let's check the status of Quagga. ``` % docker exec -it q1 bash ``` You can do something like: ``` root@a40ac8058ca7:/# telnet localhost bgpd ``` btw, the password is "zebra". The names of Quagga containers are q1, q2, q3, q4, q5, q6, q7, and q8 respectively. You can execute bash on any container with 'docker exec' command. Stop ---- The following command stops and cleans up everything. ``` % ./route-server-docker.sh stop ``` You can safely excute this command whenever something goes wrong and start again. gobgp-1.29/tools/route-server/quagga-rsconfig.go000066400000000000000000000053401324612745600217650ustar00rootroot00000000000000package main import ( "bytes" "fmt" "io/ioutil" "log" "net" "os" "path/filepath" "github.com/BurntSushi/toml" "github.com/jessevdk/go-flags" "github.com/osrg/gobgp/config" ) type QuaggaConfig struct { id int config *config.Neighbor gobgpConfig *config.Global serverIP net.IP } func NewQuaggaConfig(id int, gConfig *config.Global, myConfig *config.Neighbor, server net.IP) *QuaggaConfig { return &QuaggaConfig{ id: id, config: myConfig, gobgpConfig: gConfig, serverIP: server, } } func (qt *QuaggaConfig) Config() *bytes.Buffer { buf := bytes.NewBuffer(nil) buf.WriteString("hostname bgpd\n") buf.WriteString("password zebra\n") buf.WriteString(fmt.Sprintf("router bgp %d\n", qt.config.Config.PeerAs)) buf.WriteString(fmt.Sprintf("bgp router-id 192.168.0.%d\n", qt.id)) buf.WriteString(fmt.Sprintf("network 192.168.%d.0/24\n", qt.id)) buf.WriteString(fmt.Sprintf("neighbor %s remote-as %d\n", qt.serverIP, qt.gobgpConfig.Config.As)) buf.WriteString(fmt.Sprintf("neighbor %s password %s\n", qt.serverIP, qt.config.Config.AuthPassword)) buf.WriteString("log file /var/log/quagga/bgpd.log") return buf } func create_config_files(nr int, outputDir string) { quaggaConfigList := make([]*QuaggaConfig, 0) gobgpConf := config.Bgp{} gobgpConf.Global.Config.As = 65000 gobgpConf.Global.Config.RouterId = "192.168.255.1" for i := 1; i < nr+1; i++ { c := config.Neighbor{} c.Config.PeerAs = 65000 + uint32(i) c.Config.NeighborAddress = fmt.Sprintf("10.0.0.%d", i) c.Config.AuthPassword = fmt.Sprintf("password%d", i) gobgpConf.Neighbors = append(gobgpConf.Neighbors, c) q := NewQuaggaConfig(i, &gobgpConf.Global, &c, net.ParseIP("10.0.255.1")) quaggaConfigList = append(quaggaConfigList, q) os.Mkdir(fmt.Sprintf("%s/q%d", outputDir, i), 0755) err := ioutil.WriteFile(fmt.Sprintf("%s/q%d/bgpd.conf", outputDir, i), q.Config().Bytes(), 0644) if err != nil { log.Fatal(err) } } var buffer bytes.Buffer encoder := toml.NewEncoder(&buffer) encoder.Encode(gobgpConf) err := ioutil.WriteFile(fmt.Sprintf("%s/gobgpd.conf", outputDir), buffer.Bytes(), 0644) if err != nil { log.Fatal(err) } } func main() { var opts struct { ClientNumber int `short:"n" long:"client-number" description:"specfying the number of clients" default:"8"` OutputDir string `short:"c" long:"output" description:"specifying the output directory"` } parser := flags.NewParser(&opts, flags.Default) _, err := parser.Parse() if err != nil { os.Exit(1) } if opts.OutputDir == "" { opts.OutputDir, _ = filepath.Abs(".") } else { if _, err := os.Stat(opts.OutputDir); os.IsNotExist(err) { os.Mkdir(opts.OutputDir, 0755) } } create_config_files(opts.ClientNumber, opts.OutputDir) } gobgp-1.29/tools/route-server/route-server-docker.sh000077500000000000000000000051311324612745600226250ustar00rootroot00000000000000#!/bin/sh NR_PEERS=8 BRIDGE_NAME=br0 CONFIG_DIR=/usr/local/gobgp GOBGP_DOCKER_NAME=gobgp USE_HOST=0 check_user() { if [ `whoami` = "root" ]; then echo "Super user cannot execute! Please execute as non super user" exit 2 fi } run_quagga() { local docker_name=q$1 docker run --privileged=true -v $CONFIG_DIR/$docker_name:/etc/quagga --name $docker_name -id osrg/quagga sudo pipework $BRIDGE_NAME $docker_name 10.0.0.$1/16 } stop_quagga() { local docker_name=q$1 docker rm -f $docker_name } delete_bridge() { local name=$1 local sysfs_name=/sys/class/net/$name if [ -e $sysfs_name ]; then sudo ifconfig $name down sudo brctl delbr $name fi } while getopts c:n:u OPT do case $OPT in c) CONFIG_DIR="$OPTARG" ;; n) NR_PEERS="$OPTARG" ;; u) USE_HOST=1 ;; *) echo "Unknown option" exit 1 ;; esac done shift $((OPTIND - 1)) case "$1" in start) i=1 while [ $i -le $NR_PEERS ] do run_quagga $i i=$(( i+1 )) done if [ $USE_HOST -eq 1 ]; then sudo ip addr add 10.0.255.1/16 dev $BRIDGE_NAME else docker run --privileged=true -v $CONFIG_DIR:/mnt -d --name $GOBGP_DOCKER_NAME -id osrg/gobgp sudo pipework $BRIDGE_NAME $GOBGP_DOCKER_NAME 10.0.255.1/16 fi ;; stop) i=1 while [ $i -le $NR_PEERS ] do stop_quagga $i i=$(( i+1 )) done delete_bridge $BRIDGE_NAME docker rm -f $GOBGP_DOCKER_NAME ;; install) sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 sudo sh -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list" sudo apt-get update sudo apt-get install -y --force-yes lxc-docker-1.3.2 sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker sudo gpasswd -a `whoami` docker sudo apt-get install -y --force-yes emacs23-nox sudo apt-get install -y --force-yes wireshark sudo apt-get install -y --force-yes iputils-arping sudo apt-get install -y --force-yes bridge-utils sudo apt-get install -y --force-yes tcpdump sudo apt-get install -y --force-yes lv sudo wget https://raw.github.com/jpetazzo/pipework/master/pipework -O /usr/local/bin/pipework sudo chmod 755 /usr/local/bin/pipework sudo docker pull osrg/quagga sudo docker pull osrg/gobgp sudo mkdir /usr/local/gobgp sudo docker run --privileged=true -v /usr/local/gobgp:/mnt --name gobgp --rm osrg/gobgp go run /root/gobgp/tools/route-server/quagga-rsconfig.go -c /mnt ;; *) echo $1 echo "Usage: root-server-docker {start|stop|install}" exit 2 ;; esac gobgp-1.29/tools/spell-check/000077500000000000000000000000001324612745600161075ustar00rootroot00000000000000gobgp-1.29/tools/spell-check/README.md000066400000000000000000000012321324612745600173640ustar00rootroot00000000000000# What's this? This script is a spell checker for GoBGP's source codes. ## Requirements - [scspell3k](https://pypi.python.org/pypi/scspell3k): Spell checker for source code written in Python. ```bash pip install scspell3k ``` ## How to use Just run `scspell.sh ` ```bash bash tools/spell-check/scspell.sh ``` Example of output: ```bash # Format: # path/to/file.go: xxx/xxx.go: 'mispeld' not found in dictionary (from token 'Mispeld') ``` ## Adding new words to dictionary If you want to add new words to the dictionary for this spell checker, please insert words into `tools/spell-check/dictionary.txt` or `tools/spell-check/ignore.txt`. gobgp-1.29/tools/spell-check/dictionary.txt000066400000000000000000000004721324612745600210200ustar00rootroot00000000000000NATURAL: afisafi aggregator aigp aspath bgpd cidr distinguisher dscp ebgp ethernet evpn flowspec gobgp gobgpd grpc ibgp ipproto keepalive keepalives llgr localhost mpls multicast multihop multipath mututally nexthop nexthops nlri pmsi prepend reachability rpki sadb safi subcode syscall syslog unicast uptime zapi gobgp-1.29/tools/spell-check/ignore.txt000066400000000000000000000005211324612745600201310ustar00rootroot00000000000000# Words should be ignored and too specific for adding to dictionary.txt # Hexadecimal number ffff FFFF # Function name Debugf epoll Errorf Fatalf getpid getsockopt Infof itoa setsockopt Sprintf strconv # Implementation specific dumpv2 elems etag Extcomms fdinfo ifindex ifname linktype macadv Rcvd Receivedv4 Receivedv6 softreset stmt gobgp-1.29/tools/spell-check/scspell.sh000077500000000000000000000034401324612745600201140ustar00rootroot00000000000000#!/usr/bin/env bash SCRIPT_DIR=`dirname $0` GOBGP=${SCRIPT_DIR}/../../ FUNCS=( Debug Debugf Debugln Error Errorf Errorln Fatal Fatalf Fatalln Fprint Fprintf Fprintln Info Infof Infoln Panic Panicf Panicln Print Printf Println Sprint Sprintf Sprintln Warn Warnf Warning Warningf Warningln Warnln ) CHECK_LOG=/tmp/gobgp/scspell.log mkdir -p `dirname ${CHECK_LOG}` rm -f ${CHECK_LOG} # Clean up previous output # Do find *.go files except under vendor directory for FILE in `find ${GOBGP} -type d -name vendor -prune -o -type f -name *.go | sort` do TMP_FILE=${FILE/${GOBGP}//tmp/gobgp/} mkdir -p `dirname ${TMP_FILE}` rm -f ${TMP_FILE} # Clean up previous output for FUNC in ${FUNCS[@]} do # Do grep cases like: # fmt.Print("...") # or # fmt.Print( # "...") grep ${FUNC}'("' ${FILE} | grep -o '".*"' >> ${TMP_FILE} grep ${FUNC}'($' -A 1 ${FILE} | grep -o '".*"' >> ${TMP_FILE} done # If any case found if [ -s ${TMP_FILE} ] then # Apply exclude rules defined in ignore.txt for WORD in `grep -v -e '^\s*#' -e '^$' ${SCRIPT_DIR}/ignore.txt` do sed -i "s/${WORD}//g" ${TMP_FILE} done # Do scspell with dictionary.txt and reformat messages scspell \ --use-builtin-base-dict \ --override-dictionary ${SCRIPT_DIR}/dictionary.txt \ --report-only \ ${TMP_FILE} 2>&1 \ | tee -a ${CHECK_LOG} \ | sed "s/\/tmp\/gobgp\///" | cut -d ':' -f -1,3- fi #rm ${TMP_FILE} done RESULT=0 # If any output of scspell exists if [ -s ${CHECK_LOG} ] then echo "---" echo "See ${CHECK_LOG} for more details." # Set return code as error RESULT=1 fi #rm -f ${CHECK_LOG} exit ${RESULT} gobgp-1.29/zebra/000077500000000000000000000000001324612745600136605ustar00rootroot00000000000000gobgp-1.29/zebra/afi_string.go000066400000000000000000000005341324612745600163360ustar00rootroot00000000000000// Code generated by "stringer -type=AFI"; DO NOT EDIT. package zebra import "fmt" const _AFI_name = "AFI_IPAFI_IP6AFI_ETHERAFI_MAX" var _AFI_index = [...]uint8{0, 6, 13, 22, 29} func (i AFI) String() string { i -= 1 if i >= AFI(len(_AFI_index)-1) { return fmt.Sprintf("AFI(%d)", i+1) } return _AFI_name[_AFI_index[i]:_AFI_index[i+1]] } gobgp-1.29/zebra/api_type_string.go000066400000000000000000000033431324612745600174120ustar00rootroot00000000000000// Code generated by "stringer -type=API_TYPE"; DO NOT EDIT. package zebra import "fmt" const _API_TYPE_name = "FRR_INTERFACE_ADDINTERFACE_ADDINTERFACE_DELETEINTERFACE_ADDRESS_ADDINTERFACE_ADDRESS_DELETEINTERFACE_UPINTERFACE_DOWNIPV4_ROUTE_ADDIPV4_ROUTE_DELETEIPV6_ROUTE_ADDIPV6_ROUTE_DELETEREDISTRIBUTE_ADDREDISTRIBUTE_DELETEREDISTRIBUTE_DEFAULT_ADDREDISTRIBUTE_DEFAULT_DELETEIPV4_NEXTHOP_LOOKUPIPV6_NEXTHOP_LOOKUPIPV4_IMPORT_LOOKUPIPV6_IMPORT_LOOKUPINTERFACE_RENAMEROUTER_ID_ADDROUTER_ID_DELETEROUTER_ID_UPDATEHELLOIPV4_NEXTHOP_LOOKUP_MRIBVRF_UNREGISTERINTERFACE_LINK_PARAMSNEXTHOP_REGISTERNEXTHOP_UNREGISTERNEXTHOP_UPDATEMESSAGE_MAXFRR_BFD_DEST_REPLAYFRR_REDISTRIBUTE_IPV4_ADDFRR_REDISTRIBUTE_IPV4_DELFRR_REDISTRIBUTE_IPV6_ADDFRR_REDISTRIBUTE_IPV6_DELFRR_VRF_UNREGISTERFRR_VRF_ADDFRR_VRF_DELETEFRR_INTERFACE_VRF_UPDATEFRR_BFD_CLIENT_REGISTERFRR_INTERFACE_ENABLE_RADVFRR_INTERFACE_DISABLE_RADVFRR_IPV4_NEXTHOP_LOOKUP_MRIBFRR_INTERFACE_LINK_PARAMSFRR_MPLS_LABELS_ADDFRR_MPLS_LABELS_DELETEFRR_IPV4_NEXTHOP_ADDFRR_IPV4_NEXTHOP_DELETEFRR_IPV6_NEXTHOP_ADDFRR_IPV6_NEXTHOP_DELETEFRR_IPMR_ROUTE_STATSFRR_LABEL_MANAGER_CONNECTFRR_GET_LABEL_CHUNKFRR_RELEASE_LABEL_CHUNKFRR_PW_ADDFRR_PW_DELETEFRR_PW_SETFRR_PW_UNSETFRR_PW_STATUS_UPDATE" var _API_TYPE_index = [...]uint16{0, 17, 30, 46, 67, 91, 103, 117, 131, 148, 162, 179, 195, 214, 238, 265, 284, 303, 321, 339, 355, 368, 384, 400, 405, 429, 443, 464, 480, 498, 512, 523, 542, 567, 592, 617, 642, 660, 671, 685, 709, 732, 757, 783, 811, 836, 855, 877, 897, 920, 940, 963, 983, 1008, 1027, 1050, 1060, 1073, 1083, 1095, 1115} func (i API_TYPE) String() string { if i >= API_TYPE(len(_API_TYPE_index)-1) { return fmt.Sprintf("API_TYPE(%d)", i) } return _API_TYPE_name[_API_TYPE_index[i]:_API_TYPE_index[i+1]] } gobgp-1.29/zebra/link_type_string.go000066400000000000000000000025701324612745600175770ustar00rootroot00000000000000// Code generated by "stringer -type=LINK_TYPE"; DO NOT EDIT. package zebra import "fmt" const _LINK_TYPE_name = "LINK_TYPE_UNKNOWNLINK_TYPE_ETHERLINK_TYPE_EETHERLINK_TYPE_AX25LINK_TYPE_PRONETLINK_TYPE_IEEE802LINK_TYPE_ARCNETLINK_TYPE_APPLETLKLINK_TYPE_DLCILINK_TYPE_ATMLINK_TYPE_METRICOMLINK_TYPE_IEEE1394LINK_TYPE_EUI64LINK_TYPE_INFINIBANDLINK_TYPE_SLIPLINK_TYPE_CSLIPLINK_TYPE_SLIP6LINK_TYPE_CSLIP6LINK_TYPE_RSRVDLINK_TYPE_ADAPTLINK_TYPE_ROSELINK_TYPE_X25LINK_TYPE_PPPLINK_TYPE_CHDLCLINK_TYPE_LAPBLINK_TYPE_RAWHDLCLINK_TYPE_IPIPLINK_TYPE_IPIP6LINK_TYPE_FRADLINK_TYPE_SKIPLINK_TYPE_LOOPBACKLINK_TYPE_LOCALTLKLINK_TYPE_FDDILINK_TYPE_SITLINK_TYPE_IPDDPLINK_TYPE_IPGRELINK_TYPE_IP6GRELINK_TYPE_PIMREGLINK_TYPE_HIPPILINK_TYPE_ECONETLINK_TYPE_IRDALINK_TYPE_FCPPLINK_TYPE_FCALLINK_TYPE_FCPLLINK_TYPE_FCFABRICLINK_TYPE_IEEE802_TRLINK_TYPE_IEEE80211LINK_TYPE_IEEE80211_RADIOTAPLINK_TYPE_IEEE802154LINK_TYPE_IEEE802154_PHY" var _LINK_TYPE_index = [...]uint16{0, 17, 32, 48, 62, 78, 95, 111, 129, 143, 156, 174, 192, 207, 227, 241, 256, 271, 287, 302, 317, 331, 344, 357, 372, 386, 403, 417, 432, 446, 460, 478, 496, 510, 523, 538, 553, 569, 585, 600, 616, 630, 644, 658, 672, 690, 710, 729, 757, 777, 801} func (i LINK_TYPE) String() string { if i >= LINK_TYPE(len(_LINK_TYPE_index)-1) { return fmt.Sprintf("LINK_TYPE(%d)", i) } return _LINK_TYPE_name[_LINK_TYPE_index[i]:_LINK_TYPE_index[i+1]] } gobgp-1.29/zebra/nexthop_flag_string.go000066400000000000000000000011051324612745600202500ustar00rootroot00000000000000// Code generated by "stringer -type=NEXTHOP_FLAG"; DO NOT EDIT. package zebra import "fmt" const _NEXTHOP_FLAG_name = "NEXTHOP_IFINDEXNEXTHOP_IFNAMENEXTHOP_IPV4NEXTHOP_IPV4_IFINDEXNEXTHOP_IPV4_IFNAMENEXTHOP_IPV6NEXTHOP_IPV6_IFINDEXNEXTHOP_IPV6_IFNAMENEXTHOP_BLACKHOLE" var _NEXTHOP_FLAG_index = [...]uint8{0, 15, 29, 41, 61, 80, 92, 112, 131, 148} func (i NEXTHOP_FLAG) String() string { i -= 1 if i >= NEXTHOP_FLAG(len(_NEXTHOP_FLAG_index)-1) { return fmt.Sprintf("NEXTHOP_FLAG(%d)", i+1) } return _NEXTHOP_FLAG_name[_NEXTHOP_FLAG_index[i]:_NEXTHOP_FLAG_index[i+1]] } gobgp-1.29/zebra/ptm_enable_string.go000066400000000000000000000006441324612745600177070ustar00rootroot00000000000000// Code generated by "stringer -type=PTM_ENABLE"; DO NOT EDIT. package zebra import "fmt" const _PTM_ENABLE_name = "PTM_ENABLE_OFFPTM_ENABLE_ONPTM_ENABLE_UNSPEC" var _PTM_ENABLE_index = [...]uint8{0, 14, 27, 44} func (i PTM_ENABLE) String() string { if i >= PTM_ENABLE(len(_PTM_ENABLE_index)-1) { return fmt.Sprintf("PTM_ENABLE(%d)", i) } return _PTM_ENABLE_name[_PTM_ENABLE_index[i]:_PTM_ENABLE_index[i+1]] } gobgp-1.29/zebra/ptm_status_string.go000066400000000000000000000006461324612745600200060ustar00rootroot00000000000000// Code generated by "stringer -type=PTM_STATUS"; DO NOT EDIT. package zebra import "fmt" const _PTM_STATUS_name = "PTM_STATUS_DOWNPTM_STATUS_UPPTM_STATUS_UNKNOWN" var _PTM_STATUS_index = [...]uint8{0, 15, 28, 46} func (i PTM_STATUS) String() string { if i >= PTM_STATUS(len(_PTM_STATUS_index)-1) { return fmt.Sprintf("PTM_STATUS(%d)", i) } return _PTM_STATUS_name[_PTM_STATUS_index[i]:_PTM_STATUS_index[i+1]] } gobgp-1.29/zebra/route_type_string.go000066400000000000000000000013571324612745600200020ustar00rootroot00000000000000// Code generated by "stringer -type=ROUTE_TYPE"; DO NOT EDIT. package zebra import "fmt" const _ROUTE_TYPE_name = "ROUTE_SYSTEMROUTE_KERNELROUTE_CONNECTROUTE_STATICROUTE_RIPROUTE_RIPNGROUTE_OSPFROUTE_OSPF6ROUTE_ISISROUTE_BGPROUTE_PIMROUTE_HSLSROUTE_OLSRROUTE_BABELROUTE_MAXFRR_ROUTE_VNCFRR_ROUTE_VNC_DIRECTFRR_ROUTE_VNC_DIRECT_RHFRR_ROUTE_BGP_DIRECTFRR_ROUTE_BGP_DIRECT_EXTFRR_ROUTE_ALLFRR_ROUTE_MAX" var _ROUTE_TYPE_index = [...]uint16{0, 12, 24, 37, 49, 58, 69, 79, 90, 100, 109, 118, 128, 138, 149, 158, 171, 191, 214, 234, 258, 271, 284} func (i ROUTE_TYPE) String() string { if i >= ROUTE_TYPE(len(_ROUTE_TYPE_index)-1) { return fmt.Sprintf("ROUTE_TYPE(%d)", i) } return _ROUTE_TYPE_name[_ROUTE_TYPE_index[i]:_ROUTE_TYPE_index[i+1]] } gobgp-1.29/zebra/safi_string.go000066400000000000000000000006141324612745600165200ustar00rootroot00000000000000// Code generated by "stringer -type=SAFI"; DO NOT EDIT. package zebra import "fmt" const _SAFI_name = "SAFI_UNICASTSAFI_MULTICASTSAFI_RESERVED_3SAFI_MPLS_VPNSAFI_MAX" var _SAFI_index = [...]uint8{0, 12, 26, 41, 54, 62} func (i SAFI) String() string { i -= 1 if i >= SAFI(len(_SAFI_index)-1) { return fmt.Sprintf("SAFI(%d)", i+1) } return _SAFI_name[_SAFI_index[i]:_SAFI_index[i+1]] } gobgp-1.29/zebra/zapi.go000066400000000000000000001322131324612745600151540ustar00rootroot00000000000000// Copyright (C) 2014, 2015 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package zebra import ( "encoding/binary" "fmt" "io" "net" "strings" "syscall" log "github.com/sirupsen/logrus" "github.com/osrg/gobgp/packet/bgp" ) const ( HEADER_MARKER = 255 FRR_HEADER_MARKER = 254 INTERFACE_NAMSIZ = 20 ) // Internal Interface Status. type INTERFACE_STATUS uint8 const ( INTERFACE_ACTIVE INTERFACE_STATUS = 0x01 INTERFACE_SUB INTERFACE_STATUS = 0x02 INTERFACE_LINKDETECTION INTERFACE_STATUS = 0x04 INTERFACE_VRF_LOOPBACK INTERFACE_STATUS = 0x08 ) // Interface Link Layer Types. //go:generate stringer -type=LINK_TYPE type LINK_TYPE uint32 const ( LINK_TYPE_UNKNOWN LINK_TYPE = iota LINK_TYPE_ETHER LINK_TYPE_EETHER LINK_TYPE_AX25 LINK_TYPE_PRONET LINK_TYPE_IEEE802 LINK_TYPE_ARCNET LINK_TYPE_APPLETLK LINK_TYPE_DLCI LINK_TYPE_ATM LINK_TYPE_METRICOM LINK_TYPE_IEEE1394 LINK_TYPE_EUI64 LINK_TYPE_INFINIBAND LINK_TYPE_SLIP LINK_TYPE_CSLIP LINK_TYPE_SLIP6 LINK_TYPE_CSLIP6 LINK_TYPE_RSRVD LINK_TYPE_ADAPT LINK_TYPE_ROSE LINK_TYPE_X25 LINK_TYPE_PPP LINK_TYPE_CHDLC LINK_TYPE_LAPB LINK_TYPE_RAWHDLC LINK_TYPE_IPIP LINK_TYPE_IPIP6 LINK_TYPE_FRAD LINK_TYPE_SKIP LINK_TYPE_LOOPBACK LINK_TYPE_LOCALTLK LINK_TYPE_FDDI LINK_TYPE_SIT LINK_TYPE_IPDDP LINK_TYPE_IPGRE LINK_TYPE_IP6GRE LINK_TYPE_PIMREG LINK_TYPE_HIPPI LINK_TYPE_ECONET LINK_TYPE_IRDA LINK_TYPE_FCPP LINK_TYPE_FCAL LINK_TYPE_FCPL LINK_TYPE_FCFABRIC LINK_TYPE_IEEE802_TR LINK_TYPE_IEEE80211 LINK_TYPE_IEEE80211_RADIOTAP LINK_TYPE_IEEE802154 LINK_TYPE_IEEE802154_PHY ) const VRF_DEFAULT = 0 func HeaderSize(version uint8) uint16 { switch version { case 3, 4: return 8 default: return 6 } } func (t INTERFACE_STATUS) String() string { ss := make([]string, 0, 3) if t&INTERFACE_ACTIVE > 0 { ss = append(ss, "ACTIVE") } if t&INTERFACE_SUB > 0 { ss = append(ss, "SUB") } if t&INTERFACE_LINKDETECTION > 0 { ss = append(ss, "LINKDETECTION") } if t&INTERFACE_VRF_LOOPBACK > 0 { ss = append(ss, "VRF_LOOPBACK") } return strings.Join(ss, "|") } // Interface Connected Address Flags type INTERFACE_ADDRESS_FLAG uint8 const ( INTERFACE_ADDRESS_SECONDARY INTERFACE_ADDRESS_FLAG = 0x01 INTERFACE_ADDRESS_PEER INTERFACE_ADDRESS_FLAG = 0x02 INTERFACE_ADDRESS_UNNUMBERED INTERFACE_ADDRESS_FLAG = 0x04 ) func (t INTERFACE_ADDRESS_FLAG) String() string { ss := make([]string, 0, 3) if t&INTERFACE_ADDRESS_SECONDARY > 0 { ss = append(ss, "SECONDARY") } if t&INTERFACE_ADDRESS_PEER > 0 { ss = append(ss, "PEER") } if t&INTERFACE_ADDRESS_UNNUMBERED > 0 { ss = append(ss, "UNNUMBERED") } return strings.Join(ss, "|") } // Address Family Identifier. //go:generate stringer -type=AFI type AFI uint8 const ( AFI_IP AFI = 1 AFI_IP6 AFI = 2 AFI_ETHER AFI = 3 AFI_MAX AFI = 4 ) // Subsequent Address Family Identifier. //go:generate stringer -type=SAFI type SAFI uint8 const ( _ SAFI = iota SAFI_UNICAST SAFI_MULTICAST SAFI_RESERVED_3 SAFI_MPLS_VPN SAFI_MAX ) // API Types. //go:generate stringer -type=API_TYPE type API_TYPE uint16 // For Quagga. const ( _ API_TYPE = iota INTERFACE_ADD INTERFACE_DELETE INTERFACE_ADDRESS_ADD INTERFACE_ADDRESS_DELETE INTERFACE_UP INTERFACE_DOWN IPV4_ROUTE_ADD IPV4_ROUTE_DELETE IPV6_ROUTE_ADD IPV6_ROUTE_DELETE REDISTRIBUTE_ADD REDISTRIBUTE_DELETE REDISTRIBUTE_DEFAULT_ADD REDISTRIBUTE_DEFAULT_DELETE IPV4_NEXTHOP_LOOKUP IPV6_NEXTHOP_LOOKUP IPV4_IMPORT_LOOKUP IPV6_IMPORT_LOOKUP INTERFACE_RENAME ROUTER_ID_ADD ROUTER_ID_DELETE ROUTER_ID_UPDATE HELLO IPV4_NEXTHOP_LOOKUP_MRIB VRF_UNREGISTER INTERFACE_LINK_PARAMS NEXTHOP_REGISTER NEXTHOP_UNREGISTER NEXTHOP_UPDATE MESSAGE_MAX ) // For FRRouting. const ( FRR_INTERFACE_ADD API_TYPE = iota FRR_INTERFACE_DELETE FRR_INTERFACE_ADDRESS_ADD FRR_INTERFACE_ADDRESS_DELETE FRR_INTERFACE_UP FRR_INTERFACE_DOWN FRR_IPV4_ROUTE_ADD FRR_IPV4_ROUTE_DELETE FRR_IPV6_ROUTE_ADD FRR_IPV6_ROUTE_DELETE FRR_REDISTRIBUTE_ADD FRR_REDISTRIBUTE_DELETE FRR_REDISTRIBUTE_DEFAULT_ADD FRR_REDISTRIBUTE_DEFAULT_DELETE FRR_ROUTER_ID_ADD FRR_ROUTER_ID_DELETE FRR_ROUTER_ID_UPDATE FRR_HELLO FRR_NEXTHOP_REGISTER FRR_NEXTHOP_UNREGISTER FRR_NEXTHOP_UPDATE FRR_INTERFACE_NBR_ADDRESS_ADD FRR_INTERFACE_NBR_ADDRESS_DELETE FRR_INTERFACE_BFD_DEST_UPDATE FRR_IMPORT_ROUTE_REGISTER FRR_IMPORT_ROUTE_UNREGISTER FRR_IMPORT_CHECK_UPDATE FRR_IPV4_ROUTE_IPV6_NEXTHOP_ADD FRR_BFD_DEST_REGISTER FRR_BFD_DEST_DEREGISTER FRR_BFD_DEST_UPDATE FRR_BFD_DEST_REPLAY FRR_REDISTRIBUTE_IPV4_ADD FRR_REDISTRIBUTE_IPV4_DEL FRR_REDISTRIBUTE_IPV6_ADD FRR_REDISTRIBUTE_IPV6_DEL FRR_VRF_UNREGISTER FRR_VRF_ADD FRR_VRF_DELETE FRR_INTERFACE_VRF_UPDATE FRR_BFD_CLIENT_REGISTER FRR_INTERFACE_ENABLE_RADV FRR_INTERFACE_DISABLE_RADV FRR_IPV4_NEXTHOP_LOOKUP_MRIB FRR_INTERFACE_LINK_PARAMS FRR_MPLS_LABELS_ADD FRR_MPLS_LABELS_DELETE FRR_IPV4_NEXTHOP_ADD FRR_IPV4_NEXTHOP_DELETE FRR_IPV6_NEXTHOP_ADD FRR_IPV6_NEXTHOP_DELETE FRR_IPMR_ROUTE_STATS FRR_LABEL_MANAGER_CONNECT FRR_GET_LABEL_CHUNK FRR_RELEASE_LABEL_CHUNK FRR_PW_ADD FRR_PW_DELETE FRR_PW_SET FRR_PW_UNSET FRR_PW_STATUS_UPDATE ) // Route Types. //go:generate stringer -type=ROUTE_TYPE type ROUTE_TYPE uint8 // For Quagga. const ( ROUTE_SYSTEM ROUTE_TYPE = iota ROUTE_KERNEL ROUTE_CONNECT ROUTE_STATIC ROUTE_RIP ROUTE_RIPNG ROUTE_OSPF ROUTE_OSPF6 ROUTE_ISIS ROUTE_BGP ROUTE_PIM ROUTE_HSLS ROUTE_OLSR ROUTE_BABEL ROUTE_MAX ) // For FRRouting. const ( FRR_ROUTE_SYSTEM ROUTE_TYPE = iota FRR_ROUTE_KERNEL FRR_ROUTE_CONNECT FRR_ROUTE_STATIC FRR_ROUTE_RIP FRR_ROUTE_RIPNG FRR_ROUTE_OSPF FRR_ROUTE_OSPF6 FRR_ROUTE_ISIS FRR_ROUTE_BGP FRR_ROUTE_PIM FRR_ROUTE_HSLS FRR_ROUTE_OLSR FRR_ROUTE_TABLE FRR_ROUTE_LDP FRR_ROUTE_VNC FRR_ROUTE_VNC_DIRECT FRR_ROUTE_VNC_DIRECT_RH FRR_ROUTE_BGP_DIRECT FRR_ROUTE_BGP_DIRECT_EXT FRR_ROUTE_ALL FRR_ROUTE_MAX ) var routeTypeValueMap = map[string]ROUTE_TYPE{ "system": ROUTE_SYSTEM, "kernel": ROUTE_KERNEL, "connect": ROUTE_CONNECT, // hack for backyard compatibility "directly-connected": ROUTE_CONNECT, "static": ROUTE_STATIC, "rip": ROUTE_RIP, "ripng": ROUTE_RIPNG, "ospf": ROUTE_OSPF, "ospf3": ROUTE_OSPF6, "isis": ROUTE_ISIS, "bgp": ROUTE_BGP, "pim": ROUTE_PIM, "hsls": ROUTE_HSLS, "olsr": ROUTE_OLSR, "babel": ROUTE_BABEL, "table": FRR_ROUTE_TABLE, "ldp": FRR_ROUTE_LDP, "vnc": FRR_ROUTE_VNC, "vnc-direct": FRR_ROUTE_VNC_DIRECT, "vnc-direct-rh": FRR_ROUTE_VNC_DIRECT_RH, "bgp-direct": FRR_ROUTE_BGP_DIRECT, "bgp-direct-ext": FRR_ROUTE_BGP_DIRECT_EXT, "all": FRR_ROUTE_ALL, } func RouteTypeFromString(typ string) (ROUTE_TYPE, error) { t, ok := routeTypeValueMap[typ] if ok { return t, nil } return t, fmt.Errorf("unknown route type: %s", typ) } // API Message Flags. type MESSAGE_FLAG uint8 // For Quagga. const ( MESSAGE_NEXTHOP MESSAGE_FLAG = 0x01 MESSAGE_IFINDEX MESSAGE_FLAG = 0x02 MESSAGE_DISTANCE MESSAGE_FLAG = 0x04 MESSAGE_METRIC MESSAGE_FLAG = 0x08 MESSAGE_MTU MESSAGE_FLAG = 0x10 MESSAGE_TAG MESSAGE_FLAG = 0x20 ) func (t MESSAGE_FLAG) String() string { var ss []string if t&MESSAGE_NEXTHOP > 0 { ss = append(ss, "NEXTHOP") } if t&MESSAGE_IFINDEX > 0 { ss = append(ss, "IFINDEX") } if t&MESSAGE_DISTANCE > 0 { ss = append(ss, "DISTANCE") } if t&MESSAGE_METRIC > 0 { ss = append(ss, "METRIC") } if t&MESSAGE_MTU > 0 { ss = append(ss, "MTU") } if t&MESSAGE_TAG > 0 { ss = append(ss, "TAG") } return strings.Join(ss, "|") } // For FRRouting. const ( FRR_MESSAGE_NEXTHOP MESSAGE_FLAG = 0x01 FRR_MESSAGE_IFINDEX MESSAGE_FLAG = 0x02 FRR_MESSAGE_DISTANCE MESSAGE_FLAG = 0x04 FRR_MESSAGE_METRIC MESSAGE_FLAG = 0x08 FRR_MESSAGE_TAG MESSAGE_FLAG = 0x10 FRR_MESSAGE_MTU MESSAGE_FLAG = 0x20 FRR_MESSAGE_SRCPFX MESSAGE_FLAG = 0x40 ) // Message Flags type FLAG uint64 const ( FLAG_INTERNAL FLAG = 0x01 FLAG_SELFROUTE FLAG = 0x02 FLAG_BLACKHOLE FLAG = 0x04 FLAG_IBGP FLAG = 0x08 FLAG_SELECTED FLAG = 0x10 FLAG_CHANGED FLAG = 0x20 FLAG_STATIC FLAG = 0x40 FLAG_REJECT FLAG = 0x80 FLAG_SCOPE_LINK FLAG = 0x100 FLAG_FIB_OVERRIDE FLAG = 0x200 ) func (t FLAG) String() string { var ss []string if t&FLAG_INTERNAL > 0 { ss = append(ss, "FLAG_INTERNAL") } if t&FLAG_SELFROUTE > 0 { ss = append(ss, "FLAG_SELFROUTE") } if t&FLAG_BLACKHOLE > 0 { ss = append(ss, "FLAG_BLACKHOLE") } if t&FLAG_IBGP > 0 { ss = append(ss, "FLAG_IBGP") } if t&FLAG_SELECTED > 0 { ss = append(ss, "FLAG_SELECTED") } if t&FLAG_CHANGED > 0 { ss = append(ss, "FLAG_CHANGED") } if t&FLAG_STATIC > 0 { ss = append(ss, "FLAG_STATIC") } if t&FLAG_REJECT > 0 { ss = append(ss, "FLAG_REJECT") } if t&FLAG_SCOPE_LINK > 0 { ss = append(ss, "FLAG_SCOPE_LINK") } if t&FLAG_FIB_OVERRIDE > 0 { ss = append(ss, "FLAG_FIB_OVERRIDE") } return strings.Join(ss, "|") } // Nexthop Flags. //go:generate stringer -type=NEXTHOP_FLAG type NEXTHOP_FLAG uint8 // For Quagga. const ( _ NEXTHOP_FLAG = iota NEXTHOP_IFINDEX NEXTHOP_IFNAME NEXTHOP_IPV4 NEXTHOP_IPV4_IFINDEX NEXTHOP_IPV4_IFNAME NEXTHOP_IPV6 NEXTHOP_IPV6_IFINDEX NEXTHOP_IPV6_IFNAME NEXTHOP_BLACKHOLE ) // For FRRouting. const ( _ NEXTHOP_FLAG = iota FRR_NEXTHOP_IFINDEX FRR_NEXTHOP_IPV4 FRR_NEXTHOP_IPV4_IFINDEX FRR_NEXTHOP_IPV6 FRR_NEXTHOP_IPV6_IFINDEX FRR_NEXTHOP_BLACKHOLE ) // Interface PTM Enable Configuration. //go:generate stringer -type=PTM_ENABLE type PTM_ENABLE uint8 const ( PTM_ENABLE_OFF PTM_ENABLE = 0 PTM_ENABLE_ON PTM_ENABLE = 1 PTM_ENABLE_UNSPEC PTM_ENABLE = 2 ) // PTM Status. //go:generate stringer -type=PTM_STATUS type PTM_STATUS uint8 const ( PTM_STATUS_DOWN PTM_STATUS = 0 PTM_STATUS_UP PTM_STATUS = 1 PTM_STATUS_UNKNOWN PTM_STATUS = 2 ) type Client struct { outgoing chan *Message incoming chan *Message redistDefault ROUTE_TYPE conn net.Conn Version uint8 } func NewClient(network, address string, typ ROUTE_TYPE, version uint8) (*Client, error) { conn, err := net.Dial(network, address) if err != nil { return nil, err } outgoing := make(chan *Message) incoming := make(chan *Message, 64) if version < 2 { version = 2 } else if version > 4 { version = 4 } c := &Client{ outgoing: outgoing, incoming: incoming, redistDefault: typ, conn: conn, Version: version, } go func() { for { m, more := <-outgoing if more { b, err := m.Serialize() if err != nil { log.WithFields(log.Fields{ "Topic": "Zebra", }).Warnf("failed to serialize: %s", m) continue } _, err = conn.Write(b) if err != nil { log.WithFields(log.Fields{ "Topic": "Zebra", }).Errorf("failed to write: %s", err) close(outgoing) } } else { log.Debug("finish outgoing loop") return } } }() // Send HELLO/ROUTER_ID_ADD messages to negotiate the Zebra message version. c.SendHello() c.SendRouterIDAdd() receiveSingleMsg := func() (*Message, error) { headerBuf, err := readAll(conn, int(HeaderSize(version))) if err != nil { err = fmt.Errorf("failed to read header: %s", err) log.WithFields(log.Fields{ "Topic": "Zebra", }).Error(err) return nil, err } log.WithFields(log.Fields{ "Topic": "Zebra", }).Debugf("read header from zebra: %v", headerBuf) hd := &Header{} err = hd.DecodeFromBytes(headerBuf) if err != nil { err = fmt.Errorf("failed to decode header: %s", err) log.WithFields(log.Fields{ "Topic": "Zebra", }).Error(err) return nil, err } bodyBuf, err := readAll(conn, int(hd.Len-HeaderSize(version))) if err != nil { err = fmt.Errorf("failed to read body: %s", err) log.WithFields(log.Fields{ "Topic": "Zebra", }).Error(err) return nil, err } log.WithFields(log.Fields{ "Topic": "Zebra", }).Debugf("read body from zebra: %v", bodyBuf) m, err := ParseMessage(hd, bodyBuf) if err != nil { log.WithFields(log.Fields{ "Topic": "Zebra", }).Warnf("failed to parse message: %s", err) return nil, nil } return m, nil } // Try to receive the first message from Zebra. if m, err := receiveSingleMsg(); err != nil { c.Close() // Return error explicitly in order to retry connection. return nil, err } else if m != nil { incoming <- m } // Start receive loop only when the first message successfully received. go func() { for { if m, err := receiveSingleMsg(); err != nil { return } else if m != nil { incoming <- m } } }() return c, nil } func readAll(conn net.Conn, length int) ([]byte, error) { buf := make([]byte, length) _, err := io.ReadFull(conn, buf) return buf, err } func (c *Client) Receive() chan *Message { return c.incoming } func (c *Client) Send(m *Message) { defer func() { if err := recover(); err != nil { log.WithFields(log.Fields{ "Topic": "Zebra", }).Debugf("recovered: %s", err) } }() log.WithFields(log.Fields{ "Topic": "Zebra", "Header": m.Header, "Body": m.Body, }).Debug("send command to zebra") c.outgoing <- m } func (c *Client) SendCommand(command API_TYPE, vrfId uint16, body Body) error { var marker uint8 = HEADER_MARKER if c.Version >= 4 { marker = FRR_HEADER_MARKER } m := &Message{ Header: Header{ Len: HeaderSize(c.Version), Marker: marker, Version: c.Version, VrfId: vrfId, Command: command, }, Body: body, } c.Send(m) return nil } func (c *Client) SendHello() error { if c.redistDefault > 0 { command := HELLO body := &HelloBody{ RedistDefault: c.redistDefault, Instance: 0, } if c.Version >= 4 { command = FRR_HELLO } return c.SendCommand(command, VRF_DEFAULT, body) } return nil } func (c *Client) SendRouterIDAdd() error { command := ROUTER_ID_ADD if c.Version >= 4 { command = FRR_ROUTER_ID_ADD } return c.SendCommand(command, VRF_DEFAULT, nil) } func (c *Client) SendInterfaceAdd() error { command := INTERFACE_ADD if c.Version >= 4 { command = FRR_INTERFACE_ADD } return c.SendCommand(command, VRF_DEFAULT, nil) } func (c *Client) SendRedistribute(t ROUTE_TYPE, vrfId uint16) error { command := REDISTRIBUTE_ADD if c.redistDefault != t { bodies := make([]*RedistributeBody, 0) if c.Version <= 3 { bodies = append(bodies, &RedistributeBody{ Redist: t, }) } else { // version >= 4 command = FRR_REDISTRIBUTE_ADD for _, afi := range []AFI{AFI_IP, AFI_IP6} { bodies = append(bodies, &RedistributeBody{ Afi: afi, Redist: t, Instance: 0, }) } } for _, body := range bodies { return c.SendCommand(command, vrfId, body) } } return nil } func (c *Client) SendRedistributeDelete(t ROUTE_TYPE) error { if t < ROUTE_MAX { command := REDISTRIBUTE_DELETE if c.Version >= 4 { command = FRR_REDISTRIBUTE_DELETE } body := &RedistributeBody{ Redist: t, } return c.SendCommand(command, VRF_DEFAULT, body) } else { return fmt.Errorf("unknown route type: %d", t) } } func (c *Client) SendIPRoute(vrfId uint16, body *IPRouteBody, isWithdraw bool) error { command := IPV4_ROUTE_ADD if c.Version <= 3 { if body.Prefix.To4() != nil { if isWithdraw { command = IPV4_ROUTE_DELETE } } else { if isWithdraw { command = IPV6_ROUTE_DELETE } else { command = IPV6_ROUTE_ADD } } } else { // version >= 4 if body.Prefix.To4() != nil { if isWithdraw { command = FRR_IPV4_ROUTE_DELETE } else { command = FRR_IPV4_ROUTE_ADD } } else { if isWithdraw { command = FRR_IPV6_ROUTE_DELETE } else { command = FRR_IPV6_ROUTE_ADD } } } return c.SendCommand(command, vrfId, body) } func (c *Client) SendNexthopRegister(vrfId uint16, body *NexthopRegisterBody, isWithdraw bool) error { // Note: NEXTHOP_REGISTER and NEXTHOP_UNREGISTER messages are not // supported in Zebra protocol version<3. if c.Version < 3 { return fmt.Errorf("NEXTHOP_REGISTER/NEXTHOP_UNREGISTER are not supported in version: %d", c.Version) } command := NEXTHOP_REGISTER if c.Version == 3 { if isWithdraw { command = NEXTHOP_UNREGISTER } } else { // version >= 4 if isWithdraw { command = FRR_NEXTHOP_UNREGISTER } else { command = FRR_NEXTHOP_REGISTER } } return c.SendCommand(command, vrfId, body) } func (c *Client) Close() error { close(c.outgoing) return c.conn.Close() } type Header struct { Len uint16 Marker uint8 Version uint8 VrfId uint16 Command API_TYPE } func (h *Header) Serialize() ([]byte, error) { buf := make([]byte, HeaderSize(h.Version)) binary.BigEndian.PutUint16(buf[0:2], h.Len) buf[2] = h.Marker buf[3] = h.Version switch h.Version { case 2: binary.BigEndian.PutUint16(buf[4:6], uint16(h.Command)) case 3, 4: binary.BigEndian.PutUint16(buf[4:6], uint16(h.VrfId)) binary.BigEndian.PutUint16(buf[6:8], uint16(h.Command)) default: return nil, fmt.Errorf("Unsupported ZAPI version: %d", h.Version) } return buf, nil } func (h *Header) DecodeFromBytes(data []byte) error { if uint16(len(data)) < 4 { return fmt.Errorf("Not all ZAPI message header") } h.Len = binary.BigEndian.Uint16(data[0:2]) h.Marker = data[2] h.Version = data[3] if uint16(len(data)) < HeaderSize(h.Version) { return fmt.Errorf("Not all ZAPI message header") } switch h.Version { case 2: h.Command = API_TYPE(binary.BigEndian.Uint16(data[4:6])) case 3, 4: h.VrfId = binary.BigEndian.Uint16(data[4:6]) h.Command = API_TYPE(binary.BigEndian.Uint16(data[6:8])) default: return fmt.Errorf("Unsupported ZAPI version: %d", h.Version) } return nil } type Body interface { DecodeFromBytes([]byte, uint8) error Serialize(uint8) ([]byte, error) String() string } type UnknownBody struct { Data []byte } func (b *UnknownBody) DecodeFromBytes(data []byte, version uint8) error { b.Data = data return nil } func (b *UnknownBody) Serialize(version uint8) ([]byte, error) { return b.Data, nil } func (b *UnknownBody) String() string { return fmt.Sprintf("data: %v", b.Data) } type HelloBody struct { RedistDefault ROUTE_TYPE Instance uint16 } func (b *HelloBody) DecodeFromBytes(data []byte, version uint8) error { b.RedistDefault = ROUTE_TYPE(data[0]) if version >= 4 { b.Instance = binary.BigEndian.Uint16(data[1:3]) } return nil } func (b *HelloBody) Serialize(version uint8) ([]byte, error) { if version <= 3 { return []byte{uint8(b.RedistDefault)}, nil } else { // version >= 4 buf := make([]byte, 3, 3) buf[0] = uint8(b.RedistDefault) binary.BigEndian.PutUint16(buf[1:3], b.Instance) return buf, nil } } func (b *HelloBody) String() string { return fmt.Sprintf( "route_type: %s, instance :%d", b.RedistDefault.String(), b.Instance) } type RedistributeBody struct { Afi AFI Redist ROUTE_TYPE Instance uint16 } func (b *RedistributeBody) DecodeFromBytes(data []byte, version uint8) error { if version <= 3 { b.Redist = ROUTE_TYPE(data[0]) } else { // version >= 4 b.Afi = AFI(data[0]) b.Redist = ROUTE_TYPE(data[1]) b.Instance = binary.BigEndian.Uint16(data[2:4]) } return nil } func (b *RedistributeBody) Serialize(version uint8) ([]byte, error) { if version <= 3 { return []byte{uint8(b.Redist)}, nil } else { // version >= 4 buf := make([]byte, 4, 4) buf[0] = uint8(b.Afi) buf[1] = uint8(b.Redist) binary.BigEndian.PutUint16(buf[2:4], b.Instance) return buf, nil } } func (b *RedistributeBody) String() string { return fmt.Sprintf( "afi: %s, route_type: %s, instance :%d", b.Afi.String(), b.Redist.String(), b.Instance) } type InterfaceUpdateBody struct { Name string Index uint32 Status INTERFACE_STATUS Flags uint64 PTMEnable PTM_ENABLE PTMStatus PTM_STATUS Metric uint32 Speed uint32 MTU uint32 MTU6 uint32 Bandwidth uint32 Linktype LINK_TYPE HardwareAddr net.HardwareAddr } func (b *InterfaceUpdateBody) DecodeFromBytes(data []byte, version uint8) error { if len(data) < INTERFACE_NAMSIZ+29 { return fmt.Errorf("lack of bytes. need %d but %d", INTERFACE_NAMSIZ+29, len(data)) } b.Name = strings.Trim(string(data[:INTERFACE_NAMSIZ]), "\u0000") data = data[INTERFACE_NAMSIZ:] b.Index = binary.BigEndian.Uint32(data[0:4]) b.Status = INTERFACE_STATUS(data[4]) b.Flags = binary.BigEndian.Uint64(data[5:13]) if version >= 4 { b.PTMEnable = PTM_ENABLE(data[13]) b.PTMStatus = PTM_STATUS(data[14]) b.Metric = binary.BigEndian.Uint32(data[15:19]) b.Speed = binary.BigEndian.Uint32(data[19:23]) data = data[23:] } else { b.Metric = binary.BigEndian.Uint32(data[13:17]) data = data[17:] } b.MTU = binary.BigEndian.Uint32(data[0:4]) b.MTU6 = binary.BigEndian.Uint32(data[4:8]) b.Bandwidth = binary.BigEndian.Uint32(data[8:12]) if version >= 3 { b.Linktype = LINK_TYPE(binary.BigEndian.Uint32(data[12:16])) data = data[16:] } else { data = data[12:] } l := binary.BigEndian.Uint32(data[:4]) if l > 0 { if len(data) < 4+int(l) { return fmt.Errorf("lack of bytes. need %d but %d", 4+l, len(data)) } b.HardwareAddr = data[4 : 4+l] } return nil } func (b *InterfaceUpdateBody) Serialize(version uint8) ([]byte, error) { return []byte{}, nil } func (b *InterfaceUpdateBody) String() string { s := fmt.Sprintf( "name: %s, idx: %d, status: %s, flags: %s, ptm_enable: %s, ptm_status: %s, metric: %d, speed: %d, mtu: %d, mtu6: %d, bandwidth: %d, linktype: %s", b.Name, b.Index, b.Status.String(), intfflag2string(b.Flags), b.PTMEnable.String(), b.PTMStatus.String(), b.Metric, b.Speed, b.MTU, b.MTU6, b.Bandwidth, b.Linktype.String()) if len(b.HardwareAddr) > 0 { return s + fmt.Sprintf(", mac: %s", b.HardwareAddr.String()) } return s } type InterfaceAddressUpdateBody struct { Index uint32 Flags INTERFACE_ADDRESS_FLAG Prefix net.IP Length uint8 Destination net.IP } func (b *InterfaceAddressUpdateBody) DecodeFromBytes(data []byte, version uint8) error { b.Index = binary.BigEndian.Uint32(data[:4]) b.Flags = INTERFACE_ADDRESS_FLAG(data[4]) family := data[5] var addrlen int8 switch family { case syscall.AF_INET: addrlen = net.IPv4len case syscall.AF_INET6: addrlen = net.IPv6len default: return fmt.Errorf("unknown address family: %d", family) } b.Prefix = data[6 : 6+addrlen] b.Length = data[6+addrlen] b.Destination = data[7+addrlen : 7+addrlen*2] return nil } func (b *InterfaceAddressUpdateBody) Serialize(version uint8) ([]byte, error) { return []byte{}, nil } func (b *InterfaceAddressUpdateBody) String() string { return fmt.Sprintf( "idx: %d, flags: %s, addr: %s/%d", b.Index, b.Flags.String(), b.Prefix.String(), b.Length) } type RouterIDUpdateBody struct { Length uint8 Prefix net.IP } func (b *RouterIDUpdateBody) DecodeFromBytes(data []byte, version uint8) error { family := data[0] var addrlen int8 switch family { case syscall.AF_INET: addrlen = net.IPv4len case syscall.AF_INET6: addrlen = net.IPv6len default: return fmt.Errorf("unknown address family: %d", family) } b.Prefix = data[1 : 1+addrlen] b.Length = data[1+addrlen] return nil } func (b *RouterIDUpdateBody) Serialize(version uint8) ([]byte, error) { return []byte{}, nil } func (b *RouterIDUpdateBody) String() string { return fmt.Sprintf("id: %s/%d", b.Prefix.String(), b.Length) } type IPRouteBody struct { Type ROUTE_TYPE Instance uint16 Flags FLAG Message MESSAGE_FLAG SAFI SAFI Prefix net.IP PrefixLength uint8 SrcPrefix net.IP SrcPrefixLength uint8 Nexthops []net.IP Ifindexs []uint32 Distance uint8 Metric uint32 Mtu uint32 Tag uint32 Api API_TYPE } func (b *IPRouteBody) RouteFamily() bgp.RouteFamily { switch b.Api { case IPV4_ROUTE_ADD, IPV4_ROUTE_DELETE, FRR_REDISTRIBUTE_IPV4_ADD, FRR_REDISTRIBUTE_IPV4_DEL: return bgp.RF_IPv4_UC case IPV6_ROUTE_ADD, IPV6_ROUTE_DELETE, FRR_REDISTRIBUTE_IPV6_ADD, FRR_REDISTRIBUTE_IPV6_DEL: return bgp.RF_IPv6_UC default: return bgp.RF_OPAQUE } } func (b *IPRouteBody) IsWithdraw() bool { switch b.Api { case IPV4_ROUTE_DELETE, FRR_REDISTRIBUTE_IPV4_DEL, IPV6_ROUTE_DELETE, FRR_REDISTRIBUTE_IPV6_DEL: return true default: return false } } func (b *IPRouteBody) Serialize(version uint8) ([]byte, error) { var buf []byte nhfIPv4 := uint8(NEXTHOP_IPV4) nhfIPv6 := uint8(NEXTHOP_IPV6) nhfIndx := uint8(NEXTHOP_IFINDEX) nhfBlkH := uint8(NEXTHOP_BLACKHOLE) if version <= 3 { buf = make([]byte, 5) buf[0] = uint8(b.Type) buf[1] = uint8(b.Flags) buf[2] = uint8(b.Message) binary.BigEndian.PutUint16(buf[3:5], uint16(b.SAFI)) } else { // version >= 4 buf = make([]byte, 10) buf[0] = uint8(b.Type) binary.BigEndian.PutUint16(buf[1:3], uint16(b.Instance)) binary.BigEndian.PutUint32(buf[3:7], uint32(b.Flags)) buf[7] = uint8(b.Message) binary.BigEndian.PutUint16(buf[8:10], uint16(b.SAFI)) nhfIPv4 = uint8(FRR_NEXTHOP_IPV4) nhfIPv6 = uint8(FRR_NEXTHOP_IPV6) nhfIndx = uint8(FRR_NEXTHOP_IFINDEX) nhfBlkH = uint8(FRR_NEXTHOP_BLACKHOLE) } byteLen := (int(b.PrefixLength) + 7) / 8 buf = append(buf, b.PrefixLength) buf = append(buf, b.Prefix[:byteLen]...) if b.Message&FRR_MESSAGE_SRCPFX > 0 { byteLen = (int(b.SrcPrefixLength) + 7) / 8 buf = append(buf, b.SrcPrefixLength) buf = append(buf, b.SrcPrefix[:byteLen]...) } if b.Message&MESSAGE_NEXTHOP > 0 { if b.Flags&FLAG_BLACKHOLE > 0 { buf = append(buf, []byte{1, nhfBlkH}...) } else { buf = append(buf, uint8(len(b.Nexthops)+len(b.Ifindexs))) } for _, v := range b.Nexthops { if v.To4() != nil { buf = append(buf, nhfIPv4) buf = append(buf, v.To4()...) } else { buf = append(buf, nhfIPv6) buf = append(buf, v.To16()...) } } for _, v := range b.Ifindexs { buf = append(buf, nhfIndx) bbuf := make([]byte, 4) binary.BigEndian.PutUint32(bbuf, v) buf = append(buf, bbuf...) } } if b.Message&MESSAGE_DISTANCE > 0 { buf = append(buf, b.Distance) } if b.Message&MESSAGE_METRIC > 0 { bbuf := make([]byte, 4) binary.BigEndian.PutUint32(bbuf, b.Metric) buf = append(buf, bbuf...) } if version <= 3 { if b.Message&MESSAGE_MTU > 0 { bbuf := make([]byte, 4) binary.BigEndian.PutUint32(bbuf, b.Mtu) buf = append(buf, bbuf...) } if b.Message&MESSAGE_TAG > 0 { bbuf := make([]byte, 4) binary.BigEndian.PutUint32(bbuf, b.Tag) buf = append(buf, bbuf...) } } else { // version >= 4 if b.Message&FRR_MESSAGE_TAG > 0 { bbuf := make([]byte, 4) binary.BigEndian.PutUint32(bbuf, b.Tag) buf = append(buf, bbuf...) } if b.Message&FRR_MESSAGE_MTU > 0 { bbuf := make([]byte, 4) binary.BigEndian.PutUint32(bbuf, b.Mtu) buf = append(buf, bbuf...) } } return buf, nil } func (b *IPRouteBody) DecodeFromBytes(data []byte, version uint8) error { isV4 := true if version <= 3 { isV4 = b.Api == IPV4_ROUTE_ADD || b.Api == IPV4_ROUTE_DELETE } else { isV4 = b.Api == FRR_REDISTRIBUTE_IPV4_ADD || b.Api == FRR_REDISTRIBUTE_IPV4_DEL } var addrLen uint8 = net.IPv4len if !isV4 { addrLen = net.IPv6len } b.Type = ROUTE_TYPE(data[0]) if version <= 3 { b.Flags = FLAG(data[1]) data = data[2:] } else { // version >= 4 b.Instance = binary.BigEndian.Uint16(data[1:3]) b.Flags = FLAG(binary.BigEndian.Uint32(data[3:7])) data = data[7:] } b.Message = MESSAGE_FLAG(data[0]) b.SAFI = SAFI(SAFI_UNICAST) b.PrefixLength = data[1] if b.PrefixLength > addrLen*8 { return fmt.Errorf("prefix length is greater than %d", addrLen*8) } pos := 2 buf := make([]byte, addrLen) byteLen := int((b.PrefixLength + 7) / 8) copy(buf, data[pos:pos+byteLen]) if isV4 { b.Prefix = net.IP(buf).To4() } else { b.Prefix = net.IP(buf).To16() } pos += byteLen if b.Message&FRR_MESSAGE_SRCPFX > 0 { b.SrcPrefixLength = data[pos] pos += 1 buf = make([]byte, addrLen) byteLen = int((b.SrcPrefixLength + 7) / 8) copy(buf, data[pos:pos+byteLen]) if isV4 { b.SrcPrefix = net.IP(buf).To4() } else { b.SrcPrefix = net.IP(buf).To16() } pos += byteLen } rest := 0 var numNexthop int if b.Message&MESSAGE_NEXTHOP > 0 { numNexthop = int(data[pos]) // rest = numNexthop(1) + (nexthop(4 or 16) + placeholder(1) + ifindex(4)) * numNexthop rest += 1 + numNexthop*(int(addrLen)+5) } if b.Message&MESSAGE_DISTANCE > 0 { // distance(1) rest += 1 } if b.Message&MESSAGE_METRIC > 0 { // metric(4) rest += 4 } if version <= 3 { if b.Message&MESSAGE_MTU > 0 { // mtu(4) rest += 4 } if b.Message&MESSAGE_TAG > 0 { // tag(4) rest += 4 } } else { // version >= 4 if b.Message&FRR_MESSAGE_TAG > 0 { // tag(4) rest += 4 } if b.Message&FRR_MESSAGE_MTU > 0 { // mtu(4) rest += 4 } } if len(data[pos:]) != rest { return fmt.Errorf("message length invalid") } b.Nexthops = []net.IP{} b.Ifindexs = []uint32{} if b.Message&MESSAGE_NEXTHOP > 0 { pos += 1 for i := 0; i < numNexthop; i++ { addr := data[pos : pos+int(addrLen)] var nexthop net.IP if isV4 { nexthop = net.IP(addr).To4() } else { nexthop = net.IP(addr).To16() } b.Nexthops = append(b.Nexthops, nexthop) // skip nexthop and 1byte place holder pos += int(addrLen + 1) ifidx := binary.BigEndian.Uint32(data[pos : pos+4]) b.Ifindexs = append(b.Ifindexs, ifidx) pos += 4 } } if b.Message&MESSAGE_DISTANCE > 0 { b.Distance = data[pos] pos += 1 } if b.Message&MESSAGE_METRIC > 0 { b.Metric = binary.BigEndian.Uint32(data[pos : pos+4]) pos += 4 } if version <= 3 { if b.Message&MESSAGE_MTU > 0 { b.Mtu = binary.BigEndian.Uint32(data[pos : pos+4]) pos += 4 } if b.Message&MESSAGE_TAG > 0 { b.Tag = binary.BigEndian.Uint32(data[pos : pos+4]) pos += 4 } } else { if b.Message&FRR_MESSAGE_TAG > 0 { b.Tag = binary.BigEndian.Uint32(data[pos : pos+4]) pos += 4 } if b.Message&FRR_MESSAGE_MTU > 0 { b.Mtu = binary.BigEndian.Uint32(data[pos : pos+4]) pos += 4 } } return nil } func (b *IPRouteBody) String() string { s := fmt.Sprintf( "type: %s, instance: %d, flags: %s, message: %d, safi: %s, prefix: %s/%d, src_prefix: %s/%d", b.Type.String(), b.Instance, b.Flags.String(), b.Message, b.SAFI.String(), b.Prefix.String(), b.PrefixLength, b.SrcPrefix.String(), b.SrcPrefixLength) for i, nh := range b.Nexthops { s += fmt.Sprintf(", nexthops[%d]: %s", i, nh.String()) } for i, idx := range b.Ifindexs { s += fmt.Sprintf(", ifindex[%d]: %d", i, idx) } return s + fmt.Sprintf( ", distance: %d, metric: %d, mtu: %d, tag: %d", b.Distance, b.Metric, b.Mtu, b.Tag) } type NexthopLookupBody struct { Api API_TYPE Addr net.IP Distance uint8 Metric uint32 Nexthops []*Nexthop } type Nexthop struct { Ifname string Ifindex uint32 Type NEXTHOP_FLAG Addr net.IP } func (n *Nexthop) String() string { s := fmt.Sprintf( "type: %s, addr: %s, ifindex: %d, ifname: %s", n.Type.String(), n.Addr.String(), n.Ifindex, n.Ifname) return s } func serializeNexthops(nexthops []*Nexthop, isV4 bool, version uint8) ([]byte, error) { buf := make([]byte, 0) if len(nexthops) == 0 { return buf, nil } buf = append(buf, byte(len(nexthops))) nhIfindex := NEXTHOP_IFINDEX nhIfname := NEXTHOP_IFNAME nhIPv4 := NEXTHOP_IPV4 nhIPv4Ifindex := NEXTHOP_IPV4_IFINDEX nhIPv4Ifname := NEXTHOP_IPV4_IFNAME nhIPv6 := NEXTHOP_IPV6 nhIPv6Ifindex := NEXTHOP_IPV6_IFINDEX nhIPv6Ifname := NEXTHOP_IPV6_IFNAME if version >= 4 { nhIfindex = FRR_NEXTHOP_IFINDEX nhIfname = NEXTHOP_FLAG(0) nhIPv4 = FRR_NEXTHOP_IPV4 nhIPv4Ifindex = FRR_NEXTHOP_IPV4_IFINDEX nhIPv4Ifname = NEXTHOP_FLAG(0) nhIPv6 = FRR_NEXTHOP_IPV6 nhIPv6Ifindex = FRR_NEXTHOP_IPV6_IFINDEX nhIPv6Ifname = NEXTHOP_FLAG(0) } for _, nh := range nexthops { buf = append(buf, byte(nh.Type)) switch nh.Type { case nhIfindex, nhIfname: bbuf := make([]byte, 4) binary.BigEndian.PutUint32(bbuf, nh.Ifindex) buf = append(buf, bbuf...) case nhIPv4, nhIPv6: if isV4 { buf = append(buf, nh.Addr.To4()...) } else { buf = append(buf, nh.Addr.To16()...) } if version >= 4 { // On FRRouting version 3.0 or later, NEXTHOP_IPV4 and // NEXTHOP_IPV6 have the same structure with // NEXTHOP_TYPE_IPV4_IFINDEX and NEXTHOP_TYPE_IPV6_IFINDEX. bbuf := make([]byte, 4) binary.BigEndian.PutUint32(bbuf, nh.Ifindex) buf = append(buf, bbuf...) } case nhIPv4Ifindex, nhIPv4Ifname, nhIPv6Ifindex, nhIPv6Ifname: if isV4 { buf = append(buf, nh.Addr.To4()...) } else { buf = append(buf, nh.Addr.To16()...) } bbuf := make([]byte, 4) binary.BigEndian.PutUint32(bbuf, nh.Ifindex) buf = append(buf, bbuf...) } } return buf, nil } func decodeNexthopsFromBytes(nexthops *[]*Nexthop, data []byte, isV4 bool, version uint8) (int, error) { addrLen := net.IPv4len if !isV4 { addrLen = net.IPv6len } nhIfindex := NEXTHOP_IFINDEX nhIfname := NEXTHOP_IFNAME nhIPv4 := NEXTHOP_IPV4 nhIPv4Ifindex := NEXTHOP_IPV4_IFINDEX nhIPv4Ifname := NEXTHOP_IPV4_IFNAME nhIPv6 := NEXTHOP_IPV6 nhIPv6Ifindex := NEXTHOP_IPV6_IFINDEX nhIPv6Ifname := NEXTHOP_IPV6_IFNAME if version >= 4 { nhIfindex = FRR_NEXTHOP_IFINDEX nhIfname = NEXTHOP_FLAG(0) nhIPv4 = FRR_NEXTHOP_IPV4 nhIPv4Ifindex = FRR_NEXTHOP_IPV4_IFINDEX nhIPv4Ifname = NEXTHOP_FLAG(0) nhIPv6 = FRR_NEXTHOP_IPV6 nhIPv6Ifindex = FRR_NEXTHOP_IPV6_IFINDEX nhIPv6Ifname = NEXTHOP_FLAG(0) } numNexthop := int(data[0]) offset := 1 for i := 0; i < numNexthop; i++ { nh := &Nexthop{} nh.Type = NEXTHOP_FLAG(data[offset]) offset += 1 switch nh.Type { case nhIfindex, nhIfname: nh.Ifindex = binary.BigEndian.Uint32(data[offset : offset+4]) offset += 4 case nhIPv4, nhIPv6: if isV4 { nh.Addr = net.IP(data[offset : offset+addrLen]).To4() } else { nh.Addr = net.IP(data[offset : offset+addrLen]).To16() } offset += addrLen if version >= 4 { // On FRRouting version 3.0 or later, NEXTHOP_IPV4 and // NEXTHOP_IPV6 have the same structure with // NEXTHOP_TYPE_IPV4_IFINDEX and NEXTHOP_TYPE_IPV6_IFINDEX. nh.Ifindex = binary.BigEndian.Uint32(data[offset : offset+4]) offset += 4 } case nhIPv4Ifindex, nhIPv4Ifname, nhIPv6Ifindex, nhIPv6Ifname: if isV4 { nh.Addr = net.IP(data[offset : offset+addrLen]).To4() } else { nh.Addr = net.IP(data[offset : offset+addrLen]).To16() } offset += addrLen nh.Ifindex = binary.BigEndian.Uint32(data[offset : offset+4]) offset += 4 } *nexthops = append(*nexthops, nh) } return offset, nil } func (b *NexthopLookupBody) Serialize(version uint8) ([]byte, error) { isV4 := false if version <= 3 { isV4 = b.Api == IPV4_NEXTHOP_LOOKUP } else { // version >= 4 isV4 = b.Api == FRR_IPV4_NEXTHOP_LOOKUP_MRIB } buf := make([]byte, 0) if isV4 { buf = append(buf, b.Addr.To4()...) } else { buf = append(buf, b.Addr.To16()...) } return buf, nil } func (b *NexthopLookupBody) DecodeFromBytes(data []byte, version uint8) error { isV4 := false if version <= 3 { isV4 = b.Api == IPV4_NEXTHOP_LOOKUP } else { // version >= 4 isV4 = b.Api == FRR_IPV4_NEXTHOP_LOOKUP_MRIB } addrLen := net.IPv4len if !isV4 { addrLen = net.IPv6len } if len(data) < addrLen { return fmt.Errorf("message length invalid") } buf := make([]byte, addrLen) copy(buf, data[0:addrLen]) pos := addrLen if isV4 { b.Addr = net.IP(buf).To4() } else { b.Addr = net.IP(buf).To16() } if version >= 4 { b.Distance = data[pos] pos++ } if len(data[pos:]) > int(1+addrLen) { b.Metric = binary.BigEndian.Uint32(data[pos : pos+4]) pos += 4 b.Nexthops = []*Nexthop{} if nexthopsByteLen, err := decodeNexthopsFromBytes(&b.Nexthops, data[pos:], isV4, version); err != nil { return err } else { pos += nexthopsByteLen } } return nil } func (b *NexthopLookupBody) String() string { s := fmt.Sprintf( "addr: %s, distance:%d, metric: %d", b.Addr.String(), b.Distance, b.Metric) if len(b.Nexthops) > 0 { for _, nh := range b.Nexthops { s = s + fmt.Sprintf(", nexthop:{%s}", nh.String()) } } return s } type ImportLookupBody struct { Api API_TYPE PrefixLength uint8 Prefix net.IP Addr net.IP Metric uint32 Nexthops []*Nexthop } func (b *ImportLookupBody) Serialize(version uint8) ([]byte, error) { buf := make([]byte, 1) buf[0] = b.PrefixLength buf = append(buf, b.Addr.To4()...) return buf, nil } func (b *ImportLookupBody) DecodeFromBytes(data []byte, version uint8) error { isV4 := b.Api == IPV4_IMPORT_LOOKUP addrLen := net.IPv4len if !isV4 { addrLen = net.IPv6len } if len(data) < addrLen { return fmt.Errorf("message length invalid") } buf := make([]byte, addrLen) copy(buf, data[0:addrLen]) pos := addrLen b.Addr = net.IP(buf).To4() if len(data[pos:]) > int(1+addrLen) { b.Metric = binary.BigEndian.Uint32(data[pos : pos+4]) pos += 4 b.Nexthops = []*Nexthop{} if nexthopsByteLen, err := decodeNexthopsFromBytes(&b.Nexthops, data[pos:], isV4, version); err != nil { return err } else { pos += nexthopsByteLen } } return nil } func (b *ImportLookupBody) String() string { s := fmt.Sprintf( "prefix: %s/%d, addr: %s, metric: %d", b.Prefix.String(), b.PrefixLength, b.Addr.String(), b.Metric) if len(b.Nexthops) > 0 { for _, nh := range b.Nexthops { s = s + fmt.Sprintf(", nexthop:{%s}", nh.String()) } } return s } type RegisteredNexthop struct { Connected uint8 Family uint16 // Note: Ignores PrefixLength (uint8), // because this field should be always: // - 32 if Address Family is AF_INET // - 128 if Address Family is AF_INET6 Prefix net.IP } func (n *RegisteredNexthop) Len() int { // Connected (1 byte) + Address Family (2 bytes) + Prefix Length (1 byte) + Prefix (variable) if n.Family == uint16(syscall.AF_INET) { return 4 + net.IPv4len } else { return 4 + net.IPv6len } } func (n *RegisteredNexthop) Serialize() ([]byte, error) { // Connected (1 byte) buf := make([]byte, 4) buf[0] = byte(n.Connected) // Address Family (2 bytes) binary.BigEndian.PutUint16(buf[1:3], n.Family) // Prefix Length (1 byte) + Prefix (variable) switch n.Family { case uint16(syscall.AF_INET): buf[3] = byte(net.IPv4len * 8) buf = append(buf, n.Prefix.To4()...) case uint16(syscall.AF_INET6): buf[3] = byte(net.IPv6len * 8) buf = append(buf, n.Prefix.To16()...) default: return nil, fmt.Errorf("invalid address family: %d", n.Family) } return buf, nil } func (n *RegisteredNexthop) DecodeFromBytes(data []byte) error { // Connected (1 byte) n.Connected = uint8(data[0]) offset := 1 // Address Family (2 bytes) n.Family = binary.BigEndian.Uint16(data[offset : offset+2]) isV4 := n.Family == uint16(syscall.AF_INET) addrLen := int(net.IPv4len) if !isV4 { addrLen = net.IPv6len } // Note: Ignores Prefix Length (1 byte) offset += 3 // Prefix (variable) if isV4 { n.Prefix = net.IP(data[offset : offset+addrLen]).To4() } else { n.Prefix = net.IP(data[offset : offset+addrLen]).To16() } return nil } func (n *RegisteredNexthop) String() string { return fmt.Sprintf( "connected: %d, family: %d, prefix: %s", n.Connected, n.Family, n.Prefix.String()) } type NexthopRegisterBody struct { Api API_TYPE Nexthops []*RegisteredNexthop } func (b *NexthopRegisterBody) Serialize(version uint8) ([]byte, error) { buf := make([]byte, 0) // List of Registered Nexthops for _, nh := range b.Nexthops { nhBuf, err := nh.Serialize() if err != nil { return nil, err } buf = append(buf, nhBuf...) } return buf, nil } func (b *NexthopRegisterBody) DecodeFromBytes(data []byte, version uint8) error { offset := 0 // List of Registered Nexthops b.Nexthops = []*RegisteredNexthop{} for len(data[offset:]) > 0 { nh := new(RegisteredNexthop) err := nh.DecodeFromBytes(data[offset:]) if err != nil { return err } b.Nexthops = append(b.Nexthops, nh) offset += nh.Len() if len(data) < offset { break } } return nil } func (b *NexthopRegisterBody) String() string { s := make([]string, 0) for _, nh := range b.Nexthops { s = append(s, fmt.Sprintf("nexthop:{%s}", nh.String())) } return strings.Join(s, ", ") } type NexthopUpdateBody struct { Api API_TYPE Family uint16 // Note: Ignores PrefixLength (uint8), // because this field should be always: // - 32 if Address Family is AF_INET // - 128 if Address Family is AF_INET6 Prefix net.IP Distance uint8 Metric uint32 Nexthops []*Nexthop } func (b *NexthopUpdateBody) Serialize(version uint8) ([]byte, error) { // Address Family (2 bytes) buf := make([]byte, 3) binary.BigEndian.PutUint16(buf, b.Family) // Prefix Length (1 byte) + Prefix (variable) switch b.Family { case uint16(syscall.AF_INET): buf[2] = byte(net.IPv4len * 8) buf = append(buf, b.Prefix.To4()...) case uint16(syscall.AF_INET6): buf[2] = byte(net.IPv6len * 8) buf = append(buf, b.Prefix.To16()...) default: return nil, fmt.Errorf("invalid address family: %d", b.Family) } return buf, nil } func (b *NexthopUpdateBody) DecodeFromBytes(data []byte, version uint8) error { // Address Family (2 bytes) b.Family = binary.BigEndian.Uint16(data[0:2]) isV4 := b.Family == uint16(syscall.AF_INET) addrLen := int(net.IPv4len) if !isV4 { addrLen = net.IPv6len } // Note: Ignores Prefix Length (1 byte) offset := 3 // Prefix (variable) if isV4 { b.Prefix = net.IP(data[offset : offset+addrLen]).To4() } else { b.Prefix = net.IP(data[offset : offset+addrLen]).To16() } offset += addrLen // Distance (1 byte) (if version>=4) // Metric (4 bytes) // Number of Nexthops (1 byte) if version >= 4 { if len(data[offset:]) < 6 { return fmt.Errorf("invalid message length: missing distance(1 byte), metric(4 bytes) or nexthops(1 byte): %d<6", len(data[offset:])) } b.Distance = data[offset] offset += 1 } else if len(data[offset:]) < 5 { return fmt.Errorf("invalid message length: missing metric(4 bytes) or nexthops(1 byte): %d<5", len(data[offset:])) } b.Metric = binary.BigEndian.Uint32(data[offset : offset+4]) offset += 4 // List of Nexthops b.Nexthops = []*Nexthop{} if nexthopsByteLen, err := decodeNexthopsFromBytes(&b.Nexthops, data[offset:], isV4, version); err != nil { return err } else { offset += nexthopsByteLen } return nil } func (b *NexthopUpdateBody) String() string { s := fmt.Sprintf( "family: %d, prefix: %s, distance: %d, metric: %d", b.Family, b.Prefix.String(), b.Distance, b.Metric) for _, nh := range b.Nexthops { s = s + fmt.Sprintf(", nexthop:{%s}", nh.String()) } return s } type Message struct { Header Header Body Body } func (m *Message) Serialize() ([]byte, error) { var body []byte if m.Body != nil { var err error body, err = m.Body.Serialize(m.Header.Version) if err != nil { return nil, err } } m.Header.Len = uint16(len(body)) + HeaderSize(m.Header.Version) hdr, err := m.Header.Serialize() if err != nil { return nil, err } return append(hdr, body...), nil } func (m *Message) parseMessage(data []byte) error { switch m.Header.Command { case INTERFACE_ADD, INTERFACE_DELETE, INTERFACE_UP, INTERFACE_DOWN: m.Body = &InterfaceUpdateBody{} case INTERFACE_ADDRESS_ADD, INTERFACE_ADDRESS_DELETE: m.Body = &InterfaceAddressUpdateBody{} case ROUTER_ID_UPDATE: m.Body = &RouterIDUpdateBody{} case IPV4_ROUTE_ADD, IPV6_ROUTE_ADD, IPV4_ROUTE_DELETE, IPV6_ROUTE_DELETE: m.Body = &IPRouteBody{Api: m.Header.Command} case IPV4_NEXTHOP_LOOKUP, IPV6_NEXTHOP_LOOKUP: m.Body = &NexthopLookupBody{Api: m.Header.Command} case IPV4_IMPORT_LOOKUP: m.Body = &ImportLookupBody{Api: m.Header.Command} case NEXTHOP_UPDATE: m.Body = &NexthopUpdateBody{Api: m.Header.Command} default: m.Body = &UnknownBody{} } return m.Body.DecodeFromBytes(data, m.Header.Version) } func (m *Message) parseFrrMessage(data []byte) error { switch m.Header.Command { case FRR_INTERFACE_ADD, FRR_INTERFACE_DELETE, FRR_INTERFACE_UP, FRR_INTERFACE_DOWN: m.Body = &InterfaceUpdateBody{} case FRR_INTERFACE_ADDRESS_ADD, FRR_INTERFACE_ADDRESS_DELETE: m.Body = &InterfaceAddressUpdateBody{} case FRR_ROUTER_ID_UPDATE: m.Body = &RouterIDUpdateBody{} case FRR_NEXTHOP_UPDATE: m.Body = &NexthopUpdateBody{} case FRR_INTERFACE_NBR_ADDRESS_ADD, FRR_INTERFACE_NBR_ADDRESS_DELETE: // TODO m.Body = &UnknownBody{} case FRR_INTERFACE_BFD_DEST_UPDATE: // TODO m.Body = &UnknownBody{} case FRR_IMPORT_CHECK_UPDATE: // TODO m.Body = &UnknownBody{} case FRR_BFD_DEST_REPLAY: // TODO m.Body = &UnknownBody{} case FRR_REDISTRIBUTE_IPV4_ADD, FRR_REDISTRIBUTE_IPV4_DEL, FRR_REDISTRIBUTE_IPV6_ADD, FRR_REDISTRIBUTE_IPV6_DEL: m.Body = &IPRouteBody{Api: m.Header.Command} case FRR_INTERFACE_VRF_UPDATE: // TODO m.Body = &UnknownBody{} case FRR_INTERFACE_LINK_PARAMS: // TODO m.Body = &UnknownBody{} case FRR_PW_STATUS_UPDATE: // TODO m.Body = &UnknownBody{} default: m.Body = &UnknownBody{} } return m.Body.DecodeFromBytes(data, m.Header.Version) } func ParseMessage(hdr *Header, data []byte) (m *Message, err error) { m = &Message{Header: *hdr} if m.Header.Version == 4 { err = m.parseFrrMessage(data) } else { err = m.parseMessage(data) } if err != nil { return nil, err } return m, nil } gobgp-1.29/zebra/zapi_bsd.go000066400000000000000000000027261324612745600160110ustar00rootroot00000000000000// Copyright (C) 2014, 2015 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. // +build freebsd netbsd openbsd package zebra import ( "strings" "syscall" ) func intfflag2string(flag uint64) string { ss := make([]string, 0, 10) if flag&syscall.IFF_UP > 0 { ss = append(ss, "UP") } if flag&syscall.IFF_BROADCAST > 0 { ss = append(ss, "BROADCAST") } if flag&syscall.IFF_DEBUG > 0 { ss = append(ss, "DEBUG") } if flag&syscall.IFF_LOOPBACK > 0 { ss = append(ss, "LOOPBACK") } if flag&syscall.IFF_POINTOPOINT > 0 { ss = append(ss, "POINTOPOINT") } if flag&syscall.IFF_RUNNING > 0 { ss = append(ss, "RUNNING") } if flag&syscall.IFF_NOARP > 0 { ss = append(ss, "NOARP") } if flag&syscall.IFF_PROMISC > 0 { ss = append(ss, "PROMISC") } if flag&syscall.IFF_ALLMULTI > 0 { ss = append(ss, "ALLMULTI") } if flag&syscall.IFF_MULTICAST > 0 { ss = append(ss, "MULTICAST") } return strings.Join(ss, " | ") } gobgp-1.29/zebra/zapi_darwin.go000066400000000000000000000027751324612745600165310ustar00rootroot00000000000000// Copyright (C) 2014, 2015 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package zebra import ( "strings" "syscall" ) func intfflag2string(flag uint64) string { ss := make([]string, 0, 10) if flag&syscall.IFF_UP > 0 { ss = append(ss, "UP") } if flag&syscall.IFF_BROADCAST > 0 { ss = append(ss, "BROADCAST") } if flag&syscall.IFF_DEBUG > 0 { ss = append(ss, "DEBUG") } if flag&syscall.IFF_LOOPBACK > 0 { ss = append(ss, "LOOPBACK") } if flag&syscall.IFF_POINTOPOINT > 0 { ss = append(ss, "POINTOPOINT") } if flag&syscall.IFF_NOTRAILERS > 0 { ss = append(ss, "NOTRAILERS") } if flag&syscall.IFF_RUNNING > 0 { ss = append(ss, "RUNNING") } if flag&syscall.IFF_NOARP > 0 { ss = append(ss, "NOARP") } if flag&syscall.IFF_PROMISC > 0 { ss = append(ss, "PROMISC") } if flag&syscall.IFF_ALLMULTI > 0 { ss = append(ss, "ALLMULTI") } if flag&syscall.IFF_MULTICAST > 0 { ss = append(ss, "MULTICAST") } return strings.Join(ss, " | ") } gobgp-1.29/zebra/zapi_linux.go000066400000000000000000000040521324612745600163720ustar00rootroot00000000000000// Copyright (C) 2014, 2015 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package zebra import ( "strings" "syscall" ) func intfflag2string(flag uint64) string { ss := make([]string, 0, 10) if flag&syscall.IFF_UP > 0 { ss = append(ss, "UP") } if flag&syscall.IFF_BROADCAST > 0 { ss = append(ss, "BROADCAST") } if flag&syscall.IFF_DEBUG > 0 { ss = append(ss, "DEBUG") } if flag&syscall.IFF_LOOPBACK > 0 { ss = append(ss, "LOOPBACK") } if flag&syscall.IFF_POINTOPOINT > 0 { ss = append(ss, "POINTOPOINT") } if flag&syscall.IFF_NOTRAILERS > 0 { ss = append(ss, "NOTRAILERS") } if flag&syscall.IFF_RUNNING > 0 { ss = append(ss, "RUNNING") } if flag&syscall.IFF_NOARP > 0 { ss = append(ss, "NOARP") } if flag&syscall.IFF_PROMISC > 0 { ss = append(ss, "PROMISC") } if flag&syscall.IFF_ALLMULTI > 0 { ss = append(ss, "ALLMULTI") } if flag&syscall.IFF_MASTER > 0 { ss = append(ss, "MASTER") } if flag&syscall.IFF_SLAVE > 0 { ss = append(ss, "SLAVE") } if flag&syscall.IFF_MULTICAST > 0 { ss = append(ss, "MULTICAST") } if flag&syscall.IFF_PORTSEL > 0 { ss = append(ss, "PORTSEL") } if flag&syscall.IFF_AUTOMEDIA > 0 { ss = append(ss, "AUTOMEDIA") } if flag&syscall.IFF_DYNAMIC > 0 { ss = append(ss, "DYNAMIC") } // if flag&syscall.IFF_LOWER_UP > 0 { // ss = append(ss, "LOWER_UP") // } // if flag&syscall.IFF_DORMANT > 0 { // ss = append(ss, "DORMANT") // } // if flag&syscall.IFF_ECHO > 0 { // ss = append(ss, "ECHO") // } return strings.Join(ss, " | ") } gobgp-1.29/zebra/zapi_test.go000066400000000000000000000345731324612745600162250ustar00rootroot00000000000000// Copyright (C) 2014, 2015 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package zebra import ( "encoding/binary" "net" "syscall" "testing" "github.com/stretchr/testify/assert" ) func Test_Header(t *testing.T) { assert := assert.New(t) //DecodeFromBytes buf := make([]byte, 6) binary.BigEndian.PutUint16(buf[0:], 10) buf[2] = HEADER_MARKER buf[3] = 2 binary.BigEndian.PutUint16(buf[4:], uint16(IPV4_ROUTE_ADD)) h := &Header{} err := h.DecodeFromBytes(buf) assert.Equal(nil, err) //Serialize buf, err = h.Serialize() assert.Equal(nil, err) h2 := &Header{} err = h2.DecodeFromBytes(buf) assert.Equal(nil, err) assert.Equal(h, h2) // header_size mismatch buf = make([]byte, HeaderSize(2)-1) binary.BigEndian.PutUint16(buf[0:], 10) buf[2] = 0xff buf[3] = 0x02 h3 := &Header{} err = h3.DecodeFromBytes(buf) assert.NotEqual(nil, err) } func Test_InterfaceUpdateBody(t *testing.T) { assert := assert.New(t) //DecodeFromBytes buf := make([]byte, INTERFACE_NAMSIZ+49) pos := INTERFACE_NAMSIZ binary.BigEndian.PutUint32(buf[pos:], 1) pos += 4 buf[pos] = byte(INTERFACE_ACTIVE) pos += 1 binary.BigEndian.PutUint64(buf[pos:], 1) pos += 8 // flags binary.BigEndian.PutUint32(buf[pos:], 1) pos += 4 // metric binary.BigEndian.PutUint32(buf[pos:], 1500) pos += 4 // MTU binary.BigEndian.PutUint32(buf[pos:], 1500) pos += 4 // MTU6 binary.BigEndian.PutUint32(buf[pos:], 200) pos += 4 // bandwidth binary.BigEndian.PutUint32(buf[pos:], 6) pos += 4 // hwaddr_len mac, _ := net.ParseMAC("01:23:45:67:89:ab") copy(buf[pos:pos+6], []byte(mac)) pos += 4 b := &InterfaceUpdateBody{} err := b.DecodeFromBytes(buf, 2) assert.Equal(nil, err) assert.Equal("01:23:45:67:89:ab", b.HardwareAddr.String()) buf = make([]byte, INTERFACE_NAMSIZ+28) b = &InterfaceUpdateBody{} err = b.DecodeFromBytes(buf, 2) assert.NotEqual(nil, err) } func Test_InterfaceAddressUpdateBody(t *testing.T) { assert := assert.New(t) //DecodeFromBytes buf := make([]byte, 15) pos := 0 binary.BigEndian.PutUint32(buf[pos:], 0) // index pos += 4 buf[pos] = 0x01 // flags pos += 1 buf[pos] = 0x2 // family pos += 1 ip := net.ParseIP("192.168.100.1").To4() // prefix copy(buf[pos:pos+4], []byte(ip)) pos += 4 buf[pos] = byte(24) // prefix len pos += 1 dst := net.ParseIP("192.168.100.255").To4() // destination copy(buf[pos:pos+4], []byte(dst)) b := &InterfaceAddressUpdateBody{} err := b.DecodeFromBytes(buf, 2) assert.Equal(uint32(0), b.Index) assert.Equal(INTERFACE_ADDRESS_FLAG(1), b.Flags) assert.Equal("192.168.100.1", b.Prefix.String()) assert.Equal(uint8(24), b.Length) assert.Equal("192.168.100.255", b.Destination.String()) // af invalid buf[5] = 0x4 pos += 1 b = &InterfaceAddressUpdateBody{} err = b.DecodeFromBytes(buf, 2) assert.NotEqual(nil, err) } func Test_RouterIDUpdateBody(t *testing.T) { assert := assert.New(t) //DecodeFromBytes buf := make([]byte, 6) pos := 0 buf[pos] = 0x2 pos += 1 ip := net.ParseIP("192.168.100.1").To4() copy(buf[pos:pos+4], []byte(ip)) pos += 4 buf[pos] = byte(32) b := &RouterIDUpdateBody{} err := b.DecodeFromBytes(buf, 2) assert.Equal(nil, err) assert.Equal("192.168.100.1", b.Prefix.String()) assert.Equal(uint8(32), b.Length) // af invalid buf[0] = 0x4 pos += 1 b = &RouterIDUpdateBody{} err = b.DecodeFromBytes(buf, 2) assert.NotEqual(nil, err) } func Test_IPRouteBody_IPv4(t *testing.T) { assert := assert.New(t) //DecodeFromBytes IPV4_ROUTE buf := make([]byte, 26) buf[0] = byte(ROUTE_CONNECT) buf[1] = byte(FLAG_SELECTED) buf[2] = byte(MESSAGE_NEXTHOP | MESSAGE_DISTANCE | MESSAGE_METRIC | MESSAGE_MTU) buf[3] = 24 ip := net.ParseIP("192.168.100.0").To4() copy(buf[4:7], []byte(ip)) buf[7] = 1 nexthop := net.ParseIP("0.0.0.0").To4() copy(buf[8:12], []byte(nexthop)) buf[12] = 1 binary.BigEndian.PutUint32(buf[13:], 1) buf[17] = 0 // distance binary.BigEndian.PutUint32(buf[18:], 1) binary.BigEndian.PutUint32(buf[22:], 1) r := &IPRouteBody{Api: IPV4_ROUTE_ADD} err := r.DecodeFromBytes(buf, 2) assert.Equal(nil, err) assert.Equal("192.168.100.0", r.Prefix.String()) assert.Equal(uint8(0x18), r.PrefixLength) assert.Equal(MESSAGE_NEXTHOP|MESSAGE_DISTANCE|MESSAGE_METRIC|MESSAGE_MTU, r.Message) assert.Equal("0.0.0.0", r.Nexthops[0].String()) assert.Equal(uint32(1), r.Ifindexs[0]) assert.Equal(uint8(0), r.Distance) assert.Equal(uint32(1), r.Metric) assert.Equal(uint32(1), r.Mtu) //Serialize buf, err = r.Serialize(2) assert.Equal(nil, err) assert.Equal([]byte{0x2, 0x10, 0x1d}, buf[0:3]) assert.Equal([]byte{0x0, 0x1}, buf[3:5]) assert.Equal(byte(24), buf[5]) ip = net.ParseIP("192.168.100.0").To4() assert.Equal([]byte(ip)[0:3], buf[6:9]) assert.Equal(byte(NEXTHOP_IPV4), buf[10]) assert.Equal(byte(NEXTHOP_IFINDEX), buf[15]) assert.Equal(byte(0x0), buf[20]) bi := make([]byte, 4) binary.BigEndian.PutUint32(bi, 1) assert.Equal(bi, buf[21:25]) assert.Equal(bi, buf[25:]) // length invalid buf = make([]byte, 18) buf[0] = byte(ROUTE_CONNECT) buf[1] = byte(FLAG_SELECTED) buf[2] = byte(MESSAGE_NEXTHOP | MESSAGE_DISTANCE | MESSAGE_METRIC) buf[3] = 24 ip = net.ParseIP("192.168.100.0").To4() copy(buf[4:7], []byte(ip)) buf[7] = 1 nexthop = net.ParseIP("0.0.0.0").To4() copy(buf[8:12], []byte(nexthop)) buf[12] = 1 binary.BigEndian.PutUint32(buf[13:], 1) r = &IPRouteBody{Api: IPV4_ROUTE_ADD} err = r.DecodeFromBytes(buf, 2) assert.Equal("message length invalid", err.Error()) // no nexthop buf = make([]byte, 12) buf[0] = byte(ROUTE_CONNECT) buf[1] = byte(FLAG_SELECTED) buf[2] = byte(MESSAGE_DISTANCE | MESSAGE_METRIC) buf[3] = 24 ip = net.ParseIP("192.168.100.0").To4() copy(buf[4:7], []byte(ip)) buf[7] = 1 binary.BigEndian.PutUint32(buf[8:], 0) r = &IPRouteBody{Api: IPV4_ROUTE_ADD} err = r.DecodeFromBytes(buf, 2) assert.Equal(nil, err) } func Test_IPRouteBody_IPv6(t *testing.T) { assert := assert.New(t) //DecodeFromBytes IPV6_ROUTE buf := make([]byte, 43) buf[0] = byte(ROUTE_CONNECT) buf[1] = byte(FLAG_SELECTED) buf[2] = byte(MESSAGE_NEXTHOP | MESSAGE_DISTANCE | MESSAGE_METRIC | MESSAGE_MTU) buf[3] = 64 ip := net.ParseIP("2001:db8:0:f101::").To16() copy(buf[4:12], []byte(ip)) buf[12] = 1 nexthop := net.ParseIP("::").To16() copy(buf[13:29], []byte(nexthop)) // ifindex buf[29] = 1 binary.BigEndian.PutUint32(buf[30:], 1) buf[34] = 0 // distance binary.BigEndian.PutUint32(buf[35:], 1) binary.BigEndian.PutUint32(buf[39:], 1) r := &IPRouteBody{Api: IPV6_ROUTE_ADD} err := r.DecodeFromBytes(buf, 2) assert.Equal(nil, err) assert.Equal("2001:db8:0:f101::", r.Prefix.String()) assert.Equal(uint8(64), r.PrefixLength) assert.Equal(MESSAGE_NEXTHOP|MESSAGE_DISTANCE|MESSAGE_METRIC|MESSAGE_MTU, r.Message) assert.Equal("::", r.Nexthops[0].String()) assert.Equal(uint32(1), r.Ifindexs[0]) assert.Equal(uint8(0), r.Distance) assert.Equal(uint32(1), r.Metric) assert.Equal(uint32(1), r.Mtu) //Serialize buf, err = r.Serialize(2) assert.Equal(nil, err) assert.Equal([]byte{0x2, 0x10, 0x1d}, buf[0:3]) assert.Equal([]byte{0x0, 0x1}, buf[3:5]) assert.Equal(byte(64), buf[5]) ip = net.ParseIP("2001:db8:0:f101::").To16() assert.Equal([]byte(ip)[0:8], buf[6:14]) assert.Equal(byte(2), buf[14]) assert.Equal(byte(NEXTHOP_IPV6), buf[15]) ip = net.ParseIP("::").To16() assert.Equal([]byte(ip), buf[16:32]) assert.Equal(byte(NEXTHOP_IFINDEX), buf[32]) bi := make([]byte, 4) binary.BigEndian.PutUint32(bi, 1) assert.Equal(bi, buf[33:37]) //distance assert.Equal(byte(0), buf[37]) bi = make([]byte, 4) binary.BigEndian.PutUint32(bi, 1) assert.Equal(bi, buf[38:42]) assert.Equal(bi, buf[42:]) // length invalid buf = make([]byte, 50) buf[0] = byte(ROUTE_CONNECT) buf[1] = byte(FLAG_SELECTED) buf[2] = byte(MESSAGE_NEXTHOP | MESSAGE_DISTANCE | MESSAGE_METRIC) buf[3] = 24 ip = net.ParseIP("2001:db8:0:f101::").To4() copy(buf[4:12], []byte(ip)) buf[13] = 1 nexthop = net.ParseIP("::").To16() copy(buf[14:30], []byte(nexthop)) buf[31] = 1 binary.BigEndian.PutUint32(buf[32:], 1) r = &IPRouteBody{Api: IPV6_ROUTE_ADD} err = r.DecodeFromBytes(buf, 2) assert.Equal("message length invalid", err.Error()) // no nexthop buf = make([]byte, 11) buf[0] = byte(ROUTE_CONNECT) buf[1] = byte(FLAG_SELECTED) buf[2] = byte(MESSAGE_DISTANCE | MESSAGE_METRIC) buf[3] = 16 ip = net.ParseIP("2501::").To16() copy(buf[4:6], []byte(ip)) buf[6] = 1 binary.BigEndian.PutUint32(buf[7:], 0) r = &IPRouteBody{Api: IPV6_ROUTE_ADD} err = r.DecodeFromBytes(buf, 2) assert.Equal(nil, err) } func Test_NexthopLookupBody(t *testing.T) { assert := assert.New(t) //ipv4 //DecodeFromBytes pos := 0 buf := make([]byte, 18) ip := net.ParseIP("192.168.50.0").To4() copy(buf[0:4], []byte(ip)) pos += 4 binary.BigEndian.PutUint32(buf[pos:], 10) pos += 4 buf[pos] = byte(1) pos += 1 buf[pos] = byte(4) pos += 1 ip = net.ParseIP("172.16.1.101").To4() copy(buf[pos:pos+4], []byte(ip)) pos += 4 binary.BigEndian.PutUint32(buf[pos:], 3) b := &NexthopLookupBody{Api: IPV4_NEXTHOP_LOOKUP} err := b.DecodeFromBytes(buf, 2) assert.Equal(nil, err) assert.Equal("192.168.50.0", b.Addr.String()) assert.Equal(uint32(10), b.Metric) assert.Equal(uint32(3), b.Nexthops[0].Ifindex) assert.Equal(NEXTHOP_FLAG(4), b.Nexthops[0].Type) assert.Equal("172.16.1.101", b.Nexthops[0].Addr.String()) //Serialize buf, err = b.Serialize(2) ip = net.ParseIP("192.168.50.0").To4() assert.Equal(nil, err) assert.Equal([]byte(ip)[0:4], buf[0:4]) // length invalid buf = make([]byte, 3) b = &NexthopLookupBody{Api: IPV4_NEXTHOP_LOOKUP} err = b.DecodeFromBytes(buf, 2) assert.NotEqual(nil, err) //ipv6 //DecodeFromBytes pos = 0 buf = make([]byte, 46) ip = net.ParseIP("2001:db8:0:f101::").To16() copy(buf[0:16], []byte(ip)) pos += 16 binary.BigEndian.PutUint32(buf[pos:], 10) pos += 4 buf[pos] = byte(1) pos += 1 buf[pos] = byte(4) pos += 1 ip = net.ParseIP("2001:db8:0:1111::1").To16() copy(buf[pos:pos+16], []byte(ip)) pos += 16 binary.BigEndian.PutUint32(buf[pos:], 3) b = &NexthopLookupBody{Api: IPV6_NEXTHOP_LOOKUP} err = b.DecodeFromBytes(buf, 2) assert.Equal(nil, err) assert.Equal("2001:db8:0:f101::", b.Addr.String()) assert.Equal(uint32(10), b.Metric) assert.Equal(uint32(3), b.Nexthops[0].Ifindex) assert.Equal(NEXTHOP_FLAG(4), b.Nexthops[0].Type) assert.Equal("2001:db8:0:1111::1", b.Nexthops[0].Addr.String()) //Serialize buf, err = b.Serialize(2) ip = net.ParseIP("2001:db8:0:f101::").To16() assert.Equal(nil, err) assert.Equal([]byte(ip)[0:16], buf[0:16]) // length invalid buf = make([]byte, 15) b = &NexthopLookupBody{Api: IPV6_NEXTHOP_LOOKUP} err = b.DecodeFromBytes(buf, 2) assert.NotEqual(nil, err) } func Test_ImportLookupBody(t *testing.T) { assert := assert.New(t) //DecodeFromBytes pos := 0 buf := make([]byte, 18) ip := net.ParseIP("192.168.50.0").To4() copy(buf[0:4], []byte(ip)) pos += 4 binary.BigEndian.PutUint32(buf[pos:], 10) pos += 4 buf[pos] = byte(1) pos += 1 buf[pos] = byte(4) pos += 1 ip = net.ParseIP("172.16.1.101").To4() copy(buf[pos:pos+4], []byte(ip)) pos += 4 binary.BigEndian.PutUint32(buf[pos:], 3) b := &ImportLookupBody{Api: IPV4_IMPORT_LOOKUP} err := b.DecodeFromBytes(buf, 2) assert.Equal(nil, err) assert.Equal("192.168.50.0", b.Addr.String()) assert.Equal(uint32(10), b.Metric) assert.Equal(uint32(3), b.Nexthops[0].Ifindex) assert.Equal(NEXTHOP_FLAG(4), b.Nexthops[0].Type) assert.Equal("172.16.1.101", b.Nexthops[0].Addr.String()) //Serialize b.PrefixLength = uint8(24) buf, err = b.Serialize(2) ip = net.ParseIP("192.168.50.0").To4() assert.Equal(nil, err) assert.Equal(uint8(24), buf[0]) assert.Equal([]byte(ip)[0:4], buf[1:5]) // length invalid buf = make([]byte, 3) b = &ImportLookupBody{Api: IPV4_IMPORT_LOOKUP} err = b.DecodeFromBytes(buf, 2) assert.NotEqual(nil, err) } func Test_NexthopRegisterBody(t *testing.T) { assert := assert.New(t) // Input binary bufIn := []byte{ 0x01, 0x00, 0x02, 0x20, // connected(1 byte)=1, afi(2 bytes)=AF_INET, prefix_len(1 byte)=32 0xc0, 0xa8, 0x01, 0x01, // prefix(4 bytes)="192.168.1.1" 0x00, 0x00, 0x0a, 0x80, // connected(1 byte)=0, afi(2 bytes)=AF_INET6, prefix_len(1 byte)=128 0x20, 0x01, 0x0d, 0xb8, // prefix(16 bytes)="2001:db8:1:1::1" 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, } binary.BigEndian.PutUint16(bufIn[1:], syscall.AF_INET) binary.BigEndian.PutUint16(bufIn[9:], syscall.AF_INET6) // Test DecodeFromBytes() b := &NexthopRegisterBody{Api: NEXTHOP_REGISTER} err := b.DecodeFromBytes(bufIn, 3) assert.Nil(err) // Test decoded values assert.Equal(uint8(1), b.Nexthops[0].Connected) assert.Equal(uint16(syscall.AF_INET), b.Nexthops[0].Family) assert.Equal(net.ParseIP("192.168.1.1").To4(), b.Nexthops[0].Prefix) assert.Equal(uint8(0), b.Nexthops[1].Connected) assert.Equal(uint16(syscall.AF_INET6), b.Nexthops[1].Family) assert.Equal(net.ParseIP("2001:db8:1:1::1").To16(), b.Nexthops[1].Prefix) // Test Serialize() bufOut, err := b.Serialize(3) assert.Nil(err) // Test serialised value assert.Equal(bufIn, bufOut) } func Test_NexthopUpdateBody(t *testing.T) { assert := assert.New(t) // Input binary bufIn := []byte{ 0x00, 0x02, 0x20, // afi(2 bytes)=AF_INET, prefix_len(1 byte)=32 0xc0, 0xa8, 0x01, 0x01, // prefix(4 bytes)="192.168.1.1" 0x00, 0x00, 0x00, 0x01, // metric(4 bytes)=1 0x01, // nexthops(1 byte)=1 0x04, // nexthop_type(1 byte)=NEXTHOP_IPV4_IFINDEX 0xc0, 0xa8, 0x01, 0x01, // nexthop_ip(4 bytes)="192.168.0.1" 0x00, 0x00, 0x00, 0x02, // nexthop_ifindex(4 byte)=2 } // Test DecodeFromBytes() b := &NexthopUpdateBody{Api: NEXTHOP_UPDATE} err := b.DecodeFromBytes(bufIn, 2) assert.Nil(err) // Test decoded values assert.Equal(uint16(syscall.AF_INET), b.Family) assert.Equal(net.ParseIP("192.168.1.1").To4(), b.Prefix) assert.Equal(uint32(1), b.Metric) nexthop := &Nexthop{ Type: NEXTHOP_FLAG(NEXTHOP_IPV4_IFINDEX), Addr: net.ParseIP("192.168.1.1").To4(), Ifindex: uint32(2), } assert.Equal(1, len(b.Nexthops)) assert.Equal(nexthop, b.Nexthops[0]) } gobgp-1.29/zebra/zapi_windows.go000066400000000000000000000020401324612745600167200ustar00rootroot00000000000000// Copyright (C) 2014, 2015 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or // implied. // See the License for the specific language governing permissions and // limitations under the License. package zebra import ( "strings" "syscall" ) func intfflag2string(flag uint64) string { ss := make([]string, 0, 10) if flag&syscall.IFF_UP > 0 { ss = append(ss, "UP") } if flag&syscall.IFF_BROADCAST > 0 { ss = append(ss, "BROADCAST") } if flag&syscall.IFF_LOOPBACK > 0 { ss = append(ss, "LOOPBACK") } if flag&syscall.IFF_MULTICAST > 0 { ss = append(ss, "MULTICAST") } return strings.Join(ss, " | ") }