1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 """
25 Mesh Routing, this is the protocol by which an ad-hoc network of nodes can be created and
26 how messages may be passed between them.
27
28 Routing in SpoonRPC
29
30 Baseline:
31 - a network of nodes (each node is probably a server)
32 - nodes are connected directly to each other by links (probably TCP)
33 - from node A to node B, there may be more than one direct route (redundant paths are okay, encouraged in fact,
34 to avoid pitfalls of IRC)
35
36 Route tracking
37
38 Each node tracks -- for every other node in the network -- how many hops it is to that node, and which link is used
39 to get there.
40
41 New links
42
43 When a node gains a new link, it sends a message to each of its other links announcing the new hop-count for this
44 new link ("Node T, 1 hop"). Each link compares this with any existing route for T, and if the hop count is smaller,
45 the route entry is replaced and a new hop count message is sent to each of its local links (except the one it received
46 the original message from). In this way, the new route will propagate. When such a message loops back on itself around
47 a redundant link, it will be discarded because the hop count will be larger than the one already stored.
48
49 If the hop count is larger, but the link making the announcement is the link already in this node's route table, then
50 the route's hop-count is changed and the message is propagated anyway. (It means the node's route is now longer than
51 it was, or possibly infinite.)
52
53 An example may help: Node A is linked directly to B and C. It has a route to node X through B, with 4 hops. (All it
54 stores for "X" is "B, 4 hops".) If it receives a message from C saying "X is 2 hops for me" then it will change X's
55 route to "C, 3 hops" and send this new route to its other local links (like B). If, on the other hand, C says "X is 5
56 hops for me" then A will ignore the message. If A receives a message from B saying "X is 5 hops for me" then A changes
57 its route to "B, 6 hops" and sends this new route to all local links.
58
59 Once a new link is established, the two newly connected nodes will each send the other a hopcount update message
60 containing all of the nodes each knows about. In the case of a node with no connections to the network, connecting
61 to a node with connections, this will make the new node aware of all of the nodes that are available. In the case of
62 a link forming between two existing networks (or perhaps fragments of a previous network that got disconnected), these
63 messages will cause the two networks to be made fully aware of eachother.
64
65 Dead links
66
67 When a node drops a link, it sends out a message to all other links announcing the hop-count for the dead route as
68 "infinity". (This can be represented as 0, -1, maxint, or whatever.) If a link receives an "infinity" route for node
69 X from the link that is its current route to X, then the node must drop that route and propagate the "infinity" route
70 to all its other links. In this way, if that was the only route to X, everyone will eventually drop the route and have
71 X unreachable.
72
73 If a node receives an "infinity" route for node X from a link that is not its current route to X, it should immediately
74 propagate its current route to that link. This will end up being propagated as if it were a new link (this is really just
75 good neighbor policy.)
76
77 This rerouting mechanism may leave some nodes with inefficient (winding) routes to nodes that move around. So the
78 "good neighbor" policy dictates that when you have a route to X, and a link that is not your route sends you info
79 about a much longer route (at least 2 hops longer than yours), you turn around and tell this link about your route.
80
81 """
82 from spoon.transports import LinkMessage
83 from spoon import LMTYPE_NETWORK, LMTYPE_NETWORK_PROTO, NMTYPE_MESSAGING
84 from spoon import serialprop, __SPOONNETMSG_TAG__
85 from spoon import ber
86
87
88
89
91 """
92 NetMessage is the protocol unit for the networking component of spoon. It contains a
93 src (source node id), dest (destination node id), message type and an object attachment.
94 The object attachment can of course be anything at all that's serializable.
95 The message type indicates what subsystem the message should be handled by. Currently
96 only messaging is implemented, but in the future, who knows.
97 @todo: At some point in the future I'd like to add an HMAC here, but I have to sort out
98 how keys and such are shared at a protocol level here.
99 """
100 - def __init__(self, src=None, dst=None, mtype=None, attach=None):
101 self.src = src
102 self.dst = dst
103 self.mtype = mtype
104 self.attach = attach
105
106
107 @ber.encoder(NetMessage)
116
117 @ber.decoder(__SPOONNETMSG_TAG__)
128
129
131 pass
132
134 """
135 Raised when trying to send to a node that is not currently reachable. This is not necessarily a
136 permanent condition. Particularly if a transport has just been lost, a node may become available again
137 within a few seconds as a new route is found.
138 """
142
143
144
145 INFINITY = 2147483647
146
148 """
149 This class handles LMTYPE_NETWORK messages and LMTYPE_NETWORK_PROTO messages. LMTYPE_NETWORK_PROTO is handled
150 as a routing update, and LMTYPE_NETWORK will be handled as network traffic to be dealt with or routed.
151
152 If there is a SingletonMessaging instance already created (e.g. through the use of
153 the acceptMsg decorator) and messaging is None
154
155 @ivar hub: the TransportHub that the router is associated with, should be set at init time and never changed.
156 @ivar nodes: This is the routing table. It's a dictionary whose keys are node ids, and the values are a
157 tuple consisting of the hop count to the node and the transport used to get there.
158 @ivar transports: A set of the transports that are currently active.
159 """
160 - def __init__(self, hub, messaging=None):
167
168
169
170
171
172
174 """
175 Handles the routing protocol initilization.
176 This involves first exchanging routing tables entirely, then updating
177 our routing table as appropriate. While updating our routing table,
178 we need to construct a routing update message and send it to all transports other than
179 the transport being added.
180 @param t: The transport being added to the network
181 """
182
183 wholeTable = {}
184 for k,v in self.nodes.iteritems():
185 wholeTable[k] = v[0]
186 wholeTableMessage = LinkMessage(LMTYPE_NETWORK_PROTO, wholeTable)
187
188 remoteTable = {}
189
190 if nodeId < self.hub.nodeId:
191
192 t.write(wholeTableMessage)
193 remoteTable = t.read()
194 else:
195 remoteTable = t.read()
196 t.write(wholeTableMessage)
197
198 if type(remoteTable) != LinkMessage:
199 self.hub._log.error("Invalid protocol message received during transport initialization of routing on transport "+repr(t)+", got '%s'"%(type(remoteTable)))
200 raise RoutingProtocolError("Invalid protocol message received during transport initialization of routing on transport "+repr(t))
201
202
203 self.transports.add(t)
204 self.handleUpdate(t, remoteTable.attach, {t.nodeId: (1, t)})
205
207 """
208 Removes the transport from the network. This involves figuring out which nodes are now unreachable (any routes
209 we had over that transport), removing any references to the transport from our routing table, and updating
210 our neighbors about our changed routing table.
211 @param t: The transport being removed from the network
212 """
213 updatedRoutes = {}
214 self.transports.remove(t)
215 for k,v in self.nodes.iteritems():
216 if v[1] == t:
217 self.nodes[k] = (INFINITY, None)
218 updatedRoutes[k] = INFINITY
219
220
221 for x in self.transports:
222 x.write(LinkMessage(LMTYPE_NETWORK_PROTO, updatedRoutes))
223
225 """
226 Handles a routing table update, builds the update message and sends it out to all except the src of the update.
227 @param t: The source transport from which the update came (in other words where NOT to send our update)
228 @param update: A dict containing the routing update.
229 @param localUpdate: Only used in the specific case of when creating a new transport. This will include an
230 entry for the node we've just connected to, so that it may be merged into the update sent out to the
231 other nodes.
232 """
233
234 updatedRoutes = {}
235
236 goodNeighborUpdate = {}
237 if localUpdate:
238
239 for k, v in localUpdate.iteritems():
240 self.nodes[k] = v
241 updatedRoutes[k] = v[0]
242
243 for k, v in update.iteritems():
244 currentRoute = self.nodes.get(k, None)
245 newCount = v + 1
246
247 if currentRoute:
248
249
250
251 if (newCount < currentRoute[0]) or ((newCount > currentRoute[0]) and (t == currentRoute[1])):
252 updatedRoutes[k] = newCount
253 self.nodes[k] = (newCount, t)
254
255
256
257
258 elif(newCount > currentRoute[0]) and (t != currentRoute[1]):
259 goodNeighborUpdate[k] = currentRoute[0]
260
261 else:
262 updatedRoutes[k] = newCount
263 self.nodes[k] = (newCount, t)
264
265
266 if updatedRoutes:
267 for x in self.transports:
268 if x != t:
269 x.write(LinkMessage(LMTYPE_NETWORK_PROTO, updatedRoutes))
270 if goodNeighborUpdate:
271 t.write(LinkMessage(LMTYPE_NETWORK_PROTO, updatedRoutes))
272
274 """
275 Called by the SpoonTransportThread when a link message has been received for one of our
276 LMTYPEs.
277 @param t: The transport that it came from
278 @param lm: The LinkMessage that came in.
279 """
280 self.hub._log.debug("Recieving link message "+repr(lm))
281 if lm.msgtype == LMTYPE_NETWORK_PROTO:
282 self.handleUpdate(t, lm.attach)
283 elif lm.msgtype == LMTYPE_NETWORK:
284 self.handleNetworkTraffic(t, lm.attach)
285 else:
286 raise RoutingProtocolError("Received unknown LinkMessage type %d on transport %s"%(lm.msgtype, repr(t)))
287
289 if type(obj) != NetMessage:
290 raise RoutingProtocolError("Received unknown content in LinkMessage on transport %s"%(repr(t)))
291 handler = self.nmtypes.get(obj.mtype, None)
292
293 if obj.dst == self.hub.nodeId:
294 if not handler:
295
296 self.hub._log.warning("Received network message with a handler type of %s but no handler is registered."%(obj.mtype))
297 return
298 handler.handleMessage(obj.src, obj.attach)
299 else:
300 route = self.nodes.get(obj.dst, (INFINITY, None))
301 if route[0] == INFINITY:
302 self.hub._log.debug("Dropped network message to %d, no current route."%obj.dst)
303 return
304 if route[1] == t:
305 self.hub._log.error("Immediate routing loop detected, something very bad is happening!\
306 Received message for node %d from our route for it, %d"%(obj.dst, t.NodeId))
307 route[1].write(LinkMessage(LMTYPE_NETWORK, obj))
308
310 """
311 Sends a network message to the destination specified.
312 A NetMessage object is constructed from the arguments provided to this
313 method and routed appropriately.
314 @param dst: The node id of the destination
315 @param mtype: The message type (probably NMTYPE_MESSAGE)
316 @param obj: The object attachment of the NetMessage
317 """
318 msg = NetMessage(self.hub.nodeId, dst, mtype, obj)
319 route = self.nodes.get(dst, (INFINITY, None))
320 if route[0] == INFINITY:
321 raise NodeUnreachable("NodeId %s is not currently reachable"%dst, dst)
322 route[1].write(LinkMessage(LMTYPE_NETWORK, msg))
323