Docs and controller API version

This commit is contained in:
Adam Ierymenko 2016-07-08 13:10:02 -07:00
parent 5bf3c54cd3
commit 6d8de214eb
4 changed files with 230 additions and 145 deletions

View File

@ -1,32 +1,233 @@
Network Controller Implementation
Network Controller Microservice
======
This folder contains code implementing the node/NetworkController.hpp interface to allow ZeroTier nodes to create and manage virtual networks.
ZeroTier's 16-digit network IDs are really just a concatenation of the 10-digit ZeroTier address of a network controller followed by a 6-digit (24-bit) network number on that controller. Fans of software defined networking will recognize this as a variation of the familiar [separation of data plane and control plane](http://sdntutorials.com/difference-between-control-plane-and-data-plane/) SDN design pattern.
This code implements the *node/NetworkController.hpp* interface and provides a SQLite3-backed network controller microservice. Including it in the build allows ZeroTier One to act as a controller and create/manage networks.
This is the same code we use to run [my.zerotier.com](https://my.zerotier.com/), which is a web UI and API that runs in front of a pool of controllers.
### Building
By default this code is not built or included in the client. To build on Linux, BSD, or Mac add ZT_\ENABLE_\NETWORK_\CONTROLLER=1 to the make command line. You'll need the development headers for Sqlite3 installed. They ship as part of OSX and Xcode. On Linux or BSD you'll probably need to install a package.
On Linux, Mac, or BSD you can create a controller-enabled build with:
make ZT_ENABLE_NETWORK_CONTROLLER=1
You will need the development headers and libraries for SQLite3 installed.
### Running
When started, a controller-enabled build of ZeroTier One will automatically create and initialize a *controller.db* in its home folder. This is where all the controller's data and persistent state lives.
After building and installing (`make install`) a controller-enabled build of ZeroTier One, start it and try:
Since Sqlite3 supports multiple processes attached to the same database, it is safe to back up a running database with the command line *sqlite3* utility:
sudo zerotier-cli /controller
sqlite3 /path/to/controller.db .dump
You should see something like:
In production ZeroTier runs this frequently and keeps many timestamped copies going back about a week. These are also backed up (encrypted) to Amazon S3 along with the rest of our data.
{
"controller": true,
"apiVersion": 2,
"clock": 1468002975497,
"instanceId": "8ab354604debe1da27ee627c9ef94a48"
}
### Administrating
When started, a controller-enabled build of ZeroTier One will automatically create and initialize a `controller.db` file in its home folder. This is where all the controller's data and persistent state lives. If you're upgrading an old controller it will upgrade its database schema automatically on first launch. Make a backup of the old controller's database first since you can't go backward.
See service/README.md for documentation on the JSON API presented by this network controller implementation. Also see *nodejs-zt1-client* for a NodeJS JavaScript interface.
Controllers periodically make backups of their database as `controller.db.backup`. This is done so that this file can be more easily copied/rsync'ed to other systems without worrying about corruption. SQLite3 supports multiple processes accessing the same database file, so `sqlite3 /path/to/controller.db .dump` also works but can be slow on a busy controller.
### Reliability
Controllers can in theory host up to 2^24 networks and serve many millions of devices (or more), but we recommend running multiple controllers for a lot of networks to spread load and be more fault tolerant.
Network controllers can go offline without affecting already-configured members of running networks. You just won't be able to change anything and new members will not be able to join.
### Dockerizing Controllers
High-availability can be implemented through fail-over. A simple method involves making a frequent backup of the SQLite database (use the SQLite command line client to do this safely) and the network configuration master's working directory. Then, if the master goes down, another instance of it can rapidly be provisioned elsewhere. Since ZeroTier addresses are mobile, the new instance will quickly (usually no more than 30s) take over for the old one and service requests.
ZeroTier network controllers can easily be run in Docker or other container systems. Since containers do not need to actually join networks, extra privilege options like "--device=/dev/net/tun --privileged" are not needed. You'll just need to map the local JSON API port of the running controller and allow it to access the Internet (over UDP/9993 at a minimum) so things can reach and query it.
### Limits
### Implementing High Availability Fail-Over
A single network configuration master can administrate up to 2^24 (~16m) networks as per the ZeroTier protocol limit. There is no hard limit on the number of clients, though millions or more would impose significant CPU demands on a server. Optimizations could be implemented such as memoization/caching to reduce this.
ZeroTier network controllers are not single points of failure for networks-- in the sense that if a controller goes down *existing* members of a network can continue to communicate. But new members (or those that have been offline for a while) can't join, existing members can't be de-authorized, and other changes to the network's configuration can't be made. This means that short "glitches" in controller availability are not a major issue.
Because controllers are just regular ZeroTier nodes and controller queries are in-band, controllers can trivially be moved without worrying about changes to underlying physical IPs. This makes high-availability fail-over very easy to implement.
Just set up two cloud hosts, preferably in different data centers (e.g. two different AWS regions or Digital Ocean SF and NYC). Now create a *cron* or other job that frequently mirrors *controller.db.backup* from the active controller to the hot spare.
If the active controller goes down, rename `controller.db.backup` to `controller.db` on the hot spare and start the ZeroTier One service there. The spare will take over and has now become the active controller. If the original active node comes back, it should take on the role of spare and should not start its service. Instead it should start mirroring the active controller's backup and wait until it is needed.
The details of actually implementing this kind of HA fail-over on Linux or other OSes are beyond the scope of these docs and there are many ways to do it. Docker orchestration tools like Kubernetes can also be used to accomplish this if you've dockerized your controller.
### Network Controller API
The controller API is hosted via the same JSON API endpoint that ZeroTier One uses for local control (usually at 127.0.0.1 port 9993). All controller options are rounded under the */controller* base path.
The controller microservice does not implement any fine-grained access control (authentication is via authtoken.secret just like the regular JSON API) or other complex mangement features. It just takes network and network member configurations and reponds to controller queries. We have an enterprise product called [ZeroTier Central](https://my.zerotier.com/) that we host as a service (and that companies can license to self-host) that does this.
All working network IDs on a controller must begin with the controller's ZeroTier address. The API will *allow* "foreign" networks to be added but the controller will have no way of doing anything with them.
Also note that the API is *very* sensitive about types. Integers must be integers and strings strings, etc. Incorrectly typed and unrecognized fields are just ignored.
#### `/controller`
* Purpose: Check for controller function and return controller status
* Methods: GET
* Returns: { object }
| Field | Type | Description | Writable |
| ------------------ | ----------- | ------------------------------------------------- | -------- |
| controller | boolean | Always 'true' | no |
| apiVersion | integer | Controller API version, currently 2 | no |
| clock | integer | Current clock on controller, ms since epoch | no |
| instanceId | string | A random ID generated on first controller DB init | no |
The instance ID can be used to check whether a controller's database has been reset or otherwise switched.
#### `/controller/network`
* Purpose: List all networks hosted by this controller
* Methods: GET
* Returns: [ string, ... ]
This returns an array of 16-digit hexadecimal network IDs.
#### `/controller/network/<network ID>`
* Purpose: Create, configure, and delete hosted networks
* Methods: GET, POST, DELETE
* Returns: { object }
By making queries to this path you can create, configure, and delete networks. DELETE is final, so don't do it unless you really mean it.
When POSTing new networks take care that their IDs are not in use, otherwise you may overwrite an existing one. To create a new network with a random unused ID, POST to `/controller/network/##########______`. The #'s are the controller's 10-digit ZeroTier address and they're followed by six underscores. Check the `nwid` field of the returned JSON object for your network's newly allocated ID. Subsequent POSTs to this network must refer to its actual path.
| Field | Type | Description | Writable |
| --------------------- | ------------- | ------------------------------------------------- | -------- |
| nwid | string | 16-digit network ID | no |
| controllerInstanceId | string | Controller database instance ID | no |
| clock | integer | Current clock, ms since epoch | no |
| name | string | A short name for this network | YES |
| private | boolean | Is access control enabled? | YES |
| enableBroadcast | boolean | Ethernet ff:ff:ff:ff:ff:ff allowed? | YES |
| allowPassiveBridging | boolean | Allow any member to bridge (very experimental) | YES |
| v4AssignMode | string | If 'zt', auto-assign IPv4 from pool(s) | YES |
| v6AssignMode | string | IPv6 address auto-assign modes; see below | YES |
| multicastLimit | integer | Maximum recipients for a multicast packet | YES |
| creationTime | integer | Time network was first created | no |
| revision | integer | Network config revision counter | no |
| memberRevisionCounter | integer | Network member revision counter | no |
| authorizedMemberCount | integer | Number of authorized members (for private nets) | no |
| relays | array[object] | Alternative relays; see below | YES |
| routes | array[object] | Managed IPv4 and IPv6 routes; see below | YES |
| ipAssignmentPools | array[object] | IP auto-assign ranges; see below | YES |
| rules | array[object] | Traffic rules; see below | YES |
(The `ipLocalRoutes` field appeared in older versions but is no longer present. Routes will now show up in `routes`.)
Two important things to know about networks:
- Networks without rules won't carry any traffic. See below for an example with rules to permit IPv4 and IPv6.
- Managed IP address assignments and IP assignment pools that do not fall within a route configured in `routes` are ignored and won't be used or sent to members.
- The default for `private` is `true` and this is probably what you want. Turning `private` off means *anyone* can join your network with only its 16-digit network ID. It's also impossible to de-authorize a member as these networks don't issue or enforce certificates. Such "party line" networks are used for decentralized app backplanes, gaming, and testing but are not common in ordinary use.
**IPv6 Auto-Assign Modes:**
This field is (for legacy reasons) a comma-delimited list of strings. These can be `rfc4193`, `6plane`, and `zt`. RFC4193 and 6PLANE are special addressing modes that deterministically assign IPv6 addresses based on the network ID and the ZeroTier address of each member. The `zt` mode enables IPv6 auto-assignment from arbitrary IPv6 IP ranges configured in `ipAssignmentPools`.
**Relay object format:**
Relay objects define network-specific preferred relay nodes. Traffic to peers on this network will preferentially use these relays if they are available, and otherwise will fall back to the global rootserver infrastructure.
| Field | Type | Description | Writable |
| --------------------- | ------------- | ------------------------------------------------- | -------- |
| address | string | 10-digit ZeroTier address of relay | YES |
| phyAddress | string | Optional IP/port suggestion for finding relay | YES |
**IP assignment pool object format:**
| Field | Type | Description | Writable |
| --------------------- | ------------- | ------------------------------------------------- | -------- |
| ipRangeStart | string | Starting IP address in range | YES |
| ipRangeEnd | string | Ending IP address in range (inclusive) | YES |
Pools are only used if auto-assignment is on for the given address type (IPv4 or IPv6) and if the entire range falls within a managed route.
IPv6 ranges work just like IPv4 ranges and look like this:
{
"ipRangeStart": "fd00:feed:feed:beef:0000:0000:0000:0000",
"ipRangeEnd": "fd00:feed:feed:beef:ffff:ffff:ffff:ffff"
}
(You can POST a shortened-form IPv6 address but the API will always report back un-shortened canonical form addresses.)
That defines a range within network `fd00:feed:feed:beef::/64` that contains up to 2^64 addresses. If an IPv6 range is large enough, the controller will assign addresses by placing each member's device ID into the address in a manner similar to the RFC4193 and 6PLANE modes. Otherwise it will assign addresses at random.
**Rule object format:**
Rules are matched in order of ruleNo. If no rules match, the default action is `drop`. To allow all traffic, create a single rule with all *null* fields and an action of `accept`.
In the future there will be many, many more types of rules. As of today only filtering by Ethernet packet type is supported.
| Field | Type | Description | Writable |
| --------------------- | ------------- | ------------------------------------------------- | -------- |
| ruleNo | integer | Rule sorting key | YES |
| etherType | integer | Ethernet frame type (e.g. 34525 for IPv6) | YES |
| action | string | Currently either `allow` or `drop` | YES |
**An Example: The Configuration for Earth**
Here is an example of a correctly configured ZeroTier network with IPv4 auto-assigned addresses from 28.0.0.0/7 (a "de-facto private" space) and RFC4193 IPv6 addressing. Users might recognize this as *Earth*, our public "global LAN party" that's used for demos and testing and occasionally gaming.
For your own networks you'll probably want to change `private` to `true` unless you like company. These rules on the other hand probably are what you want. These allow IPv4, IPv4 ARP, and IPv6 Ethernet frames. To allow only IPv4 omit the one for Ethernet type 34525 (IPv6).
{
"nwid": "8056c2e21c000001",
"controllerInstanceId": "8ab354604debe1da27ee627c9ef94a48",
"clock": 1468004857100,
"name": "earth.zerotier.net",
"private": false,
"enableBroadcast": false,
"allowPassiveBridging": false,
"v4AssignMode": "zt",
"v6AssignMode": "rfc4193",
"multicastLimit": 64,
"creationTime": 1442292573165,
"revision": 234,
"memberRevisionCounter": 3326,
"authorizedMemberCount": 2873,
"relays": [],
"routes": [
{"target":"28.0.0.0/7","via":null,"flags":0,"metric":0}],
"ipAssignmentPools": [
{"ipRangeStart":"28.0.0.1","ipRangeEnd":"29.255.255.254"}],
"rules": [
{
"ruleNo": 20,
"etherType": 2048,
"action": "accept"
},{
"ruleNo": 21,
"etherType": 2054,
"action": "accept"
},{
"ruleNo": 30,
"etherType": 34525,
"action": "accept"
}]
}
#### `/controller/network/<network ID>/member`
#### `/controller/network/<network ID>/member/<address>`
* Purpose: Create, authorize, or remove a network member
* Methods: GET, POST, DELETE
* Returns: { object }
| Field | Type | Description | Writable |
| --------------------- | ------------- | ------------------------------------------------- | -------- |
| nwid | string | 16-digit network ID | no |
| clock | integer | Current clock, ms since epoch | no |
| address | string | Member's 10-digit ZeroTier address | no |
| authorized | boolean | Is member authorized? (for private networks) | YES |
| activeBridge | boolean | Member is able to bridge to other Ethernet nets | YES |
| identity | string | Member's public ZeroTier identity (if known) | no |
| ipAssignments | array[string] | Managed IP address assignments | YES |
| memberRevision | integer | Member revision counter | no |
Note that managed IP assignments are only used if they fall within a managed route. Otherwise they are ignored.

View File

@ -70,7 +70,7 @@
#define ZT_NETCONF_SQLITE_SCHEMA_VERSION_STR "4"
// API version reported via JSON control plane
#define ZT_NETCONF_CONTROLLER_API_VERSION 1
#define ZT_NETCONF_CONTROLLER_API_VERSION 2
// Number of requests to remember in member history
#define ZT_NETCONF_DB_MEMBER_HISTORY_LENGTH 8

14
node/README.md Normal file
View File

@ -0,0 +1,14 @@
ZeroTier Virtual Switch Core
======
This directory contains the *real* ZeroTier: a completely OS-independent global virtual Ethernet switch engine. This is where the magic happens.
Give it wire packets and it gives you Ethernet packets, and vice versa. The core contains absolutely no actual I/O, port configuration, or other OS-specific code (except Utils::getSecureRandom()). It provides a simple C API via [/include/ZeroTierOne.h](../include/ZeroTierOne.h). It's designed to be small and maximally portable for future use on small embedded and special purpose systems.
Code in here follows these guidelines:
- Keep it minimal, especially in terms of code footprint and memory use.
- There should be no OS-dependent code here unless absolutely necessary (e.g. getSecureRandom).
- If it's not part of the core virtual Ethernet switch it does not belong here.
- No C++11 or C++14 since older and embedded compilers don't support it yet and this should be maximally portable.
- Minimize the use of complex C++ features since at some point we might end up "minus-minus'ing" this code if doing so proves necessary to port to tiny embedded systems.

View File

@ -120,133 +120,3 @@ Path objects describe direct physical paths to peer. If no path objects are list
<tr><td>fixed</td><td>boolean</td><td>If true, this is a statically-defined "fixed" path</td><td>no</td></tr>
<tr><td>preferred</td><td>boolean</td><td>If true, this is the current preferred path</td><td>no</td></tr>
</table>
### Network Controller API
If ZeroTier One was built with *ZT\_ENABLE\_NETWORK\_CONTROLLER* defined, the following API paths are available. Otherwise these paths will return 404.
#### /controller
* Purpose: Check for controller function and return controller status
* Methods: GET
* Returns: { object }
<table>
<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr>
<tr><td>controller</td><td>boolean</td><td>Always 'true' if controller is running</td><td>no</td></tr>
<tr><td>apiVersion</td><td>integer</td><td>JSON API version, currently 1</td><td>no</td></tr>
<tr><td>clock</td><td>integer</td><td>Controller system clock in ms since epoch</td><td>no</td></tr>
</table>
#### /controller/network
* Purpose: List all networks hosted by this controller
* Methods: GET
* Returns: [ string, ... ]
This returns an array of 16-digit hexadecimal network IDs. Unlike /network under the top-level API, it does not dump full network information for all networks as this may be quite large for a large controller.
#### /controller/network/\<network ID\>
* Purpose: Create, configure, and delete hosted networks
* Methods: GET, POST, DELETE
* Returns: { object }
By making queries to this path you can create, configure, and delete networks. DELETE is final, so don't do it unless you really mean it.
It's important to understand how network IDs work. The first ten digits (most significant 40 bits) of a network ID are the ZeroTier address of the controller. This is how clients find it. The last six digits (least significant 24 bits) are arbitrary and serve to identify the network uniquely on the controller.
Thus a network's first ten digits *must* be the controller's address. If your controller is *deadbeef01*, then the networks it controls must have IDs like *deadbeef01feed02* or *deadbeef01beef03*. This API however *does not* enforce this requirement. It will allow you to add arbitrary network IDs, but they won't work since clients will never be able to find them. To create a new network with a random last six digits safely and atomically, you can POST to */controller/network/##########\_\_\_\_\_\_* where ########## is the controller's address and the underscores are as shown. This will pick a random unallocated network ID, which will be returned in the 'nwid' field of the returned JSON object.
<table>
<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr>
<tr><td>nwid</td><td>string</td><td>16-digit hex network ID</td><td>no</td></tr>
<tr><td>name</td><td>string</td><td>Short network name (max: 127 chars)</td><td>yes</td></tr>
<tr><td>private</td><td>boolean</td><td>False if public network, true for access control</td><td>yes</td></tr>
<tr><td>enableBroadcast</td><td>boolean</td><td>True to allow Ethernet broadcast (ff:ff:ff:ff:ff:ff)</td><td>yes</td></tr>
<tr><td>allowPassiveBridging</td><td>boolean</td><td>True to allow any member to bridge (experimental!)</td><td>yes</td></tr>
<tr><td>v4AssignMode</td><td>string</td><td>'none', 'zt', or 'dhcp' (see below)</td><td>yes</td></tr>
<tr><td>v6AssignMode</td><td>string</td><td>'none', 'zt', or 'dhcp' (see below)</td><td>yes</td></tr>
<tr><td>multicastLimit</td><td>integer</td><td>Maximum number of multicast recipients per multicast/broadcast address</td><td>yes</td></tr>
<tr><td>creationTime</td><td>integer</td><td>Time network was created in ms since epoch</td><td>no</td></tr>
<tr><td>revision</td><td>integer</td><td>Network config revision number</td><td>no</td></tr>
<tr><td>memberRevisionCounter</td><td>integer</td><td>Current value of network revision counter (incremented after every member add or revision)</td><td>no</td></tr>
<tr><td>clock</td><td>integer</td><td>Current clock in ms since epoch (for convenience)</td><td>no</td></tr>
<tr><td>authorizedMemberCount</td><td>integer</td><td>Number of authorized members</td><td>no</td></tr>
<tr><td>relays</td><td>[object]</td><td>Array of network-specific relay nodes (see below)</td><td>yes</td></tr>
<tr><td>ipLocalRoutes</td><td>[string]</td><td>Array of IP network/netmask entries corresponding to networks routed directly via this interface (e.g. 10.0.0.0/8 to route 10.0.0.0 via this interface)</td></tr>
<tr><td>ipAssignmentPools</td><td>[object]</td><td>Array of IP auto-assignment pools for 'zt' assignment mode</td><td>yes</td></tr>
<tr><td>rules</td><td>[object]</td><td>Array of network flow rules (see below)</td><td>yes</td></tr>
</table>
The network member list includes both authorized and unauthorized members. DELETE unauthorized members to remove them from the list. Relays, IP assignment pools, and rules are edited via direct POSTs to the network object. New values replace all previous values.
Networks must have rules. If there are no rules, the default action is 'deny'. As also documented in the Rule object definition below, rules currently only support etherType and allow/deny. Thus to make a functioning network, add etherType allow entries for IPV4/ARP and/or IPv6. Alternately you can add a null allow entry to allow all traffic, causing the network to behave like a normal pass-through switch.
**Relay object format:**
Relay objects define network-specific preferred relay nodes. Traffic to peers on this network will preferentially use these relays if they are available, and otherwise will fall back to the global rootserver infrastructure.
<table>
<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td></tr>
<tr><td>address</td><td>string</td><td>10-digit ZeroTier address of relay node</td></tr>
<tr><td>phyAddress</td><td>string</td><td>Fixed path address in IP/port format e.g. 192.168.1.1/9993</td></tr>
</table>
**IP assignment pool object format:**
IP assignment pools are only used if they are within a network specified in ipLocalRoutes.
<table>
<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td></tr>
<tr><td>ipRangeStart</td><td>string</td><td>Start of IP assignment range</td></tr>
<tr><td>ipRangeEnd</td><td>string</td><td>End of IP assignment range</td></tr>
</table>
**Rule object format:**
* **Note**: at the moment, <u>only rules specifying allowed Ethernet types are used</u>. The database supports a richer rule set, but this is not implemented yet in the client. <u>Other types of rules will have no effect</u> (yet).
Rules are matched in order of ruleNo. If no rules match, the default action is 'drop'. To allow all traffic, create a single rule with all *null* fields and an action of 'accept'.
Rule object fields can be *null*, in which case they are omitted from the object. A null field indicates "no match on this criteria."
IP related fields apply only to Ethernet frames of type IPv4 or IPV6. Otherwise they are ignored.
<table>
<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td></tr>
<tr><td>ruleNo</td><td>integer</td><td>User-defined rule ID and sort order</td></tr>
<tr><td>nodeId</td><td>string</td><td>10-digit hex ZeroTier address of node if this rule is local to only one member</td></tr>
<tr><td>sourcePort</td><td>string</td><td>10-digit hex ZeroTier address of source port on virtual switch (source device address)</td></tr>
<tr><td>destPort</td><td>string</td><td>10-digit hex ZeroTier address of destination port on virtual switch (destination device address)</td></tr>
<tr><td>vlanId</td><td>integer</td><td>Ethernet VLAN ID</td></tr>
<tr><td>vlanPcp</td><td>integer</td><td>Ethernet VLAN priority code point (PCP) ID</td></tr>
<tr><td>etherType</td><td>integer</td><td>Ethernet frame type</td></tr>
<tr><td>macSource</td><td>string</td><td>Ethernet source MAC address</td></tr>
<tr><td>macDest</td><td>string</td><td>Ethernet destination MAC address</td></tr>
<tr><td>ipSource</td><td>string</td><td>Source IP address</td></tr>
<tr><td>ipDest</td><td>string</td><td>Destination IP address</td></tr>
<tr><td>ipTos</td><td>integer</td><td>IP TOS field</td></tr>
<tr><td>ipProtocol</td><td>integer</td><td>IP protocol</td></tr>
<tr><td>ipSourcePort</td><td>integer</td><td>IP source port</td></tr>
<tr><td>ipDestPort</td><td>integer</td><td>IP destination port</td></tr>
<tr><td>action</td><td>string</td><td>Rule action: accept, drop, etc.</td></tr>
</table>
#### /controller/network/\<network ID\>/member/\<address\>
* Purpose: Create, authorize, or remove a network member
* Methods: GET, POST, DELETE
* Returns: { object }
<table>
<tr><td><b>Field</b></td><td><b>Type</b></td><td><b>Description</b></td><td><b>Writable</b></td></tr>
<tr><td>nwid</td><td>string</td><td>16-digit hex network ID</td><td>no</td></tr>
<tr><td>clock</td><td>integer</td><td>Current clock in ms since epoch (for convenience)</td><td>no</td></tr>
<tr><td>address</td><td>string</td><td>10-digit hex ZeroTier address</td><td>no</td></tr>
<tr><td>authorized</td><td>boolean</td><td>Is member authorized?</td><td>yes</td></tr>
<tr><td>activeBridge</td><td>boolean</td><td>This member is an active network bridge</td><td>yes</td></tr>
<tr><td>identity</td><td>string</td><td>Full ZeroTier identity of member</td><td>no</td></tr>
<tr><td>ipAssignments</td><td>[string]</td><td>Array of IP/bits IP assignments</td><td>yes</td></tr>
<tr><td>memberRevision</td><td>integer</td><td>Member revision counter value from network at time of last revision or member creation</td><td>no</td></tr>
</table>