3.9 TLV处理实现:扩展信息的"瑞士军刀"

TLV是什么

TLV(Type-Length-Value)是一种通用的数据编码格式:

TLV结构:
┌─────────┬──────────┬───────────────────┐
│Type     │Length    │Value              │
│2 bytes  │2 bytes   │Variable           │
└─────────┴──────────┴───────────────────┘

Type:类型标识,区分不同的TLV
Length:Value字段的长度(不含Type和Length)
Value:实际数据

优点:
- 可扩展:新类型不影响旧实现
- 灵活:变长数据
- 解析简单:按Type分发处理

PTP中的TLV应用

PTP协议大量使用TLV:

1. 管理消息(Management Message)
   - GET/SET操作
   - 读取/写入数据集

2. 信号消息(Signaling Message)
   - 单播协商
   - 事件订阅

3. 扩展TLV
   - 组织扩展
   - Profile扩展

4. 可选TLV
   - 路径追踪
   - 备用时间偏移

TLV类型定义

TLV类型常量

/* tlv.h, 第28-56行 */

/* 基本TLV类型 */
#define TLV_MANAGEMENT                              0x0001
#define TLV_MANAGEMENT_ERROR_STATUS                 0x0002
#define TLV_ORGANIZATION_EXTENSION                  0x0003

/* 单播协商TLV */
#define TLV_REQUEST_UNICAST_TRANSMISSION            0x0004
#define TLV_GRANT_UNICAST_TRANSMISSION              0x0005
#define TLV_CANCEL_UNICAST_TRANSMISSION             0x0006
#define TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION 0x0007

/* 可选TLV */
#define TLV_PATH_TRACE                              0x0008
#define TLV_ALTERNATE_TIME_OFFSET_INDICATOR         0x0009

/* 安全TLV */
#define TLV_AUTHENTICATION_2008                     0x2000
#define TLV_AUTHENTICATION_CHALLENGE                0x2001
#define TLV_SECURITY_ASSOCIATION_UPDATE             0x2002
#define TLV_CUM_FREQ_SCALE_FACTOR_OFFSET            0x2003

/* IEEE 802.1扩展TLV */
#define TLV_ORGANIZATION_EXTENSION_PROPAGATE        0x4000
#define TLV_ENHANCED_ACCURACY_METRICS               0x4001

/* Profile扩展TLV */
#define TLV_ORGANIZATION_EXTENSION_DO_NOT_PROPAGATE 0x8000
#define TLV_L1_SYNC                                 0x8001
#define TLV_SLAVE_RX_SYNC_TIMING_DATA               0x8004
#define TLV_CUMULATIVE_RATE_RATIO                   0x8007
#define TLV_PAD                                     0x8008
#define TLV_AUTHENTICATION                          0x8009

TLV类型范围

IEEE 1588定义的TLV类型范围:

0x0000:保留
0x0001-0x1FFF:标准TLV
0x2000-0x3FFF:安全TLV
0x4000-0x7FFF:组织扩展TLV(可传播)
0x8000-0xFFFF:Profile特定TLV(不传播)

类型分类:
- 标准TLV:所有实现都应支持
- 安全TLV:用于PTP安全扩展
- 组织扩展:厂商/组织自定义
- Profile扩展:特定Profile使用

Management ID定义

/* tlv.h, 第66-132行 */

/* 时钟管理ID */
#define MID_USER_DESCRIPTION              0x0002
#define MID_SAVE_IN_NON_VOLATILE_STORAGE  0x0003
#define MID_RESET_NON_VOLATILE_STORAGE    0x0004
#define MID_INITIALIZE                    0x0005
#define MID_DEFAULT_DATA_SET              0x2000
#define MID_CURRENT_DATA_SET              0x2001
#define MID_PARENT_DATA_SET               0x2002
#define MID_TIME_PROPERTIES_DATA_SET      0x2003
#define MID_PRIORITY1                     0x2005
#define MID_PRIORITY2                     0x2006
#define MID_DOMAIN                        0x2007
#define MID_SLAVE_ONLY                    0x2008
#define MID_TIME                          0x200F

/* 端口管理ID */
#define MID_NULL_MANAGEMENT               0x0000
#define MID_CLOCK_DESCRIPTION             0x0001
#define MID_PORT_DATA_SET                 0x2004
#define MID_LOG_ANNOUNCE_INTERVAL         0x2009
#define MID_LOG_SYNC_INTERVAL             0x200B
#define MID_VERSION_NUMBER                0x200C
#define MID_ENABLE_PORT                   0x200D
#define MID_DISABLE_PORT                  0x200E
#define MID_DELAY_MECHANISM               0x6000

/* LinuxPTP扩展管理ID(非标准) */
#define MID_TIME_STATUS_NP                0xC000
#define MID_GRANDMASTER_SETTINGS_NP       0xC001
#define MID_PORT_DATA_SET_NP              0xC002
#define MID_SUBSCRIBE_EVENTS_NP           0xC003
#define MID_PORT_PROPERTIES_NP            0xC004
#define MID_PORT_STATS_NP                 0xC005

/* 管理错误ID */
#define MID_RESPONSE_TOO_BIG              0x0001
#define MID_NO_SUCH_ID                    0x0002
#define MID_WRONG_LENGTH                  0x0003
#define MID_WRONG_VALUE                   0x0004
#define MID_NOT_SETABLE                   0x0005
#define MID_NOT_SUPPORTED                 0x0006
#define MID_GENERAL_ERROR                 0xFFFE

管理ID范围

管理ID范围分配:

0x0000-0x0FFF:端口管理ID
- 操作特定端口
- 如PORT_DATA_SET

0x1000-0x1FFF:保留

0x2000-0x3FFF:时钟管理ID
- 操作整个时钟
- 如DEFAULT_DATA_SET

0x4000-0x5FFF:透明时钟管理ID

0x6000-0x7FFF:其他管理ID

0xC000-0xFFFF:厂商扩展
- LinuxPTP使用0xCxxx
- "_NP"后缀表示"Non-Portable"

TLV结构定义

通用TLV结构

/* msg.h中定义 */

struct TLV {
    Enumeration16 type;    /* TLV类型 */
    UInteger16 length;     /* Value字段长度 */
    /* Value字段跟随 */
};

管理TLV结构

/* tlv.h, 第210-215行 */

struct management_tlv {
    Enumeration16 type;    /* TLV类型 = TLV_MANAGEMENT */
    UInteger16 length;     /* length字段 */
    Enumeration16 id;      /* 管理ID */
    Octet data[0];         /* 数据(变长) */
};

管理TLV示例

获取DEFAULT_DATA_SET的请求:
┌─────────┬──────────┬──────────┐
│Type     │Length    │ID        │
│0x0001   │0x0002    │0x2000    │
└─────────┴──────────┴──────────┘
Type = TLV_MANAGEMENT (0x0001)
Length = 2(只有ID字段,2字节)
ID = MID_DEFAULT_DATA_SET (0x2000)

响应:
┌─────────┬──────────┬──────────┬──────────────────┐
│Type     │Length    │ID        │defaultDS          │
│0x0001   │0x0046    │0x2000    │(70 bytes)         │
└─────────┴──────────┴──────────┴──────────────────┘
Length = 2 (ID) + 70 (defaultDS) = 72

管理错误TLV

/* tlv.h, 第222-229行 */

struct management_error_status {
    Enumeration16 type;    /* TLV_MANAGEMENT_ERROR_STATUS */
    UInteger16 length;
    Enumeration16 error;   /* 错误码 */
    Enumeration16 id;      /* 触发错误的管理ID */
    Octet reserved[4];
    Octet data[0];         /* 可选的附加数据 */
};

错误处理示例

请求不存在的管理ID:
请求ID = 0x9999(无效)

响应:
┌─────────┬──────────┬──────────┬──────────┬──────────┐
│Type     │Length    │Error     │ID        │Reserved  │
│0x0002   │0x0008    │0x0002    │0x9999    │0x00000000│
└─────────┴──────────┴──────────┴──────────┴──────────┘

Error = MID_NO_SUCH_ID (0x0002)

路径追踪TLV

/* tlv.h, 第272-276行 */

struct path_trace_tlv {
    Enumeration16 type;    /* TLV_PATH_TRACE */
    UInteger16 length;
    struct ClockIdentity cid[0];    /* 时钟ID数组 */
};

/* 最大路径追踪长度 */
#define PATH_TRACE_MAX \
    ((sizeof(struct message_data) - sizeof(struct announce_msg) - sizeof(struct TLV)) / \
     sizeof(struct ClockIdentity))

路径追踪原理

Path Trace TLV记录报文经过的路径:

主时钟A发送Announce:
Path Trace = [A]

边界时钟B转发:
Path Trace = [A, B]

边界时钟C转发:
Path Trace = [A, B, C]

从时钟接收:
Path Trace = [A, B, C]
知道路径:A → B → C → 从时钟

应用:
- 故障诊断
- 拓扑发现
- 路径验证

单播协商TLV

/* tlv.h, 第169-177行 */

struct grant_unicast_xmit_tlv {
    Enumeration16 type;        /* TLV_GRANT_UNICAST_TRANSMISSION */
    UInteger16 length;
    uint8_t message_type;      /* 消息类型 */
    Integer8 logInterMessagePeriod;  /* 发送间隔 */
    UInteger32 durationField;  /* 授权时长(秒) */
    uint8_t reserved;
    uint8_t flags;             /* 标志 */
};

单播协商流程

请求单播传输:

1. 从时钟发送REQUEST:
┌─────────┬──────────┬──────────┬──────────┬──────────┐
│Type     │Length    │MsgType   │Interval  │Duration  │
│0x0004   │0x000A    │SYNC      │0         │300       │
└─────────┴──────────┴──────────┴──────────┴──────────┘
请求:发送Sync消息,间隔1秒(logInterval=0),持续300秒

2. 主时钟响应GRANT:
┌─────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│Type     │Length    │MsgType   │Interval  │Duration  │Flags     │
│0x0005   │0x000C    │SYNC      │0         │300       │0         │
└─────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
同意:按请求参数授权

3. 取消单播:

从时钟发送CANCEL:
┌─────────┬──────────┬──────────┬──────────┐
│Type     │Length    │MsgType   │Reserved  │
│0x0006   │0x0004    │SYNC      │0         │
└─────────┴──────────┴──────────┴──────────┘

主时钟响应ACK:
┌─────────┬──────────┬──────────┬──────────┐
│Type     │Length    │MsgType   │Reserved  │
│0x0007   │0x0004    │SYNC      │0         │
└─────────┴──────────┴──────────┴──────────┘

字节序转换

为什么需要字节序转换

网络字节序:大端(Big-Endian)
主机字节序:小端(Little-Endian,x86/ARM)

PTP协议规定:
- 所有消息字段使用网络字节序
- 发送前:主机序 → 网络序
- 接收后:网络序 → 主机序

示例:
主机值:0x12345678
网络序:12 34 56 78(大端)
主机序:78 56 34 12(小端)

转换:
htons:host to network short(16位)
htonl:host to network long(32位)
ntohs:network to host short
ntohl:network to host long

TLV字节序转换宏

/* tlv.c, 第29-32行 */

#define HTONS(x) (x) = htons(x)
#define HTONL(x) (x) = htonl(x)
#define NTOHS(x) (x) = ntohs(x)
#define NTOHL(x) (x) = ntohl(x)

64位字节序转换

/* tlv.c, 第97-113行 */

static int64_t host2net64_unaligned(void *p)
{
    int64_t v;
    memcpy(&v, p, sizeof(v));
    v = host2net64(v);
    memcpy(p, &v, sizeof(v));
    return v;
}

static int64_t net2host64_unaligned(void *p)
{
    int64_t v;
    memcpy(&v, p, sizeof(v));
    v = net2host64(v);
    memcpy(p, &v, sizeof(v));
    return v;
}

为什么用memcpy

对齐问题:

某些平台(如ARM)要求内存访问对齐:
- 32位访问:地址4字节对齐
- 64位访问:地址8字节对齐

PTP消息可能未对齐:
- 消息打包紧凑
- 字段可能在对齐边界之外

直接访问未对齐地址:
- x86:可以工作(性能略降)
- ARM:可能导致SIGBUS崩溃

使用memcpy:
- 安全处理未对齐地址
- 编译器优化后效率高

时间戳字节序转换

/* tlv.c, 第58-70行 */

static void timestamp_host2net(struct Timestamp *t)
{
    HTONL(t->seconds_lsb);
    HTONS(t->seconds_msb);
    HTONL(t->nanoseconds);
}

static void timestamp_net2Host(struct Timestamp *t)
{
    NTOHL(t->seconds_lsb);
    NTOHS(t->seconds_msb);
    NTOHL(t->nanoseconds);
}

PTP时间戳结构

struct Timestamp {
    UInteger48 seconds;      /* 48位秒 */
    UInteger32 nanoseconds;  /* 32位纳秒 */
};

/* 实际存储 */
struct Timestamp {
    uint16_t seconds_msb;    /* 秒的高16位 */
    uint32_t seconds_lsb;    /* 秒的低32位 */
    uint32_t nanoseconds;    /* 纳秒部分 */
};

TLV接收处理

tlv_post_recv函数

/* tlv.c, 第1145-1224行 */

int tlv_post_recv(struct tlv_extra *extra)
{
    struct TLV *tlv = extra->tlv;
    int result = 0;

    switch (tlv->type) {
    case TLV_MANAGEMENT:
        /* 管理TLV */
        mgt = (struct management_tlv *) tlv;
        mgt->id = ntohs(mgt->id);    /* 转换ID */
        result = mgt_post_recv(mgt, tlv->length - sizeof(mgt->id), extra);
        break;

    case TLV_MANAGEMENT_ERROR_STATUS:
        /* 错误TLV */
        mes = (struct management_error_status *) tlv;
        mes->error = ntohs(mes->error);
        mes->id = ntohs(mes->id);
        break;

    case TLV_REQUEST_UNICAST_TRANSMISSION:
    case TLV_GRANT_UNICAST_TRANSMISSION:
    case TLV_CANCEL_UNICAST_TRANSMISSION:
    case TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION:
        /* 单播协商TLV */
        result = unicast_negotiation_post_recv(extra);
        break;

    case TLV_PATH_TRACE:
        /* 路径追踪TLV */
        if (tlv_array_invalid(tlv, 0, sizeof(struct ClockIdentity))) {
            goto bad_length;
        }
        break;

    case TLV_ALTERNATE_TIME_OFFSET_INDICATOR:
        /* 备用时间偏移TLV */
        result = alttime_offset_post_recv(extra);
        break;

    case TLV_AUTHENTICATION:
        /* 认证TLV */
        result = auth_post_recv(extra);
        break;

    /* ... 其他类型 ... */
    }

    return result;
bad_length:
    return -EBADMSG;
}

处理流程

TLV接收处理流程:

1. 接收原始字节流
2. 检查TLV类型
3. 根据类型分发处理函数
4. 验证长度有效性
5. 字节序转换
6. 解析数据字段
7. 返回处理结果

关键点:
- 先验证长度,防止缓冲区溢出
- 使用memcpy处理未对齐字段
- 根据类型选择处理函数

管理TLV接收处理

/* tlv.c, 第164-512行 - 简化版 */

static int mgt_post_recv(struct management_tlv *m, uint16_t data_len,
                         struct tlv_extra *extra)
{
    switch (m->id) {
    case MID_CLOCK_DESCRIPTION:
        /* 时钟描述 - 复杂解析 */
        cd = &extra->cd;
        buf = m->data;
        
        /* 解析各个变长字段 */
        cd->clockType = (UInteger16 *) buf;
        buf += sizeof(*cd->clockType);
        flip16(cd->clockType);    /* 字节序转换 */
        
        cd->physicalLayerProtocol = (struct PTPText *) buf;
        buf += sizeof(struct PTPText) + cd->physicalLayerProtocol->length;
        
        /* ... 继续解析其他字段 ... */
        break;

    case MID_DEFAULT_DATA_SET:
        /* 默认数据集 */
        if (data_len != sizeof(struct defaultDS))
            goto bad_length;
        
        dds = (struct defaultDS *) m->data;
        dds->numberPorts = ntohs(dds->numberPorts);
        dds->clockQuality.offsetScaledLogVariance =
            ntohs(dds->clockQuality.offsetScaledLogVariance);
        break;

    case MID_CURRENT_DATA_SET:
        /* 当前数据集 */
        if (data_len != sizeof(struct currentDS))
            goto bad_length;
        
        cds = (struct currentDS *) m->data;
        cds->stepsRemoved = ntohs(cds->stepsRemoved);
        cds->offsetFromMaster = net2host64(cds->offsetFromMaster);
        cds->meanPathDelay = net2host64(cds->meanPathDelay);
        break;

    case MID_TIME_STATUS_NP:
        /* LinuxPTP扩展:时间状态 */
        if (data_len != sizeof(struct time_status_np))
            goto bad_length;
        
        tsn = (struct time_status_np *) m->data;
        tsn->master_offset = net2host64(tsn->master_offset);
        tsn->ingress_time = net2host64(tsn->ingress_time);
        tsn->cumulativeScaledRateOffset = ntohl(tsn->cumulativeScaledRateOffset);
        tsn->gmPresent = ntohl(tsn->gmPresent);
        break;
    }

    return 0;
bad_length:
    return -EBADMSG;
}

变长字段解析

/* CLOCK_DESCRIPTION的变长解析 */

case MID_CLOCK_DESCRIPTION:
    cd = &extra->cd;
    buf = m->data;
    len = data_len;

    /* 1. clockType(固定2字节) */
    cd->clockType = (UInteger16 *) buf;
    buf += 2; len -= 2;
    flip16(cd->clockType);

    /* 2. physicalLayerProtocol(PTPText变长) */
    cd->physicalLayerProtocol = (struct PTPText *) buf;
    buf += 1 + cd->physicalLayerProtocol->length;
    len -= 1 + cd->physicalLayerProtocol->length;

    /* 3. physicalAddress(PhysicalAddress变长) */
    cd->physicalAddress = (struct PhysicalAddress *) buf;
    u16 = flip16(&cd->physicalAddress->length);
    buf += 2 + u16;
    len -= 2 + u16;

    /* ... 其他字段类似 ... */

PTPText结构:
struct PTPText {
    UInteger8 length;    /* 文本长度 */
    Octet text[0];       /* 文本内容 */
};

PhysicalAddress结构:
struct PhysicalAddress {
    UInteger16 length;   /* 地址长度 */
    Octet address[0];    /* 地址内容 */
};

TLV发送处理

tlv_pre_send函数

/* tlv.c, 第1226-1291行 */

void tlv_pre_send(struct TLV *tlv, struct tlv_extra *extra)
{
    struct management_tlv *mgt;

    switch (tlv->type) {
    case TLV_MANAGEMENT:
        mgt = (struct management_tlv *) tlv;
        if (tlv->length > sizeof(mgt->id))
            mgt_pre_send(mgt, extra);    /* 处理数据 */
        mgt->id = htons(mgt->id);        /* 转换ID */
        break;

    case TLV_MANAGEMENT_ERROR_STATUS:
        mes = (struct management_error_status *) tlv;
        mes->error = htons(mes->error);
        mes->id = htons(mes->id);
        break;

    case TLV_ORGANIZATION_EXTENSION:
        org_pre_send((struct organization_tlv *) tlv);
        break;

    case TLV_REQUEST_UNICAST_TRANSMISSION:
    case TLV_GRANT_UNICAST_TRANSMISSION:
    case TLV_CANCEL_UNICAST_TRANSMISSION:
    case TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION:
        unicast_negotiation_pre_send(tlv);
        break;

    /* ... 其他类型 ... */
    }
}

管理TLV发送处理

/* tlv.c, 第514-692行 - 简化版 */

static void mgt_pre_send(struct management_tlv *m, struct tlv_extra *extra)
{
    switch (m->id) {
    case MID_CLOCK_DESCRIPTION:
        if (extra) {
            cd = &extra->cd;
            flip16(cd->clockType);
            flip16(&cd->physicalAddress->length);
            flip16(&cd->protocolAddress->networkProtocol);
            flip16(&cd->protocolAddress->addressLength);
        }
        break;

    case MID_DEFAULT_DATA_SET:
        dds = (struct defaultDS *) m->data;
        dds->numberPorts = htons(dds->numberPorts);
        dds->clockQuality.offsetScaledLogVariance =
            htons(dds->clockQuality.offsetScaledLogVariance);
        break;

    case MID_CURRENT_DATA_SET:
        cds = (struct currentDS *) m->data;
        cds->stepsRemoved = htons(cds->stepsRemoved);
        cds->offsetFromMaster = host2net64(cds->offsetFromMaster);
        cds->meanPathDelay = host2net64(cds->meanPathDelay);
        break;

    case MID_TIME_STATUS_NP:
        tsn = (struct time_status_np *) m->data;
        tsn->master_offset = host2net64(tsn->master_offset);
        tsn->ingress_time = host2net64(tsn->ingress_time);
        tsn->cumulativeScaledRateOffset = htonl(tsn->cumulativeScaledRateOffset);
        tsn->gmPresent = htonl(tsn->gmPresent);
        break;
    }
}

发送流程

TLV发送流程:

1. 应用层填充数据(主机字节序)
2. 调用tlv_pre_send
3. 根据ID类型处理
4. 转换多字节字段为网络字节序
5. 发送

注意:
- 单字节字段不需要转换
- 字符串字段不需要转换
- 数值字段需要转换

TLV内存管理

tlv_extra结构

/* tlv.h, 第469-476行 */

struct tlv_extra {
    TAILQ_ENTRY(tlv_extra) list;    /* 链表节点 */
    struct TLV *tlv;                /* 指向TLV */
    union {
        struct mgmt_clock_description cd;    /* 时钟描述 */
        struct nsm_resp_tlv_foot *foot;      /* NSM响应尾部 */
    };
};

为什么需要tlv_extra

TLV消息可能包含复杂结构:
- 变长字段
- 嵌套结构
- 需要额外解析

tlv_extra的作用:
- 保存解析后的指针
- 避免重复解析
- 管理额外内存

示例:
CLOCK_DESCRIPTION包含多个PTPText:
- physicalLayerProtocol
- productDescription
- userDescription

解析后,tlv_extra->cd保存各个指针。

内存池

/* tlv.c, 第41-42行 */

static TAILQ_HEAD(tlv_pool, tlv_extra) tlv_pool =
    TAILQ_HEAD_INITIALIZER(tlv_pool);
/* tlv.c, 第1117-1143行 */

struct tlv_extra *tlv_extra_alloc(void)
{
    struct tlv_extra *extra = TAILQ_FIRST(&tlv_pool);

    if (extra) {
        /* 从池中获取 */
        TAILQ_REMOVE(&tlv_pool, extra, list);
    } else {
        /* 池为空,新分配 */
        extra = calloc(1, sizeof(*extra));
    }
    return extra;
}

void tlv_extra_recycle(struct tlv_extra *extra)
{
    memset(extra, 0, sizeof(*extra));
    TAILQ_INSERT_HEAD(&tlv_pool, extra, list);
}

void tlv_extra_cleanup(void)
{
    struct tlv_extra *extra;

    while ((extra = TAILQ_FIRST(&tlv_pool)) != NULL) {
        TAILQ_REMOVE(&tlv_pool, extra, list);
        free(extra);
    }
}

内存池的优点

PTP消息处理频繁:
- 每秒多个消息
- 每个消息可能有TLV
- 频繁分配/释放影响性能

内存池优化:
1. 使用后不释放,放回池中
2. 下次使用时从池中取
3. 避免频繁malloc/free
4. 减少内存碎片

适用场景:
- 频繁创建/销毁的对象
- 对象大小固定
- 短生命周期对象

组织扩展TLV

organization_tlv结构

/* tlv.h, 第261-266行 */

struct organization_tlv {
    Enumeration16 type;    /* TLV类型 */
    UInteger16 length;
    Octet id[3];           /* 组织ID(OUI) */
    Octet subtype[3];      /* 子类型 */
    /* 数据跟随 */
};

组织ID(OUI)

/* tlv.h, 第255-259行 */

#define IEEE_802_1_COMMITTEE 0x00, 0x80, 0xC2
uint8_t ieee8021_id[3] = { IEEE_802_1_COMMITTEE };

#define IEEE_C37_238_PROFILE 0x1C, 0x12, 0x9D
uint8_t ieeec37_238_id[3] = { IEEE_C37_238_PROFILE };

#define ITU_T_COMMITTEE 0x00, 0x19, 0xA7
uint8_t itu_t_id[3] = { ITU_T_COMMITTEE };
OUI(Organizationally Unique Identifier):

IEEE 802.1:00-80-C2
- 802.1工作组
- 定义AVB、TSN等

IEEE C37.238:1C-12-9D
- 电力Profile
- 用于电力系统

ITU-T:00-19-A7
- ITU电信标准化部门
- 定义电信相关扩展

组织扩展TLV结构:
┌─────────┬──────────┬─────────┬─────────┬──────────┐
│Type     │Length    │OUI      │Subtype  │Data      │
│0x0003   │N         │3 bytes  │3 bytes  │Variable  │
└─────────┴──────────┴─────────┴─────────┴──────────┘

follow_up_info_tlv

/* tlv.h, 第342-351行 */

struct follow_up_info_tlv {
    Enumeration16 type;
    UInteger16 length;
    Octet id[3];          /* IEEE 802.1 OUI */
    Octet subtype[3];     /* 子类型=1 */
    Integer32 cumulativeScaledRateOffset;
    UInteger16 gmTimeBaseIndicator;
    ScaledNs lastGmPhaseChange;
    Integer32 scaledLastGmPhaseChange;
};

Follow-Up Info TLV作用

Follow-Up Info TLV携带额外同步信息:

cumulativeScaledRateOffset:
- 累积频率偏差
- 用于频率同步

gmTimeBaseIndicator:
- GM时间基准指示
- 检测GM切换

lastGmPhaseChange:
- 上次GM相位变化
- 用于相位追踪

用途:
- 透明时钟
- 边界时钟
- 多跳同步

实战示例

构造管理请求

/* 示例:构造GET CURRENT_DATA_SET请求 */

struct ptp_message *msg;
struct management_tlv *mgt;
struct tlv_extra *extra;

/* 分配消息 */
msg = msg_allocate();
if (!msg) return -ENOMEM;

/* 设置消息头 */
msg->header.messageType = MANAGEMENT;
msg->header.messageLength = sizeof(struct management_msg);
msg->header.domainNumber = 0;

/* 设置目标端口 */
msg->management.targetPortIdentity.clockIdentity = ...;
msg->management.targetPortIdentity.portNumber = ...;

/* 设置动作 */
msg->management.header.actionField = GET;

/* 构造TLV */
mgt = (struct management_tlv *) msg->management.suffix;
mgt->type = TLV_MANAGEMENT;
mgt->length = sizeof(mgt->id);    /* 只有ID,无数据 */
mgt->id = MID_CURRENT_DATA_SET;

/* 更新消息长度 */
msg->header.messageLength += sizeof(struct TLV) + mgt->length;

/* 发送前处理 */
tlv_pre_send((struct TLV *)mgt, NULL);
msg_pre_send(msg);

解析管理响应

/* 示例:解析CURRENT_DATA_SET响应 */

struct currentDS *cds;
struct management_tlv *mgt;

/* 接收后处理 */
tlv_post_recv(extra);

mgt = (struct management_tlv *) msg->management.suffix;
if (mgt->id == MID_CURRENT_DATA_SET) {
    cds = (struct currentDS *) mgt->data;
    
    /* 此时数据已是主机字节序 */
    printf("stepsRemoved: %d\n", cds->stepsRemoved);
    printf("offsetFromMaster: %lld ns\n", cds->offsetFromMaster);
    printf("meanPathDelay: %lld ns\n", cds->meanPathDelay);
}

小结:TLV处理的核心要点

TLV结构

  • Type-Length-Value格式
  • 可扩展,灵活

类型分类

  • 标准、安全、组织扩展、Profile

字节序转换

  • 接收后转主机序
  • 发送前转网络序
  • 注意对齐问题

内存管理

  • tlv_extra结构
  • 内存池优化

主要TLV

  • 管理TLV:GET/SET操作
  • 单播协商TLV
  • 路径追踪TLV
  • 组织扩展TLV

下集预告

TLV解决了"如何携带扩展信息",但管理协议如何工作?

下一节,我们将分析管理协议与pmc工具——看看如何通过管理消息配置PTP设备。

【悬念留给3.10】

pmc是LinuxPTP的管理客户端工具。

它如何与ptp4l通信?

如何读取和配置参数?

如何诊断PTP问题?

下一节,揭开管理协议的面纱。

📚 本文内容摘自本人的开源书《PTP技术书 - 从思想实验到协议实现》

全书从时间本质的思想实验出发,深度解析 IEEE 1588 协议、逐章分析 LinuxPTP 源码,并带你动手实现一个轻量级 PTP 程序(ptp-lite)。

🔗 在线阅读/下载:ptp-book

git clone https://github.com/Lularible/ptp-book.git

⭐ 如果对您有帮助,欢迎 Star 支持,也欢迎通过 GitHub Issues 交流讨论。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐