Add support for adding/modifying/deleting IPv6 service and dest entries by introducing several new functions and implementing corresponding switches and checks in existing code. Signed-off-by: Julius R. Volz <juliusv@xxxxxxxxxx> 3 files changed, 364 insertions(+), 19 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index ab59696..9790ed4 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -1017,6 +1017,12 @@ extern struct ctl_path net_vs_ctl_path[]; extern struct ip_vs_service * ip_vs_service_get(__u32 fwmark, __u16 protocol, __be32 vaddr, __be16 vport); +#ifdef CONFIG_IP_VS_IPV6 +extern struct ip_vs_service * +ip_vs_service_get_v6(__u32 fwmark, __u16 protocol, + const struct in6_addr *vaddr, __be16 vport); +#endif + static inline void ip_vs_service_put(struct ip_vs_service *svc) { atomic_dec(&svc->usecnt); @@ -1024,6 +1030,11 @@ static inline void ip_vs_service_put(struct ip_vs_service *svc) extern struct ip_vs_dest * ip_vs_lookup_real_service(__u16 protocol, __be32 daddr, __be16 dport); +#ifdef CONFIG_IP_VS_IPV6 +extern struct ip_vs_dest * +ip_vs_lookup_real_service_v6(__u16 protocol, const struct in6_addr *daddr, + __be16 dport); +#endif extern int ip_vs_use_count_inc(void); extern void ip_vs_use_count_dec(void); extern int ip_vs_control_init(void); @@ -1031,6 +1042,11 @@ extern void ip_vs_control_cleanup(void); extern struct ip_vs_dest * ip_vs_find_dest(__be32 daddr, __be16 dport, __be32 vaddr, __be16 vport, __u16 protocol); +#ifdef CONFIG_IP_VS_IPV6 +extern struct ip_vs_dest * +ip_vs_find_dest_v6(const struct in6_addr *daddr, __be16 dport, + const struct in6_addr *vaddr, __be16 vport, __u16 protocol); +#endif extern struct ip_vs_dest *ip_vs_try_bind_dest(struct ip_vs_conn *cp); diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index ea0fd77..6b031a8 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -633,8 +633,16 @@ struct ip_vs_dest *ip_vs_try_bind_dest(struct ip_vs_conn *cp) struct ip_vs_dest *dest; if ((cp) && (!cp->dest)) { - dest = ip_vs_find_dest(cp->daddr, cp->dport, - cp->vaddr, cp->vport, cp->protocol); +#ifdef CONFIG_IP_VS_IPV6 + if (cp->af == AF_INET6) + dest = ip_vs_find_dest_v6(&cp->daddr.v6, cp->dport, + &cp->vaddr.v6, cp->vport, + cp->protocol); + else +#endif + dest = ip_vs_find_dest(cp->daddr.v4, cp->dport, + cp->vaddr.v4, cp->vport, + cp->protocol); ip_vs_bind_dest(cp, dest); return dest; } else diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c index ca198b9..388278a 100644 --- a/net/netfilter/ipvs/ip_vs_ctl.c +++ b/net/netfilter/ipvs/ip_vs_ctl.c @@ -428,6 +428,31 @@ __ip_vs_service_get(__u16 protocol, __be32 vaddr, __be16 vport) return NULL; } +#ifdef CONFIG_IP_VS_IPV6 +static __inline__ struct ip_vs_service * +__ip_vs_service_get_v6(__u16 protocol, const struct in6_addr *vaddr, __be16 vport) +{ + unsigned hash; + struct ip_vs_service *svc; + + /* Check for "full" addressed entries */ + hash = ip_vs_svc_hashkey_v6(protocol, vaddr, vport); + + list_for_each_entry(svc, &ip_vs_svc_table[hash], s_list){ + if ((svc->af == AF_INET6) + && ipv6_addr_equal(&svc->addr.v6, vaddr) + && (svc->port == vport) + && (svc->protocol == protocol)) { + /* HIT */ + atomic_inc(&svc->usecnt); + return svc; + } + } + + return NULL; +} +#endif + /* * Get service by {fwmark} in the service table. @@ -500,6 +525,56 @@ ip_vs_service_get(__u32 fwmark, __u16 protocol, __be32 vaddr, __be16 vport) return svc; } +#ifdef CONFIG_IP_VS_IPV6 +struct ip_vs_service * +ip_vs_service_get_v6(__u32 fwmark, __u16 protocol, const struct in6_addr *vaddr, __be16 vport) +{ + struct ip_vs_service *svc; + + read_lock(&__ip_vs_svc_lock); + + /* + * Check the table hashed by fwmark first + */ + if (fwmark && (svc = __ip_vs_svc_fwm_get(fwmark))) + goto out; + + /* + * Check the table hashed by <protocol,addr,port> + * for "full" addressed entries + */ + svc = __ip_vs_service_get_v6(protocol, vaddr, vport); + + if (svc == NULL + && protocol == IPPROTO_TCP + && atomic_read(&ip_vs_ftpsvc_counter) + && (vport == FTPDATA || ntohs(vport) >= PROT_SOCK)) { + /* + * Check if ftp service entry exists, the packet + * might belong to FTP data connections. + */ + svc = __ip_vs_service_get_v6(protocol, vaddr, FTPPORT); + } + + if (svc == NULL + && atomic_read(&ip_vs_nullsvc_counter)) { + /* + * Check if the catch-all port (port zero) exists + */ + svc = __ip_vs_service_get_v6(protocol, vaddr, 0); + } + + out: + read_unlock(&__ip_vs_svc_lock); + + IP_VS_DBG(9, "lookup service: fwm %u %s " NIP6_FMT ":%u %s\n", + fwmark, ip_vs_proto_name(protocol), + NIP6(*vaddr), ntohs(vport), + svc?"hit":"not hit"); + + return svc; +} +#endif static inline void __ip_vs_bind_svc(struct ip_vs_dest *dest, struct ip_vs_service *svc) @@ -621,6 +696,38 @@ ip_vs_lookup_real_service(__u16 protocol, __be32 daddr, __be16 dport) return NULL; } +#ifdef CONFIG_IP_VS_IPV6 +struct ip_vs_dest * +ip_vs_lookup_real_service_v6(__u16 protocol, const struct in6_addr *daddr, + __be16 dport) +{ + unsigned hash; + struct ip_vs_dest *dest; + + /* + * Check for "full" addressed entries + * Return the first found entry + */ + hash = ip_vs_rs_hashkey_v6(daddr, dport); + + read_lock(&__ip_vs_rs_lock); + list_for_each_entry(dest, &ip_vs_rtable[hash], d_list) { + if ((dest->af == AF_INET6) + && (ipv6_addr_equal(&dest->addr.v6, daddr)) + && (dest->port == dport) + && ((dest->protocol == protocol) || + dest->vfwmark)) { + /* HIT */ + read_unlock(&__ip_vs_rs_lock); + return dest; + } + } + read_unlock(&__ip_vs_rs_lock); + + return NULL; +} +#endif + /* * Lookup destination by {addr,port} in the given service */ @@ -643,6 +750,29 @@ ip_vs_lookup_dest(struct ip_vs_service *svc, __be32 daddr, __be16 dport) return NULL; } +#ifdef CONFIG_IP_VS_IPV6 +static struct ip_vs_dest * +ip_vs_lookup_dest_v6(struct ip_vs_service *svc, const struct in6_addr *daddr, + __be16 dport) +{ + struct ip_vs_dest *dest; + + /* + * Find the destination for the given service + */ + list_for_each_entry(dest, &svc->destinations, n_list) { + if ((dest->af == AF_INET6) + && ipv6_addr_equal(&dest->addr.v6, daddr) + && (dest->port == dport)) { + /* HIT */ + return dest; + } + } + + return NULL; +} +#endif + /* * Find destination by {daddr,dport,vaddr,protocol} * Cretaed to be used in ip_vs_process_message() in @@ -669,6 +799,25 @@ struct ip_vs_dest *ip_vs_find_dest(__be32 daddr, __be16 dport, return dest; } +#ifdef CONFIG_IP_VS_IPV6 +struct ip_vs_dest *ip_vs_find_dest_v6(const struct in6_addr *daddr, __be16 dport, + const struct in6_addr *vaddr, __be16 vport, + __u16 protocol) +{ + struct ip_vs_dest *dest; + struct ip_vs_service *svc; + + svc = ip_vs_service_get_v6(0, protocol, vaddr, vport); + if (!svc) + return NULL; + dest = ip_vs_lookup_dest_v6(svc, daddr, dport); + if (dest) + atomic_inc(&dest->refcnt); + ip_vs_service_put(svc); + return dest; +} +#endif + /* * Lookup dest by {svc,addr,port} in the destination trash. * The destination trash is used to hold the destinations that are removed @@ -723,12 +872,59 @@ ip_vs_trash_get_dest(struct ip_vs_service *svc, __be32 daddr, __be16 dport) return NULL; } +#ifdef CONFIG_IP_VS_IPV6 +static struct ip_vs_dest * +ip_vs_trash_get_dest_v6(struct ip_vs_service *svc, const struct in6_addr *daddr, + __be16 dport) +{ + struct ip_vs_dest *dest, *nxt; + + /* + * Find the destination in trash + */ + list_for_each_entry_safe(dest, nxt, &ip_vs_dest_trash, n_list) { + IP_VS_DBG(3, "Destination %u/" NIP6_FMT ":%u still in trash, " + "dest->refcnt=%d\n", + dest->vfwmark, + NIP6(dest->addr.v6), ntohs(dest->port), + atomic_read(&dest->refcnt)); + if (dest->af == AF_INET6 && + ipv6_addr_equal(&dest->addr.v6, daddr) && + dest->port == dport && + dest->vfwmark == svc->fwmark && + dest->protocol == svc->protocol && + (svc->fwmark || + (ipv6_addr_equal(&dest->vaddr.v6, &svc->addr.v6) && + dest->vport == svc->port))) { + /* HIT */ + return dest; + } + + /* + * Try to purge the destination from trash if not referenced + */ + if (atomic_read(&dest->refcnt) == 1) { + IP_VS_DBG(3, "Removing destination %u/" NIP6_FMT ":%u " + "from trash\n", + dest->vfwmark, + NIP6(dest->addr.v6), ntohs(dest->port)); + list_del(&dest->n_list); + ip_vs_dst_reset(dest); + __ip_vs_unbind_svc(dest); + kfree(dest); + } + } + + return NULL; +} +#endif + /* * Clean up all the destinations in the trash * Called by the ip_vs_control_cleanup() * - * When the ip_vs_control_clearup is activated by ipvs module exit, + * When the ip_vs_control_cleanup is activated by ipvs module exit, * the service tables must have been flushed and all the connections * are expired, and the refcnt of each destination in the trash must * be 1, so we simply release them here. @@ -831,9 +1027,19 @@ ip_vs_new_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest, EnterFunction(2); - atype = inet_addr_type(&init_net, udest->addr); - if (atype != RTN_LOCAL && atype != RTN_UNICAST) - return -EINVAL; +#ifdef CONFIG_IP_VS_IPV6 + if (svc->af == AF_INET6) { + atype = ipv6_addr_type(&udest->addr.v6); + if (!(atype & IPV6_ADDR_UNICAST) && + !__ip_vs_addr_is_local_v6(&udest->addr.v6)) + return -EINVAL; + } else +#endif + { + atype = inet_addr_type(&init_net, udest->addr.v4); + if (atype != RTN_LOCAL && atype != RTN_UNICAST) + return -EINVAL; + } dest = kzalloc(sizeof(struct ip_vs_dest), GFP_ATOMIC); if (dest == NULL) { @@ -874,12 +1080,18 @@ static int ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest) { struct ip_vs_dest *dest; - __be32 daddr = udest->addr; + union ip_vs_addr_user daddr = udest->addr; __be16 dport = udest->port; int ret; EnterFunction(2); + if (udest->af != svc->af) { + IP_VS_ERR("ip_vs_add_dest(): address families of service and " + "destination do not match\n"); + return -EFAULT; + } + if (udest->weight < 0) { IP_VS_ERR("ip_vs_add_dest(): server weight less than zero\n"); return -ERANGE; @@ -894,7 +1106,14 @@ ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest) /* * Check if the dest already exists in the list */ - dest = ip_vs_lookup_dest(svc, daddr, dport); +#ifdef CONFIG_IP_VS_IPV6 + dest = (svc->af == AF_INET) + ? ip_vs_lookup_dest(svc, daddr.v4, dport) + : ip_vs_lookup_dest_v6(svc, &daddr.v6, dport); +#else + dest = ip_vs_lookup_dest(svc, daddr.v4, dport); +#endif + if (dest != NULL) { IP_VS_DBG(1, "ip_vs_add_dest(): dest already exists\n"); return -EEXIST; @@ -904,7 +1123,14 @@ ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest) * Check if the dest already exists in the trash and * is from the same service */ - dest = ip_vs_trash_get_dest(svc, daddr, dport); +#ifdef CONFIG_IP_VS_IPV6 + dest = (svc->af == AF_INET) + ? ip_vs_trash_get_dest(svc, daddr.v4, dport) + : ip_vs_trash_get_dest_v6(svc, &daddr.v6, dport); +#else + dest = ip_vs_trash_get_dest(svc, daddr.v4, dport); +#endif + if (dest != NULL) { IP_VS_DBG_V4(svc->af, 3, "Get destination %u.%u.%u.%u:%u from trash, " "dest->refcnt=%d, service %u/%u.%u.%u.%u:%u\n", @@ -989,11 +1215,17 @@ static int ip_vs_edit_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest) { struct ip_vs_dest *dest; - __be32 daddr = udest->addr; + union ip_vs_addr_user daddr = udest->addr; __be16 dport = udest->port; EnterFunction(2); + if (udest->af != svc->af) { + IP_VS_ERR("ip_vs_edit_dest(): address families of service and " + "destination do not match\n"); + return -EFAULT; + } + if (udest->weight < 0) { IP_VS_ERR("ip_vs_edit_dest(): server weight less than zero\n"); return -ERANGE; @@ -1008,7 +1240,14 @@ ip_vs_edit_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest) /* * Lookup the destination list */ - dest = ip_vs_lookup_dest(svc, daddr, dport); +#ifdef CONFIG_IP_VS_IPV6 + dest = (svc->af == AF_INET) + ? ip_vs_lookup_dest(svc, daddr.v4, dport) + : ip_vs_lookup_dest_v6(svc, &daddr.v6, dport); +#else + dest = ip_vs_lookup_dest(svc, daddr.v4, dport); +#endif + if (dest == NULL) { IP_VS_DBG(1, "ip_vs_edit_dest(): dest doesn't exist\n"); return -ENOENT; @@ -1104,15 +1343,28 @@ static void __ip_vs_unlink_dest(struct ip_vs_service *svc, * Delete a destination server in the given service */ static int -ip_vs_del_dest(struct ip_vs_service *svc,struct ip_vs_dest_user *udest) +ip_vs_del_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest) { struct ip_vs_dest *dest; - __be32 daddr = udest->addr; + union ip_vs_addr_user daddr = udest->addr; __be16 dport = udest->port; EnterFunction(2); - dest = ip_vs_lookup_dest(svc, daddr, dport); + if (udest->af != svc->af) { + IP_VS_ERR("ip_vs_add_dest(): address families of service and" + "destination do not match\n"); + return -EFAULT; + } + +#ifdef CONFIG_IP_VS_IPV6 + dest = (svc->af == AF_INET) + ? ip_vs_lookup_dest(svc, daddr.v4, dport) + : ip_vs_lookup_dest_v6(svc, &daddr.v6, dport); +#else + dest = ip_vs_lookup_dest(svc, daddr.v4, dport); +#endif + if (dest == NULL) { IP_VS_DBG(1, "ip_vs_del_dest(): destination not found!\n"); return -ENOENT; @@ -1165,6 +1417,19 @@ ip_vs_add_service(struct ip_vs_service_user *u, struct ip_vs_service **svc_p) goto out_mod_dec; } +#ifdef CONFIG_IP_VS_IPV6 + if (u->af == AF_INET6) { + if (!sched->supports_ipv6) { + ret = EAFNOSUPPORT; + goto out_err; + } + if ((u->netmask < 1) || (u->netmask > 128)) { + ret = EINVAL; + goto out_err; + } + } +#endif + svc = kzalloc(sizeof(struct ip_vs_service), GFP_ATOMIC); if (svc == NULL) { IP_VS_DBG(1, "ip_vs_add_service: kmalloc failed.\n"); @@ -1253,6 +1518,19 @@ ip_vs_edit_service(struct ip_vs_service *svc, struct ip_vs_service_user *u) } old_sched = sched; +#ifdef CONFIG_IP_VS_IPV6 + if (u->af == AF_INET6) { + if (!sched->supports_ipv6) { + ret = EAFNOSUPPORT; + goto out; + } + if ((u->netmask < 1) || (u->netmask > 128)) { + ret = EINVAL; + goto out; + } + } +#endif + write_lock_bh(&__ip_vs_svc_lock); /* @@ -1685,6 +1963,7 @@ static struct ctl_table vs_vars[] = { struct ctl_path net_vs_ctl_path[] = { { .procname = "net", .ctl_name = CTL_NET, }, + /* TODO IPv6: possible to move / duplicate this? */ { .procname = "ipv4", .ctl_name = NET_IPV4, }, { .procname = "vs", }, { } @@ -2031,12 +2310,33 @@ do_ip_vs_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len) if (cmd == IP_VS_SO_SET_ZERO) { /* if no service address is set, zero counters in all */ - if (!usvc->fwmark && !usvc->addr && !usvc->port) { +#ifdef CONFIG_IP_VS_IPV6 + struct in6_addr zero_addr = { .s6_addr32 = {0, 0, 0, 0} }; + if (usvc->af == AF_INET6 && !usvc->fwmark && + ipv6_addr_equal(&usvc->addr.v6,&zero_addr) && !usvc->port) { + ret = ip_vs_zero_all(); + goto out_unlock; + } +#endif + if (!usvc->fwmark && !usvc->addr.v4 && !usvc->port) { ret = ip_vs_zero_all(); goto out_unlock; } } + /* Check for valid address family */ + if (usvc->af != AF_INET) { +#ifdef CONFIG_IP_VS_IPV6 + if (usvc->af != AF_INET6) { + ret = -EAFNOSUPPORT; + goto out_unlock; + } +#else + ret = -EAFNOSUPPORT; + goto out_unlock; +#endif + } + /* Check for valid protocol: TCP or UDP, even for fwmark!=0 */ if (usvc->protocol!=IPPROTO_TCP && usvc->protocol!=IPPROTO_UDP) { IP_VS_ERR_V4(usvc->af, "set_ctl: invalid protocol: %d %d.%d.%d.%d:%d %s\n", @@ -2053,8 +2353,14 @@ do_ip_vs_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len) /* Lookup the exact service by <protocol, addr, port> or fwmark */ if (usvc->fwmark == 0) - svc = __ip_vs_service_get(usvc->protocol, - usvc->addr, usvc->port); +#ifdef CONFIG_IP_VS_IPV6 + if (usvc->af == AF_INET6) + svc = __ip_vs_service_get_v6(usvc->protocol, + &usvc->addr.v6, usvc->port); + else +#endif + svc = __ip_vs_service_get(usvc->protocol, + usvc->addr.v4, usvc->port); else svc = __ip_vs_svc_fwm_get(usvc->fwmark); @@ -2183,9 +2489,17 @@ __ip_vs_get_dest_entries(const struct ip_vs_get_dests *get, if (get->fwmark) svc = __ip_vs_svc_fwm_get(get->fwmark); + else if (get->af == AF_INET6) +#ifdef CONFIG_IP_VS_IPV6 + svc = __ip_vs_service_get_v6(get->protocol, + &get->addr.v6, get->port); +#else + return -EAFNOSUPPORT; +#endif else svc = __ip_vs_service_get(get->protocol, - get->addr, get->port); + get->addr.v4, get->port); + if (svc) { int count = 0; struct ip_vs_dest *dest; @@ -2325,9 +2639,16 @@ do_ip_vs_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) entry = (struct ip_vs_service_entry *)arg; if (entry->fwmark) svc = __ip_vs_svc_fwm_get(entry->fwmark); +#ifdef CONFIG_IP_VS_IPV6 + else if (entry->af == AF_INET6) + svc = __ip_vs_service_get_v6(entry->protocol, + &entry->addr.v6, + entry->port); +#endif else svc = __ip_vs_service_get(entry->protocol, - entry->addr, entry->port); + entry->addr.v4, entry->port); + if (svc) { ip_vs_copy_service(entry, svc); if (copy_to_user(user, entry, sizeof(*entry)) != 0) -- 1.5.3.6 -- To unsubscribe from this list: send the line "unsubscribe lvs-devel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html