Arhn - архитектура программирования

Сниффер/монитор в конфигурации клиент/сервер с ZMQ в Python

Я реализовал клиент/сервер через ZeroMQ и хотел бы добавить сниффер/монитор для захвата связи между ними.

              client <---------> server
              (REQ)       |         (REP)
                          | 
                          |
                          v
                        sniffer  <-this is what I want to add

Если клиент/сервер взаимодействует через сокет 5555, скажем, как я могу добавить сниффер для прослушивания того же сокета? Есть ли способ отличить, какое сообщение от клиента, а какое от сервера? Может кто поделиться опытом?

Отредактировано в соответствии с ответом Яна

Конфигурация станет такой:

client [REQ]----->[ROUTER:4444] monitor [DEALER]------->[REP:5555] server
                              [PUB:7777]
                                  ^
                                  |
                                  |
                                  |
                                  | 
                                [SUB] 
                            monitorclient(sniffer)  <-this is what I want to add

Стрелки показывают направление подключения (направление к связанному порту).

Сообщения идут:

  • client -> monitor -> server -> monitor -> client
  • а также monitor -> monitorclient

Более красивая картинка здесь.

27.05.2014

  • Что за розетка? В Pub/Sub это тривиально. 27.05.2014
  • REQ/REP... Я обновил схему. 27.05.2014
  • Вам нужно решение для периодической отладки или для постоянного ведения журнала? 27.05.2014
  • случайная отладка, скажем так. Чтобы быть более точным, клиент имитирует скрипт, который отправляет команды оборудованию (в данном случае имитируется сервером), а сниффер захватывает и анализирует сообщения. Я мог бы провести анализ в клиенте, но не хочу замедлять общение. Другим решением было бы пересылать сообщения клиентом на сниффер, но я теряю сообщения, отправленные клиентом на сервер. 27.05.2014

Ответы:


1

Для сниффинга нам понадобится какая-то промежуточная часть.

zmq предлагает несколько вариантов

  • напишите свою собственную программу, принимая запросы на одной стороне, отправляя их, получая ответ, отправляя исходному запрашивающему и сообщая об этом трафике вам
  • используйте zmq.proxy — однако для этого требуется последняя версия libzmq (zmq.zmq_version_info() >= 3), которая в настоящее время даже недоступна в моей Ubuntu 14.04, поэтому я пропускаю это.
  • используйте MonitoredQueue — это то, что вам, вероятно, нужно. Это обеспечивает цикл обмена сообщениями между внешним и внутренним интерфейсом, а также публикацию/проталкивание/отправку их в другой сокет.

План

Это решение основано на примере MonitoredQueue из pyzmq doc< /а>

Сервер привязан к порту 5555

Сервер будет привязан к порту 5555. В отличие от других примеров, я оставлю ваш сервер фиксированной частью и не буду менять его при подключении к MontitoredQueue. Однако такой обмен не является проблемой и не создаст никаких проблем (при условии, что вы правильно настроите MonitoredQueue).

MonitoredQueue привязан к порту 4444, подключен к порту 5555, публикует трафик на порту 7777

MonitoredQueue находится между клиентом и сервером. Он прослушивает порт 4444, отправляет запросы на сервер и отвечает клиенту. При этом любое проходящее сообщение будет опубликовано с соответствующим префиксом «in» или «out» на сокете PUB. Позже мы увидим, что они будут содержать не только префикс и запрос/ответ, но и личность клиента.

Клиент подключается к порту 4444

Клиент мог напрямую подключаться к серверу по порту 5555, но это не позволяло нам перехватывать трафик. По этой причине мы подключим клиента к порту 4444, где находится MonitoredQueue, ожидающий сервера и прослушивания.

Вы увидите, что клиенту и серверу не придется менять строчку кода для участия в этом обмене.

Реальный код

server.py

В нашем случае сервер ожидает строку, которую можно преобразовать в целое число, и возвращает строку с удвоенным значением.

import zmq

def double_server(server_url="tcp://*:5555"):
    context = zmq.Context()
    socket = context.socket(zmq.REP)
    socket.bind(server_url)
    print "server started..."
    while True:
        req = socket.recv()
        print "server received request", req
        result = str(2*int(req))
        socket.send(result)
        print "server replied with", result

if __name__ == "__main__":
    double_server()

client.py

Наш клиент попытается 5 раз запросить какой-либо результат на порту 4444 на локальном хосте.

import zmq

def client(server_url="tcp://localhost:4444"):
    context = zmq.Context()
    socket = context.socket(zmq.REQ)
    # socket.setsockopt(zmq.IDENTITY, "client_id_abc") # see Conclusions
    socket.connect(server_url)

    for i in range(5):
        print "request", i
        socket.send(str(i))
        res = socket.recv()
        print i, "result: ", res

if __name__ == "__main__":
    client()

Вы можете попробовать прямо сейчас подключиться к порту 5555, чтобы убедиться, что он работает, но для нашего прослушивания он должен общаться с MonitoredQueue.

monitor.py

Вот и вся магия. pyzmq уже предоставляет устройство MonitoredQueue, поэтому мы можем просто взять его и использовать.

import zmq
from zmq.devices.monitoredqueuedevice import MonitoredQueue
from zmq.utils.strtypes import asbytes

def monitoredqueue(frontend_url="tcp://*:4444", server_url="tcp://localhost:5555", capture_url="tcp://*:7777"):
    mondev = MonitoredQueue(zmq.ROUTER, zmq.DEALER, zmq.PUB, asbytes("in"), asbytes("out"))
    mondev.bind_in(frontend_url)
    mondev.connect_out(server_url)
    mondev.bind_mon(capture_url)
    mondev.setsockopt_in(zmq.HWM, 1)
    mondev.start()
    print "monitored queue started"

if __name__ == "__main__":
    monitoredqueue()

Примечание о типах сокетов и псевдонимах:

  • zmq.ROUTER раньше назывался zmq.XREP
  • zmq.DEALER раньше назывался zmq.XREQ
  • эти псевдонимы все еще работают.

MonitoredQueue будет публиковать каждое сообщение, проходящее через сокет zmq.PUB на порту 7777. Эти сообщения будут иметь префикс «in» и «out», а также будут содержать один фрейм со строкой идентификатора. Эта идентификационная строка назначается сокетом ROUTER и во время обмена уникальна для всех подключенных клиентов. Эта идентификация является частью так называемого конверта и представляет собой сообщение запроса/ответа, ограниченное пустым фреймом (как скоро будет видно).

monitorclient.py

Этот клиент монитора здесь только для того, чтобы показать, как получить перехваченную информацию.

Он подписывается на порт 7777, обслуживаемый монитором (MonitoredQueue), и распечатывает его. Важно использовать составное сообщение, иначе мы упустим часть информации.

import zmq

def monitorclient(server_url="tcp://localhost:7777"):
    context = zmq.Context()
    socket = context.socket(zmq.SUB)
    socket.connect(server_url)
    socket.setsockopt(zmq.SUBSCRIBE, "")
    print "started monitoring client"

    while True:
        res = socket.recv_multipart()
        print res

if __name__ == "__main__":
    monitorclient()

Запустить его

Нам понадобится 4 открытые консоли, в каждой мы запустим по одному python скрипту

Сначала запустите сервер:

$ python server.py

Начать мониторинг очереди

$ python monitor.py

Запустить клиент, чтение перехваченных сообщений

$ python monitorclient.py

Наконец, запустите клиент, пытающийся получить ответ от сервера, проксируемого MonitoredQueue.

$ python client.py
request 0
0 result:  0
request 1
1 result:  2
request 2
2 result:  4
request 3
3 result:  6
request 4
4 result:  8

Результаты соответствуют ожиданиям.

Теперь проверьте вывод server.py:

$ python server.py
server received request 0
server replied with 0
server received request 1
server replied with 2
server received request 2
server replied with 4
server received request 3
server replied with 6
server received request 4
server replied with 8

Ничего удивительного, все идет хорошо.

Наш monitor.py ничего не выводит, нам нужно проверить вывод из monitorclient.py

$ python monitorclient.py 
started monitoring client
['in', '\x00\xc4\x84\x1c\xf2\xc2.@\xd3\x86cN\x0e\x06\x7f\xaf\x0b', '', '0']
['out', '\x00\xc4\x84\x1c\xf2\xc2.@\xd3\x86cN\x0e\x06\x7f\xaf\x0b', '', '0']
['in', '\x00\xc4\x84\x1c\xf2\xc2.@\xd3\x86cN\x0e\x06\x7f\xaf\x0b', '', '1']
['out', '\x00\xc4\x84\x1c\xf2\xc2.@\xd3\x86cN\x0e\x06\x7f\xaf\x0b', '', '2']
['in', '\x00\xc4\x84\x1c\xf2\xc2.@\xd3\x86cN\x0e\x06\x7f\xaf\x0b', '', '2']
['out', '\x00\xc4\x84\x1c\xf2\xc2.@\xd3\x86cN\x0e\x06\x7f\xaf\x0b', '', '4']
['in', '\x00\xc4\x84\x1c\xf2\xc2.@\xd3\x86cN\x0e\x06\x7f\xaf\x0b', '', '3']
['out', '\x00\xc4\x84\x1c\xf2\xc2.@\xd3\x86cN\x0e\x06\x7f\xaf\x0b', '', '6']
['in', '\x00\xc4\x84\x1c\xf2\xc2.@\xd3\x86cN\x0e\x06\x7f\xaf\x0b', '', '4']
['out', '\x00\xc4\x84\x1c\xf2\xc2.@\xd3\x86cN\x0e\x06\x7f\xaf\x0b', '', '8']

Здесь вы видите распечатку всех 10 сообщений, 5 запросов, 5 ответов.

Каждый имеет структуру [prefix, identity, emptyframe, message], где

  • prefix либо "входит", либо "выходит"
  • identity — это строка, назначенная конкретному клиенту MonitoredQueues. Каждый раз, когда клиент подключается, это удостоверение может меняться. В качестве бонуса мы можем подключить несколько клиентов и при этом иметь возможность различать разные клиенты. Если вам нужны определенные идентификаторы клиентов, см. строку с комментариями в client.py с socket.setsockopt(zmq.IDENTITY, "client_id_abc"). Если вы раскомментируете его, вы увидите "client_id_abc" как идентификатор вашего клиента.
  • emptyframe рассматривается как '' и отделяет конверт от данных сообщения.
  • message — это то, что спросил клиент или какой сервер ответил.

Выводы

  • сниффинг работает, и PyZMQ уже предлагает устройство MonitoredQueue для этой цели
  • с zmq.PUB сниффинг не будет блокировать связь, вы можете просто проигнорировать сниффинговые данные и все будет работать.
  • для производства было бы целесообразно сделать MonitoredQueue фиксированной частью системы, таким образом привязав его к известному IP-адресу и порту. Это потребует изменения на сервере, который должен будет подключиться (вместо текущей привязки). Такое изменение тривиально и не влияет на остальной код и поведение. Если у вас есть только одна конечная точка для мониторинга, вы также можете встроить монитор в сервер (для этого потребуется 2 потока, один для сервера, другой для монитора).
  • zmq отличный "Лего" для такого рода задач.
27.05.2014
  • Это замечательно! Я обновил вопрос в соответствии с вашим решением. Пожалуйста, посмотрите и подтвердите это. Есть ли другой способ, чтобы монитор перехватывал сообщения, не добавляя monitorclient между 'client и server. В остальном ваше решение отличное, и я ценю время, потраченное на его изучение. Ваше здоровье! 27.05.2014
  • @flamenco Мне нравится ваш тщательный подход к ясности вашего вопроса. Внесены небольшие исправления. 27.05.2014
  • @flamenco Если вы хотите отслеживать трафик без добавления промежуточного устройства zmq, вам придется прослушивать TCP-пакеты в сети. Это возможно, но не так просто сделать. ZMTP описывает формат сообщений ZMQ. Все просто для REQ/REP с короткими отдельными сообщениями, как в вашем случае, для других ситуаций есть шанс, что несколько сообщений ZMQ путешествуют в одном TCP-пакете и, возможно, сообщение zmq пересекает границу TCP. 27.05.2014
  • Большой! Я посмотрю на это, когда стану более универсальным с ZMQ. Пока это служит цели. 27.05.2014
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..