Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[linux-2.6-block.git] / net / netfilter / ipvs / ip_vs_ctl.c
index 741d91aa4a8de63157b37fdd89294b156f34350c..07e0967bf1296671657bda8f77e311cb28d3cb99 100644 (file)
@@ -510,15 +510,37 @@ static inline unsigned int ip_vs_rs_hashkey(int af,
 static void ip_vs_rs_hash(struct netns_ipvs *ipvs, struct ip_vs_dest *dest)
 {
        unsigned int hash;
+       __be16 port;
 
        if (dest->in_rs_table)
                return;
 
+       switch (IP_VS_DFWD_METHOD(dest)) {
+       case IP_VS_CONN_F_MASQ:
+               port = dest->port;
+               break;
+       case IP_VS_CONN_F_TUNNEL:
+               switch (dest->tun_type) {
+               case IP_VS_CONN_F_TUNNEL_TYPE_GUE:
+                       port = dest->tun_port;
+                       break;
+               case IP_VS_CONN_F_TUNNEL_TYPE_IPIP:
+               case IP_VS_CONN_F_TUNNEL_TYPE_GRE:
+                       port = 0;
+                       break;
+               default:
+                       return;
+               }
+               break;
+       default:
+               return;
+       }
+
        /*
         *      Hash by proto,addr,port,
         *      which are the parameters of the real service.
         */
-       hash = ip_vs_rs_hashkey(dest->af, &dest->addr, dest->port);
+       hash = ip_vs_rs_hashkey(dest->af, &dest->addr, port);
 
        hlist_add_head_rcu(&dest->d_list, &ipvs->rs_table[hash]);
        dest->in_rs_table = 1;
@@ -550,7 +572,8 @@ bool ip_vs_has_real_service(struct netns_ipvs *ipvs, int af, __u16 protocol,
                if (dest->port == dport &&
                    dest->af == af &&
                    ip_vs_addr_equal(af, &dest->addr, daddr) &&
-                   (dest->protocol == protocol || dest->vfwmark)) {
+                   (dest->protocol == protocol || dest->vfwmark) &&
+                   IP_VS_DFWD_METHOD(dest) == IP_VS_CONN_F_MASQ) {
                        /* HIT */
                        return true;
                }
@@ -580,7 +603,37 @@ struct ip_vs_dest *ip_vs_find_real_service(struct netns_ipvs *ipvs, int af,
                if (dest->port == dport &&
                    dest->af == af &&
                    ip_vs_addr_equal(af, &dest->addr, daddr) &&
-                       (dest->protocol == protocol || dest->vfwmark)) {
+                   (dest->protocol == protocol || dest->vfwmark) &&
+                   IP_VS_DFWD_METHOD(dest) == IP_VS_CONN_F_MASQ) {
+                       /* HIT */
+                       return dest;
+               }
+       }
+
+       return NULL;
+}
+
+/* Find real service record by <af,addr,tun_port>.
+ * In case of multiple records with the same <af,addr,tun_port>, only
+ * the first found record is returned.
+ *
+ * To be called under RCU lock.
+ */
+struct ip_vs_dest *ip_vs_find_tunnel(struct netns_ipvs *ipvs, int af,
+                                    const union nf_inet_addr *daddr,
+                                    __be16 tun_port)
+{
+       struct ip_vs_dest *dest;
+       unsigned int hash;
+
+       /* Check for "full" addressed entries */
+       hash = ip_vs_rs_hashkey(af, daddr, tun_port);
+
+       hlist_for_each_entry_rcu(dest, &ipvs->rs_table[hash], d_list) {
+               if (dest->tun_port == tun_port &&
+                   dest->af == af &&
+                   ip_vs_addr_equal(af, &dest->addr, daddr) &&
+                   IP_VS_DFWD_METHOD(dest) == IP_VS_CONN_F_TUNNEL) {
                        /* HIT */
                        return dest;
                }
@@ -826,24 +879,29 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest,
        conn_flags = udest->conn_flags & IP_VS_CONN_F_DEST_MASK;
        conn_flags |= IP_VS_CONN_F_INACTIVE;
 
+       /* Need to rehash? */
+       if ((udest->conn_flags & IP_VS_CONN_F_FWD_MASK) !=
+           IP_VS_DFWD_METHOD(dest) ||
+           udest->tun_type != dest->tun_type ||
+           udest->tun_port != dest->tun_port)
+               ip_vs_rs_unhash(dest);
+
        /* set the tunnel info */
        dest->tun_type = udest->tun_type;
        dest->tun_port = udest->tun_port;
+       dest->tun_flags = udest->tun_flags;
 
        /* set the IP_VS_CONN_F_NOOUTPUT flag if not masquerading/NAT */
        if ((conn_flags & IP_VS_CONN_F_FWD_MASK) != IP_VS_CONN_F_MASQ) {
                conn_flags |= IP_VS_CONN_F_NOOUTPUT;
        } else {
-               /*
-                *    Put the real service in rs_table if not present.
-                *    For now only for NAT!
-                */
-               ip_vs_rs_hash(ipvs, dest);
                /* FTP-NAT requires conntrack for mangling */
                if (svc->port == FTPPORT)
                        ip_vs_register_conntrack(svc);
        }
        atomic_set(&dest->conn_flags, conn_flags);
+       /* Put the real service in rs_table if not present. */
+       ip_vs_rs_hash(ipvs, dest);
 
        /* bind the service */
        old_svc = rcu_dereference_protected(dest->svc, 1);
@@ -2904,6 +2962,7 @@ static const struct nla_policy ip_vs_dest_policy[IPVS_DEST_ATTR_MAX + 1] = {
        [IPVS_DEST_ATTR_ADDR_FAMILY]    = { .type = NLA_U16 },
        [IPVS_DEST_ATTR_TUN_TYPE]       = { .type = NLA_U8 },
        [IPVS_DEST_ATTR_TUN_PORT]       = { .type = NLA_U16 },
+       [IPVS_DEST_ATTR_TUN_FLAGS]      = { .type = NLA_U16 },
 };
 
 static int ip_vs_genl_fill_stats(struct sk_buff *skb, int container_type,
@@ -3210,6 +3269,8 @@ static int ip_vs_genl_fill_dest(struct sk_buff *skb, struct ip_vs_dest *dest)
                       dest->tun_type) ||
            nla_put_be16(skb, IPVS_DEST_ATTR_TUN_PORT,
                         dest->tun_port) ||
+           nla_put_u16(skb, IPVS_DEST_ATTR_TUN_FLAGS,
+                       dest->tun_flags) ||
            nla_put_u32(skb, IPVS_DEST_ATTR_U_THRESH, dest->u_threshold) ||
            nla_put_u32(skb, IPVS_DEST_ATTR_L_THRESH, dest->l_threshold) ||
            nla_put_u32(skb, IPVS_DEST_ATTR_ACTIVE_CONNS,
@@ -3330,7 +3391,8 @@ static int ip_vs_genl_parse_dest(struct ip_vs_dest_user_kern *udest,
        /* If a full entry was requested, check for the additional fields */
        if (full_entry) {
                struct nlattr *nla_fwd, *nla_weight, *nla_u_thresh,
-                             *nla_l_thresh, *nla_tun_type, *nla_tun_port;
+                             *nla_l_thresh, *nla_tun_type, *nla_tun_port,
+                             *nla_tun_flags;
 
                nla_fwd         = attrs[IPVS_DEST_ATTR_FWD_METHOD];
                nla_weight      = attrs[IPVS_DEST_ATTR_WEIGHT];
@@ -3338,6 +3400,7 @@ static int ip_vs_genl_parse_dest(struct ip_vs_dest_user_kern *udest,
                nla_l_thresh    = attrs[IPVS_DEST_ATTR_L_THRESH];
                nla_tun_type    = attrs[IPVS_DEST_ATTR_TUN_TYPE];
                nla_tun_port    = attrs[IPVS_DEST_ATTR_TUN_PORT];
+               nla_tun_flags   = attrs[IPVS_DEST_ATTR_TUN_FLAGS];
 
                if (!(nla_fwd && nla_weight && nla_u_thresh && nla_l_thresh))
                        return -EINVAL;
@@ -3353,6 +3416,9 @@ static int ip_vs_genl_parse_dest(struct ip_vs_dest_user_kern *udest,
 
                if (nla_tun_port)
                        udest->tun_port = nla_get_be16(nla_tun_port);
+
+               if (nla_tun_flags)
+                       udest->tun_flags = nla_get_u16(nla_tun_flags);
        }
 
        return 0;