Linux Phonet 协议族¶
简介¶
Phonet 是诺基亚蜂窝调制解调器用于 IPC 和 RPC 的一种数据包协议。通过 Linux Phonet 套接字族,Linux 主机进程可以从/向调制解调器或任何连接到调制解调器的外部设备接收和发送消息。调制解调器负责路由。
Phonet 数据包可以通过各种硬件连接进行交换,具体取决于设备,例如
带有 CDC Phonet 接口的 USB,
红外线,
蓝牙,
RS232 串行端口(带有专用的 “FBUS” 行规程),
某些 TI OMAP 处理器的 SSI 总线。
数据包格式¶
Phonet 数据包具有如下所示的通用头部
struct phonethdr {
uint8_t pn_media; /* Media type (link-layer identifier) */
uint8_t pn_rdev; /* Receiver device ID */
uint8_t pn_sdev; /* Sender device ID */
uint8_t pn_res; /* Resource ID or function */
uint16_t pn_length; /* Big-endian message byte length (minus 6) */
uint8_t pn_robj; /* Receiver object ID */
uint8_t pn_sobj; /* Sender object ID */
};
在 Linux 上,链路层头部包括 pn_media 字节(见下文)。接下来的 7 个字节是网络层头部的一部分。
设备 ID 是分开的:6 个高位构成设备地址,而 2 个低位用于多路复用,就像 8 位对象标识符一样。 因此,Phonet 可以被视为具有 6 位地址空间和 10 位传输协议的网络层(很像 IP 世界中的端口号)。
调制解调器的地址号始终为零。所有其他设备都有自己的 6 位地址。
链路层¶
Phonet 链路始终是点对点链路。链路层头部由单个 Phonet 媒体类型字节组成。从调制解调器的角度来看,它唯一地标识了数据包传输所通过的链路。 每个 Phonet 网络设备都应预先添加并根据需要设置媒体类型字节。为了方便起见,提供了一个通用的 phonet_header_ops 链路层头部操作结构。它根据网络设备硬件地址设置媒体类型。
Linux Phonet 网络接口支持专用的链路层数据包类型(ETH_P_PHONET),该类型超出以太网类型的范围。它们只能发送和接收 Phonet 数据包。
虚拟 TUN 隧道设备驱动程序也可以用于 Phonet。这需要 IFF_TUN 模式,_不带_ IFF_NO_PI 标志。在这种情况下,没有链路层头部,因此没有 Phonet 媒体类型字节。
请注意,不允许 Phonet 接口重新排序数据包,因此它们应仅使用(默认)Linux FIFO qdisc。
网络层¶
Phonet 套接字地址族映射 Phonet 数据包头
struct sockaddr_pn {
sa_family_t spn_family; /* AF_PHONET */
uint8_t spn_obj; /* Object ID */
uint8_t spn_dev; /* Device ID */
uint8_t spn_resource; /* Resource or function */
uint8_t spn_zero[...]; /* Padding */
};
资源字段仅在发送和接收时使用;bind() 和 getsockname() 会忽略它。
低级数据报协议¶
应用程序可以使用 PF_PHONET 系列中的 Phonet 数据报套接字协议发送 Phonet 消息。每个套接字都绑定到 2^10 个可用对象 ID 之一,并且可以与任何其他对等方发送和接收数据包。
struct sockaddr_pn addr = { .spn_family = AF_PHONET, };
ssize_t len;
socklen_t addrlen = sizeof(addr);
int fd;
fd = socket(PF_PHONET, SOCK_DGRAM, 0);
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
/* ... */
sendto(fd, msg, msglen, 0, (struct sockaddr *)&addr, sizeof(addr));
len = recvfrom(fd, buf, sizeof(buf), 0,
(struct sockaddr *)&addr, &addrlen);
此协议遵循 SOCK_DGRAM 无连接语义。但是,connect() 和 getpeername() 不受支持,因为它们似乎在 Phonet 用法中没有用处(可以很容易地添加)。
资源订阅¶
一个 Phonet 数据报套接字可以订阅任意数量的 8 位 Phonet 资源,如下所示
uint32_t res = 0xXX;
ioctl(fd, SIOCPNADDRESOURCE, &res);
使用 SIOCPNDELRESOURCE I/O 控制请求或在关闭套接字时,以类似方式取消订阅。
请注意,在任何给定时间,任何给定资源只能订阅一个套接字。如果不是,ioctl() 将返回 EBUSY。
Phonet 管道协议¶
Phonet 管道协议是一个简单的顺序数据包协议,具有端到端拥塞控制。它使用被动侦听套接字范例。侦听套接字绑定到唯一的空闲对象 ID。每个侦听套接字可以处理多达 255 个并发连接,每个 accept() 的套接字一个。
int lfd, cfd;
lfd = socket(PF_PHONET, SOCK_SEQPACKET, PN_PROTO_PIPE);
listen (lfd, INT_MAX);
/* ... */
cfd = accept(lfd, NULL, NULL);
for (;;)
{
char buf[...];
ssize_t len = read(cfd, buf, sizeof(buf));
/* ... */
write(cfd, msg, msglen);
}
传统上,连接是通过 “第三方” 应用程序在两个端点之间建立的。这意味着两个端点都是被动的。
从 Linux 内核版本 2.6.39 开始,也可以通过在主动端使用 connect() 直接连接两个端点。这旨在支持更新的诺基亚无线调制解调器 API,例如在 ST-Ericsson U8500 平台中的诺基亚超薄调制解调器中找到的 API
struct sockaddr_spn spn;
int fd;
fd = socket(PF_PHONET, SOCK_SEQPACKET, PN_PROTO_PIPE);
memset(&spn, 0, sizeof(spn));
spn.spn_family = AF_PHONET;
spn.spn_obj = ...;
spn.spn_dev = ...;
spn.spn_resource = 0xD9;
connect(fd, (struct sockaddr *)&spn, sizeof(spn));
/* normal I/O here ... */
close(fd);
管道协议在 SOL_PNPIPE 级别提供两个套接字选项
PNPIPE_ENCAP 接受一个整数值(int),值为
- PNPIPE_ENCAP_NONE
套接字正常运行(默认)。
- PNPIPE_ENCAP_IP
套接字用作虚拟 IP 接口的后端。这需要 CAP_NET_ADMIN 功能。诺基亚调制解调器上的 GPRS 数据支持可以使用此功能。请注意,在此模式下,无法可靠地 poll() 或从套接字 read()。
- PNPIPE_IFINDEX
是一个只读整数值。它包含 PNPIPE_ENCAP 创建的网络接口的接口索引,如果封装关闭,则为零。
- PNPIPE_HANDLE
是一个只读整数值。它包含管道的底层标识符(“管道句柄”)。这仅针对已连接或正在连接的套接字描述符定义。