added feature to import traces from XML template, fixed some LFT subprocess errors on Unix based systems, more clean non-debug mode, added 'localhost' filter to target parser

This commit is contained in:
psy 2013-10-03 16:01:19 -07:00
parent 1c8a3f799e
commit a6c3115ca6
6 changed files with 172 additions and 72 deletions

View File

@ -2,9 +2,19 @@
Changelog: Border-Check Changelog: Border-Check
============================== ==============================
=================
October 4, 2013:
=================
- added feature to import traces from XML template
- fixed some LFT subprocess errors on Unix based systems
- more clean non-debug mode
- added 'localhost' filter to target parser
================= =================
September 27, 2013: September 27, 2013:
================= =================
- Public release: Border Check v0.1 - public release: Border Check v0.1

View File

@ -30,7 +30,11 @@ BC runs on OSx and Unix systems. It requires the following libraries/dependencie
sudo apt-get install python-pip sudo apt-get install python-pip
and then run pip install lxml and pip install pygeoip and then run:
pip install lxml
pip install pygeoip
- biplist: - biplist:
https://pypi.python.org/pypi/biplist/0.5 https://pypi.python.org/pypi/biplist/0.5

View File

@ -22,6 +22,7 @@ Options:
-h, --help show this help message and exit -h, --help show this help message and exit
-d, --debug debug mode -d, --debug debug mode
--xml=EXPORT_XML export traces to xml (ex: --xml foo.xml) --xml=EXPORT_XML export traces to xml (ex: --xml foo.xml)
--load=IMPORT_XML import traces to show (ex: --load bar.xml)
--bh=BROWSER_HISTORY set browser's history path --bh=BROWSER_HISTORY set browser's history path
-b BROWSER set browser manually: F = Firefox / C = Chrome / S = Safari / Ch = Chromium -b BROWSER set browser manually: F = Firefox / C = Chrome / S = Safari / Ch = Chromium
@ -64,4 +65,8 @@ $ python bc --bh "Library/Safari/History.plist"
$ python bc --bh "Library/Safari/History.plist" $ python bc --bh "Library/Safari/History.plist"
------------------- -------------------
* Import 'traces' from xml:
$ python bc --load "mytravel.xml"
-------------------

183
main.py
View File

@ -23,9 +23,6 @@ from webserver import BorderCheckWebserver
from xml_exporter import xml_reporting from xml_exporter import xml_reporting
import webbrowser import webbrowser
# set to emit debug messages about errors (0 = off).
DEBUG = 1
class bc(object): class bc(object):
""" """
BC main Class BC main Class
@ -96,17 +93,21 @@ class bc(object):
try: try:
return func(*args) return func(*args)
except Exception as e: except Exception as e:
print("\n[Error] - Something wrong fetching urls. Aborting..."), "\n" if not options.debug:
if DEBUG: print("[Error] - Something wrong happens!. Try to run again with --debug option to see more detailed information about the error on a Traceback output."), "\n"
else:
print("[Error] - Something wrong happens!. You have the reason at the end of the Traceback. If you don't understand what's happen, try to contact with project contributors."), "\n"
if options.debug == 1:
traceback.print_exc() traceback.print_exc()
sys.exit(2) print "" # \n after traceback ouput
sys.exit(2)
def check_root(self): def check_root(self):
""" """
Check root permissions Check root permissions
""" """
if not os.geteuid()==0: if not os.geteuid()==0:
sys.exit("\nOnly root can run this script...\n") sys.exit("Warning: Only root can launch traceroutes. (Try: 'sudo ./bc')\n")
def check_browser(self): def check_browser(self):
""" """
@ -384,7 +385,10 @@ class bc(object):
print "Can't get Safari version information, you'll have to look it up manually \n" print "Can't get Safari version information, you'll have to look it up manually \n"
else: else:
print "Version:", self.browser_version print "Version:", self.browser_version
print "History:", self.browser_history_path, "\n" if self.options.import_xml: # history not needed on xml importing
pass
else:
print "History:", self.browser_history_path, "\n"
def getURL(self): def getURL(self):
""" """
@ -429,6 +433,9 @@ class bc(object):
""" """
Run an LFT Run an LFT
""" """
# LFT needs root
root = self.try_running(self.check_root, "\nInternal error checking root permissions.")
#try: #try:
if self.operating_system == 'darwin': if self.operating_system == 'darwin':
try: try:
@ -438,19 +445,19 @@ class bc(object):
self.content = a.stdout.read() self.content = a.stdout.read()
if self.operating_system == 'linux': if self.operating_system == 'linux':
if self.method == '-e': if self.method == '-e': # tcp probes
self.method = '-E' self.method = '-E'
try: try:
self.content = subprocess.check_output(['lft', '-S', '-n', self.destination_ip]) self.content = subprocess.check_output(['lft', '-S', '-n', self.method, self.destination_ip])
# support for older python versions (<2.75) that don't support subprocess.check_output # support for older python versions (<2.75) that don't support subprocess.check_output
except: except:
a = subprocess.Popen(['lft', '-S', '-n', self.destination_ip], stdout=subprocess.PIPE) a = subprocess.Popen(['lft', '-S', '-n', self.method, self.destination_ip], stdout=subprocess.PIPE)
self.content = a.stdout.read() self.content = a.stdout.read()
self.attempts += 1 self.attempts += 1
if self.options.debug == True: if self.options.debug == True:
print "Tracing:", self.destination_ip, "with method:", self.method, 'attempt:', self.attempts, '\n' print "Tracing:", self.destination_ip, "with method:", self.method, 'attempt:', self.attempts, '\n'
self.lft_parse() self.lft_parse()
def lft_parse(self): def lft_parse(self):
""" """
Parse the lft to see if it produced any results, if not, run another LFT using a different method Parse the lft to see if it produced any results, if not, run another LFT using a different method
@ -524,7 +531,7 @@ class bc(object):
for ip in line: for ip in line:
if re.match(r'\d{1,4}\.\dms$', ip): if re.match(r'\d{1,4}\.\dms$', ip):
self.timestamp = ip.replace('ms', '') self.timestamp = ip.replace('ms', '')
if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', ip): if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', ip) or re.match('localhost', ip):
pass pass
else: else:
if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$",ip): if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$",ip):
@ -639,6 +646,45 @@ class bc(object):
os.remove('GeoIPASNum.gz') os.remove('GeoIPASNum.gz')
print "Database: GeoIPASNum \n" print "Database: GeoIPASNum \n"
def importXML(self):
"""
Import travels data directly from XML file (no root needed) and launch a web browser on a thread with a map showing them.
"""
try:
xml_results = xml_reporting(self)
xml_imported = xml_results.read_xml_results() # read xml directly from file
except:
print("[Error] - Something wrong importing data from XML file. Aborting..."), "\n"
sys.exit(2)
# Set the maxmind geo databases
self.geoip = pygeoip.GeoIP('GeoLiteCity.dat')
self.geoasn = pygeoip.GeoIP('GeoIPASNum.dat')
match_ip = xml_imported[0].strip('http://').strip(':8080')
#regex for filtering local network IPs
if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', match_ip) or match_ip.startswith('file://') or match_ip.startswith('localhost'):
print '='*45 + "\n", "Target:\n" + '='*45 + "\n"
print "URL:", self.options.import_xml, "\n"
print "Warning: This target is not valid!.\n"
sys.exit(2)
else:
if xml_imported[0].startswith('file://'):
print '='*45 + "\n", "Target:\n" + '='*45 + "\n"
print "URL:", self.options.import_xml, "\n"
print "Warning: This target is not valid!.\n"
sys.exit(2)
else:
print '='*45 + "\n", "Target:\n" + '='*45 + "\n"
print "URL:", self.options.import_xml, "\n"
print "Host:", xml_imported[0], "\n"
os.system('cp -r ' + self.options.import_xml + ' data.xml') # copy XML data provided by user to data.xml template
# start web mode (on a different thread)
try:
webbrowser.open('http://127.0.0.1:8080', new=1)
BorderCheckWebserver(self)
except (KeyboardInterrupt, SystemExit):
sys.exit()
def run(self, opts=None): def run(self, opts=None):
""" """
Run BorderCheck Run BorderCheck
@ -653,77 +699,78 @@ class bc(object):
print('='*75) print('='*75)
print(str(p.version)) print(str(p.version))
print('='*75) print('='*75)
# root checker
root = self.try_running(self.check_root, "\nInternal error checking root permissions.")
# extract browser type and path # extract browser type and path
browser = self.try_running(self.check_browser, "\nInternal error checking browser files path.") browser = self.try_running(self.check_browser, "\nInternal error checking browser files path.")
# extract url # extract url
url = self.try_running(self.getURL, "\nInternal error getting urls from browser's database.") url = self.try_running(self.getURL, "\nInternal error getting urls from browser's database.")
# set geoip database # set geoip database
geo = self.try_running(self.getGEO, "\nInternal error setting geoIP database.") geo = self.try_running(self.getGEO, "\nInternal error setting geoIP database.")
# run traceroutes # read from XML or run traceroutes + stay latent mode
match_ip = self.url[0].strip('http://').strip(':8080') if options.import_xml:
#regex for filtering local network IPs import_xml = self.try_running(self.importXML, "\nInternal error importing XML data from file.")
if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', match_ip) or match_ip.startswith('file://'):
print '='*45 + "\n", "Target:\n" + '='*45 + "\n"
print "URL:", self.url[0], "\n"
print "Warning: This target is not valid!.\n"
pass
else: else:
if self.url[0].startswith('file://'): match_ip = self.url[0].strip('http://').strip(':8080')
#regex for filtering local network IPs
if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', match_ip) or match_ip.startswith('file://') or match_ip.startswith('localhost'):
print '='*45 + "\n", "Target:\n" + '='*45 + "\n" print '='*45 + "\n", "Target:\n" + '='*45 + "\n"
print "URL:", self.url[0], "\n" print "URL:", self.url[0], "\n"
print "Warning: This target is not valid!.\n" print "Warning: This target is not valid!.\n"
pass pass
else: else:
traces = self.try_running(self.traces, "\nInternal error tracerouting.") if self.url[0].startswith('file://'):
# start web mode (on a different thread) print '='*45 + "\n", "Target:\n" + '='*45 + "\n"
try: print "URL:", self.url[0], "\n"
t = threading.Thread(target=BorderCheckWebserver, args=(self, )) print "Warning: This target is not valid!.\n"
t.daemon = True
t.start()
time.sleep(2)
except (KeyboardInterrupt, SystemExit):
t.join()
sys.exit()
# open same browser of history access on a new tab
try:
webbrowser.open('http://127.0.0.1:8080', new=1)
except:
print "Error: Browser is not responding correctly.\n"
print('='*75)
print(str(p.version))
print('='*75 + "\n")
print "Status: Waiting for new urls ...\n"
print "Type 'Control+C' to exit.\n"
# stay latent waiting for new urls
while True:
url = urlparse(self.getURL()).netloc
#url = url.replace('www.','')
try:
match_ip = url.strip('http://').strip(':8080')
except:
print '='*45 + "\n", "Target:\n" + '='*45 + "\n"
print "URL:", self.url[0], "\n"
pass
if url != self.old_url:
if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', match_ip):
pass pass
else: else:
if self.url[0].startswith('file://'): traces = self.try_running(self.traces, "\nInternal error tracerouting.")
# start web mode (on a different thread)
try:
t = threading.Thread(target=BorderCheckWebserver, args=(self, ))
t.daemon = True
t.start()
time.sleep(2)
except (KeyboardInterrupt, SystemExit):
t.join()
sys.exit()
# open same browser of history access on a new tab
try:
webbrowser.open('http://127.0.0.1:8080', new=1)
except:
print "Error: Browser is not responding correctly.\n"
print('='*75)
print(str(p.version))
print('='*75 + "\n")
print "Status: Waiting for new urls ...\n"
print "Type 'Control+C' to exit.\n"
# stay latent waiting for new urls
while True:
url = urlparse(self.getURL()).netloc
#url = url.replace('www.','')
try:
match_ip = url.strip('http://').strip(':8080')
except:
print '='*45 + "\n", "Target:\n" + '='*45 + "\n"
print "URL:", self.url[0], "\n"
pass
if url != self.old_url:
if re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}$', match_ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$', match_ip) or match_ip.startswith('localhost'):
pass pass
else: else:
if os.path.exists('data.xml'): # removing xml data to has a new map each time that bc is launched if self.url[0].startswith('file://'):
os.remove('data.xml') pass
open('data.xml', 'w') # starting a new xml data container in write mode else:
traces = self.try_running(self.traces, "\nInternal error tracerouting.") if os.path.exists('data.xml'): # removing xml data to has a new map each time that bc is launched
# open same browser of history access on a new tab os.remove('data.xml')
# try: open('data.xml', 'w') # starting a new xml data container in write mode
# webbrowser.open('http://127.0.0.1:8080', new=2) # open on same tab? traces = self.try_running(self.traces, "\nInternal error tracerouting.")
# except: # open same browser of history access on a new tab
# print "Error: Browser is not responding correctly.\n" # try:
time.sleep(5) # free process time or goodbye :-) # webbrowser.open('http://127.0.0.1:8080', new=2) # open on same tab?
# except:
# print "Error: Browser is not responding correctly.\n"
time.sleep(5) # free process time or goodbye :-)
if __name__ == "__main__": if __name__ == "__main__":
app = bc() app = bc()

View File

@ -15,6 +15,7 @@ class BCOptions(optparse.OptionParser):
self.add_option("-d", "--debug", action="store_true", dest="debug", help="debug mode") self.add_option("-d", "--debug", action="store_true", dest="debug", help="debug mode")
self.add_option("--xml", action="store", dest="export_xml", help="export traces to xml (ex: --xml foo.xml)") self.add_option("--xml", action="store", dest="export_xml", help="export traces to xml (ex: --xml foo.xml)")
self.add_option("--load", action="store", dest="import_xml", help="import traces to show (ex: --load bar.xml)")
self.add_option("--bh", action="store", dest="browser_history", help="set browser's history path") self.add_option("--bh", action="store", dest="browser_history", help="set browser's history path")
self.add_option("-b", action="store", dest="browser", help="set browser manually: F = Firefox / C = Chrome / S = Safari / Ch = Chromium") self.add_option("-b", action="store", dest="browser", help="set browser manually: F = Firefox / C = Chrome / S = Safari / Ch = Chromium")
#self.add_option("--proxy", action="store", dest="proxy", help="set proxy server") #self.add_option("--proxy", action="store", dest="proxy", help="set proxy server")

View File

@ -4,8 +4,6 @@
BC (Border-Check) is a tool to retrieve info of traceroute tests over website navigation routes. BC (Border-Check) is a tool to retrieve info of traceroute tests over website navigation routes.
GPLv3 - 2013 by psy (epsylon@riseup.net) GPLv3 - 2013 by psy (epsylon@riseup.net)
""" """
import xml.etree.ElementTree as ET
class xml_reporting(object): class xml_reporting(object):
""" """
Print results from a traceroute in an XML fashion Print results from a traceroute in an XML fashion
@ -15,6 +13,7 @@ class xml_reporting(object):
self.instance = bc self.instance = bc
def print_xml_results(self, filename): def print_xml_results(self, filename):
import xml.etree.ElementTree as ET
root = ET.Element("travel") root = ET.Element("travel")
i = 1 i = 1
for i in self.instance.result_list: for i in self.instance.result_list:
@ -48,3 +47,37 @@ class xml_reporting(object):
tree = ET.ElementTree(root) tree = ET.ElementTree(root)
tree.write(filename) tree.write(filename)
def read_xml_results(self):
from xml.dom.minidom import parseString
file = open(self.instance.options.import_xml,'r')
data = file.read()
file.close()
dom = parseString(data)
travel_tag = dom.getElementsByTagName('travel')[0].toxml()
travel_data = travel_tag.replace('<travel>','').replace('</travel>','').split('<')[0]
hop_tag = dom.getElementsByTagName('hop')[0].toxml()
hop_data = hop_tag.replace('<hop>','').replace('</hop>','')
host_tag = dom.getElementsByTagName('host')[0].toxml()
host_data = host_tag.replace('<host>','').replace('</host>','')
hop_ip_tag = dom.getElementsByTagName('hop_ip')[0].toxml()
hop_ip_data = hop_ip_tag.replace('<hop_ip>','').replace('</hop_ip>','')
longitude_tag = dom.getElementsByTagName('longitude')[0].toxml()
longitude_data = longitude_tag.replace('<longitude>','').replace('</longitude>','')
latitude_tag = dom.getElementsByTagName('latitude')[0].toxml()
latitude_data = hop_tag.replace('<latitude>','').replace('</latitude>','')
city_tag = dom.getElementsByTagName('city')[0].toxml()
city_data = city_tag.replace('<city>','').replace('</city>','')
country_tag = dom.getElementsByTagName('country')[0].toxml()
country_data = country_tag.replace('<country>','').replace('</country>','')
server_name_tag = dom.getElementsByTagName('server_name')[0].toxml()
server_name_data = server_name_tag.replace('<server_name>','').replace('</server_name>','')
asn_tag = dom.getElementsByTagName('asn')[0].toxml()
asn_data = asn_tag.replace('<asn>','').replace('</asn>','')
timestamp_tag = dom.getElementsByTagName('timestamp')[0].toxml()
timestamp_data = timestamp_tag.replace('<timestamp>','').replace('</timestamp>','')
country_code_tag = dom.getElementsByTagName('country_code')[0].toxml()
country_code_data = country_code_tag.replace('<country_code>','').replace('</country_code>','')
meta_tag = dom.getElementsByTagName('meta')[0].toxml()
meta_data = meta_tag.replace('<meta>','').replace('</meta>','')
return travel_data, hop_data, hop_ip_data, longitude_data, latitude_data, city_data, country_data, server_name_data, asn_data, timestamp_data, country_code_data, meta_data