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 Pipe 協議¶
Phonet Pipe 協議是一種簡單的排序資料包協議,具有端到端擁塞控制。 它使用被動監聽套接字範例。 監聽套接字繫結到唯一的空閒物件 ID。 每個監聽套接字最多可以處理 255 個併發連線,每個 accept()’d 套接字一個。
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
是一個只讀整數值。 它包含管道的底層識別符號(“管道控制代碼”)。 這僅針對已連線或正在連線的套接字描述符定義。