owned this note
owned this note
Published
Linked with GitHub
---
title: "[行動網路安全] Project2 程式說明"
tags: Mobile Network Security
---
[行動網路安全] Project2 程式說明
===
- Project2 說明: <https://hackmd.io/QLSj036NSAmIHJXDA3SfQA?both>
## Todo Check List
### ./src/dev.c
- 填寫 struct `sockaddr_ll addr`,用於在函式 `set_sock_fd` 中 bind
- 把整個訊框儲存到 `self->frame`
```c=
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <arpa/inet.h>
#include "dev.h"
#include "net.h"
#include "esp.h"
#include "replay.h"
#include "transport.h"
// 結構 ifreq 定義在 /usr/include/net/if.h,用來配置和獲取ip位址、遮罩及 MTU 等接口訊息。
inline static int get_ifr_mtu(struct ifreq *ifr)
{
int fd;
// 建立 socket
// PF_PACKET 17,Packet family 底層資料包介面
// SOCK_RAW 3,ip Raw protocol interface,可以接收到本機網路卡上的資料訊框或者資料包,用於監聽網路的流量和分析
// IPPROTO_IP,Dummy protocol for TCP
if ((fd = socket(PF_PACKET, SOCK_RAW, 0)) < 0) {
perror("socket()");
exit(EXIT_FAILURE); // 回傳 1,表示沒有成功執行
}
// ioctl 是設備驅動程序中對設備的 I/O 通道進行管理的函數
if (ioctl(fd, SIOCGIFMTU, ifr) < 0) {
perror("ioctl()");
close(fd);
exit(EXIT_FAILURE);
}
// 回傳 ifr 的 mtu (最大傳輸單元)
return ifr->ifr_mtu;
}
inline static struct sockaddr_ll init_addr(char *name)
{
struct sockaddr_ll addr;
// 把記憶體字符串的前 n 個 bytes 變為 0
// void bzero(void *s, int n);
bzero(&addr, sizeof(addr));
// 如果要從指定乙太網路介面上的獲取封包時,
// 在 struct sockaddr_ll 中指定網路介面,绑定 (bind) 封包到該介面上。
// 只有 sll_protocol 和 sll_ifindex 這兩個位址字段是用來 bind 的。
// [TODO]: Fill up struct sockaddr_ll addr which will be used to bind in func set_sock_fd
addr.sll_ifindex = *name;
addr.sll_family = PF_PACKET;
addr.sll_protocol = htons(ETH_P_ALL);
if (addr.sll_ifindex == 0) {
perror("if_nameindex()");
exit(EXIT_FAILURE);
}
return addr;
}
inline static int set_sock_fd(struct sockaddr_ll dev)
{
// dev: self->addr
int fd;
// 接收完整的資料鏈結層訊框
if ((fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
perror("socket()");
exit(EXIT_FAILURE);
}
bind(fd, (struct sockaddr *)&dev, sizeof(dev));
return fd;
}
void fmt_frame(Dev *self, Net net, Esp esp, Txp txp)
{
// 把訊框存入 self->frame,以及其長度存入 self->framelen
// [TODO]: store the whole frame into self->frame
// and store the length of the frame into self->framelen
// self->framelen = LINKHDRLEN + net.hdrlen + sizeof(EspHeader) + txp.hdrlen + txp.plen + sizeof(EspTrailer) + esp.authlen;
self->frame = self->linkhdr;
self->framelen = LINKHDRLEN + net.hdrlen + net.plen;
}
ssize_t tx_frame(Dev *self)
{
if (!self) {
fprintf(stderr, "Invalid arguments of %s.", __func__);
return -1;
}
// ssize_t 是 signed size_t
ssize_t nb;
// socklen_t 和 int 相同
socklen_t addrlen = sizeof(self->addr);
// sendto() 將資料由指定的 socket 傳給對方主機,參數 s 為已建好連線的 socket。
nb = sendto(self->fd, self->frame, self->framelen,
0, (struct sockaddr *)&self->addr, addrlen);
if (nb <= 0) perror("sendto()");
return nb;
}
ssize_t rx_frame(Dev *self)
{
if (!self) {
fprintf(stderr, "Invalid arguments of %s.", __func__);
return -1;
}
ssize_t nb;
socklen_t addrlen = sizeof(self->addr);
nb = recvfrom(self->fd, self->frame, self->mtu,
0, (struct sockaddr *)&self->addr, &addrlen);
if (nb <= 0)
perror("recvfrom()");
return nb;
}
void init_dev(Dev *self, char *dev_name)
{
if (!self || !dev_name || strlen(dev_name) + 1 > IFNAMSIZ) {
fprintf(stderr, "Invalid arguments of %s.", __func__);
exit(EXIT_FAILURE);
}
struct ifreq ifr;
// sprintf 用來格式化字串
// snprintf 限制最大的字串長度
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", dev_name);
self->mtu = get_ifr_mtu(&ifr);
self->addr = init_addr(dev_name);
self->fd = set_sock_fd(self->addr);
self->frame = (uint8_t *)malloc(BUFSIZE * sizeof(uint8_t));
self->framelen = 0;
self->fmt_frame = fmt_frame;
self->tx_frame = tx_frame;
self->rx_frame = rx_frame;
self->linkhdr = (uint8_t *)malloc(LINKHDRLEN);
}
```
#### 說明
- [第19行]
ifreq: 用於 socket ioctl 的介面請求結構。所有介面 ioctl 必須具有以 ifr_name 開頭的參數定義。其餘可能是特定於介面的。
```c
struct ifreq
{
# define IFHWADDRLEN 6
# define IFNAMSIZ IF_NAMESIZE
union{
char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "en0". */
} ifr_ifrn;
union{
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short int ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
__caddr_t ifru_data;
} ifr_ifru;
};
```
- [第30行]
ioctl: 系統調用操作特殊檔案的底層裝置參數。特別是,字元特殊檔案(例如終端機)的許多操作特性可以透過 ioctl() 請求進行控制。參數 fd 必須是 open file descriptor。第二個參數是裝置相關的請求程式碼。第三個參數是指向記憶體的無型別指針。傳統上它是 `char *argp`(從 void * 是有效 C 之前的日子開始)。
```c=
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
```
- [第50行]
```c=
struct sockaddr_ll {
unsigned short sll_family; /* Always AF_PACKET */
unsigned short sll_protocol; /* Physical-layer protocol */
int sll_ifindex; /* Interface number */
unsigned short sll_hatype; /* ARP hardware type */
unsigned char sll_pkttype; /* Packet type */
unsigned char sll_halen; /* Length of address */
unsigned char sll_addr[8]; /* Physical-layer address */
};
```
sll_protocol: 標準乙太網路協定類型,按照網路位元組順序。
sll_ifindex: interface 索引,0 代表所有的網路介面。
- [第75行]
```c=
struct dev {
int mtu;
struct sockaddr_ll addr;
int fd;
unit8_t *frame;
unit16_t *framelen;
unit8_t *linkhdr;
void (*fmt_frame)(Dev *self, Net net, Esp esp, Txp txp);
ssize_t (*tx_frame)(Dev *self);
ssize_t (*rx_frame)(Dev *self);
};
struct esp{
EspHeader hdr;
unit8_t *pl; // ESP payload
size_t plen; // ESP payload length
unit8_t *pad; // ESP padding
EspTrailer tlr;
unit8_t *auth;
size_t authlen;
unit8_t *esp_key;
unit8_t *(*set_padpl)(Esp *self);
unit8_t *(*set_auth)(Esp *self,
ssize_t (*hmac)(unit8_t const * size_t,
unit8_t const *, size_t
unit8_t *));
void (*get_key)(Esp *self);
unit8_t *(*dissect)(Esp *self, unit8_t *esp_pkt, size_t esp_len);
Esp *(*fmt_rep)(Esp *self, Proto p);
};
typedef enum proto {
UNKN_PROTO = 0,
IPv4 = IPPROTO_IP, // 0, Dummy protocol for TCP.
ESP = IPPROTO_ESP, // 50, ESP.
TCP = IPPROTO_TCP, // 6, Transmission Control Protocol.
} Proto;
struct net{
char *src_ip;
char *dst_ip;
char *x_src_ip; /* Expected src IP addr */
char *x_dsc_ip; /* Expected dst IP addr */
struct iphdr ip4hdr;
size_t hdrlen;
unit16_t plen;
Proto pro;
unit8_t *(*dissect)(Net *self, unit8_t *pkt, size_t pkt_len);
Net *(*fmt_req)(Net *self);
};
```
### ./src/transport.c
- 完成 TCP checksum 計算
- 收集 `segm` 資訊
- 填寫 `self->tcphdr`
```c=
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "net.h"
#include "transport.h"
uint16_t cal_tcp_cksm(struct iphdr iphdr, struct tcphdr tcphdr, uint8_t *pl, int plen)
{
// [TODO]: Finish TCP checksum calculation
// 計算 TCP checksum
// 參考: https://gist.github.com/david-hoze/0c7021434796997a4ca42d7731a7073a
uint16_t cksm = 0;
size_t tcpLen;
tcpLen = sizeof(tcphdr) + plen;
//add the pseudo header
//the source ip
cksm += (iphdr.saddr >> 16) & 0xFFFF;
cksm += (iphdr.saddr) & 0xFFFF;
//the dest ip
cksm += (iphdr.daddr >> 16) & 0xFFFF;
cksm += (iphdr.daddr) & 0xFFFF;
//protocol and reserved: 6
cksm += htons(IPPROTO_TCP);
//the length
cksm += htons(tcpLen);
//add the IP payload
while (tcpLen > 1) {
cksm += * pl++;
tcpLen -= 2;
}
//if any bytes left, pad the bytes and add
if(tcpLen > 0) {
//printf("+++++++++++padding, %dn", tcpLen);
cksm += ((*pl)&htons(0xFF00));
}
//Fold 32-bit sum to 16 bits: add carrier to result
while (cksm >> 16) {
cksm = (cksm & 0xffff) + (cksm >> 16);
}
cksm = ~cksm;
//return computation result
return cksm
}
uint8_t *dissect_tcp(Net *net, Txp *self, uint8_t *segm, size_t segm_len)
{
// [TODO]: Collect information from segm
// (Check IP addr & port to determine the next seq and ack value)
// Return payload of TCP
self->pl = segm + self->hdrlen;
// reference from replay.c
struct tcphdr *thdr = (struct tcphdr *)(segm);
self->thdr.th_sport = *segm;
self->thdr.th_dport = *(segm+2);
self->thdr.th_seq = *(segm+4);
self->thdr.th_ack = *(segm+8);
self->thdr.th_win = *(segm+14);
self->thdr.th_sum = *(segm+16);
self->thdr.th_urp = *(segm+18);
if(strcmp(net->x_src_ip, net->src_ip) == 0) {
self->x_tx_seq = ntohl(self->thdr.th_seq) + self->plen;
self->x_tx_ack = ntohl(self->thdr.th_ack);
self->x_src_port = ntohs(self->thdr.th_sport);
self->x_dst_port = ntohs(self->thdr.th_dport);
}
if (strcmp(net->x_src_ip, net->dst_ip) == 0) {
self->x_tx_seq = ntohl(self->thdr.th_ack);
self->x_tx_ack = ntohl(self->thdr.th_seq) + txp->plen;
self->x_src_port = ntohs(self->thdr.th_dport);
self->x_dst_port = ntohs(self->thdr.th_sport);
}
return self->pl;
}
Txp *fmt_tcp_rep(Txp *self, struct iphdr iphdr, uint8_t *data, size_t dlen)
{
// [TODO]: Fill up self->tcphdr (prepare to send)
// https://stackoverflow.com/questions/36292016/c-create-tcp-syn-without-filling-ip-header-using-raw-socket
//struct tcphdr *tcp = (struct tcphdr *) dlen + sizeof(iphdr);
//tcp->source = htons(atoi("6668")); // source port
//tcp->dest = htons(atoi("3868")); // destination port
self->thdr.source = htons(self->x_src_port);
self->thdr.dest = htons(self->x_dst_port);
self->thdr.seq = htons(self->x_tx_seq); // inital sequence number
self->thdr.ack_seq = htons(self->x_tx_ack); // acknowledgement number
self->thdr.ack = 1; // acknowledgement flag
self->thdr.syn = 1; // synchronize flag
self->thdr.rst = 0; // reset flag
self->thdr.psh = 1; // push flag
self->thdr.fin = 0; // finish flag
self->thdr.urg = 0; // urgent flag
self->thdr.check = 0; // tcp checksum
self->thdr.doff = 0; // data offset
//self->tcphdr = tcp;
return self;
}
inline void init_txp(Txp *self)
{
self->pl = (uint8_t *)malloc(IP_MAXPACKET * sizeof(uint8_t));
self->hdrlen = sizeof(struct tcphdr);
self->dissect = dissect_tcp;
self->fmt_rep = fmt_tcp_rep;
}
```
#### 說明
- Txp
```c=
struct txp {
uint16_t x
}
```
### ./src/net.c
- 完成 IP checksum 計算
- 收集 `pkt` 資訊
- 從 SAD dump 身份驗證密鑰
```c=
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ifaddrs.h>
#include <unistd.h>
#include "net.h"
#include "transport.h"
#include "esp.h"
uint16_t cal_ipv4_cksm(struct iphdr iphdr)
{
// [TODO]: Finish IP checksum calculation
// 參考: https://blog.csdn.net/one_clouder/article/details/52987837
iphdr.check = ip_fast_csum(iphdr, iphdr.ihl);
return iphdr.check;
}
uint8_t *dissect_ip(Net *self, uint8_t *pkt, size_t pkt_len)
{
// [TODO]: Collect information from pkt.
// Return payload of network layer
pkt = pkt + self->hdrlen;
self->plen = pkt_len - self->hdrlen;
return pkt;
}
Net *fmt_net_rep(Net *self)
{
// [TODO]: Fill up self->ip4hdr (prepare to send)
self->ip4hdr.check = cal_ipv4_cksm(self->ip4hdr);
self->ip4hdr.tot_len = self->plen + self->hdrlen;
return self;
}
void init_net(Net *self)
{
if (!self) {
fprintf(stderr, "Invalid arguments of %s.", __func__);
exit(EXIT_FAILURE);
}
self->src_ip = (char *)malloc(INET_ADDRSTRLEN * sizeof(char));
self->dst_ip = (char *)malloc(INET_ADDRSTRLEN * sizeof(char));
self->x_src_ip = (char *)malloc(INET_ADDRSTRLEN * sizeof(char));
self->x_dst_ip = (char *)malloc(INET_ADDRSTRLEN * sizeof(char));
self->hdrlen = sizeof(struct iphdr);
self->dissect = dissect_ip;
self->fmt_rep = fmt_net_rep;
}
```
#### 說明
- iphdr
```c=
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
unsigned int ihl:4;
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4;
unsigned int ihl:4;
#else
#error "Please fix <bits/endian.h>"
#endif
uint16_t tot_len;
uint16_t id;
uint16_t frag_off;
uint8_t ttl;
uint8_t protocol;
uint16_t check;
uint32_t saddr;
uint32_t daddr;
};
```
### ./src/esp.c
- 填寫 `self->pad` 和 `self->pad_len`(參考 RFC4303 Session 2.4)
- 把所有需要驗證的東西都放到 `buff` 並加上 `nb`
- 收集 `esp_pkt` 資訊
- 填寫 ESP `header` 和 `tailer`
```c=
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <linux/pfkeyv2.h>
#include "esp.h"
#include "transport.h"
#include "hmac.h"
EspHeader esp_hdr_rec;
void get_ik(int type, uint8_t *key)
{
// [TODO]: Dump authentication key from security association database (SADB)
// (Ref. RFC2367 Section 2.3.4 & 2.4 & 3.1.10)
// https://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch19lev1sec3.html
/*
* struct sadb_key {
* uint16_t sadb_key_len;
* uint16_t sadb_key_exttype;
* uint16_t sadb_key_bits;
* uint16_t sadb_key_reserved;
* };
*/
/* sizeof(struct sadb_key) == 8 */
/*
struct sadb_key sadb_k;
sadb_k.sadb_key_len = 8;
sadb_k.sadb_key_exttype = SADB_EXT_KEY_AUTH;
sadb_k.sadb_key_bits = 1;
sadb_k.sadb_key_reserved = 32;
*/
key->sadb_key_len = 8;
key->sadb_key_exttype = type;
/* sadb_key_bits: The length of the valid key data, in bits.
* A value of zero in sadb_key_bits MUST cause an error.
*/
key->sadb_key_bits = 1;
key->sadb_key_reserved = 32;
}
void get_esp_key(Esp *self)
{
get_ik(SADB_SATYPE_ESP, self->esp_key);
}
uint8_t *set_esp_pad(Esp *self)
{
// [TODO]: Fiill up self->pad and self->pad_len (Ref. RFC4303 Section 2.4)
uint8_t padding[256 - sizeof(self)] = {0};
self->pad = padding;
self->tlr.pad_len = 256 - sizeof(self);
return self->pad;
}
uint8_t *set_esp_auth(Esp *self,
ssize_t (*hmac)(uint8_t const *, size_t,
uint8_t const *, size_t,
uint8_t *))
{
if (!self || !hmac) {
fprintf(stderr, "Invalid arguments of %s().\n", __func__);
return NULL;
}
uint8_t buff[BUFSIZE];
size_t esp_keylen = 16;
size_t nb = 0; // Number of bytes to be hashed
ssize_t ret;
// [TODO]: Put everything needed to be authenticated into buff and add up nb
ret = hmac(self->esp_key, esp_keylen, buff, nb, self->auth);
if (ret == -1) {
fprintf(stderr, "Error occurs when try to compute authentication data");
return NULL;
}
self->authlen = ret;
return self->auth;
}
uint8_t *dissect_esp(Esp *self, uint8_t *esp_pkt, size_t esp_len)
{
// [TODO]: Collect information from esp_pkt.
// Return payload of ESP
self->pl = esp_pkt + sizeof(EspHeader); // 加上 ESP header 長度後即為 ESP payload
self->plen = esp_len - sizeof(EspHeader);
return self->pl;
}
Esp *fmt_esp_rep(Esp *self, Proto p)
{
// [TODO]: Fill up ESP header and trailer (prepare to send)
// Proto p = TCP = 6;
self->hdr.seq = ntohl(self->hdr.seq) + 1;// Sequence number
self->tlr.pad_len = 3;// Pad Length(8 bits)
self->tlr.nxt = p; // Next Header(8 bits)
return self;
}
void init_esp(Esp *self)
{
self->pl = (uint8_t *)malloc(MAXESPPLEN * sizeof(uint8_t));
self->pad = (uint8_t *)malloc(MAXESPPADLEN * sizeof(uint8_t));
self->auth = (uint8_t *)malloc(HMAC96AUTHLEN * sizeof(uint8_t));
self->authlen = HMAC96AUTHLEN;
self->esp_key = (uint8_t *)malloc(BUFSIZE * sizeof(uint8_t));
self->set_padpl = set_esp_pad;
self->set_auth = set_esp_auth;
self->get_key = get_esp_key;
self->dissect = dissect_esp;
self->fmt_rep = fmt_esp_rep;
}
```
#### 說明
- RFC2367 Section 2.3.4 Key Extension
```c=
struct sadb_key {
uint16_t sadb_key_len;
uint16_t sadb_key_exttype;
uint16_t sadb_key_bits;
uint16_t sadb_key_reserved;
};
/* sizeof(struct sadb_key) == 8 */
```
key extension 有兩種。AUTH 版本與身份驗證密鑰(例如 IPsec AH、OSPF MD5)一起使用,而 ENCRYPT 版本與加密密鑰(例如 IPsec ESP)一起使用。 PF_KEY 只處理完全形成的加密密鑰,而不是「原始密鑰材料」。例如,當使用 ISAKMP/Oakley 時,key management daemon 始終負責將 Diffie-Hellman 計算的結果轉換為不同的完整密鑰,然後再通過 PF_KEY 將這些密鑰發送到 kernel。制定此規則是因為 PF_KEY 主要在支持多種安全協定(不僅僅是 IP security)以及多種密鑰管理方案,包括手動密鑰,它沒有「原始密鑰材料」的概念。一個乾淨的、獨立於協定的介面對於不同操作系統的可移植性以及對不同安全協議的可移植性很重要。
如果演算法定義其密鑰包含 parity bits(例如 DES),那麼與 PF_KEY 一起使用的密鑰也必須包含這些 parity bits。例如,這意味著單個 DES 密鑰始終是 64-bit 數量。
當特定的安全協定只需要一個驗證和/或一個加密密鑰時,使用適當的密鑰擴展傳輸完整的密鑰。當一個特定的安全協定需要多個密鑰來實現相同的功能時(例如,使用 2 或 3 個密鑰的三重 DES 和非對稱演算法),那麼這兩個完全形成的密鑰必須按照用於 outbound 封包處理的順序連接在一起。在多個密鑰的情況下,演算法必須能夠根據提供的資訊確定各個密鑰的長度。總密鑰長度(結合所使用演算法的知識)通常提供足夠的資訊來進行此確定。
密鑰總是按照它們用於 outbound 封包處理的順序通過 PF_KEY 介面。對於 inbound 處理,使用 key 的正確順序可能與用於 PF_KEY 介面的規範串聯順序不同。 在 inbound 和 outbound 處理中以正確的順序使用密鑰是實現的責任。
例如,考慮一對使用 ESP 三密鑰 Triple-DES SA 進行單播通訊的節點。傳送者節點上的 outbound SA 和 接收者節點上的 inbound SA 都將在各自的 ENCRYPT 密鑰擴展中包含密鑰 A,然後是密鑰 B,然後是密鑰 C。 outbound SA 將首先使用密鑰 A,然後是密鑰 B,然後在加密時使用密鑰 C。 inbound SA 將使用密鑰 C,然後是密鑰 B,然後在解密時使用密鑰 A。(Note: 我們知道 3DES 實際上是加密-解密-加密。)密鑰 A、密鑰 B、密鑰 C 的規範順序用於 3DES,並且應該記錄在案。「加密」的順序是這個例子的規範順序。[Sch96]
Key data bits從 most-significant 到 least significant 排列。例如,一個 22-bit 密鑰將佔用三個八位位元組,其中最低有效兩位不包含密鑰材料。然後將使用五個額外的八位位元組填充到下一個 64-bit boundary。
雖然與 PF_KEY 沒有直接關係,但存在與密鑰的奇數十六進位表示有關的使用者界面問題。考慮 16-bit 數字的例子:
0x123
這將需要兩個八位位元組的存儲空間。但是,在沒有其他資訊的情況下,不清楚顯示的值是否存儲為:
01 23 OR 12 30
作者認為形式(0x123 == 0x0123)是解釋這種歧義的更好方法。額外的資訊(例如,指定 0x0123 或 0x1230,或指定這只是一個 12-bit 數字)可以解決這個問題。
- RFC2367 2.4 Illustration of Message Layout
下面顯示了八位元組在 PF_KEY 資訊中表示的方式。Optional 字段也是這樣表示。
base header:

根據各種基本表頭字段的值,基本表頭之後可以跟隨一個或多個以下擴展字段。以下字段是有序的,如果它們出現,它們應該按照下面顯示的順序出現。
擴展字段不得重複。如果存在必須重複擴展的情況,應引起作者的注意。
**The Association extension**

**The Lifetime extension**


**The Address extension**

**The Key extension**

**The Identity extension**

**The Sensitivity extension**

**The Proposal extension**


**The Supported Algorithms extension**

**The SPI Range extension**
