searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

VPP IPSEC NAT-T数据转发实现

2023-06-29 08:24:16
100
0

一、技术背景

IPSec提供了端到端的IP通信的安全性,但在NAT环境下对IPSec的支持有限,AH协议是肯定不能进行NAT的了,这和AH设计的理念是相违背的;ESP协议在NAT环境下最多只能有一个VPN主机能建立VPN通道,无法实现多台机器同时在NAT环境下进行ESP通信。关于IPSec在NAT环境下的需求问题在RFC3715中进行了描述。

  NAT穿越(NAT Traversal,NAT-T)就是为解决这个问题而提出的,RFC3947,3948中定义,在RFC4306中也加入了NAT-T的说明,但并没废除RFC3947,3948,只是不区分阶段1和阶段2。该方法将ESP协议包封装到UDP包中(在原ESP协议的IP包头外添加新的IP头和UDP头),使之可以在NAT环境下使用的一种方法,这样在NAT的内部网中可以有多个IPSec主机建立VPN通道进行通信。

二、使用限制

AH封装的校验从IP头开始,如果NAT将IP的头部改动,AH的校验就会失败,因此我们得出结论,AH是无法与NAT共存的。ESP封装的传输模式:对于NAT来说,ESP封装比AH的优势在于,无论是加密还是完整性的校验,IP头部都没有被包括进去。但是还是有新的问题,对于ESP的传输模式,NAT 无法更新上层校验和。TCP 和 UDP 报头包含一个校验和,它整合了源和目标 IP 地址和端口号的值。

当 NAT 改变了某个包的 IP 地址和(或)端口号时,它通常要更新 TCP 或 UDP 校验和。当 TCP 或 UDP 校验和使用了 ESP 来加密时,它就无法更新这个校验和。由于地址或端口已经被 NAT 更改,目的地的校验和检验就会失败。虽然 UDP 校验和是可选的,但是 TCP 校验和却是必需的。

  ESP封装的隧道模式:从ESP隧道模式的封装中,我们可以发现,ESP隧道模式将整个原始的IP包整个进行了加密,且在ESP的头部外面新加了一层IP头部,所以NAT如果只改变最前面的IP地址对后面受到保护的部分是不会有影响的。因此,IPsec只有采用ESP的隧道模式来封装数据时才能与NAT共存。

由于完整性校验牵涉到IP头部,所以NAT无法对其修改,不兼容。

ESP的传输模式,因为TCP部分被加密,NAT无法对TCP校验和进行修改,不兼容。

ESP的隧道模式,由于NAT改动外部的IP而不能改动被加密的原始IP,使得只有这种情况下才能与NAT共存。

三、IPSEC NAT-T数据转发实现

NAT-T的基本思路是IPSec封装好的数据包外再进行一次UDP数据封装,即在IP和ESP报文之间插入一个8个字节UDP头部(端口号默认为4500)。这样,当此数据包穿过NAT网关时,被修改的只是最外层的IP和udp数据报。

                                     图1 ESP隧道模式加密

                                      图2 ESP隧道模式NAT-T封装

VPP中数据转发面实现NAT-T功能分成两个部分:一是注册一个新的IPSEC node用于处理UDP收到的4500端口报文,并注册UDP 4500端口与之关联;二是实现IPSEC加解封装流程对NAT-T的支持。

3.1 NAT-T node处理实现

3.1.1 注册PSEC node

IPSEC协商检查到发生NAT转换,会把原有封装协商报文的UDP 500端口转换成4500端口,为了保证IPSEC后续协商正常进行,IPSEC控制面需要能够正常的处理UDP 4500端口的报文,因此需注册一个新的IPSEC node用于处理从UDP收到的4500端口报文。

注册一个ikev2_nat的node,主要定义ikev2_nat_node_fn函数和next_nodes。ikev2_nat_node_fn函数用于处理UDP 4500端口报文,包括协商和数据报文。next_nodes定义了该node后续的处理node,主要是UDP 4500数据报文送往IPSEC解密流程进行解密处理。

static vlib_node_registration_t ikev2_nat_node;

/* *INDENT-OFF* */

VLIB_REGISTER_NODE (ikev2_nat_node,static) = {

  .function = ikev2_nat_node_fn,

  .name = "ikev2_nat",

  .vector_size = sizeof (u32),

  .format_trace = format_ikev2_nat_trace,

  .type = VLIB_NODE_TYPE_INTERNAL,

  .n_errors = ARRAY_LEN(ikev2_nat_error_strings),

  .error_strings = ikev2_nat_error_strings,

  .n_next_nodes = IKEV2_NAT_N_NEXT,

  .next_nodes = {

    [IKEV2_NAT_NEXT_IPSEC4_INPUT] = "ipsec4-input-feature",

    [IKEV2_NEXT_IP4_LOOKUP] = "ip4-lookup",

        [IKEV2_NEXT_ERROR_DROP] = "error-drop",

  },

};

/* *INDENT-ON* */

 

typedef enum

{

  IKEV2_NAT_NEXT_IPSEC4_INPUT,

  IKEV2_NAT_NEXT_IP4_LOOKUP,

  IKEV2_NAT_NEXT_ERROR_DROP,

  IKEV2_NAT_N_NEXT,

} ikev2_nat_next_t;

a、ikev2_nat_node_fn函数实现

在IPSEC控制面检查到链路中存在NAT设备后,后续的所有IPSEC报文(控制协商报文、NAT保活报文和IPSEC数据报文)都需要使用UDP 4500端口进行封装,因此需要实现一个node处理函数,用于处理从UDP接收的4500端口报文。该函数主要功能是根据配置的NAT-T封装类型,判断收到的报文是那种类型的报文,控制协商报文直接传递IPSEC控制面进行处理,NAT-T保活报文在函数内部直接丢弃,IPSEC数据报文则偏移UDP头并传递给解密模块进行后续解密处理。

/* UDP encapsulation types */

#define UDP_ENCAP_ESPINUDP_NON_IKE 1 /* draft-ietf-ipsec-nat-t-ike-00/01 */

#define UDP_ENCAP_ESPINUDP 2 /* draft-ietf-ipsec-udp-encaps-06 */

static uword  ikev2_nat_node_fn (vlib_main_t * vm,

       vlib_node_runtime_t * node, vlib_frame_t * frame)

{

.........

switch (encap_type) {

default:

case UDP_ENCAP_ESPINUDP:

/* Check if this is a keepalive packet.  If so, eat it. */

if (len == 1 && udpdata[0] == 0xff) {

goto drop;

} else if (len > sizeof(struct esp_header_t) && udpdata32[0] != 0) {

/* ESP Packet without Non-ESP header */

len = sizeof(struct udp_header_t);

} else

/* Must be an IKE packet.. pass it through */

goto ikev2_deal;

break;

case UDP_ENCAP_ESPINUDP_NON_IKE:

/* Check if this is a keepalive packet.  If so, eat it. */

if (len == 1 && udpdata[0] == 0xff) {

goto drop;

} else if (len > 2 * sizeof(u32) + sizeof(struct esp_header_t) &&

   udpdata32[0] == 0 && udpdata32[1] == 0) {

/* ESP Packet with Non-IKE marker */

len = sizeof(struct udp_header_t) + 2 * sizeof(u32);

} else

/* Must be an IKE packet.. pass it through */

goto ikev2_deal;

break;

}

    /*ESP Packet, goto ipsec input node*/

    goto ipsec_input_deal;

.........

    return frame->n_vectors;

}

3.1.2 注册UDP监听4500端口

在IPSEC ike初始化函数中添加UDP 4500端口监听,并把其和ikev2_nat_node进行关联,这样UDP模块收到4500端口报文,则会交给ikev2_nat_node的处理函数进行后续处理。

clib_error_t *

ikev2_init (vlib_main_t * vm)

{

………

  udp_register_dst_port (vm, 500, ikev2_node.index, 1);

  udp_register_dst_port (vm, UDP_DST_PORT_ipsec, ikev2_nat_node.index, 1);

// 把ikev2_nat_node的index和UDP 4500进行关联

  return 0;

}

注:UDP_DST_PORT_ipsec为VPP中定义的IPSEC NAT-T 4500端口。

 

3.2、IPSEC NAT-T加解封装实现

因IPSEC NAT-T只支持ESP的隧道模式,因此只需在ESP的隧道模式处理流程中加入UDP头部的处理即可。其实现思路为:加封装方向,当进行ESP封装时,检查到SA中的udp_encap标记被设置,则在封装外部IP头前先根据设置的NAT-t封装类型,封装一个UDP头,并调整buffer的各个字段;解封装方向,收到UDP 4500端口报文,则直接跳过UDP头部进行后续处理。

3.2.1 SA中添加封装结构

NAT-T的封装源端口和type是变化的,因此需要从SA中去获取每一次的值。

struct ipsec_encap_tmpl {

__u16 encap_type;

__be16 encap_sport;

__be16 encap_dport;

};

typedef struct

{

……….

  u8 udp_encap;

  ipsec_encap_tmpl encap;//for NAT-T

………

} ipsec_sa_t;

3.2.2 ESP 中加封装实现

如果SA中udp_encap标记被设置,则直接根据ipsec_encap_tmpl结构进行封装处理。如果封装类型为UDP_ENCAP_ESPINUDP_NON_IKE,则需要添加两个U32的数据。

always_inline uword

esp_encrypt_inline (vlib_main_t * vm,

    vlib_node_runtime_t * node, vlib_frame_t * from_frame,

    int is_ip6)

{

if (sa0->udp_encap)

{

    u32 ike_len = 0;

    struct udp_header_t *uh;

    ouh0->udp.src_port = sa0->encap.encap_sport;

    ouh0->udp.dst_port = sa0->encap.encap_dport;

    ouh0->udp.checksum = 0;

 

    uh = &ouh0->udp;

    switch (sa0->encap.encap_type) {

    default:

    case UDP_ENCAP_ESPINUDP:

      break;

    case UDP_ENCAP_ESPINUDP_NON_IKE:

      udpdata32 = (__be32 *)(uh + 1);

      udpdata32[0] = udpdata32[1] = 0;

      ike_len = 2*sizeof(udpdata32);

      break;

    }

    ouh0->ip4.protocol = IP_PROTOCOL_UDP;

    ip_udp_hdr_size =

    sizeof (udp_header_t) + sizeof (ip4_header_t) + ike_len;

    ouh0->udp.length =  data_len;

}

}

3.2.3 ESP解封装中处理

解封装流程,在获取ESP头部时需要判断IP头部中的协议字段,协议字段是IP_PROTOCOL_UDP时需要偏移UDP头部才能正确的获取ESP头。

VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,

  vlib_node_runtime_t * node,

  vlib_frame_t * from_frame)

{

........

    

    esp_header_t *esp0;

    esp0 = (esp_header_t *) ((u8 *) ip0 + ip4_header_bytes (ip0));

    if (PREDICT_FALSE (ip0->protocol == IP_PROTOCOL_UDP))

    {

      esp0 = (esp_header_t *) ((u8 *) esp0 + sizeof (udp_header_t));//偏移UDP头部

    }

      

.......

}

3.2.4 QAT硬件加解密处理(选做)

在QAT处理流程中同样加入对UDP头部的处理,加封装流程判断SA中udp_encap标记确定是否需要封装UDP 头部。解封装流程添加对UDP 报文的处理,这里同样需要对UDP报文进行区分,如果是IPSEC NAT-T非数据报文,则需要上送控制面进行处理,QAT中只处理解封装数据报文。

 

 

0条评论
0 / 1000
刘****珂
3文章数
0粉丝数
刘****珂
3 文章 | 0 粉丝
刘****珂
3文章数
0粉丝数
刘****珂
3 文章 | 0 粉丝
原创

VPP IPSEC NAT-T数据转发实现

2023-06-29 08:24:16
100
0

一、技术背景

IPSec提供了端到端的IP通信的安全性,但在NAT环境下对IPSec的支持有限,AH协议是肯定不能进行NAT的了,这和AH设计的理念是相违背的;ESP协议在NAT环境下最多只能有一个VPN主机能建立VPN通道,无法实现多台机器同时在NAT环境下进行ESP通信。关于IPSec在NAT环境下的需求问题在RFC3715中进行了描述。

  NAT穿越(NAT Traversal,NAT-T)就是为解决这个问题而提出的,RFC3947,3948中定义,在RFC4306中也加入了NAT-T的说明,但并没废除RFC3947,3948,只是不区分阶段1和阶段2。该方法将ESP协议包封装到UDP包中(在原ESP协议的IP包头外添加新的IP头和UDP头),使之可以在NAT环境下使用的一种方法,这样在NAT的内部网中可以有多个IPSec主机建立VPN通道进行通信。

二、使用限制

AH封装的校验从IP头开始,如果NAT将IP的头部改动,AH的校验就会失败,因此我们得出结论,AH是无法与NAT共存的。ESP封装的传输模式:对于NAT来说,ESP封装比AH的优势在于,无论是加密还是完整性的校验,IP头部都没有被包括进去。但是还是有新的问题,对于ESP的传输模式,NAT 无法更新上层校验和。TCP 和 UDP 报头包含一个校验和,它整合了源和目标 IP 地址和端口号的值。

当 NAT 改变了某个包的 IP 地址和(或)端口号时,它通常要更新 TCP 或 UDP 校验和。当 TCP 或 UDP 校验和使用了 ESP 来加密时,它就无法更新这个校验和。由于地址或端口已经被 NAT 更改,目的地的校验和检验就会失败。虽然 UDP 校验和是可选的,但是 TCP 校验和却是必需的。

  ESP封装的隧道模式:从ESP隧道模式的封装中,我们可以发现,ESP隧道模式将整个原始的IP包整个进行了加密,且在ESP的头部外面新加了一层IP头部,所以NAT如果只改变最前面的IP地址对后面受到保护的部分是不会有影响的。因此,IPsec只有采用ESP的隧道模式来封装数据时才能与NAT共存。

由于完整性校验牵涉到IP头部,所以NAT无法对其修改,不兼容。

ESP的传输模式,因为TCP部分被加密,NAT无法对TCP校验和进行修改,不兼容。

ESP的隧道模式,由于NAT改动外部的IP而不能改动被加密的原始IP,使得只有这种情况下才能与NAT共存。

三、IPSEC NAT-T数据转发实现

NAT-T的基本思路是IPSec封装好的数据包外再进行一次UDP数据封装,即在IP和ESP报文之间插入一个8个字节UDP头部(端口号默认为4500)。这样,当此数据包穿过NAT网关时,被修改的只是最外层的IP和udp数据报。

                                     图1 ESP隧道模式加密

                                      图2 ESP隧道模式NAT-T封装

VPP中数据转发面实现NAT-T功能分成两个部分:一是注册一个新的IPSEC node用于处理UDP收到的4500端口报文,并注册UDP 4500端口与之关联;二是实现IPSEC加解封装流程对NAT-T的支持。

3.1 NAT-T node处理实现

3.1.1 注册PSEC node

IPSEC协商检查到发生NAT转换,会把原有封装协商报文的UDP 500端口转换成4500端口,为了保证IPSEC后续协商正常进行,IPSEC控制面需要能够正常的处理UDP 4500端口的报文,因此需注册一个新的IPSEC node用于处理从UDP收到的4500端口报文。

注册一个ikev2_nat的node,主要定义ikev2_nat_node_fn函数和next_nodes。ikev2_nat_node_fn函数用于处理UDP 4500端口报文,包括协商和数据报文。next_nodes定义了该node后续的处理node,主要是UDP 4500数据报文送往IPSEC解密流程进行解密处理。

static vlib_node_registration_t ikev2_nat_node;

/* *INDENT-OFF* */

VLIB_REGISTER_NODE (ikev2_nat_node,static) = {

  .function = ikev2_nat_node_fn,

  .name = "ikev2_nat",

  .vector_size = sizeof (u32),

  .format_trace = format_ikev2_nat_trace,

  .type = VLIB_NODE_TYPE_INTERNAL,

  .n_errors = ARRAY_LEN(ikev2_nat_error_strings),

  .error_strings = ikev2_nat_error_strings,

  .n_next_nodes = IKEV2_NAT_N_NEXT,

  .next_nodes = {

    [IKEV2_NAT_NEXT_IPSEC4_INPUT] = "ipsec4-input-feature",

    [IKEV2_NEXT_IP4_LOOKUP] = "ip4-lookup",

        [IKEV2_NEXT_ERROR_DROP] = "error-drop",

  },

};

/* *INDENT-ON* */

 

typedef enum

{

  IKEV2_NAT_NEXT_IPSEC4_INPUT,

  IKEV2_NAT_NEXT_IP4_LOOKUP,

  IKEV2_NAT_NEXT_ERROR_DROP,

  IKEV2_NAT_N_NEXT,

} ikev2_nat_next_t;

a、ikev2_nat_node_fn函数实现

在IPSEC控制面检查到链路中存在NAT设备后,后续的所有IPSEC报文(控制协商报文、NAT保活报文和IPSEC数据报文)都需要使用UDP 4500端口进行封装,因此需要实现一个node处理函数,用于处理从UDP接收的4500端口报文。该函数主要功能是根据配置的NAT-T封装类型,判断收到的报文是那种类型的报文,控制协商报文直接传递IPSEC控制面进行处理,NAT-T保活报文在函数内部直接丢弃,IPSEC数据报文则偏移UDP头并传递给解密模块进行后续解密处理。

/* UDP encapsulation types */

#define UDP_ENCAP_ESPINUDP_NON_IKE 1 /* draft-ietf-ipsec-nat-t-ike-00/01 */

#define UDP_ENCAP_ESPINUDP 2 /* draft-ietf-ipsec-udp-encaps-06 */

static uword  ikev2_nat_node_fn (vlib_main_t * vm,

       vlib_node_runtime_t * node, vlib_frame_t * frame)

{

.........

switch (encap_type) {

default:

case UDP_ENCAP_ESPINUDP:

/* Check if this is a keepalive packet.  If so, eat it. */

if (len == 1 && udpdata[0] == 0xff) {

goto drop;

} else if (len > sizeof(struct esp_header_t) && udpdata32[0] != 0) {

/* ESP Packet without Non-ESP header */

len = sizeof(struct udp_header_t);

} else

/* Must be an IKE packet.. pass it through */

goto ikev2_deal;

break;

case UDP_ENCAP_ESPINUDP_NON_IKE:

/* Check if this is a keepalive packet.  If so, eat it. */

if (len == 1 && udpdata[0] == 0xff) {

goto drop;

} else if (len > 2 * sizeof(u32) + sizeof(struct esp_header_t) &&

   udpdata32[0] == 0 && udpdata32[1] == 0) {

/* ESP Packet with Non-IKE marker */

len = sizeof(struct udp_header_t) + 2 * sizeof(u32);

} else

/* Must be an IKE packet.. pass it through */

goto ikev2_deal;

break;

}

    /*ESP Packet, goto ipsec input node*/

    goto ipsec_input_deal;

.........

    return frame->n_vectors;

}

3.1.2 注册UDP监听4500端口

在IPSEC ike初始化函数中添加UDP 4500端口监听,并把其和ikev2_nat_node进行关联,这样UDP模块收到4500端口报文,则会交给ikev2_nat_node的处理函数进行后续处理。

clib_error_t *

ikev2_init (vlib_main_t * vm)

{

………

  udp_register_dst_port (vm, 500, ikev2_node.index, 1);

  udp_register_dst_port (vm, UDP_DST_PORT_ipsec, ikev2_nat_node.index, 1);

// 把ikev2_nat_node的index和UDP 4500进行关联

  return 0;

}

注:UDP_DST_PORT_ipsec为VPP中定义的IPSEC NAT-T 4500端口。

 

3.2、IPSEC NAT-T加解封装实现

因IPSEC NAT-T只支持ESP的隧道模式,因此只需在ESP的隧道模式处理流程中加入UDP头部的处理即可。其实现思路为:加封装方向,当进行ESP封装时,检查到SA中的udp_encap标记被设置,则在封装外部IP头前先根据设置的NAT-t封装类型,封装一个UDP头,并调整buffer的各个字段;解封装方向,收到UDP 4500端口报文,则直接跳过UDP头部进行后续处理。

3.2.1 SA中添加封装结构

NAT-T的封装源端口和type是变化的,因此需要从SA中去获取每一次的值。

struct ipsec_encap_tmpl {

__u16 encap_type;

__be16 encap_sport;

__be16 encap_dport;

};

typedef struct

{

……….

  u8 udp_encap;

  ipsec_encap_tmpl encap;//for NAT-T

………

} ipsec_sa_t;

3.2.2 ESP 中加封装实现

如果SA中udp_encap标记被设置,则直接根据ipsec_encap_tmpl结构进行封装处理。如果封装类型为UDP_ENCAP_ESPINUDP_NON_IKE,则需要添加两个U32的数据。

always_inline uword

esp_encrypt_inline (vlib_main_t * vm,

    vlib_node_runtime_t * node, vlib_frame_t * from_frame,

    int is_ip6)

{

if (sa0->udp_encap)

{

    u32 ike_len = 0;

    struct udp_header_t *uh;

    ouh0->udp.src_port = sa0->encap.encap_sport;

    ouh0->udp.dst_port = sa0->encap.encap_dport;

    ouh0->udp.checksum = 0;

 

    uh = &ouh0->udp;

    switch (sa0->encap.encap_type) {

    default:

    case UDP_ENCAP_ESPINUDP:

      break;

    case UDP_ENCAP_ESPINUDP_NON_IKE:

      udpdata32 = (__be32 *)(uh + 1);

      udpdata32[0] = udpdata32[1] = 0;

      ike_len = 2*sizeof(udpdata32);

      break;

    }

    ouh0->ip4.protocol = IP_PROTOCOL_UDP;

    ip_udp_hdr_size =

    sizeof (udp_header_t) + sizeof (ip4_header_t) + ike_len;

    ouh0->udp.length =  data_len;

}

}

3.2.3 ESP解封装中处理

解封装流程,在获取ESP头部时需要判断IP头部中的协议字段,协议字段是IP_PROTOCOL_UDP时需要偏移UDP头部才能正确的获取ESP头。

VLIB_NODE_FN (ipsec4_input_node) (vlib_main_t * vm,

  vlib_node_runtime_t * node,

  vlib_frame_t * from_frame)

{

........

    

    esp_header_t *esp0;

    esp0 = (esp_header_t *) ((u8 *) ip0 + ip4_header_bytes (ip0));

    if (PREDICT_FALSE (ip0->protocol == IP_PROTOCOL_UDP))

    {

      esp0 = (esp_header_t *) ((u8 *) esp0 + sizeof (udp_header_t));//偏移UDP头部

    }

      

.......

}

3.2.4 QAT硬件加解密处理(选做)

在QAT处理流程中同样加入对UDP头部的处理,加封装流程判断SA中udp_encap标记确定是否需要封装UDP 头部。解封装流程添加对UDP 报文的处理,这里同样需要对UDP报文进行区分,如果是IPSEC NAT-T非数据报文,则需要上送控制面进行处理,QAT中只处理解封装数据报文。

 

 

文章来自个人专栏
DPU网络
3 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0