ZMTP协议

news2024/12/22 23:44:40

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
message命令或者消息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

8607ab1ed5f523a6bbc4491ec74b924f.png
解析接如下

1b1998d082deac0fe4c96b7aafb00f94.png

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解析过后的协议。

6bd633daef76a532eb2eed806a8e6485.png

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方法

代码如下:

//
//  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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/963153.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何将国标规范用EndNote插入到英文期刊中,自定义文献插入指南

EndNote自定义文献 1.插入国标JTG 2034-2020这种新建一个Standard![](https://img-blog.csdnimg.cn/406cf11d1496431a9cf784f3ab71c6a1.png)Reference填入信息参考 插入英文期刊规范ASTM 1.插入国标JTG 2034-2020这种 首先找到大家要投稿的英文期刊&#xff0c;然后去找那些中…

Python:多变量赋值

相关文章 Python专栏https://blog.csdn.net/weixin_45791458/category_12403403.html?spm1001.2014.3001.5482 Python中的赋值语句可以同时对多个变量进行对象绑定&#xff08;赋值&#xff09;&#xff0c;既可以是多变量链式赋值&#xff0c;也可以是多变量平行赋值&#x…

爱胜品YPS-1133DN系列打印机网络驱动安装的一点小经验

爱胜品YPS-1133DN打印机基本参数&#xff1a; 项目 详细参数 品牌 ICSP爱胜品 外观配色 上灰下白经典实用设计 打印速度 33ppm&#xff08;A4&#xff09;、35ppm&#xff08;Letter&#xff09;、58ppm&#xff08;A5&#xff09; 首页打印时间 ≤8秒 最大月打印量 …

基于aarch64分析kernel源码 四:printk 内核打印

一、参考 Message logging with printk — The Linux Kernel documentation 如何获得正确的printk格式占位符 — The Linux Kernel documentation 使用printk记录消息 — The Linux Kernel documentation printk 内核打印 – 人人都懂物联网 (getiot.tech) 内核printk原理…

对语言模型能否替代知识图谱的再思考

深度学习自然语言处理 原创作者&#xff1a;cola 进NLP群—>加入NLP交流群 知识图谱&#xff08;KGs&#xff09;包含了许多真实世界的知识&#xff0c;在许多领域都发挥着重要重用&#xff0c;但是大型的知识图谱构建过程需要大量的人工介入。随着语言模型&#xff08;LMs&…

Vagrant + VirtualBox + CentOS7 + WindTerm 5分钟搭建本地linux开发环境

1、准备阶段 Vagrant 版本&#xff1a;vagrant_2.2.18_x86_64.msi 链接&#xff1a;https://developer.hashicorp.com/vagrant/downloads VirtualBox 版本&#xff1a;VirtualBox-6.1.46-158378-Win.exe 链接&#xff1a;https://www.virtualbox.org/wiki/Download_Old_Builds …

RHCE——十四、变量和引用

RHCE 一、深入认识变量1、什么是变量2、变量的名称3、变量的类型4、变量的定义5、自定义变量6、环境变量7、位置变量 二、变量赋值和作用域1、显示赋值&#xff1a;变量名变量值2、read 从键盘读入变量值3、变量和引号4、变量的作用域5、变量的运算 一、深入认识变量 1、什么是…

编程题四大算法思想(三)——贪心法:找零问题、背包问题、任务调度问题、活动选择问题、Prim算法

文章目录 贪心法找零问题&#xff08;change-making problem&#xff09;贪心算法要求基本思想适合求解问题的特征 背包问题0/1背包问题0/1背包问题——贪心法 分数背包问题 任务调度问题活动选择问题活动选择——贪心法最早结束时间优先——最优性证明 Prim算法 贪心法 我在当…

【Electron将HTML项目打包成桌面应用exe文件】

目标&#xff1a;前端将静态页面文件夹所有页面打包成一个exe文件&#xff08;不包含其它文件&#xff09;可运行。 步骤 1、初始化 npm init此时项目多出一个package.json文件。 {"name": "my-electron-app","version": "1.0.0",…

前端面试中Vue的有经典面试题二

7. Vue中给data中的对象属性添加一个新的属性时会发生什么&#xff0c;如何解决&#xff1f; 示例&#xff1a; 点击button会发现&#xff0c; obj.b 已经成功添加&#xff0c;但是视图并未刷新&#xff1a; 原因在于在Vue实例创建时&#xff0c; obj.b 并未声明&#xff0c;因…

【多线程案例】单例模式(懒汉模式和饿汉模式)

文章目录 1. 什么是单例模式&#xff1f;2. 立即加载/“饿汉模式”3. 延时加载/“懒汉模式”3.1 第一版3.2 第二版3.3 第三版3.4 第四版 1. 什么是单例模式&#xff1f; 提起单例模式&#xff0c;就必须介绍设计模式&#xff0c;而设计模式就是在软件设计中&#xff0c;针对特殊…

无涯教程-JavaScript - HYPGEOMDIST函数

HYPGEOMDIST函数替代Excel 2010中的HYPGEOM.DIST函数。 描述 该函数返回超几何分布。 HYPGEOMDIST返回给定样本数量,给定样本数量,总体成功率和总体数量的概率。 将HYPGEOMDIST用于具有有限总体的问题,其中每个观察输出都是成功或失败,并且给定大小的每个子集的选择可能性均…

《数字图像处理-OpenCV/Python》连载(4)图像的读取与保存

《数字图像处理-OpenCV/Python》连载&#xff08;4&#xff09;图像的读取与保存 本书京东优惠购书链接&#xff1a;https://item.jd.com/14098452.html 本书CSDN独家连载专栏&#xff1a;https://blog.csdn.net/youcans/category_12418787.html 第1章 图像的基本操作 为了方…

小兔鲜商02

npm i vueuse/core -fvue插件使用&#xff1a; 许多公用的全局组件&#xff0c;&#xff0c;可以通过插件注册进去&#xff0c;就不用一个一个导入组件&#xff0c;&#xff0c; import XtxSkeleton from /components/library/xtx-skeletonexport default {install (app) {// …

ELK高级搜索(三)

文章目录 11&#xff0e;索引Index入门11.1 索引管理11.2 定制分词器11.3 type底层结构11.4 定制dynamic mapping11.5 零停机重建索引 12&#xff0e;中文分词器 IK分词器12.1 Ik分词器安装使用12.2 ik配置文件12.3 使用mysql热更新 13&#xff0e;java api 实现索引管理14&…

Spring源码解析-总览

1、前言 Spring源码一直贯穿我们Java的开发中&#xff0c;只要你是一个Java开发人员就一定知道Spring全家桶。Spring全家桶为我们一共一站式服务&#xff0c;IOC、AOP更是Spring显著特性。但是Spring到底怎么为我们提供容器&#xff0c;管理资源的呢&#xff1f;下来&#xff0…

1.4状态机模型

状态机简介&#xff1a; 1.大盗阿福 阿福是一名经验丰富的大盗。趁着月黑风高&#xff0c;阿福打算今晚洗劫一条街上的店铺。 这条街上一共有 N N N家店铺&#xff0c;每家店中都有一些现金。 阿福事先调查得知&#xff0c;只有当他同时洗劫了两家相邻的店铺时&#xff0…

js对中文进行base64编码和解码操作,解决中文乱码问题

我使用github api的接口获取文件内容&#xff0c;然后使用atob进行解码&#xff0c;但是发现&#xff1a;乱码.......糟心啊 所以就有了我封装的方法&#xff1a; export const encode64 (str) > {// 首先&#xff0c;我们使用 encodeURIComponent 来获得百分比编码的UTF…

m1芯片macOS系统卡顿问题解决方法

m1芯片的MacBook在使用过程中会出现“假死”的情况。主要表现为鼠标转圈圈&#xff0c;很多操作都不能实现&#xff0c;不能输入文本&#xff0c;系统ui也响应十分慢&#xff0c;而资源监视却看不到很高的占用。一般出现此类情况只能关机或重启。这其中的"罪魁祸首"便…

管理类联考——逻辑——形式逻辑——汇总篇——知识点突破——形式逻辑——联言选言假言——矛盾

角度 角度——汇总——持续优化 性质 &#xff08;1&#xff09; 所有的 S 是 P 所有的S是P 所有的S是P 与 有的 S 不是 P 有的S不是P 有的S不是P矛盾 &#xff08;2&#xff09; 所有的 S 不是 P 所有的S不是P 所有的S不是P 与 有的 S 是 P 有的S是P 有的S是P 矛盾 &#xf…