Linux內核中的IPSEC實現4(AH封裝)

http://blog.chinaunix.net/uid-127037-id-2919566.html
8. 安全協議

與IPSEC相關的安全協議是AH(51)和ESP(50), IPSEC使用這兩個協議對普通數據包進行封裝, AH只認證不加密, ESP既加密又認證, 當ESP和AH同時使用時, 一般都是先進行ESP封裝, 再進行AH封裝, 因爲AH是對整個IP包進行驗證的, 而ESP只驗證負載部分.

在IPV4下的AH和ESP的協議實現在net/ipv4/ah4.c和net/ipv4/esp4.c中, 每個協議實現實際是要完成兩個結構: struct net_protocol和struct xfrm_type, 前者用於處理接收的該協議類型的IP包, 後者則是IPSEC協議處理.

8.1 AH

8.1.1 初始化

/* net/ipv4/ah4.c */
static int __init ah4_init(void)
{
// 登記AH協議的xfrm協議處理結構
 if (xfrm_register_type(&ah_type, AF_INET) < 0) {
  printk(KERN_INFO "ip ah init: can't add xfrm type\n");
  return -EAGAIN;
 }
// 登記AH協議到IP協議
 if (inet_add_protocol(&ah4_protocol, IPPROTO_AH) < 0) {
  printk(KERN_INFO "ip ah init: can't add protocol\n");
  xfrm_unregister_type(&ah_type, AF_INET);
  return -EAGAIN;
 }
 return 0;
}

8.1.2 IPV4下的AH協議處理結構

// AH協議處理結構, 接收到IPV4包後, 系統根據IP頭中的protocol字段選擇相應的上層協議處理
// 函數, 當IP協議號是51時, 數據包將調用該結構的handler處理函數:
static struct net_protocol ah4_protocol = {
 .handler = xfrm4_rcv,
 .err_handler = ah4_err,
 .no_policy = 1,
};
AH協議結構的handler函數爲xfrm4_rcv, 在net/ipv4/xfrm4_input.c 中定義, 在上一篇中進行了介紹.

// 錯誤處理, 收到ICMP錯誤包時的處理情況, 此時的skb包是ICMP包
static void ah4_err(struct sk_buff *skb, u32 info)
{
// 應用層, data指向ICMP錯誤包裏的內部IP頭
 struct iphdr *iph = (struct iphdr*)skb->data;
// AH頭
 struct ip_auth_hdr *ah = (struct ip_auth_hdr*)(skb->data+(iph->ihl<<2));
 struct xfrm_state *x;
// ICMP錯誤類型檢查, 本處理函數只處理"目的不可達"和"需要分片"兩種錯誤
 if (skb->h.icmph->type != ICMP_DEST_UNREACH ||
     skb->h.icmph->code != ICMP_FRAG_NEEDED)
  return;
// 重新查找SA
 x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, ah->spi, IPPROTO_AH, AF_INET);
 if (!x)
  return;
 printk(KERN_DEBUG "pmtu discovery on SA AH/%08x/%08x\n",
        ntohl(ah->spi), ntohl(iph->daddr));
 xfrm_state_put(x);
}

8.1.3 AH4協議的IPSEC處理結構

// AH4的xfrm協議處理結構
static struct xfrm_type ah_type =
{
 .description = "AH4",
 .owner  = THIS_MODULE,
 .proto       = IPPROTO_AH,
// 狀態初始化
 .init_state = ah_init_state,
// 協議釋放
 .destructor = ah_destroy,
// 協議輸入
 .input  = ah_input,
// 協議輸出
 .output  = ah_output
};
結構的重點是input和ouput函數

8.1.3.1 狀態初始化
ah_data數據結構:
/* include/net/ah.h */
struct ah_data
{
// 密鑰指針
 u8   *key;
// 密鑰長度
 int   key_len;
// 工作初始化向量
 u8   *work_icv;
// 初始化向量完整長度
 int   icv_full_len;
// 初始化向量截斷長度
 int   icv_trunc_len;
// HASH算法
 struct crypto_hash *tfm;
};

// 該函數被xfrm狀態(SA)初始化函數xfrm_init_state調用
// 用來生成SA中所用的AH數據處理結構相關信息
static int ah_init_state(struct xfrm_state *x)
{
 struct ah_data *ahp = NULL;
 struct xfrm_algo_desc *aalg_desc;
 struct crypto_hash *tfm;
// 對AH協議的SA, 認證算法是必須的, 否則就沒法進行AH認證了
 if (!x->aalg)
  goto error;
 /* null auth can use a zero length key */
// 認證算法密鑰長度要大於512
 if (x->aalg->alg_key_len > 512)
  goto error;
// 如果要進行UDP封裝(進行NAT穿越), 錯誤, 因爲AH是不支持NAT的
 if (x->encap)
  goto error;
// 分配ah_data數據結構空間
 ahp = kzalloc(sizeof(*ahp), GFP_KERNEL);
 if (ahp == NULL)
  return -ENOMEM;
// 設置AH數據結構的密鑰和長度
 ahp->key = x->aalg->alg_key;
 ahp->key_len = (x->aalg->alg_key_len+7)/8;
// 分配認證算法HASH結構指針並賦值給AH數據結構
// 算法是固定相同的, 但在每個應用使用算法時的上下文是不同的, 該結構就是描述具體應用
// 時的相關處理的上下文數據的
 tfm = crypto_alloc_hash(x->aalg->alg_name, 0, CRYPTO_ALG_ASYNC);
 if (IS_ERR(tfm))
  goto error;
 ahp->tfm = tfm;
// 設置認證算法密鑰
 if (crypto_hash_setkey(tfm, ahp->key, ahp->key_len))
  goto error;
 
 /*
  * Lookup the algorithm description maintained by xfrm_algo,
  * verify crypto transform properties, and store information
  * we need for AH processing.  This lookup cannot fail here
  * after a successful crypto_alloc_hash().
  */
// 分配算法描述結構
 aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0);
 BUG_ON(!aalg_desc);
 if (aalg_desc->uinfo.auth.icv_fullbits/8 !=
     crypto_hash_digestsize(tfm)) {
  printk(KERN_INFO "AH: %s digestsize %u != %hu\n",
         x->aalg->alg_name, crypto_hash_digestsize(tfm),
         aalg_desc->uinfo.auth.icv_fullbits/8);
  goto error;
 }
// AH數據結構的初始化向量的總長和截斷長度的賦值 
 ahp->icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8;
 ahp->icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/8;
 
 BUG_ON(ahp->icv_trunc_len > MAX_AH_AUTH_LEN);
// 分配初始化向量空間, 沒對其賦值, 其初始值就是隨機值, 這也是初始化向量所需要的
 ahp->work_icv = kmalloc(ahp->icv_full_len, GFP_KERNEL);
 if (!ahp->work_icv)
  goto error;
// AH類型SA中AH頭長度: ip_auth_hdr結構和初始化向量長度, 按8字節對齊 
// 反映在AH封裝操作時要將數據包增加的長度
 x->props.header_len = XFRM_ALIGN8(sizeof(struct ip_auth_hdr) + ahp->icv_trunc_len);
// 如果是通道模式, 增加IP頭長度
 if (x->props.mode == XFRM_MODE_TUNNEL)
  x->props.header_len += sizeof(struct iphdr);
// SA數據指向AH數據結構
 x->data = ahp;
 return 0;
error:
 if (ahp) {
  kfree(ahp->work_icv);
  crypto_free_hash(ahp->tfm);
  kfree(ahp);
 }
 return -EINVAL;
}

8.1.3.2 協議釋放
// 該函數被xfrm狀態(SA)釋放函數xfrm_state_gc_destroy()調用
static void ah_destroy(struct xfrm_state *x)
{
 struct ah_data *ahp = x->data;
 if (!ahp)
  return;
// 釋放初始化向量空間
 kfree(ahp->work_icv);
 ahp->work_icv = NULL;
// 算法描述釋放
 crypto_free_hash(ahp->tfm);
 ahp->tfm = NULL;
// AH數據結構釋放
 kfree(ahp);
}

8.1.3.3 協議輸入

// 接收數據處理, 在xfrm4_rcv_encap()函數中調用
// 進行AH認證, 剝離AH頭
static int ah_input(struct xfrm_state *x, struct sk_buff *skb)
{
 int ah_hlen;
 int ihl;
 int err = -EINVAL;
 struct iphdr *iph;
 struct ip_auth_hdr *ah;
 struct ah_data *ahp;
// IP頭備份空間
 char work_buf[60];
// skb數據包要準備留出AH頭空間
 if (!pskb_may_pull(skb, sizeof(struct ip_auth_hdr)))
  goto out;
// IP上層數據爲AH數據
 ah = (struct ip_auth_hdr*)skb->data;
// SA相關的AH處理數據
 ahp = x->data;
 ah_hlen = (ah->hdrlen + 2) << 2;
// AH頭部長度合法性檢查
 if (ah_hlen != XFRM_ALIGN8(sizeof(struct ip_auth_hdr) + ahp->icv_full_len) &&
     ah_hlen != XFRM_ALIGN8(sizeof(struct ip_auth_hdr) + ahp->icv_trunc_len)) 
  goto out;
// skb數據包要準備留出實際AH頭空間
 if (!pskb_may_pull(skb, ah_hlen))
  goto out;
 /* We are going to _remove_ AH header to keep sockets happy,
  * so... Later this can change. */
// 對於clone的包要複製成獨立包
 if (skb_cloned(skb) &&
     pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
  goto out;
 skb->ip_summed = CHECKSUM_NONE;
// 可能包已經進行了複製, 所以對ah重新賦值
 ah = (struct ip_auth_hdr*)skb->data;
 iph = skb->nh.iph;
// IP頭長度
 ihl = skb->data - skb->nh.raw;
// 備份外部IP頭數據
 memcpy(work_buf, iph, ihl);
// 將IP頭中的一些參數清零, 這些參數不進行認證
 iph->ttl = 0;
 iph->tos = 0;
 iph->frag_off = 0;
 iph->check = 0;
// IP頭長度超過20字節時,處理IP選項參數
 if (ihl > sizeof(*iph)) {
  u32 dummy;
  if (ip_clear_mutable_options(iph, &dummy))
   goto out;
 }
        {
// 認證數據緩衝區
  u8 auth_data[MAX_AH_AUTH_LEN];
// 拷貝數據包中的認證數據到緩衝區
  memcpy(auth_data, ah->auth_data, ahp->icv_trunc_len);
// 包括IP頭部分數據
  skb_push(skb, ihl);
// 計算認證值是否匹配, 非0表示出錯
  err = ah_mac_digest(ahp, skb, ah->auth_data);
// 認證失敗返回錯誤
  if (err)
   goto out;
  err = -EINVAL;
// 複製一定長度的認證數據作爲初始化向量
  if (memcmp(ahp->work_icv, auth_data, ahp->icv_trunc_len)) {
   x->stats.integrity_failed++;
   goto out;
  }
 }
// 將備份的IP頭緩衝區中的協議改爲AH內部包裹的協議
 ((struct iphdr*)work_buf)->protocol = ah->nexthdr;
// 將原來IP頭數據拷貝到原來AH頭後面作爲新IP頭
 skb->h.raw = memcpy(skb->nh.raw += ah_hlen, work_buf, ihl);
// skb包縮減原來的IP頭和AH頭, 以新IP頭作爲數據開始
 __skb_pull(skb, ah_hlen + ihl);
 return 0;
out:
 return err;
}

8.1.3.4 協議輸出

// 發送數據處理, 在xfrm4_output_one()中調用
// 計算AH認證值, 添加AH頭
static int ah_output(struct xfrm_state *x, struct sk_buff *skb)
{
 int err;
 struct iphdr *iph, *top_iph;
 struct ip_auth_hdr *ah;
 struct ah_data *ahp;
// 臨時IP頭緩衝區, 最大IP頭60字節
 union {
  struct iphdr iph;
  char   buf[60];
 } tmp_iph;
// 當前的IP頭將作爲最外部IP頭
 top_iph = skb->nh.iph;
// 臨時IP頭,用於臨時保存IP頭內部分字段數據
 iph = &tmp_iph.iph;
// 將當前IP頭中不進行認證的字段數據複製到臨時IP頭
 iph->tos = top_iph->tos;
 iph->ttl = top_iph->ttl;
 iph->frag_off = top_iph->frag_off;
// 如果有IP選項, 處理IP選項
 if (top_iph->ihl != 5) {
  iph->daddr = top_iph->daddr;
  memcpy(iph+1, top_iph+1, top_iph->ihl*4 - sizeof(struct iphdr));
  err = ip_clear_mutable_options(top_iph, &top_iph->daddr);
  if (err)
   goto error;
 }
// AH頭定位在外部IP頭後面, skb緩衝中已經預留出AH頭的數據部分了, 
// 這是通過mode->output函數預留的, 通常調用type->output前要調用mode->oputput
 ah = (struct ip_auth_hdr *)((char *)top_iph+top_iph->ihl*4);
// AH中的下一個頭用原來的外部IP頭中的協議
 ah->nexthdr = top_iph->protocol;
// 將外部IP頭的不進行認證計算的部分字段清零
 top_iph->tos = 0;
 top_iph->tot_len = htons(skb->len);
 top_iph->frag_off = 0;
 top_iph->ttl = 0;
// IP協議改爲AH
 top_iph->protocol = IPPROTO_AH;
 top_iph->check = 0;
// AH數據處理結構
 ahp = x->data;
// AH頭長度對齊
 ah->hdrlen  = (XFRM_ALIGN8(sizeof(struct ip_auth_hdr) + 
       ahp->icv_trunc_len) >> 2) - 2;
// AH頭參數賦值
 ah->reserved = 0;
// SPI值
 ah->spi = x->id.spi;
// 序列號
 ah->seq_no = htonl(++x->replay.oseq);
// 通知防止重放攻擊處理, 更新序列號
 xfrm_aevent_doreplay(x);
// 對skb進行AH認證值的計算
 err = ah_mac_digest(ahp, skb, ah->auth_data);
 if (err)
  goto error;
// 賦值初始化向量值到認證數據部分
 memcpy(ah->auth_data, ahp->work_icv, ahp->icv_trunc_len);
// 恢復原來IP頭的的不認證部分的值
 top_iph->tos = iph->tos;
 top_iph->ttl = iph->ttl;
 top_iph->frag_off = iph->frag_off;
 if (top_iph->ihl != 5) {
  top_iph->daddr = iph->daddr;
  memcpy(top_iph+1, iph+1, top_iph->ihl*4 - sizeof(struct iphdr));
 }
// 重新計算IP頭的認證值
 ip_send_check(top_iph);
 err = 0;
error:
 return err;
}
 
8.2 ESP

8.2.1 初始化

/* net/ipv4/esp4.c */
static int __init esp4_init(void)
{
// 登記ESP協議的xfrm協議處理結構
 if (xfrm_register_type(&esp_type, AF_INET) < 0) {
  printk(KERN_INFO "ip esp init: can't add xfrm type\n");
  return -EAGAIN;
 }
// 登記ESP協議到IP協議
 if (inet_add_protocol(&esp4_protocol, IPPROTO_ESP) < 0) {
  printk(KERN_INFO "ip esp init: can't add protocol\n");
  xfrm_unregister_type(&esp_type, AF_INET);
  return -EAGAIN;
 }
 return 0;
}

8.2.2 IPV4下的ESP協議處理結構

// ESP協議處理結構, 接收到IPV4包後, 系統根據IP頭中的protocol
// 字段選擇相應的上層協議處理函數, 當IP協議號是50時, 數據包將
// 調用該結構的handler處理函數:
static struct net_protocol esp4_protocol = {
 .handler = xfrm4_rcv,
 .err_handler = esp4_err,
 .no_policy = 1,
};

ESP協議結構的handler函數也是xfrm4_rcv, 在net/ipv4/xfrm4_input.c 中定義, 
在上一篇中進行了介紹.

// 錯誤處理, 收到ICMP錯誤包時的處理情況, 此時的skb包是ICMP包
static void esp4_err(struct sk_buff *skb, u32 info)
{
// 應用層, data指向ICMP錯誤包裏的內部IP頭
 struct iphdr *iph = (struct iphdr*)skb->data;
// ESP頭
 struct ip_esp_hdr *esph = (struct ip_esp_hdr*)(skb->data+(iph->ihl<<2));
 struct xfrm_state *x;
// ICMP錯誤類型檢查, 本處理函數只處理"目的不可達"和"需要分片"兩種錯誤
 if (skb->h.icmph->type != ICMP_DEST_UNREACH ||
     skb->h.icmph->code != ICMP_FRAG_NEEDED)
  return;
// 重新查找SA
 x = xfrm_state_lookup((xfrm_address_t *)&iph->daddr, esph->spi, IPPROTO_ESP, AF_INET);
 if (!x)
  return;
 NETDEBUG(KERN_DEBUG "pmtu discovery on SA ESP/%08x/%08x\n",
   ntohl(esph->spi), ntohl(iph->daddr));
 xfrm_state_put(x);
}

8.2.3 ESP4協議的IPSEC處理結構

static struct xfrm_type esp_type =
{
 .description = "ESP4",
 .owner  = THIS_MODULE,
 .proto       = IPPROTO_ESP,
// 狀態初始化
 .init_state = esp_init_state,
// 協議釋放
 .destructor = esp_destroy,
// 計算最大長度
 .get_max_size = esp4_get_max_size,
// 協議輸入
 .input  = esp_input,
// 協議輸出
 .output  = esp_output
};

8.2.3.1 狀態初始化
esp_data數據結構:
/* include/net/esp.h */
struct esp_data
{
 struct scatterlist  sgbuf[ESP_NUM_FAST_SG];
 /* Confidentiality */
// 加密使用的相關數據
 struct {
// 密鑰
  u8   *key;  /* Key */
// 密鑰長度
  int   key_len; /* Key length */
// 填充長度
  int   padlen;  /* 0..255 */
  /* ivlen is offset from enc_data, where encrypted data start.
   * It is logically different of crypto_tfm_alg_ivsize(tfm).
   * We assume that it is either zero (no ivec), or
   * >= crypto_tfm_alg_ivsize(tfm). */
// 初始化向量長度
  int   ivlen;
// 初始化向量是否初始化標誌
  int   ivinitted;
// 初始化向量
  u8   *ivec;  /* ivec buffer */
// 加密算法
  struct crypto_blkcipher *tfm;  /* crypto handle */
 } conf;
 /* Integrity. It is active when icv_full_len != 0 */
// 認證使用的相關數據
 struct {
// 密鑰
  u8   *key;  /* Key */
// 密鑰長度
  int   key_len; /* Length of the key */
// 初始化向量
  u8   *work_icv;
// 初始化向量全長
  int   icv_full_len;
// 初始化向量截斷長度
  int   icv_trunc_len;
// 初始化向量更新函數, 好象沒用
  void   (*icv)(struct esp_data*,
                                 struct sk_buff *skb,
                                 int offset, int len, u8 *icv);
// HASH算法
  struct crypto_hash *tfm;
 } auth;
};
// ESP的esp_data數據結構初始化
static int esp_init_state(struct xfrm_state *x)
{
 struct esp_data *esp = NULL;
 struct crypto_blkcipher *tfm;
 /* null auth and encryption can have zero length keys */
// 如果有認證算法, 密鑰至少512, ESP的認證處理是可選的, 但在實際中都會使用認證
 if (x->aalg) {
  if (x->aalg->alg_key_len > 512)
   goto error;
 }
// ESP加密算法是必須的
 if (x->ealg == NULL)
  goto error;
// 分配esp_data數據結構空間
 esp = kzalloc(sizeof(*esp), GFP_KERNEL);
 if (esp == NULL)
  return -ENOMEM;
// 如果定義了認證算法, 初始化認證算法參數, 和AH類似
 if (x->aalg) {
  struct xfrm_algo_desc *aalg_desc;
  struct crypto_hash *hash;
// 認證密鑰和長度設置
  esp->auth.key = x->aalg->alg_key;
  esp->auth.key_len = (x->aalg->alg_key_len+7)/8;
// 分配HASH算法的實現
  hash = crypto_alloc_hash(x->aalg->alg_name, 0,
      CRYPTO_ALG_ASYNC);
  if (IS_ERR(hash))
   goto error;
  esp->auth.tfm = hash;
// 設置HASH算法密鑰
  if (crypto_hash_setkey(hash, esp->auth.key, esp->auth.key_len))
   goto error;
// 找到算法描述
  aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0);
  BUG_ON(!aalg_desc);
// 檢查算法初始化向量長度合法性
  if (aalg_desc->uinfo.auth.icv_fullbits/8 !=
      crypto_hash_digestsize(hash)) {
   NETDEBUG(KERN_INFO "ESP: %s digestsize %u != %hu\n",
     x->aalg->alg_name,
     crypto_hash_digestsize(hash),
     aalg_desc->uinfo.auth.icv_fullbits/8);
   goto error;
  }
// 初始化向量的全長和截斷長度
  esp->auth.icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8;
  esp->auth.icv_trunc_len = aalg_desc->uinfo.auth.icv_truncbits/8;
// 分配全長度的初始化向量空間
  esp->auth.work_icv = kmalloc(esp->auth.icv_full_len, GFP_KERNEL);
  if (!esp->auth.work_icv)
   goto error;
 }
// 初始化加密算法相關參數, ESP使用的加密算法都是對稱塊加密算法, 不可能用非對稱算法的
// 加密密鑰
 esp->conf.key = x->ealg->alg_key;
// 加密密鑰長度
 esp->conf.key_len = (x->ealg->alg_key_len+7)/8;
// 分配加密算法的具體實現結構
 tfm = crypto_alloc_blkcipher(x->ealg->alg_name, 0, CRYPTO_ALG_ASYNC);
 if (IS_ERR(tfm))
  goto error;
 esp->conf.tfm = tfm;
// 初始化向量大小
 esp->conf.ivlen = crypto_blkcipher_ivsize(tfm);
// 填充數據長度初始化爲0
 esp->conf.padlen = 0;
// 初始化向量長度非0, 分配具體的初始化向量空間
 if (esp->conf.ivlen) {
  esp->conf.ivec = kmalloc(esp->conf.ivlen, GFP_KERNEL);
  if (unlikely(esp->conf.ivec == NULL))
   goto error;
  esp->conf.ivinitted = 0;
 }
// 設置加密算法密鑰
 if (crypto_blkcipher_setkey(tfm, esp->conf.key, esp->conf.key_len))
  goto error;
// 定義SA中ESP頭部長度: ESP頭加初始化向量長度
// 反映在ESP封裝操作時要將數據包增加的長度
 x->props.header_len = sizeof(struct ip_esp_hdr) + esp->conf.ivlen;
// 如果是通道模式, 還需要增加IP頭長度
 if (x->props.mode == XFRM_MODE_TUNNEL)
  x->props.header_len += sizeof(struct iphdr);
// 如果要進行UDP封裝
 if (x->encap) {
  struct xfrm_encap_tmpl *encap = x->encap;
  switch (encap->encap_type) {
  default:
   goto error;
  case UDP_ENCAP_ESPINUDP:
// 該類型封裝增加UDP頭長度
   x->props.header_len += sizeof(struct udphdr);
   break;
  case UDP_ENCAP_ESPINUDP_NON_IKE:
// 該類型封裝增加UDP頭長度外加加8字節
   x->props.header_len += sizeof(struct udphdr) + 2 * sizeof(u32);
   break;
  }
 }
// 將esp_data作爲SA的data指針
 x->data = esp;
// 追蹤長度, 最大增加長度和當前的計算的增加長度的差值,在路由時會用到
// 對於AH, 由於沒有定義get_max_size(), 該值位0
 x->props.trailer_len = esp4_get_max_size(x, 0) - x->props.header_len;
 return 0;
error:
 x->data = esp;
 esp_destroy(x);
 x->data = NULL;
 return -EINVAL;
}

8.2.3.2 協議釋放

// 該函數被xfrm狀態(SA)釋放函數xfrm_state_gc_destroy()調用
static void esp_destroy(struct xfrm_state *x)
{
 struct esp_data *esp = x->data;
 if (!esp)
  return;
// 釋放加密算法
 crypto_free_blkcipher(esp->conf.tfm);
 esp->conf.tfm = NULL;
// 釋放加密初始化向量
 kfree(esp->conf.ivec);
 esp->conf.ivec = NULL;
// 釋放認證算法
 crypto_free_hash(esp->auth.tfm);
 esp->auth.tfm = NULL;
// 釋放認證初始化向量
 kfree(esp->auth.work_icv);
 esp->auth.work_icv = NULL;
// 釋放esp_data
 kfree(esp);
}

8.2.3.3 計算最大長度

// 在xfrm_state_mtu()函數中調用, 計算最大增加的數據長度
// AH中沒有該函數, 增加的長度使用x->props.header_len
static u32 esp4_get_max_size(struct xfrm_state *x, int mtu)
{
 struct esp_data *esp = x->data;
// 加密塊長度, 按4字節對齊
 u32 blksize = ALIGN(crypto_blkcipher_blocksize(esp->conf.tfm), 4);
 int enclen = 0;
 switch (x->props.mode) {
 case XFRM_MODE_TUNNEL:
// 通道模式下的MTU, 按加密塊大小對齊, +2是要包括2字節數據長度
  mtu = ALIGN(mtu +2, blksize);
  break;
 default:
 case XFRM_MODE_TRANSPORT:
  /* The worst case */
// 傳輸模式下, MTU先按4字節對齊, 再加塊長度減4
  mtu = ALIGN(mtu + 2, 4) + blksize - 4;
  break;
 case XFRM_MODE_BEET:
   /* The worst case. */
  enclen = IPV4_BEET_PHMAXLEN;
  mtu = ALIGN(mtu + enclen + 2, blksize);
  break;
 }
// 如果加密算法中定義了填充長度, MTU也要按填充長度對齊
 if (esp->conf.padlen)
  mtu = ALIGN(mtu, esp->conf.padlen);
// 返回MTU加提議中需要增加的頭部長度和認證初始化向量的截斷長度
// enclen只在BEET模式下非0, 在通道和傳輸模式下都是0
 return mtu + x->props.header_len + esp->auth.icv_trunc_len - enclen;
}
 
8.2.3.4 協議輸入

struct scatterlist結構說明:
/* include/asm-i386/scatterlist.h */
struct scatterlist {
    struct page  *page;
    unsigned int offset;
    dma_addr_t  dma_address;
    unsigned int length;
};

/*
 * Note: detecting truncated vs. non-truncated authentication data is very
 * expensive, so we only support truncated data, which is the recommended
 * and common case.
 */
// 接收數據處理, 在xfrm4_rcv_encap()函數中調用
// 進行ESP認證解密, 剝離ESP頭, 解密成普通數據包, 數據包長度減少
// 輸入的數據包是ESP包
static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
{
 struct iphdr *iph;
 struct ip_esp_hdr *esph;
 struct esp_data *esp = x->data;
 struct crypto_blkcipher *tfm = esp->conf.tfm;
 struct blkcipher_desc desc = { .tfm = tfm };
 struct sk_buff *trailer;
 int blksize = ALIGN(crypto_blkcipher_blocksize(tfm), 4);
// 認證初始化向量截斷長度
 int alen = esp->auth.icv_trunc_len;
// 需要加密的數據長度: 總長減ESP頭, 加密初始化向量長度, 認證初始化向量長度
 int elen = skb->len - sizeof(struct ip_esp_hdr) - esp->conf.ivlen - alen;
 int nfrags;
 int ihl;
 u8 nexthdr[2];
 struct scatterlist *sg;
 int padlen;
 int err;
// 在skb頭留出ESP頭的空間
 if (!pskb_may_pull(skb, sizeof(struct ip_esp_hdr)))
  goto out;
// 檢查需要加密的數據長度, 必須大於0而且按塊大小對齊的
 if (elen <= 0 || (elen & (blksize-1)))
  goto out;
 /* If integrity check is required, do this. */
// 認證計算處理
 if (esp->auth.icv_full_len) {
  u8 sum[alen];
// 計算認證值, 認證值保存在esp_data結構中
  err = esp_mac_digest(esp, skb, 0, skb->len - alen);
  if (err)
   goto out;
// 將skb中的認證初始化向量部分數據拷貝到緩衝區sum中
  if (skb_copy_bits(skb, skb->len - alen, sum, alen))
   BUG();
// 比較sum中的向量值和認證算法結構中的向量值是否匹配, 數據包正常情況下應該是相同的
  if (unlikely(memcmp(esp->auth.work_icv, sum, alen))) {
   x->stats.integrity_failed++;
   goto out;
  }
 }
// 使數據包是可寫的
 if ((nfrags = skb_cow_data(skb, 0, &trailer)) < 0)
  goto out;
 skb->ip_summed = CHECKSUM_NONE;
// 定位在數據包中的ESP頭位置, 爲當前的data位置
 esph = (struct ip_esp_hdr*)skb->data;
 /* Get ivec. This can be wrong, check against another impls. */
// 設置加密算法的初始化向量
 if (esp->conf.ivlen)
  crypto_blkcipher_set_iv(tfm, esph->enc_data, esp->conf.ivlen);
 sg = &esp->sgbuf[0];
 if (unlikely(nfrags > ESP_NUM_FAST_SG)) {
  sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC);
  if (!sg)
   goto out;
 }
 skb_to_sgvec(skb, sg, sizeof(struct ip_esp_hdr) + esp->conf.ivlen, elen);
// 解密操作, 返回非0表示失敗
 err = crypto_blkcipher_decrypt(&desc, sg, sg, elen);
 if (unlikely(sg != &esp->sgbuf[0]))
  kfree(sg);
// 解密失敗返回
 if (unlikely(err))
  return err;
// 拷貝兩字節數據
 if (skb_copy_bits(skb, skb->len-alen-2, nexthdr, 2))
  BUG();
 padlen = nexthdr[0];
 if (padlen+2 >= elen)
  goto out;
 /* ... check padding bits here. Silly. :-) */ 
// 新的IP頭
 iph = skb->nh.iph;
 ihl = iph->ihl * 4;
// 如果是NAT穿越情況, 進行一些處理
 if (x->encap) {
// xfrm封裝模板
  struct xfrm_encap_tmpl *encap = x->encap;
// 定位UDP數據頭位置, 在IP頭之後
  struct udphdr *uh = (void *)(skb->nh.raw + ihl);
  /*
   * 1) if the NAT-T peer's IP or port changed then
   *    advertize the change to the keying daemon.
   *    This is an inbound SA, so just compare
   *    SRC ports.
   */
// 如果IP頭源地址和SA提議中的源地址不同或源端口不同
  if (iph->saddr != x->props.saddr.a4 ||
      uh->source != encap->encap_sport) {
   xfrm_address_t ipaddr;
// 保存當前IP頭源地址
   ipaddr.a4 = iph->saddr;
// 進行NAT通知回調處理
   km_new_mapping(x, &ipaddr, uh->source);
    
   /* XXX: perhaps add an extra
    * policy check here, to see
    * if we should allow or
    * reject a packet from a
    * different source
    * address/port.
    */
  }
 
  /*
   * 2) ignore UDP/TCP checksums in case
   *    of NAT-T in Transport Mode, or
   *    perform other post-processing fixes
   *    as per draft-ietf-ipsec-udp-encaps-06,
   *    section 3.1.2
   */
// 如果是傳輸模式或BEET模式, 設置不需要計算校驗和
  if (x->props.mode == XFRM_MODE_TRANSPORT ||
      x->props.mode == XFRM_MODE_BEET)
   skb->ip_summed = CHECKSUM_UNNECESSARY;
 }
// 新IP頭中協議
 iph->protocol = nexthdr[1];
// 縮減skb數據包長度
 pskb_trim(skb, skb->len - alen - padlen - 2);
// 重新定位IP上層數據頭位置
 skb->h.raw = __skb_pull(skb, sizeof(*esph) + esp->conf.ivlen) - ihl;
 return 0;
out:
 return -EINVAL;
}

8.2.3.4 協議輸出

// 發送數據處理, 在xfrm4_output_one()中調用
// 添加ESP頭, 對數據包進行加密和認證處理, 數據包長度擴大
// 在NAT穿越情況下會封裝爲UDP數據
static int esp_output(struct xfrm_state *x, struct sk_buff *skb)
{
 int err;
 struct iphdr *top_iph;
 struct ip_esp_hdr *esph;
 struct crypto_blkcipher *tfm;
 struct blkcipher_desc desc;
 struct esp_data *esp;
 struct sk_buff *trailer;
 int blksize;
 int clen;
 int alen;
 int nfrags;
 /* Strip IP+ESP header. */
// 縮減skb數據, 減去IP頭和ESP頭, 剩下的數據就是要進行加密和認證的部分
 __skb_pull(skb, skb->h.raw - skb->data);
 /* Now skb is pure payload to encrypt */
 err = -ENOMEM;
 /* Round to block size */
// 加密塊的初始值
 clen = skb->len;
// 獲取SA的esp_data數據結構
 esp = x->data;
// 認證初始化向量截斷長度
 alen = esp->auth.icv_trunc_len;
// 加密算法
 tfm = esp->conf.tfm;
// 給塊加密算法描述結構賦值
 desc.tfm = tfm;
 desc.flags = 0;
// 每個加密塊大小
 blksize = ALIGN(crypto_blkcipher_blocksize(tfm), 4);
// 對齊要加密的數據總長
 clen = ALIGN(clen + 2, blksize);
// 如果要考慮填充, 繼續對齊
 if (esp->conf.padlen)
  clen = ALIGN(clen, esp->conf.padlen);
// 使數據包可寫
 if ((nfrags = skb_cow_data(skb, clen-skb->len+alen, &trailer)) < 0)
  goto error;
 /* Fill padding... */
// 長度對齊後填充多餘長度部分內容
 do {
  int i;
  for (i=0; ilen - 2; i++)
   *(u8*)(trailer->tail + i) = i+1;
 } while (0);
// 最後兩字節表示填充數據的長度
 *(u8*)(trailer->tail + clen-skb->len - 2) = (clen - skb->len)-2;
 pskb_put(skb, trailer, clen - skb->len);
// 在將IP頭部分擴展回來
 __skb_push(skb, skb->data - skb->nh.raw);
// 現在的IP頭作爲外部IP頭
 top_iph = skb->nh.iph;
// esp頭跟在IP頭後
 esph = (struct ip_esp_hdr *)(skb->nh.raw + top_iph->ihl*4);
// 數據總長增加認證部分長度
 top_iph->tot_len = htons(skb->len + alen);
 *(u8*)(trailer->tail - 1) = top_iph->protocol;
 /* this is non-NULL only with UDP Encapsulation */
 if (x->encap) {
// NAT穿越情況下要將數據封裝爲UDP包
  struct xfrm_encap_tmpl *encap = x->encap;
  struct udphdr *uh;
  u32 *udpdata32;
// IP頭後改爲UDP頭
  uh = (struct udphdr *)esph;
// 填充UDP頭參數, 源端口, 目的端口, UDP數據長度
  uh->source = encap->encap_sport;
  uh->dest = encap->encap_dport;
  uh->len = htons(skb->len + alen - top_iph->ihl*4);
// 校驗和爲0, 表示不需要計算校驗和, ESP本身就進行認證了
  uh->check = 0;
  switch (encap->encap_type) {
  default:
  case UDP_ENCAP_ESPINUDP:
// 在該模式下ESP頭跟在UDP頭後面
   esph = (struct ip_esp_hdr *)(uh + 1);
   break;
  case UDP_ENCAP_ESPINUDP_NON_IKE:
// 在該模式下ESP頭跟在UDP頭後面8字節處
   udpdata32 = (u32 *)(uh + 1);
   udpdata32[0] = udpdata32[1] = 0;
   esph = (struct ip_esp_hdr *)(udpdata32 + 2);
   break;
  }
// 外部IP頭協議是UDP
  top_iph->protocol = IPPROTO_UDP;
 } else
// 非NAT穿越情況下, 外部IP頭中的協議是ESP
  top_iph->protocol = IPPROTO_ESP;
// 填充ESP頭中的SPI和序列號
 esph->spi = x->id.spi;
 esph->seq_no = htonl(++x->replay.oseq);
// 序列號更新通知回調
 xfrm_aevent_doreplay(x);
// 如果加密初始化向量長度非零, 設置加密算法中的初始化向量
 if (esp->conf.ivlen) {
  if (unlikely(!esp->conf.ivinitted)) {
   get_random_bytes(esp->conf.ivec, esp->conf.ivlen);
   esp->conf.ivinitted = 1;
  }
  crypto_blkcipher_set_iv(tfm, esp->conf.ivec, esp->conf.ivlen);
 }
// 加密操作
 do {
  struct scatterlist *sg = &esp->sgbuf[0];
  if (unlikely(nfrags > ESP_NUM_FAST_SG)) {
   sg = kmalloc(sizeof(struct scatterlist)*nfrags, GFP_ATOMIC);
   if (!sg)
    goto error;
  }
  skb_to_sgvec(skb, sg, esph->enc_data+esp->conf.ivlen-skb->data, clen);
// 對數據加密
  err = crypto_blkcipher_encrypt(&desc, sg, sg, clen);
  if (unlikely(sg != &esp->sgbuf[0]))
   kfree(sg);
 } while (0);
 if (unlikely(err))
  goto error;
// 將加密算法初始化向量拷貝到數據包
 if (esp->conf.ivlen) {
  memcpy(esph->enc_data, esp->conf.ivec, esp->conf.ivlen);
  crypto_blkcipher_get_iv(tfm, esp->conf.ivec, esp->conf.ivlen);
 }
// 認證計算, 計算出HASH值並拷貝到數據包中
 if (esp->auth.icv_full_len) {
  err = esp_mac_digest(esp, skb, (u8 *)esph - skb->data,
         sizeof(*esph) + esp->conf.ivlen + clen);
  memcpy(pskb_put(skb, trailer, alen), esp->auth.work_icv, alen);
 }
// 重新計算外部IP頭校驗和
 ip_send_check(top_iph);
error:
 return err;
}
 
發佈了5 篇原創文章 · 獲贊 7 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章