Суть проблемы
Одним из самых страшных бичей сети ethernet являются, так называемые, петли. Они возникают когда (в основном из-за человеческого фактора) в топологии сети образуется кольцо. К примеру, два порта коммутатора соединили патч-кордом (часто бывает когда два свича заменяют на один и не глядя втыкают всё, что было) или запустили узел по новой линии, а старую отключить забыли (последствия могут быт печальными и трудно выявляемыми). В результате такой петли пакеты начинают множиться, сбиваются таблицы коммутации и начинается лавинообразный рост трафика. В таких условиях возможны зависания сетевого оборудования и полное нарушение работы сети.
Помимо настоящих петель не редки случаи когда при выгорания порта (коммутатора или сетевой карты) он начинает возвращать полученные пакеты назад в сеть, при этом чаще всего соединение согласовывается в 10M, а линк поднимается даже при отключенном кабеле. Когда в сегменте такой порт только один, последствия могут быть не столь плачевными, но всё же весьма чувствительны (особенно сильно страдают пользователи висты и семёрки). В любом случае с такими вещами нужно нещадно бороться и понимать тот факт, что намеренно или случайно создавая петлю, пусть и на небольшой период времени, можно отключить целый сегмент сети.
Матчасть
К счастью большинство современных управляемых коммутаторов, в том или ином виде, имеют функции выявления петель (loopdetect, stp), и даже более того, семейство протоколов stp позволяет специально строить кольцевую топологию (для повышения отказоустойчивости и надёжности). Но тут есть и обратная сторона медали, не редко случается так, что один сгоревший порт может оставить без связи целый район. Или скажем у того же stp перестроение топологии происходит далеко не мгновенно, связь в этот момент, естественно, оставляет желать лучшего. Кроме того, некоторые производители весьма халатно относятся к реализации протоколов обнаружения петель, скажем DES-3016 (глинк) вообще не может определить петлю если просто соединить два его порта.
Принципы выявления
Принцип обнаружения петель (loopdetect) довольно простой. В сеть отправляется специальный пакет с броадкаст адресом (предназначен всем) и если он вернулся назад, считаем, что сеть за этим интерфейсом закольцована. Дальнейшие действия зависят от типа оборудования и настроек. Чаще всего порт полностью или частично (в отдельном vlan) блокируется, событие записывается в логи, отправляются snmp-трапы. Тут в дело вступают системные администраторы и аварийная служба.
Если вся сеть управляемая, то выявить и устранить петлю довольно не сложно. Но не так уж мало сетей где к одному порту подключена цепочка из 5 — 6 неуправляемых коммутаторов. Устранение такой петли может занять немало времени и сил. Процесс поиска же сводится к последовательному отключению (включению) портов. Для определения наличия петли используется либо вышестоящий управляемый коммутатор, либо какой-нибудь снифер (wireshark, tcpdump). Первый способ весьма опасен в следствие наличия задержки между включением и выключением блокировки, в лучшем случае у пользователей просто будут лаги, а в худшем — сработает loopdetect выше по линии и отвалится уже куда больший сегмент. Во втором случае опасности для пользователей нет, но зато намного сложнее определять наличие петли (особенно в небольшом сегменте, где мало броадкаст трафика), всё-таки снифер вещь, по определению, пассивная.
Своими руками
Как было сказано выше, аппаратных реализаций поиска петель хватает с лихвой. Так что не долго думая, включаю wireshark настраиваю фильтр и смотрю, что и как делает коммутатор. Собственно всё просто: в порт отправляется пакет ethernet с адресом назначения cf:00:00:00:00:00, типом 0x9000 (CTP) и c неведомым номером функции 256 (в найденной мной документации описаны только две). Адрес назначение является броадкастовым, так что при наличии в сети петли назад должно вернутся несколько копий этого пакета.
Сперва определился с библиотеками:
- Для захвата и отправки сырых пакетов воспользуюсь библиотекой pcapy;
- С генерацией пакетов мне поможет dpkt;
- Для воспроизведения звука воспользуюсь pyaudeo и wave;
- Ну и несколько стандартных библиотек.
Далее все легко и просто. Создаю экземпляр класса pcapy.open_live c выбранным интерфейсом и добавляю к нему фильтр. Создаю первый цикл, который будет периодически отправлять пакет, а внутри него второй, что бы захватывать и обрабатывать вернувшиеся пакеты. Если захваченный пакет идентичен отправленному, то добавляется +1 к счётчику. Если после истечения тайм аута получено больше одной копии пакета, проигрывается звук, а на консоль выводится сообщение о петле.
С получившимся скриптом можно ознакомится далее.
import pcapy, dpkt , sys import time , random, socket import pyaudio , wave def packetBody(length): rez = [] for x in range(0,length): rez.append(random.choice('0123456789abcdef') + random.choice('0123456789abcdef')) return rez class loopDetector: packetCount = 0 loopCount = 0 timeout = 1 def __init__(self,iface): self.iface = iface self.pcaper = pcapy.open_live(iface,100,1,500) self.Mac = '00:19:5b:'+':'.join(packetBody(3)) self.pcaper.setfilter('ether dst cf:00:00:00:00:00 and ether src %s' % self.Mac) wf = wave.open('alarm.wav', 'rb') self.pyA = pyaudio.PyAudio() self.stream = self.pyA.open(format = self.pyA.get_format_from_width(wf.getsampwidth()), channels = wf.getnchannels(), rate = wf.getframerate(), output = True) self.wfData = wf.readframes(100000) wf.close() def __del__(self): self.stream.stop_stream() self.stream.close() self.pyA.terminate() def PlayAlarm(self): self.stream.write(self.wfData) def Capture(self,hdr,data): if data == str(self.sPkt): self.packetReceived += 1 def Process(self): while 1: try: pktData = '00000001' + ''.join(packetBody(42)) self.sPkt = dpkt.ethernet.Ethernet(dst="cf0000000000".decode('hex'), src=''.join(self.Mac.split(':')).decode('hex'), type=36864,data=pktData.decode('hex')) endTime = time.time() + self.timeout print "Send packet to %s" % self.iface self.packetCount += 1 self.pcaper.sendpacket(str(self.sPkt)) self.packetReceived = 0 while time.time() < endTime: try: self.pcaper.dispatch(-1,self.Capture) except socket.timeout: pass if self.packetReceived > 1: self.loopCount += 1 print "Loop Detected. Duplication found %s" % self.packetReceived self.PlayAlarm() except KeyboardInterrupt: break print "Packets sent: ", self.packetCount , "Loops discovered : " , self.loopCount def main(): dev_list = {} n = 0 iface = '' for x in pcapy.findalldevs(): dev_list[n] = x n += 1 try: iface = dev_list[0] except KeyError: print "No device found" exit(1) if len(sys.argv) == 2: try: if sys.argv[1] in ['list','ls','all']: for x in dev_list: print 'Index:', x, 'Device name:' ,dev_list[x] return 0 else: iface = dev_list[int(sys.argv[1])] except KeyError: print "Invalid device id, trying use first" iface = dev_list[0] ld = loopDetector(iface) ld.Process() if __name__ == "__main__": main()
