Initial migration from internal repositories.

This commit is contained in:
Michael Quigley
2019-12-03 12:09:18 -05:00
parent fa58583592
commit cc94b1c4e2
221 changed files with 20126 additions and 4 deletions
+201
View File
@@ -0,0 +1,201 @@
Apache 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.
+294 -4
View File
@@ -1,7 +1,297 @@
# Ziti Fabric
**This documentation requires revision after the migration to GitHub is complete.**
Ziti is a modern, programmable network overlay with associated edge components, for application-embedded, zero trust network connectivity, written by developers for developers. Ziti allows developers to take control of networking while with secure connectivity and advanced security concepts such as Zero Trust.
# Ziti Fabric Layer
The Ziti Fabric represents the geo-scale network overlay, and core network programming model for the Ziti ecosystem.
The Ziti Fabric is a long-haul transport fabric for the Ziti ecosystem.
Currently working through migrating our internal repositories over to GitHub. Stay tuned.
## Creating a Development Workspace
If you're going to be working on the Ziti fabric, or just want to track the latest development changes, you'll need a development workspace.
First, install the golang environment for your platform. Visit http://golang.org to download your installer.
As of this update, we're currently using version `1.13` of golang.
Make sure that `go` is in your path:
$ go version
go version go1.13.1 linux/amd64
You'll need to create a `GOPATH` for Ziti. I like to put mine in `~/Repos/nf/ziti`, but you can root it wherever you'd like. I'd suggest you call it `ziti`.
Essentially:
* `mkdir ziti`
* `cd ziti`
* `export GOPATH=$(pwd)`
When you've got your `GOPATH` ready, clone the repository.
* Clone this repository into `$GOPATH/src/bitbucket.org/netfoundry/ziti`
* `mkdir -p $GOPATH/src/bitbucket.org/netfoundry`
* `cd $GOPATH/src/bitbucket.org/netfoundry`
* `git clone git@bitbucket.org:netfoundry/ziti`
Build the tree:
$ go install ./...
The binaries will be placed in `$GOPATH/bin`.
The development configuration files live in `$GOPATH/src/github.com/netfoundry/ziti-fabric/fabric/etc`, and contain relative paths, which expect the executables to be started from `$GOPATH`.
$ cd $GOPATH
$ mkdir -p db
$ bin/ziti-controller run src/github.com/netfoundry/ziti-fabric/fabric/etc/ctrl.yml
## Launching A Simple Environment
You'll want to open a number of terminal windows. All commands are executed relative to `$GOPATH` (shell PWD is `$GOPATH`).
### Launch The Controller
`$ bin/ziti-controller run src/github.com/netfoundry/ziti-fabric/fabric/etc/ctrl.yml`
### Configure Dotzeet
In order to use the `ziti-fabric` tool, you'll need a working identity configuration in your home directory. Create the file `~/.ziti/identities.yml` containing the following. Substitute your concrete `$GOPATH` for the actual value of your `$GOPATH` in this file:
```
default:
caCert: "$GOPATH/src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/certs/ca-chain.cert.pem"
cert: "$GOPATH/src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/certs/dotzeet-client.cert.pem"
serverCert: "$GOPATH/src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/certs/dotzeet-server.cert.pem"
key: "$GOPATH/src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/private/dotzeet.key.pem"
endpoint: tls:127.0.0.1:10000
```
Where, `"caCert": "$GOPATH/src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/certs/ca-chain.cert.pem"` becomes `"caCert": "/home/michael/Repos/nf/ziti/src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/certs/ca-chain.cert.pem"`.
The `endpoint:` specification should point at the `mgmt` listener address for your `ziti-controller`.
### Enroll Routers
With your controller running, use the `ziti-fabric` tool to enroll routers:
$ bin/ziti-fabric create router src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/certs/001-client.cert.pem
$ bin/ziti-fabric create router src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/certs/002-client.cert.pem
$ bin/ziti-fabric create router src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/certs/003-client.cert.pem
$ bin/ziti-fabric create router src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/certs/004-client.cert.pem
### Start Routers
With your controller running, you can now start routers to begin building your mesh:
`$ bin/ziti-router run src/github.com/netfoundry/ziti-fabric/fabric/etc/001.yml`
There are 4 router configurations provided (001, 002, 003, 004).
Start routers 001, 002, and 003.
The configuration provided in the tree assembles a "diamond" shaped mesh, where router `001` is intended to initiate
(ingress) sessions, and router `003` is intended to terminate (egress) sessions. With smart routing and dynamic healing
in play, traffic can flow between router `001` and router `003` over either router `002` or `004`.
### Create a Google Service
Create a service to access `google.com`:
`$ bin/ziti-fabric create service google tcp:google.com:80 003`
### Access the Google Service
Access the google service using `ziti-fabric`:
`$ bin/ziti-fabric-test http http://google --host www.google.com`
You should see HTTP output from the google website.
## Using The Fabric InfluxDB Metrics Reporter
First you'll want to launch an InfluxDB docker container like this:
$ docker run --name influxdb -d -p 8086:8086 -v /opt/influxdb:/var/lib/influxdb influxdb
The directory `/opt/influxdb` should be changed to wherever you would like InfluxDB to persist its data.
Next, open an `influx` cli connection:
$ docker exec -it influxdb influx
Connected to http://localhost:8086 version 1.7.7
InfluxDB shell version: 1.7.7
>
Then create the `ziti` database:
> create database ziti
>
Exit the `influx` cli. InfluxDB is ready to go.
Make sure you uncomment the `metrics` section in the `fabric/etc/ctrl.yml`:
metrics:
influxdb:
enabled: true
url: http://localhost:8086
database: ziti
Restart your controller and metrics should begin flowing into InfluxDB. You can verify like this:
$ docker exec -it influxdb influx
Connected to http://localhost:8086 version 1.7.7
InfluxDB shell version: 1.7.7
> use ziti
Using database ziti
> show series
key
---
egress.rx.bytesrate,source=001
egress.rx.bytesrate,source=002
egress.rx.bytesrate,source=003
----8<----
link.OY8P.tx.msgsize,source=002
link.OY8P.tx.msgsize,source=003
link.yMey.latency,source=001
link.yMey.latency,source=002
link.yMey.rx.bytesrate,source=001
link.yMey.rx.bytesrate,source=002
link.yMey.rx.msgrate,source=001
link.yMey.rx.msgrate,source=002
link.yMey.rx.msgsize,source=001
link.yMey.rx.msgsize,source=002
link.yMey.tx.bytesrate,source=001
link.yMey.tx.bytesrate,source=002
link.yMey.tx.msgrate,source=001
link.yMey.tx.msgrate,source=002
link.yMey.tx.msgsize,source=001
link.yMey.tx.msgsize,source=002
> select * from "link.yMey.latency"
name: link.yMey.latency
time count max mean min p50 p75 p95 p99 p999 p9999 source stddev variance
---- ----- --- ---- --- --- --- --- --- ---- ----- ------ ------ --------
1563471804584021939 1 615541 615541 615541 615541 615541 615541 615541 615541 615541 001 0 0
1563471807396555092 1 549054 549054 549054 549054 549054 549054 549054 549054 549054 002 0 0
1563471819583707653 2 630068 622804.5 615541 622804.5 630068 630068 630068 630068 630068 001 7263.5 52758432.25
1563471822396356386 2 549054 506257 463460 506257 549054 549054 549054 549054 549054 002 42797 1831583209
> select * from "link.yMey.latency"
name: link.yMey.latency
time count max mean min p50 p75 p95 p99 p999 p9999 source stddev variance
---- ----- --- ---- --- --- --- --- --- ---- ----- ------ ------ --------
1563471804584021939 1 615541 615541 615541 615541 615541 615541 615541 615541 615541 001 0 0
1563471807396555092 1 549054 549054 549054 549054 549054 549054 549054 549054 549054 002 0 0
1563471819583707653 2 630068 622804.5 615541 622804.5 630068 630068 630068 630068 630068 001 7263.5 52758432.25
1563471822396356386 2 549054 506257 463460 506257 549054 549054 549054 549054 549054 002 42797 1831583209
1563471834584197383 3 630068 555820 421851 615541 630068 630068 630068 630068 630068 001 94915.85098742289 9009018768.666666
>
## Using The Fabric (JSON) Gateway
Launch the `ziti-fabric-gw` like this:
$ bin/ziti-fabric-gw run src/github.com/netfoundry/ziti-fabric/fabric/etc/gw.yml
Client access like this:
$ cd ${GOPATH}/src/bitbucket.org/netfoundry/ziti
$ curl --cacert fabric/etc/ca/intermediate/certs/ca-chain.cert.pem --cert fabric/etc/ca/intermediate/certs/ctrl-client.cert.pem --key fabric/etc/ca/intermediate/private/ctrl.key.pem https://127.0.0.1:10080/ctrl/services | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 443 100 443 0 0 88600 0 --:--:-- --:--:-- --:--:-- 88600
{
"data": [
{
"id": "google",
"endpointAddress": "tls:www.google.com:443",
"egress": "003",
"binding": ""
},
{
"id": "googles",
"endpointAddress": "tcp:www.google.com:443",
"egress": "003",
"binding": ""
},
{
"id": "loop",
"endpointAddress": "tcp:127.0.0.1:8171",
"egress": "003",
"binding": ""
},
{
"id": "loop-broken",
"endpointAddress": "tcp:127.0.0.1:8171",
"egress": "003",
"binding": ""
},
{
"id": "quigley",
"endpointAddress": "tcp:one.quigley.com:80",
"egress": "003",
"binding": ""
}
]
}
To use `ziti-fabric-gw` with a plain HTTP listener (without TLS), comment out the `certPath` and `keyPath` lines in `gw.yml`:
# Comment out the following lines to disable the TLS listener
#certPath: src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/certs/mgmt-gw.cert.pem
#keyPath: src/github.com/netfoundry/ziti-fabric/fabric/etc/ca/intermediate/private/mgmt-gw.key.pem
And then the listener can be accessed with plain HTTP:
$ curl http://127.0.0.1:10080/ctrl/services | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 443 100 443 0 0 432k 0 --:--:-- --:--:-- --:--:-- 432k
{
"data": [
{
"id": "google",
"binding": "",
"endpointAddress": "tls:www.google.com:443",
"egress": "003"
}
]
}
## Generating Network Load Using loop2
In order to create interesting metrics, you'll need to create some network load. A simple tool for that is the `ziti-fabric-test loop2` tool.
Create the `loop` service in the fabric (if it's not already there):
$ bin/ziti-fabric create service loop tcp:127.0.0.1:8171 003
Launch a `loop2` listener (in $GOPATH):
$ bin/ziti-fabric-test loop2 listener
Launch a `loop2` dialer (begin generating load):
$ bin/ziti-fabric-test loop2 dialer src/github.com/netfoundry/ziti-fabric/fabric/etc/loop2/10-ambient.loop2.yml
Take a look at the various `loop2` scenario configurations in `fabric/etc/loop2` for examples of how to create a specific kind of workload.
## Creating Keys and Certificates for Development
This is not necessary if you are using the PKI infrastructure that is already configured in the development config files (above).
This is only necessary if you want to generate your own PKI infrastructure for your own configuration.
ziti pki create --pki-root=/home/plorenz/tmp/002 ca --ca-name=root-ca --ca-file=root-ca
ziti pki create --pki-root=/home/plorenz/tmp/002/ --ca-name=root-ca intermediate
ziti pki create key --pki-root /home/plorenz/tmp/002/ --ca-name=intermediate --key-file ctrl
ziti pki create --pki-root=/home/plorenz/tmp/002/ --ca-name=intermediate server --server-file=ctrl-server --ip 127.0.0.1 --key-file ctrl
ziti pki create --pki-root=/home/plorenz/tmp/002/ --ca-name=intermediate client --client-file=ctrl-client --key-file ctrl
ziti pki create key --pki-root /home/plorenz/tmp/002/ --ca-name=intermediate --key-file dotzeet
ziti pki create --pki-root=/home/plorenz/tmp/002/ --ca-name=intermediate server --server-file=dotzeet-server --ip 127.0.0.1 --key-file dotzeet
ziti pki create --pki-root=/home/plorenz/tmp/002/ --ca-name=intermediate client --client-file=dotzeet-client --key-file dotzeet
ziti pki create key --pki-root /home/plorenz/tmp/002/ --ca-name=intermediate --key-file 002
ziti pki create --pki-root=/home/plorenz/tmp/002/ --ca-name=intermediate server --server-file=002-server --ip 127.0.0.1 --key-file 002 --server-name 002
ziti pki create --pki-root=/home/plorenz/tmp/002/ --ca-name=intermediate client --client-file=002-client --key-file 002 --client-name 002
+250
View File
@@ -0,0 +1,250 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 controller
import (
"github.com/netfoundry/ziti-foundation/config"
"github.com/netfoundry/ziti-fabric/fabric/controller/db"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/metrics"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/netfoundry/ziti-fabric/fabric/xgress"
"bytes"
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/netfoundry/ziti-foundation/transport"
"gopkg.in/yaml.v2"
"io/ioutil"
"time"
)
type Config struct {
Id *identity.TokenId
Network *network.Options
Db *db.Db
Trace struct {
Handler *channel2.TraceHandler
}
Profile struct {
Memory struct {
Path string
Interval time.Duration
}
CPU struct {
Path string
}
}
Ctrl struct {
Listener transport.Address
Options *channel2.Options
}
Mgmt struct {
Listener transport.Address
Options *channel2.Options
}
Metrics *metrics.Config
src map[interface{}]interface{}
}
func (config *Config) Configure(sub config.Subconfig) error {
return sub.LoadConfig(config.src)
}
func LoadConfig(path string) (*Config, error) {
cfgBytes, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
cfgmap := make(map[interface{}]interface{})
if err = yaml.NewDecoder(bytes.NewReader(cfgBytes)).Decode(&cfgmap); err != nil {
return nil, err
}
config.InjectEnv(cfgmap)
if value, found := cfgmap["v"]; found {
if value.(int) != 3 {
panic("config version mismatch: see docs for information on config updates")
}
} else {
panic("no config version: see docs for information on config")
}
identityConfig := identity.IdentityConfig{}
if value, found := cfgmap["identity"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
if value, found := submap["key"]; found {
identityConfig.Key = value.(string)
}
if value, found := submap["cert"]; found {
identityConfig.Cert = value.(string)
}
if value, found := submap["server_cert"]; found {
identityConfig.ServerCert = value.(string)
}
if value, found := submap["server_key"]; found {
identityConfig.ServerKey = value.(string)
}
if value, found := submap["ca"]; found {
identityConfig.CA = value.(string)
}
}
}
config := &Config{
Network: network.DefaultOptions(),
src: cfgmap,
}
if id, err := identity.LoadIdentity(identityConfig); err != nil {
return nil, fmt.Errorf("unable to load identity (%s)", err)
} else {
config.Id = identity.NewIdentity(id)
}
if value, found := cfgmap["network"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
if options, err := network.LoadOptions(submap); err == nil {
config.Network = options
} else {
return nil, fmt.Errorf("invalid 'network' stanza (%s)", err)
}
} else {
pfxlog.Logger().Warn("invalid or empty 'network' stanza")
}
}
if value, found := cfgmap["db"]; found {
str, err := db.Open(value.(string))
if err != nil {
return nil, err
}
config.Db = str
} else {
panic("config must provide [db]")
}
if value, found := cfgmap["trace"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
if value, found := submap["path"]; found {
handler, err := channel2.NewTraceHandler(value.(string), config.Id)
if err != nil {
return nil, err
}
handler.AddDecoder(&channel2.Decoder{})
handler.AddDecoder(&ctrl_pb.Decoder{})
handler.AddDecoder(&xgress.Decoder{})
handler.AddDecoder(&mgmt_pb.Decoder{})
config.Trace.Handler = handler
}
}
}
if value, found := cfgmap["profile"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
if value, found := submap["memory"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
if value, found := submap["path"]; found {
config.Profile.Memory.Path = value.(string)
}
if value, found := submap["intervalMs"]; found {
config.Profile.Memory.Interval = time.Duration(value.(int)) * time.Millisecond
} else {
config.Profile.Memory.Interval = 15 * time.Second
}
}
}
if value, found := submap["cpu"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
if value, found := submap["path"]; found {
config.Profile.CPU.Path = value.(string)
}
}
}
}
}
if value, found := cfgmap["ctrl"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
if value, found := submap["listener"]; found {
listener, err := transport.ParseAddress(value.(string))
if err != nil {
return nil, err
}
config.Ctrl.Listener = listener
} else {
panic("config must provide [ctrl/listener]")
}
config.Ctrl.Options = channel2.DefaultOptions()
if value, found := submap["options"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
config.Ctrl.Options = channel2.LoadOptions(submap)
}
}
if config.Trace.Handler != nil {
config.Ctrl.Options.PeekHandlers = append(config.Ctrl.Options.PeekHandlers, config.Trace.Handler)
}
} else {
panic("config [ctrl] section in unexpected format")
}
} else {
panic("config must provide [ctrl]")
}
if value, found := cfgmap["mgmt"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
if value, found := submap["listener"]; found {
listener, err := transport.ParseAddress(value.(string))
if err != nil {
return nil, err
}
config.Mgmt.Listener = listener
} else {
panic("config must provide [mgmt/listener]")
}
config.Mgmt.Options = channel2.DefaultOptions()
if value, found := submap["options"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
config.Mgmt.Options = channel2.LoadOptions(submap)
}
}
} else {
panic("config [mgmt] section in unexpected format")
}
} else {
panic("config must provide [mgmt]")
}
if value, found := cfgmap["metrics"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
if metricsCfg, err := metrics.LoadConfig(submap); err == nil {
config.Metrics = metricsCfg
} else {
return nil, fmt.Errorf("error loading metrics config (%s)", err)
}
} else {
pfxlog.Logger().Warn("invalid or empty [metrics] stanza")
}
}
return config, nil
}
+168
View File
@@ -0,0 +1,168 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 controller
import (
"github.com/netfoundry/ziti-fabric/fabric/controller/handler_ctrl"
"github.com/netfoundry/ziti-fabric/fabric/controller/handler_mgmt"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/xctrl"
"github.com/netfoundry/ziti-fabric/fabric/xctrl_example"
"github.com/netfoundry/ziti-fabric/fabric/xmgmt"
"github.com/netfoundry/ziti-foundation/profiler"
"encoding/json"
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-foundation/channel2"
)
type Controller struct {
config *Config
network *network.Network
ctrlConnectHandler *handler_ctrl.ConnectHandler
mgmtConnectHandler *handler_mgmt.ConnectHandler
xctrls []xctrl.Xctrl
xmgmts []xmgmt.Xmgmt
ctrlListener channel2.UnderlayListener
mgmtListener channel2.UnderlayListener
}
func NewController(cfg *Config) (*Controller, error) {
c := &Controller{
config: cfg,
}
if n, err := network.NewNetwork(cfg.Id, cfg.Network, cfg.Db, cfg.Metrics); err == nil {
c.network = n
} else {
return nil, err
}
if err := c.showOptions(); err != nil {
return nil, err
}
return c, nil
}
func (c *Controller) Run() error {
c.startProfiling()
if err := c.registerComponents(); err != nil {
return fmt.Errorf("error registering component: %s", err)
}
/**
* ctrl listener/accepter.
*/
c.ctrlListener = channel2.NewClassicListener(c.config.Id, c.config.Ctrl.Listener)
if err := c.ctrlListener.Listen(c.ctrlConnectHandler); err != nil {
panic(err)
}
ctrlAccepter := handler_ctrl.NewCtrlAccepter(c.network, c.xctrls, c.ctrlListener, c.config.Ctrl.Options)
go ctrlAccepter.Run()
/* */
/**
* mgmt listener/accepter.
*/
c.mgmtListener = channel2.NewClassicListener(c.config.Id, c.config.Mgmt.Listener)
if err := c.mgmtListener.Listen(c.mgmtConnectHandler); err != nil {
panic(err)
}
mgmtAccepter := handler_mgmt.NewMgmtAccepter(c.mgmtListener, c.config.Mgmt.Options)
go mgmtAccepter.Run()
/* */
c.network.Run()
return nil
}
func (c *Controller) Shutdown() {
if c.ctrlListener != nil {
if err := c.ctrlListener.Close(); err != nil {
pfxlog.Logger().WithError(err).Error("failed to close ctrl channel listener")
}
}
if c.mgmtListener != nil {
if err := c.mgmtListener.Close(); err != nil {
pfxlog.Logger().WithError(err).Error("failed to close mgmt channel listener")
}
}
c.network.Shutdown()
}
func (c *Controller) showOptions() error {
if ctrl, err := json.MarshalIndent(c.config.Ctrl.Options, "", " "); err == nil {
pfxlog.Logger().Infof("ctrl = %s", string(ctrl))
} else {
return err
}
if mgmt, err := json.MarshalIndent(c.config.Mgmt.Options, "", " "); err == nil {
pfxlog.Logger().Infof("mgmt = %s", string(mgmt))
} else {
return err
}
return nil
}
func (c *Controller) startProfiling() {
if c.config.Profile.Memory.Path != "" {
go profiler.NewMemory(c.config.Profile.Memory.Path, c.config.Profile.Memory.Interval).Run()
}
}
func (c *Controller) registerComponents() error {
c.ctrlConnectHandler = handler_ctrl.NewConnectHandler(c.network, c.xctrls)
c.mgmtConnectHandler = handler_mgmt.NewConnectHandler(c.network)
c.config.Mgmt.Options.BindHandlers = []channel2.BindHandler{handler_mgmt.NewBindHandler(c.network, c.xmgmts)}
if err := c.RegisterXctrl(xctrl_example.NewExample()); err != nil {
return err
}
return nil
}
func (c *Controller) RegisterXctrl(x xctrl.Xctrl) error {
if err := c.config.Configure(x); err != nil {
return err
}
if x.Enabled() {
c.xctrls = append(c.xctrls, x)
}
return nil
}
func (c *Controller) RegisterXmgmt(x xmgmt.Xmgmt) error {
if err := c.config.Configure(x); err != nil {
return err
}
if x.Enabled() {
c.xmgmts = append(c.xmgmts, x)
}
return nil
}
func (c *Controller) GetNetwork() *network.Network {
return c.network
}
+70
View File
@@ -0,0 +1,70 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 db
import (
"errors"
"fmt"
"go.etcd.io/bbolt"
)
type Db struct {
db *bbolt.DB
}
func Open(path string) (*Db, error) {
db, err := bbolt.Open(path, 0600, nil)
if err != nil {
return nil, fmt.Errorf("unable to open controller database [%s] (%s)", path, err)
}
if err := db.Update(createRoots); err != nil {
return nil, err
}
return &Db{db: db}, nil
}
func (db *Db) Close() error {
return db.db.Close()
}
func (db *Db) Update(fn func(tx *bbolt.Tx) error) error {
return db.db.Update(fn)
}
func (db *Db) View(fn func(tx *bbolt.Tx) error) error {
return db.db.View(fn)
}
func (db *Db) RootBucket(tx *bbolt.Tx) (*bbolt.Bucket, error) {
ziti := tx.Bucket([]byte("ziti"))
if ziti == nil {
return nil, errors.New("db missing 'ziti' root")
}
return ziti, nil
}
func createRoots(tx *bbolt.Tx) error {
if ziti, err := tx.CreateBucketIfNotExists([]byte("ziti")); err == nil {
if _, err := ziti.CreateBucketIfNotExists([]byte("services")); err != nil {
return err
}
if _, err := ziti.CreateBucketIfNotExists([]byte("routers")); err != nil {
return err
}
}
return nil
}
+90
View File
@@ -0,0 +1,90 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_ctrl
import (
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/xctrl"
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-foundation/transport"
)
type CtrlAccepter struct {
network *network.Network
xctrls []xctrl.Xctrl
listener channel2.UnderlayListener
options *channel2.Options
}
func NewCtrlAccepter(network *network.Network,
xctrls []xctrl.Xctrl,
listener channel2.UnderlayListener,
options *channel2.Options) *CtrlAccepter {
return &CtrlAccepter{
network: network,
xctrls: xctrls,
listener: listener,
options: options,
}
}
func (ctrlAccepter *CtrlAccepter) Run() {
log := pfxlog.Logger()
log.Info("started")
defer log.Warn("exited")
for {
ch, err := channel2.NewChannel("ctrl", ctrlAccepter.listener, ctrlAccepter.options)
if err == nil {
if r, err := ctrlAccepter.network.KnownRouter(ch.Id().Token); err == nil {
if ch.Underlay().Headers() != nil {
if listenerValue, found := ch.Underlay().Headers()[channel2.HelloListenerHeader]; found {
listenerString := string(listenerValue)
if listener, err := transport.ParseAddress(listenerString); err == nil {
r.AdvertisedListener = listener
} else {
log.Errorf("error parsing listener address (%s)", err)
_ = ch.Close()
continue
}
} else {
log.Warn("missing listener attribute")
}
} else {
log.Warn("no attributes provided")
}
r.Control = ch
if err := r.Control.Bind(newBindHandler(r, ctrlAccepter.network, ctrlAccepter.xctrls)); err != nil {
log.Errorf("error binding router (%s)", err)
_ = ch.Close()
continue
}
ctrlAccepter.network.ConnectRouter(r)
log.Infof("accepted new router connection [r/%s]", r.Id)
}
} else {
log.Errorf("error accepting (%s)", err)
if err.Error() == "closed" {
return
}
}
}
}
+62
View File
@@ -0,0 +1,62 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_ctrl
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/trace"
"github.com/netfoundry/ziti-fabric/fabric/xctrl"
)
type bindHandler struct {
router *network.Router
network *network.Network
xctrls []xctrl.Xctrl
}
func newBindHandler(router *network.Router, network *network.Network, xctrls []xctrl.Xctrl) *bindHandler {
return &bindHandler{router: router, network: network, xctrls: xctrls}
}
func (bindHandler *bindHandler) BindChannel(ch channel2.Channel) error {
ch.SetLogicalName(bindHandler.router.Id)
ch.AddReceiveHandler(newSessionRequestHandler(bindHandler.router, bindHandler.network))
ch.AddReceiveHandler(newHostRequestHandler(bindHandler.router, bindHandler.network))
ch.AddReceiveHandler(newLinkHandler(bindHandler.router, bindHandler.network))
ch.AddReceiveHandler(newFaultHandler(bindHandler.router, bindHandler.network))
ch.AddReceiveHandler(newMetricsHandler(bindHandler.network))
ch.AddReceiveHandler(bindHandler.network.GetTraceEventController())
ch.AddReceiveHandler(newInspectHandler(bindHandler.network))
ch.AddPeekHandler(trace.NewChannelPeekHandler(bindHandler.network.GetAppId(), ch, bindHandler.network.GetTraceController(), bindHandler.network.GetTraceEventController()))
xctrlDone := make(chan struct{})
for _, x := range bindHandler.xctrls {
if err := ch.Bind(x); err != nil {
return err
}
if err := x.Run(ch, bindHandler.network.GetDb(), xctrlDone); err != nil {
return err
}
}
if len(bindHandler.xctrls) > 0 {
ch.AddCloseHandler(newXctrlCloseHandler(xctrlDone))
}
ch.AddCloseHandler(newCloseHandler(bindHandler.router, bindHandler.network))
return nil
}
@@ -0,0 +1,74 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_ctrl
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type hostRequestHandler struct {
r *network.Router
network *network.Network
}
var successResponse = &ctrl_pb.BindResponse{Success: true}
func newHostRequestHandler(r *network.Router, network *network.Network) *hostRequestHandler {
return &hostRequestHandler{r: r, network: network}
}
func (h *hostRequestHandler) ContentType() int32 {
return int32(ctrl_pb.ContentType_BindRequestType)
}
func (h *hostRequestHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
log := pfxlog.ContextLogger(ch.Label())
request := &ctrl_pb.BindRequest{}
if err := proto.Unmarshal(msg.Body, request); err != nil {
log.Errorf("unexpected error (%s)", err)
return
}
response := successResponse
var err error
if request.BindType == ctrl_pb.BindType_Bind {
err = h.network.BindService(h.r, request.Token, request.ServiceId)
} else if request.BindType == ctrl_pb.BindType_Unbind {
err = h.network.UnbindService(h.r, request.Token, request.ServiceId)
}
if err != nil {
log.Errorf("unexpected error (%s)", err)
response = &ctrl_pb.BindResponse{Success: false, Message: err.Error()}
}
if body, err := proto.Marshal(response); err == nil {
responseMsg := channel2.NewMessage(int32(ctrl_pb.ContentType_BindResponseType), body)
responseMsg.ReplyTo(msg)
if err := h.r.Control.Send(responseMsg); err != nil {
log.Errorf("unable to respond (%s)", err)
}
} else {
log.Errorf("unexpected error (%s)", err)
}
}
+51
View File
@@ -0,0 +1,51 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_ctrl
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/michaelquigley/pfxlog"
)
type closeHandler struct {
r *network.Router
network *network.Network
}
func newCloseHandler(r *network.Router, network *network.Network) *closeHandler {
return &closeHandler{r: r, network: network}
}
func (h *closeHandler) HandleClose(ch channel2.Channel) {
log := pfxlog.ContextLogger("r/" + h.r.Id)
log.Warn("disconnected")
h.network.DisconnectRouter(h.r)
}
type xctrlCloseHandler struct {
done chan struct{}
}
func newXctrlCloseHandler(done chan struct{}) channel2.CloseHandler {
return &xctrlCloseHandler{done: done}
}
func (h *xctrlCloseHandler) HandleClose(ch channel2.Channel) {
pfxlog.ContextLogger(ch.Label()).Info("closing Xctrl instances")
close(h.done)
}
+74
View File
@@ -0,0 +1,74 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_ctrl
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/xctrl"
"crypto/sha1"
"crypto/x509"
"errors"
"fmt"
"github.com/michaelquigley/pfxlog"
)
type ConnectHandler struct {
network *network.Network
xctrls []xctrl.Xctrl
}
func NewConnectHandler(network *network.Network, xctrls []xctrl.Xctrl) *ConnectHandler {
return &ConnectHandler{network: network, xctrls: xctrls}
}
func (h *ConnectHandler) HandleConnection(hello *channel2.Hello, certificates []*x509.Certificate) error {
log := pfxlog.ContextLogger(hello.IdToken)
id := hello.IdToken
/*
* Control channel connections dump the client-supplied certificate details. We'll
* soon be using these certificates for router enrollment.
*/
fingerprint := ""
if certificates != nil {
log.Debugf("peer has [%d] certificates", len(certificates))
for i, c := range certificates {
fingerprint = fmt.Sprintf("%x", sha1.Sum(c.Raw))
log.Debugf("%d): peer certificate fingerprint [%s]", i, fingerprint)
log.Debugf("%d): peer common name [%s]", i, c.Subject.CommonName)
}
} else {
log.Warnf("peer has no certificates")
}
/* */
if h.network.ConnectedRouter(id) {
return errors.New("router already connected")
}
if r, err := h.network.KnownRouter(id); err == nil {
if r.Fingerprint != fingerprint {
return errors.New("unenrolled router")
}
} else {
return errors.New("unenrolled router")
}
return nil
}
+76
View File
@@ -0,0 +1,76 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_ctrl
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type faultHandler struct {
r *network.Router
network *network.Network
}
func newFaultHandler(r *network.Router, network *network.Network) *faultHandler {
return &faultHandler{r: r, network: network}
}
func (h *faultHandler) ContentType() int32 {
return int32(ctrl_pb.ContentType_FaultType)
}
func (h *faultHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
log := pfxlog.ContextLogger(ch.Label())
fault := &ctrl_pb.Fault{}
if err := proto.Unmarshal(msg.Body, fault); err == nil {
switch fault.Subject {
case ctrl_pb.FaultSubject_LinkFault:
linkId := &identity.TokenId{Token: fault.Id}
if err := h.network.LinkConnected(linkId, false); err == nil {
if link, found := h.network.GetLink(linkId); found {
h.network.LinkChanged(link)
}
log.Infof("link fault [l/%s]", linkId.Token)
}
case ctrl_pb.FaultSubject_IngressFault:
if err := h.network.RemoveSession(&identity.TokenId{Token: fault.Id}, false); err != nil {
log.Errorf("error handling ingress fault (%s)", err)
} else {
log.Debugf("handled ingress fault for (%s)", fault.Id)
}
case ctrl_pb.FaultSubject_EgressFault:
if err := h.network.RemoveSession(&identity.TokenId{Token: fault.Id}, false); err != nil {
log.Errorf("error handling egress fault (%s)", err)
} else {
log.Debugf("handled egress fault for (%s)", fault.Id)
}
default:
log.Errorf("unexpected subject (%s)", fault.Subject.String())
}
} else {
log.Errorf("unexpected error (%s)", err)
}
}
+77
View File
@@ -0,0 +1,77 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_ctrl
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type inspectHandler struct {
network *network.Network
}
func newInspectHandler(network *network.Network) *inspectHandler {
return &inspectHandler{network: network}
}
func (h *inspectHandler) ContentType() int32 {
return int32(ctrl_pb.ContentType_InspectRequestType)
}
func (h *inspectHandler) HandleReceive(request *channel2.Message, ch channel2.Channel) {
log := pfxlog.ContextLogger(ch.Label())
inspectRequest := &ctrl_pb.InspectRequest{}
if err := proto.Unmarshal(request.Body, inspectRequest); err != nil {
log.Errorf("unexpected error (%s)", err)
h.respondWithError(ch, request, fmt.Errorf("failed to decode request: (%v)", err).Error())
}
response := &ctrl_pb.InspectResponse{Success: true}
for _, value := range inspectRequest.RequestedValues {
if value == "capability" {
for _, capability := range h.network.GetCapabilities() {
response.AddValue("capability", capability)
}
}
}
h.respond(ch, request, response)
}
func (h *inspectHandler) respondWithError(ch channel2.Channel, request *channel2.Message, errs...string) {
response := &ctrl_pb.InspectResponse{Success: false, Errors: errs }
h.respond(ch, request, response)
}
func (h *inspectHandler) respond(ch channel2.Channel, request *channel2.Message, response *ctrl_pb.InspectResponse) {
log := pfxlog.ContextLogger(ch.Label())
if body, err := proto.Marshal(response); err == nil {
responseMsg := channel2.NewMessage(int32(ctrl_pb.ContentType_InspectResponseType), body)
responseMsg.ReplyTo(request)
if err := ch.Send(responseMsg); err != nil {
log.Errorf("unable to respond to inspect request(%s)", err)
}
} else {
log.Errorf("unexpected error marshalling response (%s)", err)
}
}
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_ctrl
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type linkHandler struct {
r *network.Router
network *network.Network
}
func newLinkHandler(r *network.Router, network *network.Network) *linkHandler {
return &linkHandler{r: r, network: network}
}
func (h *linkHandler) ContentType() int32 {
return int32(ctrl_pb.ContentType_LinkType)
}
func (h *linkHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
log := pfxlog.ContextLogger(ch.Label())
link := &ctrl_pb.Link{}
if err := proto.Unmarshal(msg.Body, link); err == nil {
if err := h.network.LinkConnected(&identity.TokenId{Token: link.Id}, true); err == nil {
log.Infof("link connected [l/%s]", link.Id)
} else {
log.Errorf("unexpected error (%s)", err)
}
} else {
log.Errorf("unexpected error (%s)", err)
}
}
+47
View File
@@ -0,0 +1,47 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_ctrl
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/metrics"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type metricsHandler struct {
metrics.EventController
}
func newMetricsHandler(network *network.Network) *metricsHandler {
return &metricsHandler{network.GetMetricsEventController()}
}
func (h *metricsHandler) ContentType() int32 {
return int32(ctrl_pb.ContentType_MetricsType)
}
func (h *metricsHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
metricsMsg := &ctrl_pb.MetricsMessage{}
if err := proto.Unmarshal(msg.Body, metricsMsg); err == nil {
h.AcceptMetrics(metricsMsg)
} else {
pfxlog.ContextLogger(ch.Label()).Errorf("unexpected error (%s)", err)
}
}
@@ -0,0 +1,83 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_ctrl
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/ctrl_msg"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
"time"
)
type sessionRequestHandler struct {
r *network.Router
network *network.Network
}
func newSessionRequestHandler(r *network.Router, network *network.Network) *sessionRequestHandler {
return &sessionRequestHandler{r: r, network: network}
}
func (h *sessionRequestHandler) ContentType() int32 {
return int32(ctrl_pb.ContentType_SessionRequestType)
}
func (h *sessionRequestHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
log := pfxlog.ContextLogger(ch.Label())
request := &ctrl_pb.SessionRequest{}
if err := proto.Unmarshal(msg.Body, request); err == nil {
/*
* This is running in a goroutine because CreateSession does a 'SendAndWait', which cannot be invoked from
* inside a ReceiveHandler (without parallel support).
*/
go func() {
if session, err := h.network.CreateSession(h.r, &identity.TokenId{Token: request.IngressId}, request.ServiceId); err == nil {
responseMsg := ctrl_msg.NewSessionSuccessMsg(session.Id.Token, session.Circuit.IngressId)
responseMsg.ReplyTo(msg)
if startXgressSession, err := h.r.Control.SendAndWaitWithTimeout(responseMsg, time.Second*10); err != nil {
log.Errorf("unable to respond (%s)", err)
h.network.RemoveSession(session.Id, true)
} else {
if startXgressSession.ContentType == int32(ctrl_pb.ContentType_StartXgressType) {
if err = h.network.StartSessionEgress(session.Id); err != nil {
log.WithError(err).Error("failure starting xgress")
h.network.RemoveSession(session.Id, true)
}
} else {
log.Errorf("unexpected reply to dial response: %v", startXgressSession.ContentType)
h.network.RemoveSession(session.Id, true)
}
}
} else {
responseMsg := ctrl_msg.NewSessionFailedMsg(err.Error())
responseMsg.ReplyTo(msg)
if err := h.r.Control.Send(responseMsg); err != nil {
log.Errorf("unable to respond (%s)", err)
}
}
}()
/* */
} else {
log.Errorf("unexpected error (%s)", err)
}
}
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-foundation/channel2"
)
type MgmtAccepter struct {
listener channel2.UnderlayListener
options *channel2.Options
}
func NewMgmtAccepter(listener channel2.UnderlayListener,
options *channel2.Options) *MgmtAccepter {
return &MgmtAccepter{
listener: listener,
options: options,
}
}
func (mgmtAccepter *MgmtAccepter) Run() {
log := pfxlog.Logger()
log.Info("started")
defer log.Warn("exited")
for {
ch, err := channel2.NewChannel("mgmt", mgmtAccepter.listener, mgmtAccepter.options)
if err == nil {
log.Debugf("accepted mgmt connection [%s]", ch.Id().Token)
} else {
log.Errorf("error accepting (%s)", err)
if err.Error() == "closed" {
return
}
}
}
}
+80
View File
@@ -0,0 +1,80 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/trace"
"github.com/netfoundry/ziti-fabric/fabric/xmgmt"
)
type BindHandler struct {
network *network.Network
xmgmts []xmgmt.Xmgmt
}
func NewBindHandler(network *network.Network, xmgmts []xmgmt.Xmgmt) *BindHandler {
return &BindHandler{network: network, xmgmts: xmgmts}
}
func (bindHandler *BindHandler) BindChannel(ch channel2.Channel) error {
network := bindHandler.network
ch.AddReceiveHandler(newCreateRouterHandler(network))
ch.AddReceiveHandler(newCreateServiceHandler(network))
ch.AddReceiveHandler(newGetServiceHandler(network))
ch.AddReceiveHandler(newInspectHandler(network))
ch.AddReceiveHandler(newListLinksHandler(network))
ch.AddReceiveHandler(newListRoutersHandler(network))
ch.AddReceiveHandler(newListServicesHandler(network))
ch.AddReceiveHandler(newListSessionsHandler(network))
ch.AddReceiveHandler(newRemoveRouterHandler(network))
ch.AddReceiveHandler(newRemoveServiceHandler(network))
ch.AddReceiveHandler(newRemoveSessionHandler(network))
ch.AddReceiveHandler(newSetLinkCostHandler(network))
ch.AddReceiveHandler(newSetLinkDownHandler(network))
streamMetricHandler := newStreamMetricsHandler(network)
ch.AddReceiveHandler(streamMetricHandler)
ch.AddCloseHandler(streamMetricHandler)
streamSessionsHandler := newStreamSessionsHandler(network)
ch.AddReceiveHandler(streamSessionsHandler)
ch.AddCloseHandler(streamSessionsHandler)
streamTracesHandler := newStreamTracesHandler(network)
ch.AddReceiveHandler(streamTracesHandler)
ch.AddCloseHandler(streamTracesHandler)
ch.AddReceiveHandler(newTogglePipeTracesHandler(network))
ch.AddPeekHandler(trace.NewChannelPeekHandler(network.GetAppId(), ch, network.GetTraceController(), network.GetTraceEventController()))
xmgmtDone := make(chan struct{})
for _, x := range bindHandler.xmgmts {
if err := ch.Bind(x); err != nil {
return err
}
if err := x.Run(ch, xmgmtDone); err != nil {
return err
}
}
if len(bindHandler.xmgmts) > 0 {
ch.AddCloseHandler(newXmgmtCloseHandler(xmgmtDone))
}
return nil
}
+35
View File
@@ -0,0 +1,35 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/michaelquigley/pfxlog"
)
type xmgmtCloseHandler struct {
done chan struct{}
}
func newXmgmtCloseHandler(done chan struct{}) channel2.CloseHandler {
return &xmgmtCloseHandler{done: done}
}
func (h *xmgmtCloseHandler) HandleClose(ch channel2.Channel) {
pfxlog.Logger().Infof("closing Xmgmt instances for [%s]", ch.Label())
close(h.done)
}
+58
View File
@@ -0,0 +1,58 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"crypto/x509"
"github.com/michaelquigley/pfxlog"
)
type ConnectHandler struct {
network *network.Network
}
func NewConnectHandler(network *network.Network) *ConnectHandler {
return &ConnectHandler{
network: network,
}
}
func (h *ConnectHandler) HandleConnection(hello *channel2.Hello, certificates []*x509.Certificate) error {
return nil
}
func sendSuccess(request *channel2.Message, ch channel2.Channel, message string) {
sendResult(request, ch, message, true)
}
func sendFailure(request *channel2.Message, ch channel2.Channel, message string) {
sendResult(request, ch, message, false)
}
func sendResult(request *channel2.Message, ch channel2.Channel, message string, success bool) {
log := pfxlog.ContextLogger(ch.Label())
if !success {
log.Errorf("mgmt error (%s)", message)
}
response := channel2.NewResult(success, message)
response.ReplyTo(request)
_ = ch.Send(response)
log.Debug("success")
}
@@ -0,0 +1,54 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type createRouterHandler struct {
network *network.Network
}
func newCreateRouterHandler(network *network.Network) *createRouterHandler {
return &createRouterHandler{network: network}
}
func (h *createRouterHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_CreateRouterRequestType)
}
func (h *createRouterHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
log := pfxlog.ContextLogger(ch.Label())
create := &mgmt_pb.CreateRouterRequest{}
if err := proto.Unmarshal(msg.Body, create); err == nil {
r := network.NewRouter(create.Router.Id, create.Router.Fingerprint)
if err := h.network.CreateRouter(r); err == nil {
log.Infof("created router [r/%s] with fingerprint [%s]", r.Id, r.Fingerprint)
sendSuccess(msg, ch, "")
} else {
sendFailure(msg, ch, err.Error())
}
} else {
sendFailure(msg, ch, err.Error())
}
}
@@ -0,0 +1,59 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/golang/protobuf/proto"
)
type createServiceHandler struct {
network *network.Network
}
func newCreateServiceHandler(network *network.Network) *createServiceHandler {
return &createServiceHandler{network: network}
}
func (h *createServiceHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_CreateServiceRequestType)
}
func (h *createServiceHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
cs := &mgmt_pb.CreateServiceRequest{}
err := proto.Unmarshal(msg.Body, cs)
if err == nil {
binding := "transport"
if cs.Service.Binding != "" {
binding = cs.Service.Binding
}
if err = h.network.CreateService(&network.Service{
Id: cs.Service.Id,
Binding: binding,
EndpointAddress: cs.Service.EndpointAddress,
Egress: cs.Service.Egress,
}); err == nil {
sendSuccess(msg, ch, "")
} else {
sendFailure(msg, ch, err.Error())
}
} else {
sendFailure(msg, ch, err.Error())
}
}
@@ -0,0 +1,68 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type getServiceHandler struct {
network *network.Network
}
func newGetServiceHandler(network *network.Network) *getServiceHandler {
return &getServiceHandler{network: network}
}
func (h *getServiceHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_GetServiceRequestType)
}
func (h *getServiceHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
rs := &mgmt_pb.GetServiceRequest{}
err := proto.Unmarshal(msg.Body, rs)
if err == nil {
response := &mgmt_pb.GetServiceResponse{}
if svc, found := h.network.GetService(rs.ServiceId); found {
response.Service = &mgmt_pb.Service{
Id: svc.Id,
Binding: svc.Binding,
EndpointAddress: svc.EndpointAddress,
Egress: svc.Egress,
}
body, err := proto.Marshal(response)
if err == nil {
responseMsg := channel2.NewMessage(int32(mgmt_pb.ContentType_GetServiceResponseType), body)
responseMsg.ReplyTo(msg)
ch.Send(responseMsg)
} else {
pfxlog.ContextLogger(ch.Label()).Errorf("unexpected error (%s)", err)
}
} else {
sendFailure(msg, ch, "no such service")
}
} else {
sendFailure(msg, ch, err.Error())
}
}
+196
View File
@@ -0,0 +1,196 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/netfoundry/ziti-foundation/util/debugz"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
"regexp"
"strings"
"sync"
"time"
)
type inspectHandler struct {
network *network.Network
}
func newInspectHandler(network *network.Network) *inspectHandler {
return &inspectHandler{network: network}
}
func (*inspectHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_InspectRequestType)
}
func (handler *inspectHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
context := &inspectRequestContext{
handler: handler,
msg: msg,
ch: ch,
request: &mgmt_pb.InspectRequest{},
response: &mgmt_pb.InspectResponse{Success: true},
}
var err error
if err = proto.Unmarshal(msg.Body, context.request); err != nil {
context.appendError(handler.network.GetAppId().Token, err.Error())
}
context.regex, err = regexp.Compile(context.request.AppRegex)
if err != nil {
context.appendError(handler.network.GetAppId().Token, err.Error())
}
if !context.response.Success {
context.sendResponse()
return
}
context.processLocal()
context.processRemote()
context.sendResponse()
}
type inspectRequestContext struct {
handler *inspectHandler
msg *channel2.Message
ch channel2.Channel
request *mgmt_pb.InspectRequest
response *mgmt_pb.InspectResponse
regex *regexp.Regexp
}
func (context *inspectRequestContext) processLocal() {
if context.regex.MatchString(context.handler.network.GetAppId().Token) {
for _, requested := range context.request.RequestedValues {
if strings.ToLower(requested) == "stackdump" {
context.appendValue(context.handler.network.GetAppId().Token, requested, debugz.GenerateStack())
}
}
}
}
func (context *inspectRequestContext) processRemote() {
routerRequest := &ctrl_pb.InspectRequest{RequestedValues: context.request.RequestedValues}
body, err := proto.Marshal(routerRequest)
if err != nil {
context.appendError(context.handler.network.GetAppId().Token, err.Error())
return
}
remoteResultChan := make(chan *remoteInspectResult)
waitGroup := &sync.WaitGroup{}
for _, router := range context.handler.network.AllConnectedRouters() {
if context.regex.MatchString(router.Id) {
msg := channel2.NewMessage(int32(ctrl_pb.ContentType_InspectRequestType), body)
respCh, err := router.Control.SendAndWait(msg)
if err != nil {
context.appendError(router.Id, err.Error())
} else {
waitGroup.Add(1)
go handleInspectResponse(router.Id, respCh, remoteResultChan, waitGroup)
}
}
}
// Close chan once all results have been queued
go func() {
waitGroup.Wait()
close(remoteResultChan)
}()
for remoteResult := range remoteResultChan {
for _, err := range remoteResult.result.Errors {
context.appendError(remoteResult.appId, err)
}
for _, val := range remoteResult.result.Values {
context.appendValue(remoteResult.appId, val.Name, val.Value)
}
}
}
func handleInspectResponse(appId string, respCh <-chan *channel2.Message, msgsCh chan<- *remoteInspectResult, waitGroup *sync.WaitGroup) {
defer waitGroup.Done()
timeout := time.After(time.Second * 5)
select {
case response, ok := <-respCh:
if !ok {
routerInspectFailed(appId, msgsCh, "no response from router")
} else if response.ContentType == int32(ctrl_pb.ContentType_InspectResponseType) {
result := &ctrl_pb.InspectResponse{}
if err := proto.Unmarshal(response.Body, result); err != nil {
routerInspectFailed(appId, msgsCh, fmt.Sprintf("Failed to decode response from router: %v", err))
} else {
msgsCh <- &remoteInspectResult{appId: appId, result: result}
}
} else {
msg := fmt.Sprintf("Unexpected response type from router %v: %v\n", appId, response.ContentType)
routerInspectFailed(appId, msgsCh, msg)
}
case <-timeout:
routerInspectFailed(appId, msgsCh, "timed out waiting for response from router")
}
}
func routerInspectFailed(appId string, msgsCh chan<- *remoteInspectResult, msg string) {
msgsCh <- &remoteInspectResult{
appId: appId,
result: &ctrl_pb.InspectResponse{Errors: []string{msg}},
}
}
func (context *inspectRequestContext) sendResponse() {
body, err := proto.Marshal(context.response)
if err != nil {
pfxlog.Logger().Errorf("unexpected error serializing InspectResponse (%s)", err)
return
}
responseMsg := channel2.NewMessage(int32(mgmt_pb.ContentType_InspectResponseType), body)
responseMsg.ReplyTo(context.msg)
if err := context.ch.Send(responseMsg); err != nil {
pfxlog.Logger().Errorf("unexpected error sending InspectResponse (%s)", err)
}
}
func (context *inspectRequestContext) appendValue(appId string, name string, value string) {
context.response.Values = append(context.response.Values, &mgmt_pb.InspectResponse_InspectValue{
AppId: appId,
Name: name,
Value: value,
})
}
func (context *inspectRequestContext) appendError(appId string, err string) {
context.response.Success = false
context.response.Errors = append(context.response.Errors, fmt.Sprintf("%v: %v", appId, err))
}
type remoteInspectResult struct {
appId string
result *ctrl_pb.InspectResponse
}
@@ -0,0 +1,75 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type listLinksHandler struct {
network *network.Network
}
func newListLinksHandler(network *network.Network) *listLinksHandler {
return &listLinksHandler{network: network}
}
func (h *listLinksHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_ListLinksRequestType)
}
func (h *listLinksHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
list := &mgmt_pb.ListLinksRequest{}
if err := proto.Unmarshal(msg.Body, list); err != nil {
sendFailure(msg, ch, err.Error())
return
}
links := h.network.GetAllLinks()
response := &mgmt_pb.ListLinksResponse{
Links: make([]*mgmt_pb.Link, 0),
}
for _, l := range links {
responseLink := &mgmt_pb.Link{
Id: l.Id.Token,
Src: l.Src.Id,
Dst: l.Dst.Id,
State: l.CurrentState().Mode.String(),
Down: l.Down,
Cost: int32(l.Cost),
SrcLatency: l.SrcLatency,
DstLatency: l.DstLatency,
}
response.Links = append(response.Links, responseLink)
}
body, err := proto.Marshal(response)
if err != nil {
sendFailure(msg, ch, err.Error())
return
}
responseMsg := channel2.NewMessage(int32(mgmt_pb.ContentType_ListLinksResponseType), body)
responseMsg.ReplyTo(msg)
if err := ch.Send(responseMsg); err != nil {
pfxlog.Logger().Errorf("unexpected error sending response (%s)", err)
}
}
@@ -0,0 +1,79 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type listRoutersHandler struct {
network *network.Network
}
func newListRoutersHandler(network *network.Network) *listRoutersHandler {
return &listRoutersHandler{network: network}
}
func (h *listRoutersHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_ListRoutersRequestType)
}
func (h *listRoutersHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
log := pfxlog.ContextLogger(ch.Label())
list := &mgmt_pb.ListRoutersRequest{}
if err := proto.Unmarshal(msg.Body, list); err == nil {
response := &mgmt_pb.ListRoutersResponse{Routers: make([]*mgmt_pb.Router, 0)}
if routers, err := h.network.AllRouters(); err == nil {
log.Infof("got [%d] routers", len(routers))
for _, r := range routers {
connected := false
if connR, found := h.network.GetConnectedRouter(r.Id); found {
connected = true
r.AdvertisedListener = connR.AdvertisedListener
}
responseR := &mgmt_pb.Router{
Id: r.Id,
Fingerprint: r.Fingerprint,
Connected: connected,
}
if r.AdvertisedListener != nil {
responseR.ListenerAddress = r.AdvertisedListener.String()
}
response.Routers = append(response.Routers, responseR)
}
if body, err := proto.Marshal(response); err == nil {
responseMsg := channel2.NewMessage(int32(mgmt_pb.ContentType_ListRoutersResponseType), body)
responseMsg.ReplyTo(msg)
if err := ch.Send(responseMsg); err != nil {
pfxlog.ContextLogger(ch.Label()).Errorf("unexpected error sending response (%s)", err)
}
} else {
sendFailure(msg, ch, err.Error())
}
} else {
sendFailure(msg, ch, err.Error())
}
}
}
@@ -0,0 +1,72 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type listServicesHandler struct {
network *network.Network
}
func newListServicesHandler(network *network.Network) *listServicesHandler {
return &listServicesHandler{network: network}
}
func (h *listServicesHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_ListServicesRequestType)
}
func (h *listServicesHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
ls := &mgmt_pb.ListServicesRequest{}
err := proto.Unmarshal(msg.Body, ls)
if err == nil {
response := &mgmt_pb.ListServicesResponse{Services: make([]*mgmt_pb.Service, 0)}
svcs, err := h.network.AllServices()
if err == nil {
for _, s := range svcs {
response.Services = append(response.Services, &mgmt_pb.Service{
Id: s.Id,
Binding: s.Binding,
EndpointAddress: s.EndpointAddress,
Egress: s.Egress,
})
}
body, err := proto.Marshal(response)
if err == nil {
responseMsg := channel2.NewMessage(int32(mgmt_pb.ContentType_ListServicesResponseType), body)
responseMsg.ReplyTo(msg)
if err := ch.Send(responseMsg); err != nil {
pfxlog.ContextLogger(ch.Label()).Errorf("unexpected error sending response (%s)", err)
}
} else {
sendFailure(msg, ch, err.Error())
}
} else {
sendFailure(msg, ch, err.Error())
}
} else {
sendFailure(msg, ch, err.Error())
}
}
@@ -0,0 +1,81 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type listSessionsHandler struct {
network *network.Network
}
func newListSessionsHandler(network *network.Network) *listSessionsHandler {
return &listSessionsHandler{network: network}
}
func (h *listSessionsHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_ListSessionsRequestType)
}
func (h *listSessionsHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
list := &mgmt_pb.ListSessionsRequest{}
if err := proto.Unmarshal(msg.Body, list); err != nil {
sendFailure(msg, ch, err.Error())
return
}
sessions := h.network.GetAllSessions()
response := &mgmt_pb.ListSessionsResponse{
Sessions: make([]*mgmt_pb.Session, 0),
}
for _, s := range sessions {
responseSession := &mgmt_pb.Session{
Id: s.Id.Token,
ClientId: s.ClientId.Token,
ServiceId: s.Service.Id,
Circuit: NewCircuit(s.Circuit),
}
response.Sessions = append(response.Sessions, responseSession)
}
body, err := proto.Marshal(response)
if err != nil {
sendFailure(msg, ch, err.Error())
return
}
responseMsg := channel2.NewMessage(int32(mgmt_pb.ContentType_ListSessionsResponseType), body)
responseMsg.ReplyTo(msg)
if err := ch.Send(responseMsg); err != nil {
pfxlog.ContextLogger(ch.Label()).Errorf("unexpected error sending response (%s)", err)
}
}
func NewCircuit(circuit *network.Circuit) *mgmt_pb.Circuit {
mgmtCircuit := &mgmt_pb.Circuit{}
for _, r := range circuit.Path {
mgmtCircuit.Path = append(mgmtCircuit.Path, r.Id)
}
for _, l := range circuit.Links {
mgmtCircuit.Links = append(mgmtCircuit.Links, l.Id.Token)
}
return mgmtCircuit
}
@@ -0,0 +1,58 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type removeRouterHandler struct {
network *network.Network
}
func newRemoveRouterHandler(network *network.Network) *removeRouterHandler {
return &removeRouterHandler{network: network}
}
func (h *removeRouterHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_RemoveRouterRequestType)
}
func (h *removeRouterHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
log := pfxlog.ContextLogger(ch.Label())
remove := &mgmt_pb.RemoveRouterRequest{}
if err := proto.Unmarshal(msg.Body, remove); err == nil {
router, err := h.network.GetRouter(remove.RouterId)
if err == nil {
if err := h.network.RemoveRouter(router); err == nil {
log.Infof("removed router [r/%s]", remove.RouterId)
sendSuccess(msg, ch, "")
} else {
sendFailure(msg, ch, err.Error())
}
} else {
sendFailure(msg, ch, err.Error())
}
} else {
sendFailure(msg, ch, err.Error())
}
}
@@ -0,0 +1,54 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/golang/protobuf/proto"
)
type removeServiceHandler struct {
network *network.Network
}
func newRemoveServiceHandler(network *network.Network) *removeServiceHandler {
return &removeServiceHandler{network: network}
}
func (h *removeServiceHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_RemoveServiceRequestType)
}
func (h *removeServiceHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
rs := &mgmt_pb.RemoveServiceRequest{}
err := proto.Unmarshal(msg.Body, rs)
if err == nil {
if svc, found := h.network.GetService(rs.ServiceId); found {
if err := h.network.RemoveService(svc.Id); err != nil {
sendFailure(msg, ch, err.Error())
} else {
sendSuccess(msg, ch, "")
}
} else {
sendFailure(msg, ch, "no such service")
}
} else {
sendFailure(msg, ch, err.Error())
}
}
@@ -0,0 +1,53 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type removeSessionHandler struct {
network *network.Network
}
func newRemoveSessionHandler(network *network.Network) *removeSessionHandler {
return &removeSessionHandler{network: network}
}
func (handler *removeSessionHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_RemoveSessionRequestType)
}
func (handler *removeSessionHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
request := &mgmt_pb.RemoveSessionRequest{}
if err := proto.Unmarshal(msg.Body, request); err == nil {
sessionId := &identity.TokenId{Token: request.SessionId}
if err := handler.network.RemoveSession(sessionId, request.Now); err == nil {
sendSuccess(msg, ch, "")
} else {
pfxlog.Logger().Errorf("unexpected error removing session s/[%s] (%s)", sessionId.Token, err)
sendFailure(msg, ch, err.Error())
}
} else {
sendFailure(msg, ch, err.Error())
}
}
@@ -0,0 +1,57 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/netfoundry/ziti-foundation/identity/identity"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type setLinkCostHandler struct {
network *network.Network
}
func newSetLinkCostHandler(network *network.Network) *setLinkCostHandler {
return &setLinkCostHandler{network: network}
}
func (h *setLinkCostHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_SetLinkCostRequestType)
}
func (h *setLinkCostHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
log := pfxlog.ContextLogger(ch.Label())
set := &mgmt_pb.SetLinkCostRequest{}
if err := proto.Unmarshal(msg.Body, set); err == nil {
if l, found := h.network.GetLink(&identity.TokenId{Token: set.LinkId}); found {
l.Cost = int(set.Cost)
h.network.LinkChanged(l)
log.Infof("set cost of link [l/%s] to [%d]", set.LinkId, set.Cost)
sendSuccess(msg, ch, "")
} else {
sendFailure(msg, ch, fmt.Sprintf("unknown link [l/%s]", set.LinkId))
}
} else {
sendFailure(msg, ch, err.Error())
}
}
@@ -0,0 +1,57 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/netfoundry/ziti-foundation/identity/identity"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type setLinkDownHandler struct {
network *network.Network
}
func newSetLinkDownHandler(network *network.Network) *setLinkDownHandler {
return &setLinkDownHandler{network: network}
}
func (h *setLinkDownHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_SetLinkDownRequestType)
}
func (h *setLinkDownHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
log := pfxlog.ContextLogger(ch.Label())
set := &mgmt_pb.SetLinkDownRequest{}
if err := proto.Unmarshal(msg.Body, set); err == nil {
if l, found := h.network.GetLink(&identity.TokenId{Token: set.LinkId}); found {
l.Down = set.Down
h.network.LinkChanged(l)
log.Infof("set down state of link [l/%s] to [%t]", set.LinkId, set.Down)
sendSuccess(msg, ch, "")
} else {
sendFailure(msg, ch, fmt.Sprintf("unknown link [l/%s]", set.LinkId))
}
} else {
sendFailure(msg, ch, err.Error())
}
}
@@ -0,0 +1,235 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/metrics"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes/timestamp"
"github.com/michaelquigley/pfxlog"
"regexp"
)
type streamMetricsHandler struct {
network *network.Network
streamHandlers []metrics.Handler
}
func newStreamMetricsHandler(network *network.Network) *streamMetricsHandler {
return &streamMetricsHandler{network: network}
}
func (*streamMetricsHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_StreamMetricsRequestType)
}
func (handler *streamMetricsHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
request := &mgmt_pb.StreamMetricsRequest{}
if err := proto.Unmarshal(msg.Body, request); err != nil {
sendFailure(msg, ch, err.Error())
return
}
filters, err := parseFilters(request)
if err != nil {
sendFailure(msg, ch, err.Error())
return
}
metricsStreamHandler := &MetricsStreamHandler{
ch: ch,
filters: filters,
eventController: handler.network.GetMetricsEventController(),
}
handler.streamHandlers = append(handler.streamHandlers, metricsStreamHandler)
handler.network.GetMetricsEventController().AddHandler(metricsStreamHandler)
}
func (handler *streamMetricsHandler) HandleClose(ch channel2.Channel) {
for _, streamHandler := range handler.streamHandlers {
handler.network.GetMetricsEventController().RemoveHandler(streamHandler)
}
}
func parseFilters(msg *mgmt_pb.StreamMetricsRequest) ([]*metricsFilter, error) {
var filters []*metricsFilter
for _, filterDef := range msg.Matchers {
filter := &metricsFilter{}
if filterDef.NameRegex != "" {
regex, err := regexp.Compile(filterDef.NameRegex)
if err != nil {
return nil, err
}
filter.metricFilter = regex
}
if filterDef.NameRegex != "" {
regex, err := regexp.Compile(filterDef.SourceIDRegex)
if err != nil {
return nil, err
}
filter.sourceFilter = regex
}
filters = append(filters, filter)
}
return filters, nil
}
type metricsFilter struct {
sourceFilter *regexp.Regexp
metricFilter *regexp.Regexp
}
type MetricsStreamHandler struct {
ch channel2.Channel
filters []*metricsFilter
eventController metrics.EventController
}
func (handler *MetricsStreamHandler) AcceptMetrics(msg *ctrl_pb.MetricsMessage) {
if handler.filters == nil {
handler.filter(nil, msg)
return
}
var filters []*metricsFilter
for _, filter := range handler.filters {
if filter.sourceFilter == nil || filter.sourceFilter.Match([]byte(msg.SourceId)) {
filters = append(filters, filter)
}
}
if len(filters) > 0 {
handler.filter(filters, msg)
}
}
func (handler *MetricsStreamHandler) filter(filters []*metricsFilter, msg *ctrl_pb.MetricsMessage) {
event := &mgmt_pb.StreamMetricsEvent{
SourceId: msg.SourceId,
Timestamp: msg.Timestamp,
Tags: msg.Tags,
}
for name, value := range msg.IntValues {
filterIntMetric(name, value, event, filters)
}
for name, value := range msg.FloatValues {
filterFloatMetric(name, value, event, filters)
}
for name, value := range msg.Meters {
filterIntMetric(name+".count", value.Count, event, filters)
filterFloatMetric(name+".mean_rate", value.MeanRate, event, filters)
filterFloatMetric(name+".m1_rate", value.M1Rate, event, filters)
filterFloatMetric(name+".m5_rate", value.M5Rate, event, filters)
filterFloatMetric(name+".m15_rate", value.M15Rate, event, filters)
}
for name, value := range msg.Histograms {
filterIntMetric(name+".count", value.Count, event, filters)
filterIntMetric(name+".min", value.Min, event, filters)
filterIntMetric(name+".max", value.Max, event, filters)
filterFloatMetric(name+".mean", value.Mean, event, filters)
filterFloatMetric(name+".std_dev", value.StdDev, event, filters)
filterFloatMetric(name+".variance", value.Variance, event, filters)
filterFloatMetric(name+".p50", value.P50, event, filters)
filterFloatMetric(name+".p75", value.P75, event, filters)
filterFloatMetric(name+".p95", value.P95, event, filters)
filterFloatMetric(name+".p99", value.P99, event, filters)
filterFloatMetric(name+".p999", value.P999, event, filters)
filterFloatMetric(name+".p9999", value.P9999, event, filters)
}
for name, interval := range msg.IntervalCounters {
if nameMatches(name, filters) {
for _, bucket := range interval.Buckets {
intervalMetric := &mgmt_pb.StreamMetricsEvent_IntervalMetric{
Name: name,
}
intervalMetric.Values = bucket.Values
intervalMetric.IntervalStartUTC = &timestamp.Timestamp{Seconds: bucket.IntervalStartUTC}
intervalEndSeconds := bucket.IntervalStartUTC + int64(interval.IntervalLength)
intervalMetric.IntervalEndUTC = &timestamp.Timestamp{Seconds: intervalEndSeconds}
event.IntervalMetrics = append(event.IntervalMetrics, intervalMetric)
}
}
}
if len(event.IntMetrics) > 0 || len(event.FloatMetrics) > 0 || len(event.IntervalMetrics) > 0 {
handler.send(event)
}
}
func filterIntMetric(name string, value int64, event *mgmt_pb.StreamMetricsEvent, filters []*metricsFilter) {
if nameMatches(name, filters) {
if event.IntMetrics == nil {
event.IntMetrics = make(map[string]int64)
}
event.IntMetrics[name] = value
}
}
func filterFloatMetric(name string, value float64, event *mgmt_pb.StreamMetricsEvent, filters []*metricsFilter) {
if nameMatches(name, filters) {
if event.FloatMetrics == nil {
event.FloatMetrics = make(map[string]float64)
}
event.FloatMetrics[name] = value
}
}
func nameMatches(name string, filters []*metricsFilter) bool {
if len(filters) == 0 {
return true
}
for _, filter := range filters {
if filter.metricFilter == nil || filter.metricFilter.Match([]byte(name)) {
return true
}
}
return false
}
func (handler *MetricsStreamHandler) send(msg *mgmt_pb.StreamMetricsEvent) {
body, err := proto.Marshal(msg)
if err != nil {
pfxlog.Logger().Errorf("unexpected error serializing StreamMetricsEvent (%s)", err)
return
}
responseMsg := channel2.NewMessage(int32(mgmt_pb.ContentType_StreamMetricsEventType), body)
if err := handler.ch.Send(responseMsg); err != nil {
pfxlog.Logger().Errorf("unexpected error sending StreamMetricsEvent (%s)", err)
handler.close()
}
}
func (handler *MetricsStreamHandler) close() {
if err := handler.ch.Close(); err != nil {
pfxlog.Logger().WithError(err).Errorf("failure while closing handler")
}
handler.eventController.RemoveHandler(handler)
}
@@ -0,0 +1,115 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type streamSessionsHandler struct {
network *network.Network
streamHandlers []network.SessionLifeCycleListener
}
func newStreamSessionsHandler(network *network.Network) *streamSessionsHandler {
return &streamSessionsHandler{network: network}
}
func (*streamSessionsHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_StreamSessionsRequestType)
}
func (handler *streamSessionsHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
request := &mgmt_pb.StreamSessionsRequest{}
if err := proto.Unmarshal(msg.Body, request); err != nil {
sendFailure(msg, ch, err.Error())
return
}
sessionEventRouter := handler.network.GetSessionLifeCycleController()
sessionsStreamHandler := &SessionsStreamHandler{
ch: ch,
router: sessionEventRouter,
}
handler.streamHandlers = append(handler.streamHandlers, sessionsStreamHandler)
sessionEventRouter.AddListener(sessionsStreamHandler)
}
func (handler *streamSessionsHandler) HandleClose(ch channel2.Channel) {
for _, listener := range handler.streamHandlers {
handler.network.GetSessionLifeCycleController().RemoveListener(listener)
}
}
type SessionsStreamHandler struct {
ch channel2.Channel
router network.SessionLifeCycleController
}
func (handler *SessionsStreamHandler) SessionCreated(sessionId *identity.TokenId, clientId *identity.TokenId, serviceId string, circuit *network.Circuit) {
event := &mgmt_pb.StreamSessionsEvent{
EventType: mgmt_pb.StreamSessionEventType_SessionCreated,
SessionId: sessionId.Token,
ClientId: clientId.Token,
ServiceId: serviceId,
Circuit: NewCircuit(circuit),
}
handler.sendEvent(event)
}
func (handler *SessionsStreamHandler) SessionDeleted(sessionId *identity.TokenId) {
event := &mgmt_pb.StreamSessionsEvent{
EventType: mgmt_pb.StreamSessionEventType_SessionDeleted,
SessionId: sessionId.Token,
}
handler.sendEvent(event)
}
func (handler *SessionsStreamHandler) CircuitUpdated(sessionId *identity.TokenId, circuit *network.Circuit) {
event := &mgmt_pb.StreamSessionsEvent{
EventType: mgmt_pb.StreamSessionEventType_CircuitUpdated,
SessionId: sessionId.Token,
Circuit: NewCircuit(circuit),
}
handler.sendEvent(event)
}
func (handler *SessionsStreamHandler) sendEvent(event *mgmt_pb.StreamSessionsEvent) {
body, err := proto.Marshal(event)
if err != nil {
pfxlog.Logger().Errorf("unexpected error serializing StreamSessionsEvent (%s)", err)
return
}
responseMsg := channel2.NewMessage(int32(mgmt_pb.ContentType_StreamSessionsEventType), body)
if err := handler.ch.Send(responseMsg); err != nil {
pfxlog.Logger().Errorf("unexpected error sending StreamMetricsEvent (%s)", err)
handler.close()
}
}
func (handler *SessionsStreamHandler) close() {
if err := handler.ch.Close(); err != nil {
pfxlog.Logger().WithError(err).Error("unexpected error closing mgmt channel")
}
handler.router.RemoveListener(handler)
}
@@ -0,0 +1,171 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/netfoundry/ziti-fabric/fabric/trace"
trace_pb "github.com/netfoundry/ziti-foundation/trace/pb"
"fmt"
"github.com/golang/protobuf/proto"
"sync"
"time"
)
type traceTogglePipeHandler struct {
network *network.Network
}
func newTogglePipeTracesHandler(network *network.Network) *traceTogglePipeHandler {
return &traceTogglePipeHandler{network: network}
}
func (*traceTogglePipeHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_TogglePipeTracesRequestType)
}
func (handler *traceTogglePipeHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
request := &trace_pb.TogglePipeTracesRequest{}
if err := proto.Unmarshal(msg.Body, request); err != nil {
sendFailure(msg, ch, err.Error())
return
}
matchers, result := trace.NewPipeToggleMatchers(request)
if !result.Success {
handler.complete(msg, ch, result)
return
}
resultChan := make(chan trace.ToggleApplyResult)
verbosity := trace.GetVerbosity(request.Verbosity)
if checkMatch(handler.network.GetAppId().Token, matchers, verbosity, result) {
if request.Enable {
handler.network.GetTraceController().EnableTracing(trace.SourceTypePipe, matchers.PipeMatcher, resultChan)
} else {
handler.network.GetTraceController().DisableTracing(trace.SourceTypePipe, matchers.PipeMatcher, resultChan)
}
getApplyResults(resultChan, verbosity, result)
}
if !result.Success {
handler.complete(msg, ch, result)
return
}
remoteResultChan := make(chan *remoteToggleResult)
waitGroup := &sync.WaitGroup{}
for _, router := range handler.network.AllConnectedRouters() {
if checkMatch(router.Id, matchers, verbosity, result) {
msg := channel2.NewMessage(int32(ctrl_pb.ContentType_TogglePipeTracesRequestType), msg.Body)
respCh, err := router.Control.SendAndWait(msg)
if err != nil {
result.Success = false
result.Append(fmt.Sprintf("Trace enable of Router %v failed with: %v", router.Id, err))
} else {
waitGroup.Add(1)
go handleResponse(router.Id, respCh, remoteResultChan, waitGroup)
}
}
}
// Close chan once all results have been queued
go func() {
waitGroup.Wait()
close(remoteResultChan)
}()
for remoteToggleResult := range remoteResultChan {
if !remoteToggleResult.success {
result.Success = false
}
result.Message.WriteString(remoteToggleResult.message)
}
handler.complete(msg, ch, result)
}
func (handler *traceTogglePipeHandler) complete(msg *channel2.Message, ch channel2.Channel, result *trace.ToggleResult) {
if result.Success {
sendSuccess(msg, ch, result.Message.String())
} else {
sendFailure(msg, ch, result.Message.String())
}
}
func checkMatch(appId string, matchers *trace.PipeToggleMatchers, verbosity trace.ToggleVerbosity, result *trace.ToggleResult) bool {
appMatches := matchers.AppMatcher.Matches(appId)
applyResult := &trace.ToggleApplyResultImpl{
Matched: appMatches,
Message: fmt.Sprintf("App %v matched? %v", appId, appMatches),
}
applyResult.Append(result, verbosity)
return appMatches
}
func getApplyResults(resultChan chan trace.ToggleApplyResult, verbosity trace.ToggleVerbosity, result *trace.ToggleResult) {
timout := time.After(time.Second * 5)
for {
select {
case applyResult, chanOpen := <-resultChan:
if !chanOpen {
return
}
applyResult.Append(result, verbosity)
case <-timout:
result.Success = false
result.Append("Timed out waiting for toggle to be applied to controller")
return
}
}
}
func handleResponse(targetId string, respCh <-chan *channel2.Message, msgsCh chan<- *remoteToggleResult, waitGroup *sync.WaitGroup) {
defer waitGroup.Done()
timeout := time.After(time.Second * 5)
select {
case response, ok := <-respCh:
if !ok {
msg := fmt.Sprintf("No response from router %v\n", targetId)
msgsCh <- &remoteToggleResult{false, msg}
} else if response.ContentType == channel2.ContentTypeResultType {
result := channel2.UnmarshalResult(response)
msgsCh <- &remoteToggleResult{result.Success, result.Message}
} else {
msg := fmt.Sprintf("Unexpected response type from router %v: %v\n", targetId, response.ContentType)
msgsCh <- &remoteToggleResult{false, msg}
}
case <-timeout:
msg := fmt.Sprintf("Timed out waiting for response from router %v\n", targetId)
msgsCh <- &remoteToggleResult{false, msg}
}
}
type remoteToggleResult struct {
success bool
message string
}
@@ -0,0 +1,102 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 handler_mgmt
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/controller/network"
"github.com/netfoundry/ziti-fabric/fabric/pb/mgmt_pb"
"github.com/netfoundry/ziti-fabric/fabric/trace"
"github.com/netfoundry/ziti-foundation/trace/pb"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
type streamTracesHandler struct {
network *network.Network
streamHandlers []trace.EventHandler
}
func newStreamTracesHandler(network *network.Network) *streamTracesHandler {
return &streamTracesHandler{network: network}
}
func (*streamTracesHandler) ContentType() int32 {
return int32(mgmt_pb.ContentType_StreamTracesRequestType)
}
func (handler *streamTracesHandler) HandleReceive(msg *channel2.Message, ch channel2.Channel) {
request := &mgmt_pb.StreamTracesRequest{}
if err := proto.Unmarshal(msg.Body, request); err != nil {
sendFailure(msg, ch, err.Error())
return
}
eventRouter := handler.network.GetTraceEventController()
filter := createFilter(request)
eventHandler := &traceEventsHandler{ch, eventRouter, filter}
handler.streamHandlers = append(handler.streamHandlers, eventHandler)
eventRouter.AddHandler(eventHandler)
}
func (handler *streamTracesHandler) HandleClose(ch channel2.Channel) {
for _, streamHandler := range handler.streamHandlers {
handler.network.GetTraceEventController().RemoveHandler(streamHandler)
}
}
func createFilter(request *mgmt_pb.StreamTracesRequest) trace.Filter {
if !request.EnabledFilter {
return trace.NewAllowAllFilter()
}
if request.FilterType == mgmt_pb.TraceFilterType_INCLUDE {
return trace.NewIncludeFilter(request.ContentTypes)
}
return trace.NewExcludeFilter(request.ContentTypes)
}
type traceEventsHandler struct {
ch channel2.Channel
router trace.EventController
filter trace.Filter
}
func (handler *traceEventsHandler) Accept(event *trace_pb.ChannelMessage) {
if !handler.filter.Accept(event) {
return
}
body, err := proto.Marshal(event)
if err != nil {
pfxlog.Logger().Errorf("unexpected error unmarshalling ChannelMessage (%s)", err)
return
}
responseMsg := channel2.NewMessage(int32(mgmt_pb.ContentType_StreamTracesEventType), body)
if err := handler.ch.Send(responseMsg); err != nil {
pfxlog.Logger().Errorf("unexpected error sending ChannelMessage (%s)", err)
handler.close()
}
}
func (handler *traceEventsHandler) close() {
if err := handler.ch.Close(); err != nil {
pfxlog.Logger().WithError(err).Error("unexpected error while closing mgmt channel")
}
handler.router.RemoveHandler(handler)
}
+74
View File
@@ -0,0 +1,74 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/netfoundry/ziti-foundation/util/info"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
)
func (network *Network) assemble() {
log := pfxlog.Logger()
if network.routerController.count() > 1 {
log.Debugf("assembling with [%d] routers", network.routerController.count())
missingLinks, err := network.linkController.missingLinks(network.routerController.all())
if err == nil {
for _, missingLink := range missingLinks {
network.linkController.add(missingLink)
dial := &ctrl_pb.Dial{
Id: missingLink.Id.Token,
Address: missingLink.Dst.AdvertisedListener.String(),
}
bytes, err := proto.Marshal(dial)
if err == nil {
msg := channel2.NewMessage(int32(ctrl_pb.ContentType_DialType), bytes)
missingLink.Src.Control.Send(msg)
} else {
log.Errorf("unexpected error (%s)", err)
}
}
} else {
log.WithField("err", err).Error("missing link enumeration failed")
}
}
}
func (network *Network) clean() {
log := pfxlog.Logger()
failedLinks := network.linkController.linksInMode(Failed)
now := info.NowInMilliseconds()
lRemove := make(map[string]*Link)
for _, l := range failedLinks {
if now-l.CurrentState().Timestamp >= 30000 {
lRemove[l.Id.Token] = l
}
}
for _, lr := range lRemove {
log.Infof("removing [l/%s]", lr.Id.Token)
network.linkController.remove(lr)
}
}
+155
View File
@@ -0,0 +1,155 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/netfoundry/ziti-foundation/identity/identity"
"fmt"
)
type Circuit struct {
Path []*Router
Links []*Link
Binding string
IngressId string
EgressId string
}
func (circuit *Circuit) String() string {
if len(circuit.Path) < 1 {
return "{}"
}
if len(circuit.Links) != len(circuit.Path) - 1 {
return "{malformed}"
}
out := fmt.Sprintf("[r/%s]", circuit.Path[0].Id)
for i := 0; i < len(circuit.Links); i++ {
out += fmt.Sprintf("->[l/%s]", circuit.Links[i].Id.Token)
out += fmt.Sprintf("->[r/%s]", circuit.Path[i+1].Id)
}
return out
}
func (circuit *Circuit) EqualPath(other *Circuit) bool {
if len(circuit.Path) != len(other.Path) {
return false
}
if len(circuit.Links) != len(other.Links) {
return false
}
for i := 0; i < len(circuit.Path); i++ {
if circuit.Path[i] != other.Path[i] {
return false
}
}
for i := 0; i < len(circuit.Links); i++ {
if circuit.Links[i] != other.Links[i] {
return false
}
}
return true
}
func (circuit *Circuit) EgressRouter() *Router {
if len(circuit.Path) > 0 {
return circuit.Path[len(circuit.Path)-1]
}
return nil
}
func (circuit *Circuit) CreateRouteMessages(sessionId *identity.TokenId, egressAddress string) ([]*ctrl_pb.Route, error) {
var routeMessages []*ctrl_pb.Route
if len(circuit.Links) == 0 {
// single router path
routeMessage := &ctrl_pb.Route{SessionId: sessionId.Token}
routeMessage.Forwards = append(routeMessage.Forwards, &ctrl_pb.Route_Forward{
SrcAddress: circuit.IngressId,
DstAddress: circuit.EgressId,
})
routeMessage.Forwards = append(routeMessage.Forwards, &ctrl_pb.Route_Forward{
SrcAddress: circuit.EgressId,
DstAddress: circuit.IngressId,
})
routeMessage.Egress = &ctrl_pb.Route_Egress{
Binding: circuit.Binding,
Address: circuit.EgressId,
Destination: egressAddress,
}
routeMessages = append(routeMessages, routeMessage)
}
for i, link := range circuit.Links {
if i == 0 {
// ingress
routeMessage := &ctrl_pb.Route{SessionId: sessionId.Token}
routeMessage.Forwards = append(routeMessage.Forwards, &ctrl_pb.Route_Forward{
SrcAddress: circuit.IngressId,
DstAddress: link.Id.Token,
})
routeMessage.Forwards = append(routeMessage.Forwards, &ctrl_pb.Route_Forward{
SrcAddress: link.Id.Token,
DstAddress: circuit.IngressId,
})
routeMessages = append(routeMessages, routeMessage)
}
if i >= 0 && i < len(circuit.Links)-1 {
// transit
nextLink := circuit.Links[i+1]
routeMessage := &ctrl_pb.Route{SessionId: sessionId.Token}
routeMessage.Forwards = append(routeMessage.Forwards, &ctrl_pb.Route_Forward{
SrcAddress: link.Id.Token,
DstAddress: nextLink.Id.Token,
})
routeMessage.Forwards = append(routeMessage.Forwards, &ctrl_pb.Route_Forward{
SrcAddress: nextLink.Id.Token,
DstAddress: link.Id.Token,
})
routeMessages = append(routeMessages, routeMessage)
}
if i == len(circuit.Links)-1 {
// egress
routeMessage := &ctrl_pb.Route{SessionId: sessionId.Token}
routeMessage.Egress = &ctrl_pb.Route_Egress{
Binding: circuit.Binding,
Address: circuit.EgressId,
Destination: egressAddress,
}
routeMessage.Forwards = append(routeMessage.Forwards, &ctrl_pb.Route_Forward{
SrcAddress: circuit.EgressId,
DstAddress: link.Id.Token,
})
routeMessage.Forwards = append(routeMessage.Forwards, &ctrl_pb.Route_Forward{
SrcAddress: link.Id.Token,
DstAddress: circuit.EgressId,
})
routeMessages = append(routeMessages, routeMessage)
}
}
return routeMessages, nil
}
func (circuit *Circuit) usesLink(l *Link) bool {
if circuit.Links != nil {
for _, o := range circuit.Links {
if o == l {
return true
}
}
}
return false
}
+161
View File
@@ -0,0 +1,161 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/netfoundry/ziti-foundation/transport/tcp"
"github.com/stretchr/testify/assert"
"testing"
)
func TestSimpleCircuit2(t *testing.T) {
network, err := NewNetwork(nil, nil, nil, nil)
assert.Nil(t, err)
addr := "tcp:0.0.0.0:0"
transportAddr, err := tcp.AddressParser{}.Parse(addr)
assert.Nil(t, err)
r0 := newRouter("r0", "", transportAddr, nil)
network.routerController.add(r0)
r1 := newRouter("r1", "", transportAddr, nil)
network.routerController.add(r1)
l0 := newLink(&identity.TokenId{Token: "l0"})
l0.Src = r0
l0.Dst = r1
l0.addState(newLinkState(Connected))
network.linkController.add(l0)
circuit, err := network.CreateCircuit(r0, r1)
assert.NotNil(t, circuit)
assert.Nil(t, err)
assert.Equal(t, 2, len(circuit.Path))
assert.Equal(t, r0, circuit.Path[0])
assert.Equal(t, r1, circuit.Path[1])
assert.Equal(t, 1, len(circuit.Links))
assert.Equal(t, l0, circuit.Links[0])
assert.Equal(t, r1, circuit.EgressRouter())
s0 := &identity.TokenId{Token: "s0"}
routeMessages, err := circuit.CreateRouteMessages(s0, addr)
assert.NotNil(t, routeMessages)
assert.Nil(t, err)
assert.Equal(t, 2, len(routeMessages))
// ingress route message
rm0 := routeMessages[0]
assert.Equal(t, s0.Token, rm0.SessionId)
assert.Nil(t, rm0.Egress)
assert.Equal(t, 2, len(rm0.Forwards))
assert.Equal(t, circuit.IngressId, rm0.Forwards[0].SrcAddress)
assert.Equal(t, l0.Id.Token, rm0.Forwards[0].DstAddress)
assert.Equal(t, l0.Id.Token, rm0.Forwards[1].SrcAddress)
assert.Equal(t, circuit.IngressId, rm0.Forwards[1].DstAddress)
// egress route message
rm1 := routeMessages[1]
assert.Equal(t, s0.Token, rm1.SessionId)
assert.NotNil(t, rm1.Egress)
assert.Equal(t, circuit.EgressId, rm1.Egress.Address)
assert.Equal(t, addr, rm1.Egress.Destination)
assert.Equal(t, circuit.EgressId, rm1.Forwards[0].SrcAddress)
assert.Equal(t, l0.Id.Token, rm1.Forwards[0].DstAddress)
assert.Equal(t, l0.Id.Token, rm1.Forwards[1].SrcAddress)
assert.Equal(t, circuit.EgressId, rm1.Forwards[1].DstAddress)
}
func TestTransitCircuit2(t *testing.T) {
network, err := NewNetwork(nil, nil, nil, nil)
assert.Nil(t, err)
addr := "tcp:0.0.0.0:0"
transportAddr, err := tcp.AddressParser{}.Parse(addr)
assert.Nil(t, err)
r0 := newRouter("r0", "", transportAddr, nil)
network.routerController.add(r0)
r1 := newRouter("r1", "", transportAddr, nil)
network.routerController.add(r1)
r2 := newRouter("r2", "", transportAddr, nil)
network.routerController.add(r2)
l0 := newLink(&identity.TokenId{Token: "l0"})
l0.Src = r0
l0.Dst = r1
l0.addState(newLinkState(Connected))
network.linkController.add(l0)
l1 := newLink(&identity.TokenId{Token: "l1"})
l1.Src = r1
l1.Dst = r2
l1.addState(newLinkState(Connected))
network.linkController.add(l1)
circuit, err := network.CreateCircuit(r0, r2)
assert.NotNil(t, circuit)
assert.Nil(t, err)
assert.Equal(t, 3, len(circuit.Path))
assert.Equal(t, r0, circuit.Path[0])
assert.Equal(t, r1, circuit.Path[1])
assert.Equal(t, r2, circuit.Path[2])
assert.Equal(t, 2, len(circuit.Links))
assert.Equal(t, l0, circuit.Links[0])
assert.Equal(t, l1, circuit.Links[1])
assert.Equal(t, r2, circuit.EgressRouter())
s0 := &identity.TokenId{Token: "s0"}
routeMessages, err := circuit.CreateRouteMessages(s0, addr)
assert.NotNil(t, routeMessages)
assert.Nil(t, err)
assert.Equal(t, 3, len(routeMessages))
// ingress route message
rm0 := routeMessages[0]
assert.Equal(t, s0.Token, rm0.SessionId)
assert.Nil(t, rm0.Egress)
assert.Equal(t, 2, len(rm0.Forwards))
assert.Equal(t, circuit.IngressId, rm0.Forwards[0].SrcAddress)
assert.Equal(t, l0.Id.Token, rm0.Forwards[0].DstAddress)
assert.Equal(t, l0.Id.Token, rm0.Forwards[1].SrcAddress)
assert.Equal(t, circuit.IngressId, rm0.Forwards[1].DstAddress)
// transit route message
rm1 := routeMessages[1]
assert.Equal(t, s0.Token, rm1.SessionId)
assert.Nil(t, rm1.Egress)
assert.Equal(t, 2, len(rm1.Forwards))
assert.Equal(t, l0.Id.Token, rm1.Forwards[0].SrcAddress)
assert.Equal(t, l1.Id.Token, rm1.Forwards[0].DstAddress)
assert.Equal(t, l1.Id.Token, rm1.Forwards[1].SrcAddress)
assert.Equal(t, l0.Id.Token, rm1.Forwards[1].DstAddress)
// egress route message
rm2 := routeMessages[2]
assert.Equal(t, s0.Token, rm2.SessionId)
assert.NotNil(t, rm2.Egress)
assert.Equal(t, circuit.EgressId, rm2.Egress.Address)
assert.Equal(t, transportAddr.String(), rm2.Egress.Destination)
assert.Equal(t, circuit.EgressId, rm2.Forwards[0].SrcAddress)
assert.Equal(t, l1.Id.Token, rm2.Forwards[0].DstAddress)
assert.Equal(t, l1.Id.Token, rm2.Forwards[1].SrcAddress)
assert.Equal(t, circuit.EgressId, rm2.Forwards[1].DstAddress)
}
+22
View File
@@ -0,0 +1,22 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
type RouterPresenceHandler interface {
RouterConnected(r *Router)
RouterDisconnected(r *Router)
}
+90
View File
@@ -0,0 +1,90 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/netfoundry/ziti-foundation/util/info"
)
type Link struct {
Id *identity.TokenId
Src *Router
Dst *Router
state []*LinkState
Down bool
Cost int
SrcLatency int64
DstLatency int64
}
func newLink(id *identity.TokenId) *Link {
l := &Link{
Id: id,
state: make([]*LinkState, 0),
Down: false,
Cost: 1,
}
l.addState(&LinkState{Mode: Pending, Timestamp: info.NowInMilliseconds()})
return l
}
func (link *Link) CurrentState() *LinkState {
if link.state == nil || len(link.state) < 1 {
return nil
}
return link.state[0]
}
func (link *Link) addState(s *LinkState) {
if link.state == nil {
link.state = make([]*LinkState, 0)
}
link.state = append([]*LinkState{s}, link.state...)
}
type LinkMode byte
const (
Pending LinkMode = iota
Connected
Failed
)
func (t LinkMode) String() string {
if t == Pending {
return "Pending"
} else if t == Connected {
return "Connected"
} else if t == Failed {
return "Failed"
} else {
return ""
}
}
type LinkState struct {
Mode LinkMode
Timestamp int64
}
func newLinkState(mode LinkMode) *LinkState {
return &LinkState{
Mode: mode,
Timestamp: info.NowInMilliseconds(),
}
}
@@ -0,0 +1,365 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/netfoundry/ziti-foundation/util/sequence"
"github.com/orcaman/concurrent-map"
"math"
)
type linkController struct {
linkTable *linkTable
adjacencyTable *adjacencyTable
sequence *sequence.Sequence
}
func newLinkController() *linkController {
return &linkController{
linkTable: newLinkTable(),
adjacencyTable: newAdjacencyTable(),
sequence: sequence.NewSequence(),
}
}
func (linkController *linkController) add(link *Link) {
linkController.linkTable.add(link)
linkController.adjacencyTable.add(link)
}
func (linkController *linkController) has(link *Link) bool {
return linkController.linkTable.has(link)
}
func (linkController *linkController) get(linkId *identity.TokenId) (*Link, bool) {
link, found := linkController.linkTable.get(linkId)
return link, found
}
func (linkController *linkController) all() []*Link {
return linkController.linkTable.all()
}
func (linkController *linkController) remove(link *Link) {
linkController.linkTable.remove(link)
linkController.adjacencyTable.remove(link)
}
func (linkController *linkController) allLinksForRouter(routerId string) []*Link {
if rlt, found := linkController.adjacencyTable.get(routerId); found {
return rlt.allLinksForAllRouters()
}
return nil
}
func (linkController *linkController) connectedNeighborsOfRouter(router *Router) []*Router {
neighborMap := make(map[string]*Router)
links := linkController.allLinksForRouter(router.Id)
for _, link := range links {
currentState := link.CurrentState()
if currentState != nil && currentState.Mode == Connected && !link.Down {
if link.Src != router {
neighborMap[link.Src.Id] = link.Src
}
if link.Dst != router {
neighborMap[link.Dst.Id] = link.Dst
}
}
}
neighbors := make([]*Router, 0)
for _, r := range neighborMap {
neighbors = append(neighbors, r)
}
return neighbors
}
func (linkController *linkController) leastExpensiveLink(a, b *Router) (*Link, bool) {
var selected *Link
var cost int64 = math.MaxInt64
links := linkController.allLinksForRouter(a.Id)
for _, link := range links {
currentState := link.CurrentState()
if currentState != nil && currentState.Mode == Connected && !link.Down {
if link.Src == a && link.Dst == b {
if link.SrcLatency + link.DstLatency < cost {
selected = link
}
}
if link.Dst == a && link.Src == b {
if link.SrcLatency + link.DstLatency < cost {
selected = link
}
}
}
}
if selected != nil {
return selected, true
}
return nil, false
}
func (linkController *linkController) missingLinks(routers []*Router) ([]*Link, error) {
missingLinks := make([]*Link, 0)
for _, srcR := range routers {
for _, dstR := range routers {
if srcR != dstR && dstR.AdvertisedListener != nil {
if _, found := linkController.firstDirectedLink(srcR, dstR); !found {
id, err := linkController.sequence.NextHash()
if err != nil {
return nil, err
}
link := newLink(&identity.TokenId{Token: id})
link.Src = srcR
link.Dst = dstR
missingLinks = append(missingLinks, link)
}
}
}
}
return missingLinks, nil
}
func (linkController *linkController) firstDirectedLink(a, b *Router) (*Link, bool) {
// a->b
if rlt, found := linkController.adjacencyTable.get(a.Id); found {
if links, found := rlt.allLinksForRouter(b.Id); found {
for _, link := range links {
if link.Src == a && link.Dst == b && link.CurrentState().Mode == Connected {
return link, true
}
}
}
}
// b->a
if rlt, found := linkController.adjacencyTable.get(b.Id); found {
if links, found := rlt.allLinksForRouter(a.Id); found {
for _, link := range links {
if link.Src == b && link.Dst == a && link.CurrentState().Mode == Connected {
return link, true
}
}
}
}
return nil, false
}
func (linkController *linkController) linksInMode(mode LinkMode) []*Link {
return linkController.linkTable.allInMode(mode)
}
/*
* linkTable
*/
type linkTable struct {
links cmap.ConcurrentMap // map[Link.Id.Token]*Link
}
func newLinkTable() *linkTable {
return &linkTable{links: cmap.New()}
}
func (lt *linkTable) add(link *Link) {
lt.links.Set(link.Id.Token, link)
}
func (lt *linkTable) get(linkId *identity.TokenId) (*Link, bool) {
link, found := lt.links.Get(linkId.Token)
if link != nil {
return link.(*Link), found
}
return nil, found
}
func (lt *linkTable) has(link *Link) bool {
if i, found := lt.links.Get(link.Id.Token); found {
if i.(*Link) == link {
return true
}
}
return false
}
func (lt *linkTable) all() []*Link {
links := make([]*Link, 0)
for i := range lt.links.IterBuffered() {
links = append(links, i.Val.(*Link))
}
return links
}
func (lt *linkTable) allInMode(mode LinkMode) []*Link {
links := make([]*Link, 0)
for i := range lt.links.IterBuffered() {
link := i.Val.(*Link)
if link.CurrentState().Mode == mode {
links = append(links, link)
}
}
return links
}
func (lt *linkTable) remove(link *Link) {
lt.links.Remove(link.Id.Token)
}
/*
* adjacencyTable
*/
type adjacencyTable struct {
adjacency cmap.ConcurrentMap // map[Router.Id.Token]*routerLinksTable
}
func newAdjacencyTable() *adjacencyTable {
return &adjacencyTable{adjacency: cmap.New()}
}
func (at *adjacencyTable) add(link *Link) {
// src->dst
var rlt *routerLinksTable
if i, found := at.adjacency.Get(link.Src.Id); found {
rlt = i.(*routerLinksTable)
} else {
rlt = newRouterLinksTable()
}
rlt.addLinkForRouter(link.Dst.Id, link)
at.adjacency.Set(link.Src.Id, rlt)
// dst->src
if i, found := at.adjacency.Get(link.Dst.Id); found {
rlt = i.(*routerLinksTable)
} else {
rlt = newRouterLinksTable()
}
rlt.addLinkForRouter(link.Src.Id, link)
at.adjacency.Set(link.Dst.Id, rlt)
}
func (at *adjacencyTable) get(routerId string) (*routerLinksTable, bool) {
i, found := at.adjacency.Get(routerId)
if i != nil {
return i.(*routerLinksTable), found
}
return nil, found
}
func (at *adjacencyTable) remove(link *Link) {
// src->dst
if i, found := at.adjacency.Get(link.Src.Id); found {
rlt := i.(*routerLinksTable)
rlt.removeLinkFromRouter(link.Dst.Id, link)
if rlt.size() > 0 {
at.adjacency.Set(link.Src.Id, rlt)
} else {
at.adjacency.Remove(link.Src.Id)
}
}
// dst->src
if i, found := at.adjacency.Get(link.Dst.Id); found {
rlt := i.(*routerLinksTable)
rlt.removeLinkFromRouter(link.Src.Id, link)
if rlt.size() > 0 {
at.adjacency.Set(link.Dst.Id, rlt)
} else {
at.adjacency.Remove(link.Dst.Id)
}
}
}
/*
* routerLinksTable
*/
type routerLinksTable struct {
routerLinks cmap.ConcurrentMap // map[Router.Id.Token][]*Link
}
func newRouterLinksTable() *routerLinksTable {
return &routerLinksTable{routerLinks: cmap.New()}
}
func (rlt *routerLinksTable) addLinkForRouter(routerId string, link *Link) {
var links []*Link
if i, found := rlt.routerLinks.Get(routerId); found {
links = i.([]*Link)
} else {
links = make([]*Link, 0)
}
links = append(links, link)
rlt.routerLinks.Set(routerId, links)
}
func (rlt *routerLinksTable) allLinksForRouter(routerId string) ([]*Link, bool) {
if i, found := rlt.routerLinks.Get(routerId); found {
return i.([]*Link), true
}
return nil, false
}
func (rlt *routerLinksTable) allLinksForAllRouters() []*Link {
linksMap := make(map[string]*Link)
for i := range rlt.routerLinks.IterBuffered() {
links := i.Val.([]*Link)
for _, link := range links {
linksMap[link.Id.Token] = link
}
}
allLinks := make([]*Link, 0)
for _, v := range linksMap {
allLinks = append(allLinks, v)
}
return allLinks
}
func (rlt *routerLinksTable) size() int {
return rlt.routerLinks.Count()
}
func (rlt *routerLinksTable) removeLinkFromRouter(routerId string, link *Link) {
var links []*Link
if i, found := rlt.routerLinks.Get(routerId); found {
links = i.([]*Link)
if len(links) == 1 && links[0] == link {
rlt.routerLinks.Remove(routerId)
} else {
i := -1
for j, jLink := range links {
if jLink == link {
i = j
break
}
}
if i != -1 {
links = append(links[:i], links[i+1:]...)
}
rlt.routerLinks.Set(routerId, links)
}
}
}
@@ -0,0 +1,73 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/stretchr/testify/assert"
"testing"
)
func TestLifecycle(t *testing.T) {
linkController := newLinkController()
r0 := NewRouter("r0", "")
r1 := NewRouter("r1", "")
l0 := &Link{
Id: &identity.TokenId{Token: "l0"},
Src: r0,
Dst: r1,
}
linkController.add(l0)
assert.True(t, linkController.has(l0))
links := linkController.allLinksForRouter(r0.Id)
assert.Equal(t, 1, len(links))
assert.Equal(t, l0, links[0])
links = linkController.allLinksForRouter(r1.Id)
assert.Equal(t, 1, len(links))
assert.Equal(t, l0, links[0])
linkController.remove(l0)
assert.False(t, linkController.has(l0))
links = linkController.allLinksForRouter(r0.Id)
assert.Equal(t, 0, len(links))
links = linkController.allLinksForRouter(r1.Id)
assert.Equal(t, 0, len(links))
}
func TestNeighbors(t *testing.T) {
linkController := newLinkController()
r0 := newRouter("r0", "", nil, nil)
r1 := newRouter("r1", "", nil, nil)
l0 := &Link{
Id: &identity.TokenId{Token: "l0"},
Src: r0,
Dst: r1,
}
l0.addState(newLinkState(Connected))
linkController.add(l0)
neighbors := linkController.connectedNeighborsOfRouter(r0)
assert.Equal(t, 1, len(neighbors))
assert.Equal(t, r1, neighbors[0])
}
+637
View File
@@ -0,0 +1,637 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-fabric/fabric/controller/db"
"github.com/netfoundry/ziti-fabric/fabric/metrics"
"github.com/netfoundry/ziti-fabric/fabric/pb/ctrl_pb"
"github.com/netfoundry/ziti-fabric/fabric/trace"
"github.com/netfoundry/ziti-foundation/storage/boltz"
"encoding/json"
"errors"
"fmt"
"github.com/golang/protobuf/proto"
"github.com/michaelquigley/pfxlog"
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/netfoundry/ziti-foundation/util/concurrenz"
"github.com/netfoundry/ziti-foundation/util/sequence"
"strings"
"sync"
"time"
)
type Network struct {
nodeId *identity.TokenId
db *db.Db
options *Options
routerController *routerController
routerStore RouterStore
routerChanged chan *Router
linkController *linkController
linkChanged chan *Link
serviceController *serviceController
sessionController *sessionController
sequence *sequence.Sequence
metricsEventController metrics.EventController
sessionLifeCycleController SessionLifeCycleController
traceEventController trace.EventController
traceController trace.Controller
routerPresenceHandlers []RouterPresenceHandler
capabilities []string
shutdownChan chan struct{}
isShutdown concurrenz.AtomicBoolean
lock sync.Mutex
}
func NewNetwork(nodeId *identity.TokenId, options *Options, db *db.Db, metricsCfg *metrics.Config) (*Network, error) {
serviceController, err := newServiceController(db)
if err != nil {
return nil, err
}
network := &Network{
nodeId: nodeId,
db: db,
options: options,
routerController: newRouterController(),
routerStore: NewRouterStore(db),
routerChanged: make(chan *Router),
linkController: newLinkController(),
linkChanged: make(chan *Link),
serviceController: serviceController,
sessionController: newSessionController(),
sequence: sequence.NewSequence(),
metricsEventController: metrics.NewEventController(metricsCfg),
sessionLifeCycleController: NewSessionLifeCyleController(),
traceEventController: trace.NewEventController(),
traceController: trace.NewController(),
shutdownChan: make(chan struct{}),
}
network.metricsEventController.AddHandler(network)
network.AddCapability("ziti.fabric")
network.showOptions()
return network, nil
}
func (network *Network) GetAppId() *identity.TokenId {
return network.nodeId
}
func (network *Network) GetDb() boltz.Db {
return network.db
}
func (network *Network) GetRouterStore() RouterStore {
return network.routerStore
}
func (network *Network) CreateRouter(router *Router) error {
return network.routerStore.create(router)
}
func (network *Network) GetConnectedRouter(routerId string) (*Router, bool) {
return network.routerController.get(routerId)
}
func (network *Network) GetRouter(routerId string) (*Router, error) {
return network.routerStore.loadOneById(routerId)
}
func (network *Network) AllRouters() ([]*Router, error) {
return network.routerStore.all()
}
func (network *Network) AllConnectedRouters() []*Router {
return network.routerController.all()
}
func (network *Network) RemoveRouter(router *Router) error {
return network.routerStore.remove(router.Id)
}
func (network *Network) GetServiceStore() ServiceStore {
return network.serviceController.store
}
func (network *Network) CreateService(service *Service) error {
return network.serviceController.create(service)
}
func (network *Network) GetService(serviceId string) (*Service, bool) {
return network.serviceController.get(serviceId)
}
func (network *Network) AllServices() ([]*Service, error) {
return network.serviceController.all()
}
func (network *Network) RemoveService(id string) error {
return network.serviceController.remove(id)
}
func (network *Network) GetLink(linkId *identity.TokenId) (*Link, bool) {
return network.linkController.get(linkId)
}
func (network *Network) GetAllLinks() []*Link {
return network.linkController.all()
}
func (network *Network) GetAllLinksForRouter(routerId string) []*Link {
return network.linkController.allLinksForRouter(routerId)
}
func (network *Network) GetSession(sessionId *identity.TokenId) (*session, bool) {
return network.sessionController.get(sessionId)
}
func (network *Network) GetAllSessions() []*session {
return network.sessionController.all()
}
func (network *Network) GetMetricsEventController() metrics.EventController {
return network.metricsEventController
}
func (network *Network) GetSessionLifeCycleController() SessionLifeCycleController {
return network.sessionLifeCycleController
}
func (network *Network) GetTraceEventController() trace.EventController {
return network.traceEventController
}
func (network *Network) GetTraceController() trace.Controller {
return network.traceController
}
func (network *Network) RouterChanged(r *Router) {
network.routerChanged <- r
}
func (network *Network) ConnectedRouter(id string) bool {
return network.routerController.has(id)
}
func (network *Network) KnownRouter(id string) (*Router, error) {
r, err := network.routerStore.loadOneById(id)
if err != nil {
return nil, err
}
return r, nil
}
func (network *Network) ConnectRouter(r *Router) {
network.routerController.add(r)
network.routerChanged <- r
for _, h := range network.routerPresenceHandlers {
h.RouterConnected(r)
}
}
func (network *Network) DisconnectRouter(r *Router) {
// 1: remove Links for Router
for _, l := range network.linkController.allLinksForRouter(r.Id) {
network.linkController.remove(l)
network.LinkChanged(l)
}
// 2: remove Router
network.routerController.remove(r)
network.routerChanged <- r
for _, h := range network.routerPresenceHandlers {
h.RouterDisconnected(r)
}
}
func (network *Network) LinkConnected(id *identity.TokenId, connected bool) error {
log := pfxlog.Logger()
if l, found := network.linkController.get(id); found {
if connected {
l.addState(newLinkState(Connected))
log.Infof("link [l/%s] connected", id.Token)
return nil
}
l.addState(newLinkState(Failed))
log.Infof("link [l/%s] failed", id.Token)
return nil
}
return fmt.Errorf("no such link [l/%s]", id.Token)
}
func (network *Network) LinkChanged(l *Link) {
// This is called from Channel.rxer() and thus may not block
go func() {
network.linkChanged <- l
}()
}
func (network *Network) BindService(srcR *Router, token string, serviceId string) error {
// 1: Find Service
if svc, found := network.serviceController.get(serviceId); found {
svc.Egress = srcR.Id
svc.EndpointAddress = "hosted:" + token
return network.serviceController.update(svc)
}
return errors.New("invalid service")
}
func (network *Network) UnbindService(srcR *Router, token string, serviceId string) error {
// 1: Find Service
if svc, found := network.serviceController.get(serviceId); found {
svc.Egress = network.nodeId.Token // TODO: can't set this to null, so setting it to the controller ID
svc.EndpointAddress = "hosted:unbound"
return network.serviceController.update(svc)
}
return errors.New("invalid service")
}
func (network *Network) CreateSession(srcR *Router, clientId *identity.TokenId, serviceId string) (*session, error) {
log := pfxlog.Logger()
// 1: Find Service
if svc, found := network.serviceController.get(serviceId); found {
// 2: Allocate Session Identifier
sessionIdHash, err := network.sequence.NextHash()
if err != nil {
return nil, err
}
sessionId := &identity.TokenId{Token: sessionIdHash}
// 3: Get Egress Router
if er, found := network.routerController.get(svc.Egress); found {
// 4: Create Circuit
circuit, err := network.CreateCircuit(srcR, er)
if err != nil {
return nil, err
}
circuit.Binding = "transport"
if svc.Binding != "" {
circuit.Binding = svc.Binding
} else if strings.HasPrefix(svc.EndpointAddress, "hosted") {
circuit.Binding = "edge"
} else if strings.HasPrefix(svc.EndpointAddress, "udp") {
circuit.Binding = "udp"
}
// 4a: Create Route Messages
rms, err := circuit.CreateRouteMessages(sessionId, svc.EndpointAddress)
if err != nil {
return nil, err
}
// 5: Route Egress
err = sendRoute(circuit.Path[len(circuit.Path)-1], rms[len(rms)-1])
if err != nil {
return nil, err
}
// 6: Create Intermediate Routes
for i := 0; i < len(circuit.Path)-1; i++ {
err = sendRoute(circuit.Path[i], rms[i])
if err != nil {
return nil, err
}
}
// 7: Create Session Object
ss := &session{
Id: sessionId,
ClientId: clientId,
Service: svc,
Circuit: circuit,
}
network.sessionController.add(ss)
network.sessionLifeCycleController.SessionCreated(ss.Id, ss.ClientId, ss.Service.Id, ss.Circuit)
log.Infof("created session [s/%s] ==> %s", sessionId.Token, ss.Circuit)
return ss, nil
}
return nil, errors.New("missing egress")
}
return nil, errors.New("invalid service")
}
func (network *Network) RemoveSession(sessionId *identity.TokenId, now bool) error {
log := pfxlog.Logger()
if ss, found := network.sessionController.get(sessionId); found {
for _, r := range ss.Circuit.Path {
err := sendUnroute(r, ss.Id, now)
if err != nil {
log.Errorf("error sending unroute to [r/%s] (%s)", r.Id, err)
}
}
network.sessionController.remove(ss)
network.sessionLifeCycleController.SessionDeleted(sessionId)
log.Infof("removed session [s/%s]", ss.Id.Token)
return nil
}
return fmt.Errorf("invalid session (%s)", sessionId.Token)
}
func (network *Network) StartSessionEgress(sessionId *identity.TokenId) error {
log := pfxlog.Logger()
if ss, found := network.sessionController.get(sessionId); found {
terminatingRouter := ss.Circuit.Path[len(ss.Circuit.Path)-1]
log.Infof("started session egress [s/%s]", ss.Id.Token)
return sendStartXgress(terminatingRouter, sessionId)
}
return fmt.Errorf("invalid session (%s)", sessionId.Token)
}
func (network *Network) CreateCircuit(srcR, dstR *Router) (*Circuit, error) {
ingressId, err := network.sequence.NextHash()
if err != nil {
return nil, err
}
egressId, err := network.sequence.NextHash()
if err != nil {
return nil, err
}
circuit := &Circuit{
Links: make([]*Link, 0),
IngressId: ingressId,
EgressId: egressId,
Path: make([]*Router, 0),
}
circuit.Path = append(circuit.Path, srcR)
circuit.Path = append(circuit.Path, dstR)
return network.UpdateCircuit(circuit)
}
func (network *Network) UpdateCircuit(circuit *Circuit) (*Circuit, error) {
srcR := circuit.Path[0]
dstR := circuit.Path[len(circuit.Path)-1]
path, err := network.shortestPath(srcR, dstR)
if err != nil {
return nil, err
}
circuit2 := &Circuit{
Path: path,
Binding: circuit.Binding,
IngressId: circuit.IngressId,
EgressId: circuit.EgressId,
}
if len(path) > 1 {
for i := 0; i < len(path)-1; i++ {
if link, found := network.linkController.leastExpensiveLink(path[i], path[i+1]); found {
circuit2.Links = append(circuit2.Links, link)
}
}
}
return circuit2, nil
}
func (network *Network) AddRouterPresenceHandler(h RouterPresenceHandler) {
network.routerPresenceHandlers = append(network.routerPresenceHandlers, h)
}
func (network *Network) Debug() string {
return "oh, wow"
}
func (network *Network) Run() {
log := pfxlog.Logger()
defer log.Error("exited")
log.Info("started")
for {
select {
case r := <-network.routerChanged:
log.Infof("changed router [r/%s]", r.Id)
network.assemble()
network.clean()
case l := <-network.linkChanged:
log.Infof("changed link [l/%s]", l.Id.Token)
if err := network.rerouteLink(l); err != nil {
log.Errorf("unexpected error rerouting link (%s)", err)
}
case <-time.After(time.Duration(network.options.CycleSeconds) * time.Second):
network.assemble()
network.clean()
network.smart()
case _, ok := <-network.shutdownChan:
if !ok {
return
}
}
}
}
func (network *Network) Shutdown() {
if network.isShutdown.CompareAndSwap(false, true) {
close(network.shutdownChan)
}
}
func (network *Network) AddCapability(capability string) {
network.lock.Lock()
defer network.lock.Unlock()
network.capabilities = append(network.capabilities, capability)
}
func (network *Network) GetCapabilities() []string {
network.lock.Lock()
defer network.lock.Unlock()
return network.capabilities
}
func (network *Network) rerouteLink(l *Link) error {
log := pfxlog.Logger()
log.Infof("link [l/%s] changed", l.Id.Token)
sessions := network.sessionController.all()
for _, s := range sessions {
if s.Circuit.usesLink(l) {
log.Infof("session [s/%s] uses link [l/%s]", s.Id.Token, l.Id.Token)
if err := network.rerouteSession(s); err != nil {
log.Errorf("error rerouting session [s/%s], removing", s.Id.Token)
if err := network.RemoveSession(s.Id, true); err != nil {
log.Errorf("error removing session [s/%s] (%s)", s.Id.Token, err)
}
}
}
}
return nil
}
func (network *Network) rerouteSession(s *session) error {
log := pfxlog.Logger()
log.Warnf("rerouting [s/%s]", s.Id.Token)
if cq, err := network.UpdateCircuit(s.Circuit); err == nil {
s.Circuit = cq
rms, err := cq.CreateRouteMessages(s.Id, s.Service.EndpointAddress)
if err != nil {
log.Errorf("error creating route messages (%s)", err)
return err
}
for i := 0; i < len(cq.Path); i++ {
if err := sendRoute(cq.Path[i], rms[i]); err != nil {
log.Errorf("error sending route to [r/%s] (%s)", cq.Path[i].Id, err)
}
}
log.Infof("rerouted session [s/%s]", s.Id.Token)
network.sessionLifeCycleController.CircuitUpdated(s.Id, s.Circuit)
return nil
} else {
return err
}
}
func (network *Network) smartReroute(s *session, cq *Circuit) error {
log := pfxlog.Logger()
s.Circuit = cq
rms, err := cq.CreateRouteMessages(s.Id, s.Service.EndpointAddress)
if err != nil {
log.Errorf("error creating route messages (%s)", err)
return err
}
for i := 0; i < len(cq.Path); i++ {
if err := sendRoute(cq.Path[i], rms[i]); err != nil {
log.Errorf("error sending route to [r/%s] (%s)", cq.Path[i].Id, err)
}
}
log.Debugf("rerouted session [s/%s]", s.Id.Token)
network.sessionLifeCycleController.CircuitUpdated(s.Id, s.Circuit)
return nil
}
func (network *Network) AcceptMetrics(metrics *ctrl_pb.MetricsMessage) {
log := pfxlog.Logger()
router, err := network.routerStore.loadOneById(metrics.SourceId)
if err != nil {
log.Warnf("could not find router [r/%s] while processing metrics", metrics.SourceId)
return
}
for _, link := range network.GetAllLinksForRouter(router.Id) {
metricId := "link." + link.Id.Token + ".latency"
if latency, ok := metrics.Histograms[metricId]; ok {
if link.Src.Id == router.Id {
link.SrcLatency = int64(latency.Mean)
} else if link.Dst.Id == router.Id {
link.DstLatency = int64(latency.Mean)
} else {
log.Warnf("link not for router (wtf?)")
}
}
}
}
func sendRoute(r *Router, createMsg *ctrl_pb.Route) error {
pfxlog.Logger().Debugf("sending create route message to [r/%s] for [s/%s]", r.Id, createMsg.SessionId)
body, err := proto.Marshal(createMsg)
if err != nil {
return err
}
msg := channel2.NewMessage(int32(ctrl_pb.ContentType_RouteType), body)
waitCh, err := r.Control.SendAndWait(msg)
if err != nil {
return err
}
select {
case msg := <-waitCh:
if msg.ContentType == channel2.ContentTypeResultType {
result := channel2.UnmarshalResult(msg)
if !result.Success {
return errors.New(result.Message)
}
return nil
}
return fmt.Errorf("unexpected response type %v received in reply to route request", msg.ContentType)
case <-time.After(10 * time.Second):
pfxlog.Logger().Errorf("timed out waiting for response to route message from [r/%s] for [s/%s]", r.Id, createMsg.SessionId)
return errors.New("timeout")
}
}
func sendUnroute(r *Router, sessionId *identity.TokenId, now bool) error {
remove := &ctrl_pb.Unroute{
SessionId: sessionId.Token,
Now: now,
}
body, err := proto.Marshal(remove)
if err != nil {
return err
}
removeMsg := channel2.NewMessage(int32(ctrl_pb.ContentType_UnrouteType), body)
if err := r.Control.Send(removeMsg); err != nil {
return err
}
return nil
}
func sendStartXgress(r *Router, sessionId *identity.TokenId) error {
msg := channel2.NewMessage(int32(ctrl_pb.ContentType_StartXgressType), []byte(sessionId.Token))
if err := r.Control.Send(msg); err != nil {
return err
}
return nil
}
func (network *Network) showOptions() {
if jsonOptions, err := json.MarshalIndent(network.options, "", " "); err == nil {
pfxlog.Logger().Infof("network = %s", string(jsonOptions))
} else {
panic(err)
}
}
func (network *Network) GetServiceCache() Cache {
return network.serviceController
}
type Cache interface {
RemoveFromCache(id string)
}
+75
View File
@@ -0,0 +1,75 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"errors"
"github.com/michaelquigley/pfxlog"
)
type Options struct {
CycleSeconds uint32
Smart struct {
RerouteFraction float32
RerouteCap uint32
}
}
func DefaultOptions() *Options {
options := &Options{
CycleSeconds: 15,
}
options.Smart.RerouteFraction = 0.02
options.Smart.RerouteCap = 4
return options
}
func LoadOptions(src map[interface{}]interface{}) (*Options, error) {
options := DefaultOptions()
if value, found := src["cycleSeconds"]; found {
if cycleSeconds, ok := value.(int); ok {
options.CycleSeconds = uint32(cycleSeconds)
} else {
return nil, errors.New("invalid value for 'cycleSeconds'")
}
}
if value, found := src["smart"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
if value, found := submap["rerouteFraction"]; found {
if rerouteFraction, ok := value.(float64); ok {
options.Smart.RerouteFraction = float32(rerouteFraction)
} else {
pfxlog.Logger().Errorf("%p", value)
}
}
if value, found := submap["rerouteCap"]; found {
if rerouteCap, ok := value.(int); ok {
options.Smart.RerouteCap = uint32(rerouteCap)
} else {
pfxlog.Logger().Errorf("%p", value)
}
}
} else {
pfxlog.Logger().Errorf("invalid or empty 'smart' stanza")
}
}
return options, nil
}
+105
View File
@@ -0,0 +1,105 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"errors"
"math"
)
func (network *Network) shortestPath(srcR *Router, dstR *Router) ([]*Router, error) {
if srcR == nil || dstR == nil {
return nil, errors.New("not routable (!srcR||!dstR)")
}
if srcR == dstR {
return []*Router{srcR}, nil
}
dist := make(map[*Router]int64)
prev := make(map[*Router]*Router)
unvisited := make(map[*Router]bool)
for _, r := range network.routerController.all() {
dist[r] = math.MaxInt32
unvisited[r] = true
}
dist[srcR] = 0
for len(unvisited) > 0 {
u := minCost(unvisited, dist)
delete(unvisited, u)
neighbors := network.linkController.connectedNeighborsOfRouter(u)
for _, r := range neighbors {
if _, found := unvisited[r]; found {
cost := int64(r.CostFactor)
if l, found := network.linkController.leastExpensiveLink(r, u); found {
cost += l.SrcLatency
cost += l.DstLatency
}
alt := dist[u] + cost
if alt < dist[r] {
dist[r] = alt
prev[r] = u
}
}
}
}
/*
* dist: (r2->r1->r0)
* r0 = 2 <- r1
* r1 = 1 <- r2
* r2 = 0 <- nil
*/
routerPath := make([]*Router, 0)
p := prev[dstR]
for p != nil {
routerPath = append([]*Router{p}, routerPath...)
p = prev[p]
}
routerPath = append(routerPath, dstR)
if routerPath[0] != srcR {
return nil, errors.New("not routable (~srcR)")
}
if routerPath[len(routerPath)-1] != dstR {
return nil, errors.New("not routable (~dstR)")
}
return routerPath, nil
}
func minCost(q map[*Router]bool, dist map[*Router]int64) *Router {
if dist == nil || len(dist) < 1 {
return nil
}
min := int64(math.MaxInt64)
var selected *Router
for r := range q {
d := dist[r]
if d <= min {
selected = r
min = d
}
}
return selected
}
+89
View File
@@ -0,0 +1,89 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-foundation/channel2"
"github.com/netfoundry/ziti-foundation/transport"
"github.com/orcaman/concurrent-map"
)
type Router struct {
Id string
Fingerprint string
AdvertisedListener transport.Address
Control channel2.Channel
CostFactor int
}
func NewRouter(id, fingerprint string) *Router {
return &Router{
Id: id,
Fingerprint: fingerprint,
}
}
func newRouter(id string, fingerprint string, advLstnr transport.Address, ctrl channel2.Channel) *Router {
return &Router{
Id: id,
Fingerprint: fingerprint,
AdvertisedListener: advLstnr,
Control: ctrl,
CostFactor: 1,
}
}
type routerController struct {
connected cmap.ConcurrentMap // map[string]*Router
}
func newRouterController() *routerController {
return &routerController{
connected: cmap.New(),
}
}
func (c *routerController) add(r *Router) {
c.connected.Set(r.Id, r)
}
func (c *routerController) has(id string) bool {
return c.connected.Has(id)
}
func (c *routerController) get(id string) (*Router, bool) {
if t, found := c.connected.Get(id); found {
return t.(*Router), true
}
return nil, false
}
func (c *routerController) all() []*Router {
routers := make([]*Router, 0)
for i := range c.connected.IterBuffered() {
routers = append(routers, i.Val.(*Router))
}
return routers
}
func (c *routerController) count() int {
return c.connected.Count()
}
func (c *routerController) remove(r *Router) {
c.connected.Remove(r.Id)
}
+142
View File
@@ -0,0 +1,142 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-foundation/storage/boltz"
"fmt"
"go.etcd.io/bbolt"
)
const (
EntityTypeRouters = "routers"
FieldFingerprint = "fingerprint"
)
func (router *Router) GetId() string {
return router.Id
}
func (router *Router) SetId(id string) {
router.Id = id
}
func (router *Router) LoadValues(_ boltz.CrudStore, bucket *boltz.TypedBucket) {
router.Fingerprint = bucket.GetStringOrError(FieldFingerprint)
}
func (router *Router) SetValues(ctx *boltz.PersistContext) {
ctx.SetString(FieldFingerprint, router.Fingerprint)
}
func (router *Router) GetEntityType() string {
return EntityTypeRouters
}
type RouterStore interface {
boltz.CrudStore
create(router *Router) error
update(router *Router) error
remove(id string) error
all() ([]*Router, error)
loadOneById(id string) (*Router, error)
LoadOneById(tx *bbolt.Tx, id string) (*Router, error)
}
func NewRouterStore(db boltz.Db) RouterStore {
notFoundErrorFactory := func(id string) error {
return fmt.Errorf("missing router '%s'", id)
}
store := &routerStoreImpl{
db: db,
BaseStore: boltz.NewBaseStore(nil, EntityTypeRouters, notFoundErrorFactory, boltz.RootBucket),
}
store.InitImpl(store)
return store
}
type routerStoreImpl struct {
db boltz.Db
*boltz.BaseStore
}
func (store *routerStoreImpl) NewStoreEntity() boltz.BaseEntity {
return &Router{}
}
func (store *routerStoreImpl) create(router *Router) error {
return store.db.Update(func(tx *bbolt.Tx) error {
return store.Create(boltz.NewMutateContext(tx), router)
})
}
func (store *routerStoreImpl) update(router *Router) error {
return store.db.Update(func(tx *bbolt.Tx) error {
return store.Update(boltz.NewMutateContext(tx), router, nil)
})
}
func (store *routerStoreImpl) remove(id string) error {
return store.db.Update(func(tx *bbolt.Tx) error {
return store.DeleteById(boltz.NewMutateContext(tx), id)
})
}
func (store *routerStoreImpl) loadOneById(id string) (router *Router, err error) {
err = store.db.View(func(tx *bbolt.Tx) error {
router, err = store.LoadOneById(tx, id)
return err
})
if err != nil {
return nil, err
}
if router == nil {
return nil, fmt.Errorf("missing router '%s'", id)
}
return
}
func (store *routerStoreImpl) LoadOneById(tx *bbolt.Tx, id string) (*Router, error) {
router := &Router{}
if found, err := store.BaseLoadOneById(tx, id, router); !found || err != nil {
return nil, err
}
return router, nil
}
func (store *routerStoreImpl) all() ([]*Router, error) {
routers := make([]*Router, 0)
err := store.db.View(func(tx *bbolt.Tx) error {
ids, _, err := store.QueryIds(tx, "true")
if err != nil {
return err
}
for _, id := range ids {
router, err := store.LoadOneById(tx, string(id))
if err != nil {
return err
}
routers = append(routers, router)
}
return nil
})
if err != nil {
return nil, err
}
return routers, nil
}
+91
View File
@@ -0,0 +1,91 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-fabric/fabric/controller/db"
"github.com/michaelquigley/pfxlog"
"github.com/orcaman/concurrent-map"
)
type Service struct {
Id string
Binding string
EndpointAddress string
Egress string
}
type serviceController struct {
cache cmap.ConcurrentMap
store ServiceStore
}
func newServiceController(db *db.Db) (*serviceController, error) {
return &serviceController{cache: cmap.New(), store: NewServiceStore(db)}, nil
}
func (c *serviceController) create(s *Service) error {
err := c.store.create(s)
if err != nil {
return err
}
c.cache.Set(s.Id, s)
return nil
}
func (c *serviceController) update(s *Service) error {
err := c.store.update(s)
if err != nil {
return err
}
c.cache.Set(s.Id, s)
return nil
}
func (c *serviceController) get(id string) (*Service, bool) {
if t, found := c.cache.Get(id); found {
return t.(*Service), true
}
svc, err := c.store.loadOneById(id)
if err != nil {
pfxlog.Logger().Errorf("failed loading service (%s)", err)
return nil, false
}
if svc != nil {
c.cache.Set(svc.Id, svc)
return svc, true
}
return nil, false
}
func (c *serviceController) all() ([]*Service, error) {
svcs, err := c.store.all()
if err != nil {
return nil, err
}
return svcs, nil
}
func (c *serviceController) remove(id string) error {
c.cache.Remove(id)
return c.store.remove(id)
}
func (c *serviceController) RemoveFromCache(id string) {
c.cache.Remove(id)
}
+152
View File
@@ -0,0 +1,152 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-foundation/storage/ast"
"github.com/netfoundry/ziti-foundation/storage/boltz"
"fmt"
"go.etcd.io/bbolt"
)
const (
EntityTypeServices = "services"
FieldServiceBinding = "binding"
FieldServiceEndpoint = "endpoint"
FieldServiceEgress = "egress"
)
func (service *Service) GetId() string {
return service.Id
}
func (service *Service) SetId(id string) {
service.Id = id
}
func (service *Service) LoadValues(_ boltz.CrudStore, bucket *boltz.TypedBucket) {
service.Binding = bucket.GetStringWithDefault(FieldServiceBinding, "")
service.EndpointAddress = bucket.GetStringWithDefault(FieldServiceEndpoint, "")
service.Egress = bucket.GetStringWithDefault(FieldServiceEgress, "")
}
func (service *Service) SetValues(ctx *boltz.PersistContext) {
ctx.SetString(FieldServiceBinding, service.Binding)
ctx.SetString(FieldServiceEndpoint, service.EndpointAddress)
ctx.SetString(FieldServiceEgress, service.Egress)
}
func (service *Service) GetEntityType() string {
return EntityTypeServices
}
type ServiceStore interface {
boltz.CrudStore
create(service *Service) error
update(svc *Service) error
remove(id string) error
all() ([]*Service, error)
loadOneById(id string) (*Service, error)
LoadOneById(tx *bbolt.Tx, id string) (*Service, error)
}
func NewServiceStore(db boltz.Db) ServiceStore {
notFoundErrorFactory := func(id string) error {
return fmt.Errorf("missing service '%s'", id)
}
store := &serviceStoreImpl{
db: db,
BaseStore: boltz.NewBaseStore(nil, EntityTypeServices, notFoundErrorFactory, boltz.RootBucket),
}
store.InitImpl(store)
store.AddSymbol(FieldServiceBinding, ast.NodeTypeString)
store.AddSymbol(FieldServiceEndpoint, ast.NodeTypeString)
store.AddSymbol(FieldServiceEgress, ast.NodeTypeString)
return store
}
type serviceStoreImpl struct {
db boltz.Db
*boltz.BaseStore
}
func (store *serviceStoreImpl) NewStoreEntity() boltz.BaseEntity {
return &Service{}
}
func (store *serviceStoreImpl) create(service *Service) error {
return store.db.Update(func(tx *bbolt.Tx) error {
return store.Create(boltz.NewMutateContext(tx), service)
})
}
func (store *serviceStoreImpl) update(service *Service) error {
return store.db.Update(func(tx *bbolt.Tx) error {
return store.Update(boltz.NewMutateContext(tx), service, nil)
})
}
func (store *serviceStoreImpl) remove(id string) error {
return store.db.Update(func(tx *bbolt.Tx) error {
return store.DeleteById(boltz.NewMutateContext(tx), id)
})
}
func (store *serviceStoreImpl) loadOneById(id string) (service *Service, err error) {
err = store.db.View(func(tx *bbolt.Tx) error {
service, err = store.LoadOneById(tx, id)
return err
})
if err != nil {
return nil, err
}
if service == nil {
return nil, fmt.Errorf("missing service '%s'", id)
}
return
}
func (store *serviceStoreImpl) LoadOneById(tx *bbolt.Tx, id string) (*Service, error) {
service := &Service{}
if found, err := store.BaseLoadOneById(tx, id, service); !found || err != nil {
return nil, err
}
return service, nil
}
func (store *serviceStoreImpl) all() ([]*Service, error) {
services := make([]*Service, 0)
err := store.db.View(func(tx *bbolt.Tx) error {
ids, _, err := store.QueryIds(tx, "true")
if err != nil {
return err
}
for _, id := range ids {
service, err := store.LoadOneById(tx, string(id))
if err != nil {
return err
}
services = append(services, service)
}
return nil
})
if err != nil {
return nil, err
}
return services, nil
}
+88
View File
@@ -0,0 +1,88 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/orcaman/concurrent-map"
)
type session struct {
Id *identity.TokenId
ClientId *identity.TokenId
Service *Service
Circuit *Circuit
}
func (s *session) latency() int64 {
var latency int64
for _, l := range s.Circuit.Links {
latency += l.SrcLatency
latency += l.DstLatency
}
return latency
}
type sessionController struct {
sessions cmap.ConcurrentMap // map[string]*Session
sessionsByService cmap.ConcurrentMap // map[string]*Sessions
}
func newSessionController() *sessionController {
return &sessionController{
sessions: cmap.New(),
sessionsByService: cmap.New(),
}
}
func (c *sessionController) add(sn *session) {
c.sessions.Set(sn.Id.Token, sn)
if !c.sessionsByService.Has(sn.Service.Id) {
c.sessionsByService.Set(sn.Service.Id, cmap.New())
}
t, _ := c.sessionsByService.Get(sn.Service.Id)
sessionsForService := t.(cmap.ConcurrentMap)
sessionsForService.Set(sn.Id.Token, sn)
}
func (c *sessionController) get(id *identity.TokenId) (*session, bool) {
if t, found := c.sessions.Get(id.Token); found {
return t.(*session), true
}
return nil, false
}
func (c *sessionController) all() []*session {
sessions := make([]*session, 0)
for i := range c.sessions.IterBuffered() {
sessions = append(sessions, i.Val.(*session))
}
return sessions
}
func (c *sessionController) remove(sn *session) {
c.sessions.Remove(sn.Id.Token)
if t, found := c.sessionsByService.Get(sn.Service.Id); found {
sessionsForService := t.(cmap.ConcurrentMap)
sessionsForService.Remove(sn.Id.Token)
if sessionsForService.Count() < 1 {
c.sessionsByService.Remove(sn.Service.Id)
}
}
}
@@ -0,0 +1,165 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-foundation/identity/identity"
)
type SessionLifeCycleListener interface {
SessionCreated(sessionId *identity.TokenId, clientId *identity.TokenId, serviceId string, circuit *Circuit)
SessionDeleted(sessionId *identity.TokenId)
CircuitUpdated(sessionId *identity.TokenId, circuit *Circuit)
}
// SessionLifeCycleController allows for sending session changes to multiple Listeners
type SessionLifeCycleController interface {
SessionLifeCycleListener
// AddListener adds the given listener to the controller
AddListener(listener SessionLifeCycleListener)
// RemoveListener removes the given listener from the controller
RemoveListener(listener SessionLifeCycleListener)
// Shutdown stops the controller
Shutdown()
}
// NewSessionLifeCycleController creates a new SessionLifeCycleController instance
func NewSessionLifeCyleController() SessionLifeCycleController {
controller := &sessionLifeCycleControllerImpl{
changeChan: make(chan sessionLifeCycleControllerChange, 1),
eventChan: make(chan sessionLifeCycleEvent, 25),
listeners: make(map[SessionLifeCycleListener]SessionLifeCycleListener),
}
go controller.run()
return controller
}
type sessionLifeCycleControllerImpl struct {
changeChan chan sessionLifeCycleControllerChange
eventChan chan sessionLifeCycleEvent
listeners map[SessionLifeCycleListener]SessionLifeCycleListener
}
func (controller *sessionLifeCycleControllerImpl) SessionCreated(sessionId *identity.TokenId, clientId *identity.TokenId, serviceId string, circuit *Circuit) {
event := &sessionCreatedEvent{
sessionId: sessionId,
clientId: clientId,
serviceId: serviceId,
circuit: circuit,
}
controller.eventChan <- event
}
func (controller *sessionLifeCycleControllerImpl) SessionDeleted(sessionId *identity.TokenId) {
event := &sessionDeletedEvent{
sessionId: sessionId,
}
controller.eventChan <- event
}
func (controller *sessionLifeCycleControllerImpl) CircuitUpdated(sessionId *identity.TokenId, circuit *Circuit) {
event := &sessionCircuitEvent{
sessionId: sessionId,
circuit: circuit,
}
controller.eventChan <- event
}
func (controller *sessionLifeCycleControllerImpl) AddListener(listener SessionLifeCycleListener) {
controller.changeChan <- sessionLifeCycleControllerChange{listener, sessionLifeCycleControllerAddListener}
}
func (controller *sessionLifeCycleControllerImpl) RemoveListener(listener SessionLifeCycleListener) {
controller.changeChan <- sessionLifeCycleControllerChange{listener, sessionLifeCycleControllerRemoveListener}
}
func (controller *sessionLifeCycleControllerImpl) Shutdown() {
controller.changeChan <- sessionLifeCycleControllerChange{nil, sessionLifeCycleControllerShutdown}
}
func (controller *sessionLifeCycleControllerImpl) run() {
running := true
for running {
select {
case change := <-controller.changeChan:
if sessionLifeCycleControllerAddListener == change.changeType {
controller.listeners[change.listener] = change.listener
} else if sessionLifeCycleControllerRemoveListener == change.changeType {
delete(controller.listeners, change.listener)
} else if sessionLifeCycleControllerShutdown == change.changeType {
running = false
}
case event := <-controller.eventChan:
event.handle(controller)
}
}
}
type sessionLifecycleControllerChangeType uint8
const (
sessionLifeCycleControllerAddListener = iota
sessionLifeCycleControllerRemoveListener
sessionLifeCycleControllerShutdown
)
type sessionLifeCycleControllerChange struct {
listener SessionLifeCycleListener
changeType sessionLifecycleControllerChangeType
}
type sessionLifeCycleEvent interface {
handle(controller *sessionLifeCycleControllerImpl)
}
type sessionCreatedEvent struct {
sessionId *identity.TokenId
clientId *identity.TokenId
serviceId string
circuit *Circuit
}
func (event *sessionCreatedEvent) handle(controller *sessionLifeCycleControllerImpl) {
for _, handler := range controller.listeners {
go handler.SessionCreated(event.sessionId, event.clientId, event.serviceId, event.circuit)
}
}
type sessionDeletedEvent struct {
sessionId *identity.TokenId
}
func (event *sessionDeletedEvent) handle(controller *sessionLifeCycleControllerImpl) {
for _, handler := range controller.listeners {
go handler.SessionDeleted(event.sessionId)
}
}
type sessionCircuitEvent struct {
sessionId *identity.TokenId
circuit *Circuit
}
func (event *sessionCircuitEvent) handle(controller *sessionLifeCycleControllerImpl) {
for _, handler := range controller.listeners {
go handler.CircuitUpdated(event.sessionId, event.circuit)
}
}
+89
View File
@@ -0,0 +1,89 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 network
import (
"github.com/netfoundry/ziti-foundation/identity/identity"
"github.com/michaelquigley/pfxlog"
"sort"
)
func (network *Network) smart() {
log := pfxlog.Logger()
log.Debugf("smart network processing")
/*
* Order sessions in decreasing overall latency order
*/
sessions := network.GetAllSessions()
log.Debugf("observing [%d] sessions", len(sessions))
sessionLatencies := make(map[*identity.TokenId]int64)
var orderedSessions []*identity.TokenId
for _, s := range sessions {
sessionLatencies[s.Id] = s.latency()
orderedSessions = append(orderedSessions, s.Id)
}
sort.SliceStable(orderedSessions, func(i, j int) bool {
iId := orderedSessions[i]
jId := orderedSessions[j]
return sessionLatencies[jId] < sessionLatencies[iId]
})
/* */
/*
* Develop candidates for rerouting.
*/
newCircuits := make(map[*session]*Circuit)
var candidates []*session
count := 0
ceiling := int(float32(len(sessions)) * network.options.Smart.RerouteFraction)
if ceiling < 1 {
ceiling = 1
}
if ceiling > int(network.options.Smart.RerouteCap) {
ceiling = int(network.options.Smart.RerouteCap)
}
log.Debugf("smart reroute ceiling [%d]", ceiling)
for _, sId := range orderedSessions {
if session, found := network.GetSession(sId); found {
if updatedCircuit, err := network.UpdateCircuit(session.Circuit); err == nil {
if !updatedCircuit.EqualPath(session.Circuit) {
if count < ceiling {
count++
candidates = append(candidates, session)
newCircuits[session] = updatedCircuit
log.Infof("rerouting [s/%s] [l:%d] %s ==> %s", session.Id.Token, sessionLatencies[session.Id], session.Circuit.String(), updatedCircuit.String())
}
}
}
}
}
/* */
/*
* Reroute.
*/
for _, session := range candidates {
if err := network.smartReroute(session, newCircuits[session]); err != nil {
log.Errorf("error rerouting [s/%s] (%s)", session.Id.Token, err)
}
}
/* */
}
+38
View File
@@ -0,0 +1,38 @@
/*
Copyright 2019 Netfoundry, Inc.
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
https://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 ctrl_msg
import (
"github.com/netfoundry/ziti-foundation/channel2"
)
const (
ContentTypeSessionSuccessType = 1001
ContentTypeSessionFailedType = 1016
SessionSuccessAddressHeader = 1100
)
func NewSessionSuccessMsg(sessionId, address string) *channel2.Message {
msg := channel2.NewMessage(ContentTypeSessionSuccessType, []byte(sessionId))
msg.Headers[SessionSuccessAddressHeader] = []byte(address)
return msg
}
func NewSessionFailedMsg(message string) *channel2.Message {
return channel2.NewMessage(ContentTypeSessionFailedType, []byte(message))
}
+52
View File
@@ -0,0 +1,52 @@
# Phase 12: Smart Routing 1
Ziti Fabric 0.4 includes an initial, experimental implementation of "smart routing".
Smart routing continually measures the latency between established links in the overlay mesh. The latency data is used in these ways:
1. When establishing a "circuit" ("path" across the mesh) for a new session, the current latency data is used as a component of the overall "cost" of each candidate path. The cost is fed into the shortest path computation, which uses that data to select the least expensive path across the mesh. So, when new sessions are created, they're given the current least expensive path across the mesh.
2. The controller periodically scans the network, looking for the sessions experiencing the highest amount of latency. A portion of the sessions with the most latency have new paths computed, and if a session's newly-computed path is different then its current path, that session will be dynamically "optimized" onto the new path.
This is an experimental implementation. It _should_ result in relatively balanced network utilization, along with some amount of the active network sessions experiencing better then average performance. These algorithms need testing and feedback from real world workloads.
Keep in mind that the "cost" attribute on each link is user-settable (see the `ziti-fabric link set-cost` tool), and will be added to the link cost considered by the controller. By smart utilization of the link cost, you can directly influence the decisions made by the smart routing algorithms.
Setting the link cost will provoke the controller to immediately re-consider the routes for each of the sessions currently traversing the adjusted link. If better paths are available for those sessions, they will be re-routed.
## Network Scaling
This style of smart routing is intended to dovetail with a scaling strategy where additional network capacity (routers) is spun up and made available as the network workload increases. Smart routing will see the new, non-latent capacity and begin redirecting traffic to it, re-balancing the network.
There are quite a number of different strategies that are possible for selecting locations to site additional mesh capacity. The work around exploring and understanding those strategies needs to be coordinated with the design of the smart routing and network optimization algorithms present in Ziti. The "fablab" project currently exists to create tooling and infrastructure to explore these kinds of scenarios. We also encourage you to experiment.
As overall network volume decreases, the additional capacity can be removed from the overlay mesh, and smart routing will again optimize the sessions to best take advantage of performance available in the mesh topology.
## Configuration
There are a couple of configuration settings for tuning how smart routing behaves on your network:
network:
cycleSeconds: 15
smart:
rerouteFraction: 0.02
rerouteCap: 4
The above stanza goes into your controller's YAML configuration file.
The `cycleSeconds` parameter controls how frequently the controller will scan the network, looking for active sessions to be optimized.
The `rerouteFraction` parameter controls the fraction of the total number of sessions to be considered for optimization. The sessions are scanned in descending order of their overall latency, and the worst `rerouteFraction` will be considered.
The `rerouteCap` parameter defines the upper limit on the total number of sessions to be considered for optimization, even if `rerouteFraction` would select more sessions. This allows an overall cap to set on the number of sessions to be optimized.
By tuning the controller scan cycle rate (`cycleSeconds`), you can control how frequently your network will be optimized. This is the primary configuration control, which determines how quickly the smart routing algorithms will respond to changing network weather. This performance will need to be balanced against controller CPU burn, and the additional control plane bandwidth required to implement the changing session routing.
The current implementation is missing a configuration setting, which determines how frequently latency probes are performed on the overlay mesh links. Latency probes are currently performed on 10 second intervals. There probably is not much point in reducing `network.cycleSeconds` below `10`, until the latency probe interval can be configured. We expect this, along with other additional configuation settings, will be available very soon.
## What's Next?
Research for the next version of these algorithms is currently underway. We expect the next iteration to include cost considerations and targets around bandwidth metrics.
We would love your feedback. Please let us know how our current smart routing implementation performs on your real world workloads. If you have ideas for additional capabilities, please let us know!
+56
View File
@@ -0,0 +1,56 @@
v: 2
identity:
cert: fabric/etc/ca/intermediate/certs/001-client.cert.pem
server_cert: fabric/etc/ca/intermediate/certs/001-server.cert.pem
key: fabric/etc/ca/intermediate/private/001.key.pem
ca: fabric/etc/ca/intermediate/certs/ca-chain.cert.pem
# Configure the forwarder options
#
forwarder:
# How frequently does the forwarder probe the link latency. This will ultimately determine the resolution of the
# responsiveness available to smart routing. This resolution comes at the expense of bandwidth utilization for the
# probes, control plane utilization, and CPU utilization processing the results.
#
latencyProbeInterval: 1000
#trace:
# path: 001.trace
#profile:
# memory:
# path: 001.memprof
# cpu:
# path: 001.cpuprof
ctrl:
endpoint: tls:127.0.0.1:6262
listeners:
- binding: proxy
address: tcp:0.0.0.0:1122
service: ssh
- binding: proxy
address: tcp:0.0.0.0:10001
service: mgmt
- binding: transport
address: tls:0.0.0.0:7001
options:
retransmission: true
randomDrops: true
drop1InN: 5000
- binding: udp
address: udp:0.0.0.0:7002
options:
retransmission: true
randomDrops: true
drop1InN: 5000
# xctrl_example
#
#example:
# delay: 1
+21
View File
@@ -0,0 +1,21 @@
v: 2
identity:
cert: fabric/etc/ca/intermediate/certs/002-client.cert.pem
server_cert: fabric/etc/ca/intermediate/certs/002-server.cert.pem
key: fabric/etc/ca/intermediate/private/002.key.pem
ca: fabric/etc/ca/intermediate/certs/ca-chain.cert.pem
#trace:
# path: 002.trace
ctrl:
endpoint: tls:127.0.0.1:6262
link:
listener: quic:127.0.0.1:6002
listeners:
- binding: transport
address: tls:0.0.0.0:7002 # make sure we don't conflict with router 001
+34
View File
@@ -0,0 +1,34 @@
v: 2
identity:
cert: fabric/etc/ca/intermediate/certs/003-client.cert.pem
server_cert: fabric/etc/ca/intermediate/certs/003-server.cert.pem
key: fabric/etc/ca/intermediate/private/003.key.pem
ca: fabric/etc/ca/intermediate/certs/ca-chain.cert.pem
# Configure the forwarder options
#
forwarder:
# How frequently does the forwarder probe the link latency. This will ultimately determine the resolution of the
# responsiveness available to smart routing. This resolution comes at the expense of bandwidth utilization for the
# probes, control plane utilization, and CPU utilization processing the results.
#
latencyProbeInterval: 1000
#trace:
# path: 003.trace
#profile:
# memory:
# path: 003.memprof
# intervalMs: 5000
ctrl:
endpoint: tls:127.0.0.1:6262
dialers:
- binding: transport
options:
retransmission: true
randomDrops: false
drop1InN: 5000
+25
View File
@@ -0,0 +1,25 @@
v: 2
identity:
cert: fabric/etc/ca/intermediate/certs/004-client.cert.pem
server_cert: fabric/etc/ca/intermediate/certs/004-server.cert.pem
key: fabric/etc/ca/intermediate/private/004.key.pem
ca: fabric/etc/ca/intermediate/certs/ca-chain.cert.pem
# Configure the forwarder options
#
forwarder:
# How frequently does the forwarder probe the link latency. This will ultimately determine the resolution of the
# responsiveness available to smart routing. This resolution comes at the expense of bandwidth utilization for the
# probes, control plane utilization, and CPU utilization processing the results.
#
latencyProbeInterval: 1000
#trace:
# path: 004.trace
ctrl:
endpoint: tls:127.0.0.1:6262
link:
listener: quic:127.0.0.1:6004
+35
View File
@@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
MIIGFjCCA/6gAwIBAgIJAOq4qnKYsnlbMA0GCSqGSIb3DQEBCwUAMIGXMQswCQYD
VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEjAQBgNVBAcMCUNoYXJs
b3R0ZTETMBEGA1UECgwKTmV0Rm91bmRyeTEpMCcGA1UECwwgTmV0Rm91bmRyeSBD
ZXJ0aWZpY2F0ZSBBdXRob3JpdHkxGzAZBgNVBAMMEk5ldEZvdW5kcnkgUm9vdCBD
QTAeFw0xODA1MTgxNjU0NDdaFw0zODA1MTMxNjU0NDdaMIGXMQswCQYDVQQGEwJV
UzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEjAQBgNVBAcMCUNoYXJsb3R0ZTET
MBEGA1UECgwKTmV0Rm91bmRyeTEpMCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZp
Y2F0ZSBBdXRob3JpdHkxGzAZBgNVBAMMEk5ldEZvdW5kcnkgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqr9dr7vj0KvdYkZgMMgS00ohGd
MVzpN6JFKb87uEbb7U2uH905uNkm4OOyilY/w0UD8PCU3ISmFgoB5LAcOa77icV3
qJ+h4MTaVirps2EtyA34DcyU3k+Fvauc7fZhhnAdcZai/6O13aBorR9awfwJDGfq
fa/uq88xN5gU/yniOpNnDZu9lbD4sxhMULLnjUMWpAtBqwyDXSZdEBVc7s5r3L4P
Wt7XMurjBLq7wi0mgrN+a9mQy8P3QL+1Di7SIlovsYHZIT0w48N4ZW+mehIAiRJW
a9ENQCPbH0+66TKTOgvU4BbNzkKD7kvFGYt6NUTs+89wdpO01qDa1EPDa9sx4Uo3
w29AfVBhxo9Zl7hWX1voMC5NoflE33X0f8X5kfKNnJtU/ALXiV+Wd4grI3LwkRX9
gwhsQANae6J53MN6GTY6mf7T71mvZf/F+MKjyJYcVs1pWdBfvB1Fo6jvzeUSi6dy
p7joUMEsGnIC4ModCkhPcwryuQL/XIE3Gwfji8zB5Rm4rS7s8L0oe2alB+5+VgZ/
EDfiRjpZTePPJAd62gtiOjFOo0QSO4anDLajWPtFoIEQUhtiWeVuGkRYVNpB/3rP
YZDqbVTfLsnIN+UdK70WjUk2QbepeyMPB1r/pbs1ElGB0WuGc7CubsXov1fg/Uef
NT9UkvrovA+aIdWNAgMBAAGjYzBhMB0GA1UdDgQWBBRB8+kUbjrl49psAMznlIp3
1nor6TAfBgNVHSMEGDAWgBRB8+kUbjrl49psAMznlIp31nor6TAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAJ9TvhopZ
E5rGe57y1lvk8WQRucNFxGspdGGem0DS+uAHjJ2djFf2EQFkPyjo8HLet7C+XmZ5
I4ETrPJvgkPzSUd3MdJUUjBvOw054SMKyW/88D/dj4nsMK3qA80wN5i4Syx2p5KI
yhjPaCe96fh+VIwqBMAl3f9KlWuffkfNeJT0ySYVWk8QydNy06PD4mdwKQbn4P03
Xh3G2Bn+1R9sf4ZR+HIZXFF0+tfkpCoDJqkx8Roxddrwo2zs8b6wOJJYj5KD2NQa
3GCR8p1U20Vb5AxfLLt5PeJc8D7WvaEUCGB8hoa0ElSHE8cyEq2NsCABf7r7YPcY
BtoPX6VxAeEqvLeHKBDc5fv0UlbrPI9LS2JX8t+i8epf/fH2rk9NUE7AQ5Lb8sJz
hHYztL4cg9/h5In6cfi4Ih0njZYKuiv+ZGy4vuB4KSyLvQYe4LKc/jKpAWzO79S1
sESw3yWhOcuk4ifFoYHVjxQ+bXhvYujX/ceSgxWHT9RocnROadnWvuZ8aF9HIBlh
F29PWwH4RvhvkDbMRmqxNz0jAU6GcjhmKU+hnmzbF6OqtXDcZK+i8SF2mutCa/G6
H5VDbfL/7/jP1vbFbIO7bNW94HjCGo97auSDEmsSXgZ8uTYNb503qPzdmXiU+hRl
jDOQLqtoJofIWUChpNurPQfqu9UljEk9LpQ=
-----END CERTIFICATE-----
+1
View File
@@ -0,0 +1 @@
V 280515165723Z 1000 unknown /C=US/ST=North Carolina/O=NetFoundry/OU=NetFoundry Certificate Authority/CN=NetFoundry Intermediate CA
+1
View File
@@ -0,0 +1 @@
unique_subject = yes
View File
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEtzCCAp+gAwIBAgICEA0wDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDgxMzE5Mjg0MFoX
DTI4MDgxMDE5Mjg0MFowczELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMQwwCgYDVQQDDAMwMDEwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDbqcpLyr/yZFw2YxOy1RKhO/jw4ra8Z15KdEHN
bz5hcNlT6dmVFQj4sBW9AsB06ynqzrO5cKjnWF/LZXzTRGlDUmxmKZOZCcb4NVy/
4fwWNN8IBuJQBrrz1qTMBJaG4DRbZ+zTICrb2EJGJyNFZbNKvlRQzn4xMjMm57Up
v2enImy8BHUWahjkcCG/uXPTJzjupTweRI34DPxTU2q3hH7zwocdFMe/mkzShMb1
qwj33BByHWzSK60NawE140li24v3ELLhre/C6cZSV1Nyk4U9jreSeaQ/uj5cmk08
dPnCeBcTqMIwhAOihMYM3vqhOZbNfssr7gXd0/8bv/7P6JGpAgMBAAGjPDA6MAkG
A1UdEwQCMAAwCwYDVR0PBAQDAgXgMCAGA1UdEQQZMBeCCWxvY2FsaG9zdIcEfwAA
AYcEAAAAADANBgkqhkiG9w0BAQsFAAOCAgEAmPpB0t/nGnpJ8zS01256g0P1UjwI
GOj8DF4XhQVChDk+9VYEg1DIvku52m1F7Wxgp8q2ELTEjnywZrK/VD4sos0cWuID
f4DYsUqdM8gGHc5uVY/eubWtcWAo7qegrfdq+S6MD8DqdiCnBTe5xruWBaZhm9aP
lwPpVSqDVTGyjV9wLB/mNc0xOEYg0fenS0UbHZAlDWHHCZQmkMnW1bU07qxKfu0i
/1Ok0H0w/kEIxv6LuSSxPJQ9yvy9eB+2HypdeQl4kuZzue/tXrLn20MhWHPaJZjr
BUDcLYg7Badd2lyySh4T5eSY1XyEPogHCx8XMWRixjkaWgdZpriVgf1g7wXc6W6G
fD9pxQyQ1pq3zL9rbWWzyrmp4gwOsAOq1HwmGJtHI9qVU3VxSrbZCF9bnYTRdIpS
UNWMqOv5uuYspKzcYl2FFdGUd3QQ5ExSsFRZ2+xjfDyhlPOOuJtb3RK3HoY35Gjg
DWB3mU331H+UtCE8oCs5nKQcS0iH86D+CnqJkkvQHSVH+QV4JU202x+Ym0q3aaqy
AuE5Nj5ybnrN/WsL3YYkpbIYZAvGWnvHGYOLjs7caLBwv2vcfibAsPX4m+OhJfuU
0zCzTCS6fzf/Jg9R1p7392CESYfJ34eVuda7ctWZrMXFyGsxeydCO2TTdDjRolO/
tToiRRetrmH3JcY=
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEvjCCAqagAwIBAgICEAkwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDgxMzE5MjcyNVoX
DTI4MDgxMDE5MjcyNVowejELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMRMwEQYDVQQDDAowMDEtc2VydmVyMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA26nKS8q/8mRcNmMTstUSoTv48OK2
vGdeSnRBzW8+YXDZU+nZlRUI+LAVvQLAdOsp6s6zuXCo51hfy2V800RpQ1JsZimT
mQnG+DVcv+H8FjTfCAbiUAa689akzASWhuA0W2fs0yAq29hCRicjRWWzSr5UUM5+
MTIzJue1Kb9npyJsvAR1FmoY5HAhv7lz0yc47qU8HkSN+Az8U1Nqt4R+88KHHRTH
v5pM0oTG9asI99wQch1s0iutDWsBNeNJYtuL9xCy4a3vwunGUldTcpOFPY63knmk
P7o+XJpNPHT5wngXE6jCMIQDooTGDN76oTmWzX7LK+4F3dP/G7/+z+iRqQIDAQAB
ozwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAXgglsb2NhbGhv
c3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggIBAInGp9pJYVCUJErrsart
M5vtK/Mm2qBpbpcdj8Gnokh3ubL5rY0MjpOz5AWj0c4wFgV28EdAncjXEor4VOEI
JgsbTm3STNJnA9BT7vAwNezbl1N6jOTh8vUrjaSx5AsWVbah6nZX8rfc9db/m+zd
iYedN0VJF9Fi6wrK2CssDkbYc6PNrK/lhgyWAcacQdthZTWmqmObNToAY1FNk6m0
OHXW0ipuRFqNRUw6snU9+SqbcOnCqKtpAan+YfNj8cXuiytjxGfQVpQVsp9zdXqX
efkc13iKcJ46GLkWcWrjWKkVt3EjUbaWENECmz4/Nd1LgzmEaTwVUCxtAlVnKk7P
oW5dgJQpWk5chP0XidyvwiKf2iC/fck+drj7f+iJDd26pZ1LFvlA7MO9eYu5HpWQ
tL4dF2MnubU98Hg3m/ryygLZh7I2rvPsx8/g1m8LYhiJ4QFkXKdZ8IQ1daqUtUmr
ooH++0C6r9fNgyEbhGMungcC5DHP58CofDJuoUyYhUyNpQgq/XWKx23epQiOgnJm
rb3AB8X3TE40LneNfKdS+BNq4q2s/GYrI0BfmZ1BXjH0YgR0iLv1PZl+0L7++RHO
wE6kjuIs64UCkzIzaNByVUuAVRRqZUV0j6U07ONovqhPSNwky4h8VBaTLHWOfK4Q
k0H8p9E549WS07pfmKIgoKvh
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEtzCCAp+gAwIBAgICEA4wDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDgxMzE5MjkwM1oX
DTI4MDgxMDE5MjkwM1owczELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMQwwCgYDVQQDDAMwMDIwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDGfv6XkzO866Bi+rVZNGHtOjxXZE0HW5rwaxVQ
3zMtk/8Elrlyr6ZuguObyUfLJdnScvirjaGbUqm6XTL7H9yRUKBshQZDtaci6XRm
4gq21X7pQ0f1oUmsW5q+dDqbJ8JJ+HbEhbPH7MmZfsTLIASWMKK/pEzTf9t98fyN
4LJO/Yr++PmE7ZEz+YRwGphSupWEatLlfRh7XviihxKcXTiH8Z/qD8A6W8lngu2d
HFU7VXiBITPwvx/GeanahwduqpnZJNWaCAA7h0MCCatJ01ITGGPgsBCfJWyezBw8
CAyLWYL/dlauaq1q5Xg/Bp8V6HddEy20QypRxdY/kPobAcVPAgMBAAGjPDA6MAkG
A1UdEwQCMAAwCwYDVR0PBAQDAgXgMCAGA1UdEQQZMBeCCWxvY2FsaG9zdIcEfwAA
AYcEAAAAADANBgkqhkiG9w0BAQsFAAOCAgEAAA9Str4WlbxQaKuzZ38glD9jjkCE
tA9jwCJnNv5uh1o4z4RFLPK4s9EsY28g13LRKTm5ft0ndO4ofTb0xUDH9ddeTjsY
QtXrzrh79MzH9IIIyej8YubRqeVeQdR0a0RoG4IACziaA3hrZlIDbM7xxrXKYVzC
dvV7epVT1bO4TrIf96/JwHBBtbWBi+GedOtPgAxlFQ99nvlPEeKIEmp9kv111Ot/
WpawpVE6EALDFAS7rT3zU2t/9qHtWb6i6QMGdJ/WKsHVOOMRMhR86tYKxYz1M4Ux
NI/2GQCdYSmbmzAdKKWWrrTCMal6MjhPjWnFyjXUgUkSuIyfJ8U5MQ/0hI8BF3K5
SqH96WVoKGud0ohPC2bjdZ8XqRHvBU6nhJ5pnNA8YjMevombwMWOAVpYjfnCJK39
hODeOiwdDm0AWDJ18wcAnyJH+aX6L50y2BWhE2Gc90P2kqA6YgRCAy2rwBRvDNua
yPGSyHJfNbyQ8e6tVbWDEkIQkP1A0uDD3SxoWNcxOeAmtQV6Ig779pxZ+GqHKR57
bJ5wJjFJsPRgHz/ViP11IMffldb+6tvMKR4iVZO2eZ9GPqIwxXF154pKR2lL+mvV
3I9kBfkYLd4Z61hTmCAJpZaIlVTGsjLAU2W56m/OOI8piaZ/UUMMrFiVtKuFp6kF
OcrllxHkVaVgFbA=
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEvjCCAqagAwIBAgICEAowDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDgxMzE5MjczNloX
DTI4MDgxMDE5MjczNlowejELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMRMwEQYDVQQDDAowMDItc2VydmVyMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxn7+l5MzvOugYvq1WTRh7To8V2RN
B1ua8GsVUN8zLZP/BJa5cq+mboLjm8lHyyXZ0nL4q42hm1Kpul0y+x/ckVCgbIUG
Q7WnIul0ZuIKttV+6UNH9aFJrFuavnQ6myfCSfh2xIWzx+zJmX7EyyAEljCiv6RM
03/bffH8jeCyTv2K/vj5hO2RM/mEcBqYUrqVhGrS5X0Ye174oocSnF04h/Gf6g/A
OlvJZ4LtnRxVO1V4gSEz8L8fxnmp2ocHbqqZ2STVmggAO4dDAgmrSdNSExhj4LAQ
nyVsnswcPAgMi1mC/3ZWrmqtauV4PwafFeh3XRMttEMqUcXWP5D6GwHFTwIDAQAB
ozwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAXgglsb2NhbGhv
c3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggIBAKoioClffib8U2RlOXTD
oPc5FWW6pPQoYP8Acy67G96Pacudy7wLWO7TI1s5KQyceyz5ZNmITtfXMgxJbCJv
Gd36fikb2iqlfMM89JWz17M5wTpZ83DRNLqDEL5NkEy6DZmnMd/w/saRvc2ZYsul
CaNsK9xQBgwesYMC80stTzpiw8p1sDh/JDjMDq+NodNtxgFN4nm7wIbmRRS2V1si
jfSAPK3cMyMNEGp/2C5ELpEq2kbHX/J4GOlva5c2g6Jte6sz328pCSmklNnGpjZL
HsSvXp4YUFwlcsF68qg9z/XRMvjodxQL5Jgz7Wz07lSIkqmRjZbECXXmdkyml3+E
zB2xIEzTbSG38tKOZ9bJ0KN7wPVUgZzIxX96/12a2q+fVVtKkVVQ/Yd/M9CpP8Yh
PRAznJppvYxdVpGIFyyD28b1K9TH3Hgo6W5GzJCqDf2tr+xVsLyoFu+ctBj9OKoT
yVW99v23d8iNiB4Kj4CIXIu7gbp4iJ1FbjBzubDVQnjwTkv3KnW6oOXSPN6rAg3+
Pa/fizIAw5ScWg8IV3jrVrQm5XOr6GN5dh42f9Pd5BSjKO6uL5asKPD6hptAKdYz
DTs13LHwtimowZuhZ0+fmthHMLTCygkXUwLmDEka6Mqivhr4zdBjVY1qQefOjm2W
i8pWD20RqNu3F2f0HL6JZQ1/
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEtzCCAp+gAwIBAgICEA8wDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDgxMzE5MjkxM1oX
DTI4MDgxMDE5MjkxM1owczELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMQwwCgYDVQQDDAMwMDMwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCcg8MIdQyQgc/av7DR8Ulx8ysYWGrT8wIoV515
O2Vn5V3PGNJqosNzOyVmPRhRxN1Bmutj39CA53Jnjb1BgH4e5WEdqWoW2pfuf2fk
kx0zgoxRbiOFT1+SmcUKQo9m6PfsAGuLUuW2cvPLNk6SPHuGbcvPkP8jUqjsP9ut
ewVGTiJto18SbOANg7PuVA76feeYPOitaDaX8kAGTsaNMEz/lriPCwzqrwpCfw+I
5QW91u/T52o/O4REKCno1/I3FJpk3LbEAYiGUtXOVlv7/HBj4BfCCpdcIA9P7TYk
v1dtadylT4G1lnXz+OoOUFvvIoFBaNB/x4zDVvIpncpuz/CJAgMBAAGjPDA6MAkG
A1UdEwQCMAAwCwYDVR0PBAQDAgXgMCAGA1UdEQQZMBeCCWxvY2FsaG9zdIcEfwAA
AYcEAAAAADANBgkqhkiG9w0BAQsFAAOCAgEAqfnYmdpLwOKsqoh8EXMdsa9AQeRy
MiqZEpyA6tCkfthC1qzFDr4L5Gb5eXZI0Xo0yYMTvMx2oaP/zu81EEvsj6L5Tnig
eaZqqm7DlUp42FQOdikv2OTctuZ2y1QAsE4+zStawE7il4ED5C2cTDbVBZccJa2X
jl7WB4505Ucec+fmgX7lQB99/NF5Ca9cCnvhb71waTPsV9GTXTBYPvd44N2g/o7y
Wq/3ldqMkdj2TrPTWjVxXfDD6CtHCAyXpJmab6tHSbiquYyHxBVgcJgxrlxxpH4U
/xdYPjRHqXh02q42Pm0MwIp05NdcxcUv+H+9YKlqr9Mz7zZdbjb22QbfZr+59xwd
V4QW+EhEV0OWcAvkbULosSolm6EZX3QmfET53rZqlXf2FSTSF+J6DhQ0hCIIk86D
5Sd+gHO2xPieFtFD4DdcW/RLa7a5csIFg919zpADT3XOc9bM27gcBc1uc5h9Ov+/
KzGRcnobI4f5sRgAJumLMgVF1iIE94QJpC07zmzy6t9GEJ3z2t24v58Ai6h4THg0
CTa7FCZzcvNX7dTu3tueS23NJtQNMq89sOJiyrXFi/8+sgnqz+ed3QMtPpU6j6bf
WSXtC7GDkumLYMxnQm7v9eVjO4EkC+2dOfIZBDI+nrGXXw0OpDptXgGQEmFC3ie4
2M821rvVbFSjhDU=
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEvjCCAqagAwIBAgICEAswDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDgxMzE5Mjc0OVoX
DTI4MDgxMDE5Mjc0OVowejELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMRMwEQYDVQQDDAowMDMtc2VydmVyMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIPDCHUMkIHP2r+w0fFJcfMrGFhq
0/MCKFedeTtlZ+VdzxjSaqLDczslZj0YUcTdQZrrY9/QgOdyZ429QYB+HuVhHalq
FtqX7n9n5JMdM4KMUW4jhU9fkpnFCkKPZuj37ABri1LltnLzyzZOkjx7hm3Lz5D/
I1Ko7D/brXsFRk4ibaNfEmzgDYOz7lQO+n3nmDzorWg2l/JABk7GjTBM/5a4jwsM
6q8KQn8PiOUFvdbv0+dqPzuERCgp6NfyNxSaZNy2xAGIhlLVzlZb+/xwY+AXwgqX
XCAPT+02JL9XbWncpU+BtZZ18/jqDlBb7yKBQWjQf8eMw1byKZ3Kbs/wiQIDAQAB
ozwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAXgglsb2NhbGhv
c3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggIBAHyJ6NQTPGg/CJTC/9nb
KHaMra0aFLnvNUFXCiRsLXY7VNx7Z8HsWkm6TrTsTPIu3deMxfg4Rvxc0vDDsl2p
YU3rjUNMpzWZQXj2mNO1cPdG30fAIWNCc5qiGXoI2eJjEDM5QXyLMVqneTg7PQ0g
cAnf6h6FLmifhdR5e/VZPEG9mEhubETevmIq4lSqQ1lopMjihT1fC9l+a/WaQXxa
Ic6Uq9vHdGONFgvjsnjXN8py/zKw303mnAXOEHiaznyxJ2+5UjZ7IYxcO5p6soJR
XkkHIv/WyAM+kfljvzVjxx2jTwV15L+399T0x8g5F0fjwd5c0H05JcOfpVB1gPq6
HZ+B1wyRs5xNQtYJ9On8KIW1Y21fzjUhKglhGrZ3148DB9WJ/Fk7CJUEuTk37E1L
LIhI4/ml1fvE2VNmPabI7xjMtFRaALRKmAhGINnI/guAKHYJqcSQIBIJfOTrXYx1
Z2SrT0sUre2toVBNzyyI68m7eGOYaMCTOUL5gCI/SXf3r4LKhNitVow8Rw3Xt1il
9xRxZ6SzTs065kjJys4Tuw4a+NF21PVq4dD/HVVabQRDf/6OubjAARo4FyomWsyy
nkWwIoZi21e2F+RM5rnuXUuZ7/eV6dJPfBQ2E4y4E0qE+vfNZpMi6OnJ1xdTfTx+
z9e60k+pZ+r/ozXQ2srE4ZvZ
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEtzCCAp+gAwIBAgICEBAwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDgxMzE5MjkyNFoX
DTI4MDgxMDE5MjkyNFowczELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMQwwCgYDVQQDDAMwMDQwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCqBDdOcSnf0fCctQwn+yyjygU3qqx74bQEpNh0
YEstPxbqOrGzkUUV6vIqMAfyQDcEAaXbxh35/W4ms73waN/+swYDkQVbUn31qB7n
psm/wqTz6NVUJ0r20KFJMVoGtfqO991gl1r/xFT6gc7+v+hGj+6iAuMED7v3xFA7
qNRD8N+iNzq2ArdUX4OfMN0d2y1Zq5sBqicA3vMV91+KDvKfmQRfGFkvyW6Y1S30
WDEWHVCgvmnnw9l8+A8kdEe5/YQbBe5bwdaLYCtv8h6Jz16Ok7jBbrrasYbpcsD8
wOI5CnyuV7wKcSFO/vcEO/P3YYqL65puKGKVMteyige4/rpvAgMBAAGjPDA6MAkG
A1UdEwQCMAAwCwYDVR0PBAQDAgXgMCAGA1UdEQQZMBeCCWxvY2FsaG9zdIcEfwAA
AYcEAAAAADANBgkqhkiG9w0BAQsFAAOCAgEAe6zSSoa8HWmjvtV6+aktA9vUgrHz
uUtw3OFM/4Gg2U/M1pDirBobh/EZwDqeTNqVFLBibMAWj3V0mND+SLm0V36hOFiE
lcnYSJXYoErJ7LLScUfRjPZdu9JhNl8DoQ8NgbHewVZhecA1MbUsTCbSNsufREM8
JYbd9+C52N4hqdfxuT+8imGE1Tg3/9CcldukVC4XNBhSYskhy7zMf6UNvTpBH3EO
434fraxCjgnPiQJq5S6Bq3Oq1Kbr0o/l7gLNIzz/KlOBJQsVIPExFEMQ7kZQzDQh
AmvuoRJEWB3fdYYmxGPOB/pJH8OIjyzcyC3XNDtyz51alXD3G40GHECO0Gnb/Tvo
kKODky2nPWrlVuoY6R6bQsFojsUQJSifz3p6jUE2Cpv3/axg0juumJKaaDJqoxVN
Bp1vGS+IIcj6nZEyfuUCRkaBAssUKXC2i1uTeoyovsBkjOef9fyclN5ym9/fsyUV
AnWxwpxIrfG26AeV2nenRLFUqzvP5xJNF1HBwijmOdYn2tlBImxv8ziRe7JhUyxE
fXiqFyEgaPDJ3SgDEIzmYd1tt7vqzpqJHQOe4hJDoga5q+qX8IJOJEz2vy6E9sm9
tGdVTvJG0422Hlk6OZcitD+zavn8KjVQ8mMszbm3LurjBYO3830KhAWrOiN6f8gg
X6sIXw8/4UKo5d0=
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEvjCCAqagAwIBAgICEAwwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDgxMzE5MjgwNFoX
DTI4MDgxMDE5MjgwNFowejELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMRMwEQYDVQQDDAowMDQtc2VydmVyMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqgQ3TnEp39HwnLUMJ/sso8oFN6qs
e+G0BKTYdGBLLT8W6jqxs5FFFeryKjAH8kA3BAGl28Yd+f1uJrO98Gjf/rMGA5EF
W1J99age56bJv8Kk8+jVVCdK9tChSTFaBrX6jvfdYJda/8RU+oHO/r/oRo/uogLj
BA+798RQO6jUQ/Dfojc6tgK3VF+DnzDdHdstWaubAaonAN7zFfdfig7yn5kEXxhZ
L8lumNUt9FgxFh1QoL5p58PZfPgPJHRHuf2EGwXuW8HWi2Arb/Ieic9ejpO4wW66
2rGG6XLA/MDiOQp8rle8CnEhTv73BDvz92GKi+uabihilTLXsooHuP66bwIDAQAB
ozwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAXgglsb2NhbGhv
c3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggIBAEPPwvTShn9x9E0um/Rj
HNjGzcuShd0E/tWlUHnn9F1i6Bz0SlbYngbxIiQUxgmraRtzH5wH4Z01xE6vdA6S
unckj1XyhwGTV+pufxSkRk6Ox67MZjjXOst36hyQjWNsNf3xDaPFxAvxqWqaMJdf
mHAUlDiXIw7kbuZM/eBGx97wNf2F1YPSnvcfPir5ZhGKg4K7ZIlV0Opvemwgqf0s
vjkQPRpnILfrInQbOlnFEDMh/tMN6eIEGPDX1MjmMS9cckr5ZacevrjlsNyuM7C/
QrVuYHFJ9Le1Eum6GjzMGILUxADoc3r1UgDfaysIaru02lefnhfNeOYixjvV3Dkc
rGH3Vtwc4a8M85qh7oKlJ4+f1e3Qn01LmDlD8Y99a30ebbtfApCCUbHJerXoG9Cy
J7hcFyO6ZBcTHfVWsHjAoL2oojdo6smbf+7pR8+pxAdnExRsI3sioaw9nnKPCOz2
T6NzlbvCts3d2Tpi9eJg2t+uxA02e+qxC+MIk+/xlZ41VvIkUsB4wQzK5nBpziCs
wGCzR/C0uHFUVPubQd0oB5Hh09Z4Dbff28m1N/Aqkty8SQGwz9sJAWVDN60cBGIj
f+NhitZf6ERhoZUDODuhcuNmCilmZn9inPIGgtMVhtWpFXIB3eSoMSHKUWyFeOfj
mv9NYvwrZkt0PCGOxCtwXAYV
-----END CERTIFICATE-----
@@ -0,0 +1,70 @@
-----BEGIN CERTIFICATE-----
MIIGBjCCA+6gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZcxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTESMBAGA1UEBwwJQ2hhcmxvdHRlMRMw
EQYDVQQKDApOZXRGb3VuZHJ5MSkwJwYDVQQLDCBOZXRGb3VuZHJ5IENlcnRpZmlj
YXRlIEF1dGhvcml0eTEbMBkGA1UEAwwSTmV0Rm91bmRyeSBSb290IENBMB4XDTE4
MDUxODE2NTcyM1oXDTI4MDUxNTE2NTcyM1owgYsxCzAJBgNVBAYTAlVTMRcwFQYD
VQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEpMCcGA1UE
CwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNVBAMMGk5l
dEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAsb1EPhMUweS9WpjT7L54xAOmZqugJ6fhSrFfLUwNUy172q+ASvZT
pT1zKIPcZpGmPB3TX2bHaAR67BbRkUR11JgWE3U8+FsGrYmPZtaKM6fg8Mh0WZ41
oMYQNJyQixOktrgqfybyJoT5PeT5AA7QQmd8mku2X9nkAu6gWPf2nHNc7SeQdijm
yQQaVK3oqyaxOzWzsU/XbfMEz/ObkefUxgt5Z6jlK0xcW0Q+QgtawMKLUiuo6obW
RPcl7Hm9Sze8XJS5pbvS5JkUszxoRZuDVHZylrlHIpA/IL+BnvS+M7SP28UWe9sk
rv/s6ACpJtuPJ1EYf5fakugOpY7i+hq7YNi//csbc49Qjn2OtttrR7JcTaHUEU1I
/tQbQGAtNkI4pJjRVUdDawQFQlWHZD1COixNLErs2HzAI00DhLrY6SKITI/kjN0X
x010XdMcdfay0PLWm6RwxiRmMQFL4GNIC895NF1q6xV4W4rWgqUNlcvKpy+i1chW
pRbUHe16ul0qh10fcESrRvAbXn5YrQJLrwbSr+85ubN8lYdNLE0qg2cIXZlUilar
ZZzWghtCe+KkUpjfRuAi/CqfSwNK3QXEfeVEK6S49mHeSekOizFIw7fmDhCz9vXw
MOnbryRSLEJks0gIRcSDVChXheAqC98y4kcQdniNWFnqJXoqA+rrSokCAwEAAaNm
MGQwHQYDVR0OBBYEFK8UXC/sq6dGVFAqEXHsQDzqzwuUMB8GA1UdIwQYMBaAFEHz
6RRuOuXj2mwAzOeUinfWeivpMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/
BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBDAAaOE2Nbb49eOjyTNxIeOB+ZKQjJ
1hUEgRrootAA8NYbtKW/vyxTWnNi5XOGXd4DFS9OKZ0mL/7NyLc0mbTwPH2ZT8KT
PUTSCpo6yktZ/7TMjyAtWZiOMg2EH+6m/nlNSXk/v5fb8+JQLdZfpxoA017dHh3t
c8l7KOskCZNwQHgF/YMXrPXUNbsGkXRuJLtpjPw5O9GvPys7p+a1aJH1WCTly9zf
B6j+rMF+UGCPDT30sxitVlohik83j6pKLgEAP/gi8nJbILlTP7ce+gJeHR2tfDvm
K91X6QgCF2STUFBU7/9H1/pPRRykOxQpAd8xqSgqGEyp9Ie4tysZjwoUEnG8IVJ5
ykrIFximvnb4B+LABV9WEo08n8m1R8wEryrISi8fBPn3Pr5nuayOfFLa15CLTkZF
40FN8ika1qNZy8bWRDwTZJQUUb7VCheRWcMwdZdNmhl3J+VZLpQ+ruW7b2ajwacH
z5NwBHKNcmxXb/4vHq/BnlcayHnSqT6036+OZQ+owDegcYmWV6LaM7xLErjHz2EE
38M2YSiW5SU1zluDe+iHb6l3Gd3Fj/X1gkMWFgYh0XPMSUSyimLNYzy4THKzmWlc
QNFoLLiIDbLrMt+vk+vBkIsNTTPXRJOFPBhmIF6uIUj+2YhzNotX/pQtqMKms3pP
lmHqdH6biwygEQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGFjCCA/6gAwIBAgIJAOq4qnKYsnlbMA0GCSqGSIb3DQEBCwUAMIGXMQswCQYD
VQQGEwJVUzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEjAQBgNVBAcMCUNoYXJs
b3R0ZTETMBEGA1UECgwKTmV0Rm91bmRyeTEpMCcGA1UECwwgTmV0Rm91bmRyeSBD
ZXJ0aWZpY2F0ZSBBdXRob3JpdHkxGzAZBgNVBAMMEk5ldEZvdW5kcnkgUm9vdCBD
QTAeFw0xODA1MTgxNjU0NDdaFw0zODA1MTMxNjU0NDdaMIGXMQswCQYDVQQGEwJV
UzEXMBUGA1UECAwOTm9ydGggQ2Fyb2xpbmExEjAQBgNVBAcMCUNoYXJsb3R0ZTET
MBEGA1UECgwKTmV0Rm91bmRyeTEpMCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZp
Y2F0ZSBBdXRob3JpdHkxGzAZBgNVBAMMEk5ldEZvdW5kcnkgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqr9dr7vj0KvdYkZgMMgS00ohGd
MVzpN6JFKb87uEbb7U2uH905uNkm4OOyilY/w0UD8PCU3ISmFgoB5LAcOa77icV3
qJ+h4MTaVirps2EtyA34DcyU3k+Fvauc7fZhhnAdcZai/6O13aBorR9awfwJDGfq
fa/uq88xN5gU/yniOpNnDZu9lbD4sxhMULLnjUMWpAtBqwyDXSZdEBVc7s5r3L4P
Wt7XMurjBLq7wi0mgrN+a9mQy8P3QL+1Di7SIlovsYHZIT0w48N4ZW+mehIAiRJW
a9ENQCPbH0+66TKTOgvU4BbNzkKD7kvFGYt6NUTs+89wdpO01qDa1EPDa9sx4Uo3
w29AfVBhxo9Zl7hWX1voMC5NoflE33X0f8X5kfKNnJtU/ALXiV+Wd4grI3LwkRX9
gwhsQANae6J53MN6GTY6mf7T71mvZf/F+MKjyJYcVs1pWdBfvB1Fo6jvzeUSi6dy
p7joUMEsGnIC4ModCkhPcwryuQL/XIE3Gwfji8zB5Rm4rS7s8L0oe2alB+5+VgZ/
EDfiRjpZTePPJAd62gtiOjFOo0QSO4anDLajWPtFoIEQUhtiWeVuGkRYVNpB/3rP
YZDqbVTfLsnIN+UdK70WjUk2QbepeyMPB1r/pbs1ElGB0WuGc7CubsXov1fg/Uef
NT9UkvrovA+aIdWNAgMBAAGjYzBhMB0GA1UdDgQWBBRB8+kUbjrl49psAMznlIp3
1nor6TAfBgNVHSMEGDAWgBRB8+kUbjrl49psAMznlIp31nor6TAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAJ9TvhopZ
E5rGe57y1lvk8WQRucNFxGspdGGem0DS+uAHjJ2djFf2EQFkPyjo8HLet7C+XmZ5
I4ETrPJvgkPzSUd3MdJUUjBvOw054SMKyW/88D/dj4nsMK3qA80wN5i4Syx2p5KI
yhjPaCe96fh+VIwqBMAl3f9KlWuffkfNeJT0ySYVWk8QydNy06PD4mdwKQbn4P03
Xh3G2Bn+1R9sf4ZR+HIZXFF0+tfkpCoDJqkx8Roxddrwo2zs8b6wOJJYj5KD2NQa
3GCR8p1U20Vb5AxfLLt5PeJc8D7WvaEUCGB8hoa0ElSHE8cyEq2NsCABf7r7YPcY
BtoPX6VxAeEqvLeHKBDc5fv0UlbrPI9LS2JX8t+i8epf/fH2rk9NUE7AQ5Lb8sJz
hHYztL4cg9/h5In6cfi4Ih0njZYKuiv+ZGy4vuB4KSyLvQYe4LKc/jKpAWzO79S1
sESw3yWhOcuk4ifFoYHVjxQ+bXhvYujX/ceSgxWHT9RocnROadnWvuZ8aF9HIBlh
F29PWwH4RvhvkDbMRmqxNz0jAU6GcjhmKU+hnmzbF6OqtXDcZK+i8SF2mutCa/G6
H5VDbfL/7/jP1vbFbIO7bNW94HjCGo97auSDEmsSXgZ8uTYNb503qPzdmXiU+hRl
jDOQLqtoJofIWUChpNurPQfqu9UljEk9LpQ=
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEvzCCAqegAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDUxODE3MDMwN1oX
DTI4MDUxNTE3MDMwN1owezELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMRQwEgYDVQQDDAtjdHJsX2NsaWVudDCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGXmeb1l8EjH6EbDgY8UU66MFB4
2THjA6DwHRaU8TVaGUqM0HUwA6o1oOuDDaq0WGExVTW80UnQC9PoGKO630JXAIh1
ouLIGLCTRwzuHeDdFqh94w/U8bpt8UmdCwM4B9SAbZNWJtzQlR1bH/7NdujyQHhr
064hESpQeXERt5JD9kEnTaDTBmRBS2Va/piq+O7os8Xdxwm//5CgFJ4mY+Jx2M97
brH5vo9gWI2lWHo3MrZS2CryNnYv3KETwpsz7FnSab67sch1yg0Y1ewpjXpytNNv
PjDN9FmP2DPcV2pecZur13y5urhv73sOYrFIZeO0c2ZwpPYQ5H11jpEYv0sCAwEA
AaM8MDowCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwIAYDVR0RBBkwF4IJbG9jYWxo
b3N0hwR/AAABhwQAAAAAMA0GCSqGSIb3DQEBCwUAA4ICAQCOuukYQ6cgstNbbYVe
xJsKRRdLqIdkHthH4dCtOaB5MxIH+n8CpAWwZcepbf6/RsPnsE54kVw1rJwgbWxY
Leyl5iVyuSzQ2NtiJOJAqHiKWLi965bs8e7rHEK/k6AnRpCaqXWZKUMSgFJ1wyLn
B602AE9OHkXgXumNyjE1IVP30BKd4DYOtB33Z3J77F3UIcaDcaU6IJThVO4VmLle
Fv4Vnw8iMvMfAy0FuwAahHEshFW53HW5ZSoi4a8BcwRNy17xQpGKjAOw4naOWe+H
A1T2ayKAGMA9jFcXKn7+lfTSgYdwmhKUGMQTlsqdpHktvBjQUFyPcvMt99AI5Amy
0MNaTQ/tXr6lb3ws40YFOqjD7VKhJcoqcwHqX0xXfI7X0zYNY3DGVTkvUnWTDcle
pF3O6ZKllbL6LKFkJXJRVhqOuomsAuho+vi2A3la/PBcF48z4M0aH8j6HGftzgNe
AS0Ys5iOqJhr1qdBils6hSL84cCsNYyHG+V95jdwNepSOOBqc5+OydFoUjFQScK3
Q3bjV7T59N11yCMOJzukrJorGtUr/nRdQPcEsCQlvGj60+kx3FEygBIpeiZy5AAS
h7KoWY1Sh2l4YW0Oh4AmeLDY0ryBXDafgAuwGFh7zENLBjDitdpFSuo+d/YksOnG
CR1KiiHDLLh/aqOiR7T0/HKFqg==
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEuDCCAqCgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDUxODE3MDE1MFoX
DTI4MDUxNTE3MDE1MFowdDELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMQ0wCwYDVQQDDARjdHJsMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZeZ5vWXwSMfoRsOBjxRTrowUHjZMeMDoPAd
FpTxNVoZSozQdTADqjWg64MNqrRYYTFVNbzRSdAL0+gYo7rfQlcAiHWi4sgYsJNH
DO4d4N0WqH3jD9Txum3xSZ0LAzgH1IBtk1Ym3NCVHVsf/s126PJAeGvTriERKlB5
cRG3kkP2QSdNoNMGZEFLZVr+mKr47uizxd3HCb//kKAUniZj4nHYz3tusfm+j2BY
jaVYejcytlLYKvI2di/coRPCmzPsWdJpvruxyHXKDRjV7CmNenK0028+MM30WY/Y
M9xXal5xm6vXfLm6uG/vew5isUhl47RzZnCk9hDkfXWOkRi/SwIDAQABozwwOjAJ
BgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAXgglsb2NhbGhvc3SHBH8A
AAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggIBAGH1o/uGn8QdwnM9LyoGfaDKCLYZ
p2w+9Bhpbm1RcOgg8Srw6yJHQHrDJgVAH6YYDaueS1TTiUNOUDmHLBuX4AGPga6h
8sxVsB0faYqM82yDXO/wQdw5FmcvBNFpchK5R72L7NSRshdq/VUL+FsWdQw58uLn
s6QDfMme3fU0WRQ5vAkSEm057BKeBXviRT9xNcnYMTlqZaQE2pEdzPG8xicBJNvL
V5zP4HOi63/it2Tv1M2YXVcriBXJkx023CHKZ4tm2enqVofZLyjvzqLTeNuJc0jV
zwjBpp4hG/dNc4p9kl1fQOcd7N6EfBggQy3f89J2W5X4AXBgCjxcubj3pg2q2qV6
83d45xD4K1HJFRlgpxg26Z/e09GL4yUdST77elv+SKaYNSOuyzOu+IoBv8GHEnH4
QqbW8/56taQ9bUSGw+r8yvv7uw69bPig1OnAc9hZ4cc4yOVnzW7L98OeKae/XR5I
Ewv1kvrv7ISsYKjSOIGtcQLZ2/xoHsf3xJrZ14viG2OCoRj93P18xod2yi2Vgr2Y
uBdknNjcV3SVv4dsbU8bMSHTThlQM5qrPZHlnbQjncWwXesLCUqeL0O7Tzz6lGT6
rpD1hgO9R3c9Vmqa5g+Anqbv50YTt+57M5NcLtHTAdfWIOa92LZF8FOpqTE405A/
xLJv+nDUo0iraA3w
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEuzCCAqOgAwIBAgICEBEwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDgxMzE5NDQ0MFoX
DTI4MDgxMDE5NDQ0MFowdzELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMRAwDgYDVQQDDAdkb3R6ZWV0MIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtMRtlGukei2+nYGjLbgvSn2JNZO7HGm2
Tbh4/VfZ9GJquE4iyGoRenNgyEP1LdoBkYlFGrerxO9cOR4o2iPNg1IhnOp96SxP
IE5ogg7pfU9waewlOhULjBwdPDa/cmBIwV80eDX6X7jAsSWtCxnaB7QwTJNRzM0I
9456QKEpzrOHv9kEUzeaulD/T5eFGRAUEES1h1JdVp+Ay6ZzbE4n208VdNEDV1bm
61VQ5RSbzCRTvoIE2xBwt8YtBlulz4aof/Wm0dR5zzcQqXeNrU4W3jwpzycSEyop
5nBoYrWmjg6r8oZ7PVPTMD+AsBFfzCecrTGCOSdHNWf+p5F1KLvZiwIDAQABozww
OjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAXgglsb2NhbGhvc3SH
BH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggIBAKAGn986vhFHlxQ1bG0+hQdu
vrSG7E45Y81AuospC7ABm74Zny/zg3choeVTcoZoKmZhKAPizX5LMbkML6x/kjQt
jbZFeez6Q1LAn5IdiDlLUXY46/HubClF/gfAaJcQP9h2CQfqhqsl5eVqfbcz3fSB
MMZYuAs6DcaJcS2u6rIXmH89F69bs83o0kdiMTFyC35WXzh1d0lqbi0p29Bc5Btd
eEgbHEqyP2oYASCI7hSeMhIu0kRhUsGDvBER6Sag6k/L7t6lrQ/r1EP2ss0ycheV
ShdCaX3oikH9LFk9hSBTGf0HGxwku54GFRU3ch+EeRHd6iJj6cVKuPV7cpmNFSF+
n34byUC01fbjLhlszJIGIt01R+9CercU8MyoD7+TmzY90yu1PFZ7B9jDqkkUqSyQ
HPE/diq0Is/RoI0q5HL6bqk+/HwQQ9XcvCsC2kbijtGVMMhBRl7m2GMyiFtWlhVu
trNWaOj9tXlbBIkERgDWRteCkMgU6zjbKvqepQC5WAkvu0LnhpbhTXqjaNGZqBio
1j/BKp+6ozb1Jamv4C0/rmEMBRH5Z+X9vqDmkAwLJb7wZySrFBQchvKnPnwHUQv0
JniImSdIKbiAakU4v6wMCft6tDvDmWKuluN3JmAVz00y9/vbVwMFdrRQQjlePDBR
XG4LbZTH8lXKIrLKDCEW
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEwjCCAqqgAwIBAgICEBIwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDgxMzE5NDUwMVoX
DTI4MDgxMDE5NDUwMVowfjELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMRcwFQYDVQQDDA5kb3R6ZWV0LXNlcnZlcjCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALTEbZRrpHotvp2Boy24L0p9
iTWTuxxptk24eP1X2fRiarhOIshqEXpzYMhD9S3aAZGJRRq3q8TvXDkeKNojzYNS
IZzqfeksTyBOaIIO6X1PcGnsJToVC4wcHTw2v3JgSMFfNHg1+l+4wLElrQsZ2ge0
MEyTUczNCPeOekChKc6zh7/ZBFM3mrpQ/0+XhRkQFBBEtYdSXVafgMumc2xOJ9tP
FXTRA1dW5utVUOUUm8wkU76CBNsQcLfGLQZbpc+GqH/1ptHUec83EKl3ja1OFt48
Kc8nEhMqKeZwaGK1po4Oq/KGez1T0zA/gLARX8wnnK0xgjknRzVn/qeRdSi72YsC
AwEAAaM8MDowCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwIAYDVR0RBBkwF4IJbG9j
YWxob3N0hwR/AAABhwQAAAAAMA0GCSqGSIb3DQEBCwUAA4ICAQCD/bQOYXMhywqT
UsWgLvNsff5R3xT2ddn/ECN0KJSGZKYnPrCimWAVIEz97gVgPQneLyYz2aAEbARR
IFvnKt4saXb/jbTNThV43IU4kTlpU7B4VY0w6RjmMCwpCwokIeRLNIU2SmswApoW
isVBIeZ4LTdByKy3a74rPk8Nbt8pOa27adWoLrO9bxjReZ/RqeWwSVVOgVyiVkTe
UadIADo1HJoFouyvjJsNG8E9B2V5KZ9FgdbJzGKKlMkHd1DoauNHTcORwcLz8G2X
rf9yEiFYQKlgYaHpR7YtblVR9DiWtT4Nd25JeCiTZ+1/npj2stt+ZnjaZcFkLh43
1Lw+HlD4eR4MLPEvlcBvmpTJcTOCY0ZL1GLK+Ip1dL90cseIBmON07zIqCNKLqUF
baovuK1MTG/Hs/y5N1KOBFcc18O4F2DpSW0hD7AEFjEArs4mUwGrlqroCNjGo4K/
CAXVltyFQqxWcPBzNYG/HKgYe6uatAjPirQrV5p/8CXOzXP7nEFF5t1tppilseib
Amb8ClAby+//S6bDsvcaRNTtYVUJpv04ScjiT9/s1nBleGx5RMsz062JUbL70S9j
bjBYvseajIZzaMIUL0zc3wNFOxUVCTaDMxt5GxO24YI8JBrRjPbpzLesJ5iMmjZq
3hgAjgNwSNau0M0jKoqYSoJgBGnZaA==
-----END CERTIFICATE-----
@@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
MIIGBjCCA+6gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZcxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTESMBAGA1UEBwwJQ2hhcmxvdHRlMRMw
EQYDVQQKDApOZXRGb3VuZHJ5MSkwJwYDVQQLDCBOZXRGb3VuZHJ5IENlcnRpZmlj
YXRlIEF1dGhvcml0eTEbMBkGA1UEAwwSTmV0Rm91bmRyeSBSb290IENBMB4XDTE4
MDUxODE2NTcyM1oXDTI4MDUxNTE2NTcyM1owgYsxCzAJBgNVBAYTAlVTMRcwFQYD
VQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEpMCcGA1UE
CwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNVBAMMGk5l
dEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAsb1EPhMUweS9WpjT7L54xAOmZqugJ6fhSrFfLUwNUy172q+ASvZT
pT1zKIPcZpGmPB3TX2bHaAR67BbRkUR11JgWE3U8+FsGrYmPZtaKM6fg8Mh0WZ41
oMYQNJyQixOktrgqfybyJoT5PeT5AA7QQmd8mku2X9nkAu6gWPf2nHNc7SeQdijm
yQQaVK3oqyaxOzWzsU/XbfMEz/ObkefUxgt5Z6jlK0xcW0Q+QgtawMKLUiuo6obW
RPcl7Hm9Sze8XJS5pbvS5JkUszxoRZuDVHZylrlHIpA/IL+BnvS+M7SP28UWe9sk
rv/s6ACpJtuPJ1EYf5fakugOpY7i+hq7YNi//csbc49Qjn2OtttrR7JcTaHUEU1I
/tQbQGAtNkI4pJjRVUdDawQFQlWHZD1COixNLErs2HzAI00DhLrY6SKITI/kjN0X
x010XdMcdfay0PLWm6RwxiRmMQFL4GNIC895NF1q6xV4W4rWgqUNlcvKpy+i1chW
pRbUHe16ul0qh10fcESrRvAbXn5YrQJLrwbSr+85ubN8lYdNLE0qg2cIXZlUilar
ZZzWghtCe+KkUpjfRuAi/CqfSwNK3QXEfeVEK6S49mHeSekOizFIw7fmDhCz9vXw
MOnbryRSLEJks0gIRcSDVChXheAqC98y4kcQdniNWFnqJXoqA+rrSokCAwEAAaNm
MGQwHQYDVR0OBBYEFK8UXC/sq6dGVFAqEXHsQDzqzwuUMB8GA1UdIwQYMBaAFEHz
6RRuOuXj2mwAzOeUinfWeivpMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/
BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBDAAaOE2Nbb49eOjyTNxIeOB+ZKQjJ
1hUEgRrootAA8NYbtKW/vyxTWnNi5XOGXd4DFS9OKZ0mL/7NyLc0mbTwPH2ZT8KT
PUTSCpo6yktZ/7TMjyAtWZiOMg2EH+6m/nlNSXk/v5fb8+JQLdZfpxoA017dHh3t
c8l7KOskCZNwQHgF/YMXrPXUNbsGkXRuJLtpjPw5O9GvPys7p+a1aJH1WCTly9zf
B6j+rMF+UGCPDT30sxitVlohik83j6pKLgEAP/gi8nJbILlTP7ce+gJeHR2tfDvm
K91X6QgCF2STUFBU7/9H1/pPRRykOxQpAd8xqSgqGEyp9Ie4tysZjwoUEnG8IVJ5
ykrIFximvnb4B+LABV9WEo08n8m1R8wEryrISi8fBPn3Pr5nuayOfFLa15CLTkZF
40FN8ika1qNZy8bWRDwTZJQUUb7VCheRWcMwdZdNmhl3J+VZLpQ+ruW7b2ajwacH
z5NwBHKNcmxXb/4vHq/BnlcayHnSqT6036+OZQ+owDegcYmWV6LaM7xLErjHz2EE
38M2YSiW5SU1zluDe+iHb6l3Gd3Fj/X1gkMWFgYh0XPMSUSyimLNYzy4THKzmWlc
QNFoLLiIDbLrMt+vk+vBkIsNTTPXRJOFPBhmIF6uIUj+2YhzNotX/pQtqMKms3pP
lmHqdH6biwygEQ==
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEuzCCAqOgAwIBAgICEAYwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDYxOTE5MjQ0OFoX
DTI4MDYxNjE5MjQ0OFowdzELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMRAwDgYDVQQDDAdtZ210LWd3MIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtJU/MT5Om7hLHOj+u8f+rBlzdx6xcQc3
99Fd6kLywH5m+jPc6VWrM0r6B+rrTMvxud1XvrwFQ5RZrYXvGkMqWTWX5ZVQeXOe
+UcBVhFKRx7F7V2mRLQiwgCswzWBsN00pq6Wee104GX54mj9nrovb/V4kX1q1NJr
R0rbEwIG8gceJGT5xt1bHsMpd2QXDqZG6s4oX4ib0QItxs2wNY+7YUuA996Je+6u
ihD/es2m4wkpjKb/snMEE1gResXAzGfLjBAxoCO7UlSC6wlrxnRsvTMYLWV9LuLL
c5PmzF9crMIU5LSKYIu6uNeWeuetco7QfZmyxsq74UNBlr5Z5tRRGQIDAQABozww
OjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAXgglsb2NhbGhvc3SH
BH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggIBAJTgeIOI7gCNOerPRCbTgHax
vajpPc12QIboMnbDEpo2WgLCju/P9DOTnIusARb1SuUG4FkI7x0AaSM23QSCWIw8
hHvJg97pXyHbdSBr+9oR2eQ3/kzb0hMQTnsOub3O6Zqju+ZDyzx9OCygfXuacG42
wj9GhdS1+NgXnImYqkdPl+OtWdrjNRL9VgGnrQSEVsJIRJaKG3AoQxBj7dO0Lwlz
gKkg6+o2iZETT/iEjdszYsHUjnUB5H/T553ruwRH27VV/4TppSGpy6GoOdr54oa3
H8RDQfEzb2No6zGZ1voKmPp1csUqI0P8CH5T6sPGpmcokLCH53AjDsAAK8K2msa5
ZQLoh0PunE4LbHdTVmcrHLl009uEoYzH4QCsSo0yA1qImtZah6Pu/NEAFZ2g8BTe
T2kZNm2laYL0kZXQs3uUf+T87uAFEBQ++dnCPVF6v0vAJy6eN4U8m87fncYK9XNq
/QCcqJCOZnEBJyBPaJEx7UuFqxPyXbU/rqk+VOlsaazhvinmgGQOQWFwej6ASf/Q
UTevZTrqVBCV+4YkMSGDCqczhYamsg5UpDqYzD1uHrmYDuHJ+MFGM3TuOMxtxtdr
E6KhXsMrfc0Gwg48x6l2KomhIWK/p0KZ49xlbUWXHurKZcBsZnxOelG4Q0LN4Pea
QXRit0jZCBdZCEReq1hu
-----END CERTIFICATE-----
+1
View File
@@ -0,0 +1 @@
1000
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDAzCCAesCAQAwczELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMQwwCgYDVQQDDAMwMDEwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDbqcpLyr/yZFw2YxOy1RKhO/jw4ra8Z15KdEHNbz5h
cNlT6dmVFQj4sBW9AsB06ynqzrO5cKjnWF/LZXzTRGlDUmxmKZOZCcb4NVy/4fwW
NN8IBuJQBrrz1qTMBJaG4DRbZ+zTICrb2EJGJyNFZbNKvlRQzn4xMjMm57Upv2en
Imy8BHUWahjkcCG/uXPTJzjupTweRI34DPxTU2q3hH7zwocdFMe/mkzShMb1qwj3
3BByHWzSK60NawE140li24v3ELLhre/C6cZSV1Nyk4U9jreSeaQ/uj5cmk08dPnC
eBcTqMIwhAOihMYM3vqhOZbNfssr7gXd0/8bv/7P6JGpAgMBAAGgSzBJBgkqhkiG
9w0BCQ4xPDA6MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMCAGA1UdEQQZMBeCCWxv
Y2FsaG9zdIcEfwAAAYcEAAAAADANBgkqhkiG9w0BAQsFAAOCAQEAqNqHubzNgIzw
qCMV/Njoprd9Ia6JK8N1O9RnvhYJCIc1SETJCaNF4O83AmVCZ7HgDIOYeHcYbzyh
IVhfTlMdL1mkCq5h9f5TGokEBe+abMD2G9ff5l3MKMDi8SDMM7e+OyUignuCybo4
rlKINukDR8Qn/UM4clBDYWp6VfWcnQNot1+1IJrMLigTmsn6Ggn+irNmFaLg5EFM
O6S6mObGR0CzEkPD/VJ+xKHho2n+yCrC4TWUWUQuvgd6Ms5hx9AAEBGEMyr7KJK7
KJuWeJ++mThBksKU5pwTRF9k7CfDShoc2auTTMNyz5KcN35rQ0AiG+4S07Ee6elv
sCSWVD8saw==
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDCjCCAfICAQAwejELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMRMwEQYDVQQDDAowMDEtc2VydmVyMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA26nKS8q/8mRcNmMTstUSoTv48OK2vGde
SnRBzW8+YXDZU+nZlRUI+LAVvQLAdOsp6s6zuXCo51hfy2V800RpQ1JsZimTmQnG
+DVcv+H8FjTfCAbiUAa689akzASWhuA0W2fs0yAq29hCRicjRWWzSr5UUM5+MTIz
Jue1Kb9npyJsvAR1FmoY5HAhv7lz0yc47qU8HkSN+Az8U1Nqt4R+88KHHRTHv5pM
0oTG9asI99wQch1s0iutDWsBNeNJYtuL9xCy4a3vwunGUldTcpOFPY63knmkP7o+
XJpNPHT5wngXE6jCMIQDooTGDN76oTmWzX7LK+4F3dP/G7/+z+iRqQIDAQABoEsw
SQYJKoZIhvcNAQkOMTwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREE
GTAXgglsb2NhbGhvc3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggEBACsD
3UPwGHbffGyakLcmlI/kYR4la5w6fUBaGVFmhnvQ1GtWBYm1xZEBBEsRMcwcnV4H
f+uWLhPbrN5n3Af+o5e+5BIcxvgz820cV67c08/1uwMCaWUOdY5mYFU1UFkSXydQ
/97gL9rQkK8zuqT6dEWcWYJ0AER+ZBPeSlbNP2HNdfXHKniaZjU8Tswvyxmb/2gG
gb4Tmq7JA7gqvz1A5cXAMHBLfZlaDvA9TuXaqv7gHdPDlIuqioGy43ARp57wFNB1
Z2lNdDjkwQr9baagX9jDlnoTPzo5ACQtOfORIYPm8GTHe62Vm7etQeergoTvjxe2
495uzvPv7ssDuup8JxE=
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDAzCCAesCAQAwczELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMQwwCgYDVQQDDAMwMDIwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDGfv6XkzO866Bi+rVZNGHtOjxXZE0HW5rwaxVQ3zMt
k/8Elrlyr6ZuguObyUfLJdnScvirjaGbUqm6XTL7H9yRUKBshQZDtaci6XRm4gq2
1X7pQ0f1oUmsW5q+dDqbJ8JJ+HbEhbPH7MmZfsTLIASWMKK/pEzTf9t98fyN4LJO
/Yr++PmE7ZEz+YRwGphSupWEatLlfRh7XviihxKcXTiH8Z/qD8A6W8lngu2dHFU7
VXiBITPwvx/GeanahwduqpnZJNWaCAA7h0MCCatJ01ITGGPgsBCfJWyezBw8CAyL
WYL/dlauaq1q5Xg/Bp8V6HddEy20QypRxdY/kPobAcVPAgMBAAGgSzBJBgkqhkiG
9w0BCQ4xPDA6MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMCAGA1UdEQQZMBeCCWxv
Y2FsaG9zdIcEfwAAAYcEAAAAADANBgkqhkiG9w0BAQsFAAOCAQEAOeXMaHBB2Wlz
IWW8vd0BxWKtsE8rrd7mL32rivU+mv/ARruGusF8n3kINzE6w3nLCvk0QRAelhK2
Jp7Ml5ugmS2y/O/y8+vr9iJc2vcd04i1gCzWcjFXZTVNVVIB79Pvycvi6rVWyz5k
X6HlvRrRyf3Sq/52heaHkh8xEWkyDvphUHnorClMaezwCRxfvXgln2riBPdSjNG8
+Viz5L2N3Rkiim0AkiAb5NMhYzNFtDajdi36qFT3T8ZpwkbDn1eYb0TkoaPr/ITX
EFdsXCsG82QexxLYrA0IC9rygxkHGKErQaLvCQ8X6EgNz3Rsw1F7w0kkiCMfQ36c
k8nxba2a8w==
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDCjCCAfICAQAwejELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMRMwEQYDVQQDDAowMDItc2VydmVyMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxn7+l5MzvOugYvq1WTRh7To8V2RNB1ua
8GsVUN8zLZP/BJa5cq+mboLjm8lHyyXZ0nL4q42hm1Kpul0y+x/ckVCgbIUGQ7Wn
Iul0ZuIKttV+6UNH9aFJrFuavnQ6myfCSfh2xIWzx+zJmX7EyyAEljCiv6RM03/b
ffH8jeCyTv2K/vj5hO2RM/mEcBqYUrqVhGrS5X0Ye174oocSnF04h/Gf6g/AOlvJ
Z4LtnRxVO1V4gSEz8L8fxnmp2ocHbqqZ2STVmggAO4dDAgmrSdNSExhj4LAQnyVs
nswcPAgMi1mC/3ZWrmqtauV4PwafFeh3XRMttEMqUcXWP5D6GwHFTwIDAQABoEsw
SQYJKoZIhvcNAQkOMTwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREE
GTAXgglsb2NhbGhvc3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggEBAGVv
VIaN7FH3nBxexB9iz9blTw9uvz2PH9g525ebcbw4E7tMWpuJzux+DkIpxKPHsAjz
UkUH/6j9W/C5JjNnGMFwFrdCQIga8FhrOgG+EGuRGIWDk2Mlh1nolOaKSdIguH6a
Ejjc8EFc/VICOn670dJ/LT6PT1CTYFRge9s9F0GxMa07QcBy0aw2RedXOQFSMxd5
ynCqxn8hhlkRU8SPqz/KH2jZmxFDYjDltMsVz55duVOslyF3RNA8oca0M9y5tgpl
4w3YgF+sjGCQ4fGTcNl6hq9XWF0w9lX2lDrObSR5UWxvHlqskRmkh6tF4N5eZNpG
MCyQNrB3uJw28KPvyLk=
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDAzCCAesCAQAwczELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMQwwCgYDVQQDDAMwMDMwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCcg8MIdQyQgc/av7DR8Ulx8ysYWGrT8wIoV515O2Vn
5V3PGNJqosNzOyVmPRhRxN1Bmutj39CA53Jnjb1BgH4e5WEdqWoW2pfuf2fkkx0z
goxRbiOFT1+SmcUKQo9m6PfsAGuLUuW2cvPLNk6SPHuGbcvPkP8jUqjsP9utewVG
TiJto18SbOANg7PuVA76feeYPOitaDaX8kAGTsaNMEz/lriPCwzqrwpCfw+I5QW9
1u/T52o/O4REKCno1/I3FJpk3LbEAYiGUtXOVlv7/HBj4BfCCpdcIA9P7TYkv1dt
adylT4G1lnXz+OoOUFvvIoFBaNB/x4zDVvIpncpuz/CJAgMBAAGgSzBJBgkqhkiG
9w0BCQ4xPDA6MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMCAGA1UdEQQZMBeCCWxv
Y2FsaG9zdIcEfwAAAYcEAAAAADANBgkqhkiG9w0BAQsFAAOCAQEAKJoDBGGyGUSN
uQbM0r+TGWV92wIH6AcgU5FsRoMhpNAScha+lhn1+2qUJlgpUAywye0tUzE3PXlX
bjNE/1FNLoIN3X5BfXeDdxtkyQMFYAzctDVSixRHOLU2GMQaYtiecSD/cb3iLgvk
P7QwbXtNNim3eoqYjvyMvP/wVivfjvtgAIquxbtzSuMDrOSwkS57CyZZ+Yvasjx3
y2PgWCSYaB/pV99s768b9nf3aNQqmYUaEkwYQFBjWQOWpjS9YYFLwRZLGa2tjFSQ
l0hATmzhxfWj/skLQad41HgG/foTXXBI1ZayDqFVYLMI0cy4hOFnCVwHBzVcAjaT
EsxBl5IlJQ==
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDCjCCAfICAQAwejELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMRMwEQYDVQQDDAowMDMtc2VydmVyMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIPDCHUMkIHP2r+w0fFJcfMrGFhq0/MC
KFedeTtlZ+VdzxjSaqLDczslZj0YUcTdQZrrY9/QgOdyZ429QYB+HuVhHalqFtqX
7n9n5JMdM4KMUW4jhU9fkpnFCkKPZuj37ABri1LltnLzyzZOkjx7hm3Lz5D/I1Ko
7D/brXsFRk4ibaNfEmzgDYOz7lQO+n3nmDzorWg2l/JABk7GjTBM/5a4jwsM6q8K
Qn8PiOUFvdbv0+dqPzuERCgp6NfyNxSaZNy2xAGIhlLVzlZb+/xwY+AXwgqXXCAP
T+02JL9XbWncpU+BtZZ18/jqDlBb7yKBQWjQf8eMw1byKZ3Kbs/wiQIDAQABoEsw
SQYJKoZIhvcNAQkOMTwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREE
GTAXgglsb2NhbGhvc3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggEBAIhu
MLS2WGErqzN+15zKdYOoeXZaAGIPk3UZW7G7Fe/yNw5ukNF0nCQBsu97WBYQkXV5
JY7Ur0Tx/4aplvcUAKbGIQ5FBTzlqQYkuQVgqtDbvWy7NzT7GoLdVsxZm5fFkHqq
ny1l60xRHWnpSyUsAsKvYWPbTMqvpnm2/6JHWs+p3vpaDRvyN66bOi268ozvrVa0
SlzrkGfm4TIeuxSB5uKIEGDvbAoMK8UEINtuQR+nMa7eOMeDc2cfJXe608dkBZzi
z+cmijYe15OMBuocPNhpJI3Ij5/20HY4VV4QyWwnLp2aATq2QrdijIeIgfRLfe6u
ztzBGpuDSxP5zxgyWbo=
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDAzCCAesCAQAwczELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMQwwCgYDVQQDDAMwMDQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCqBDdOcSnf0fCctQwn+yyjygU3qqx74bQEpNh0YEst
PxbqOrGzkUUV6vIqMAfyQDcEAaXbxh35/W4ms73waN/+swYDkQVbUn31qB7npsm/
wqTz6NVUJ0r20KFJMVoGtfqO991gl1r/xFT6gc7+v+hGj+6iAuMED7v3xFA7qNRD
8N+iNzq2ArdUX4OfMN0d2y1Zq5sBqicA3vMV91+KDvKfmQRfGFkvyW6Y1S30WDEW
HVCgvmnnw9l8+A8kdEe5/YQbBe5bwdaLYCtv8h6Jz16Ok7jBbrrasYbpcsD8wOI5
CnyuV7wKcSFO/vcEO/P3YYqL65puKGKVMteyige4/rpvAgMBAAGgSzBJBgkqhkiG
9w0BCQ4xPDA6MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMCAGA1UdEQQZMBeCCWxv
Y2FsaG9zdIcEfwAAAYcEAAAAADANBgkqhkiG9w0BAQsFAAOCAQEAMTokHy9zmf6A
q48lxaGS+RC0NZlpzwf/NybVkCiAg16MjX1ipLa2hUw0m7CiqGuqZBi5guQISn2c
KPGYp/cEAk3YQh2y5jb8mOvc6t6TW827dJUMkjuEYExhoEZJRPFOSoI/tCMjCjiM
l3hXt4HlErNvMTA3uDACZwiNTQ6uGzv0VCH8+dpTtUkhinYJc1oUy8YAKQr6xvgR
dEt7iRn6FuH0Z9cY35MAR1aFzNIoGcS+UTn/r2BefZNwiyTDTlnXquPL8aVgRkcp
2MkfjH3wia/M/2rtl/vn5dkEZ2jnPqQCMilsV8DUgGe9E6d8Dk7Jeu4H2vo1XTO/
OIUhslqO4A==
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDCjCCAfICAQAwejELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMRMwEQYDVQQDDAowMDQtc2VydmVyMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqgQ3TnEp39HwnLUMJ/sso8oFN6qse+G0
BKTYdGBLLT8W6jqxs5FFFeryKjAH8kA3BAGl28Yd+f1uJrO98Gjf/rMGA5EFW1J9
9age56bJv8Kk8+jVVCdK9tChSTFaBrX6jvfdYJda/8RU+oHO/r/oRo/uogLjBA+7
98RQO6jUQ/Dfojc6tgK3VF+DnzDdHdstWaubAaonAN7zFfdfig7yn5kEXxhZL8lu
mNUt9FgxFh1QoL5p58PZfPgPJHRHuf2EGwXuW8HWi2Arb/Ieic9ejpO4wW662rGG
6XLA/MDiOQp8rle8CnEhTv73BDvz92GKi+uabihilTLXsooHuP66bwIDAQABoEsw
SQYJKoZIhvcNAQkOMTwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREE
GTAXgglsb2NhbGhvc3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggEBACr4
joLa7wXzoT+w4SNDZG3SW8++iKEe+niP3rG1q47lWgSIn4Y3xfVUgLu/RlHAfPCY
PSMJx6INsalcqj5I4AWr7t8CVgcXMKbfz98IJJqmQ+DFuE9eJHortDOgELGKzkpc
i9cg6uUUjLXJ0+OLS2tIVHy1lS8UMwI8gV/7J4x05bofDrTWyZgNTH75z7G6QWkx
wC3BnfXHLagCgJFgNI/LPE1AFfhbScx98M+r3kiRJwdJF40NMJzL68ng4XyFScCA
V9W5sOTf0Htu9PqOYQxpKjkHGB9WPpIyQ6NX4kcp7OtRqGg1SFQJZLyHy6TkfEID
EcU1OYQEAwzIanV2a8s=
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDCzCCAfMCAQAwezELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMRQwEgYDVQQDDAtjdHJsX2NsaWVudDCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALGXmeb1l8EjH6EbDgY8UU66MFB42THj
A6DwHRaU8TVaGUqM0HUwA6o1oOuDDaq0WGExVTW80UnQC9PoGKO630JXAIh1ouLI
GLCTRwzuHeDdFqh94w/U8bpt8UmdCwM4B9SAbZNWJtzQlR1bH/7NdujyQHhr064h
ESpQeXERt5JD9kEnTaDTBmRBS2Va/piq+O7os8Xdxwm//5CgFJ4mY+Jx2M97brH5
vo9gWI2lWHo3MrZS2CryNnYv3KETwpsz7FnSab67sch1yg0Y1ewpjXpytNNvPjDN
9FmP2DPcV2pecZur13y5urhv73sOYrFIZeO0c2ZwpPYQ5H11jpEYv0sCAwEAAaBL
MEkGCSqGSIb3DQEJDjE8MDowCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwIAYDVR0R
BBkwF4IJbG9jYWxob3N0hwR/AAABhwQAAAAAMA0GCSqGSIb3DQEBCwUAA4IBAQBb
+MaqsY2rcoHg107Qc+WyxoKE+jCzZIyPTxTYBx7g6NtRQFhPVoZYKdfMRpKRe9Wa
eMsGKbLTBW9WsUsYNNJhQmDDfgWkWP1mmL5Q8ZkC22CPnsarx/8XjERe1fSbK8/g
BvowJgoacymwr2XkfT8a8q/ZVP4htXpAmbJ4tMKxR/YbsbwttOkKlW9hPrGOyU5Q
S6y5eN3PWM+B6VGNLWZC+yowQWyQOtXCWWjbuwHD5sPO6PItZ423E+8h4hyc7BOk
kIJbkJF9uHBsBHpm4qWnRaiB0CDhvn71Nm/I5zul+cpEi9Ean6X1nmp+A96dnqU/
DjNmWl4GQUmK+0vFi47w
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDBDCCAewCAQAwdDELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMQ0wCwYDVQQDDARjdHJsMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAsZeZ5vWXwSMfoRsOBjxRTrowUHjZMeMDoPAdFpTx
NVoZSozQdTADqjWg64MNqrRYYTFVNbzRSdAL0+gYo7rfQlcAiHWi4sgYsJNHDO4d
4N0WqH3jD9Txum3xSZ0LAzgH1IBtk1Ym3NCVHVsf/s126PJAeGvTriERKlB5cRG3
kkP2QSdNoNMGZEFLZVr+mKr47uizxd3HCb//kKAUniZj4nHYz3tusfm+j2BYjaVY
ejcytlLYKvI2di/coRPCmzPsWdJpvruxyHXKDRjV7CmNenK0028+MM30WY/YM9xX
al5xm6vXfLm6uG/vew5isUhl47RzZnCk9hDkfXWOkRi/SwIDAQABoEswSQYJKoZI
hvcNAQkOMTwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAXggls
b2NhbGhvc3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggEBAK2KV6DHNg3X
6/5BVvwrw/xShn6JsegGzmryhI5KulPUtZxT1ug8zlOHlX8tocv3A6rnD9U/qb/H
X3zF9InHoMqMNZzM5HAYW6soDNGVdDzMAdUgOHzxhKpIjWP1+nrHzhLuSYZT9cAl
eGgo/75rjF8KP4F7X5ApjQ3lTG+prov+qQBGQKWMwtlcpFvSngI+nt+P5HB1MZhK
uG2h183j5fGOShHabePH/fU6i2XvoQ7MyQSSD2kmarYcyPvVlV1q3+aV+LUYGFhN
FBhcnx3zwJmgGUBRKO2wEGmiwl2NGrsgWFP7OpvGj75Mrf8olrC68nCQMbiXJlAF
iF9lnavVuAU=
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDBzCCAe8CAQAwdzELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMRAwDgYDVQQDDAdkb3R6ZWV0MIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtMRtlGukei2+nYGjLbgvSn2JNZO7HGm2Tbh4
/VfZ9GJquE4iyGoRenNgyEP1LdoBkYlFGrerxO9cOR4o2iPNg1IhnOp96SxPIE5o
gg7pfU9waewlOhULjBwdPDa/cmBIwV80eDX6X7jAsSWtCxnaB7QwTJNRzM0I9456
QKEpzrOHv9kEUzeaulD/T5eFGRAUEES1h1JdVp+Ay6ZzbE4n208VdNEDV1bm61VQ
5RSbzCRTvoIE2xBwt8YtBlulz4aof/Wm0dR5zzcQqXeNrU4W3jwpzycSEyop5nBo
YrWmjg6r8oZ7PVPTMD+AsBFfzCecrTGCOSdHNWf+p5F1KLvZiwIDAQABoEswSQYJ
KoZIhvcNAQkOMTwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAX
gglsb2NhbGhvc3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggEBAItwmLYp
5IVaWIKVm7KiNziSAUlETSm3pFN+GSo0k029XIn2jxMJ1nflr5rSTIa1PuB7xHI9
T8rMSWreJsfRZoNyi+VPom+ZMCy8g7VWBYW6FS6wqg8U+bErLIvd4MyV/Ke6MiRA
9/sqJMHuN+2MC3oWR38O8cgclWur7najVbIX+3P+Ucq93qtrWaVarVzfO1STtNmL
zocAH5BbaOTQojdDzTTMlyrRM2ZVqAfDR9tGprEWxxW1tF4xFk3oh3sEWDhGiH3x
vwXgSS2MoY7U6j+gXqXTODvhTZPD75BCepIItCOOLxUbCVlIzyPGf9YqtTpNIAG0
xnYiEbwg6noBKR4=
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDDjCCAfYCAQAwfjELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMRcwFQYDVQQDDA5kb3R6ZWV0LXNlcnZlcjCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALTEbZRrpHotvp2Boy24L0p9iTWT
uxxptk24eP1X2fRiarhOIshqEXpzYMhD9S3aAZGJRRq3q8TvXDkeKNojzYNSIZzq
feksTyBOaIIO6X1PcGnsJToVC4wcHTw2v3JgSMFfNHg1+l+4wLElrQsZ2ge0MEyT
UczNCPeOekChKc6zh7/ZBFM3mrpQ/0+XhRkQFBBEtYdSXVafgMumc2xOJ9tPFXTR
A1dW5utVUOUUm8wkU76CBNsQcLfGLQZbpc+GqH/1ptHUec83EKl3ja1OFt48Kc8n
EhMqKeZwaGK1po4Oq/KGez1T0zA/gLARX8wnnK0xgjknRzVn/qeRdSi72YsCAwEA
AaBLMEkGCSqGSIb3DQEJDjE8MDowCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwIAYD
VR0RBBkwF4IJbG9jYWxob3N0hwR/AAABhwQAAAAAMA0GCSqGSIb3DQEBCwUAA4IB
AQCwsVa3ljYFd8KchIahQacOvnj1MpxqiZS2NdlkA+JfMlfmArPkuBYphb1p2vWY
sfnvoyQ7VtqGsb3zesQKlq4eN6RdmwPA1FjntnurqaNucfTpX9HwiNlmDefHFlTI
0L8MF25EJ7IDcuApThG5qGb1NVYxtaR8SZOfGahkr3xXKI1uHosyVPIET/pDQ85f
bCmBcPt2327S2VW596czfdmO9Aw8AuI9L1BLcKlY/m3xAq2U8g3QG3aB54PvApFl
86BMXfqvpcJL+LGGbNE4eYM3TlH+4JRQlAgYJ//Tep0/QynkJro9te121DYYLvmy
yEj1Cz/atugjFmC1wCcWC5qY
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIFMDCCAxgCAQAwgZ8xCzAJBgNVBAYTAlVTMRcwFQYDVQQIDA5Ob3J0aCBDYXJv
bGluYTESMBAGA1UEBwwJQ2hhcmxvdHRlMRMwEQYDVQQKDApOZXRGb3VuZHJ5MSkw
JwYDVQQLDCBOZXRGb3VuZHJ5IENlcnRpZmljYXRlIEF1dGhvcml0eTEjMCEGA1UE
AwwaTmV0Rm91bmRyeSBJbnRlcm1lZGlhdGUgQ0EwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQCxvUQ+ExTB5L1amNPsvnjEA6Zmq6Anp+FKsV8tTA1TLXva
r4BK9lOlPXMog9xmkaY8HdNfZsdoBHrsFtGRRHXUmBYTdTz4WwatiY9m1oozp+Dw
yHRZnjWgxhA0nJCLE6S2uCp/JvImhPk95PkADtBCZ3yaS7Zf2eQC7qBY9/acc1zt
J5B2KObJBBpUreirJrE7NbOxT9dt8wTP85uR59TGC3lnqOUrTFxbRD5CC1rAwotS
K6jqhtZE9yXseb1LN7xclLmlu9LkmRSzPGhFm4NUdnKWuUcikD8gv4Ge9L4ztI/b
xRZ72ySu/+zoAKkm248nURh/l9qS6A6ljuL6Grtg2L/9yxtzj1COfY6222tHslxN
odQRTUj+1BtAYC02QjikmNFVR0NrBAVCVYdkPUI6LE0sSuzYfMAjTQOEutjpIohM
j+SM3RfHTXRd0xx19rLQ8tabpHDGJGYxAUvgY0gLz3k0XWrrFXhbitaCpQ2Vy8qn
L6LVyFalFtQd7Xq6XSqHXR9wRKtG8BteflitAkuvBtKv7zm5s3yVh00sTSqDZwhd
mVSKVqtlnNaCG0J74qRSmN9G4CL8Kp9LA0rdBcR95UQrpLj2Yd5J6Q6LMUjDt+YO
ELP29fAw6duvJFIsQmSzSAhFxINUKFeF4CoL3zLiRxB2eI1YWeoleioD6utKiQID
AQABoEswSQYJKoZIhvcNAQkOMTwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAg
BgNVHREEGTAXgglsb2NhbGhvc3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQAD
ggIBAADmvwgKE//IQfLADi9rlDUBHS+poNT3na2WKaMwRswdSp0FOqWjoIKZmfg8
zSMXnszQ7Q4kj+QpzzaHQVBj9tQFLN44NntWMfmU9Eh7gNFZG+0ShfSnO5WnC28u
5c2yxls1G1CwHtivRHdRr7kut7w9IbnPe68Wm0/P1LiOQqQJvKFCVIlmFQCkDJ+A
EWppvfEd51YeHMl69UuUAiPZFPAw+HBxbyQiTtB8hzJqkwyqJEhY7+UbFryyk4KM
E5nUqyMJI3BnPZ943nR9fx8yH7R28n0m7lGQGdTLhxxVB2aZG1ko/AgteODQhSUy
cPrN7jWoE7sSOWOZIGgeGVrkm6TxCytjZZGGjnA2uSLGxHgpuUIqP45YDUIu9vvx
mZXVILbANzZ4kk8evoCyVrXBpX+ODj4HK/ApSb91ZJOXAvczKTP+g7bSbnXWaqkn
kFc//MzVuVC455eym+4X6MulNYAe/SIhQitzLc0l1rHx2QvvDRAnMkeGVmGEVA2D
sJVmPS1pnNy9X/S1O1kgRxVeJ59ymnMidYDOgNcW3pPN9ScJs3lTBN9eYJyTQNic
OClNyP3UOr0bDi3ACIWoPjBzL2JrC94pH5xxfwfm2FMjqOLYudnqQqkYYvWs6I3O
TIyUkMfuZWSlkGrYhMABX9yHaJCJLEFwSP6ELQEfGa0L/ww6
-----END CERTIFICATE REQUEST-----
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIDBzCCAe8CAQAwdzELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENhcm9s
aW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkxFDAS
BgNVBAsMC1ppdGkgRmFicmljMRAwDgYDVQQDDAdtZ210LWd3MIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtJU/MT5Om7hLHOj+u8f+rBlzdx6xcQc399Fd
6kLywH5m+jPc6VWrM0r6B+rrTMvxud1XvrwFQ5RZrYXvGkMqWTWX5ZVQeXOe+UcB
VhFKRx7F7V2mRLQiwgCswzWBsN00pq6Wee104GX54mj9nrovb/V4kX1q1NJrR0rb
EwIG8gceJGT5xt1bHsMpd2QXDqZG6s4oX4ib0QItxs2wNY+7YUuA996Je+6uihD/
es2m4wkpjKb/snMEE1gResXAzGfLjBAxoCO7UlSC6wlrxnRsvTMYLWV9LuLLc5Pm
zF9crMIU5LSKYIu6uNeWeuetco7QfZmyxsq74UNBlr5Z5tRRGQIDAQABoEswSQYJ
KoZIhvcNAQkOMTwwOjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAX
gglsb2NhbGhvc3SHBH8AAAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggEBAGP3liiH
ezmKbo4Jy02TBJVhHtJrv2X8QYQ//oYUs8aJOuhMTGP6yD7eUJ9eUR+JEvZQA44O
llSNrCqgt55LctIpGv60SZfpylaQ7gvKBk+zWJXao7+ZYvmrEebQlUY7opgU2SDm
bmsXAEhQth70+dV02XseHeKw4SVFrjisDOrOgjz/JJtYHFyDnf5ec5m+cLSZH24V
uE1Lny8sdb3+6pgxEwTJJCI+zzQDCBRPX9LvQhJBISAEeIn4rAj8yXwF6q7jHGle
ePoVQt9tb9Zy1hIcHAYOofA4PioNIxK2/UhMRsy1XdDV4bidlzAY8Gxctz4E8nGK
xzB604/xUwPyLwE=
-----END CERTIFICATE REQUEST-----
+19
View File
@@ -0,0 +1,19 @@
V 280515170150Z 1000 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=ctrl
V 280515170307Z 1001 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=ctrl_client
V 280515170427Z 1002 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r1
V 280515170459Z 1003 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r1_client
V 280515170610Z 1004 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r2
V 280515170623Z 1005 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r2_client
V 280616192448Z 1006 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=mgmt-gw
V 280806180805Z 1007 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r3
V 280806180939Z 1008 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r3_client
V 280810192725Z 1009 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=001-server
V 280810192736Z 100A unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=002-server
V 280810192749Z 100B unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=003-server
V 280810192804Z 100C unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=004-server
V 280810192840Z 100D unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=001
V 280810192903Z 100E unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=002
V 280810192913Z 100F unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=003
V 280810192924Z 1010 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=004
V 280810194440Z 1011 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=dotzeet
V 280810194501Z 1012 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=dotzeet-server
@@ -0,0 +1 @@
unique_subject = yes
@@ -0,0 +1 @@
unique_subject = yes
+18
View File
@@ -0,0 +1,18 @@
V 280515170150Z 1000 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=ctrl
V 280515170307Z 1001 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=ctrl_client
V 280515170427Z 1002 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r1
V 280515170459Z 1003 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r1_client
V 280515170610Z 1004 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r2
V 280515170623Z 1005 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r2_client
V 280616192448Z 1006 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=mgmt-gw
V 280806180805Z 1007 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r3
V 280806180939Z 1008 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=r3_client
V 280810192725Z 1009 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=001-server
V 280810192736Z 100A unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=002-server
V 280810192749Z 100B unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=003-server
V 280810192804Z 100C unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=004-server
V 280810192840Z 100D unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=001
V 280810192903Z 100E unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=002
V 280810192913Z 100F unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=003
V 280810192924Z 1010 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=004
V 280810194440Z 1011 unknown /C=US/ST=North Carolina/L=Charlotte/O=NetFoundry/OU=Ziti Fabric/CN=dotzeet
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEuDCCAqCgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDUxODE3MDE1MFoX
DTI4MDUxNTE3MDE1MFowdDELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMQ0wCwYDVQQDDARjdHJsMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsZeZ5vWXwSMfoRsOBjxRTrowUHjZMeMDoPAd
FpTxNVoZSozQdTADqjWg64MNqrRYYTFVNbzRSdAL0+gYo7rfQlcAiHWi4sgYsJNH
DO4d4N0WqH3jD9Txum3xSZ0LAzgH1IBtk1Ym3NCVHVsf/s126PJAeGvTriERKlB5
cRG3kkP2QSdNoNMGZEFLZVr+mKr47uizxd3HCb//kKAUniZj4nHYz3tusfm+j2BY
jaVYejcytlLYKvI2di/coRPCmzPsWdJpvruxyHXKDRjV7CmNenK0028+MM30WY/Y
M9xXal5xm6vXfLm6uG/vew5isUhl47RzZnCk9hDkfXWOkRi/SwIDAQABozwwOjAJ
BgNVHRMEAjAAMAsGA1UdDwQEAwIF4DAgBgNVHREEGTAXgglsb2NhbGhvc3SHBH8A
AAGHBAAAAAAwDQYJKoZIhvcNAQELBQADggIBAGH1o/uGn8QdwnM9LyoGfaDKCLYZ
p2w+9Bhpbm1RcOgg8Srw6yJHQHrDJgVAH6YYDaueS1TTiUNOUDmHLBuX4AGPga6h
8sxVsB0faYqM82yDXO/wQdw5FmcvBNFpchK5R72L7NSRshdq/VUL+FsWdQw58uLn
s6QDfMme3fU0WRQ5vAkSEm057BKeBXviRT9xNcnYMTlqZaQE2pEdzPG8xicBJNvL
V5zP4HOi63/it2Tv1M2YXVcriBXJkx023CHKZ4tm2enqVofZLyjvzqLTeNuJc0jV
zwjBpp4hG/dNc4p9kl1fQOcd7N6EfBggQy3f89J2W5X4AXBgCjxcubj3pg2q2qV6
83d45xD4K1HJFRlgpxg26Z/e09GL4yUdST77elv+SKaYNSOuyzOu+IoBv8GHEnH4
QqbW8/56taQ9bUSGw+r8yvv7uw69bPig1OnAc9hZ4cc4yOVnzW7L98OeKae/XR5I
Ewv1kvrv7ISsYKjSOIGtcQLZ2/xoHsf3xJrZ14viG2OCoRj93P18xod2yi2Vgr2Y
uBdknNjcV3SVv4dsbU8bMSHTThlQM5qrPZHlnbQjncWwXesLCUqeL0O7Tzz6lGT6
rpD1hgO9R3c9Vmqa5g+Anqbv50YTt+57M5NcLtHTAdfWIOa92LZF8FOpqTE405A/
xLJv+nDUo0iraA3w
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEvzCCAqegAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDUxODE3MDMwN1oX
DTI4MDUxNTE3MDMwN1owezELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMRQwEgYDVQQDDAtjdHJsX2NsaWVudDCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALGXmeb1l8EjH6EbDgY8UU66MFB4
2THjA6DwHRaU8TVaGUqM0HUwA6o1oOuDDaq0WGExVTW80UnQC9PoGKO630JXAIh1
ouLIGLCTRwzuHeDdFqh94w/U8bpt8UmdCwM4B9SAbZNWJtzQlR1bH/7NdujyQHhr
064hESpQeXERt5JD9kEnTaDTBmRBS2Va/piq+O7os8Xdxwm//5CgFJ4mY+Jx2M97
brH5vo9gWI2lWHo3MrZS2CryNnYv3KETwpsz7FnSab67sch1yg0Y1ewpjXpytNNv
PjDN9FmP2DPcV2pecZur13y5urhv73sOYrFIZeO0c2ZwpPYQ5H11jpEYv0sCAwEA
AaM8MDowCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwIAYDVR0RBBkwF4IJbG9jYWxo
b3N0hwR/AAABhwQAAAAAMA0GCSqGSIb3DQEBCwUAA4ICAQCOuukYQ6cgstNbbYVe
xJsKRRdLqIdkHthH4dCtOaB5MxIH+n8CpAWwZcepbf6/RsPnsE54kVw1rJwgbWxY
Leyl5iVyuSzQ2NtiJOJAqHiKWLi965bs8e7rHEK/k6AnRpCaqXWZKUMSgFJ1wyLn
B602AE9OHkXgXumNyjE1IVP30BKd4DYOtB33Z3J77F3UIcaDcaU6IJThVO4VmLle
Fv4Vnw8iMvMfAy0FuwAahHEshFW53HW5ZSoi4a8BcwRNy17xQpGKjAOw4naOWe+H
A1T2ayKAGMA9jFcXKn7+lfTSgYdwmhKUGMQTlsqdpHktvBjQUFyPcvMt99AI5Amy
0MNaTQ/tXr6lb3ws40YFOqjD7VKhJcoqcwHqX0xXfI7X0zYNY3DGVTkvUnWTDcle
pF3O6ZKllbL6LKFkJXJRVhqOuomsAuho+vi2A3la/PBcF48z4M0aH8j6HGftzgNe
AS0Ys5iOqJhr1qdBils6hSL84cCsNYyHG+V95jdwNepSOOBqc5+OydFoUjFQScK3
Q3bjV7T59N11yCMOJzukrJorGtUr/nRdQPcEsCQlvGj60+kx3FEygBIpeiZy5AAS
h7KoWY1Sh2l4YW0Oh4AmeLDY0ryBXDafgAuwGFh7zENLBjDitdpFSuo+d/YksOnG
CR1KiiHDLLh/aqOiR7T0/HKFqg==
-----END CERTIFICATE-----
@@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIEtjCCAp6gAwIBAgICEAIwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAlVT
MRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTETMBEGA1UECgwKTmV0Rm91bmRyeTEp
MCcGA1UECwwgTmV0Rm91bmRyeSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxIzAhBgNV
BAMMGk5ldEZvdW5kcnkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDUxODE3MDQyN1oX
DTI4MDUxNTE3MDQyN1owcjELMAkGA1UEBhMCVVMxFzAVBgNVBAgMDk5vcnRoIENh
cm9saW5hMRIwEAYDVQQHDAlDaGFybG90dGUxEzARBgNVBAoMCk5ldEZvdW5kcnkx
FDASBgNVBAsMC1ppdGkgRmFicmljMQswCQYDVQQDDAJyMTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAMqCBmQ1w36rpLpVXO+qhcbYvGWh7TiVYC2eD1XB
jSuW1BTeTRV129Obhus4IW81YIGC6dZchH828eQbp8VrgCWPIVbdHvXEFcbppE/V
7BSfG3DFMRICWTyTMHb3bDGhvQl+fiCf4Z3YHGa/JE3emBiTFJK5ohBu1BIIy7gb
25rPaiWcrrrWyCq793UQOFv7pQ5C602xHS4G1EAfn/pwXv/jGQoAn7nQY2pzNHOa
/psMp/TRuqSKoeh8phkYxRzz9GrhMl3DnhiYC8M62mmWUWTxHyimMtXsOiJptM2m
S9Jg8FNb2esdRqJBO7Ms+X/P0wgs90SCYKntXxjttd0PmxsCAwEAAaM8MDowCQYD
VR0TBAIwADALBgNVHQ8EBAMCBeAwIAYDVR0RBBkwF4IJbG9jYWxob3N0hwR/AAAB
hwQAAAAAMA0GCSqGSIb3DQEBCwUAA4ICAQB9KgzI2wIcX/+u1w1T7teTIoyvnDLD
x2X2+ajFUrKsHIQJYz2Gu3ds5VBfWST9vOIDQzv7LWi3uIl/15WkvPUSgdhCzy7W
n0YH7rybxF3cvMrytctg4z4/xflIjZq/7IYsncqNZKSIbZNP46RNzfJfIPHA58rO
Tb/Sq8k57jPOGePOlPXuyUKwaGuKkmGdvz566PY7A2O7y5JmuJx2o9xkSJM9LVRT
X9Ff5ii9PjBALNOtraI7FtNAzMZ/oPiAw4B3MxyoH6WXQyfnd9mLLU3Rrkhl5rA+
/inQ/HZpv93evu7E4nniwq1bCZTNF4kgIGENdPewWdr+yhcCfm2qYsUC4gStZHCW
FTeV9e7CdfOA5Oj5EsCRWMYUnlNEXwzugBPB9tX7iYFIHL85PWCyuGKx9rtlQ/EP
7DRIoZibdKZGYAq1XtjSHeYzG3OXxq40o4D0f+HkQnsGl5wVF2IIM5ll8tuxKrRd
WiWbEmLFNEJ2wvv63oCaZCKRJxXxpnHif9E+ufOSMTySSyqHMPuSjRuIVwmundD+
IwGHndcHWSb1hzKPvDUd+OBvM9PZ2A6IOL7XZaRaV0x5O58Fpkn+QU8CXnbbOxRE
D2tysrka4vnHmLDgdM0IKJd05gp8QWpW36Kg0JYckMj0fWWJ1EFPBOJch4HWIYd9
7aTY4K+wdj1KOg==
-----END CERTIFICATE-----

Some files were not shown because too many files have changed in this diff Show More