Files
OpenGFW/analyzer/udp/easytier.go
T
Mxmilu666andClaude Fable 5.1 cc6ad72706 feat(udp): add EasyTier analyzer for tcp:// and udp:// transports
Port of NoBrand-Cloud/OpenGFW-RS commit 1177ff7 to Go.

EasyTier's ciphers cover only the payload: the 16-byte PeerManagerHeader
stays in the clear, so peer ids, packet type and two redundant length
fields are readable even on an encrypted mesh. The plaintext handshake
also carries the network name, which lets a rule block one mesh and
leave the rest alone (easytier.network_name). A Syn answered by a Sack
echoing the same connection id and magic, or a handshake carrying the
0xd1e1a5e1 magic, settles a flow on its own; otherwise two consistent
frames are required. Hole-punch probes are reported but never set yes.

The wg://, quic:// and wss:// transports are left to the wireguard, quic
and tls analyzers. fet does not cover the tcp:// transport because it
classifies the plaintext handshake segment and is then done.

Rules must be written as `easytier != nil && ...`: the rule engine only
exposes analyzers that have published properties.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjQ1CTBn8j65cUZ2JKsY7L
2026-09-12 17:05:39 +08:00

767 lines
21 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package udp
import (
"encoding/binary"
"maps"
"unicode"
"unicode/utf8"
"github.com/apernet/OpenGFW/analyzer"
"github.com/apernet/OpenGFW/analyzer/utils"
)
// EasyTierAnalyzer 识别 EasyTier(https://github.com/EasyTier/EasyTier)
type EasyTierAnalyzer struct{}
var (
_ analyzer.UDPAnalyzer = (*EasyTierAnalyzer)(nil)
_ analyzer.TCPAnalyzer = (*EasyTierAnalyzer)(nil)
_ analyzer.UDPStream = (*easyTierUDPStream)(nil)
_ analyzer.TCPStream = (*easyTierTCPStream)(nil)
)
const (
// easyTierUDPTunnelHeaderSize UDPTunnelHeader:conn_id u32、msg_type u8、padding u8、len u16,全部小端
easyTierUDPTunnelHeaderSize = 8
// easyTierTCPTunnelHeaderSize TCPTunnelHeader:一个小端 u32 帧体长度
easyTierTCPTunnelHeaderSize = 4
// easyTierPeerManagerHeaderSize PeerManagerHeader:from u32、to u32、type u8、flags u8、fwd u8、reserved u8、len u32
easyTierPeerManagerHeaderSize = 16
// easyTierAEADTailSize AEAD 加密追加的 16 字节 tag 加 12 字节 nonce,头里声明的长度不包含它
easyTierAEADTailSize = 28
// easyTierMaxFrameBody 帧体长度上限。上游自己的限制更低(TCP 隧道 2000,字节流隧道 4096,
// UDP 会话 8192),这里取最大的那个,精确的约束交给长度不变量
easyTierMaxFrameBody = 8192
// easyTierMaxForwardCounter 路由在 >7 时丢弃后再自增,所以线上最大能见到 8
easyTierMaxForwardCounter = 8
// easyTierHandshakeMagic HandshakeRequest.magic,见上游 peers/conn/peer_conn.rs
easyTierHandshakeMagic uint64 = 0xd1e1a5e1
// easyTierSecretDigestLen NetworkSecretDigest = [u8; 32]
easyTierSecretDigestLen = 32
// easyTierNoiseEphemeralLen Noise XX 首条消息以 32 字节临时公钥开头
easyTierNoiseEphemeralLen = 32
// easyTierFramesForYes 没有握手可指的情况下,置 yes 需要攒够的自洽帧数
easyTierFramesForYes = 2
// easyTierUDPInvalidCountThreshold UDP 流上连续无法解析的数据报容忍数,与 wireguard 分析器一致
easyTierUDPInvalidCountThreshold = 4
// easyTierPacketLimit 超过这个包数仍未定性就结束该流,与 openvpn 分析器一致
easyTierPacketLimit = 256
)
// easyTierHolePunchBodyLens 上游打洞包的两种体长:连通性路径 16 字节,UDP 会话层 32 字节。
// 体本身是随机数据,长度是唯一能看的东西。
var easyTierHolePunchBodyLens = [...]int{16, 32}
// UDP 隧道消息类型(UdpPacketType)
const (
easyTierMsgSyn = 1
easyTierMsgSack = 2
easyTierMsgData = 3
easyTierMsgFin = 4
easyTierMsgHolePunch = 5
easyTierMsgV4HolePunch = 6
easyTierMsgV6HolePunch = 7
)
// 需要特殊处理的 PeerManagerHeader packet_type(PacketType)
const (
easyTierPktHandshake = 2
easyTierPktNoiseMsg1 = 13
easyTierPktNoiseMsg3 = 15
)
// PeerManagerHeaderFlags
const (
easyTierFlagEncrypted = 0b0000_0001
easyTierFlagLatencyFirst = 0b0000_0010
easyTierFlagExitNode = 0b0000_0100
easyTierFlagNoProxy = 0b0000_1000
easyTierFlagCompressed = 0b0001_0000
// easyTierFlagLiveness 两个存活探测标志,是仅有的让 reserved 字节有意义的标志
easyTierFlagLiveness = 0b1010_0000
)
func (a *EasyTierAnalyzer) Name() string {
return "easytier"
}
func (a *EasyTierAnalyzer) Limit() int {
// 定长帧协议,按字节截断会让帧读取失步;用 easyTierPacketLimit 约束开销
return 0
}
func (a *EasyTierAnalyzer) NewUDP(info analyzer.UDPInfo, logger analyzer.Logger) analyzer.UDPStream {
return newEasyTierUDPStream(logger)
}
func (a *EasyTierAnalyzer) NewTCP(info analyzer.TCPInfo, logger analyzer.Logger) analyzer.TCPStream {
return newEasyTierTCPStream(logger)
}
// easyTierEvidence 一帧能证明多少
type easyTierEvidence int
const (
// easyTierEvidenceWeak 格式正确但单独不足为据:体是随机数据的短定长数据报。字段照发,不推进 yes
easyTierEvidenceWeak easyTierEvidence = iota
// easyTierEvidenceFrame 带 PeerManagerHeader 且通过全部校验的帧
easyTierEvidenceFrame
// easyTierEvidenceConclusive 匹配上的握手,别的东西不会碰巧产生它
easyTierEvidenceConclusive
)
// easyTierCommon 对外发布的属性,加上 yes 背后的计数。
//
// 描述最近一帧的字段每次都会覆盖;从握手里读出来的字段是粘性的,
// 因为网络名只发一次,而按 mesh 名写的规则必须在握手滚过去之后还能用。
type easyTierCommon struct {
props analyzer.PropMap
frames int
conclusive bool
packetCount int
rxPacketCount int
txPacketCount int
}
func newEasyTierCommon(transport string) easyTierCommon {
return easyTierCommon{
props: analyzer.PropMap{
"yes": false,
"transport": transport,
"tx_pkt_cnt": 0,
"rx_pkt_cnt": 0,
},
}
}
func (c *easyTierCommon) countPacket(rev bool) {
c.packetCount++
if rev {
c.rxPacketCount++
c.props["rx_pkt_cnt"] = c.rxPacketCount
} else {
c.txPacketCount++
c.props["tx_pkt_cnt"] = c.txPacketCount
}
}
func (c *easyTierCommon) record(evidence easyTierEvidence) {
switch evidence {
case easyTierEvidenceConclusive:
c.conclusive = true
case easyTierEvidenceFrame:
c.frames++
}
c.props["yes"] = c.conclusive || c.frames >= easyTierFramesForYes
}
func (c *easyTierCommon) set(key string, value interface{}) {
c.props[key] = value
}
// setSticky 只写一次、之后不再覆盖,用于从握手里读出的字段
func (c *easyTierCommon) setSticky(key string, value interface{}) {
if _, ok := c.props[key]; !ok {
c.props[key] = value
}
}
// update 发布一份属性快照。引擎会持有这份 map,所以必须复制,不能把内部的交出去
func (c *easyTierCommon) update() *analyzer.PropUpdate {
return &analyzer.PropUpdate{
Type: analyzer.PropUpdateReplace,
M: maps.Clone(c.props),
}
}
func (c *easyTierCommon) exhausted() bool {
return c.packetCount > easyTierPacketLimit
}
// applyHeader 记录 PeerManagerHeader 的字段,并返回这一帧能证明多少
func (c *easyTierCommon) applyHeader(h easyTierPeerManagerHeader, body []byte) easyTierEvidence {
c.set("packet_type", h.packetType)
c.set("packet_type_name", easyTierPacketTypeName(h.packetType))
c.set("from_peer_id", h.fromPeerID)
c.set("to_peer_id", h.toPeerID)
c.set("forward_counter", h.forwardCounter)
c.set("payload_len", h.length)
c.set("encrypted", h.flags&easyTierFlagEncrypted != 0)
c.set("compressed", h.flags&easyTierFlagCompressed != 0)
c.set("latency_first", h.flags&easyTierFlagLatencyFirst != 0)
c.set("exit_node", h.flags&easyTierFlagExitNode != 0)
c.set("no_proxy", h.flags&easyTierFlagNoProxy != 0)
payload := body[easyTierPeerManagerHeaderSize:]
switch h.packetType {
case easyTierPktHandshake:
return c.applyPlainHandshake(payload)
case easyTierPktNoiseMsg1:
c.setSticky("secure", true)
c.applyNoiseMsg1(payload)
// 只有临时公钥和一段 protobuf,随机流也能凑出来,不算确证
return easyTierEvidenceFrame
case easyTierPktNoiseMsg3:
c.setSticky("secure", true)
return easyTierEvidenceFrame
default:
return easyTierEvidenceFrame
}
}
// applyPlainHandshake 明文 HandshakeRequest,magic 对上就定性
func (c *easyTierCommon) applyPlainHandshake(payload []byte) easyTierEvidence {
hs, ok := easyTierParseHandshakeRequest(payload)
if !ok {
return easyTierEvidenceFrame
}
c.setSticky("magic_ok", true)
c.setSticky("secure", false)
if hs.hasVersion {
c.setSticky("version", hs.version)
}
if hs.networkName != "" {
c.setSticky("network_name", hs.networkName)
}
return easyTierEvidenceConclusive
}
// applyNoiseMsg1 Noise XX 的首条消息此时还没有密钥,protobuf 载荷是明文。
// 机会性地读:解析不干净就什么都不说,不猜。
func (c *easyTierCommon) applyNoiseMsg1(payload []byte) {
if len(payload) <= easyTierNoiseEphemeralLen {
return
}
msg, ok := easyTierParseNoiseMsg1(payload[easyTierNoiseEphemeralLen:])
if !ok {
return
}
if msg.hasVersion {
c.setSticky("version", msg.version)
}
if msg.networkName != "" {
c.setSticky("network_name", msg.networkName)
}
}
type easyTierPeerManagerHeader struct {
fromPeerID uint32
toPeerID uint32
packetType uint8
flags uint8
forwardCounter uint8
length uint32
}
// easyTierReadPeerManagerHeader 读取并校验帧体开头的 PeerManagerHeader。
//
// 声明的载荷长度要和实际收到的帧体对照。AEAD 加密会追加 28 字节尾部而不改声明长度,
// 压缩会缩短载荷也不改声明长度,所以只有两个标志都没置位时关系才是精确相等。
func easyTierReadPeerManagerHeader(body []byte) (easyTierPeerManagerHeader, bool) {
var h easyTierPeerManagerHeader
if len(body) < easyTierPeerManagerHeaderSize {
return h, false
}
packetType := body[8]
if !easyTierIsKnownPacketType(packetType) {
return h, false
}
flags := body[9]
forwardCounter := body[10]
if forwardCounter > easyTierMaxForwardCounter {
return h, false
}
// reserved 只在存活探测时装 token,其余时候恒为 0
if body[11] != 0 && flags&easyTierFlagLiveness == 0 {
return h, false
}
length := binary.LittleEndian.Uint32(body[12:16])
if length > easyTierMaxFrameBody {
return h, false
}
wire := uint32(len(body) - easyTierPeerManagerHeaderSize)
var ok bool
switch {
case flags&easyTierFlagCompressed != 0:
// 压缩器只在能省下自己那 1 字节尾时才压
ok = wire >= 1 && wire <= length+easyTierAEADTailSize
case flags&easyTierFlagEncrypted != 0:
// XOR 不改长度;AEAD 追加尾部
ok = wire == length || wire == length+easyTierAEADTailSize
default:
ok = wire == length
}
if !ok {
return h, false
}
return easyTierPeerManagerHeader{
fromPeerID: binary.LittleEndian.Uint32(body[0:4]),
toPeerID: binary.LittleEndian.Uint32(body[4:8]),
packetType: packetType,
flags: flags,
forwardCounter: forwardCounter,
length: length,
}, true
}
func easyTierIsKnownPacketType(packetType uint8) bool {
return packetType >= 1 && packetType <= 21
}
func easyTierPacketTypeName(packetType uint8) string {
switch packetType {
case 1:
return "data"
case 2:
return "handshake"
case 3:
return "route_packet"
case 4:
return "ping"
case 5:
return "pong"
case 6:
return "ta_rpc"
case 7:
return "route"
case 8:
return "rpc_req"
case 9:
return "rpc_resp"
case 10:
return "foreign_network_packet"
case 11:
return "kcp_src"
case 12:
return "kcp_dst"
case 13:
return "noise_handshake_msg1"
case 14:
return "noise_handshake_msg2"
case 15:
return "noise_handshake_msg3"
case 16:
return "quic_src"
case 17:
return "quic_dst"
case 18:
return "data_with_kcp_src_modified"
case 19:
return "data_with_quic_src_modified"
case 20:
return "relay_handshake"
case 21:
return "relay_handshake_ack"
default:
return "unknown"
}
}
func easyTierMsgTypeName(msgType uint8) string {
switch msgType {
case easyTierMsgSyn:
return "syn"
case easyTierMsgSack:
return "sack"
case easyTierMsgData:
return "data"
case easyTierMsgFin:
return "fin"
case easyTierMsgHolePunch:
return "hole_punch"
case easyTierMsgV4HolePunch:
return "v4_hole_punch"
case easyTierMsgV6HolePunch:
return "v6_hole_punch"
default:
return "unknown"
}
}
// easyTierPendingSyn 一个等着 Sack 的 Syn
type easyTierPendingSyn struct {
connID uint32
magic uint64
rev bool
}
type easyTierUDPStream struct {
logger analyzer.Logger
common easyTierCommon
invalidCount int
pendingSyn *easyTierPendingSyn
}
func newEasyTierUDPStream(logger analyzer.Logger) *easyTierUDPStream {
return &easyTierUDPStream{
logger: logger,
common: newEasyTierCommon("udp"),
}
}
func (s *easyTierUDPStream) Feed(rev bool, data []byte) (u *analyzer.PropUpdate, done bool) {
// EasyTier 把 STUN 复用在同一个 socket 上,并且在自己的帧解析之前先分流 STUN;
// 照做:既不算证据,也不算错误
if easyTierIsSTUNDatagram(data) {
s.common.packetCount++
return nil, s.common.exhausted()
}
evidence, ok := s.parseDatagram(rev, data)
if !ok {
s.invalidCount++
return nil, s.invalidCount >= easyTierUDPInvalidCountThreshold
}
s.invalidCount = 0
s.common.countPacket(rev)
s.common.record(evidence)
return s.common.update(), s.common.exhausted()
}
func (s *easyTierUDPStream) Close(limited bool) *analyzer.PropUpdate {
return nil
}
func (s *easyTierUDPStream) parseDatagram(rev bool, data []byte) (easyTierEvidence, bool) {
if len(data) < easyTierUDPTunnelHeaderSize {
return 0, false
}
connID := binary.LittleEndian.Uint32(data[0:4])
msgType := data[4]
// padding 只为对齐长度字段而存在
if data[5] != 0 {
return 0, false
}
declared := int(binary.LittleEndian.Uint16(data[6:8]))
body := data[easyTierUDPTunnelHeaderSize:]
// 上游要求这两者相等,否则整个数据报直接丢弃
if declared != len(body) {
return 0, false
}
var evidence easyTierEvidence
switch msgType {
case easyTierMsgSyn:
if len(body) != 8 {
return 0, false
}
s.pendingSyn = &easyTierPendingSyn{connID: connID, magic: binary.LittleEndian.Uint64(body), rev: rev}
evidence = easyTierEvidenceWeak
case easyTierMsgSack:
// 反向回来、同一 conn_id、同一随机 64 位 magic 的 Sack,别的东西做不到
if len(body) != 8 {
return 0, false
}
magic := binary.LittleEndian.Uint64(body)
syn := s.pendingSyn
if syn != nil && syn.rev != rev && syn.connID == connID && syn.magic == magic {
evidence = easyTierEvidenceConclusive
} else {
evidence = easyTierEvidenceWeak
}
case easyTierMsgData:
h, ok := easyTierReadPeerManagerHeader(body)
if !ok {
return 0, false
}
evidence = s.common.applyHeader(h, body)
case easyTierMsgFin:
if len(body) != 0 {
return 0, false
}
evidence = easyTierEvidenceWeak
case easyTierMsgHolePunch:
// 打洞探测是定长随机体,往可能根本没跑 EasyTier 的地址上喷,
// 不足以下结论,但 msg_type_name 照样说明它是什么
if !easyTierIsHolePunchBodyLen(len(body)) {
return 0, false
}
evidence = easyTierEvidenceWeak
case easyTierMsgV4HolePunch:
// 两种定向变体在 conn_id 里回显目标端口。上游只从回环地址接受它们,主机间线上少见
if len(body) != 6 || connID != uint32(binary.LittleEndian.Uint16(body[4:6])) {
return 0, false
}
evidence = easyTierEvidenceWeak
case easyTierMsgV6HolePunch:
if len(body) != 38 || connID != uint32(binary.LittleEndian.Uint16(body[16:18])) {
return 0, false
}
evidence = easyTierEvidenceWeak
default:
return 0, false
}
s.common.set("conn_id", connID)
s.common.set("msg_type", msgType)
s.common.set("msg_type_name", easyTierMsgTypeName(msgType))
return evidence, true
}
func easyTierIsHolePunchBodyLen(n int) bool {
for _, l := range easyTierHolePunchBodyLens {
if n == l {
return true
}
}
return false
}
// easyTierIsSTUNDatagram 和 EasyTier 自己先做的判断一样:
// 它的长度字段位置上是 STUN magic cookie,且前两位为零
func easyTierIsSTUNDatagram(data []byte) bool {
return len(data) >= easyTierUDPTunnelHeaderSize &&
data[4] == 0x21 && data[5] == 0x12 && data[6] == 0xa4 && data[7] == 0x42 &&
data[0]&0xc0 == 0
}
type easyTierTCPStream struct {
logger analyzer.Logger
common easyTierCommon
reqBuf utils.ByteBuffer
respBuf utils.ByteBuffer
}
func newEasyTierTCPStream(logger analyzer.Logger) *easyTierTCPStream {
return &easyTierTCPStream{
logger: logger,
common: newEasyTierCommon("tcp"),
}
}
// easyTierFrameStatus 从一个方向的缓冲里读一帧的结果
type easyTierFrameStatus int
const (
// easyTierFrameRead 头已校验通过,帧体可以发布
easyTierFrameRead easyTierFrameStatus = iota
// easyTierFrameNeedMore 声明的帧体还没到齐
easyTierFrameNeedMore
// easyTierFrameNo 不可能是 EasyTier 的帧
easyTierFrameNo
)
func (s *easyTierTCPStream) Feed(rev, start, end bool, skip int, data []byte) (u *analyzer.PropUpdate, done bool) {
// 重组有空洞时帧边界无从推断
if skip != 0 {
return nil, true
}
if len(data) == 0 {
return nil, false
}
buf := &s.reqBuf
if rev {
buf = &s.respBuf
}
buf.Append(data)
updated := false
for {
h, body, status := easyTierReadFrame(buf)
switch status {
case easyTierFrameRead:
evidence := s.common.applyHeader(h, body)
s.common.countPacket(rev)
s.common.record(evidence)
updated = true
if s.common.exhausted() {
return s.common.update(), true
}
case easyTierFrameNeedMore:
if updated {
return s.common.update(), false
}
return nil, false
case easyTierFrameNo:
if updated {
return s.common.update(), true
}
return nil, true
}
}
}
func (s *easyTierTCPStream) Close(limited bool) *analyzer.PropUpdate {
s.reqBuf.Reset()
s.respBuf.Reset()
return nil
}
// easyTierReadFrame 帧结构是 [u32 LE 帧体长度][帧体],帧体总是以 PeerManagerHeader 开头,
// 上游会把更短的帧体按 "body too short" 拒绝。
func easyTierReadFrame(buf *utils.ByteBuffer) (easyTierPeerManagerHeader, []byte, easyTierFrameStatus) {
var h easyTierPeerManagerHeader
bodyLen, ok := buf.GetUint32(true, false)
if !ok {
return h, nil, easyTierFrameNeedMore
}
if bodyLen < easyTierPeerManagerHeaderSize || bodyLen > easyTierMaxFrameBody {
return h, nil, easyTierFrameNo
}
total := easyTierTCPTunnelHeaderSize + int(bodyLen)
frame, ok := buf.Get(total, false)
if !ok {
return h, nil, easyTierFrameNeedMore
}
body := frame[easyTierTCPTunnelHeaderSize:]
h, ok = easyTierReadPeerManagerHeader(body)
if !ok {
return h, nil, easyTierFrameNo
}
// 只在帧确认合法后才消费;被拒绝的流反正要结束,留着字节让缓冲状态更好推理
buf.Skip(total)
return h, body, easyTierFrameRead
}
// easyTierHandshakeInfo 从握手里读出来的东西。networkName 为空表示没有可用的名字
type easyTierHandshakeInfo struct {
version uint64
hasVersion bool
networkName string
}
// easyTierParseHandshakeRequest 解析 HandshakeRequest { magic = 1, my_peer_id = 2,
// version = 3, features = 4, network_name = 5, network_secret_digest = 6 }。
//
// 只有 magic 存在且正确才返回 true,调用方可以把 true 当作证据而不是提示。
func easyTierParseHandshakeRequest(payload []byte) (easyTierHandshakeInfo, bool) {
var info easyTierHandshakeInfo
var magic uint64
hasMagic := false
digestLen := -1
ok := easyTierForEachProtoField(payload, func(f easyTierProtoField) {
switch {
case f.isVarint && f.number == 1:
magic, hasMagic = f.varint, true
case f.isVarint && f.number == 3:
info.version, info.hasVersion = f.varint, true
case f.isBytes && f.number == 5:
info.networkName, _ = easyTierPrintableUTF8(f.bytes)
case f.isBytes && f.number == 6:
digestLen = len(f.bytes)
}
})
if !ok || !hasMagic || magic != easyTierHandshakeMagic {
return easyTierHandshakeInfo{}, false
}
// 上游会拒绝 digest 宽度不对的握手
if digestLen >= 0 && digestLen != easyTierSecretDigestLen {
return easyTierHandshakeInfo{}, false
}
return info, true
}
// easyTierParseNoiseMsg1 解析 PeerConnNoiseMsg1Pb { version = 1, a_network_name = 2, ... }。
//
// 这里没有 magic 可查,所以消息必须完整解析并声明一个合理的版本,否则返回 false。
func easyTierParseNoiseMsg1(payload []byte) (easyTierHandshakeInfo, bool) {
var info easyTierHandshakeInfo
ok := easyTierForEachProtoField(payload, func(f easyTierProtoField) {
switch {
case f.isVarint && f.number == 1:
info.version, info.hasVersion = f.varint, true
case f.isBytes && f.number == 2:
info.networkName, _ = easyTierPrintableUTF8(f.bytes)
}
})
// 协议版本从有这个字段起就一直是 1,很大的值说明这些字节不是这条消息
if !ok || !info.hasVersion || info.version > 16 {
return easyTierHandshakeInfo{}, false
}
return info, true
}
type easyTierProtoField struct {
number uint32
isVarint bool
varint uint64
isBytes bool
bytes []byte
}
// easyTierForEachProtoField 遍历 protobuf 字段,跳过调用方不认识的字段,
// 遇到编码站不住脚(截断、字段号为 0、这两条消息从不使用的 wire type)时返回 false。
func easyTierForEachProtoField(data []byte, fn func(easyTierProtoField)) bool {
pos := 0
for pos < len(data) {
tag, ok := easyTierReadVarint(data, &pos)
if !ok {
return false
}
number := uint32(tag >> 3)
if number == 0 {
return false
}
switch tag & 0x7 {
case 0:
value, ok := easyTierReadVarint(data, &pos)
if !ok {
return false
}
fn(easyTierProtoField{number: number, isVarint: true, varint: value})
case 2:
length, ok := easyTierReadVarint(data, &pos)
if !ok || length > uint64(len(data)-pos) {
return false
}
end := pos + int(length)
fn(easyTierProtoField{number: number, isBytes: true, bytes: data[pos:end]})
pos = end
case 5:
if len(data)-pos < 4 {
return false
}
pos += 4
case 1:
if len(data)-pos < 8 {
return false
}
pos += 8
default:
// group 类型,两条消息都不用
return false
}
}
return true
}
func easyTierReadVarint(data []byte, pos *int) (uint64, bool) {
var value uint64
for i := 0; i < 10; i++ {
if *pos >= len(data) {
return 0, false
}
b := data[*pos]
*pos++
value |= uint64(b&0x7f) << (7 * i)
if b&0x80 == 0 {
return value, true
}
}
return 0, false
}
// easyTierPrintableUTF8 网络名是运营者自己起的文本。拒绝控制字符,
// 免得一段碰巧的字节最后被引用进日志或审计记录里。
func easyTierPrintableUTF8(b []byte) (string, bool) {
if len(b) == 0 || !utf8.Valid(b) {
return "", false
}
s := string(b)
for _, r := range s {
if unicode.IsControl(r) {
return "", false
}
}
return s, true
}