- PVSM.RU - https://www.pvsm.ru -

Получение списка интернет подключений роутера DIR-615 в читаемом виде

Я живу в большой семье, в которой много компьютеров. Потребность в выходе в сеть интернет для этих компьютеров удовлетворяет роутер компании D-link DIR-615. В web интерфейсе этого устройства есть всегда меня интересовавшая запись обо всех текущих интернет подключениях проходящих через устройство. Глядя на это громадное количество записей, меня всегда тревожило, а чем это мои домочадцы таким жутким занимаются, а не стали ли они жертвами какого либо жуткого ботнета? Все записи в этой таблице подключений оперируют лишь IP адресами компьютеров в локальной сети и в сети интернет. Отбросив этическую сторону вопроса, я расскажу в этой статье свой скрипт-киддивский способ решения проблемы преобразования списка подключений в человекочитаемый вид.

Список подключений имеет примерно следующий вид:

Получение списка интернет подключений роутера DIR 615 в читаемом виде

Конечно, можно сидеть и кропотливо копировать значение поля 'Internet' в любой сервис реверсивного DNS поиска, но это жутко бесполезное занятие. Потому сперва мы найдем способ получить список хотя бы в таком виде в своей программе. В моем роутере, в его исполнении E4 еще нет телнета как в старших моделях, и он не прошит какой либо DD-WRT для получения списка подключений каким либо приличным способом. Потому данные будем получать прямо из web интерфейса. Чтобы получить доступ к этой странице, сначала на роутере нужно авторизироваться. Посмотрим как этот процесс происходит при подключении через браузер, для этого воспользуемся плагином для браузера Mozilla Firefox под названием HTTPFox.
Нажав запись всех HTTP запросов в плагине, пробуем авторизироваться на роутере, и смотрим что получилось. Следует отметить, что роутер в локальной сети располагается по адресу 192.168.0.1.

Получение списка интернет подключений роутера DIR 615 в читаемом виде

Мы видим, что процесс авторизации представляет собой простую отправку POST запроса по адресу для login.cgi. Тело запроса в основном состоит из различных вариаций нашего логина и пароля. Только выглядят они странно, но становится понятно их новое обличие. Скриптом на странице логина они из текстовых полей элементов интерфейса кодируются в base64. Скрипт кодирования записан прямо на странице логина. Но вот незадача, функция на странице давала при кодировании пароля разные результаты с функцией urlsafe_b64encode из Python библиотеки base64.Разница была в том что страничный скрипт иногда последний символ менял с '=' на 'A'. Потому я просто выдрал и заголовки и POST Data из HTTPFox и написал функцию логина. Здесь и далее для общения с роутером по средствам протокола HTTP, будет использоваться модуль Python httplib.

def LoginRouter():
	headers = {
	'(Request-Line)':	'POST /login.cgi HTTP/1.1',
	'Host':	'192.168.0.1',
	'User-Agent':	'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0',
	'Accept':	'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
	'Accept-Language':	'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3',
	'Accept-Encoding':	'gzip, deflate',
	'Referer':	'http://192.168.0.1/login_auth.asp',
	'Connection':	'keep-alive',
	'Content-Type':	'application/x-www-form-urlencoded',
	'Content-Length': '144',
	}
	post_data = 'html_response_page=login_fail.asp&login_name=YWRtaW4A&login_pass=ZmFrZXB3ZA%3D%3D&graph_id=f1b37&login_n=admin&log_pass=ZmFrZXB3ZA%3D%3D&graph_code=&login=Login'
	conn = httplib.HTTPConnection('192.168.0.1:80')
	conn.request("POST", "/login.cgi", post_data, headers)
	response = conn.getresponse()

Никакие куки нам в ответ не приходят, так что видимо роутер запоминает нас по IP адресу, что все даже упрощает.
Далее смотря исходный текст страницы интернет подключений мы видим что все нужные данные устанавливаются элементу интерфейса с тегом input. А он еще и один на странице. Так что парсер страницы будет выглядит просто, он возьмет поле value параметров тега input. Для решения задачи, мне не хотелось устанавливать какие либо сторонние библиотеки, потому от использования LXML я осознанно отказался, понимая, что конечно так будет красивее и проще. Для парсинга веб страницы воспользуемся модулем Python HTMLParser. Использование модуля подразумевает под собой создание класса наследованного от HTMLParser с определенными в нем методами, которые будут вызываться по мере нахождения парсером определенных данных. Нам понадобятся лишь методы handle_data и handle_starttag, которые вызываются соответственно когда парсер находит данные и начало тега. Данные будут выглядеть как строка примерно такого вида: ”TCP/7767/EST/OUT/192.168.0.100/52751/64.12.30.3/5190/52751”, но мы ее с легкостью порежем в dict красивого удобного вида. Получать страницу будет простым GET запросом. Если в ответной странице находим запись вида «function redirect()», то значит мы не авторизованы на роутере.

def GetInternetSessionsPage():
	class ConnectionsParser(HTMLParser):
		def __init__(self):
			HTMLParser.__init__(self)
			self.saved_data = []
		def handle_starttag(self, tag, attrs):
			if tag == 'input':
				attr = dict(attrs)
				connections_str = attr['value']
				connections_list = connections_str.split(',')
				for connection_entry in connections_list:
					data_entry_keywords = ['Protocol', 'Time out', 'State', 'Direction', 'Local IP', 'Local Port', 'Destination IP', 'Destination Port', 'NAT']
					data_entry_values = connection_entry.split('/')
					data_entry  = dict(zip(data_entry_keywords, data_entry_values))
					self.saved_data.append(data_entry)
	conn = httplib.HTTPConnection('192.168.0.1:80')
	conn.request("GET", "/internet_sessions.asp")
	response = conn.getresponse()
	if response.status == httplib.OK:
		responce_text = response.read()
		if responce_text.find('function redirect()') >=0:
			return None
		parser = ConnectionsParser()
		parser.feed(responce_text)
		return parser.saved_data

Теперь осталось только превратить ip адрес в доменное имя. По запросу в гугле находится куча сайтов для реверсивного поиска. На первом попавшемся нахожу раздел API, и понимаю что API то платное. А вот через веб интерфейс можно пользоваться сколько угодно. Чтож, будет извращаться. Но на этом сайте все оказалось проще, искомый IP адрес просто добавляется к URL, и отображается страница результата. Подключаемся по составленному адресу, и парсим все тем же HTMLParser результат. Правда вышло несколько глупо и некрасиво, потому в парсере в методе нахождения данных, записываем следующие пять записей полей данных после строки «Resolve Host:». Ну так выглядит таблица результатов на этом сайте.

def GetHostNameByIP(address):
	class DNSParser(HTMLParser):
		NUM_DATA_TO_SAVE = 5
		def __init__(self):
			HTMLParser.__init__(self)
			self.next_data_save = 0
			self.saved_data = []
		def handle_data(self, data):
			if data.find('Resolve Host:') >= 0:
				self.next_data_save = self.NUM_DATA_TO_SAVE
			if self.next_data_save>0:
				self.saved_data.append(data)
				self.next_data_save -= 1

	conn = httplib.HTTPConnection('domaintz.com:80')
	conn.request("GET", "/tools/overview/"  + address)
	response = conn.getresponse()
	if response.status == httplib.OK:
		parser = DNSParser()
		parser.feed(response.read())
		return parser.saved_data
	else:
		return 'Error ' + str(response.status)

Следом пишем простенькую функцию которая получает список соединений, если не получила то логинится на роутер и пробует снова. А потом прогоняет каждый адрес назначений через реверсивный DNS. Прикручиваем к ней простой фильтр чтоб выводилось информация только по определенному адресу локальной сети.

def LookupRouterConnections(looking_ip = None):
	sessions = GetInternetSessionsPage()
	if sessions is None:
		LoginRouter()
		sessions = GetInternetSessionsPage()
		if sessions is None:
			print 'Cant login to router, check Login/Password'
			return
	for entry in sessions:
		if entry.has_key('Destination IP'):
			if looking_ip is not None:
				if entry.get('Local IP') != looking_ip:
					continue
			print '{0} : {1}'.format(entry['Local IP'],GetHostNameByIP(entry['Destination IP']))

Пробуем:

LookupRouterConnections('192.168.0.100')

192.168.0.100 : ['Resolve Host:', '212-36-249-250.rdtc.ru', ' (212.36.249.250)', 'IP Location:', 'Russian Federation, Novokuznetsk, Regional Digital Telecommunication Company (212.36.249.250)']
192.168.0.100 : ['Resolve Host:', 'Debian-60-squeeze-64-minimal', ' (5.9.145.232)', 'IP Location:', 'Germany, RIPE Network Coordination Center (5.9.145.232)']
192.168.0.100 : ['Resolve Host:', 'server15033.teamviewer.com', ' (178.255.155.21)', 'IP Location:', 'Italy, ANEXIA Internetdienstleistungs GmbH (178.255.155.21)']
192.168.0.100 : ['Resolve Host:', 'cm-04.lux.valve.net', ' (146.66.152.15)', 'IP Location:', 'Luxembourg, Valve Corporation (146.66.152.15)']

и так далее, очень много записей.

Использованные источники:

http://ru.wikipedia.org/wiki/HTTP [1]
http://www.python.org/doc/ [2]

Автор: LeviathanDm

Источник [3]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/25264

Ссылки в тексте:

[1] http://ru.wikipedia.org/wiki/HTTP: http://ru.wikipedia.org/wiki/HTTP

[2] http://www.python.org/doc/: http://www.python.org/doc/

[3] Источник: http://habrahabr.ru/post/166417/