ZMTP协议

ZoreMQ Transport Protocol是一个传输层协议,用于ZMQ的连接的信息交互,本文档描述的是3.0协议,主要分析基于NULL Security Mechanism


协议语法

ZMTP由三部分组成,分别是 greeting、handshake、traffic

部分 描述 构成
greeting 描述ZMQ版本、安全机制等 signature + version + mechanism + as-server + filler
handshake 描述端类型,如 PUB/SUB,REQ/REP 一个或多个command
traffic 命令或者消息 command

ZMTP Wireshark 抓包

WireShark 默认不提供ZMTP解析插件,需要自己配置,步骤如下:

插件仓库:https://github.com/whitequark/zmtp-wireshark

下载插件:https://github.com/whitequark/zmtp-wireshark/blob/master/zmtp-dissector.lua

将插件zmtp-dissector.lua放到WireShark 安装目录,比如我的是:C:\Program Files\Wireshark

修改C:\Program Files\Wireshark\init.lua,在文件末尾添加

dofile(DATA_DIR.."zmtp-dissector.lua")

对基于TCP端口通讯ZMQ进行抓包,例如端口为7380,将该端口Decode As ZMTP

解析接如下

greeting

greeting 固定64个字节大小,下面将依次介绍每个部分。

signature

固定10字节大小,固定值为ff 00 00 00 00 00 00 00 01 7f;

signature可以用来校验链接是否为ZMQ链接,连续读取10个字节,判断开头是否为0xff,结尾是否为0x7f

version

固定2字节大小,格式为{major_version, minor_version}3.0 协议则为03 00,实际编码过程中只会校验 major_version;

mechanism

固定20字节大小,这里只介绍NULL Security Mechanism,也就是不校验,其值为NULL,剩余以内容填充0;

as-server

固定一个字节大小,0x00 或者 0x01 ,当mechanism为NULL时候,as-server必须为0

filler

填充greeting至64个字节。

抓包示意

由Wireshark解析过后的协议。

Frame

greeting之后的所有数据格式都为Frame,包含commandmessage

frame的格式如下:

Frame = Flag + Payload Length + Payload

抓包示意如下

  • Flag

    Flag 为1字节大小,每位代表不同的意思,参考抓包解释

    低1位:表示是否有更多Frame,这里用于ZMQ中sendmore属性

    低2位:表示长度是否为8字节长度,否则为1字节长度

    低3位:表示当前frame是否为Command

    其他:保留,为0

  • Payload Length

    数据长度,可以为1字节或者8字节大小,根据Flag中的标志位决定

  • Payload

    实际的数据,大小为Payload Length

handshake

此阶段用来交换对端的READY命令以及metadata,主要包含对端的类型。handshake本质是Command,为Frame的一种。

NULL Security Mechanism机制中,以PUB/SUB模式为例,handshake的数据如下:

Payload内容如下:

[1 byte] Command size + [n bytes]Command Name + [1 bytes]Metadata Key size + [n bytes]Metadata Key + [4 bytes]Metadata Value size + [n bytes]Metadata Value

使用Socket实现ZMQ SUB方法

代码如下:

cpp 复制代码
//
//  ZMTP 3.0 debugging subscriber
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WinSock2.h>
#include <ws2tcpip.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <cstdint>
#include <iostream>

#pragma comment(lib, "ws2_32.lib")

typedef struct
{
    uint8_t flags;     //  Must be zero
    uint8_t size;      //  Size, 0 to 255 byte
    uint8_t data[255]; //  Message data
} zmtp_msg_t;

static void derp(char *s)
{
    perror(s);
    exit(1);
}

static void tcp_send(int handle, void *buffer, size_t len)
{
    if (send(handle, (char *) buffer, len, 0) == -1)
        derp((char *) "send");
}

static void tcp_recv(int handle, void *buffer, size_t len)
{
    printf(" - reading %d bytes: ", (int) len);
    fflush(stdout);
    size_t len_recd = 0;
    while (len_recd < len)
    {
        size_t bytes = recv(handle, (char *) buffer + len_recd, len - len_recd, 0);
        if (bytes == 0)
            break; //  Peer has shutdown
        printf(" [%d]", (int) bytes);
        fflush(stdout);
        if (bytes == -1)
            derp((char *) "recv");
        len_recd += bytes;
    }
    printf("\n");
    fflush(stdout);
}

static void zmtp_recv(int handle, zmtp_msg_t *msg)
{
    tcp_recv(handle, (uint8_t *) msg, 2);
    tcp_recv(handle, msg->data, msg->size);
}

static void zmtp_send(int handle, zmtp_msg_t *msg)
{
    tcp_send(handle, (uint8_t *) msg, msg->size + 2);
}

//  This is the 3.0 greeting (64 bytes)
typedef struct
{
    uint8_t signature[10];
    uint8_t version[2];
    uint8_t mechanism[20];
    uint8_t as_server[1];
    uint8_t filler[31];
} zmtp_greeting_t;

int main(void)
{
    puts("I: starting subscriber");

    WSADATA wsData;
    if (WSAStartup(MAKEWORD(2, 2), &wsData) != 0)
    {
        std::cerr << "无法初始化Winsock" << std::endl;
        return 1;
    }

    //  Create TCP socket
    int peer;
    if ((peer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
        derp((char *) "socket");

    const char *serverIP   = "127.0.0.1";
    const int   serverPort = 5559;

    sockaddr_in serverAddress {};
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port   = htons(serverPort);
    if (inet_pton(AF_INET, serverIP, &(serverAddress.sin_addr)) <= 0)
    {
        std::cerr << "无效的服务器IP地址" << std::endl;
        closesocket(peer);
        WSACleanup();
        return 1;
    }

    //  Keep trying to connect until we succeed
    puts("I: waiting for connection");
    while (connect(peer, reinterpret_cast<sockaddr *>(&serverAddress), sizeof(serverAddress)) == -1)
        Sleep(1);

    puts("I: connected OK");
    //  This is our greeting (64 octets)
    zmtp_greeting_t outgoing = {
        {0xFF, 0, 0, 0, 0, 0, 0, 0, 1, 0x7F},
        {3, 0},
        {'N', 'U', 'L', 'L', 0},
        {0},
        {0}
    };
    //  Do full backwards version detection following RFC23
    //  Send first ten bytes of greeting to peer
    tcp_send(peer, &outgoing, 10);

    //  Read first byte from peer
    zmtp_greeting_t incoming;
    tcp_recv(peer, &incoming, 1);
    uint8_t length = incoming.signature[0];
    if (length != 0xFF)
    {
        puts("E: signature not valid (1)");
        closesocket(peer);
        exit(0);
    }
    //  Looks like 2.0+, read 9 more bytes to be sure
    tcp_recv(peer, (uint8_t *) &incoming + 1, 9);
    if ((incoming.signature[9] & 1) != 1)
    {
        puts("E: signature not valid (2)");
        closesocket(peer);
        exit(0);
    }
    //  Exchange major version numbers
    puts("I: signature valid, exchanging major versions");
    tcp_send(peer, (uint8_t *) &outgoing + 10, 1);
    tcp_recv(peer, (uint8_t *) &incoming + 10, 1);

    if (incoming.version[0] >= 3)
    {
        //  If version >= 3, the peer is using ZMTP 3.0, so send
        //  rest of the greeting and continue with ZMTP 3.0.
        puts("I: peer is talking ZMTP 3.0");
        puts("I: sending rest of greeting...");
        tcp_send(peer, (uint8_t *) &outgoing + 11, 53);
        //  Get remainder of greeting from peer
        puts("I: waiting for greeting from peer...");
        tcp_recv(peer, (uint8_t *) &incoming + 11, 53);
        //  Do NULL handshake - send READY command
        //  For now, empty dictionary
        puts("I: have full greeting from peer");
        zmtp_msg_t  ready = {0x04, 0x19};
        std::string data;
        data.push_back(0x05);
        data.append("READY");
        data.push_back(0x0b);
        data.append("Socket-Type");
        int         netByteOrderSize = htonl(3);
        const char *valueBytes       = reinterpret_cast<const char *>(&netByteOrderSize);
        data.append(valueBytes, sizeof(netByteOrderSize));
        data.append("SUB");

        memcpy(ready.data, data.c_str(), data.size());
        puts("I: sending READY");
        zmtp_send(peer, &ready);
        //  Now wait for peer's READY command
        puts("I: expecting READY from peer");
        zmtp_recv(peer, &ready);
        //assert(memcmp(ready.data, "READY   ", 8) == 0);
        puts("I: OK! NULL security handshake completed");

        puts("I: send sub command");
        zmtp_msg_t subCmd {0x00, 0x01};
        subCmd.data[0] = 0x01;
        zmtp_send(peer, &subCmd);
    }
    else
    {
        puts("E: major version not valid");
        closesocket(peer);
        exit(0);
    }
    puts("I: READY, printing messages");
    while (true)
    {
        zmtp_msg_t msg;
        zmtp_recv(peer, &msg);
        msg.data[msg.size] = 0;
        puts((char *) msg.data);
    }
    closesocket(peer);
    return 0;
}
相关推荐
菠萝加点糖1 个月前
Android使用CurveZMQ实现安全连接
zmq
jinlei20092 个月前
Windows下使用MinGW编译安装zmq的步骤
mingw·zmq
jinlei20093 个月前
python 使用zmq库运行报错:epoll.cpp:73
zmq
沙振宇9 个月前
【Qt 学习之路】使用 cmake 在Windows上 编译 ZeroMQ
windows·qt·编译·cmake·zmq·zeromq
沙振宇9 个月前
【Qt 学习之路】在 Qt 使用 ZeroMQ
qt·zmq·zeromq