Linux內核中的IPSEC實現3

6. XFRM的其他操作

6.1 HASH處理

關於HASH值的計算方法主要在net/xfrm/xfrm_hash.h中定義:
// IPV4地址HASH
static inline unsigned int __xfrm4_addr_hash(xfrm_address_t *addr)
{
// 就是地址本身
 return ntohl(addr->a4);
}
// IPV6地址HASH
static inline unsigned int __xfrm6_addr_hash(xfrm_address_t *addr)
{
// 取後2個32位數異或
 return ntohl(addr->a6[2] ^ addr->a6[3]);
}
// IPV4源,目的地址HASH
static inline unsigned int __xfrm4_daddr_saddr_hash(xfrm_address_t *daddr, xfrm_address_t *saddr)
{
// 將兩個地址異或
 return ntohl(daddr->a4 ^ saddr->a4);
}
// IPV4源,目的地址HASH
static inline unsigned int __xfrm6_daddr_saddr_hash(xfrm_address_t *daddr, xfrm_address_t *saddr)
{
// 兩個V6地址都取後2個32位數異或
 return ntohl(daddr->a6[2] ^ daddr->a6[3] ^
       saddr->a6[2] ^ saddr->a6[3]);
}
// 目的地址HASH
static inline unsigned int __xfrm_dst_hash(xfrm_address_t *daddr, xfrm_address_t *saddr,
        u32 reqid, unsigned short family,
        unsigned int hmask)
{
// 協議族和請求ID異或
 unsigned int h = family ^ reqid;
 switch (family) {
// HASH值再和源目的地址HASH結果進行異或
 case AF_INET:
  h ^= __xfrm4_daddr_saddr_hash(daddr, saddr);
  break;
 case AF_INET6:
  h ^= __xfrm6_daddr_saddr_hash(daddr, saddr);
  break;
 }
// 將HASH結果高低16位異或存低16位,高16位不動, 然後用HASH掩碼相與
 return (h ^ (h >> 16)) & hmask;
}

// 源地址HASH, 只是沒有請求ID項, 其他HASH過程和上面相同
static inline unsigned __xfrm_src_hash(xfrm_address_t *daddr,
           xfrm_address_t *saddr,
           unsigned short family,
           unsigned int hmask)
{
 unsigned int h = family;
 switch (family) {
 case AF_INET:
  h ^= __xfrm4_daddr_saddr_hash(daddr, saddr);
  break;
 case AF_INET6:
  h ^= __xfrm6_daddr_saddr_hash(daddr, saddr);
  break;
 };
 return (h ^ (h >> 16)) & hmask;
}

// 根據SPI計算HASH值
static inline unsigned int
__xfrm_spi_hash(xfrm_address_t *daddr, __be32 spi, u8 proto, unsigned short family,
  unsigned int hmask)
{
// 先將SPI和協議進行異或
 unsigned int h = (__force u32)spi ^ proto;
 switch (family) {
// HASH值再和目的地址進行單一地址HASH值異或
 case AF_INET:
  h ^= __xfrm4_addr_hash(daddr);
  break;
 case AF_INET6:
  h ^= __xfrm6_addr_hash(daddr);
  break;
 }
// HASH值再和本身的高22位, 高12位異或後再和掩碼相與
 return (h ^ (h >> 10) ^ (h >> 20)) & hmask;
}

// 索引號HASH
static inline unsigned int __idx_hash(u32 index, unsigned int hmask)
{
// 低24位和高24位異或, 高8位不動, 再和掩碼相與
 return (index ^ (index >> 8)) & hmask;
}

// 選擇子HASH
static inline unsigned int __sel_hash(struct xfrm_selector *sel, unsigned short family, unsigned int hmask)
{
// 提前源和目的地址
 xfrm_address_t *daddr = &sel->daddr;
 xfrm_address_t *saddr = &sel->saddr;
 unsigned int h = 0;
 switch (family) {
// 用源,目的地址同時進行HASH
 case AF_INET:
  if (sel->prefixlen_d != 32 ||
      sel->prefixlen_s != 32)
   return hmask + 1;
  h = __xfrm4_daddr_saddr_hash(daddr, saddr);
  break;
 case AF_INET6:
  if (sel->prefixlen_d != 128 ||
      sel->prefixlen_s != 128)
   return hmask + 1;
  h = __xfrm6_daddr_saddr_hash(daddr, saddr);
  break;
 };
// 高16位與低16位異或,高16位不變
 h ^= (h >> 16);
// 與掩碼相與, 其實HASH值中不帶協議族因素, 因爲地址本身就包含了
 return h & hmask;
}
// 地址HASH
static inline unsigned int __addr_hash(xfrm_address_t *daddr, xfrm_address_t *saddr, unsigned short family, unsigned int hmask)
{
 unsigned int h = 0;
 switch (family) {
// 用源,目的地址同時進行HASH
 case AF_INET:
  h = __xfrm4_daddr_saddr_hash(daddr, saddr);
  break;
 case AF_INET6:
  h = __xfrm6_daddr_saddr_hash(daddr, saddr);
  break;
 };
// 高16位與低16位異或,高16位不變
 h ^= (h >> 16);
// 與掩碼相與
 return h & hmask;
}

在net/xfrm/xfrm_hash.c 文件中定義了HASH表的分配和釋放函數:

struct hlist_head *xfrm_hash_alloc(unsigned int sz)
{
 struct hlist_head *n;
// 根據HASH表大小選擇合適的分配方法
// 大小不超過PAGE_SIZE, 用kmalloc分配
 if (sz <= PAGE_SIZE)
  n = kmalloc(sz, GFP_KERNEL);
// 這是在內核定義NUMA和IA64下用vmalloc分配
 else if (hashdist)
  n = __vmalloc(sz, GFP_KERNEL, PAGE_KERNEL);
 else
// 其他類型的內核用get_free_page分配
  n = (struct hlist_head *)
   __get_free_pages(GFP_KERNEL, get_order(sz));
// 空間清零
 if (n)
  memset(n, 0, sz);
 return n;
}
// 釋放HASH表空間
void xfrm_hash_free(struct hlist_head *n, unsigned int sz)
{
 if (sz <= PAGE_SIZE)
  kfree(n);
 else if (hashdist)
  vfree(n);
 else
  free_pages((unsigned long)n, get_order(sz));
}

6.2 算法操作

IPSEC操作中用到的認證, 加密, 壓縮等算法具體實現是在crypto目錄下, 而在xfrm中只是定義這些算法的說明, 表示最大可以支持這些算法, 在使用時會探測這些算法是否在內核中存在從而確定可使用的算法.
關於算法的數據結構如下:
/* include/net/xfrm.h */
// 認證算法參數
struct xfrm_algo_auth_info {
 u16 icv_truncbits; // 初始向量截斷位數
 u16 icv_fullbits;  // 初始向量總的位數
};
// 加密算法參數
struct xfrm_algo_encr_info {
 u16 blockbits;  // 塊位數
 u16 defkeybits; // 密鑰長度位數
};
// 壓縮算法參數
struct xfrm_algo_comp_info {
 u16 threshold;  // 閾值
};
// xfrm算法描述
struct xfrm_algo_desc {
 char *name;  // 名稱
 char *compat; // 名稱縮寫
 u8 available:1; // 算法是否可用(是否在內核中)
 union {
  struct xfrm_algo_auth_info auth;
  struct xfrm_algo_encr_info encr;
  struct xfrm_algo_comp_info comp;
 } uinfo; // 算法信息聯合
 struct sadb_alg desc; // 通用算法描述
};

6.2.1 認證算法
可用的認證算法通過下面的數組來描述, 包含NULL, MD5, SHA1, SHA256, RIPEMD160等認證算法:
static struct xfrm_algo_desc aalg_list[] = {
......
{
 .name = "hmac(sha1)",
 .compat = "sha1",
 .uinfo = {
  .auth = {
   .icv_truncbits = 96,// 96位截斷
   .icv_fullbits = 160, // 總共160位
  }
 },
 .desc = { // 這是對SHA1認證算法的標準描述參數
  .sadb_alg_id = SADB_AALG_SHA1HMAC, // 算法ID值
  .sadb_alg_ivlen = 0,
  .sadb_alg_minbits = 160,
  .sadb_alg_maxbits = 160
 }
},
......

相關操作函數:
// 通過算法ID查找認證算法
struct xfrm_algo_desc *xfrm_aalg_get_byid(int alg_id)
{
 int i;
// 遍歷認證數組
 for (i = 0; i < aalg_entries(); i++) {
// 查找和指定算法ID相同的算法
  if (aalg_list[i].desc.sadb_alg_id == alg_id) {
// 檢查該算法是否可用
   if (aalg_list[i].available)
    return &aalg_list[i];
   else
    break;
  }
 }
 return NULL;
}
EXPORT_SYMBOL_GPL(xfrm_aalg_get_byid);

// 統計可用的認證算法數量, 就是available的認證算法數量累加
int xfrm_count_auth_supported(void)
{
 int i, n;
 for (i = 0, n = 0; i < aalg_entries(); i++)
  if (aalg_list[i].available)
   n++;
 return n;
}
EXPORT_SYMBOL_GPL(xfrm_count_auth_supported);
6.2.2 加密算法

可用的認證算法通過下面的數組來描述, 包含NULL, DES, 3DES, CAST, AES, BLOWFISH, TWOFISH, SERPENT等加密算法:
static struct xfrm_algo_desc ealg_list[] = {
......
{
 .name = "cbc(des3_ede)",
 .compat = "des3_ede",
 .uinfo = {
  .encr = {
   .blockbits = 64,
   .defkeybits = 192,
  }
 },
 .desc = {
  .sadb_alg_id = SADB_EALG_3DESCBC,
  .sadb_alg_ivlen = 8,
  .sadb_alg_minbits = 192,
  .sadb_alg_maxbits = 192
 }
},
......

// 通過算法ID查找加密算法, 和認證算法查找類似
struct xfrm_algo_desc *xfrm_ealg_get_byid(int alg_id)
{
 int i;
 for (i = 0; i < ealg_entries(); i++) {
  if (ealg_list[i].desc.sadb_alg_id == alg_id) {
   if (ealg_list[i].available)
    return &ealg_list[i];
   else
    break;
  }
 }
 return NULL;
}
EXPORT_SYMBOL_GPL(xfrm_ealg_get_byid);

// 統計可用的加密算法數量, 就是available的加密算法數量累加
int xfrm_count_enc_supported(void)
{
 int i, n;
 for (i = 0, n = 0; i < ealg_entries(); i++)
  if (ealg_list[i].available)
   n++;
 return n;
}
EXPORT_SYMBOL_GPL(xfrm_count_enc_supported);

6.2.3 壓縮算法

可用的壓縮算法通過下面的數組來描述, 包含DELFATE, LZS, LZJH等壓縮算法:
static struct xfrm_algo_desc calg_list[] = {
......
{
 .name = "lzs",
 .uinfo = {
  .comp = {
   .threshold = 90,
  }
 },
 .desc = { .sadb_alg_id = SADB_X_CALG_LZS }
},
......

// 通過算法ID查找加密算法, 和認證算法查找類似
struct xfrm_algo_desc *xfrm_calg_get_byid(int alg_id)
{
 int i;
 for (i = 0; i < calg_entries(); i++) {
  if (calg_list[i].desc.sadb_alg_id == alg_id) {
   if (calg_list[i].available)
    return &calg_list[i];
   else
    break;
  }
 }
 return NULL;
}
EXPORT_SYMBOL_GPL(xfrm_calg_get_byid);

6.2.4 通過名稱查找算法

// 輸入參數爲算法數組, 數組元素個數, 類型, 掩碼, 名稱和是否探測在內核中存在
static struct xfrm_algo_desc *xfrm_get_byname(struct xfrm_algo_desc *list,
           int entries, u32 type, u32 mask,
           char *name, int probe)
{
 int i, status;
 if (!name)
  return NULL;
// 遍歷數組
 for (i = 0; i < entries; i++) {
// 比較算法名稱或縮寫名稱是否和指定名稱相同
  if (strcmp(name, list[i].name) &&
      (!list[i].compat || strcmp(name, list[i].compat)))
   continue;
// 找到算法結構
// 檢查算法是否在內核可用, 可用的話成功返回
  if (list[i].available)
   return &list[i];
// 如果不需要探測, 將返回空
  if (!probe)
   break;
// 需要探測算法算法存在內核, 調用crypto_has_alg()函數探測
// 返回0表示失敗, 非0表示成功
  status = crypto_has_alg(name, type, mask | CRYPTO_ALG_ASYNC);
  if (!status)
   break;
// 算法可用, 返回
  list[i].available = status;
  return &list[i];
 }
 return NULL;
}

/* crypto/api.c */
// 算法探測
int crypto_has_alg(const char *name, u32 type, u32 mask)
{
 int ret = 0;
// 根據名稱, 類型和掩碼探測算法模塊
 struct crypto_alg *alg = crypto_alg_mod_lookup(name, type, mask);
// 正確返回找到 
 if (!IS_ERR(alg)) {
// 減少模塊計數, 返回1
  crypto_mod_put(alg);
  ret = 1;
 }
 
 return ret;
}

有了xfrm_get_byname()這個通用基本函數, 具體類型的算法查找函數就很簡單了:

// 通過名稱查找認證算法
struct xfrm_algo_desc *xfrm_aalg_get_byname(char *name, int probe)
{
 return xfrm_get_byname(aalg_list, aalg_entries(),
          CRYPTO_ALG_TYPE_HASH, CRYPTO_ALG_TYPE_HASH_MASK,
          name, probe);
}
EXPORT_SYMBOL_GPL(xfrm_aalg_get_byname);
// 通過名稱查找加密算法
struct xfrm_algo_desc *xfrm_ealg_get_byname(char *name, int probe)
{
 return xfrm_get_byname(ealg_list, ealg_entries(),
          CRYPTO_ALG_TYPE_BLKCIPHER, CRYPTO_ALG_TYPE_MASK,
          name, probe);
}
EXPORT_SYMBOL_GPL(xfrm_ealg_get_byname);
// 通過名稱查找壓縮算法
struct xfrm_algo_desc *xfrm_calg_get_byname(char *name, int probe)
{
 return xfrm_get_byname(calg_list, calg_entries(),
          CRYPTO_ALG_TYPE_COMPRESS, CRYPTO_ALG_TYPE_MASK,
          name, probe);
}
EXPORT_SYMBOL_GPL(xfrm_calg_get_byname);
 
以下是通過索引號來查找算法, 就是直接返回相應數組指定位置的算法:

struct xfrm_algo_desc *xfrm_aalg_get_byidx(unsigned int idx)
{
 if (idx >= aalg_entries())
  return NULL;
 return &aalg_list[idx];
}
EXPORT_SYMBOL_GPL(xfrm_aalg_get_byidx);
struct xfrm_algo_desc *xfrm_ealg_get_byidx(unsigned int idx)
{
 if (idx >= ealg_entries())
  return NULL;
 return &ealg_list[idx];
}
EXPORT_SYMBOL_GPL(xfrm_ealg_get_byidx);

6.2.5 xfrm算法探測

該函數在SA進行調整時會調用來查看當前內核中支持的各種算法
/*
 * Probe for the availability of crypto algorithms, and set the available
 * flag for any algorithms found on the system.  This is typically called by
 * pfkey during userspace SA add, update or register.
 */
void xfrm_probe_algs(void)
{
// 內核必須定義CRYPTO選項, 否則就是空函數了
#ifdef CONFIG_CRYPTO
 int i, status;
 
 BUG_ON(in_softirq());
// 遍歷認證算法數組
 for (i = 0; i < aalg_entries(); i++) {
// 根據算法名稱確定該HASH算法是否存在, 返回0不存在, 非0存在
  status = crypto_has_hash(aalg_list[i].name, 0,
      CRYPTO_ALG_ASYNC);
// 如果狀態和原來的狀態不同, 更改
  if (aalg_list[i].available != status)
   aalg_list[i].available = status;
 }
 
// 遍歷加密算法數組
 for (i = 0; i < ealg_entries(); i++) {
// 根據算法名稱確定該加密算法是否存在, 返回0不存在, 非0存在
  status = crypto_has_blkcipher(ealg_list[i].name, 0,
           CRYPTO_ALG_ASYNC);
// 如果狀態和原來的狀態不同, 更改
  if (ealg_list[i].available != status)
   ealg_list[i].available = status;
 }
 
// 遍歷壓縮算法數組
 for (i = 0; i < calg_entries(); i++) {
// 根據算法名稱確定該壓縮算法是否存在, 返回0不存在, 非0存在
  status = crypto_has_comp(calg_list[i].name, 0,
      CRYPTO_ALG_ASYNC);
// 如果狀態和原來的狀態不同, 更改
  if (calg_list[i].available != status)
   calg_list[i].available = status;
 }
#endif
}
EXPORT_SYMBOL_GPL(xfrm_probe_algs);

6.3 通過netlink套接口訪問xfrm

通過netlink套接口訪問xfrm的處理函數在net/xfrm/xfrm_user.c中, 提供了Linux特色的非標準PF_KEY接口的SA, SP控制方法, 能完成和PF_KEY一樣控制功能, 目前iproute2中的ip工具中新增加的xfrm命令就是通過這種netlink接口來完成的, 因爲netlink操作以前已經介紹過, xfrm的操作又都是一樣的, 因此本文不再分析其實現過程.

6.4 xfrm_input

在net/xfrm/xfrm_input.c文件中定義了關於安全路徑(struct sec_path)的幾個處理函數, 用於對輸入的IPSEC包進行解析構造安全路徑使用.
// 釋放安全路徑
void __secpath_destroy(struct sec_path *sp)
{
 int i;
// 減少安全路徑中所有SA的使用計數
 for (i = 0; i < sp->len; i++)
  xfrm_state_put(sp->xvec[i]);
// 釋放安全路徑空間
 kmem_cache_free(secpath_cachep, sp);
}
EXPORT_SYMBOL(__secpath_destroy);
// 安全路徑複製
struct sec_path *secpath_dup(struct sec_path *src)
{
 struct sec_path *sp;
// 先分配安全路徑結構
 sp = kmem_cache_alloc(secpath_cachep, SLAB_ATOMIC);
 if (!sp)
  return NULL;
 sp->len = 0;
 if (src) {
  int i;
// 如果源安全路徑結構非空, 將其全部複製到新結構中
  memcpy(sp, src, sizeof(*sp));
// 增加安全路徑中所有SA的使用計數
  for (i = 0; i < sp->len; i++)
   xfrm_state_hold(sp->xvec[i]);
 }
// 設置該引用計數初始值位1
 atomic_set(&sp->refcnt, 1);
 return sp;
}
EXPORT_SYMBOL(secpath_dup);
 
/* Fetch spi and seq from ipsec header */
// 從數據包中解析SPI和序號, 返回值是網絡序的
int xfrm_parse_spi(struct sk_buff *skb, u8 nexthdr, __be32 *spi, __be32 *seq)
{
 int offset, offset_seq;
// 通過nexthdr參數來判斷協議類型, nexthdr是IPV6裏的說法, 在IPV4中就是IP頭裏的協議字段
// 根據不同協議確定數據中SPI和序列號相對數據起始點的偏移
 switch (nexthdr) {
 case IPPROTO_AH:
  offset = offsetof(struct ip_auth_hdr, spi);
  offset_seq = offsetof(struct ip_auth_hdr, seq_no);
  break;
 case IPPROTO_ESP:
  offset = offsetof(struct ip_esp_hdr, spi);
  offset_seq = offsetof(struct ip_esp_hdr, seq_no);
  break;
 case IPPROTO_COMP:
// 對應壓縮協議單獨處理
// 數據頭準備出IP壓縮頭結構長度
  if (!pskb_may_pull(skb, sizeof(struct ip_comp_hdr)))
   return -EINVAL;
// SPI值取第3,4字節的數據, 序號爲0
  *spi = htonl(ntohs(*(__be16*)(skb->h.raw + 2)));
  *seq = 0;
  return 0;
 default:
  return 1;
 }
// 數據頭準備16字節空間, 這是ip_auth_hdr和ip_esp_hdr結構最小長度
 if (!pskb_may_pull(skb, 16))
  return -EINVAL;
// 根據偏移獲取SPI和序號, 注意是網絡序的值
 *spi = *(__be32*)(skb->h.raw + offset);
 *seq = *(__be32*)(skb->h.raw + offset_seq);
 return 0;
}
EXPORT_SYMBOL(xfrm_parse_spi);
發佈了5 篇原創文章 · 獲贊 7 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章