CODING
snaproute Go BGP Code Dive (5): Starting a Peer
Last time we looked at the snaproute BGP code, we discovered the peer bringup process is a finite state machine. With this in mind, let’s try to unravel the state machine into a set of calls, beginning from our original starting point, a debug message that prints on the screen when a new peering relationship is established. The key word in the debug message was ConnEstablished,
which led to:
func (fsm *FSM) ConnEstablished() {
fsm.logger.Info(fmt.Sprintln("Neighbor:", fsm.pConf.NeighborAddress, "FSM", fsm.id, "ConnEstablished - start"))
fsm.Manager.fsmEstablished(fsm.id, fsm.peerConn.conn)
fsm.logger.Info(fmt.Sprintln("Neighbor:", fsm.pConf.NeighborAddress, "FSM", fsm.id, "ConnEstablished - end"))
}
From here, we searched for calls to ConnEstablished,
and found—
func (fsm *FSM) ChangeState(newState BaseStateIface) {
...
if oldState == BGPFSMEstablished && fsm.State.state() != BGPFSMEstablished {
fsm.ConnBroken()
} else if oldState != BGPFSMEstablished && fsm.State.state() == BGPFSMEstablished {
fsm.ConnEstablished()
}
}
Looking for ChangeState
leads us to a lot of different calls, but only one that seems to relate to establishing a new peer, as evidenced by a state that relates to established in some way. This, in turn, leads to—
func (st *OpenConfirmState) processEvent(event BGPFSMEvent, data interface{}) {
....
switch event {
...
case BGPEventKeepAliveMsg:
st.fsm.StartHoldTimer()
st.fsm.ChangeState(NewEstablishedState(st.fsm))
...
}
…and hence to processEvent,
for which there are a ton of calls. The name of the function, however, and the way it’s called, imply we’ve just landed on the tail end of a finite state machine (FSM), which we can trace back by looking at the call by reference pointer in front of processEvent.
Let’s trace in more detail from here. First, we need to look for OpenConfirmState,
which is the call by in the function call above. What’s actually happening here is that when a peer is in OpenConfirm, and some event occurs, then func (st *OpenConfirmState) processEvent(event BGPFSMEvent, ...
is called to handle the event. What we want to do is figure out how the state machine gets to OpenConfirm. Searching for OpenConfirmState
yields a number of calls, but the one that’s interesting is—
func (st *OpenSentState) processEvent(event BGPFSMEvent, data interface{}) {
....
switch event {
case BGPEventBGPOpen:
....
st.fsm.ChangeState(NewOpenConfirmState(st.fsm))
}
}
To back up one more step in the FSM, we need to search for OpenSentState.
This again produces a number of results, but the interesting one is—
func (st *ConnectState) processEvent(event BGPFSMEvent, data interface{}) {
....
switch event {
....
case BGPEventTcpCrAcked, BGPEventTcpConnConfirmed:
....
st.BaseState.fsm.ChangeState(NewOpenSentState(st.BaseState.fsm))
}
To back up one more step, we need to find ConnectState,
which lands here—
func (st *IdleState) processEvent(event BGPFSMEvent, data interface{}) {
....
switch event {
case BGPEventManualStart, BGPEventAutoStart:
st.fsm.SetConnectRetryCounter(0)
st.fsm.StartConnectRetryTimer()
st.fsm.ChangeState(NewConnectState(st.fsm))
Then IdleState,
which lands here—
func (st *ConnectState) processEvent(event BGPFSMEvent, data interface{}) {
....
switch event {
case BGPEventManualStop:
st.fsm.StopConnToPeer()
st.fsm.SetConnectRetryCounter(0)
st.fsm.StopConnectRetryTimer()
st.fsm.ChangeState(NewIdleState(st.fsm))
To back up one more, we need to search for ConnectState,
which has two results of interest. The first is—
func (st *ActiveState) processEvent(event BGPFSMEvent, data interface{}) {
....
switch event {
....
case BGPEventConnRetryTimerExp:
st.fsm.StartConnectRetryTimer()
st.fsm.ChangeState(NewConnectState(st.fsm))
This particular call appears to be moving into the connect state from a connect retry timer expiring, though. This isn’t going to take us back any further steps in the process “from nothing,” so we need to look at the other call—
func (st *IdleState) processEvent(event BGPFSMEvent, data interface{}) {
....
switch event {
case BGPEventManualStart, BGPEventAutoStart:
st.fsm.SetConnectRetryCounter(0)
st.fsm.StartConnectRetryTimer()
st.fsm.ChangeState(NewConnectState(st.fsm))
So we need to look for IdleState.
Here we run into another, similar, situation—IdleState
is called from a lot of different places. Again, what we want is the call that moves us from something prior to idle state, rather than a call that deals with errors. As it turns out, there is only one such call—
func (fsm *FSM) StartFSM() {
if fsm.State == nil {
fsm.logger.Info(fmt.Sprintln("Neighbor:", fsm.pConf.NeighborAddress, "FSM:", fsm.id,
"Start state is not set... starting the state machine in IDLE state"))
fsm.State = NewIdleState(fsm)
}
fsm.State.enter()
This is not like the other calls we’ve been chasing, in that it doesn’t use either ChangeState
or processEvent
with a call by reference. Rather, this looks like it is the actual starting point of the FSM itself, entered when a new neighbor is discovered (something we won’t be dealing with in this series).
Now that we’ve unwound the call chain, we can work back through it and build a simpler version with just raw function calls to understand the process of bringing up a peer. Starting from the beginning (and hence reversing the order in which we’ve discovered the call chain)—
- StartFSM()
- func (st *IdleState) processEvent()
- st.fsm.SetConnectRetryCounter(0)
- st.fsm.StartConnectRetryTimer()
- st.fsm.ChangeState(NewConnectState(st.fsm))
- func (st *ConnectState) processEvent()
- st.fsm.StopConnToPeer()
- st.fsm.SetConnectRetryCounter(0)
- st.fsm.StopConnectRetryTimer()
- st.fsm.ChangeState(NewIdleState(st.fsm))
- func (st *IdleState) processEvent()
- st.fsm.SetConnectRetryCounter(0)
- st.fsm.StartConnectRetryTimer()
- st.fsm.ChangeState(NewConnectState(st.fsm))
- func (st *ConnectState) processEvent()
- st.fsm.StopConnectRetryTimer()
- st.fsm.SetPeerConn(data)
- st.fsm.sendOpenMessage()
- st.fsm.SetHoldTime(st.fsm.neighborConf.RunningConf.HoldTime, st.fsm.neighborConf.RunningConf.KeepaliveTime)
- st.fsm.StartHoldTimer()
- st.BaseState.fsm.ChangeState(NewOpenSentState(st.BaseState.fsm))
- func (st *OpenSentState) processEvent()
- st.fsm.StopConnectRetryTimer()
- bgpMsg := data.(*packet.BGPMessage)
- if st.fsm.ProcessOpenMessage(bgpMsg) {
st.fsm.sendKeepAliveMessage()
st.fsm.StartHoldTimer()
st.fsm.ChangeState(NewOpenConfirmState(st.fsm))
}
- func (st *OpenConfirmState) processEvent()
- st.fsm.StartHoldTimer()
- st.fsm.ChangeState(NewEstablishedState(st.fsm))
- func (st *OpenConfirmState) processEvent()
- func (fsm *FSM) ChangeState()
- func (fsm *FSM) ConnEstablished()
In the next post in this series, we’ll start looking at what some of these functions do—and then we’ll look at some of the “byways” representing error conditions to see how they work.
snaproute Go BGP Code Dive (4): Starting a Peer
In the last three episodes of this series, we discussed getting a copy of SnapRoute’s BGP code using Git, we looked at the basic structure of the project, and then we did some general housekeeping. At this point, I’m going to assume you have the tools you need installed, and you’re ready to follow along as we ask the code questions about how BGP actually works.
Now, let’s start with a simple question: how does BGP bring a new peer up?
It seems like we should be able to look for some file that’s named something with peering in it, but, looking at the files in the project, there doesn’t seem to be any such thing (click to show a larger version of the image below if you can’t read it).
Hmmm… Given it’s not obvious where to start, what do we do? There are a number of options, of course, but three options stand out as the easiest.
First, you can just poke around the code for a bit to see if you find anything that looks like it might be what you’re looking for. This is not, generally, for the faint of heart. Over time, as you become more familiar with the way coders tend to structure things, this method will work more often than not, but for now, let’s look for an easier way to find a starting point.
Second, you could compile the code, setting breakpoints in the TCP code just before TCP sends packets off to the correct process for handling, fire a BGP packet at the box using a simulator, and see where the code actually takes you. This also isn’t for the faint of heart, so let’s see if we can think of something simpler.
Third, you can do it the way I normally do. Run the code as a process and turn on debugging, generally in an emulator so you can connect peers, etc. Now, connect a peer, and capture a few debug messages. Shut everything down and return to your code directory, then search the code for one or more of the debug messages you just captured. This should lead you to the code you’re looking for. In this case, I run into a message that looks something like this—
Neighbor: x.x.x.x FSM xx ConnEstablished - start
There is one word in here that is pretty odd—ConnEstablished—and hence will probably yield results if I do a search for it. I’m going to resort to Atom here, as I don’t want to get into grep on the command like, but doing a search across the entire project shows me (once again, click for a larger version)—
Hmmm… The most interesting of these is the one where the message is actually printed on the console, which is line 1319 in fsm.go. Popping into this file, I find the following—
func (fsm *FSM) ConnEstablished() {
fsm.logger.Info(fmt.Sprintln("Neighbor:", fsm.pConf.NeighborAddress, "FSM", fsm.id, "ConnEstablished - start"))
fsm.Manager.fsmEstablished(fsm.id, fsm.peerConn.conn)
fsm.logger.Info(fmt.Sprintln("Neighbor:", fsm.pConf.NeighborAddress, "FSM", fsm.id, "ConnEstablished - end"))
}
Now if I find where this function is called, I can find out where, in the code, neighbors actually actually established. This process of tracing back from the end point to figure out what’s actually happening can be a bit tedious, but until you’re more familiar with the basic structure of the code, it’s often the only choice you’re going to have.
As it turns out, the name of the function we need to find is ConnEstablished. We can repeat our original search to find out where this function is actually called (see the image above, as it’s the same search). There is only one call to this function, found in the same file—
func (fsm *FSM) ChangeState(newState BaseStateIface) {
....
} else if oldState != BGPFSMEstablished && fsm.State.state() == BGPFSMEstablished {
fsm.ConnEstablished()
}
}
You might notice there are a number of calls to PeerConnEstablished, as well—and we would simplify our search by jumping directly to that call—but for the moment let’s take the long way around by tracing back one step at a time.
Looking at the code, we find there are a number of calls to ChangeState, but the one that’s interesting is here—
st.fsm.ChangeState(NewEstablishedState(st.fsm))
Which is around line 611 in fsm.go, for those who are trying to follow along. This particular call is interesting among all the other calls because it is the only one that mentions the state we’re looking for, established state. We can figure out where to look next by going to the top of the function in which this line of code is called, which is—
func (st *OpenConfirmState) processEvent(event BGPFSMEvent, data interface{}) {
st.logger.Info(fmt.Sprintln("Neighbor:", st.fsm.pConf.NeighborAddress, "FSM:", st.fsm.id,
"State: OpenConfirm Event:", BGPEventTypeToStr[fusion_builder_container hundred_percent="yes" overflow="visible"][fusion_builder_row][fusion_builder_column type="1_1" background_position="left top" background_color="" border_size="" border_color="" border_style="solid" spacing="yes" background_image="" background_repeat="no-repeat" padding="" margin_top="0px" margin_bottom="0px" class="" id="" animation_type="" animation_speed="0.3" animation_direction="left" hide_on_mobile="no" center_content="no" min_height="none"][event]))
....
Now we’ve run into something odd—the function name is literally processEvent. This seems a little generic. In fact, if we search the code for processEvent, we’re going to find hundreds of instances of this function call. It looks like we’re lost in the weeds, doesn’t it? Not necessarily…
If you’ll notice, just before the function name, there’s a set of parenthesis with (st *OpenConfirmState). This is, in fact, what I would call in C a call by reference, something that’s rather common in building a finite state machine like this in code. Let me explain…
A finite state machine is normally a flow chart that shows each possible state the system can be in, how it can enter that state, and how it can exit the state. Sometimes this FSM is represented in text form, where the state is listed, possible inputs are listed, and the resulting state is given for each possible input in this particular state. Forinstance, the BGP specification contains such an FSM, as shown below—
8.1.4. TCP Connection-Based Events
Event 14: TcpConnection_Valid
Definition: Event indicating the local system reception of a TCP connection request with a valid source IP address, TCP port, destination IP address, and TCP Port. The definition of invalid source and invalid IP address is determined by the implementation.
BGP's destination port SHOULD be port 179, as defined by IANA.
TCP connection request is denoted by the local system receiving a TCP SYN.
Status: Optional
Optional Attribute Status:
1) The TrackTcpState attribute SHOULD be set to TRUE if this event occurs.
Event 15: Tcp_CR_Invalid
Definition: Event indicating the local system reception of a TCP connection request with either an invalid source address or port number, or an invalid destination address or port number.
When we run into something like processEvent in a file called fsm, we’re probably looking at a finite state machine broken up into a set of functions, each of which represent a single state, and each of which perform the right actions to move from the current state to a new state in the FSM. I know this is difficult to grock, so let me give you a more visual representation.
State A is where we begin… This state would be represented as a single function in the source code. When State A is reached, this function is called, and, depending on the input, the function for State A will either call State B’s function, or State C’s function. This chain of events will continue until the final state is reached, and the FSM either enters a steady state, or exits. What tends to be confusing about this process is that these functions might not, in fact, call one another. Instead, what generally happens is the function for State A will be called, which will result in State C being the new state. The program will exit and wait for another event. When this next event occurs, the application will send this new event to the function for the current state, State C, which will then process the event, leaving the process in state D, for instance. This process/move to a new state/exit/wait cycle happens until the state reaches a steady state, or until the process ends.
Instead of calling each function a different name, this code is built with the same function name in each state structure. Each state is represented by a structure, and each structure has a function that is called when an event happens while the FSM is in that particular state. If you’re in state A and event occurs, you call (*stateA) processEvent.
If you’re in state B and an event occurs, you call (*stateB) processEvent.
There is one structure for each state, and a single function to handle events while in that state.
This means we’re not going to be able to just jump back function by function to trace what happens. Instead of tracing the functions, we’re going to need to trace the state by looking at the function within each state that deals with events. Lucky for us, the current state is contained right there in the function call—(st *OpenConfirmState). What we’re going to need to do, then, is trace back the successive states by looking at how we get to OpenConfirmState, and then how we get to the state that gets us to OpenConfirmState, etc. Along the way, we’re going to see precisely how a new peer is brought up in this version of BGP. We’ll start tracing these states next time.
snaproute Go BGP Code Dive (3)
This week, I want to do a little more housekeeping before we get into actually asking questions of the bgp code. First there is the little matter of an editor. I use two different editors most of the time, Notepad++ and Atom.
- Notepad++ is a lightweight, general purpose text editor that I use for a lot of different things, from writing things in XML, HTML, CSS, Python Javascript, C, and just about anything else. This is an open source project hosted on the Notepad++ web site with the code hosted at github.
- Atom is a more GUI oriented “programmer’s editor.” This is a more full featured editor, going beyond basic syntax highlighting into projects, plugins that pull diffs in side by side windows, and the like. I don’t really have a build environment set up right now, so I don’t know how it would interact with compiled code, but I assume it would probably have a lot of the tricks I’m used to, like being able to trace calls through the code, etc. Atom is available here.
I haven’t actually chosen one or the other—I tend to use both pretty interchangeably, so you’re likely to see screen shots from both/either as I move through various bits of the code. There is a second bit of “housekeeping,” I wanted to point out up front how project files are usually structured. This can be a little confusing to folks who haven’t worked on large projects, so this might be helpful.
Code is built from front, or top, of the file to the end, or the back, of the file. To understand, assume I’m going to build a small program that either adds or multiplies two numbers, based on the operator in the arguments. Suppose I want to be able to extend it later, and I don’t like switch statements, so I decide to implement it as three different functions, like this—
int operate(int num1, int num2, int operator) {
if operator == 1 return add(num1, num2);
if operator == 2 return multiply(num1, num2);
}
int add(int num1, int num2) {
return num1 + num2;
}
int multiply(int num1, int num2) {
return num1 * num2;
}
I’ve not dealt with overflows, floating points, and all the rest here—this is just to illustrate a simple principle (I’d normally #define the operators, or build an array and call straight through based on a pointer to the actual operator function—but this isn’t elegant, this is simple). If I actually tried to compile this bit of code, I’d get an error on the line—
if operator == 1 return add(num1, num2);
—telling me “add” isn’t defined. There are two ways I can solve this. The first would be to declare the function someplace. Normally I’d do this in a file that’s included in other files, but with something this simple I might just put a function declaration at the top of the file and leave it at that. The problem with either of these methods is that I must remember to change the declaration if I happen to change the function itself. For instance, if I modify “add” to accept floating point numbers, I need to not only change the function, but the declaration of the function, as well.
Instead of declaring things in this way, what is normally done is to build “helper” and “basic” functions first, then to call them in more complex functions later on in the same file. If you’re going to break up a single project into multiple files, of course, you still have to make certain you declare things you’ve included in one file, and want to use in another. But in many cases, an entire module is only going to have one or two functions that can be called from other files—the majority of the functions are going to be “hidden,” in that they’re only used in the single file in which they’re defined and coded up.
I know that’s a big chunk of text there, but to give an example, the simplest thing here is to do this:
int add(int num1, int num2) {
return num1 + num2;
}
int multiply(int num1, int num2) {
return num1 * num2;
}
int operate(int num1, int num2, int operator) {
if operator == 1 return add(num1, num2);
if operator == 2 return multiply(num1, num2);
}
Now by the time operate is compiled, the functions it relies on have already been compiled—the compiler knows where to find them, and what they actually do. To complete the example, this makes perfect sense if the only function in this file I ever intend to call from anyplace else is operate. I can declare operate in a file that’s included in other files, and just leave add and multiply as local declarations, only usable from within the file operate is defined.
As a practical matter, this means the big chunks of code are going to be at the bottom of any given file. If you’re looking, for instance, for how BGP processes a particular packet in the BGP code, you’re going to find the answer at the bottom of the file containing the function that processes that particular packet. To understand how the function operates, you’re going to need to “trace up” the file, examining each of the “helper functions.”
But when you’re reading code, you need to start at the bottom of the file, not the top.
snaproute Go BGP Code Dive (2)
Now that you have a copy of BGP in Go on your machine—it’s tempting to jump right in to asking the code questions, but it’s important to get a sense of how things are structured. In essence, you need to build a mental map of how the functionality of the protocol you already know is related to specific files and structure, so you can start in the right place when finding out how things work. Interacting with an implementation from the initial stages of process bringup, building data structures, and the like isn’t all that profitable. Rather, asking questions of the code is an iterative/interactive process.
Take what you know, form an impression of where you might look to find the answer to a particular question, and, in the process of finding the answer, learn more about the protocol, which will help you find answers more quickly in the future.
So let’s poke around the directory structure a little, think about how BGP works, and think about what might be where. To begin, what might we call the basic functions of BGP? Let me take a shot at a list (if you see things you think should be on here, feel free to leave a comment—you might think of something I don’t, or we might have different ideas about what these should be, etc.):
- Handle peering sessions
- Receive updates
- Run bestpath
- Install routes into local tables
- Install routes into the Routing Information Base (RIB)
Each of these can be broken down in to a lot of other pieces and parts, but we don’t want to go too deep here for the moment—we’re really trying to guess how the basic functions of the protocol align with directories and files in the actual code. Essentially—If I want to know how this particular implementation of BGP handles peering, where would I look? Now, let’s glance at the actual contents of the SnapRoute’s Go BGP implementation, and see what we can figure out—can we match any functions to directories?
Some of the things here I can guess at just from experience, like (note I’m not going to verify this stuff, and I might be wrong in some cases, but that’s okay, we’re just taking a first stab at figuring out where things might be)—
- api—which means Application Programming Interface. Probably a set of files that declare function calls and the like into other applications.
- flexswitch—since FlexSwitch is the actual name of the project, this probably contains files related to the overall routing engine SnapRoute is creating/maintaining. I would expect to find interfaces and interprocess communication to other processes in the same project, or something like that.
- fsm—means Finite State Machine. A routing protocol can be described as a set of states, with specific events that cause the protocol to shift from one state to another. For instance, when a BGP peer shifts from active to idle,, this is a state change. The FSM would be considered the “heart” of the protocol in many ways.
- ovs—means Open Virtual Switch. This is probably interfaces to OVSDB, which allows this version of BGP to run the OpenSwitch project.
- rpc—means remote procedure call.
Another good place to look is in the /docs directory, which sometimes has useful information about how the code is structured. In this particular case, there is a diagram in the /docs directory that shows a basic overview of the code.
From this we can gather than the neighbor, FSM, and BGP RIB are considered three different modules in the code base. We can also infer there an external database that holds the BGP tables and configuration, accessed through the Thrift RPC. The server module is interesting; we’ll have to watch for this as we start asking the code specific questions, to figure out what this might be used for. I’ll give you hint up front, and say this is a pretty common structure for just about every piece of software that is driven by events.
That’s enough poking around for this post; we’ll look at some tools next, and then start into actually asking the code questions.
snaproute Go BGP Code Dive (1)
I often tell network engineers they need to learn to code—and they sometimes take my advice and run off to buy a book, or start an online program (which reminds me, I’m way, way behind in my own studies about right now). But learning to code, and being able to use that skill for anything are actually two different things. In fact, my major problem with my coding skills is finding projects I can undertake where I don’t feel like I’m wasting my time. Anyone want to write the world’s 25 millionth implementation of inserting the date and time into a document? No, I didn’t really think so.
So what can you do with coding skills? One thing you can do is <em?read the source. Thus, I’m starting an entirely new feature here at ‘net Work. Every now and again (which means I don’t know how often), I’m going to poke at some routing or control plane code or another, and try to figure out what it actually does. Why not just go through a single protocol line by line? Because—honestly—it’s not a useful way to approach a protocol in code. Rather—here is my first bit of advice—you want to learn how to ask a particular code base a specific question, or set of questions, and get a reasonable answer. Once you have this skill down, it might, or might not, be useful to become a full on coder. But a lot of people start at the wrong end of the stick, and end up with some basic coding skills and no real idea how to apply them.
To kick this series off, I’m going to start with something really basic, and unrelated to coding—the mechanics of actually getting a clone of an open source routing protocol repository on your local computer. These repositories aren’t large, so they won’t cost you a lot to carry around. Why not just access the respository remotely? I’ve found it’s just easier to understand code if I have a local copy, and a local (even if it’s skinny) tool set to work with. So let’s start with this simple bit, and see where it goes.
First, I want to find an open source code base to clone and start looking at. It might be natural to start with something in quagga, but I’m going to take a slightly different tack for this first look through code—I’m going to clone an opensource version of BGP written in Go. The specific version I’m going to clone is part of SnapRoute, who happens to be (like Cumulus) an open source routing stack company. I do intend to work with quagga in the future (in C, which is kindof my native language), but I thought I’d start someplace different for the moment.
A first note—I know nothing about Go as a language. I do know it’s a programming language of some sort, and I know it runs in a virtual machine, rather than being compiled into native processing code for any particular processor. This means Go is a compiled language, just like C. Python, for instance, is an interpreted language. Compiled languages are run through a compiler, and converted into actual machine code to run on a particular processor/etc. I’m not going to get into all the differences here, other than to note there is a difference. But the point is that if I can figure out what BGP in Go is doing, you can too. First lesson: language should never be a barrier to reading the source.
Now, to go get BGP in Go.
First things first, you need a GitHub account. So go to https://github.com in another tab and create one. worry, I’ll wait.
Once you have your account, go to the SnapRoute l3 repository, which you will find here (https://github.com/OpenSnaproute/l3). On the right side, you’ll see a green button:
Once you’ve clicked there, you’re going to see a secondary box pop up that looks like this:
Now click on “open in desktop.” If you don’t have a desktop version of Git installed, this is going to direct you to a page that will give you the right downloads and stuff to get it installed. Once you’re done with all of that, you’re going to get to a window that is going to ask you where to clone this repository. I have a git directory that’s off my main data files directory, which makes it easy for me to find the files once they’re cloned to my laptop. I would suggest you do the same thing, because you are going to want to be able to find these files easily from a command line.
Now, you might ask—why not just download a copy, rather than kicking up desktop Git and cloning the repository? The simple reason is this: if you clone the repository, you’re going to get updates to the code as folks make changes. If you just download the .zip file and push it into a directly, you’re not. It’s more useful to have the synchronized clone in the long term, because you’ll always be working with the latest code (in the main line, anyway—as people pull branches and the like, you might need to move to another branch or project, but I’ll leave these for some other day.”
When you’re done, you will have a new directory with the actual source of SnapRoute’s implementation of BGP, OSPF, and a few other layer 3 odds and ends in a directory on your hard drive. It should look something like this:
You can click on it to see a larger image. Yes, I use Windows. I know, I’m a throwback to the stone ages or something like that, but—honestly, I’ve used everything from a CoCo II through a Xerox Star through a Sun Workstation to a few dozen Apple boxes to DR-DOS boxes to WFWG 3.11 to… Well, whatever. It’s a tool, get over it.
This post has gone a bit long, so I’ll leave the next step—glomming on to a good set of editing tools and doing some basic recon—for the next post (whenever that might be).