Как стать автором
Обновить

Loopdetect своими руками

Время на прочтение5 мин
Количество просмотров99K
Суть проблемы

Одним из самых страшных бичей сети 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()


Ссылка на оригинал и исходники
Теги:
Хабы:
Всего голосов 45: ↑41 и ↓4+37
Комментарии72

Публикации

Истории

Ближайшие события