Суть проблемы
Одним из самых страшных бичей сети 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()
Ссылка на оригинал и исходники