Pull to refresh

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

Reading time 5 min
Views 95K
Суть проблемы

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


Ссылка на оригинал и исходники
Tags:
Hubs:
+37
Comments 72
Comments Comments 72

Articles