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
|