Signed-off-by: Shinta Sugimoto <[EMAIL PROTECTED]> diff --git a/include/linux/pfkeyv2.h b/include/linux/pfkeyv2.h index 265bafa..26a518b 100644 --- a/include/linux/pfkeyv2.h +++ b/include/linux/pfkeyv2.h @@ -251,7 +251,8 @@ #define SADB_X_SPDSETIDX 20 #define SADB_X_SPDEXPIRE 21 #define SADB_X_SPDDELETE2 22 #define SADB_X_NAT_T_NEW_MAPPING 23 -#define SADB_MAX 23 +#define SADB_X_MIGRATE 24 +#define SADB_MAX 24 /* Security Association flags */ #define SADB_SAFLAGS_PFS 1 diff --git a/net/key/af_key.c b/net/key/af_key.c index 5dd5094..c13c50a 100644 --- a/net/key/af_key.c +++ b/net/key/af_key.c @@ -2345,6 +2345,159 @@ out: return err; } +#ifdef CONFIG_NET_KEY_MIGRATE +static int parse_sockaddr_pair(struct sockaddr *sa, + xfrm_address_t *saddr, + xfrm_address_t *daddr, + u16 *family) +{ + switch (sa->sa_family) { + case AF_INET: + { + struct sockaddr_in *sin; + sin = (struct sockaddr_in *)sa; + if ((sin+1)->sin_family != AF_INET) + return -EINVAL; + memcpy(&saddr->a4, &sin->sin_addr, + sizeof(struct in_addr)); + sin++; + memcpy(&daddr->a4, &sin->sin_addr, + sizeof(struct in_addr)); + *family = AF_INET; + break; + } +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + case AF_INET6: + { + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)sa; + if ((sin6+1)->sin6_family != AF_INET6) + return -EINVAL; + memcpy(&saddr->a6, &sin6->sin6_addr, + sizeof(struct in6_addr)); + sin6++; + memcpy(&daddr->a6, &sin6->sin6_addr, + sizeof(struct in6_addr)); + *family = AF_INET6; + break; + } +#endif + default: + return -EINVAL; + } + return 0; +} + +/* + * Update locators stored in a given IPsec security association. + * Note that SPD as well as SAD is also updated. + */ +static int pfkey_migrate(struct sock *sk, struct sk_buff *skb, + struct sadb_msg *hdr, void **ext_hdrs) +{ + int i, len, err = -EINVAL; + u8 dir; + struct sadb_address *sa; + struct sadb_x_policy *pol; + struct sadb_x_ipsecrequest *rq; + struct xfrm_selector sel; + struct xfrm_migrate m[XFRM_MAX_DEPTH]; + struct xfrm_migrate *mp; + + if (!present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC - 1], + ext_hdrs[SADB_EXT_ADDRESS_DST - 1]) || + !ext_hdrs[SADB_X_EXT_POLICY - 1]) { + err = -EINVAL; + goto out; + } + + pol = ext_hdrs[SADB_X_EXT_POLICY - 1]; + if (!pol) { + err = -EINVAL; + goto out; + } + if (pol->sadb_x_policy_dir >= IPSEC_DIR_MAX) { + printk("pfkey: invalid policy dir (%d) specified.\n", + pol->sadb_x_policy_dir); + err = -EINVAL; + goto out; + } + dir = pol->sadb_x_policy_dir - 1; + memset(&sel, 0, sizeof(sel)); + + /* set source address info of selector */ + sa = ext_hdrs[SADB_EXT_ADDRESS_SRC - 1]; + sel.family = pfkey_sadb_addr2xfrm_addr(sa, &sel.saddr); + sel.prefixlen_s = sa->sadb_address_prefixlen; + /* allow IPSEC_PROTO_ANY to match all protocols */ + //sel.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto); + sel.proto = sa->sadb_address_proto; + sel.sport = ((struct sockaddr_in *)(sa+1))->sin_port; + if (sel.sport) + sel.sport_mask = ~0; + + /* set destination address info of selector */ + sa = ext_hdrs[SADB_EXT_ADDRESS_DST - 1], + pfkey_sadb_addr2xfrm_addr(sa, &sel.daddr); + sel.prefixlen_d = sa->sadb_address_prefixlen; + /* allow IPSEC_PROTO_ANY to match all protocols */ + //sel.proto = pfkey_proto_to_xfrm(sa->sadb_address_proto); + sel.proto = sa->sadb_address_proto; + sel.dport = ((struct sockaddr_in *)(sa+1))->sin_port; + if (sel.dport) + sel.dport_mask = ~0; + + rq = (struct sadb_x_ipsecrequest *)(pol + 1); + + /* extract ipsecrequests */ + i = 0; + len = pol->sadb_x_policy_len * 8 - sizeof(struct sadb_x_policy); + + while (len >= sizeof(struct sadb_x_ipsecrequest)) { + if (!(i % 2)) { + /* old endoints */ + mp = &m[i/2]; + parse_sockaddr_pair((struct sockaddr *)(rq + 1), + &mp->old_saddr, + &mp->old_daddr, + &mp->old_family); + } else { + /* new endoints */ + mp = &m[(i-1)/2]; + parse_sockaddr_pair((struct sockaddr *)(rq + 1), + &mp->new_saddr, + &mp->new_daddr, + &mp->new_family); + } + mp->proto = rq->sadb_x_ipsecrequest_proto; + mp->mode = rq->sadb_x_ipsecrequest_mode - 1; + mp->reqid = rq->sadb_x_ipsecrequest_reqid; + + len -= rq->sadb_x_ipsecrequest_len; + rq = (struct sadb_x_ipsecrequest *)((u8 *)rq + rq->sadb_x_ipsecrequest_len); + i++; + } + + if (!i || i % 2) { + printk("pfkey: invalid number (%d) of ipsecrequest(s).\n", i); + err = -EINVAL; + goto out; + } + + return xfrm_migrate(&sel, dir, XFRM_POLICY_TYPE_MAIN, m, i/2); + + out: + return err; +} +#else +static int pfkey_migrate(struct sock *sk, struct sk_buff *skb, + struct sadb_msg *hdr, void **ext_hdrs) +{ + return -ENOPROTOOPT; +} +#endif + + static int pfkey_spdget(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs) { unsigned int dir; @@ -2473,6 +2626,8 @@ static pfkey_handler pfkey_funcs[SADB_MA [SADB_X_SPDFLUSH] = pfkey_spdflush, [SADB_X_SPDSETIDX] = pfkey_spdadd, [SADB_X_SPDDELETE2] = pfkey_spdget, + [SADB_X_NAT_T_NEW_MAPPING] = NULL, + [SADB_X_MIGRATE] = pfkey_migrate, }; static int pfkey_process(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr) @@ -3118,6 +3273,265 @@ #endif return pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_REGISTERED, NULL); } +#ifdef CONFIG_NET_KEY_MIGRATE +static int set_sadb_address(struct sk_buff *skb, int sasize, int type, + struct xfrm_selector *sel) +{ + struct sadb_address *addr; + struct sockaddr_in *sin; +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + struct sockaddr_in6 *sin6; +#endif + addr = (struct sadb_address *)skb_put(skb, sizeof(struct sadb_address) + sasize); + addr->sadb_address_len = (sizeof(struct sadb_address) + sasize)/8; + addr->sadb_address_exttype = type; + addr->sadb_address_proto = sel->proto; + addr->sadb_address_reserved = 0; + + switch (type) { + case SADB_EXT_ADDRESS_SRC: + if (sel->family == AF_INET) { + addr->sadb_address_prefixlen = sel->prefixlen_s; + sin = (struct sockaddr_in *)(addr + 1); + sin->sin_family = AF_INET; + memcpy(&sin->sin_addr.s_addr, &sel->saddr, + sizeof(struct in_addr)); + sin->sin_port = 0; + memset(sin->sin_zero, 0, sizeof(sin->sin_zero)); + } +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + else if (sel->family == AF_INET6) { + addr->sadb_address_prefixlen = sel->prefixlen_s; + sin6 = (struct sockaddr_in6 *)(addr + 1); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = 0; + sin6->sin6_flowinfo = 0; + memcpy(&sin6->sin6_addr.s6_addr, &sel->saddr, + sizeof(struct in6_addr)); + } +#endif + break; + case SADB_EXT_ADDRESS_DST: + if (sel->family == AF_INET) { + addr->sadb_address_prefixlen = sel->prefixlen_d; + sin = (struct sockaddr_in *)(addr + 1); + sin->sin_family = AF_INET; + memcpy(&sin->sin_addr.s_addr, &sel->daddr, + sizeof(struct in_addr)); + sin->sin_port = 0; + memset(sin->sin_zero, 0, sizeof(sin->sin_zero)); + } +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + else if (sel->family == AF_INET6) { + addr->sadb_address_prefixlen = sel->prefixlen_d; + sin6 = (struct sockaddr_in6 *)(addr + 1); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = 0; + sin6->sin6_flowinfo = 0; + memcpy(&sin6->sin6_addr.s6_addr, &sel->daddr, + sizeof(struct in6_addr)); + } +#endif + break; + default: + return -EINVAL; + } + + return 0; +} + +static int set_ipsecrequest(struct sk_buff *skb, + uint8_t proto, + uint8_t mode, + int level, + uint32_t reqid, + uint8_t family, + xfrm_address_t *src, + xfrm_address_t *dst) +{ + struct sadb_x_ipsecrequest *rq; + struct sockaddr_in *sin; +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + struct sockaddr_in6 *sin6; +#endif + int sasize; + int size_req; + + sasize = pfkey_sockaddr_size(family); + if (!sasize) + return -EINVAL; + size_req = sizeof(struct sadb_x_ipsecrequest) + sasize * 2; + + rq = (struct sadb_x_ipsecrequest *)skb_put(skb, size_req); + memset(rq, 0, size_req); + rq->sadb_x_ipsecrequest_len = size_req; + rq->sadb_x_ipsecrequest_proto = proto; + rq->sadb_x_ipsecrequest_mode = mode; + rq->sadb_x_ipsecrequest_level = level; + rq->sadb_x_ipsecrequest_reserved1 = 0; + rq->sadb_x_ipsecrequest_reqid = reqid; + rq->sadb_x_ipsecrequest_reserved2 = 0; + + switch (family) { + case AF_INET: + sin = (struct sockaddr_in *)(rq + 1); + sin->sin_family = AF_INET; + memcpy(&sin->sin_addr.s_addr, src, sizeof(struct in_addr)); + sin++; + sin->sin_family = AF_INET; + memcpy(&sin->sin_addr.s_addr, dst, sizeof(struct in_addr)); + break; +#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) + case AF_INET6: + sin6 = (struct sockaddr_in6 *)(rq + 1); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = 0; + sin6->sin6_flowinfo = 0; + sin6->sin6_scope_id = 0; + memcpy(&sin6->sin6_addr.s6_addr, src, sizeof(struct in6_addr)); + sin6++; + sin6->sin6_family = AF_INET6; + sin6->sin6_port = 0; + sin6->sin6_flowinfo = 0; + sin6->sin6_scope_id = 0; + memcpy(&sin6->sin6_addr.s6_addr, dst, sizeof(struct in6_addr)); + break; +#endif + default: + return -EINVAL; + } + + return 0; +} +#endif + +#ifdef CONFIG_NET_KEY_MIGRATE +static int pfkey_send_migrate(struct xfrm_selector *sel, + u8 dir, + u8 type, + struct xfrm_migrate *m, + int num_bundles) +{ + int i; + int sasize_sel; + int sasize_lp_old; + int sasize_lp_new; + int size = 0; + int size_pol = 0; + struct sk_buff *skb; + struct sadb_msg *hdr; + struct sadb_x_policy *pol; + struct xfrm_migrate *mp; + + if (type != XFRM_POLICY_TYPE_MAIN) + return 0; + + if (num_bundles <= 0 || num_bundles > XFRM_MAX_DEPTH) + return -EINVAL; + + /* selector */ + sasize_sel = pfkey_sockaddr_size(sel->family); + if (!sasize_sel) + return -EINVAL; + size += (sizeof(struct sadb_address) + sasize_sel) * 2; + + /* policy info */ + size_pol += sizeof(struct sadb_x_policy); + + /* ipsecrequests */ + for (i=0, mp=m; i<num_bundles; i++, mp++) { + /* old locator pair */ + sasize_lp_old = pfkey_sockaddr_size(mp->old_family); + if (!sasize_lp_old) { + printk("pfkey: invalid sockaddr size specified\n"); + return -EINVAL; + } + size_pol += sizeof(struct sadb_x_ipsecrequest) + sasize_lp_old * 2; + /* new locator pair */ + sasize_lp_new = pfkey_sockaddr_size(mp->new_family); + if (!sasize_lp_new) { + printk("pfkey: invalid sockaddr size specified\n"); + return -EINVAL; + } + size_pol += sizeof(struct sadb_x_ipsecrequest) + sasize_lp_new * 2; + } + + size += sizeof(struct sadb_msg) + size_pol; + + /* alloc buffer */ + skb = alloc_skb(size, GFP_ATOMIC); + if (skb == NULL) + return -ENOMEM; + + hdr = (struct sadb_msg *)skb_put(skb, sizeof(struct sadb_msg)); + hdr->sadb_msg_version = PF_KEY_V2; + hdr->sadb_msg_type = SADB_X_MIGRATE; + hdr->sadb_msg_satype = pfkey_proto2satype(m->proto); + hdr->sadb_msg_len = size/8; + hdr->sadb_msg_errno = 0; + hdr->sadb_msg_reserved = 0; + hdr->sadb_msg_seq = 0; + hdr->sadb_msg_pid = 0; + + /* selector src */ + set_sadb_address(skb, sasize_sel, SADB_EXT_ADDRESS_SRC, sel); + + /* selector dst */ + set_sadb_address(skb, sasize_sel, SADB_EXT_ADDRESS_DST, sel); + + /* policy information */ + pol = (struct sadb_x_policy *)skb_put(skb, sizeof(struct sadb_x_policy)); + pol->sadb_x_policy_len = size_pol/8; + pol->sadb_x_policy_exttype = SADB_X_EXT_POLICY; + pol->sadb_x_policy_type = IPSEC_POLICY_IPSEC; + pol->sadb_x_policy_dir = dir + 1; + pol->sadb_x_policy_id = 0; + pol->sadb_x_policy_priority = 0; + + for (i=0, mp=m; i<num_bundles; i++, mp++) { + /* old ipsecrequest */ + if (set_ipsecrequest(skb, + mp->proto, + mp->mode + 1, + (mp->reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE), + mp->reqid, + mp->old_family, + &mp->old_saddr, + &mp->old_daddr) < 0) { + printk("pfkey: failed to set old ipsecrequest\n"); + return -EINVAL; + } + + /* new ipsecrequest */ + if (set_ipsecrequest(skb, + mp->proto, + mp->mode + 1, + (mp->reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE), + mp->reqid, + mp->new_family, + &mp->new_saddr, + &mp->new_daddr) < 0) { + printk("pfkey: failed to set new ipsecrequest\n"); + return -EINVAL; + } + } + + /* broadcast migrate message to sockets */ + pfkey_broadcast(skb, GFP_ATOMIC, BROADCAST_ALL, NULL); + + return 0; +} +#else +static int pfkey_send_migrate(struct xfrm_selector *sel, + u8 dir, + u8 type, + struct xfrm_migrate *m, + int num_bundles) +{ + return -ENOPROTOOPT; +} +#endif + static int pfkey_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, size_t len) { @@ -3287,6 +3701,7 @@ static struct xfrm_mgr pfkeyv2_mgr = .compile_policy = pfkey_compile_policy, .new_mapping = pfkey_send_new_mapping, .notify_policy = pfkey_send_policy_notify, + .migrate = pfkey_send_migrate, }; static void __exit ipsec_pfkey_exit(void)
- To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html