root/trunk/tool/renetcolRC.py @ 150

Revision 150, 24.3 KB (checked in by andreu, 12 years ago)

DoS detection update

Line 
1
2##  File: renetcolRC.py
3 
4##  Authors: ANDREU Francois-Xavier
5 
6##  Copyright (C) 2010 GIP RENATER
7 
8
9##   This file is part of renetcol.
10 
11##   renetcol is free software; you can redistribute it and/or modify
12##   it under the terms of the GNU General Public License as published by
13##   the Free Software Foundation; either version 2 of the License, or
14##   (at your option) any later version.
15 
16##   renetcol is distributed in the hope that it will be useful,
17##   but WITHOUT ANY WARRANTY; without even the implied warranty of
18##   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19##   GNU General Public License for more details.
20 
21##   You should have received a copy of the GNU General Public License
22##   along with renetcol; if not, write to the Free Software
23##   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
24 
25
26import os
27import binascii
28import struct
29import time
30
31import operator
32import threading
33import string
34import re
35import socket
36import sys
37import traceback
38import commands
39from optparse import OptionParser
40
41########### DEFAULT VALUES, YOU MUST CHANGE IT ###############################
42IPversion = 4
43localAddr = ''
44localAddr6 = '0::'
45localPort = 2222
46collectorAddr = "127.0.0.1"
47collectorAddr6 = "0::"
48collectorPort = 52571  # if you change this port number, apply the
49                       # modification on the renetcolSender.h file
50                       # and you'll need a new compilation of the collector
51##############################################################################
52
53myInputControlThread = None
54printValue =      [ 0,1,1,0,1,0,0,1,1,0, # 0 to 9
55                    0,1,1,0,0,0,1,1,0,0, # 10...
56                    1,0,0,0,0,0,0,1,1,0, #
57                    0,0,0,0,0,0,0,0,0,0, #
58                    0,0,0,0,0,0,1,1,0,0, #
59                    0,0,0,0,0,0,0,0,0,0, #
60                    0,0,0,0,0,0,0,0,0,1, #
61                    1,1,1,1,1,1,1,1,1,1, #
62                    0,0,0,0,0,0,0,0,0,0,  # 80 to 89
63                    0,0,0,0,0,0,0,0,0,0, #
64                    0,0,0,0,0,0,0,0,0,0, #
65                    0,0,0,0,0,0,0,0,0,0, #
66                    0,0,0,0,0,0,0,0,0,0, #
67                    0,0,0,0,0,0,0,0,0,0 # 130 to 139
68                    ]
69firstParseValue = [ 0,0,0,0,0,0,0,0,0,0,
70                    0,0,0,0,0,0,0,0,0,0,
71                    0,0,0,0,0,0,0,0,0,0,
72                    0,0,0,0,0,0,0,0,0,0,
73                    0,0,0,0,0,0,0,0,0,0,
74                    0,0,0,0,0,0,0,0,0,0,
75                    0,0,0,0,0,0,0,0,0,0,
76                    0,0,0,0,0,0,0,0,0,0,
77                    0,0,0,0,0,0,0,0,0,0,
78                    0,0,0,0,0,0,0,0,0,0, #
79                    0,0,0,0,0,0,0,0,0,0, #
80                    0,0,0,0,0,0,0,0,0,0, #
81                    0,0,0,0,0,0,0,0,0,0, #
82                    0,0,0,0,0,0,0,0,0,0 # 130 to 139
83                    ]
84checkUpValues =   [ 0,0,0,0,0,0,0,0,0,0,
85                    0,0,0,0,0,0,0,0,0,0,
86                    0,0,0,0,0,0,0,0,0,0,
87                    0,0,0,0,0,0,0,0,0,0,
88                    0,0,0,0,0,0,0,0,0,0,
89                    0,0,0,0,0,0,0,0,0,0,
90                    0,0,0,0,0,0,0,0,0,0,
91                    0,0,0,0,0,0,0,0,0,0,
92                    0,0,0,0,0,0,0,0,0,0,
93                    0,0,0,0,0,0,0,0,0,0, #
94                    0,0,0,0,0,0,0,0,0,0, #
95                    0,0,0,0,0,0,0,0,0,0, #
96                    0,0,0,0,0,0,0,0,0,0, #
97                    0,0,0,0,0,0,0,0,0,0 # 130 to 139
98                    ]
99fieldsName =  [ "","IN_BYT","IN_PKTS","","PROT","TOS","TCP_FL","S_PORT","IPv4@S","S_MASK",
100                "InSNMP","D_PORT","IPv4@D","D_MASK","OutSNMP","NextHop","S_AS","D_AS","","",
101                "TTL","TTL","","","","","","IPv6@S","IPv6@D","IPv6_S_MASK",
102                "IPv6_D_MASK","","","","","","","","","",
103                "","","","","","","M_T_L_T","M_T_L_@","","",
104                "","","","","","","","","","",
105                "","","","","","","","","","ROUTER",
106                "M_L_1","M_L_2","M_L_3","M_L_4","M_L_5","M_L_6","M_L_7","M_L_8","M_L_9","M_L_10",
107                "","","","","","","","","","", # 80 to 89
108                "","","","","","","","","","",
109                "","","","","","","","","","",
110                "","","","","","","","","","",
111                "","","","","","","","","","",
112                "","","","","","","","","","" # 130 to 139
113                ]
114flowCpt = 0
115flowCptW = 0
116mask = [ 0,0,0 ]
117spaceSep = " "
118tabSep = "\t"
119freeze = 0
120record = 0
121record_file_name = ""
122record_file = None
123is_already_see = 0
124collectorRule=""
125router=0
126tpl_def = {}
127os_type = 0
128oldTpl = 0
129oldTplW = 0
130fromRouter = ""
131myTimer = None
132myCPT = 1
133rrt = 3600
134
135class InputControl(threading.Thread):
136    def kill(self, timeout):
137        self.imRunning = 0
138        time.sleep(1)
139        print "\n.\n..\n..."
140        time.sleep(1)
141        print "....\n.....\n......"       
142        time.sleep(1)
143        if self.myFlowInput:
144            self.myFlowInput.socket_close()
145        self.join(timeout)
146       
147    def __init__(self, address, port):
148        self.imRunning = 1
149        self.currentflow = None
150        threading.Thread.__init__(self)
151        self.myFlowInput = FlowInput(address, port)
152
153    def run(self):
154        global freeze, record
155        while self.imRunning:
156            if self.myFlowInput:
157                tmp = self.myFlowInput.get_flow()
158                if (tmp[0]!=None):
159                    self.currentflow = Flow(tmp)
160                    if (freeze==0) & self.currentflow.enable():
161                        self.currentflow.print_flow2()
162                    if (record==1) & self.currentflow.enable():
163                        self.currentflow.write_flow2()
164##                else:
165##                    self.myFlowInput = None
166   
167class Flow:
168    def __init__(self, data):
169        self.flow = [ data[0], data[1] ]
170
171# flow selection
172    def enable(self):
173        global firstParseValue, checkUpValues, tpl_def, fromRouter
174        res = 1
175        resS = 1
176        resD = 1
177        for i in range (0, len(tpl_def[self.flow[0]])):
178            f = tpl_def[self.flow[0]][i][0]
179            # or f==15 or f==18 or f==47
180            if ( f==8 ) and ( checkUpValues[f]==1 ):
181                resS = resS & ( ((struct.unpack('>L',(socket.inet_aton(self.flow[1][i])))[0] & mask[1-1]) == firstParseValue[f]) )
182            elif ( f==12 ) and ( checkUpValues[f]==1 ):
183                resD = resD & ( ((struct.unpack('>L',(socket.inet_aton(self.flow[1][i])))[0] & mask[1-1]) == firstParseValue[f]) )
184            elif ( i==27 or i==28 ):
185                pass
186            elif ( (f==1) and (checkUpValues[f]==1) ):
187                res = res & ( (self.flow[1][i] <= ((firstParseValue[f])+((firstParseValue[f])*5/100))) and ((self.flow[1][i] >= ((firstParseValue[f])-((firstParseValue[f])*5/100)))) )
188            elif (checkUpValues[f]==1):
189                res = res & (self.flow[1][i] == firstParseValue[f])
190        if ( checkUpValues[69]==1 ):
191            res = res & ( (fromRouter == firstParseValue[69]) )
192        res = res & (resS or resD)
193        return res
194
195# flow print
196    def print_flow2(self):
197        global printValue, tpl_def, flowCpt, fieldsName, oldTpl, os_type, fromRouter
198        myliste = []
199        myTpl = []
200        strFlow = ""
201        strField = ""
202        underscore_line = ""
203        flowCpt+=1
204        if (flowCpt%60 == 0 or oldTpl != self.flow[0]):
205##        if (flowCpt%100 == 0):
206            flowCpt = 0
207            for i in range (0, len(tpl_def[self.flow[0]])):
208                f = tpl_def[self.flow[0]][i][0]
209                if printValue[f]:
210                    if ( f==27 or f==28 or f==62 or f==63 ):
211                        strField += str(fieldsName[f])
212                        l = len(str(fieldsName[f]))
213                        if ((40-l)%8 == 0):
214                            tabNb = int((40-l)/8)
215                        else:
216                            tabNb = int((40-l)/8) + 1
217                        for j in range (0, tabNb):
218                            strField += "\t"
219                        for k in range (0, 40):
220                            underscore_line += "-"
221                    elif ( f==8 or f==15 or f==12 or f==18 or f==47 ):
222                        strField += str(fieldsName[f])
223                        l = len(str(fieldsName[f]))
224                        if ((16-l)%8 == 0):
225                            tabNb = int((16-l)/8)
226                        else:
227                            tabNb = int((16-l)/8) + 1
228                        for j in range (0, tabNb):
229                            strField += "\t"
230                        for k in range (0, 16):
231                            underscore_line += "-"
232                    else:
233                        strField += str(fieldsName[f])
234                        strField += "\t"
235                        for k in range (0, 8):
236                            underscore_line += "-"
237            if (oldTpl != self.flow[0]):
238                print " "
239            if (os_type == 0):
240                esc = '\x1b['
241                sep = ';'
242                end = 'm'
243                if (printValue[69]):
244                    rt = str(fieldsName[69])
245                    toprint = esc+"37"+sep+"44"+end+strField+rt+esc+"0"+end
246                else:
247                    toprint = esc+"37"+sep+"44"+end+strField+esc+"0"+end
248                print toprint
249            else:
250                if (printValue[69]):
251                    strField += str(fieldsName[69])
252                print strField
253                print underscore_line
254        for i in range (0, len(tpl_def[self.flow[0]])):
255            f = tpl_def[self.flow[0]][i][0]
256            if printValue[f]:
257                if ( f==27 or f==28 or f==62 or f==63 ):
258                    strFlow += str(self.flow[1][i])
259                    l = len(str(self.flow[1][i]))
260                    if ((40-l)%8 == 0):
261                        tabNb = int((40-l)/8)
262                    else:
263                        tabNb = int((40-l)/8) + 1
264                    for j in range (0, tabNb):
265                        strFlow += "\t"
266                elif ( f==8 or f==12 or f==15 or f==18 or f==47 ):
267                    strFlow += str(self.flow[1][i])
268                    l = len(str(self.flow[1][i]))
269                    if ((16-l)%8 == 0):
270                        tabNb = int((16-l)/8)
271                    else:
272                        tabNb = int((16-l)/8) + 1
273                    for j in range (0, tabNb):
274                        strFlow += "\t"
275                elif (f==21):
276                    myliste += [str(self.flow[1][i]-self.flow[1][i+1])]
277                    strFlow += str(self.flow[1][i]-self.flow[1][i+1])
278                    strFlow += "\t"
279                elif (f==22):
280                    pass
281                else:
282                    myliste += [str(self.flow[1][i])]
283                    strFlow += str(self.flow[1][i])
284                    for k in range (len(str(self.flow[1][i])), 7):
285                        strFlow += " "
286                    strFlow += "\t"
287        if (printValue[69]):
288            strFlow += fromRouter+"\t"+time.strftime("%Y/%m/%d %H:%M", time.gmtime())
289        print strFlow
290        oldTpl = self.flow[0]
291
292# flow record
293    def write_flow2(self):
294        global printValue, record_file, tpl_def, flowCptW, fieldsName, oldTplW, os_type, fromRouter
295        myliste = []
296        myTpl = []
297        strFlow = ""
298        strField = ""
299        underscore_line = ""
300        flowCptW+=1
301        if (flowCptW%6 == 0 or oldTplW != self.flow[0]):
302##        if (flowCpt%100 == 0):
303            flowCptW = 0
304            for i in range (0, len(tpl_def[self.flow[0]])):
305                f = tpl_def[self.flow[0]][i][0]
306                if printValue[f]:
307                    if ( f==27 or f==28 or f==62 or f==63 ):
308                        strField += str(fieldsName[f])
309                        l = len(str(fieldsName[f]))
310                        if ((40-l)%8 == 0):
311                            tabNb = int((40-l)/8)
312                        else:
313                            tabNb = int((40-l)/8) + 1
314                        for j in range (0, tabNb):
315                            strField += ";"
316                        for k in range (0, 40):
317                            underscore_line += "-"
318                    elif ( f==8 or f==15 or f==12 or f==18 or f==47 ):
319                        strField += str(fieldsName[f])
320                        l = len(str(fieldsName[f]))
321                        if ((16-l)%8 == 0):
322                            tabNb = int((16-l)/8)
323                        else:
324                            tabNb = int((16-l)/8) + 1
325                        for j in range (0, tabNb):
326                            strField += ";"
327                        for k in range (0, 16):
328                            underscore_line += "-"
329                    else:
330                        strField += str(fieldsName[f])
331                        strField += ";"
332                        for k in range (0, 8):
333                            underscore_line += "-"
334            if (oldTplW != self.flow[0]):
335                record_file.write("")
336            if (os_type == 0):
337                esc = '\x1b['
338                sep = ';'
339                end = 'm'
340                if (printValue[69]):
341                    rt = str(fieldsName[69])
342                    toprint = esc+"37"+sep+"44"+end+strField+rt+esc+"0"+end+"\n"
343                else:
344                    toprint = esc+"37"+sep+"44"+end+strField+esc+"0"+end+"\n"
345                record_file.write(toprint)
346            else:
347                if (printValue[69]):
348                    strField += str(fieldsName[69])
349                if (record_file!=None):
350                    record_file.write(strField)
351                    record_file.write("\n")
352                    record_file.write(underscore_line)
353                    record_file.write("\n")
354        for i in range (0, len(tpl_def[self.flow[0]])):
355            f = tpl_def[self.flow[0]][i][0]
356            if printValue[f]:
357                if (f==21):
358                    myliste += [str(self.flow[1][i]-self.flow[1][i+1])]
359                elif (f==22):
360                    pass
361                else:
362                    myliste += [str(self.flow[1][i])]
363        theflow = string.join(myliste, ";")+";"+fromRouter+";"+time.strftime("%Y%m%d%H%M", time.gmtime())+"\n"
364        if (record_file!=None):
365            record_file.write(theflow)
366            oldTplW = self.flow[0]
367       
368class FlowInput:
369    def __init__(self, h, p):
370        self.HOST = h
371        self.PORT = p
372        if (IPversion == 4):
373            self.socketnumber = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
374        elif (IPversion == 6):
375            self.socketnumber = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
376        else:
377            print "IP protocol ", IPversion ," not supported"
378            exit(0)
379        self.socketnumber.bind((self.HOST, self.PORT))
380   
381    def get_flow(self):
382        global collectorRule, tpl_def, os_type, fromRouter
383        if (self.socketnumber!=None):
384            data = self.socketnumber.recvfrom(1024)
385            flow = data[0]
386            code = struct.unpack('<H', flow[0:2])
387            if (code[0] == 1): ## a def packet
388                routerSrc = struct.unpack('<L', flow[2:6])
389                sourceId = struct.unpack('<L', flow[6:10])
390                tplId = struct.unpack('<H', flow[10:12])
391                mykeylst = []
392                mykeylst += [str(routerSrc[0])]
393                mykeylst += [str(sourceId[0])]
394                mykeylst += [str(tplId[0])]
395                mykeystr = string.join(mykeylst,"");
396                fields = []
397                fieldNb = struct.unpack('<H', flow[12:14])
398                for i in range(0, fieldNb[0]):
399                    fd = struct.unpack('<H',flow[14+(i*4):16+(i*4)])
400                    tp = struct.unpack('<H',flow[14+(2+i*4):16+(2+i*4)])
401                    fields.insert(i,(fd[0],tp[0]))
402                tpl_def[mykeystr] = fields
403                return ( None, None)
404            if (code[0]==11): ## a data packet
405                routerSrc = struct.unpack('<L', flow[4:8])
406                tmp =  struct.unpack('<L', flow[4:8])
407                fromRouter = socket.inet_ntoa(struct.pack('>L', tmp[0]))
408                sourceId = struct.unpack('<L', flow[8:12])
409                tplId = struct.unpack('<H', flow[12:14])
410                mykeylst = []
411                mykeylst += [str(routerSrc[0])]
412                mykeylst += [str(sourceId[0])]
413                mykeylst += [str(tplId[0])]
414                mykeystr = string.join(mykeylst,"")
415                if (tpl_def.has_key(mykeystr)== True):
416                    field_list = []
417                    index = 0
418                    for i in range (0, len(tpl_def[mykeystr])):
419                        if (tpl_def[mykeystr][i][1]==1):
420                            value = struct.unpack('<B',flow[14+index:15+index])
421                            field_list.insert(i,value[0])
422                        if (tpl_def[mykeystr][i][1]==2):
423                            value = struct.unpack('>H',flow[14+index:16+index])
424                            field_list.insert(i,value[0])
425                        if (tpl_def[mykeystr][i][1]==3):
426                            value = struct.unpack('<BBB',flow[14+index:17+index])
427                            valueTmp = struct.pack('<BBBB',value[2],value[1],value[0],0)
428                            valueFinal = struct.unpack('<L', valueTmp)
429                            valueFinal2 = (valueFinal[0])>>4
430                            field_list.insert(i,valueFinal2)
431                        if (tpl_def[mykeystr][i][1]==4):
432                            if (tpl_def[mykeystr][i][0]==8 or tpl_def[mykeystr][i][0]==12 or tpl_def[mykeystr][i][0]==15 or tpl_def[mykeystr][i][0]==18 or tpl_def[mykeystr][i][0]==47):
433                                value = socket.inet_ntoa(flow[14+index:18+index])
434                                field_list.insert(i,value)
435                            else:
436                                value = struct.unpack('<L', flow[14+index:18+index])
437                                value = struct.unpack('>L', flow[14+index:18+index])
438                                field_list.insert(i,value[0])
439                        if (tpl_def[mykeystr][i][1]==16):
440                            if (os_type==1):
441                                field_list.insert(i,nt_inet_ntop(flow[14+index:30+index]))
442                            else:
443                                tmp = socket.inet_ntop(socket.AF_INET6,flow[14+index:30+index])
444                                field_list.insert(i,tmp)
445                        index += tpl_def[mykeystr][i][1]
446                    return mykeystr, field_list
447##                else:
448##                  print "I'm waiting the tpl def for the key", mykeystr
449##                return (routerSrc[0], field_list)
450            return ( None, None)
451        return ( None, None)
452
453    def socket_close(self):
454        if self.socketnumber:
455           try :
456               self.socketnumber.shutdown(2)
457               self.socketnumber = None
458           except socket.error:
459              pass
460
461def print_hello(widget, event):
462    print "Hello, World!"
463
464def nt_inet_ntop(packed_ip):
465    cpt = 0
466    double = 0
467    string_ip = ""
468    tmp = binascii.hexlify(packed_ip[0:2])
469    if ( int(tmp,16)!=0 ):
470        string_ip += tmp
471        tmp = binascii.hexlify(packed_ip[2:4])
472        if ( int(tmp,16)==0 ):
473            cpt+=1
474            double = 1
475            if (cpt==1):
476                string_ip += ":"
477        else:
478            cpt=0
479            string_ip += ":"+tmp
480        tmp = binascii.hexlify(packed_ip[4:6])
481        if ( int(tmp,16)==0 ):
482            cpt+=1
483            double = 1
484            if (cpt==1):
485                string_ip += ":"
486        else:
487            cpt=0
488            string_ip += ":"+tmp
489        tmp = binascii.hexlify(packed_ip[6:8])
490        if ( int(tmp,16)==0 ):
491            if (double==1 and cpt==0):
492                string_ip += ":0"
493            else:
494                cpt+=1
495                double = 1
496                if (cpt==1):
497                    string_ip += ":"
498        else:
499            cpt=0
500            string_ip += ":"+tmp
501        tmp = binascii.hexlify(packed_ip[8:10])
502        if ( int(tmp,16)==0 ):
503            if (double==1 and cpt==0):
504                string_ip += ":0"
505            else:
506                cpt+=1
507                double = 1
508                if (cpt==1):
509                    string_ip += ":"
510        else:
511            cpt=0
512            string_ip += ":"+tmp
513        tmp = binascii.hexlify(packed_ip[10:12])
514        if ( int(tmp,16)==0 ):
515            if (double==1 and cpt==0):
516                string_ip += ":0"
517            else:
518                cpt+=1
519                double = 1
520                if (cpt==1):
521                    string_ip += ":"
522        else:
523            cpt=0
524            string_ip += ":"+tmp
525        tmp = binascii.hexlify(packed_ip[12:14])
526        if ( int(tmp,16)==0 ):
527            if (double==1 and cpt==0):
528                string_ip += ":0"
529            else:
530                cpt+=1
531                double = 1
532                if (cpt==1):
533                    string_ip += ":"
534        else:
535            cpt=0
536            string_ip += ":"+tmp
537        tmp = binascii.hexlify(packed_ip[14:16])
538        if ( int(tmp,16) != 0 ):
539            string_ip += ":"+tmp
540        else:
541            string_ip += ":"
542    else:
543        string_ip = "::"
544    return string_ip
545
546def timer_action():
547    global record_file, record_file_name, myTimer, myCPT, rrt
548    myCPT+=1
549    myTimer.cancel()
550    my_new_file_name = "%s_%s.txt" % (record_file_name,time.strftime("%Y%m%d%H%M", time.gmtime()))
551    ##my_new_file_name = "%s_%d" % (record_file_name,myCPT)
552    print my_new_file_name
553    new_record_file = file(my_new_file_name, 'a+')
554    old_record_file = record_file
555    record_file = new_record_file
556    old_record_file.close()
557    now2 = time.time()
558    reste = rrt-(operator.mod(now2,rrt))
559    myTimer = threading.Timer(rrt,timer_action)
560    myTimer.start()
561
562def RotRec():
563    global record, myTimer, rrt
564
565if __name__ == "__main__":
566    global comboRouter, os_type, printShit
567    global localAddr, localPort
568    global rrt, record, record_file_name
569
570    print ""
571    print ""
572    print ""
573 
574    print "----------------------------------------------------------------------------"
575    print " renetcolRC is part of renetcol "
576    print " This module writed in python is a remote client which can received flows "
577    print " information from renetcol. It was built from renetcolGUI source without "
578    print " the gtk interface and without the interaction with renetcol."
579    print " To receive flows from renetcol the filter with the field number 69 MUST be"
580    print " actived on renetcol (in your file rules.txt)"
581    print "----------------------------------------------------------------------------"
582   
583    if (os.name =="nt"):
584        os_type = 1
585    elif (os.name =="posix"):
586        os_type = 0
587    else:
588        os_type = 3
589
590    parser = OptionParser()
591    parser.add_option("-o", "--output", dest="out", help ="Output filename prefix, ex: \"/tmp/myrecord\", timestamp will be add to the filename")
592    parser.add_option("-t", "--time", dest="tim", help ="Round robin interval (in seconde), default is \"3600\" for 1h")
593    (options, args) = parser.parse_args()
594    outputFileName = options.out
595    rrt = int(options.tim)
596
597    if (outputFileName!=None):
598        record = 1
599        record_file_name = outputFileName
600        record_file = file(record_file_name, 'a+')
601        RotRec()
602    else:
603        record = 0
604
605    print ""
606    print ""
607    print ""
608    print " ---------------------------------------"
609    print "  STARTING RECORD ROTATION"
610    print "  with following parameters:"
611    print "  output filename prefix :",outputFileName
612    print "  interval: ",rrt, " seconds"
613    print " ---------------------------------------"
614    now2 = time.time()
615    reste = rrt-(operator.mod(now2,rrt))
616    myTimer = threading.Timer(rrt,timer_action)
617    myTimer.start()
618    ##myTimer.cancel()
619    ##print "STOPPING RECORD ROTATION"
620
621    myInputControlThread = InputControl(localAddr, localPort)
622    myInputControlThread.start()
623       
Note: See TracBrowser for help on using the browser.