问题背景

最近做了一个 ipv6 相关的功能,发现使用 getifaddrs 获取的本地 ipv6 地址有可能不是真实的网络 ipv6 地址:

例如上图中通过 getifaddrs 获得了多个本地 ipv6 地址,其中 <fe80> 开头的已知是本地 ipv6 地址,被排除;还有 <2408> 这种,其实也是 "假 ipv6" 地址,对应的设备并不能访问 ipv6 网络。

对于这种假 v6 地址,无法通过遍历的方式进行枚举排除,而一旦将 v4 网络环境错认为是 v6 环境,对后面的网络操作影响比较大。需要引入一种准确判断当前网络是否有 ipv6 访问能力的方法,为此 server 端同学专门给了一个判断接口。

probe_v6_addr

出于安全考虑,这里只列出接口名称部分:

http://xxx.xxxxxxxxxx.xxxxxxx.xxxxx.xxx/xxx/probe_v6_addr

访问这个接口有两种返回,当不存在 v6 网络环境时:

no v6 addr

当存在时,返回本机的 ipv6 地址:

$ curl -s http://xxx.xxxxxxxxxx.xxxxxxx.xxxxx.xxx/xxx/probe_v6_addr
+
%240e:304:8183:2bcc:c16d:22d0:74ba:23e??-
'2408:832e:c272:b36e:55bc:554a:8952:553e,
&240e:3a0:7005:6ae2:d05a:754a:c21b:6c35??+
%240e:310:915:d939:9041:c01c:82db:a043??-
'2408:832e:c271:3851:6926:e953:e741:b1a3??+
%240e:378:1e0c:db62:7088:a216:87c:4ccd??OP46C3:/

虽然有部分二进制信息干扰,但是 ipv6 地址部分还是看得比较清楚的。返回的地址和 ifconfig 的结果可以相互印证:

$ ifconfig | grep inet6
inet6 addr: fe80::fc8e:84ff:fec0:1534/64 Scope: Link
inet6 addr: 240e:505:7e01:2994:f43c:5fc9:609e:5de6/64 Scope: Global
inet6 addr: fe80::f43c:5fc9:609e:5de6/64 Scope: Link
inet6 addr: fe80::8fd0:cd9e:52cd:5bc3/64 Scope: Link
inet6 addr: 2409:8100:7b00:5781:a4a8:71ce:b11:3c5e/64 Scope: Global
inet6 addr: fe80::a4a8:71ce:b11:3c5e/64 Scope: Link
inet6 addr: ::1/128 Scope: Host
inet6 addr: fe80::29f8:41f:7564:501d/64 Scope: Link
inet6 addr: 240e:404:7e01:5d77:29f8:41f:7564:501d/64 Scope: Global
inet6 addr: fe80::3d14:7716:4771:88fa/64 Scope: Link
inet6 addr: 240e:304:8183:2bcc:c16d:22d0:74ba:23e/64 Scope: Global
inet6 addr: 240e:304:8183:2bcc:d8c5:dce4:a89c:8a88/64 Scope: Global

其中 ipv6 地址240e:304:8183:2bcc:c16d:22d0:74ba:23e/64在两边都存在。

protobuf

上面的接口确实是基于二进制数据的协议,虽然是私有协议,但是采用了 protobuf 来进行规范,在提高性能的同时,也保留了一定的通用性。

但是这样一来,往常惯用的 curl + shell 大法要失灵了,给测试和验证工作带来了不小的麻烦。

不过好在有 proto 文件,生成一段解析的 c++ 代码也不是不可能:

> cat msg.proto
message ProbeIpv6Request {
string xxxxx = 1;
string xxxx = 2;
string xxxxxxxx = 3;
string xxxxxxx = 4;
} message V6AddrType {
string addrV6 = 1;
uint32 portV6 = 2;
} message ProbeIpv6Response {
string xxxxx = 1;
V6AddrType selfAddr = 2;
repeated V6AddrType brosAddr = 3;
}

这个 proto 文件揭示了两点:

  • 该接口也是有请求的:ProbeIpv6Request,不过可以省略
  • 该接口的响应 ProbeIpv6Response 主要包含两部分:
    • selfAddr 是设备自己的地址,有且只有一个
    • brosAddr 是设备的广播地址,可能存在多个 (repeated)
    • 地址都是由一个字符串地址和一个整型端口组成

如果使用 protoc 程序根据 msg.proto 生成 c++ 代码,再写程序解析数据,就用不着写这篇文章了。毕竟那种方式太牛刀杀鸡了,下面演示一种使用 shell 脚本就能搞定 protobuf 协议的新方法。

pbjs

在介绍新方法之前,先介绍本文的主角 pbjs。首先是在 mac 上的安装:

brew install node
brew install npm
npm install -g protobufjs
npm install -g pbjs

pbjs 是 nodejs 提供的,用来将 protobuf 二进制数据转换为 json,所以需要先安装 nodejs、npm 环境,linux 上的安装大同小异,此处不再赘述。

执行成功后验证 pbjs 是否安装:

> pbjs
Usage: pbjs [options] <schema_path> Options:
-V, --version output the version number
--es5 <js_path> Generate ES5 JavaScript code
--es6 <js_path> Generate ES6 JavaScript code
--ts <ts_path> Generate TypeScript code
--decode <msg_type> Decode standard input to JSON
--encode <msg_type> Encode standard input to JSON
-h, --help output usage information
> which pbjs
/Users/yunhai01/tools/node-v14.17.0-darwin-x64/bin/pbjs
> ls -lh /Users/yunhai01/tools/node-v14.17.0-darwin-x64/bin/pbjs
lrwxr-xr-x 1 yunhai01 staff 31B Apr 16 18:26 /Users/yunhai01/tools/node-v14.17.0-darwin-x64/bin/pbjs -> ../lib/node_modules/pbjs/cli.js

看起来这就是一个 node module 的软链接。

pbjs 的功能有很多,help 信息中已经罗列出来了,例如生成 js 代码 (--es5/--es6),生成 ts 代码 (--ts),不过最让我感兴趣的还是 --decode,意思是可以将数据解析为 json,下面用上一节的二进制数据做个练手,假设数据已经保存在名为 response.bin 的文件:

> pbjs msg.proto --decode ProbeIpv6Response < response.bin
{
"selfAddr": {
"addrV6": "240e:304:8183:2bcc:c16d:22d0:74ba:23e",
"portV6": 47832
},
"brosAddr": [
{
"addrV6": "240e:333:6b00:b00e:38db:2815:306b:3d9b",
"portV6": 18947
},
{
"addrV6": "240e:333:1707:ca6f:24d3:61ae:86cf:a6fa",
"portV6": 18112
},
{
"addrV6": "2409:8a38:9002:70b3:19a3:66a3:d778:65cc",
"portV6": 18780
},
{
"addrV6": "2408:8266:700:1a62:8ad0:4097:9220:577b",
"portV6": 18595
},
{
"addrV6": "240e:3a0:9001:4013:99c0:11c4:7d3b:e8e5",
"portV6": 18319
}
]
}

哈哈,果然成功,过程异常丝滑!

jq

有了 json 数据就好办了,下面上 jq 提取设备 IP,假设已经将数据保存在了 response.json 文件中:

> jq -r '.selfAddr.addrV6'  probe_v6.json
240e:304:8183:2bcc:c16d:22d0:74ba:23e

和之前猜测的 IP 地址结果一致。

结语

pbjs 不光可以用来解析响应,也可以用来构造 protobuf 格式的请求,主要就是依赖它的 --encode 参数:

pbjs msg.proto --encode ProbeIpv6Request < request.json > request.bin

注意 --decode/--encode 一次只能处理一个消息类型,而协议文件中可能包括多个,所以需要在这里为它们进行指定,之前指定的是 ProbeIpv6Response 消息,这里改为 ProbeIpv6Request 消息。

关于 request.json 文件,简单的可以直接手动构造,复杂的可以借助 jq --arg 动态生成,这方面详细的信息可以参考我之前写的这几篇文章:《用 shell 脚本做 tcp 协议模拟》、《使用 shell 脚本自动申请进京证 (六环外)》。

至此 protobuf 二进制数据也不再是脚本不可触控的区域,有这方面接口测试需求的同学们快用起来吧 ~

后记

使用基于 pbjs 的脚本在 android 设备上验证上述接口后,能正确返回结果,并且发现了几个小问题,为后面写 c++ 代码接入铺平了道路,比起直接使用 adb 跑脚本,编译 sdk 再打 apk 包验证成本实在是太高了,pbjs 确确实实提升了我的效率。

参考

[1]. Protocol Buffers for JavaScript

[2]. 工作笔记:protobufjs使用教程,支持proto文件打包成typescript或javascript脚本

使用脚本收发 protobuf 协议数据的更多相关文章

  1. Protobuf协议应用干货

    Protobuf应用广泛,尤其作为网络通讯协议最为普遍.本文将详细描述几个让人眼前一亮的protobuf协议设计,对准备应用或已经应用protobuf的开发者会有所启发,甚至可以直接拿过去用. 这里描 ...

  2. 开源项目SMSS开源项目(三)——protobuf协议设计

    本文的第一部分将介绍protobuf使用基础以及如何利用protobuf设计通信协议.第二部分会给出smss项目的协议设计规范和源码讲解. 一.Protobuf使用基础 什么是protobuf pro ...

  3. netty 对 protobuf 协议的解码与包装探究(2)

    netty 默认支持protobuf 的封装与解码,如果通信双方都使用netty则没有什么障碍,但如果客户端是其它语言(C#)则需要自己仿写与netty一致的方式(解码+封装),提前是必须很了解net ...

  4. 使用Go语言+Protobuf协议完成一个多人聊天室

    软件环境:Goland Github地址 一.目的 之前用纯逻辑垒完了一个可登入登出的在线多人聊天室(代码仓库地址),这次学习了Protobuf协议,于是想试着更新下聊天室的版本. 主要目的是为了掌握 ...

  5. 自定义兼容多种Protobuf协议的编解码器

    <从零开始搭建游戏服务器>自定义兼容多种Protobuf协议的编解码器 直接在protobuf序列化数据的前面,加上一个自定义的协议头,协议头里包含序列数据的长度和对应的数据类型,在数据解 ...

  6. Protobuf 协议语言指南

    l  定义一个消息(message)类型 l  标量值类型 l  Optional 的字段及默认值 l  枚举 l  使用其他消息类型 l  嵌套类型 l  更新一个消息类型 l  扩展 l  包(p ...

  7. Protobuf协议的Java应用例子

    Protobuf协议,全称:Protocol Buffer 它跟JSON,XML一样,是一个规定好的数据传播格式.不过,它的序列化和反序列化的效率太变态了…… 来看看几张图你就知道它有多变态. Pro ...

  8. java和golang通过protobuf协议相互通信

    目录 整体结构说明 protobuf2文件 golang客户端 目录结构 生成pb.go文件 main.go util.go java服务端 目录结构 pom.xml application.yml ...

  9. Protobuf协议--java实现

    Protobuf协议,全称:Protocol Buffer 它跟JSON,XML一样,是一个规定好的数据传播格式.不过,它的序列化和反序列化的效率太变态了…… 来看看几张图你就知道它有多变态.  pr ...

  10. 用CBrother脚本实现smtp协议发送一份邮件

    用CBrother脚本实现smtp协议发送一份邮件 之前用CBrother脚本写了一个拯救“小霸王服务器”的程序,公司人用着都挺好用,但是有时候谁重启了服务器其他人不知道,造成了多人多次重启,每个人都 ...

随机推荐

  1. DOM选择器之元素其他节点选择器

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. 浅读-《深入浅出Nodejs》

    原书作者:朴灵 https://book.douban.com/subject/25768396/ 这次算是重读 深入浅出Nodejs,了解到很多之前忽略的细节,收获蛮多,这次顺便将其记录分享,对学习 ...

  3. 多精度 simulator 中的 RL:一篇 14 年 ICRA 的古早论文

    目录 全文快读 0 abstract 1 intro 2 related work 3 背景 & 假设 3.1 RL & KWIK(know what it knows)的背景 3.2 ...

  4. 面对AI的兴起,从人类发展到个人发展,普通人应当如何抉择?

    这一周被各种 AI 卷的不行,从 ChatGPT 4.0 上线到百度文心一言发布会,再到微软的 Microsoft 365 Copilot. 网上有很多人.公众号吐嘈百度,而晓衡接触到的圈子还有一些不 ...

  5. 系统评价——主成分分析PCA的R语言实现(六)

    主成分分析(Principal Component Analysis,PCA),是将多个变量通过线性变换以选出较少个数重要变量的一种多元统计分析方法,起到数据约减和集成的作用.在许多领域的研究与应用中 ...

  6. SQL里按照年月日分组统计

    Mysql中使用DATE_FORMAT函数实现是相对简单的. 根据format字符串格式化date值.下列修饰符可以被用在format字符串中: %M 月名字(January--December) % ...

  7. [Java EE]小结:生成全局唯一编号的思路

    并发是一个让人很头疼的问题,通常会在服务端或数据库端做处理,保证在并发下数据的准确性. 为此,简要讨论一下,如何通过解决全局生成唯一编号的并发问题. 1 MySQL数据库的锁 1-0 锁的分类 按锁定 ...

  8. Java学习笔记03

    1. 流程控制语句 在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的.所以,我们必须清楚每条语句的执行流程.而且,很多时候要通过控制语句的执行顺序来实现我们想要的功能. 1.1 分 ...

  9. C# 自定义并动态切换光标

    系统有很多光标类型 :Cursors 类 (System.Windows.Input) | Microsoft Docs 本章介绍如何自定义光标.并动态切换光标类型. 动态切换光标类型 以白板书写为例 ...

  10. [C++基础入门] 1、C++初识

    文章目录 1 C++初识 1.1 第一个C++程序 1.1.1 创建项目 1.1.2 创建文件 1.1.3 编写代码 1.1.4 运行程序 1.2 注释 1.3 变量 1.4 常量 1.5 关键字 1 ...