Using nsm - rallytac/pub GitHub Wiki
Using Network State Machine (nsm)
nsm
is a Python tool we've put together to coordinate responsibility for resources in a shared network.
That's it! Probably all you need to know about nsm
. Isn't that awesome ... ??!!
.
.
.
.
.
OK, that was a dreadful attempt at humor, so let's just drop that attitude and get a little more serious...
What's It Do?
Alright, the best way to explain the need for and operation of nsm
is to explain by way of example. So, let's go with a typical example where something like EBS acts as a gateway between a MANET radio network and the organization's enterprise network. This utility can be found in our public repository.
Something like this:
Now, for purposes of, say, redundancy, we setup two EBS instances so that if one of them falls over (actually that would never happen because our software has absolutely no bugs and has NEVER EVER EVER been known to crash!), we'd want the other one to automatically take over. Also, we need to be absolutely sure that only one EBS is active at any one time because if two are active, we'd end up with a packet loop and nasty things would ensue.
This situation would create a loop and kill the system pretty quickly as both instances of EBS will be passing the SAME traffic between the MANET and the enterprise network - and therefore EACH OTHER!
We want something like the following where only one EBS is active (green) while the other is idle (orange).
Of course we could modify EBS to check in with it's "partner" and coordinate with it who is active. But say we're not using EBS. Maybe we're using Rallypoints, or Engage Activity Recorders, or someone else's software that we have no hope of changing. Even if we could change the software to support this kind of thing, we'd need to change all of those components. And for those that cannot be changed, we're S-O-L! So we need something else to take care of this very specialized task and which can be used in a generic fashion for all situations where we want to apply the "Highlander Principle" ("There can be only one").
OK, we made up that business of "Highlander Principle". It's not a thing. But it does sound cool - no?
Anyway ... let's just add a little to our requirement. Perhaps, instead of just 2 instances of EBS in our example, we want 3. Maybe 4? Maybe 137? (Yeah, we've seen that kind of thing before.) And ... let's say that instead of just EBS, we have a combination of stuff on each gateway machine - let's say an instance of EBS and an RP on each of our 137 machines. Modifying all those components for each machine and having each instance on each machine coordinating with other instances of itself on the other 136 machines; will become quite painful and messy very quickly.
Here's an idea of that - gnarly huh?
So, we figured that we'd build something outside of all this that is solely responsible for this hairy scary coordination business and leave the other goodies to perform the jobs they're specialized in.
How nsm
works is by communicating with other instances of itself on other machines on the network to determine who is responsible for a resource. Now, as resource is simply something you give a name to. The term doesn't mean anything special to nsm
, its just a name that has meaning for your setup. In our example, we'll name our resource "ebs-gateway" to represent EBS. We want nsm
to determine which ebs-gateway is to be active and, when that determination is made, take some form of action.
We'll get to the action stuff and dive more into multiple resources and other fun stuff further on. Right now we'll take a look at how nsm
operates.
How's It Work?
In our example where we have two EBS instances, we'll add nsm
to each computer housing an EBS instance. Then, we configure nsm
to communicate with other nsm
instances over the MANET - and exclusively over the MANET. We say exclusively because what we're fundamentally interested in having only one EBS at a time connected to the ... MANET!
It looks something like this.
Here we've installed nsm
on each machine, told it to use the network interface card that connects to the MANET, and gave it a shared address inside the MANET to chat on - this is typically IP multicast but you also tell nsm
to use IP broadcast.
And there we have it: the little blue boxes talk to each other over the shared blue "pipe" inside the MANET, coordinating between each other who's in charge of what.
Its really important to understand that the "pipe" inside the MANET cannot be a point-to-point connection. Rather, it has to be a point-to-multipoint connection like multicast or broadcast. The reason for this is twofold: first, you'd have to know the IP addresses of the machines running nsm
and keep those updated on each nsm
instance as things change. Second, if we have more than two instances of nsm
, it quickly becomes more and more cumbersome to manage that IP addressing business. So, simply using multicast (or broadcast if you really have to) solves the problem.
Also, and probably most important frankly, is that nsm
speaks to its peers over the MANET - not the enterprise network. That's because what we're trying to figue out is who is connected to the MANET, not who's connected to the enterprise network. Hopfully that makes sense.
States
As it's name hopefully conveys, nsm
is a state machine that operates over a network. Those states are idle
, goingActive
, and active
.
-
idle
means thatnsm
is just that - idle. It's not doing anything other than listening to the network (that shared pipe on the MANET). -
goingActive
means thatnsm
has decided it should activate soon - but it's not active yet. -
active
, of course, means that its active.
When nsm
starts up, it starts in the idle
state. It attaches to the shared pipe and listens for a little while to see if there are any declarations from other nsm
instances about their state(s). If it doesn't hear any declarations nsm
transitions to a goingActive
state and begins sending out declarations of its own indicating its intent to go active. This goes on for a little while and, assuming nsm
doesn't receive declarations from other nsm
instances about them either going active or active, it finally transitions to an active state.
Now, in the idle
state, nsm
does not transmit any traffic - it just listens. When it goes into the goingActive
state, it begins sending traffic (the declaration) asserting that it is going active. When it transitions to the active
state, it continues sending traffic but, this time, the content of the traffic is an indication that the nsm
is now active. Pretty straightforward.
These declarations - be they for
goingActive
oractive
- are typically sent every second. You might think this will use up a ton of bandwidth. And you'd be right depending on what you think a ton is. For us, a ton of traffic is anything more than 1kbps.nsm
typically uses 1-3kbps bandwidth. So, on networks that have megabits of bandwidth or even hundreds of kilobits of bandwidth,nsm
's minor traffic load is not even noise. But here at RTS we don't like using bandwidth that much, so we're always trying to get that traffic profile down as much as we can. We'll just agree that we're being paranoid and leave it at that.
Anyway ... once an nsm
instance has gone active, all the other nsm
instances go into idle
state and stop sending any traffic. So the bottom-line is that 99% of the time, only the active nsm
instance is sending traffic on the network. That remaining 1% covers the situation where the MANET is not delivering packets quickly enough (around 5 seconds delay) to the other nsm
instances which, in turn, will start their transitioning process and begin sending their own traffic. However, as soon as an instance that is goingActive
(or even active
) sees a declaration from another instance that it's either going active or is, in fact, already active with a higher election token; the receiving instance shuts up immediately and goes back to an idle state. Its kinda cool!
Election Token
We mentioned the term election token above. There's nothing particularly magical about it. All the token is is a random number that nsm
dynamically calculates for each resource its responsible for (we'll talk about multiple resources shortly). The token is used to determine who should actually go active in the event two or more instances of nsm
figure they're the one to go active. Imagine this: two nsm
instances - A and B. Imagine they start up on their respective machines at the same time, attach to the MANET at the same time, and start their timing counters at the same time. In practice there's about a 0.000001% (not an empircal number by the way) chance of that actually happening. But, imagine it does.
In that situation, A and B are going to get into a situaiton where they'll simultaneously enter goingActive
and begin transmitting their declarations. Of course, each peer will ostensibly receive the other peer's packet at around the same time. Without a token of some sort, neither end will know whether to continue going active or to back down to an idle state. So we have a token. Let's say A calculates a token of 55
and B calculates a token of 137
. When A receives B's declaration, it compares B's token of 137
to its own token of 55
. Its obvious that A at that point determines that B must win and A (being the good citizen it is), acknowledges B winning the "election" and backs down to an idle state. B on the other hand does the same thing and determines that it should continue on its path to declare its intention to go active for a little while and, eventually transition to an active state.
All the while while B stays online and active
, A will just listen for declarations and stay in its idle
state. However, if B goes away, A will reach a timeout point and transition to the goingActive
state. At that point it recalculates its token; tossing away the previous value of 55
. From then on - at least for the duration of goingActive
and (potentially) active
, the new token value is used. Let's say its 7721
.
OK, let's assume that in this scenario, B didn't actualy "go away", A just stopped receiving B' declarations - maybe because the network wasn't feeling well, tons of packets got corrupted, the MANET briefly partioned, or whatever. So now we have both A and B active!
That would be bad if nsm
was stupid software. But it's not (we think). Rather, both A and B, upon receiving the others' declaration both do the same thing as before - looking at that token to decide who wins. Well, A will see B's value of 137
and compare it to its token of 7721
. A won't do anything - it'll just stay active. B on the other hand does the same thing and, obviously, figures out right away its lost an "election" it wasn't even part of and has effectively been ousted from office so to speak. (This is sounding too political right now, isn't it?)
Anyway, the point is that nsm
works really hard to ensure that only one instance is active
at a time and in the event of two (or more) active instances, quickly resolves the dispute. Given timing issues (such as network propagation delay and other nasties), there's always a chance in those situations that there will briefly be two or more active instances of nsm
but that kind of "glaring" is rare and very short-lived - generally no longer than a second or two if it ever happens at all.
So ... What Now?
Right, we've covered how nsm
works and how it talks to its peers. But, so what? What does nsm
actually do with this terrific capability. Well, the answer is ... nothing!
Actually, it doesn't do nothing. It does something. But something generic in nature. We didn't want to have custom versions of nsm
for every customer and for every scenario. So, we've kept the core nsm
logic simple and straightforward (and unchanged) and put in place the ability for nsm
to run operating system command-lines every time a state transition happens. Those command-lines are defined by you rather than nsm
so you can do anything you want when your command-line (usually a script of some sort) is invoked.
When nsm
enters its idle
state, it runs a command-line specified as onIdle
in its configuration. Likewise, when it transitions to goingActive
or active
, it'll run the onGoingActive
and onActive
command-lines respectively. The contents of those command-lines/scripts/whatever is entirely up to you to define.
In our example of ensuring that only one EBS is active, you can imagine that we'd want to start EBS running when nsm
goes active
, and stop EBS running otherwise.
Here's some examples where we have EBS on a Linux machine running under systemd
:
onIdle.sh
#!/bin/bash
sudo systemctl stop engagebridged
onGoingActive.sh
#!/bin/bash
# Nothing to be done for EBS during goingActive
onActive.sh
#!/bin/bash
sudo systemctl start engagebridged
These scripts are only invoked during state transition - so you don't need to be concerned about the script being called multiple times for the same state. However, to be safe, you may want to do some checking of your own for the state of your goodies. In our example of using systemd
, its no big problem as we can call systemd
to start EBS as often as we want - it'll just ignore a start request if EBS is already running. Same goes for stopping it.
Configuration
Configuring nsm
- like pretty much all our software - is done using a JSON file. By default, nsm
will look for nsm_conf.json
in the directory where the nsm
executable is located but you can override this with a command-line option (see below).
Here's abbreviated example of a bare-minimum configuration. We'll look at the full configuration later on.
{
"id": "nsm-kit-01",
"networking": {
"interfaceName": "en0",
"address": "239.17.18.19",
"port": 8513
},
"resources":[
"ebs-gateway"
],
"run": {
"onIdle": "./onIdle.sh",
"beforeGoingActive": "./beforeGoingActive.sh",
"onGoingActive": "./onGoingActive.sh",
"beforeActive": "./beforeActive.sh",
"onActive": "./onActive.sh"
}
}
The id
field must UNIQUELY identify the instance of nsm
. If you screw this up and have duplicate id
's in your environment, bad things will happen. In the example we've given the instance an ID but, if you don't, nsm
will automatically generate one every time it starts. Actually, unless you specifically want to set your own IDs, just leave the id
blank and let nsm
take care of it.
Next, the interfaceName
is important because it tells nsm
which of the computer's network interfaces are to be used for connectivity to the MANET.
The address
and port
are the multicast address and port to use - all instances need to use the same address and port obviously. (If you want to use broadcast, set the address
to 255.255.255.255
.)
Next, there's an array/list of resource names. For our initial example, we've just got one resource name: ebs-gateway
.
Finally, the run
section tells nsm
what commands to run at various times. We already spoke about onIdle
, onGoingActive
, and onActive
. But what about those others? Well, beforeGoingActive
will be invoked before nsm
even tries to enter the goingActive
state. Similarly beforeActive
will be invoked before nsm
actually tries to go active.
In the case of beforeGoingActive
, nsm
expects the invocation to return a string that defines/overrides the range within which its random token is to be calculated. By default, this range is from 1000000
to 2000000
, meaning that the token will be calculated to fall inside this range. But, let's say that you've got some cool logic on your box which you use to determine that the local machine is more "important" than any other "common" machine. If that's the case, your script for beforeGoingActive
would, say, return something like 2000001-3000000
.
Now, your local nsm
's token is totally going to win whatever election dispute there is because it's token has tipped the scales to its own advantage. (Here we go again with the polictical stuff ...!)
Whatever logic you want to use to determine your token range is entirely up to you. Some thoughts, though, on this:
- The machine is a beefed-up core server system vs a mobile kit machine.
- The machine has better networking capabilities than anyone else.
- CPU or memory or network utilization on the machine has historically trended low.
- Organizational security policy dictates the machine must be used if available.
- Whatever else takes your fancy.
The beforeActive
script, on the other hand, is pretty much like a sanity check where nsm
gets final permission to enter the active
state. So, let's say that at the last-minute we want our machine to back off for whatever reason, we can cancel the operation. All that nsm
cares about here is whether the script returns 1
or 0
. If it returns 1
, nsm
will proceed to active
. If the script returns 0
, nsm
will back off and return to an idle
state.
Here's some examples:
beforeGoingActive.sh
#!/bin/bash
echo "2000001-3000000"
beforeActive.sh
#!/bin/bash
if [ some_magic_condition_is_satisfied_and_we_can_continue_to_active ]; then
echo "1"
else
echo "0"
fi
Detailed Configuration
Finally, the delicious geeky stuff. Here's a full example of nsm_conf.json
:
{
"id": "",
"priority": 0,
"networking": {
"interfaceName": "en0",
"address": "239.17.18.19",
"port": 8513,
"ttl": 64,
"tos": 56,
"cryptoPassword": "",
"txOversend": 0,
"rxLossPercentage": 0,
"rxErrorPercentage": 0,
"txLossPercentage": 0,
"txErrorPercentage": 0
},
"resources": [
"ebs_gateway"
],
"run": {
"onIdle": "./onIdle.sh",
"beforeGoingActive": "./beforeGoingActive.sh",
"onGoingActive": "./onGoingActive.sh",
"beforeActive": "./beforeActive.sh",
"onActive": "./onActive.sh"
},
"timing": {
"txIntervalSecs": 1,
"transitionWaitSecs": 5,
"internalMultiplier": 1
},
"electionToken": {
"start": 1000000,
"end": 2000000
},
"logging": {
"level": 1,
"dashboard": false,
"logCommandOutput": false
}
}
And here's the breakdown:
-
id
As explained earlier, this is the unique ID for this instance ofnsm
. Unless you have a specific need to track instances, just leave this field empty. -
priority
A priority level from0
-255
indicating the overall priority of thensm
instance. The default is0
. Nodes with lower priorities will always lose the election to higher priority nodes. Nodes within the same priority level will elect between each other based on their calculated tokens. This means that if a lower priority node is going active or is already active, that state will be taken away from them if a higher priority node joins the infrastructure for the same resource(s). -
networking.interfaceName
The name of the network card/device that connects the machine to the MANET. -
networking.address
The multicast IP address that all instances ofnsm
will communicate on via the MANET. Specify255.255.255.255
if you'd rather havensm
use broadcast than multicast. -
networking.port
The multicast/broadcast IP port thatnsm
must use. -
networking.ttl
The Time-To-Live value thatnsm
will set on the UDP packets it transmits. Make sure this value is sufficiently large for packets to traverse the entire MANET. -
networking.tos
The Type Of Service value set in UDP packets by the transmitter to hint to the network about the importance ofnsm
's packets. Ideally we want these packets to get the highest possible priority sonsm
defaults to a value of56
which means "network control packet". However, your mileage may vary based on the operating system thatnsm
is running on and/or whether all the components in your network pathway supports TOS packet marking. See the Wikipedia article for more information about the values you could use. -
networking.cryptoPassword
If specified, this string will be used as a baseline to generate an AES128 key with which to encrypt UDP traffic. Be aware that the encryption logic thatnsm
uses is more than simple AES128. Rather, each packet contains not just the encrypted data but some additional goodies to help protect against intrusion, replay attacks, timing attacks, and so on. Its probably a little bit of overkill but we're kinda paranoid around here. Of course, eachnsm
instance needs to use the same key. And one more thing, the encryption increases bandwidth utilization by around 75%. That sounds like a lot - and it is - but its that way becausensm
's payload is so small that the overhead added by the encryption will take it from around 200 bytes per packet (depending on resource count) to around 350 bytes per packet. -
networking.txOversend
Tellsnsm
how many additional packets to send every time it transmits. A zero value will only result in a single packet being sent each time. A value of1
will cause 1 extra to be sent each time,2
for two extra packets, and so on. This is useful if you're experiencing significant packet loss on your MANET - in which case you should probably sort that problem out as soon as possible. -
networking.rxLossPercentage
Specifies the percentage of received packets thatnsm
should throw away. This is only really useful if you're troubelshooting your system and want to see hownsm
will deal with packet loss. For example, if you want to simulate 15% packet loss on your MANET, plug in15
here. Generally, though, leave this at0
. -
networking.rxErrorPercentage
Sort of likenetworking.rxLossPercentage
but, in this case, simulates corruption of packets on your MANET. -
networking.txLossPercentage
Pretty much the same asnetworking.rxLossPercentage
but simulates loss at the transmitting end. -
networking.txErrorPercentage
Same asnetworking.rxErrorPercentage
but, in this case, simulates corruption at the transmitting end. -
resources
This is an array/list of resources that you wantnsm
to manage. All instances ofnsm
managing the same resources need to have the same list - obviously. Now, the reason this is a list and not just a single value is thatnsm
can handle multiple resources simultaneously. We'll delve into that in the next section. -
run.onIdle
You know what this is from earlier - run this command whennsm
transitions to anidle
state. -
run.beforeGoingActive
Run this before transitioning togoingActive
. See above for more. -
run.onGoingActive
Run this once transitioned togoingActive
. -
run.beforeActive
Explained before. If this command returns1
continue to transitioning toactive
. Otherwise, revert toidle
. -
run.onActive
Run this oncensm
has transitioned toactive
. -
timing.txIntervalSecs
How often to transmit packets when ingoingActive
oractive
state. Generally you should leave this at1
or you run the risk of causing some network-wide race conditions. -
timing.transitionWaitSecs
How long to wait before transitioning to a new state. The default of5
is a pretty good number but feel free to play with it. However, if you try to make it less that3
seconds,nsm
will bark at you and refuse to operate because there's all kinds of race conditions across you network that can be created if you mess this up. -
timing.internalMultiplier
By default this is set to1
- and you should leave it that way. However, if you want to play withnsm
or want transitions, packet transmissions, and all that happen faster or slower, set a value here that will be multiplied withtiming.txIntervalSecs
andtiming.transitionWaitSecs
. For example: if you want to artificially slow things down - say to half the normal speed - set a value of2
. If you want to speed things up - say to double the normal pace - set a value of0.5
. Be careful with this setting (and the othertiming
values for that matter) as misconfiguration and mismatched timings on differentnsm
instances can have serious ramifications for the network-wide state machine. -
electionToken.start
The start of the election token range. Override this at runtime with the firstvalue returned fromrun.beforeGoingActive
as described earlier. -
electionToken.end
The end of the election token range. Override this at runtime with the second value returned fromrun.beforeGoingActive
as described earlier. -
logging.level
Levels are0
(FATAL),1
(ERROR),2
(WARNING),3
(INFO), and4
(DEBUG). Generally you'd want to leave this at1
. -
logging.dashboard
Presents a cute littledashboard
where you can see your resources, their states, packet details, and so on. Only useful when you're runningnsm
in a terminal. Of no use whennsm
is running the background - where it usually would live. Generally leave this atfalse
. -
logging.logCommandOutput
If NOT in dashboard mode, shows the output from the commands invoked during state transitions. Useful if you're debugging your commands. Generally leave this atfalse
.
IMPORTANT: This is where we absolve ourselves of any responsibility for things going south on you if you configure
nsm
incorrectly. Consider your settings changes VERY carefully when you make them.
Multiple Resources
As promised, let's take a quick look at the resources
section of the configuration file. In our examples we've only shown a single named resource (ebs-gateway
) being managed. But you can specific a whole lot of resources if that makes sense. For example:
.
.
.
"resources": [
"ebs_gateway",
"pli_monitor",
"netmonitor",
"system_database",
"video_stream1",
"video_stream2"
],
.
.
.
The logic described in this document applies to each resource in this list, independently. So, let's say that on the machines in the network, we have EBS (as in our examples), and we also have an application that is handling Position Location Information (PLI), a network monitor, some sort of distributed database, and two video streams. And we want to make sure that only one instance of each of those resources is active
at any one time.
Now, we could just do this on a machine-by-machine basis and use a single resource - say primary_server
- and when it goes active, turn on our EBS, PLI, network monitor, database, and the video streams. Everything will, of course, happen on that one machine.
But if we'd like nsm
to effectively distribute those resource management tasks across the network according to its election algorithm, we split our resources out as shown in the list above. In that case, an instance of nsm
might be active for the EBS resource, while another is handling the database and the first video stream, while a third instance is handling the PLI business and the second video stream.
That is hopefully pretty straightforward to understand but there's the question of those command lines ... how do they know *which resource they're being invoked for?
Well, that's simple - nsm
will pass the resource name as the first parameter to the command line. So, say for instance we specify ./onActive.sh
for the run.onActive
element in the configuration, the actual command that nsm
will execute will be:
./onActive.sh <resource_name>
So, if the ebs_gateway
resource goes active, the command invoked will be:
./onActive.sh ebs_gateway
If the video_stream1
resource goes active, the command invoked will be:
./onActive.sh video_stream1
... and so on.
Also, these commands are invoked seperately for each resource's state transition - nsm
does not batch up command-line invocations for multiple resources as it doesn't really make much sense given that each resource can transition at different times to the others.
Asymmetric Resource Lists
By the way, not all nsm
instances have to have the same resource list. So you might have instance A have:
.
.
.
"resources": [
"ebs_gateway",
"netmonitor",
"system_database",
],
.
.
.
And instance B have:
.
.
.
"resources": [
"ebs_gateway",
"pli_monitor",
"video_stream1",
"video_stream2"
],
.
.
.
In that example, A will always "win" for netmonitor
and system_database
, while B will always win for pli_monitor
, video_stream1
, and video_stream2
. Responsibility for the shared resource - ebs_gateway
- would be duked out by A and B.
Many, Many Instances
Our examples have just shown two instances: A and B. Don't let that fool you, nsm
will (should?) scale as much as you need. If you have 10 instances or 100 or 1,000; it's core logic remains unchanged. And, because nsm
uses multicast (or broadcast) and because only the active (or going active) instance actually transmits traffic on the network, there's very little bandwidth being used no matter how many instances you have.
In our internal testing we scaled an nsm
setup to just shy of 100 instances and all worked well with network bandwidth seldom exceeding 2kbps. When we started forcing errors and purposefully mismatch timings, we did have some spikes - but those spikes seldom went above 10kbps. And that was at 65% packet loss, all kinds of corruption, and so on.
Other Areas Of Use
Hopefully you think that nsm
is just the coolest toy you've ever seen and are in awe of the spectular software we develop here at RTS. (Please say you are!)
Regardless, you're likely thinking that this thing can be used for other purposes than just MANET interaction. You're right - nsm
doesn't really care if you're using a MANET. All it wants to do is communicate with other instances of itself and automatically elect an active instance. It doesn't even need to connect to a MANET for its shared pipe - you can happily use nsm
on your regular network for all kinds of situations where you need to apply the Highlander Principle (OK, now that term is a thing - we like it too!)
For example, use nsm
to build out hot-standby systems for redundancy purposes. Or use it to solve issues with routing and ingress/egress on your enterprise network. Maybe you want to do something like have a guarantee that only a single controller entity will be active for your sexy new distributed logic engine (nah, we don't even know what that means but it sounds awesome).
Have fun!