Alcuin 9996: Testnet and Bugfixes

- add config file location command line option
- add script and config for running local testnet
- fix timestamp range bug
- force endianness for packets
- explicitly send certain commands instead of all commands
- clean up logging somewhat
- don't use misleading .vpatch extension for patches
- add patch script to Makefile for generating a patch from the latest
commit

alcuin-9996.diff alcuin-9996.tar.gz

1
2 diff --git a/Makefile b/Makefile
3 index a548f62..7ab6ef1 100644
4 --- a/Makefile
5 +++ b/Makefile
6 @@ -12,13 +12,17 @@ dist:
7 rm -rf alcuin-$(VERSION)
8
9 clean:
10 - rm -rf genesis.vdiff genesis.vdiff.escaped alcuin-$(VERSION)
11 + rm -rf genesis.diff alcuin-$(VERSION).diff.escaped alcuin-$(VERSION)
12 rm *.tar.gz
13 + rm *.diff.escaped
14 find . -name "*.swp" -delete
15 find . -name "*.pyc" -delete
16
17 genesis:
18 - git show --pretty="format:" -1 94c3ce55693f > genesis.vdiff
19 + git show --pretty="format:" -1 94c3ce55693f > genesis.diff
20
21 -escaped-genesis:
22 - git show --pretty="format:" -1 HEAD | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&apos;/g; s/((/\&lpar;\&lpar;/g; s/))/\&rpar;\&rpar;/g; s/\[([0-9])\[/\&lsqb;$1\&lsqb;/g; s/]]/\&rsqb;\&rsqb;/g' > genesis.vdiff.escaped
23 +patch:
24 + git show --pretty="format:" -1 HEAD > alcuin-$(VERSION).diff
25 +
26 +escaped-patch:
27 + git show --pretty="format:" -1 HEAD | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&apos;/g; s/((/\&lpar;\&lpar;/g; s/))/\&rpar;\&rpar;/g; s/\[([0-9])\[/\&lsqb;$1\&lsqb;/g; s/]]/\&rsqb;\&rsqb;/g' > alcuin-$(VERSION).diff.escaped
28 diff --git a/alcuin b/alcuin
29 index 44efc5d..a46c007 100755
30 --- a/alcuin
31 +++ b/alcuin
32 @@ -20,6 +20,10 @@ def main(argv):
33 version=VERSION,
34 description="alcuin is a small and limited IRC server emulator for gossip networks.")
35 op.add_option(
36 + "-c", "--config-file-path",
37 + metavar="X",
38 + help="load the configfile from X")
39 + op.add_option(
40 "-d", "--daemon",
41 action="store_true",
42 help="fork and become a daemon")
43 @@ -89,6 +93,8 @@ def main(argv):
44 options.udp_port = 7778
45 else:
46 options.udp_port = int(options.udp_port)
47 + if options.config_file_path is None:
48 + options.config_file_path = "config.py"
49 if options.chroot:
50 if os.getuid() != 0:
51 op.error("Must be root to use --chroot")
52 diff --git a/lib/client.py b/lib/client.py
53 index 0ce0d4c..f3dc6ab 100644
54 --- a/lib/client.py
55 +++ b/lib/client.py
56 @@ -3,7 +3,6 @@ import sys
57 import re
58 import string
59 from lib.server import VERSION
60 -from lib.infosec import Infosec
61 from funcs import *
62
63 class Client(object):
64 @@ -26,7 +25,6 @@ class Client(object):
65 self.__readbuffer = ""
66 self.__writebuffer = ""
67 self.__sent_ping = False
68 - self.infosec = Infosec(self.server)
69 if self.server.password:
70 self.__handle_command = self.__pass_handler
71 else:
72 @@ -298,14 +296,17 @@ class Client(object):
73 client = server.get_client(targetname)
74
75 if client:
76 - client.message(":%s %s %s :%s"
77 - % (self.prefix, command, targetname, message))
78 + formatted_message = ":%s %s %s :%s" % (self.prefix, command, targetname, message)
79 + client.message(formatted_message)
80 + self.server.peer_broadcast(formatted_message)
81 elif server.has_channel(targetname):
82 channel = server.get_channel(targetname)
83 self.message_channel(
84 channel, command, "%s :%s" % (channel.name, message))
85 self.channel_log(channel, message)
86 else:
87 + # this isn't reliably true so let's not send the error
88 + return
89 self.reply("401 %s %s :No such nick/channel"
90 % (self.nickname, targetname))
91
92 @@ -444,17 +445,6 @@ class Client(object):
93 except KeyError:
94 self.reply("421 %s %s :Unknown command" % (self.nickname, command))
95
96 - def udp_data_received(self, address, data):
97 - if data:
98 - for peer in self.server.peers:
99 - self.server.print_debug("trying peer: %s" % peer.name)
100 - message, timestamp = self.infosec.unpack(peer, data)
101 - if(message != None):
102 - self.message(message)
103 - self.server.rebroadcast(self, peer, message, timestamp)
104 - return
105 - self.server.print_debug("Unknown peer address: %s" % address)
106 -
107 def socket_readable_notification(self):
108 try:
109 data = self.socket.recv(2 ** 10)
110 @@ -469,14 +459,11 @@ class Client(object):
111 self.__parse_read_buffer()
112 self.__timestamp = time.time()
113 self.__sent_ping = False
114 - for peer in self.server.peers:
115 - peer.send(self, data)
116 else:
117 self.disconnect(quitmsg)
118
119 def socket_writable_notification(self):
120 try:
121 - self.server.print_debug("socket_writable_notification: %s" % self.__writebuffer)
122 sent = self.socket.send(self.__writebuffer)
123 self.server.print_debug(
124 "[%s:%d] <- %r" % (
125 @@ -511,6 +498,8 @@ class Client(object):
126 for client in channel.members:
127 if client != self or include_self:
128 client.message(line)
129 + # send the channel message to peers as well
130 + self.server.peer_broadcast(line)
131
132 def channel_log(self, channel, message, meta=False):
133 if not self.server.logdir:
134 diff --git a/lib/infosec.py b/lib/infosec.py
135 index 00aa6c1..a431c2f 100644
136 --- a/lib/infosec.py
137 +++ b/lib/infosec.py
138 @@ -7,9 +7,10 @@ import time
139 import struct
140 import sys
141
142 -PACKET_SIZE = 584
143 +PACKET_SIZE = 580
144 MAX_MESSAGE_SIZE = 512
145 MAX_SECRET_SIZE = 24
146 +TS_ACCEPTABLE_SKEW = 60 * 15
147
148 class Infosec(object):
149 def __init__(self, server=None):
150 @@ -30,22 +31,27 @@ class Infosec(object):
151
152 # we want to ignore this ts if it is sent back to us
153 self.server.recent.insert(int_ts)
154 + self.server.print_debug("added %d to recent" % int_ts)
155
156 # build the packet and return it
157 - packet = struct.pack("L64s512s", int_ts, digest_bytes, ciphertext_bytes)
158 + packet = struct.pack("!L64s512s", int_ts, digest_bytes, ciphertext_bytes)
159 return packet
160
161 def unpack(self, peer, packet):
162 - int_ts, digest, ciphertext_bytes = struct.unpack("L64s512s", packet)
163 + try:
164 + int_ts, digest, ciphertext_bytes = struct.unpack("!L64s512s", packet)
165 + except:
166 + self.server.print_error("Discarding malformed packet?")
167 + return None, None
168
169 # Check the timestamp and digest
170 - current_ts = int(time.time())
171 digest_check = hashlib.sha512(peer.remote_secret + ciphertext_bytes).digest()
172 - if((current_ts - int_ts) > 60 * 15):
173 - self.server.print_debug("rejected old message")
174 +
175 + if(int_ts not in self._ts_range()):
176 + self.server.print_debug("rejected message with timestamp out of range")
177 return None, None
178 elif(self.server.recent.has(int_ts)):
179 - self.server.print_debug("rejected known message")
180 + self.server.print_debug("rejected known message: %d" % int_ts)
181 return None, None
182 elif(digest_check != digest):
183 self.server.print_debug("name: %s" % peer.name)
184 @@ -64,3 +70,7 @@ class Infosec(object):
185
186 def _pad(self, text, size):
187 return text.ljust(size)
188 +
189 + def _ts_range(self):
190 + current_ts = int(time.time())
191 + return range(current_ts - TS_ACCEPTABLE_SKEW, current_ts + TS_ACCEPTABLE_SKEW)
192 diff --git a/lib/peer.py b/lib/peer.py
193 index 34b8c60..e108328 100644
194 --- a/lib/peer.py
195 +++ b/lib/peer.py
196 @@ -1,5 +1,6 @@
197 -import socket
198 -from infosec import Infosec
199 +import socket
200 +from infosec import Infosec
201 +import sys
202
203 class Peer(object):
204 def __init__(self, server, peer_entry):
205 @@ -12,10 +13,10 @@ class Peer(object):
206 self.socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
207 self.infosec = Infosec(server)
208
209 - def send(self, client, msg, timestamp=None):
210 - if(timestamp == None):
211 - full_message = str.encode(":%s %s" % (client.nickname, msg))
212 - else:
213 + def send(self, msg, timestamp=None):
214 + try:
215 full_message = str.encode(msg)
216 - self.server.print_debug("sending formatted_msg: %s" % full_message)
217 - self.socket.sendto(self.infosec.pack(self, full_message, timestamp), (self.address, self.port))
218 + self.server.print_debug("sending message: %s" % full_message)
219 + self.socket.sendto(self.infosec.pack(self, full_message, timestamp), (self.address, self.port))
220 + except Exception as ex:
221 + print("Exception while attempting to encode message: %s" % ex)
222 diff --git a/lib/server.py b/lib/server.py
223 index ad595f7..9e8cbe5 100644
224 --- a/lib/server.py
225 +++ b/lib/server.py
226 @@ -1,4 +1,4 @@
227 -VERSION = "9997"
228 +VERSION = "9996"
229
230 import os
231 import select
232 @@ -12,10 +12,11 @@ from datetime import datetime
233 from lib.client import Client
234 from lib.channel import Channel
235 from lib.infosec import PACKET_SIZE
236 +from lib.infosec import Infosec
237 from lib.peer import Peer
238 from lib.ringbuffer import Ringbuffer
239 from funcs import *
240 -import config as cfg
241 +import imp
242
243 class Server(object):
244 def __init__(self, options):
245 @@ -30,6 +31,8 @@ class Server(object):
246 self.chroot = options.chroot
247 self.setuid = options.setuid
248 self.statedir = options.statedir
249 + self.infosec = Infosec(self)
250 + self.config_file_path = options.config_file_path
251
252 if options.listen:
253 self.address = socket.gethostbyname(options.listen)
254 @@ -42,6 +45,11 @@ class Server(object):
255 self.clients = {} # Socket --> Client instance..peers = ""
256 self.nicknames = {} # irc_lower(Nickname) --> Client instance.
257 self.peers = []
258 + if self.config_file_path:
259 + cfg = imp.load_source('config', self.config_file_path)
260 + else:
261 + cfg = imp.load_source('config', "./config.py")
262 +
263 for peer_entry in cfg.peers:
264 self.peers.append(Peer(self, peer_entry))
265 self.recent = Ringbuffer(100)
266 @@ -50,11 +58,6 @@ class Server(object):
267 if self.statedir:
268 create_directory(self.statedir)
269
270 - def rebroadcast(self, target_client, source_peer, message, timestamp):
271 - for peer in self.peers:
272 - if(peer != source_peer):
273 - peer.send(target_client, message, timestamp)
274 -
275 def daemonize(self):
276 try:
277 pid = os.fork()
278 @@ -136,6 +139,29 @@ class Server(object):
279 def remove_channel(self, channel):
280 del self.channels[irc_lower(channel.name)]
281
282 + def handle_udp_data(self, data):
283 + for peer in self.peers:
284 + message, timestamp = self.infosec.unpack(peer, data)
285 + if(message != None):
286 + self.print_debug("valid message from peer: %s" % peer.name)
287 + # send the message to all clients
288 + for c in self.clients:
289 + self.clients[c].message(message)
290 + # send the message to all other peers
291 + self.rebroadcast(peer, message, timestamp)
292 + else:
293 + self.print_debug("Unknown peer address: %s" % peer.address)
294 +
295 + def peer_broadcast(self, message):
296 + for peer in self.peers:
297 + peer.send(message)
298 +
299 + def rebroadcast(self, source_peer, message, timestamp):
300 + for peer in self.peers:
301 + if(peer != source_peer):
302 + peer.send(message, timestamp)
303 +
304 +
305 def start(self):
306 # Setup UDP first
307 udp_server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
308 @@ -175,10 +201,8 @@ class Server(object):
309 for x in inputready:
310 if x == udp_server_socket:
311 bytes_address_pair = udp_server_socket.recvfrom(PACKET_SIZE)
312 - message = bytes_address_pair[0]
313 - address = bytes_address_pair[1][0]
314 - for c in self.clients:
315 - self.clients[c].udp_data_received(address, message)
316 + data = bytes_address_pair[0]
317 + self.handle_udp_data(data)
318 for x in iwtd:
319 if x in self.clients:
320 self.clients[x].socket_readable_notification()
321 diff --git a/start_test_net.sh b/start_test_net.sh
322 new file mode 100755
323 index 0000000..9930708
324 --- /dev/null
325 +++ b/start_test_net.sh
326 @@ -0,0 +1,6 @@
327 +#!/bin/bash
328 +
329 +# start 3 servers on different ports
330 +./alcuin --debug --port 6668 --udp-port 7778 --config-file test_net_configs/a.py > logs/a &
331 +./alcuin --debug --port 6669 --udp-port 7779 --config-file test_net_configs/b.py > logs/b &
332 +./alcuin --debug --port 6670 --udp-port 7780 --config-file test_net_configs/c.py > logs/c &
333 diff --git a/test_net_configs/a.py b/test_net_configs/a.py
334 new file mode 100644
335 index 0000000..143bbd1
336 --- /dev/null
337 +++ b/test_net_configs/a.py
338 @@ -0,0 +1,16 @@
339 +peers = [
340 + {
341 + "name":"b",
342 + "local_secret":"bik7TwuenAj,",
343 + "remote_secret":"5olfobNanov~",
344 + "address":"localhost",
345 + "port":7779
346 + },
347 +# {
348 +# "name":"c",
349 +# "local_secret":"bik7TwuenAj,",
350 +# "remote_secret":"Ceat]GrucEm4",
351 +# "address":"localhost",
352 +# "port":7780
353 +# }
354 +]
355 diff --git a/test_net_configs/b.py b/test_net_configs/b.py
356 new file mode 100644
357 index 0000000..5666cd8
358 --- /dev/null
359 +++ b/test_net_configs/b.py
360 @@ -0,0 +1,16 @@
361 +peers = [
362 + {
363 + "name":"a",
364 + "local_secret":"5olfobNanov~",
365 + "remote_secret":"bik7TwuenAj,",
366 + "address":"localhost",
367 + "port":7778
368 + },
369 + {
370 + "name":"c",
371 + "local_secret":"5olfobNanov~",
372 + "remote_secret":"Ceat]GrucEm4",
373 + "address":"localhost",
374 + "port":7780
375 + }
376 +]
377 diff --git a/test_net_configs/c.py b/test_net_configs/c.py
378 new file mode 100644
379 index 0000000..5887bf0
380 --- /dev/null
381 +++ b/test_net_configs/c.py
382 @@ -0,0 +1,16 @@
383 +peers = [
384 +# {
385 +# "name":"a",
386 +# "local_secret":"Ceat]GrucEm4",
387 +# "remote_secret":"bik7TwuenAj,",
388 +# "address":"localhost",
389 +# "port":7778
390 +# },
391 + {
392 + "name":"b",
393 + "local_secret":"Ceat]GrucEm4",
394 + "remote_secret":"5olfobNanov~",
395 + "address":"localhost",
396 + "port":7779
397 + }
398 +]
399

Leave a Reply