Merge branch 'ipvs-next'
Simon Horman says: ==================== This pull requests makes the following changes: * Add simple weighted fail-over scheduler. - Unlike other IPVS schedulers this offers fail-over rather than load balancing. Connections are directed to the appropriate server based solely on highest weight value and server availability. - Thanks to Kenny Mathis * Support IPv6 real servers in IPv4 virtual-services and vice versa - This feature is supported in conjunction with the tunnel (IPIP) forwarding mechanism. That is, IPv4 may be forwarded in IPv6 and vice versa. - The motivation for this is to allow more flexibility in the choice of IP version offered by both virtual-servers and real-servers as they no longer need to match: An IPv4 connection from an end-user may be forwarded to a real-server using IPv6 and vice versa. - Further work need to be done to support this feature in conjunction with connection synchronisation. For now such configurations are not allowed. - This change includes update to netlink protocol, adding a new destination address family attribute. And the necessary changes to plumb this information throughout IPVS. - Thanks to Alex Gartrell and Julian Anastasov ==================== Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
commit
fcfa8f493f
|
@ -535,6 +535,7 @@ struct ip_vs_conn {
|
||||||
union nf_inet_addr daddr; /* destination address */
|
union nf_inet_addr daddr; /* destination address */
|
||||||
volatile __u32 flags; /* status flags */
|
volatile __u32 flags; /* status flags */
|
||||||
__u16 protocol; /* Which protocol (TCP/UDP) */
|
__u16 protocol; /* Which protocol (TCP/UDP) */
|
||||||
|
__u16 daf; /* Address family of the dest */
|
||||||
#ifdef CONFIG_NET_NS
|
#ifdef CONFIG_NET_NS
|
||||||
struct net *net; /* Name space */
|
struct net *net; /* Name space */
|
||||||
#endif
|
#endif
|
||||||
|
@ -648,6 +649,9 @@ struct ip_vs_dest_user_kern {
|
||||||
/* thresholds for active connections */
|
/* thresholds for active connections */
|
||||||
u32 u_threshold; /* upper threshold */
|
u32 u_threshold; /* upper threshold */
|
||||||
u32 l_threshold; /* lower threshold */
|
u32 l_threshold; /* lower threshold */
|
||||||
|
|
||||||
|
/* Address family of addr */
|
||||||
|
u16 af;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -986,6 +990,10 @@ struct netns_ipvs {
|
||||||
char backup_mcast_ifn[IP_VS_IFNAME_MAXLEN];
|
char backup_mcast_ifn[IP_VS_IFNAME_MAXLEN];
|
||||||
/* net name space ptr */
|
/* net name space ptr */
|
||||||
struct net *net; /* Needed by timer routines */
|
struct net *net; /* Needed by timer routines */
|
||||||
|
/* Number of heterogeneous destinations, needed because
|
||||||
|
* heterogeneous are not supported when synchronization is
|
||||||
|
* enabled */
|
||||||
|
unsigned int mixed_address_family_dests;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define DEFAULT_SYNC_THRESHOLD 3
|
#define DEFAULT_SYNC_THRESHOLD 3
|
||||||
|
@ -1210,7 +1218,7 @@ static inline void __ip_vs_conn_put(struct ip_vs_conn *cp)
|
||||||
void ip_vs_conn_put(struct ip_vs_conn *cp);
|
void ip_vs_conn_put(struct ip_vs_conn *cp);
|
||||||
void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport);
|
void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport);
|
||||||
|
|
||||||
struct ip_vs_conn *ip_vs_conn_new(const struct ip_vs_conn_param *p,
|
struct ip_vs_conn *ip_vs_conn_new(const struct ip_vs_conn_param *p, int dest_af,
|
||||||
const union nf_inet_addr *daddr,
|
const union nf_inet_addr *daddr,
|
||||||
__be16 dport, unsigned int flags,
|
__be16 dport, unsigned int flags,
|
||||||
struct ip_vs_dest *dest, __u32 fwmark);
|
struct ip_vs_dest *dest, __u32 fwmark);
|
||||||
|
@ -1396,8 +1404,9 @@ void ip_vs_unregister_nl_ioctl(void);
|
||||||
int ip_vs_control_init(void);
|
int ip_vs_control_init(void);
|
||||||
void ip_vs_control_cleanup(void);
|
void ip_vs_control_cleanup(void);
|
||||||
struct ip_vs_dest *
|
struct ip_vs_dest *
|
||||||
ip_vs_find_dest(struct net *net, int af, const union nf_inet_addr *daddr,
|
ip_vs_find_dest(struct net *net, int svc_af, int dest_af,
|
||||||
__be16 dport, const union nf_inet_addr *vaddr, __be16 vport,
|
const union nf_inet_addr *daddr, __be16 dport,
|
||||||
|
const union nf_inet_addr *vaddr, __be16 vport,
|
||||||
__u16 protocol, __u32 fwmark, __u32 flags);
|
__u16 protocol, __u32 fwmark, __u32 flags);
|
||||||
void ip_vs_try_bind_dest(struct ip_vs_conn *cp);
|
void ip_vs_try_bind_dest(struct ip_vs_conn *cp);
|
||||||
|
|
||||||
|
|
|
@ -384,6 +384,9 @@ enum {
|
||||||
IPVS_DEST_ATTR_PERSIST_CONNS, /* persistent connections */
|
IPVS_DEST_ATTR_PERSIST_CONNS, /* persistent connections */
|
||||||
|
|
||||||
IPVS_DEST_ATTR_STATS, /* nested attribute for dest stats */
|
IPVS_DEST_ATTR_STATS, /* nested attribute for dest stats */
|
||||||
|
|
||||||
|
IPVS_DEST_ATTR_ADDR_FAMILY, /* Address family of address */
|
||||||
|
|
||||||
__IPVS_DEST_ATTR_MAX,
|
__IPVS_DEST_ATTR_MAX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,16 @@ config IP_VS_WLC
|
||||||
If you want to compile it in kernel, say Y. To compile it as a
|
If you want to compile it in kernel, say Y. To compile it as a
|
||||||
module, choose M here. If unsure, say N.
|
module, choose M here. If unsure, say N.
|
||||||
|
|
||||||
|
config IP_VS_FO
|
||||||
|
tristate "weighted failover scheduling"
|
||||||
|
---help---
|
||||||
|
The weighted failover scheduling algorithm directs network
|
||||||
|
connections to the server with the highest weight that is
|
||||||
|
currently available.
|
||||||
|
|
||||||
|
If you want to compile it in kernel, say Y. To compile it as a
|
||||||
|
module, choose M here. If unsure, say N.
|
||||||
|
|
||||||
config IP_VS_LBLC
|
config IP_VS_LBLC
|
||||||
tristate "locality-based least-connection scheduling"
|
tristate "locality-based least-connection scheduling"
|
||||||
---help---
|
---help---
|
||||||
|
|
|
@ -26,6 +26,7 @@ obj-$(CONFIG_IP_VS_RR) += ip_vs_rr.o
|
||||||
obj-$(CONFIG_IP_VS_WRR) += ip_vs_wrr.o
|
obj-$(CONFIG_IP_VS_WRR) += ip_vs_wrr.o
|
||||||
obj-$(CONFIG_IP_VS_LC) += ip_vs_lc.o
|
obj-$(CONFIG_IP_VS_LC) += ip_vs_lc.o
|
||||||
obj-$(CONFIG_IP_VS_WLC) += ip_vs_wlc.o
|
obj-$(CONFIG_IP_VS_WLC) += ip_vs_wlc.o
|
||||||
|
obj-$(CONFIG_IP_VS_FO) += ip_vs_fo.o
|
||||||
obj-$(CONFIG_IP_VS_LBLC) += ip_vs_lblc.o
|
obj-$(CONFIG_IP_VS_LBLC) += ip_vs_lblc.o
|
||||||
obj-$(CONFIG_IP_VS_LBLCR) += ip_vs_lblcr.o
|
obj-$(CONFIG_IP_VS_LBLCR) += ip_vs_lblcr.o
|
||||||
obj-$(CONFIG_IP_VS_DH) += ip_vs_dh.o
|
obj-$(CONFIG_IP_VS_DH) += ip_vs_dh.o
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/in.h>
|
#include <linux/in.h>
|
||||||
|
#include <linux/inet.h>
|
||||||
#include <linux/net.h>
|
#include <linux/net.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
@ -77,6 +78,13 @@ static unsigned int ip_vs_conn_rnd __read_mostly;
|
||||||
#define CT_LOCKARRAY_SIZE (1<<CT_LOCKARRAY_BITS)
|
#define CT_LOCKARRAY_SIZE (1<<CT_LOCKARRAY_BITS)
|
||||||
#define CT_LOCKARRAY_MASK (CT_LOCKARRAY_SIZE-1)
|
#define CT_LOCKARRAY_MASK (CT_LOCKARRAY_SIZE-1)
|
||||||
|
|
||||||
|
/* We need an addrstrlen that works with or without v6 */
|
||||||
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
|
#define IP_VS_ADDRSTRLEN INET6_ADDRSTRLEN
|
||||||
|
#else
|
||||||
|
#define IP_VS_ADDRSTRLEN (8+1)
|
||||||
|
#endif
|
||||||
|
|
||||||
struct ip_vs_aligned_lock
|
struct ip_vs_aligned_lock
|
||||||
{
|
{
|
||||||
spinlock_t l;
|
spinlock_t l;
|
||||||
|
@ -488,7 +496,12 @@ static inline void ip_vs_bind_xmit(struct ip_vs_conn *cp)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IP_VS_CONN_F_TUNNEL:
|
case IP_VS_CONN_F_TUNNEL:
|
||||||
cp->packet_xmit = ip_vs_tunnel_xmit;
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
|
if (cp->daf == AF_INET6)
|
||||||
|
cp->packet_xmit = ip_vs_tunnel_xmit_v6;
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
cp->packet_xmit = ip_vs_tunnel_xmit;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IP_VS_CONN_F_DROUTE:
|
case IP_VS_CONN_F_DROUTE:
|
||||||
|
@ -514,7 +527,10 @@ static inline void ip_vs_bind_xmit_v6(struct ip_vs_conn *cp)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IP_VS_CONN_F_TUNNEL:
|
case IP_VS_CONN_F_TUNNEL:
|
||||||
cp->packet_xmit = ip_vs_tunnel_xmit_v6;
|
if (cp->daf == AF_INET6)
|
||||||
|
cp->packet_xmit = ip_vs_tunnel_xmit_v6;
|
||||||
|
else
|
||||||
|
cp->packet_xmit = ip_vs_tunnel_xmit;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case IP_VS_CONN_F_DROUTE:
|
case IP_VS_CONN_F_DROUTE:
|
||||||
|
@ -580,7 +596,7 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
|
||||||
ip_vs_proto_name(cp->protocol),
|
ip_vs_proto_name(cp->protocol),
|
||||||
IP_VS_DBG_ADDR(cp->af, &cp->caddr), ntohs(cp->cport),
|
IP_VS_DBG_ADDR(cp->af, &cp->caddr), ntohs(cp->cport),
|
||||||
IP_VS_DBG_ADDR(cp->af, &cp->vaddr), ntohs(cp->vport),
|
IP_VS_DBG_ADDR(cp->af, &cp->vaddr), ntohs(cp->vport),
|
||||||
IP_VS_DBG_ADDR(cp->af, &cp->daddr), ntohs(cp->dport),
|
IP_VS_DBG_ADDR(cp->daf, &cp->daddr), ntohs(cp->dport),
|
||||||
ip_vs_fwd_tag(cp), cp->state,
|
ip_vs_fwd_tag(cp), cp->state,
|
||||||
cp->flags, atomic_read(&cp->refcnt),
|
cp->flags, atomic_read(&cp->refcnt),
|
||||||
atomic_read(&dest->refcnt));
|
atomic_read(&dest->refcnt));
|
||||||
|
@ -616,7 +632,13 @@ void ip_vs_try_bind_dest(struct ip_vs_conn *cp)
|
||||||
struct ip_vs_dest *dest;
|
struct ip_vs_dest *dest;
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
dest = ip_vs_find_dest(ip_vs_conn_net(cp), cp->af, &cp->daddr,
|
|
||||||
|
/* This function is only invoked by the synchronization code. We do
|
||||||
|
* not currently support heterogeneous pools with synchronization,
|
||||||
|
* so we can make the assumption that the svc_af is the same as the
|
||||||
|
* dest_af
|
||||||
|
*/
|
||||||
|
dest = ip_vs_find_dest(ip_vs_conn_net(cp), cp->af, cp->af, &cp->daddr,
|
||||||
cp->dport, &cp->vaddr, cp->vport,
|
cp->dport, &cp->vaddr, cp->vport,
|
||||||
cp->protocol, cp->fwmark, cp->flags);
|
cp->protocol, cp->fwmark, cp->flags);
|
||||||
if (dest) {
|
if (dest) {
|
||||||
|
@ -671,7 +693,7 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp)
|
||||||
ip_vs_proto_name(cp->protocol),
|
ip_vs_proto_name(cp->protocol),
|
||||||
IP_VS_DBG_ADDR(cp->af, &cp->caddr), ntohs(cp->cport),
|
IP_VS_DBG_ADDR(cp->af, &cp->caddr), ntohs(cp->cport),
|
||||||
IP_VS_DBG_ADDR(cp->af, &cp->vaddr), ntohs(cp->vport),
|
IP_VS_DBG_ADDR(cp->af, &cp->vaddr), ntohs(cp->vport),
|
||||||
IP_VS_DBG_ADDR(cp->af, &cp->daddr), ntohs(cp->dport),
|
IP_VS_DBG_ADDR(cp->daf, &cp->daddr), ntohs(cp->dport),
|
||||||
ip_vs_fwd_tag(cp), cp->state,
|
ip_vs_fwd_tag(cp), cp->state,
|
||||||
cp->flags, atomic_read(&cp->refcnt),
|
cp->flags, atomic_read(&cp->refcnt),
|
||||||
atomic_read(&dest->refcnt));
|
atomic_read(&dest->refcnt));
|
||||||
|
@ -740,7 +762,7 @@ int ip_vs_check_template(struct ip_vs_conn *ct)
|
||||||
ntohs(ct->cport),
|
ntohs(ct->cport),
|
||||||
IP_VS_DBG_ADDR(ct->af, &ct->vaddr),
|
IP_VS_DBG_ADDR(ct->af, &ct->vaddr),
|
||||||
ntohs(ct->vport),
|
ntohs(ct->vport),
|
||||||
IP_VS_DBG_ADDR(ct->af, &ct->daddr),
|
IP_VS_DBG_ADDR(ct->daf, &ct->daddr),
|
||||||
ntohs(ct->dport));
|
ntohs(ct->dport));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -848,7 +870,7 @@ void ip_vs_conn_expire_now(struct ip_vs_conn *cp)
|
||||||
* Create a new connection entry and hash it into the ip_vs_conn_tab
|
* Create a new connection entry and hash it into the ip_vs_conn_tab
|
||||||
*/
|
*/
|
||||||
struct ip_vs_conn *
|
struct ip_vs_conn *
|
||||||
ip_vs_conn_new(const struct ip_vs_conn_param *p,
|
ip_vs_conn_new(const struct ip_vs_conn_param *p, int dest_af,
|
||||||
const union nf_inet_addr *daddr, __be16 dport, unsigned int flags,
|
const union nf_inet_addr *daddr, __be16 dport, unsigned int flags,
|
||||||
struct ip_vs_dest *dest, __u32 fwmark)
|
struct ip_vs_dest *dest, __u32 fwmark)
|
||||||
{
|
{
|
||||||
|
@ -867,6 +889,7 @@ ip_vs_conn_new(const struct ip_vs_conn_param *p,
|
||||||
setup_timer(&cp->timer, ip_vs_conn_expire, (unsigned long)cp);
|
setup_timer(&cp->timer, ip_vs_conn_expire, (unsigned long)cp);
|
||||||
ip_vs_conn_net_set(cp, p->net);
|
ip_vs_conn_net_set(cp, p->net);
|
||||||
cp->af = p->af;
|
cp->af = p->af;
|
||||||
|
cp->daf = dest_af;
|
||||||
cp->protocol = p->protocol;
|
cp->protocol = p->protocol;
|
||||||
ip_vs_addr_set(p->af, &cp->caddr, p->caddr);
|
ip_vs_addr_set(p->af, &cp->caddr, p->caddr);
|
||||||
cp->cport = p->cport;
|
cp->cport = p->cport;
|
||||||
|
@ -874,7 +897,7 @@ ip_vs_conn_new(const struct ip_vs_conn_param *p,
|
||||||
ip_vs_addr_set(p->protocol == IPPROTO_IP ? AF_UNSPEC : p->af,
|
ip_vs_addr_set(p->protocol == IPPROTO_IP ? AF_UNSPEC : p->af,
|
||||||
&cp->vaddr, p->vaddr);
|
&cp->vaddr, p->vaddr);
|
||||||
cp->vport = p->vport;
|
cp->vport = p->vport;
|
||||||
ip_vs_addr_set(p->af, &cp->daddr, daddr);
|
ip_vs_addr_set(cp->daf, &cp->daddr, daddr);
|
||||||
cp->dport = dport;
|
cp->dport = dport;
|
||||||
cp->flags = flags;
|
cp->flags = flags;
|
||||||
cp->fwmark = fwmark;
|
cp->fwmark = fwmark;
|
||||||
|
@ -1036,6 +1059,7 @@ static int ip_vs_conn_seq_show(struct seq_file *seq, void *v)
|
||||||
struct net *net = seq_file_net(seq);
|
struct net *net = seq_file_net(seq);
|
||||||
char pe_data[IP_VS_PENAME_MAXLEN + IP_VS_PEDATA_MAXLEN + 3];
|
char pe_data[IP_VS_PENAME_MAXLEN + IP_VS_PEDATA_MAXLEN + 3];
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
|
char dbuf[IP_VS_ADDRSTRLEN];
|
||||||
|
|
||||||
if (!ip_vs_conn_net_eq(cp, net))
|
if (!ip_vs_conn_net_eq(cp, net))
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1049,25 +1073,33 @@ static int ip_vs_conn_seq_show(struct seq_file *seq, void *v)
|
||||||
}
|
}
|
||||||
pe_data[len] = '\0';
|
pe_data[len] = '\0';
|
||||||
|
|
||||||
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
|
if (cp->daf == AF_INET6)
|
||||||
|
snprintf(dbuf, sizeof(dbuf), "%pI6", &cp->daddr.in6);
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
snprintf(dbuf, sizeof(dbuf), "%08X",
|
||||||
|
ntohl(cp->daddr.ip));
|
||||||
|
|
||||||
#ifdef CONFIG_IP_VS_IPV6
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
if (cp->af == AF_INET6)
|
if (cp->af == AF_INET6)
|
||||||
seq_printf(seq, "%-3s %pI6 %04X %pI6 %04X "
|
seq_printf(seq, "%-3s %pI6 %04X %pI6 %04X "
|
||||||
"%pI6 %04X %-11s %7lu%s\n",
|
"%s %04X %-11s %7lu%s\n",
|
||||||
ip_vs_proto_name(cp->protocol),
|
ip_vs_proto_name(cp->protocol),
|
||||||
&cp->caddr.in6, ntohs(cp->cport),
|
&cp->caddr.in6, ntohs(cp->cport),
|
||||||
&cp->vaddr.in6, ntohs(cp->vport),
|
&cp->vaddr.in6, ntohs(cp->vport),
|
||||||
&cp->daddr.in6, ntohs(cp->dport),
|
dbuf, ntohs(cp->dport),
|
||||||
ip_vs_state_name(cp->protocol, cp->state),
|
ip_vs_state_name(cp->protocol, cp->state),
|
||||||
(cp->timer.expires-jiffies)/HZ, pe_data);
|
(cp->timer.expires-jiffies)/HZ, pe_data);
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
seq_printf(seq,
|
seq_printf(seq,
|
||||||
"%-3s %08X %04X %08X %04X"
|
"%-3s %08X %04X %08X %04X"
|
||||||
" %08X %04X %-11s %7lu%s\n",
|
" %s %04X %-11s %7lu%s\n",
|
||||||
ip_vs_proto_name(cp->protocol),
|
ip_vs_proto_name(cp->protocol),
|
||||||
ntohl(cp->caddr.ip), ntohs(cp->cport),
|
ntohl(cp->caddr.ip), ntohs(cp->cport),
|
||||||
ntohl(cp->vaddr.ip), ntohs(cp->vport),
|
ntohl(cp->vaddr.ip), ntohs(cp->vport),
|
||||||
ntohl(cp->daddr.ip), ntohs(cp->dport),
|
dbuf, ntohs(cp->dport),
|
||||||
ip_vs_state_name(cp->protocol, cp->state),
|
ip_vs_state_name(cp->protocol, cp->state),
|
||||||
(cp->timer.expires-jiffies)/HZ, pe_data);
|
(cp->timer.expires-jiffies)/HZ, pe_data);
|
||||||
}
|
}
|
||||||
|
@ -1105,6 +1137,7 @@ static const char *ip_vs_origin_name(unsigned int flags)
|
||||||
|
|
||||||
static int ip_vs_conn_sync_seq_show(struct seq_file *seq, void *v)
|
static int ip_vs_conn_sync_seq_show(struct seq_file *seq, void *v)
|
||||||
{
|
{
|
||||||
|
char dbuf[IP_VS_ADDRSTRLEN];
|
||||||
|
|
||||||
if (v == SEQ_START_TOKEN)
|
if (v == SEQ_START_TOKEN)
|
||||||
seq_puts(seq,
|
seq_puts(seq,
|
||||||
|
@ -1116,13 +1149,22 @@ static int ip_vs_conn_sync_seq_show(struct seq_file *seq, void *v)
|
||||||
if (!ip_vs_conn_net_eq(cp, net))
|
if (!ip_vs_conn_net_eq(cp, net))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
|
if (cp->daf == AF_INET6)
|
||||||
|
snprintf(dbuf, sizeof(dbuf), "%pI6", &cp->daddr.in6);
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
snprintf(dbuf, sizeof(dbuf), "%08X",
|
||||||
|
ntohl(cp->daddr.ip));
|
||||||
|
|
||||||
#ifdef CONFIG_IP_VS_IPV6
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
if (cp->af == AF_INET6)
|
if (cp->af == AF_INET6)
|
||||||
seq_printf(seq, "%-3s %pI6 %04X %pI6 %04X %pI6 %04X %-11s %-6s %7lu\n",
|
seq_printf(seq, "%-3s %pI6 %04X %pI6 %04X "
|
||||||
|
"%s %04X %-11s %-6s %7lu\n",
|
||||||
ip_vs_proto_name(cp->protocol),
|
ip_vs_proto_name(cp->protocol),
|
||||||
&cp->caddr.in6, ntohs(cp->cport),
|
&cp->caddr.in6, ntohs(cp->cport),
|
||||||
&cp->vaddr.in6, ntohs(cp->vport),
|
&cp->vaddr.in6, ntohs(cp->vport),
|
||||||
&cp->daddr.in6, ntohs(cp->dport),
|
dbuf, ntohs(cp->dport),
|
||||||
ip_vs_state_name(cp->protocol, cp->state),
|
ip_vs_state_name(cp->protocol, cp->state),
|
||||||
ip_vs_origin_name(cp->flags),
|
ip_vs_origin_name(cp->flags),
|
||||||
(cp->timer.expires-jiffies)/HZ);
|
(cp->timer.expires-jiffies)/HZ);
|
||||||
|
@ -1130,11 +1172,11 @@ static int ip_vs_conn_sync_seq_show(struct seq_file *seq, void *v)
|
||||||
#endif
|
#endif
|
||||||
seq_printf(seq,
|
seq_printf(seq,
|
||||||
"%-3s %08X %04X %08X %04X "
|
"%-3s %08X %04X %08X %04X "
|
||||||
"%08X %04X %-11s %-6s %7lu\n",
|
"%s %04X %-11s %-6s %7lu\n",
|
||||||
ip_vs_proto_name(cp->protocol),
|
ip_vs_proto_name(cp->protocol),
|
||||||
ntohl(cp->caddr.ip), ntohs(cp->cport),
|
ntohl(cp->caddr.ip), ntohs(cp->cport),
|
||||||
ntohl(cp->vaddr.ip), ntohs(cp->vport),
|
ntohl(cp->vaddr.ip), ntohs(cp->vport),
|
||||||
ntohl(cp->daddr.ip), ntohs(cp->dport),
|
dbuf, ntohs(cp->dport),
|
||||||
ip_vs_state_name(cp->protocol, cp->state),
|
ip_vs_state_name(cp->protocol, cp->state),
|
||||||
ip_vs_origin_name(cp->flags),
|
ip_vs_origin_name(cp->flags),
|
||||||
(cp->timer.expires-jiffies)/HZ);
|
(cp->timer.expires-jiffies)/HZ);
|
||||||
|
|
|
@ -328,7 +328,7 @@ ip_vs_sched_persist(struct ip_vs_service *svc,
|
||||||
* This adds param.pe_data to the template,
|
* This adds param.pe_data to the template,
|
||||||
* and thus param.pe_data will be destroyed
|
* and thus param.pe_data will be destroyed
|
||||||
* when the template expires */
|
* when the template expires */
|
||||||
ct = ip_vs_conn_new(¶m, &dest->addr, dport,
|
ct = ip_vs_conn_new(¶m, dest->af, &dest->addr, dport,
|
||||||
IP_VS_CONN_F_TEMPLATE, dest, skb->mark);
|
IP_VS_CONN_F_TEMPLATE, dest, skb->mark);
|
||||||
if (ct == NULL) {
|
if (ct == NULL) {
|
||||||
kfree(param.pe_data);
|
kfree(param.pe_data);
|
||||||
|
@ -357,7 +357,8 @@ ip_vs_sched_persist(struct ip_vs_service *svc,
|
||||||
ip_vs_conn_fill_param(svc->net, svc->af, iph->protocol, &iph->saddr,
|
ip_vs_conn_fill_param(svc->net, svc->af, iph->protocol, &iph->saddr,
|
||||||
src_port, &iph->daddr, dst_port, ¶m);
|
src_port, &iph->daddr, dst_port, ¶m);
|
||||||
|
|
||||||
cp = ip_vs_conn_new(¶m, &dest->addr, dport, flags, dest, skb->mark);
|
cp = ip_vs_conn_new(¶m, dest->af, &dest->addr, dport, flags, dest,
|
||||||
|
skb->mark);
|
||||||
if (cp == NULL) {
|
if (cp == NULL) {
|
||||||
ip_vs_conn_put(ct);
|
ip_vs_conn_put(ct);
|
||||||
*ignored = -1;
|
*ignored = -1;
|
||||||
|
@ -479,7 +480,7 @@ ip_vs_schedule(struct ip_vs_service *svc, struct sk_buff *skb,
|
||||||
ip_vs_conn_fill_param(svc->net, svc->af, iph->protocol,
|
ip_vs_conn_fill_param(svc->net, svc->af, iph->protocol,
|
||||||
&iph->saddr, pptr[0], &iph->daddr,
|
&iph->saddr, pptr[0], &iph->daddr,
|
||||||
pptr[1], &p);
|
pptr[1], &p);
|
||||||
cp = ip_vs_conn_new(&p, &dest->addr,
|
cp = ip_vs_conn_new(&p, dest->af, &dest->addr,
|
||||||
dest->port ? dest->port : pptr[1],
|
dest->port ? dest->port : pptr[1],
|
||||||
flags, dest, skb->mark);
|
flags, dest, skb->mark);
|
||||||
if (!cp) {
|
if (!cp) {
|
||||||
|
@ -491,9 +492,9 @@ ip_vs_schedule(struct ip_vs_service *svc, struct sk_buff *skb,
|
||||||
IP_VS_DBG_BUF(6, "Schedule fwd:%c c:%s:%u v:%s:%u "
|
IP_VS_DBG_BUF(6, "Schedule fwd:%c c:%s:%u v:%s:%u "
|
||||||
"d:%s:%u conn->flags:%X conn->refcnt:%d\n",
|
"d:%s:%u conn->flags:%X conn->refcnt:%d\n",
|
||||||
ip_vs_fwd_tag(cp),
|
ip_vs_fwd_tag(cp),
|
||||||
IP_VS_DBG_ADDR(svc->af, &cp->caddr), ntohs(cp->cport),
|
IP_VS_DBG_ADDR(cp->af, &cp->caddr), ntohs(cp->cport),
|
||||||
IP_VS_DBG_ADDR(svc->af, &cp->vaddr), ntohs(cp->vport),
|
IP_VS_DBG_ADDR(cp->af, &cp->vaddr), ntohs(cp->vport),
|
||||||
IP_VS_DBG_ADDR(svc->af, &cp->daddr), ntohs(cp->dport),
|
IP_VS_DBG_ADDR(cp->daf, &cp->daddr), ntohs(cp->dport),
|
||||||
cp->flags, atomic_read(&cp->refcnt));
|
cp->flags, atomic_read(&cp->refcnt));
|
||||||
|
|
||||||
ip_vs_conn_stats(cp, svc);
|
ip_vs_conn_stats(cp, svc);
|
||||||
|
@ -550,7 +551,7 @@ int ip_vs_leave(struct ip_vs_service *svc, struct sk_buff *skb,
|
||||||
ip_vs_conn_fill_param(svc->net, svc->af, iph->protocol,
|
ip_vs_conn_fill_param(svc->net, svc->af, iph->protocol,
|
||||||
&iph->saddr, pptr[0],
|
&iph->saddr, pptr[0],
|
||||||
&iph->daddr, pptr[1], &p);
|
&iph->daddr, pptr[1], &p);
|
||||||
cp = ip_vs_conn_new(&p, &daddr, 0,
|
cp = ip_vs_conn_new(&p, svc->af, &daddr, 0,
|
||||||
IP_VS_CONN_F_BYPASS | flags,
|
IP_VS_CONN_F_BYPASS | flags,
|
||||||
NULL, skb->mark);
|
NULL, skb->mark);
|
||||||
if (!cp)
|
if (!cp)
|
||||||
|
|
|
@ -574,8 +574,8 @@ bool ip_vs_has_real_service(struct net *net, int af, __u16 protocol,
|
||||||
* Called under RCU lock.
|
* Called under RCU lock.
|
||||||
*/
|
*/
|
||||||
static struct ip_vs_dest *
|
static struct ip_vs_dest *
|
||||||
ip_vs_lookup_dest(struct ip_vs_service *svc, const union nf_inet_addr *daddr,
|
ip_vs_lookup_dest(struct ip_vs_service *svc, int dest_af,
|
||||||
__be16 dport)
|
const union nf_inet_addr *daddr, __be16 dport)
|
||||||
{
|
{
|
||||||
struct ip_vs_dest *dest;
|
struct ip_vs_dest *dest;
|
||||||
|
|
||||||
|
@ -583,9 +583,9 @@ ip_vs_lookup_dest(struct ip_vs_service *svc, const union nf_inet_addr *daddr,
|
||||||
* Find the destination for the given service
|
* Find the destination for the given service
|
||||||
*/
|
*/
|
||||||
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
|
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
|
||||||
if ((dest->af == svc->af)
|
if ((dest->af == dest_af) &&
|
||||||
&& ip_vs_addr_equal(svc->af, &dest->addr, daddr)
|
ip_vs_addr_equal(dest_af, &dest->addr, daddr) &&
|
||||||
&& (dest->port == dport)) {
|
(dest->port == dport)) {
|
||||||
/* HIT */
|
/* HIT */
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
@ -602,7 +602,7 @@ ip_vs_lookup_dest(struct ip_vs_service *svc, const union nf_inet_addr *daddr,
|
||||||
* on the backup.
|
* on the backup.
|
||||||
* Called under RCU lock, no refcnt is returned.
|
* Called under RCU lock, no refcnt is returned.
|
||||||
*/
|
*/
|
||||||
struct ip_vs_dest *ip_vs_find_dest(struct net *net, int af,
|
struct ip_vs_dest *ip_vs_find_dest(struct net *net, int svc_af, int dest_af,
|
||||||
const union nf_inet_addr *daddr,
|
const union nf_inet_addr *daddr,
|
||||||
__be16 dport,
|
__be16 dport,
|
||||||
const union nf_inet_addr *vaddr,
|
const union nf_inet_addr *vaddr,
|
||||||
|
@ -613,14 +613,14 @@ struct ip_vs_dest *ip_vs_find_dest(struct net *net, int af,
|
||||||
struct ip_vs_service *svc;
|
struct ip_vs_service *svc;
|
||||||
__be16 port = dport;
|
__be16 port = dport;
|
||||||
|
|
||||||
svc = ip_vs_service_find(net, af, fwmark, protocol, vaddr, vport);
|
svc = ip_vs_service_find(net, svc_af, fwmark, protocol, vaddr, vport);
|
||||||
if (!svc)
|
if (!svc)
|
||||||
return NULL;
|
return NULL;
|
||||||
if (fwmark && (flags & IP_VS_CONN_F_FWD_MASK) != IP_VS_CONN_F_MASQ)
|
if (fwmark && (flags & IP_VS_CONN_F_FWD_MASK) != IP_VS_CONN_F_MASQ)
|
||||||
port = 0;
|
port = 0;
|
||||||
dest = ip_vs_lookup_dest(svc, daddr, port);
|
dest = ip_vs_lookup_dest(svc, dest_af, daddr, port);
|
||||||
if (!dest)
|
if (!dest)
|
||||||
dest = ip_vs_lookup_dest(svc, daddr, port ^ dport);
|
dest = ip_vs_lookup_dest(svc, dest_af, daddr, port ^ dport);
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,8 +657,8 @@ static void __ip_vs_dst_cache_reset(struct ip_vs_dest *dest)
|
||||||
* scheduling.
|
* scheduling.
|
||||||
*/
|
*/
|
||||||
static struct ip_vs_dest *
|
static struct ip_vs_dest *
|
||||||
ip_vs_trash_get_dest(struct ip_vs_service *svc, const union nf_inet_addr *daddr,
|
ip_vs_trash_get_dest(struct ip_vs_service *svc, int dest_af,
|
||||||
__be16 dport)
|
const union nf_inet_addr *daddr, __be16 dport)
|
||||||
{
|
{
|
||||||
struct ip_vs_dest *dest;
|
struct ip_vs_dest *dest;
|
||||||
struct netns_ipvs *ipvs = net_ipvs(svc->net);
|
struct netns_ipvs *ipvs = net_ipvs(svc->net);
|
||||||
|
@ -671,11 +671,11 @@ ip_vs_trash_get_dest(struct ip_vs_service *svc, const union nf_inet_addr *daddr,
|
||||||
IP_VS_DBG_BUF(3, "Destination %u/%s:%u still in trash, "
|
IP_VS_DBG_BUF(3, "Destination %u/%s:%u still in trash, "
|
||||||
"dest->refcnt=%d\n",
|
"dest->refcnt=%d\n",
|
||||||
dest->vfwmark,
|
dest->vfwmark,
|
||||||
IP_VS_DBG_ADDR(svc->af, &dest->addr),
|
IP_VS_DBG_ADDR(dest->af, &dest->addr),
|
||||||
ntohs(dest->port),
|
ntohs(dest->port),
|
||||||
atomic_read(&dest->refcnt));
|
atomic_read(&dest->refcnt));
|
||||||
if (dest->af == svc->af &&
|
if (dest->af == dest_af &&
|
||||||
ip_vs_addr_equal(svc->af, &dest->addr, daddr) &&
|
ip_vs_addr_equal(dest_af, &dest->addr, daddr) &&
|
||||||
dest->port == dport &&
|
dest->port == dport &&
|
||||||
dest->vfwmark == svc->fwmark &&
|
dest->vfwmark == svc->fwmark &&
|
||||||
dest->protocol == svc->protocol &&
|
dest->protocol == svc->protocol &&
|
||||||
|
@ -779,6 +779,12 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest,
|
||||||
struct ip_vs_scheduler *sched;
|
struct ip_vs_scheduler *sched;
|
||||||
int conn_flags;
|
int conn_flags;
|
||||||
|
|
||||||
|
/* We cannot modify an address and change the address family */
|
||||||
|
BUG_ON(!add && udest->af != dest->af);
|
||||||
|
|
||||||
|
if (add && udest->af != svc->af)
|
||||||
|
ipvs->mixed_address_family_dests++;
|
||||||
|
|
||||||
/* set the weight and the flags */
|
/* set the weight and the flags */
|
||||||
atomic_set(&dest->weight, udest->weight);
|
atomic_set(&dest->weight, udest->weight);
|
||||||
conn_flags = udest->conn_flags & IP_VS_CONN_F_DEST_MASK;
|
conn_flags = udest->conn_flags & IP_VS_CONN_F_DEST_MASK;
|
||||||
|
@ -816,6 +822,8 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest,
|
||||||
dest->u_threshold = udest->u_threshold;
|
dest->u_threshold = udest->u_threshold;
|
||||||
dest->l_threshold = udest->l_threshold;
|
dest->l_threshold = udest->l_threshold;
|
||||||
|
|
||||||
|
dest->af = udest->af;
|
||||||
|
|
||||||
spin_lock_bh(&dest->dst_lock);
|
spin_lock_bh(&dest->dst_lock);
|
||||||
__ip_vs_dst_cache_reset(dest);
|
__ip_vs_dst_cache_reset(dest);
|
||||||
spin_unlock_bh(&dest->dst_lock);
|
spin_unlock_bh(&dest->dst_lock);
|
||||||
|
@ -847,7 +855,7 @@ ip_vs_new_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest,
|
||||||
EnterFunction(2);
|
EnterFunction(2);
|
||||||
|
|
||||||
#ifdef CONFIG_IP_VS_IPV6
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
if (svc->af == AF_INET6) {
|
if (udest->af == AF_INET6) {
|
||||||
atype = ipv6_addr_type(&udest->addr.in6);
|
atype = ipv6_addr_type(&udest->addr.in6);
|
||||||
if ((!(atype & IPV6_ADDR_UNICAST) ||
|
if ((!(atype & IPV6_ADDR_UNICAST) ||
|
||||||
atype & IPV6_ADDR_LINKLOCAL) &&
|
atype & IPV6_ADDR_LINKLOCAL) &&
|
||||||
|
@ -875,12 +883,12 @@ ip_vs_new_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest,
|
||||||
u64_stats_init(&ip_vs_dest_stats->syncp);
|
u64_stats_init(&ip_vs_dest_stats->syncp);
|
||||||
}
|
}
|
||||||
|
|
||||||
dest->af = svc->af;
|
dest->af = udest->af;
|
||||||
dest->protocol = svc->protocol;
|
dest->protocol = svc->protocol;
|
||||||
dest->vaddr = svc->addr;
|
dest->vaddr = svc->addr;
|
||||||
dest->vport = svc->port;
|
dest->vport = svc->port;
|
||||||
dest->vfwmark = svc->fwmark;
|
dest->vfwmark = svc->fwmark;
|
||||||
ip_vs_addr_copy(svc->af, &dest->addr, &udest->addr);
|
ip_vs_addr_copy(udest->af, &dest->addr, &udest->addr);
|
||||||
dest->port = udest->port;
|
dest->port = udest->port;
|
||||||
|
|
||||||
atomic_set(&dest->activeconns, 0);
|
atomic_set(&dest->activeconns, 0);
|
||||||
|
@ -928,11 +936,11 @@ ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest)
|
||||||
return -ERANGE;
|
return -ERANGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
ip_vs_addr_copy(svc->af, &daddr, &udest->addr);
|
ip_vs_addr_copy(udest->af, &daddr, &udest->addr);
|
||||||
|
|
||||||
/* We use function that requires RCU lock */
|
/* We use function that requires RCU lock */
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
dest = ip_vs_lookup_dest(svc, &daddr, dport);
|
dest = ip_vs_lookup_dest(svc, udest->af, &daddr, dport);
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
|
|
||||||
if (dest != NULL) {
|
if (dest != NULL) {
|
||||||
|
@ -944,12 +952,12 @@ ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest)
|
||||||
* Check if the dest already exists in the trash and
|
* Check if the dest already exists in the trash and
|
||||||
* is from the same service
|
* is from the same service
|
||||||
*/
|
*/
|
||||||
dest = ip_vs_trash_get_dest(svc, &daddr, dport);
|
dest = ip_vs_trash_get_dest(svc, udest->af, &daddr, dport);
|
||||||
|
|
||||||
if (dest != NULL) {
|
if (dest != NULL) {
|
||||||
IP_VS_DBG_BUF(3, "Get destination %s:%u from trash, "
|
IP_VS_DBG_BUF(3, "Get destination %s:%u from trash, "
|
||||||
"dest->refcnt=%d, service %u/%s:%u\n",
|
"dest->refcnt=%d, service %u/%s:%u\n",
|
||||||
IP_VS_DBG_ADDR(svc->af, &daddr), ntohs(dport),
|
IP_VS_DBG_ADDR(udest->af, &daddr), ntohs(dport),
|
||||||
atomic_read(&dest->refcnt),
|
atomic_read(&dest->refcnt),
|
||||||
dest->vfwmark,
|
dest->vfwmark,
|
||||||
IP_VS_DBG_ADDR(svc->af, &dest->vaddr),
|
IP_VS_DBG_ADDR(svc->af, &dest->vaddr),
|
||||||
|
@ -992,11 +1000,11 @@ ip_vs_edit_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest)
|
||||||
return -ERANGE;
|
return -ERANGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
ip_vs_addr_copy(svc->af, &daddr, &udest->addr);
|
ip_vs_addr_copy(udest->af, &daddr, &udest->addr);
|
||||||
|
|
||||||
/* We use function that requires RCU lock */
|
/* We use function that requires RCU lock */
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
dest = ip_vs_lookup_dest(svc, &daddr, dport);
|
dest = ip_vs_lookup_dest(svc, udest->af, &daddr, dport);
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
|
|
||||||
if (dest == NULL) {
|
if (dest == NULL) {
|
||||||
|
@ -1055,6 +1063,9 @@ static void __ip_vs_unlink_dest(struct ip_vs_service *svc,
|
||||||
list_del_rcu(&dest->n_list);
|
list_del_rcu(&dest->n_list);
|
||||||
svc->num_dests--;
|
svc->num_dests--;
|
||||||
|
|
||||||
|
if (dest->af != svc->af)
|
||||||
|
net_ipvs(svc->net)->mixed_address_family_dests--;
|
||||||
|
|
||||||
if (svcupd) {
|
if (svcupd) {
|
||||||
struct ip_vs_scheduler *sched;
|
struct ip_vs_scheduler *sched;
|
||||||
|
|
||||||
|
@ -1078,7 +1089,7 @@ ip_vs_del_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest)
|
||||||
|
|
||||||
/* We use function that requires RCU lock */
|
/* We use function that requires RCU lock */
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
dest = ip_vs_lookup_dest(svc, &udest->addr, dport);
|
dest = ip_vs_lookup_dest(svc, udest->af, &udest->addr, dport);
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
|
|
||||||
if (dest == NULL) {
|
if (dest == NULL) {
|
||||||
|
@ -2244,6 +2255,7 @@ static void ip_vs_copy_udest_compat(struct ip_vs_dest_user_kern *udest,
|
||||||
udest->weight = udest_compat->weight;
|
udest->weight = udest_compat->weight;
|
||||||
udest->u_threshold = udest_compat->u_threshold;
|
udest->u_threshold = udest_compat->u_threshold;
|
||||||
udest->l_threshold = udest_compat->l_threshold;
|
udest->l_threshold = udest_compat->l_threshold;
|
||||||
|
udest->af = AF_INET;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -2480,6 +2492,12 @@ __ip_vs_get_dest_entries(struct net *net, const struct ip_vs_get_dests *get,
|
||||||
if (count >= get->num_dests)
|
if (count >= get->num_dests)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/* Cannot expose heterogeneous members via sockopt
|
||||||
|
* interface
|
||||||
|
*/
|
||||||
|
if (dest->af != svc->af)
|
||||||
|
continue;
|
||||||
|
|
||||||
entry.addr = dest->addr.ip;
|
entry.addr = dest->addr.ip;
|
||||||
entry.port = dest->port;
|
entry.port = dest->port;
|
||||||
entry.conn_flags = atomic_read(&dest->conn_flags);
|
entry.conn_flags = atomic_read(&dest->conn_flags);
|
||||||
|
@ -2777,6 +2795,7 @@ static const struct nla_policy ip_vs_dest_policy[IPVS_DEST_ATTR_MAX + 1] = {
|
||||||
[IPVS_DEST_ATTR_INACT_CONNS] = { .type = NLA_U32 },
|
[IPVS_DEST_ATTR_INACT_CONNS] = { .type = NLA_U32 },
|
||||||
[IPVS_DEST_ATTR_PERSIST_CONNS] = { .type = NLA_U32 },
|
[IPVS_DEST_ATTR_PERSIST_CONNS] = { .type = NLA_U32 },
|
||||||
[IPVS_DEST_ATTR_STATS] = { .type = NLA_NESTED },
|
[IPVS_DEST_ATTR_STATS] = { .type = NLA_NESTED },
|
||||||
|
[IPVS_DEST_ATTR_ADDR_FAMILY] = { .type = NLA_U16 },
|
||||||
};
|
};
|
||||||
|
|
||||||
static int ip_vs_genl_fill_stats(struct sk_buff *skb, int container_type,
|
static int ip_vs_genl_fill_stats(struct sk_buff *skb, int container_type,
|
||||||
|
@ -3032,7 +3051,8 @@ static int ip_vs_genl_fill_dest(struct sk_buff *skb, struct ip_vs_dest *dest)
|
||||||
nla_put_u32(skb, IPVS_DEST_ATTR_INACT_CONNS,
|
nla_put_u32(skb, IPVS_DEST_ATTR_INACT_CONNS,
|
||||||
atomic_read(&dest->inactconns)) ||
|
atomic_read(&dest->inactconns)) ||
|
||||||
nla_put_u32(skb, IPVS_DEST_ATTR_PERSIST_CONNS,
|
nla_put_u32(skb, IPVS_DEST_ATTR_PERSIST_CONNS,
|
||||||
atomic_read(&dest->persistconns)))
|
atomic_read(&dest->persistconns)) ||
|
||||||
|
nla_put_u16(skb, IPVS_DEST_ATTR_ADDR_FAMILY, dest->af))
|
||||||
goto nla_put_failure;
|
goto nla_put_failure;
|
||||||
if (ip_vs_genl_fill_stats(skb, IPVS_DEST_ATTR_STATS, &dest->stats))
|
if (ip_vs_genl_fill_stats(skb, IPVS_DEST_ATTR_STATS, &dest->stats))
|
||||||
goto nla_put_failure;
|
goto nla_put_failure;
|
||||||
|
@ -3113,6 +3133,7 @@ static int ip_vs_genl_parse_dest(struct ip_vs_dest_user_kern *udest,
|
||||||
{
|
{
|
||||||
struct nlattr *attrs[IPVS_DEST_ATTR_MAX + 1];
|
struct nlattr *attrs[IPVS_DEST_ATTR_MAX + 1];
|
||||||
struct nlattr *nla_addr, *nla_port;
|
struct nlattr *nla_addr, *nla_port;
|
||||||
|
struct nlattr *nla_addr_family;
|
||||||
|
|
||||||
/* Parse mandatory identifying destination fields first */
|
/* Parse mandatory identifying destination fields first */
|
||||||
if (nla == NULL ||
|
if (nla == NULL ||
|
||||||
|
@ -3121,6 +3142,7 @@ static int ip_vs_genl_parse_dest(struct ip_vs_dest_user_kern *udest,
|
||||||
|
|
||||||
nla_addr = attrs[IPVS_DEST_ATTR_ADDR];
|
nla_addr = attrs[IPVS_DEST_ATTR_ADDR];
|
||||||
nla_port = attrs[IPVS_DEST_ATTR_PORT];
|
nla_port = attrs[IPVS_DEST_ATTR_PORT];
|
||||||
|
nla_addr_family = attrs[IPVS_DEST_ATTR_ADDR_FAMILY];
|
||||||
|
|
||||||
if (!(nla_addr && nla_port))
|
if (!(nla_addr && nla_port))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
@ -3130,6 +3152,11 @@ static int ip_vs_genl_parse_dest(struct ip_vs_dest_user_kern *udest,
|
||||||
nla_memcpy(&udest->addr, nla_addr, sizeof(udest->addr));
|
nla_memcpy(&udest->addr, nla_addr, sizeof(udest->addr));
|
||||||
udest->port = nla_get_be16(nla_port);
|
udest->port = nla_get_be16(nla_port);
|
||||||
|
|
||||||
|
if (nla_addr_family)
|
||||||
|
udest->af = nla_get_u16(nla_addr_family);
|
||||||
|
else
|
||||||
|
udest->af = 0;
|
||||||
|
|
||||||
/* If a full entry was requested, check for the additional fields */
|
/* If a full entry was requested, check for the additional fields */
|
||||||
if (full_entry) {
|
if (full_entry) {
|
||||||
struct nlattr *nla_fwd, *nla_weight, *nla_u_thresh,
|
struct nlattr *nla_fwd, *nla_weight, *nla_u_thresh,
|
||||||
|
@ -3234,6 +3261,12 @@ static int ip_vs_genl_new_daemon(struct net *net, struct nlattr **attrs)
|
||||||
attrs[IPVS_DAEMON_ATTR_SYNC_ID]))
|
attrs[IPVS_DAEMON_ATTR_SYNC_ID]))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* The synchronization protocol is incompatible with mixed family
|
||||||
|
* services
|
||||||
|
*/
|
||||||
|
if (net_ipvs(net)->mixed_address_family_dests > 0)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
return start_sync_thread(net,
|
return start_sync_thread(net,
|
||||||
nla_get_u32(attrs[IPVS_DAEMON_ATTR_STATE]),
|
nla_get_u32(attrs[IPVS_DAEMON_ATTR_STATE]),
|
||||||
nla_data(attrs[IPVS_DAEMON_ATTR_MCAST_IFN]),
|
nla_data(attrs[IPVS_DAEMON_ATTR_MCAST_IFN]),
|
||||||
|
@ -3357,6 +3390,35 @@ static int ip_vs_genl_set_cmd(struct sk_buff *skb, struct genl_info *info)
|
||||||
need_full_dest);
|
need_full_dest);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
|
/* Old protocols did not allow the user to specify address
|
||||||
|
* family, so we set it to zero instead. We also didn't
|
||||||
|
* allow heterogeneous pools in the old code, so it's safe
|
||||||
|
* to assume that this will have the same address family as
|
||||||
|
* the service.
|
||||||
|
*/
|
||||||
|
if (udest.af == 0)
|
||||||
|
udest.af = svc->af;
|
||||||
|
|
||||||
|
if (udest.af != svc->af) {
|
||||||
|
/* The synchronization protocol is incompatible
|
||||||
|
* with mixed family services
|
||||||
|
*/
|
||||||
|
if (net_ipvs(net)->sync_state) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Which connection types do we support? */
|
||||||
|
switch (udest.conn_flags) {
|
||||||
|
case IP_VS_CONN_F_TUNNEL:
|
||||||
|
/* We are able to forward this */
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
|
|
|
@ -234,7 +234,7 @@ ip_vs_dh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
|
||||||
|
|
||||||
IP_VS_DBG_BUF(6, "DH: destination IP address %s --> server %s:%d\n",
|
IP_VS_DBG_BUF(6, "DH: destination IP address %s --> server %s:%d\n",
|
||||||
IP_VS_DBG_ADDR(svc->af, &iph->daddr),
|
IP_VS_DBG_ADDR(svc->af, &iph->daddr),
|
||||||
IP_VS_DBG_ADDR(svc->af, &dest->addr),
|
IP_VS_DBG_ADDR(dest->af, &dest->addr),
|
||||||
ntohs(dest->port));
|
ntohs(dest->port));
|
||||||
|
|
||||||
return dest;
|
return dest;
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* IPVS: Weighted Fail Over module
|
||||||
|
*
|
||||||
|
* Authors: Kenny Mathis <kmathis@chokepoint.net>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version
|
||||||
|
* 2 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Changes:
|
||||||
|
* Kenny Mathis : added initial functionality based on weight
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define KMSG_COMPONENT "IPVS"
|
||||||
|
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
|
||||||
|
#include <net/ip_vs.h>
|
||||||
|
|
||||||
|
/* Weighted Fail Over Module */
|
||||||
|
static struct ip_vs_dest *
|
||||||
|
ip_vs_fo_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
|
||||||
|
struct ip_vs_iphdr *iph)
|
||||||
|
{
|
||||||
|
struct ip_vs_dest *dest, *hweight = NULL;
|
||||||
|
int hw = 0; /* Track highest weight */
|
||||||
|
|
||||||
|
IP_VS_DBG(6, "ip_vs_fo_schedule(): Scheduling...\n");
|
||||||
|
|
||||||
|
/* Basic failover functionality
|
||||||
|
* Find virtual server with highest weight and send it traffic
|
||||||
|
*/
|
||||||
|
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
|
||||||
|
if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&
|
||||||
|
atomic_read(&dest->weight) > hw) {
|
||||||
|
hweight = dest;
|
||||||
|
hw = atomic_read(&dest->weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hweight) {
|
||||||
|
IP_VS_DBG_BUF(6, "FO: server %s:%u activeconns %d weight %d\n",
|
||||||
|
IP_VS_DBG_ADDR(hweight->af, &hweight->addr),
|
||||||
|
ntohs(hweight->port),
|
||||||
|
atomic_read(&hweight->activeconns),
|
||||||
|
atomic_read(&hweight->weight));
|
||||||
|
return hweight;
|
||||||
|
}
|
||||||
|
|
||||||
|
ip_vs_scheduler_err(svc, "no destination available");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct ip_vs_scheduler ip_vs_fo_scheduler = {
|
||||||
|
.name = "fo",
|
||||||
|
.refcnt = ATOMIC_INIT(0),
|
||||||
|
.module = THIS_MODULE,
|
||||||
|
.n_list = LIST_HEAD_INIT(ip_vs_fo_scheduler.n_list),
|
||||||
|
.schedule = ip_vs_fo_schedule,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int __init ip_vs_fo_init(void)
|
||||||
|
{
|
||||||
|
return register_ip_vs_scheduler(&ip_vs_fo_scheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __exit ip_vs_fo_cleanup(void)
|
||||||
|
{
|
||||||
|
unregister_ip_vs_scheduler(&ip_vs_fo_scheduler);
|
||||||
|
synchronize_rcu();
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(ip_vs_fo_init);
|
||||||
|
module_exit(ip_vs_fo_cleanup);
|
||||||
|
MODULE_LICENSE("GPL");
|
|
@ -233,7 +233,8 @@ static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp,
|
||||||
ip_vs_conn_fill_param(ip_vs_conn_net(cp),
|
ip_vs_conn_fill_param(ip_vs_conn_net(cp),
|
||||||
AF_INET, IPPROTO_TCP, &cp->caddr,
|
AF_INET, IPPROTO_TCP, &cp->caddr,
|
||||||
0, &cp->vaddr, port, &p);
|
0, &cp->vaddr, port, &p);
|
||||||
n_cp = ip_vs_conn_new(&p, &from, port,
|
/* As above, this is ipv4 only */
|
||||||
|
n_cp = ip_vs_conn_new(&p, AF_INET, &from, port,
|
||||||
IP_VS_CONN_F_NO_CPORT |
|
IP_VS_CONN_F_NO_CPORT |
|
||||||
IP_VS_CONN_F_NFCT,
|
IP_VS_CONN_F_NFCT,
|
||||||
cp->dest, skb->mark);
|
cp->dest, skb->mark);
|
||||||
|
@ -396,7 +397,8 @@ static int ip_vs_ftp_in(struct ip_vs_app *app, struct ip_vs_conn *cp,
|
||||||
htons(ntohs(cp->vport)-1), &p);
|
htons(ntohs(cp->vport)-1), &p);
|
||||||
n_cp = ip_vs_conn_in_get(&p);
|
n_cp = ip_vs_conn_in_get(&p);
|
||||||
if (!n_cp) {
|
if (!n_cp) {
|
||||||
n_cp = ip_vs_conn_new(&p, &cp->daddr,
|
/* This is ipv4 only */
|
||||||
|
n_cp = ip_vs_conn_new(&p, AF_INET, &cp->daddr,
|
||||||
htons(ntohs(cp->dport)-1),
|
htons(ntohs(cp->dport)-1),
|
||||||
IP_VS_CONN_F_NFCT, cp->dest,
|
IP_VS_CONN_F_NFCT, cp->dest,
|
||||||
skb->mark);
|
skb->mark);
|
||||||
|
|
|
@ -199,11 +199,11 @@ ip_vs_lblc_get(int af, struct ip_vs_lblc_table *tbl,
|
||||||
*/
|
*/
|
||||||
static inline struct ip_vs_lblc_entry *
|
static inline struct ip_vs_lblc_entry *
|
||||||
ip_vs_lblc_new(struct ip_vs_lblc_table *tbl, const union nf_inet_addr *daddr,
|
ip_vs_lblc_new(struct ip_vs_lblc_table *tbl, const union nf_inet_addr *daddr,
|
||||||
struct ip_vs_dest *dest)
|
u16 af, struct ip_vs_dest *dest)
|
||||||
{
|
{
|
||||||
struct ip_vs_lblc_entry *en;
|
struct ip_vs_lblc_entry *en;
|
||||||
|
|
||||||
en = ip_vs_lblc_get(dest->af, tbl, daddr);
|
en = ip_vs_lblc_get(af, tbl, daddr);
|
||||||
if (en) {
|
if (en) {
|
||||||
if (en->dest == dest)
|
if (en->dest == dest)
|
||||||
return en;
|
return en;
|
||||||
|
@ -213,8 +213,8 @@ ip_vs_lblc_new(struct ip_vs_lblc_table *tbl, const union nf_inet_addr *daddr,
|
||||||
if (!en)
|
if (!en)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
en->af = dest->af;
|
en->af = af;
|
||||||
ip_vs_addr_copy(dest->af, &en->addr, daddr);
|
ip_vs_addr_copy(af, &en->addr, daddr);
|
||||||
en->lastuse = jiffies;
|
en->lastuse = jiffies;
|
||||||
|
|
||||||
ip_vs_dest_hold(dest);
|
ip_vs_dest_hold(dest);
|
||||||
|
@ -521,13 +521,13 @@ ip_vs_lblc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
|
||||||
/* If we fail to create a cache entry, we'll just use the valid dest */
|
/* If we fail to create a cache entry, we'll just use the valid dest */
|
||||||
spin_lock_bh(&svc->sched_lock);
|
spin_lock_bh(&svc->sched_lock);
|
||||||
if (!tbl->dead)
|
if (!tbl->dead)
|
||||||
ip_vs_lblc_new(tbl, &iph->daddr, dest);
|
ip_vs_lblc_new(tbl, &iph->daddr, svc->af, dest);
|
||||||
spin_unlock_bh(&svc->sched_lock);
|
spin_unlock_bh(&svc->sched_lock);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
IP_VS_DBG_BUF(6, "LBLC: destination IP address %s --> server %s:%d\n",
|
IP_VS_DBG_BUF(6, "LBLC: destination IP address %s --> server %s:%d\n",
|
||||||
IP_VS_DBG_ADDR(svc->af, &iph->daddr),
|
IP_VS_DBG_ADDR(svc->af, &iph->daddr),
|
||||||
IP_VS_DBG_ADDR(svc->af, &dest->addr), ntohs(dest->port));
|
IP_VS_DBG_ADDR(dest->af, &dest->addr), ntohs(dest->port));
|
||||||
|
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
|
@ -362,18 +362,18 @@ ip_vs_lblcr_get(int af, struct ip_vs_lblcr_table *tbl,
|
||||||
*/
|
*/
|
||||||
static inline struct ip_vs_lblcr_entry *
|
static inline struct ip_vs_lblcr_entry *
|
||||||
ip_vs_lblcr_new(struct ip_vs_lblcr_table *tbl, const union nf_inet_addr *daddr,
|
ip_vs_lblcr_new(struct ip_vs_lblcr_table *tbl, const union nf_inet_addr *daddr,
|
||||||
struct ip_vs_dest *dest)
|
u16 af, struct ip_vs_dest *dest)
|
||||||
{
|
{
|
||||||
struct ip_vs_lblcr_entry *en;
|
struct ip_vs_lblcr_entry *en;
|
||||||
|
|
||||||
en = ip_vs_lblcr_get(dest->af, tbl, daddr);
|
en = ip_vs_lblcr_get(af, tbl, daddr);
|
||||||
if (!en) {
|
if (!en) {
|
||||||
en = kmalloc(sizeof(*en), GFP_ATOMIC);
|
en = kmalloc(sizeof(*en), GFP_ATOMIC);
|
||||||
if (!en)
|
if (!en)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
en->af = dest->af;
|
en->af = af;
|
||||||
ip_vs_addr_copy(dest->af, &en->addr, daddr);
|
ip_vs_addr_copy(af, &en->addr, daddr);
|
||||||
en->lastuse = jiffies;
|
en->lastuse = jiffies;
|
||||||
|
|
||||||
/* initialize its dest set */
|
/* initialize its dest set */
|
||||||
|
@ -706,13 +706,13 @@ ip_vs_lblcr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
|
||||||
/* If we fail to create a cache entry, we'll just use the valid dest */
|
/* If we fail to create a cache entry, we'll just use the valid dest */
|
||||||
spin_lock_bh(&svc->sched_lock);
|
spin_lock_bh(&svc->sched_lock);
|
||||||
if (!tbl->dead)
|
if (!tbl->dead)
|
||||||
ip_vs_lblcr_new(tbl, &iph->daddr, dest);
|
ip_vs_lblcr_new(tbl, &iph->daddr, svc->af, dest);
|
||||||
spin_unlock_bh(&svc->sched_lock);
|
spin_unlock_bh(&svc->sched_lock);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
IP_VS_DBG_BUF(6, "LBLCR: destination IP address %s --> server %s:%d\n",
|
IP_VS_DBG_BUF(6, "LBLCR: destination IP address %s --> server %s:%d\n",
|
||||||
IP_VS_DBG_ADDR(svc->af, &iph->daddr),
|
IP_VS_DBG_ADDR(svc->af, &iph->daddr),
|
||||||
IP_VS_DBG_ADDR(svc->af, &dest->addr), ntohs(dest->port));
|
IP_VS_DBG_ADDR(dest->af, &dest->addr), ntohs(dest->port));
|
||||||
|
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ ip_vs_lc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
|
||||||
else
|
else
|
||||||
IP_VS_DBG_BUF(6, "LC: server %s:%u activeconns %d "
|
IP_VS_DBG_BUF(6, "LC: server %s:%u activeconns %d "
|
||||||
"inactconns %d\n",
|
"inactconns %d\n",
|
||||||
IP_VS_DBG_ADDR(svc->af, &least->addr),
|
IP_VS_DBG_ADDR(least->af, &least->addr),
|
||||||
ntohs(least->port),
|
ntohs(least->port),
|
||||||
atomic_read(&least->activeconns),
|
atomic_read(&least->activeconns),
|
||||||
atomic_read(&least->inactconns));
|
atomic_read(&least->inactconns));
|
||||||
|
|
|
@ -107,7 +107,8 @@ ip_vs_nq_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
|
||||||
out:
|
out:
|
||||||
IP_VS_DBG_BUF(6, "NQ: server %s:%u "
|
IP_VS_DBG_BUF(6, "NQ: server %s:%u "
|
||||||
"activeconns %d refcnt %d weight %d overhead %d\n",
|
"activeconns %d refcnt %d weight %d overhead %d\n",
|
||||||
IP_VS_DBG_ADDR(svc->af, &least->addr), ntohs(least->port),
|
IP_VS_DBG_ADDR(least->af, &least->addr),
|
||||||
|
ntohs(least->port),
|
||||||
atomic_read(&least->activeconns),
|
atomic_read(&least->activeconns),
|
||||||
atomic_read(&least->refcnt),
|
atomic_read(&least->refcnt),
|
||||||
atomic_read(&least->weight), loh);
|
atomic_read(&least->weight), loh);
|
||||||
|
|
|
@ -432,7 +432,7 @@ set_sctp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp,
|
||||||
pd->pp->name,
|
pd->pp->name,
|
||||||
((direction == IP_VS_DIR_OUTPUT) ?
|
((direction == IP_VS_DIR_OUTPUT) ?
|
||||||
"output " : "input "),
|
"output " : "input "),
|
||||||
IP_VS_DBG_ADDR(cp->af, &cp->daddr),
|
IP_VS_DBG_ADDR(cp->daf, &cp->daddr),
|
||||||
ntohs(cp->dport),
|
ntohs(cp->dport),
|
||||||
IP_VS_DBG_ADDR(cp->af, &cp->caddr),
|
IP_VS_DBG_ADDR(cp->af, &cp->caddr),
|
||||||
ntohs(cp->cport),
|
ntohs(cp->cport),
|
||||||
|
|
|
@ -510,7 +510,7 @@ set_tcp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp,
|
||||||
th->fin ? 'F' : '.',
|
th->fin ? 'F' : '.',
|
||||||
th->ack ? 'A' : '.',
|
th->ack ? 'A' : '.',
|
||||||
th->rst ? 'R' : '.',
|
th->rst ? 'R' : '.',
|
||||||
IP_VS_DBG_ADDR(cp->af, &cp->daddr),
|
IP_VS_DBG_ADDR(cp->daf, &cp->daddr),
|
||||||
ntohs(cp->dport),
|
ntohs(cp->dport),
|
||||||
IP_VS_DBG_ADDR(cp->af, &cp->caddr),
|
IP_VS_DBG_ADDR(cp->af, &cp->caddr),
|
||||||
ntohs(cp->cport),
|
ntohs(cp->cport),
|
||||||
|
|
|
@ -95,7 +95,7 @@ stop:
|
||||||
spin_unlock_bh(&svc->sched_lock);
|
spin_unlock_bh(&svc->sched_lock);
|
||||||
IP_VS_DBG_BUF(6, "RR: server %s:%u "
|
IP_VS_DBG_BUF(6, "RR: server %s:%u "
|
||||||
"activeconns %d refcnt %d weight %d\n",
|
"activeconns %d refcnt %d weight %d\n",
|
||||||
IP_VS_DBG_ADDR(svc->af, &dest->addr), ntohs(dest->port),
|
IP_VS_DBG_ADDR(dest->af, &dest->addr), ntohs(dest->port),
|
||||||
atomic_read(&dest->activeconns),
|
atomic_read(&dest->activeconns),
|
||||||
atomic_read(&dest->refcnt), atomic_read(&dest->weight));
|
atomic_read(&dest->refcnt), atomic_read(&dest->weight));
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,8 @@ ip_vs_sed_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
|
||||||
|
|
||||||
IP_VS_DBG_BUF(6, "SED: server %s:%u "
|
IP_VS_DBG_BUF(6, "SED: server %s:%u "
|
||||||
"activeconns %d refcnt %d weight %d overhead %d\n",
|
"activeconns %d refcnt %d weight %d overhead %d\n",
|
||||||
IP_VS_DBG_ADDR(svc->af, &least->addr), ntohs(least->port),
|
IP_VS_DBG_ADDR(least->af, &least->addr),
|
||||||
|
ntohs(least->port),
|
||||||
atomic_read(&least->activeconns),
|
atomic_read(&least->activeconns),
|
||||||
atomic_read(&least->refcnt),
|
atomic_read(&least->refcnt),
|
||||||
atomic_read(&least->weight), loh);
|
atomic_read(&least->weight), loh);
|
||||||
|
|
|
@ -138,7 +138,7 @@ ip_vs_sh_get_fallback(struct ip_vs_service *svc, struct ip_vs_sh_state *s,
|
||||||
return dest;
|
return dest;
|
||||||
|
|
||||||
IP_VS_DBG_BUF(6, "SH: selected unavailable server %s:%d, reselecting",
|
IP_VS_DBG_BUF(6, "SH: selected unavailable server %s:%d, reselecting",
|
||||||
IP_VS_DBG_ADDR(svc->af, &dest->addr), ntohs(dest->port));
|
IP_VS_DBG_ADDR(dest->af, &dest->addr), ntohs(dest->port));
|
||||||
|
|
||||||
/* if the original dest is unavailable, loop around the table
|
/* if the original dest is unavailable, loop around the table
|
||||||
* starting from ihash to find a new dest
|
* starting from ihash to find a new dest
|
||||||
|
@ -153,7 +153,7 @@ ip_vs_sh_get_fallback(struct ip_vs_service *svc, struct ip_vs_sh_state *s,
|
||||||
return dest;
|
return dest;
|
||||||
IP_VS_DBG_BUF(6, "SH: selected unavailable "
|
IP_VS_DBG_BUF(6, "SH: selected unavailable "
|
||||||
"server %s:%d (offset %d), reselecting",
|
"server %s:%d (offset %d), reselecting",
|
||||||
IP_VS_DBG_ADDR(svc->af, &dest->addr),
|
IP_VS_DBG_ADDR(dest->af, &dest->addr),
|
||||||
ntohs(dest->port), roffset);
|
ntohs(dest->port), roffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ ip_vs_sh_reassign(struct ip_vs_sh_state *s, struct ip_vs_service *svc)
|
||||||
RCU_INIT_POINTER(b->dest, dest);
|
RCU_INIT_POINTER(b->dest, dest);
|
||||||
|
|
||||||
IP_VS_DBG_BUF(6, "assigned i: %d dest: %s weight: %d\n",
|
IP_VS_DBG_BUF(6, "assigned i: %d dest: %s weight: %d\n",
|
||||||
i, IP_VS_DBG_ADDR(svc->af, &dest->addr),
|
i, IP_VS_DBG_ADDR(dest->af, &dest->addr),
|
||||||
atomic_read(&dest->weight));
|
atomic_read(&dest->weight));
|
||||||
|
|
||||||
/* Don't move to next dest until filling weight */
|
/* Don't move to next dest until filling weight */
|
||||||
|
@ -342,7 +342,7 @@ ip_vs_sh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
|
||||||
|
|
||||||
IP_VS_DBG_BUF(6, "SH: source IP address %s --> server %s:%d\n",
|
IP_VS_DBG_BUF(6, "SH: source IP address %s --> server %s:%d\n",
|
||||||
IP_VS_DBG_ADDR(svc->af, &iph->saddr),
|
IP_VS_DBG_ADDR(svc->af, &iph->saddr),
|
||||||
IP_VS_DBG_ADDR(svc->af, &dest->addr),
|
IP_VS_DBG_ADDR(dest->af, &dest->addr),
|
||||||
ntohs(dest->port));
|
ntohs(dest->port));
|
||||||
|
|
||||||
return dest;
|
return dest;
|
||||||
|
|
|
@ -880,10 +880,17 @@ static void ip_vs_proc_conn(struct net *net, struct ip_vs_conn_param *param,
|
||||||
* but still handled.
|
* but still handled.
|
||||||
*/
|
*/
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
dest = ip_vs_find_dest(net, type, daddr, dport, param->vaddr,
|
/* This function is only invoked by the synchronization
|
||||||
param->vport, protocol, fwmark, flags);
|
* code. We do not currently support heterogeneous pools
|
||||||
|
* with synchronization, so we can make the assumption that
|
||||||
|
* the svc_af is the same as the dest_af
|
||||||
|
*/
|
||||||
|
dest = ip_vs_find_dest(net, type, type, daddr, dport,
|
||||||
|
param->vaddr, param->vport, protocol,
|
||||||
|
fwmark, flags);
|
||||||
|
|
||||||
cp = ip_vs_conn_new(param, daddr, dport, flags, dest, fwmark);
|
cp = ip_vs_conn_new(param, type, daddr, dport, flags, dest,
|
||||||
|
fwmark);
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
if (!cp) {
|
if (!cp) {
|
||||||
kfree(param->pe_data);
|
kfree(param->pe_data);
|
||||||
|
|
|
@ -80,7 +80,8 @@ ip_vs_wlc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
|
||||||
|
|
||||||
IP_VS_DBG_BUF(6, "WLC: server %s:%u "
|
IP_VS_DBG_BUF(6, "WLC: server %s:%u "
|
||||||
"activeconns %d refcnt %d weight %d overhead %d\n",
|
"activeconns %d refcnt %d weight %d overhead %d\n",
|
||||||
IP_VS_DBG_ADDR(svc->af, &least->addr), ntohs(least->port),
|
IP_VS_DBG_ADDR(least->af, &least->addr),
|
||||||
|
ntohs(least->port),
|
||||||
atomic_read(&least->activeconns),
|
atomic_read(&least->activeconns),
|
||||||
atomic_read(&least->refcnt),
|
atomic_read(&least->refcnt),
|
||||||
atomic_read(&least->weight), loh);
|
atomic_read(&least->weight), loh);
|
||||||
|
|
|
@ -216,7 +216,7 @@ ip_vs_wrr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
|
||||||
found:
|
found:
|
||||||
IP_VS_DBG_BUF(6, "WRR: server %s:%u "
|
IP_VS_DBG_BUF(6, "WRR: server %s:%u "
|
||||||
"activeconns %d refcnt %d weight %d\n",
|
"activeconns %d refcnt %d weight %d\n",
|
||||||
IP_VS_DBG_ADDR(svc->af, &dest->addr), ntohs(dest->port),
|
IP_VS_DBG_ADDR(dest->af, &dest->addr), ntohs(dest->port),
|
||||||
atomic_read(&dest->activeconns),
|
atomic_read(&dest->activeconns),
|
||||||
atomic_read(&dest->refcnt),
|
atomic_read(&dest->refcnt),
|
||||||
atomic_read(&dest->weight));
|
atomic_read(&dest->weight));
|
||||||
|
|
|
@ -157,18 +157,113 @@ retry:
|
||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
|
static inline int __ip_vs_is_local_route6(struct rt6_info *rt)
|
||||||
|
{
|
||||||
|
return rt->dst.dev && rt->dst.dev->flags & IFF_LOOPBACK;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline bool crosses_local_route_boundary(int skb_af, struct sk_buff *skb,
|
||||||
|
int rt_mode,
|
||||||
|
bool new_rt_is_local)
|
||||||
|
{
|
||||||
|
bool rt_mode_allow_local = !!(rt_mode & IP_VS_RT_MODE_LOCAL);
|
||||||
|
bool rt_mode_allow_non_local = !!(rt_mode & IP_VS_RT_MODE_LOCAL);
|
||||||
|
bool rt_mode_allow_redirect = !!(rt_mode & IP_VS_RT_MODE_RDR);
|
||||||
|
bool source_is_loopback;
|
||||||
|
bool old_rt_is_local;
|
||||||
|
|
||||||
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
|
if (skb_af == AF_INET6) {
|
||||||
|
int addr_type = ipv6_addr_type(&ipv6_hdr(skb)->saddr);
|
||||||
|
|
||||||
|
source_is_loopback =
|
||||||
|
(!skb->dev || skb->dev->flags & IFF_LOOPBACK) &&
|
||||||
|
(addr_type & IPV6_ADDR_LOOPBACK);
|
||||||
|
old_rt_is_local = __ip_vs_is_local_route6(
|
||||||
|
(struct rt6_info *)skb_dst(skb));
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
source_is_loopback = ipv4_is_loopback(ip_hdr(skb)->saddr);
|
||||||
|
old_rt_is_local = skb_rtable(skb)->rt_flags & RTCF_LOCAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlikely(new_rt_is_local)) {
|
||||||
|
if (!rt_mode_allow_local)
|
||||||
|
return true;
|
||||||
|
if (!rt_mode_allow_redirect && !old_rt_is_local)
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (!rt_mode_allow_non_local)
|
||||||
|
return true;
|
||||||
|
if (source_is_loopback)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void maybe_update_pmtu(int skb_af, struct sk_buff *skb, int mtu)
|
||||||
|
{
|
||||||
|
struct sock *sk = skb->sk;
|
||||||
|
struct rtable *ort = skb_rtable(skb);
|
||||||
|
|
||||||
|
if (!skb->dev && sk && sk->sk_state != TCP_TIME_WAIT)
|
||||||
|
ort->dst.ops->update_pmtu(&ort->dst, sk, NULL, mtu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool ensure_mtu_is_adequate(int skb_af, int rt_mode,
|
||||||
|
struct ip_vs_iphdr *ipvsh,
|
||||||
|
struct sk_buff *skb, int mtu)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
|
if (skb_af == AF_INET6) {
|
||||||
|
struct net *net = dev_net(skb_dst(skb)->dev);
|
||||||
|
|
||||||
|
if (unlikely(__mtu_check_toobig_v6(skb, mtu))) {
|
||||||
|
if (!skb->dev)
|
||||||
|
skb->dev = net->loopback_dev;
|
||||||
|
/* only send ICMP too big on first fragment */
|
||||||
|
if (!ipvsh->fragoffs)
|
||||||
|
icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
|
||||||
|
IP_VS_DBG(1, "frag needed for %pI6c\n",
|
||||||
|
&ipv6_hdr(skb)->saddr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
struct netns_ipvs *ipvs = net_ipvs(skb_net(skb));
|
||||||
|
|
||||||
|
/* If we're going to tunnel the packet and pmtu discovery
|
||||||
|
* is disabled, we'll just fragment it anyway
|
||||||
|
*/
|
||||||
|
if ((rt_mode & IP_VS_RT_MODE_TUNNEL) && !sysctl_pmtu_disc(ipvs))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (unlikely(ip_hdr(skb)->frag_off & htons(IP_DF) &&
|
||||||
|
skb->len > mtu && !skb_is_gso(skb))) {
|
||||||
|
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
|
||||||
|
htonl(mtu));
|
||||||
|
IP_VS_DBG(1, "frag needed for %pI4\n",
|
||||||
|
&ip_hdr(skb)->saddr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* Get route to destination or remote server */
|
/* Get route to destination or remote server */
|
||||||
static int
|
static int
|
||||||
__ip_vs_get_out_rt(struct sk_buff *skb, struct ip_vs_dest *dest,
|
__ip_vs_get_out_rt(int skb_af, struct sk_buff *skb, struct ip_vs_dest *dest,
|
||||||
__be32 daddr, int rt_mode, __be32 *ret_saddr)
|
__be32 daddr, int rt_mode, __be32 *ret_saddr,
|
||||||
|
struct ip_vs_iphdr *ipvsh)
|
||||||
{
|
{
|
||||||
struct net *net = dev_net(skb_dst(skb)->dev);
|
struct net *net = dev_net(skb_dst(skb)->dev);
|
||||||
struct netns_ipvs *ipvs = net_ipvs(net);
|
|
||||||
struct ip_vs_dest_dst *dest_dst;
|
struct ip_vs_dest_dst *dest_dst;
|
||||||
struct rtable *rt; /* Route to the other host */
|
struct rtable *rt; /* Route to the other host */
|
||||||
struct rtable *ort; /* Original route */
|
|
||||||
struct iphdr *iph;
|
|
||||||
__be16 df;
|
|
||||||
int mtu;
|
int mtu;
|
||||||
int local, noref = 1;
|
int local, noref = 1;
|
||||||
|
|
||||||
|
@ -218,30 +313,14 @@ __ip_vs_get_out_rt(struct sk_buff *skb, struct ip_vs_dest *dest,
|
||||||
}
|
}
|
||||||
|
|
||||||
local = (rt->rt_flags & RTCF_LOCAL) ? 1 : 0;
|
local = (rt->rt_flags & RTCF_LOCAL) ? 1 : 0;
|
||||||
if (!((local ? IP_VS_RT_MODE_LOCAL : IP_VS_RT_MODE_NON_LOCAL) &
|
if (unlikely(crosses_local_route_boundary(skb_af, skb, rt_mode,
|
||||||
rt_mode)) {
|
local))) {
|
||||||
IP_VS_DBG_RL("Stopping traffic to %s address, dest: %pI4\n",
|
IP_VS_DBG_RL("We are crossing local and non-local addresses"
|
||||||
(rt->rt_flags & RTCF_LOCAL) ?
|
" daddr=%pI4\n", &dest->addr.ip);
|
||||||
"local":"non-local", &daddr);
|
|
||||||
goto err_put;
|
goto err_put;
|
||||||
}
|
}
|
||||||
iph = ip_hdr(skb);
|
|
||||||
if (likely(!local)) {
|
if (unlikely(local)) {
|
||||||
if (unlikely(ipv4_is_loopback(iph->saddr))) {
|
|
||||||
IP_VS_DBG_RL("Stopping traffic from loopback address "
|
|
||||||
"%pI4 to non-local address, dest: %pI4\n",
|
|
||||||
&iph->saddr, &daddr);
|
|
||||||
goto err_put;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ort = skb_rtable(skb);
|
|
||||||
if (!(rt_mode & IP_VS_RT_MODE_RDR) &&
|
|
||||||
!(ort->rt_flags & RTCF_LOCAL)) {
|
|
||||||
IP_VS_DBG_RL("Redirect from non-local address %pI4 to "
|
|
||||||
"local requires NAT method, dest: %pI4\n",
|
|
||||||
&iph->daddr, &daddr);
|
|
||||||
goto err_put;
|
|
||||||
}
|
|
||||||
/* skb to local stack, preserve old route */
|
/* skb to local stack, preserve old route */
|
||||||
if (!noref)
|
if (!noref)
|
||||||
ip_rt_put(rt);
|
ip_rt_put(rt);
|
||||||
|
@ -250,28 +329,17 @@ __ip_vs_get_out_rt(struct sk_buff *skb, struct ip_vs_dest *dest,
|
||||||
|
|
||||||
if (likely(!(rt_mode & IP_VS_RT_MODE_TUNNEL))) {
|
if (likely(!(rt_mode & IP_VS_RT_MODE_TUNNEL))) {
|
||||||
mtu = dst_mtu(&rt->dst);
|
mtu = dst_mtu(&rt->dst);
|
||||||
df = iph->frag_off & htons(IP_DF);
|
|
||||||
} else {
|
} else {
|
||||||
struct sock *sk = skb->sk;
|
|
||||||
|
|
||||||
mtu = dst_mtu(&rt->dst) - sizeof(struct iphdr);
|
mtu = dst_mtu(&rt->dst) - sizeof(struct iphdr);
|
||||||
if (mtu < 68) {
|
if (mtu < 68) {
|
||||||
IP_VS_DBG_RL("%s(): mtu less than 68\n", __func__);
|
IP_VS_DBG_RL("%s(): mtu less than 68\n", __func__);
|
||||||
goto err_put;
|
goto err_put;
|
||||||
}
|
}
|
||||||
ort = skb_rtable(skb);
|
maybe_update_pmtu(skb_af, skb, mtu);
|
||||||
if (!skb->dev && sk && sk->sk_state != TCP_TIME_WAIT)
|
|
||||||
ort->dst.ops->update_pmtu(&ort->dst, sk, NULL, mtu);
|
|
||||||
/* MTU check allowed? */
|
|
||||||
df = sysctl_pmtu_disc(ipvs) ? iph->frag_off & htons(IP_DF) : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MTU checking */
|
if (!ensure_mtu_is_adequate(skb_af, rt_mode, ipvsh, skb, mtu))
|
||||||
if (unlikely(df && skb->len > mtu && !skb_is_gso(skb))) {
|
|
||||||
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
|
|
||||||
IP_VS_DBG(1, "frag needed for %pI4\n", &iph->saddr);
|
|
||||||
goto err_put;
|
goto err_put;
|
||||||
}
|
|
||||||
|
|
||||||
skb_dst_drop(skb);
|
skb_dst_drop(skb);
|
||||||
if (noref) {
|
if (noref) {
|
||||||
|
@ -295,12 +363,6 @@ err_unreach:
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_IP_VS_IPV6
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
|
|
||||||
static inline int __ip_vs_is_local_route6(struct rt6_info *rt)
|
|
||||||
{
|
|
||||||
return rt->dst.dev && rt->dst.dev->flags & IFF_LOOPBACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct dst_entry *
|
static struct dst_entry *
|
||||||
__ip_vs_route_output_v6(struct net *net, struct in6_addr *daddr,
|
__ip_vs_route_output_v6(struct net *net, struct in6_addr *daddr,
|
||||||
struct in6_addr *ret_saddr, int do_xfrm)
|
struct in6_addr *ret_saddr, int do_xfrm)
|
||||||
|
@ -339,14 +401,13 @@ out_err:
|
||||||
* Get route to destination or remote server
|
* Get route to destination or remote server
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
__ip_vs_get_out_rt_v6(struct sk_buff *skb, struct ip_vs_dest *dest,
|
__ip_vs_get_out_rt_v6(int skb_af, struct sk_buff *skb, struct ip_vs_dest *dest,
|
||||||
struct in6_addr *daddr, struct in6_addr *ret_saddr,
|
struct in6_addr *daddr, struct in6_addr *ret_saddr,
|
||||||
struct ip_vs_iphdr *ipvsh, int do_xfrm, int rt_mode)
|
struct ip_vs_iphdr *ipvsh, int do_xfrm, int rt_mode)
|
||||||
{
|
{
|
||||||
struct net *net = dev_net(skb_dst(skb)->dev);
|
struct net *net = dev_net(skb_dst(skb)->dev);
|
||||||
struct ip_vs_dest_dst *dest_dst;
|
struct ip_vs_dest_dst *dest_dst;
|
||||||
struct rt6_info *rt; /* Route to the other host */
|
struct rt6_info *rt; /* Route to the other host */
|
||||||
struct rt6_info *ort; /* Original route */
|
|
||||||
struct dst_entry *dst;
|
struct dst_entry *dst;
|
||||||
int mtu;
|
int mtu;
|
||||||
int local, noref = 1;
|
int local, noref = 1;
|
||||||
|
@ -393,32 +454,15 @@ __ip_vs_get_out_rt_v6(struct sk_buff *skb, struct ip_vs_dest *dest,
|
||||||
}
|
}
|
||||||
|
|
||||||
local = __ip_vs_is_local_route6(rt);
|
local = __ip_vs_is_local_route6(rt);
|
||||||
if (!((local ? IP_VS_RT_MODE_LOCAL : IP_VS_RT_MODE_NON_LOCAL) &
|
|
||||||
rt_mode)) {
|
if (unlikely(crosses_local_route_boundary(skb_af, skb, rt_mode,
|
||||||
IP_VS_DBG_RL("Stopping traffic to %s address, dest: %pI6c\n",
|
local))) {
|
||||||
local ? "local":"non-local", daddr);
|
IP_VS_DBG_RL("We are crossing local and non-local addresses"
|
||||||
|
" daddr=%pI6\n", &dest->addr.in6);
|
||||||
goto err_put;
|
goto err_put;
|
||||||
}
|
}
|
||||||
if (likely(!local)) {
|
|
||||||
if (unlikely((!skb->dev || skb->dev->flags & IFF_LOOPBACK) &&
|
if (unlikely(local)) {
|
||||||
ipv6_addr_type(&ipv6_hdr(skb)->saddr) &
|
|
||||||
IPV6_ADDR_LOOPBACK)) {
|
|
||||||
IP_VS_DBG_RL("Stopping traffic from loopback address "
|
|
||||||
"%pI6c to non-local address, "
|
|
||||||
"dest: %pI6c\n",
|
|
||||||
&ipv6_hdr(skb)->saddr, daddr);
|
|
||||||
goto err_put;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ort = (struct rt6_info *) skb_dst(skb);
|
|
||||||
if (!(rt_mode & IP_VS_RT_MODE_RDR) &&
|
|
||||||
!__ip_vs_is_local_route6(ort)) {
|
|
||||||
IP_VS_DBG_RL("Redirect from non-local address %pI6c "
|
|
||||||
"to local requires NAT method, "
|
|
||||||
"dest: %pI6c\n",
|
|
||||||
&ipv6_hdr(skb)->daddr, daddr);
|
|
||||||
goto err_put;
|
|
||||||
}
|
|
||||||
/* skb to local stack, preserve old route */
|
/* skb to local stack, preserve old route */
|
||||||
if (!noref)
|
if (!noref)
|
||||||
dst_release(&rt->dst);
|
dst_release(&rt->dst);
|
||||||
|
@ -429,28 +473,17 @@ __ip_vs_get_out_rt_v6(struct sk_buff *skb, struct ip_vs_dest *dest,
|
||||||
if (likely(!(rt_mode & IP_VS_RT_MODE_TUNNEL)))
|
if (likely(!(rt_mode & IP_VS_RT_MODE_TUNNEL)))
|
||||||
mtu = dst_mtu(&rt->dst);
|
mtu = dst_mtu(&rt->dst);
|
||||||
else {
|
else {
|
||||||
struct sock *sk = skb->sk;
|
|
||||||
|
|
||||||
mtu = dst_mtu(&rt->dst) - sizeof(struct ipv6hdr);
|
mtu = dst_mtu(&rt->dst) - sizeof(struct ipv6hdr);
|
||||||
if (mtu < IPV6_MIN_MTU) {
|
if (mtu < IPV6_MIN_MTU) {
|
||||||
IP_VS_DBG_RL("%s(): mtu less than %d\n", __func__,
|
IP_VS_DBG_RL("%s(): mtu less than %d\n", __func__,
|
||||||
IPV6_MIN_MTU);
|
IPV6_MIN_MTU);
|
||||||
goto err_put;
|
goto err_put;
|
||||||
}
|
}
|
||||||
ort = (struct rt6_info *) skb_dst(skb);
|
maybe_update_pmtu(skb_af, skb, mtu);
|
||||||
if (!skb->dev && sk && sk->sk_state != TCP_TIME_WAIT)
|
|
||||||
ort->dst.ops->update_pmtu(&ort->dst, sk, NULL, mtu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlikely(__mtu_check_toobig_v6(skb, mtu))) {
|
if (!ensure_mtu_is_adequate(skb_af, rt_mode, ipvsh, skb, mtu))
|
||||||
if (!skb->dev)
|
|
||||||
skb->dev = net->loopback_dev;
|
|
||||||
/* only send ICMP too big on first fragment */
|
|
||||||
if (!ipvsh->fragoffs)
|
|
||||||
icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
|
|
||||||
IP_VS_DBG(1, "frag needed for %pI6c\n", &ipv6_hdr(skb)->saddr);
|
|
||||||
goto err_put;
|
goto err_put;
|
||||||
}
|
|
||||||
|
|
||||||
skb_dst_drop(skb);
|
skb_dst_drop(skb);
|
||||||
if (noref) {
|
if (noref) {
|
||||||
|
@ -556,8 +589,8 @@ ip_vs_bypass_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
EnterFunction(10);
|
EnterFunction(10);
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
if (__ip_vs_get_out_rt(skb, NULL, iph->daddr, IP_VS_RT_MODE_NON_LOCAL,
|
if (__ip_vs_get_out_rt(cp->af, skb, NULL, iph->daddr,
|
||||||
NULL) < 0)
|
IP_VS_RT_MODE_NON_LOCAL, NULL, ipvsh) < 0)
|
||||||
goto tx_error;
|
goto tx_error;
|
||||||
|
|
||||||
ip_send_check(iph);
|
ip_send_check(iph);
|
||||||
|
@ -586,7 +619,7 @@ ip_vs_bypass_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
EnterFunction(10);
|
EnterFunction(10);
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
if (__ip_vs_get_out_rt_v6(skb, NULL, &ipvsh->daddr.in6, NULL,
|
if (__ip_vs_get_out_rt_v6(cp->af, skb, NULL, &ipvsh->daddr.in6, NULL,
|
||||||
ipvsh, 0, IP_VS_RT_MODE_NON_LOCAL) < 0)
|
ipvsh, 0, IP_VS_RT_MODE_NON_LOCAL) < 0)
|
||||||
goto tx_error;
|
goto tx_error;
|
||||||
|
|
||||||
|
@ -633,10 +666,10 @@ ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
}
|
}
|
||||||
|
|
||||||
was_input = rt_is_input_route(skb_rtable(skb));
|
was_input = rt_is_input_route(skb_rtable(skb));
|
||||||
local = __ip_vs_get_out_rt(skb, cp->dest, cp->daddr.ip,
|
local = __ip_vs_get_out_rt(cp->af, skb, cp->dest, cp->daddr.ip,
|
||||||
IP_VS_RT_MODE_LOCAL |
|
IP_VS_RT_MODE_LOCAL |
|
||||||
IP_VS_RT_MODE_NON_LOCAL |
|
IP_VS_RT_MODE_NON_LOCAL |
|
||||||
IP_VS_RT_MODE_RDR, NULL);
|
IP_VS_RT_MODE_RDR, NULL, ipvsh);
|
||||||
if (local < 0)
|
if (local < 0)
|
||||||
goto tx_error;
|
goto tx_error;
|
||||||
rt = skb_rtable(skb);
|
rt = skb_rtable(skb);
|
||||||
|
@ -721,8 +754,8 @@ ip_vs_nat_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
IP_VS_DBG(10, "filled cport=%d\n", ntohs(*p));
|
IP_VS_DBG(10, "filled cport=%d\n", ntohs(*p));
|
||||||
}
|
}
|
||||||
|
|
||||||
local = __ip_vs_get_out_rt_v6(skb, cp->dest, &cp->daddr.in6, NULL,
|
local = __ip_vs_get_out_rt_v6(cp->af, skb, cp->dest, &cp->daddr.in6,
|
||||||
ipvsh, 0,
|
NULL, ipvsh, 0,
|
||||||
IP_VS_RT_MODE_LOCAL |
|
IP_VS_RT_MODE_LOCAL |
|
||||||
IP_VS_RT_MODE_NON_LOCAL |
|
IP_VS_RT_MODE_NON_LOCAL |
|
||||||
IP_VS_RT_MODE_RDR);
|
IP_VS_RT_MODE_RDR);
|
||||||
|
@ -791,6 +824,81 @@ tx_error:
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* When forwarding a packet, we must ensure that we've got enough headroom
|
||||||
|
* for the encapsulation packet in the skb. This also gives us an
|
||||||
|
* opportunity to figure out what the payload_len, dsfield, ttl, and df
|
||||||
|
* values should be, so that we won't need to look at the old ip header
|
||||||
|
* again
|
||||||
|
*/
|
||||||
|
static struct sk_buff *
|
||||||
|
ip_vs_prepare_tunneled_skb(struct sk_buff *skb, int skb_af,
|
||||||
|
unsigned int max_headroom, __u8 *next_protocol,
|
||||||
|
__u32 *payload_len, __u8 *dsfield, __u8 *ttl,
|
||||||
|
__be16 *df)
|
||||||
|
{
|
||||||
|
struct sk_buff *new_skb = NULL;
|
||||||
|
struct iphdr *old_iph = NULL;
|
||||||
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
|
struct ipv6hdr *old_ipv6h = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (skb_headroom(skb) < max_headroom || skb_cloned(skb)) {
|
||||||
|
new_skb = skb_realloc_headroom(skb, max_headroom);
|
||||||
|
if (!new_skb)
|
||||||
|
goto error;
|
||||||
|
consume_skb(skb);
|
||||||
|
skb = new_skb;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_IP_VS_IPV6
|
||||||
|
if (skb_af == AF_INET6) {
|
||||||
|
old_ipv6h = ipv6_hdr(skb);
|
||||||
|
*next_protocol = IPPROTO_IPV6;
|
||||||
|
if (payload_len)
|
||||||
|
*payload_len =
|
||||||
|
ntohs(old_ipv6h->payload_len) +
|
||||||
|
sizeof(*old_ipv6h);
|
||||||
|
*dsfield = ipv6_get_dsfield(old_ipv6h);
|
||||||
|
*ttl = old_ipv6h->hop_limit;
|
||||||
|
if (df)
|
||||||
|
*df = 0;
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
old_iph = ip_hdr(skb);
|
||||||
|
/* Copy DF, reset fragment offset and MF */
|
||||||
|
if (df)
|
||||||
|
*df = (old_iph->frag_off & htons(IP_DF));
|
||||||
|
*next_protocol = IPPROTO_IPIP;
|
||||||
|
|
||||||
|
/* fix old IP header checksum */
|
||||||
|
ip_send_check(old_iph);
|
||||||
|
*dsfield = ipv4_get_dsfield(old_iph);
|
||||||
|
*ttl = old_iph->ttl;
|
||||||
|
if (payload_len)
|
||||||
|
*payload_len = ntohs(old_iph->tot_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return skb;
|
||||||
|
error:
|
||||||
|
kfree_skb(skb);
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int __tun_gso_type_mask(int encaps_af, int orig_af)
|
||||||
|
{
|
||||||
|
if (encaps_af == AF_INET) {
|
||||||
|
if (orig_af == AF_INET)
|
||||||
|
return SKB_GSO_IPIP;
|
||||||
|
|
||||||
|
return SKB_GSO_SIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GSO: we need to provide proper SKB_GSO_ value for IPv6:
|
||||||
|
* SKB_GSO_SIT/IPV6
|
||||||
|
*/
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* IP Tunneling transmitter
|
* IP Tunneling transmitter
|
||||||
|
@ -819,9 +927,11 @@ ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
struct rtable *rt; /* Route to the other host */
|
struct rtable *rt; /* Route to the other host */
|
||||||
__be32 saddr; /* Source for tunnel */
|
__be32 saddr; /* Source for tunnel */
|
||||||
struct net_device *tdev; /* Device to other host */
|
struct net_device *tdev; /* Device to other host */
|
||||||
struct iphdr *old_iph = ip_hdr(skb);
|
__u8 next_protocol = 0;
|
||||||
u8 tos = old_iph->tos;
|
__u8 dsfield = 0;
|
||||||
__be16 df;
|
__u8 ttl = 0;
|
||||||
|
__be16 df = 0;
|
||||||
|
__be16 *dfp = NULL;
|
||||||
struct iphdr *iph; /* Our new IP header */
|
struct iphdr *iph; /* Our new IP header */
|
||||||
unsigned int max_headroom; /* The extra header space needed */
|
unsigned int max_headroom; /* The extra header space needed */
|
||||||
int ret, local;
|
int ret, local;
|
||||||
|
@ -829,11 +939,11 @@ ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
EnterFunction(10);
|
EnterFunction(10);
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
local = __ip_vs_get_out_rt(skb, cp->dest, cp->daddr.ip,
|
local = __ip_vs_get_out_rt(cp->af, skb, cp->dest, cp->daddr.ip,
|
||||||
IP_VS_RT_MODE_LOCAL |
|
IP_VS_RT_MODE_LOCAL |
|
||||||
IP_VS_RT_MODE_NON_LOCAL |
|
IP_VS_RT_MODE_NON_LOCAL |
|
||||||
IP_VS_RT_MODE_CONNECT |
|
IP_VS_RT_MODE_CONNECT |
|
||||||
IP_VS_RT_MODE_TUNNEL, &saddr);
|
IP_VS_RT_MODE_TUNNEL, &saddr, ipvsh);
|
||||||
if (local < 0)
|
if (local < 0)
|
||||||
goto tx_error;
|
goto tx_error;
|
||||||
if (local) {
|
if (local) {
|
||||||
|
@ -844,29 +954,21 @@ ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
rt = skb_rtable(skb);
|
rt = skb_rtable(skb);
|
||||||
tdev = rt->dst.dev;
|
tdev = rt->dst.dev;
|
||||||
|
|
||||||
/* Copy DF, reset fragment offset and MF */
|
|
||||||
df = sysctl_pmtu_disc(ipvs) ? old_iph->frag_off & htons(IP_DF) : 0;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Okay, now see if we can stuff it in the buffer as-is.
|
* Okay, now see if we can stuff it in the buffer as-is.
|
||||||
*/
|
*/
|
||||||
max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(struct iphdr);
|
max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(struct iphdr);
|
||||||
|
|
||||||
if (skb_headroom(skb) < max_headroom || skb_cloned(skb)) {
|
/* We only care about the df field if sysctl_pmtu_disc(ipvs) is set */
|
||||||
struct sk_buff *new_skb =
|
dfp = sysctl_pmtu_disc(ipvs) ? &df : NULL;
|
||||||
skb_realloc_headroom(skb, max_headroom);
|
skb = ip_vs_prepare_tunneled_skb(skb, cp->af, max_headroom,
|
||||||
|
&next_protocol, NULL, &dsfield,
|
||||||
|
&ttl, dfp);
|
||||||
|
if (IS_ERR(skb))
|
||||||
|
goto tx_error;
|
||||||
|
|
||||||
if (!new_skb)
|
skb = iptunnel_handle_offloads(
|
||||||
goto tx_error;
|
skb, false, __tun_gso_type_mask(AF_INET, cp->af));
|
||||||
consume_skb(skb);
|
|
||||||
skb = new_skb;
|
|
||||||
old_iph = ip_hdr(skb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fix old IP header checksum */
|
|
||||||
ip_send_check(old_iph);
|
|
||||||
|
|
||||||
skb = iptunnel_handle_offloads(skb, false, SKB_GSO_IPIP);
|
|
||||||
if (IS_ERR(skb))
|
if (IS_ERR(skb))
|
||||||
goto tx_error;
|
goto tx_error;
|
||||||
|
|
||||||
|
@ -883,11 +985,11 @@ ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
iph->version = 4;
|
iph->version = 4;
|
||||||
iph->ihl = sizeof(struct iphdr)>>2;
|
iph->ihl = sizeof(struct iphdr)>>2;
|
||||||
iph->frag_off = df;
|
iph->frag_off = df;
|
||||||
iph->protocol = IPPROTO_IPIP;
|
iph->protocol = next_protocol;
|
||||||
iph->tos = tos;
|
iph->tos = dsfield;
|
||||||
iph->daddr = cp->daddr.ip;
|
iph->daddr = cp->daddr.ip;
|
||||||
iph->saddr = saddr;
|
iph->saddr = saddr;
|
||||||
iph->ttl = old_iph->ttl;
|
iph->ttl = ttl;
|
||||||
ip_select_ident(skb, NULL);
|
ip_select_ident(skb, NULL);
|
||||||
|
|
||||||
/* Another hack: avoid icmp_send in ip_fragment */
|
/* Another hack: avoid icmp_send in ip_fragment */
|
||||||
|
@ -920,7 +1022,10 @@ ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
struct rt6_info *rt; /* Route to the other host */
|
struct rt6_info *rt; /* Route to the other host */
|
||||||
struct in6_addr saddr; /* Source for tunnel */
|
struct in6_addr saddr; /* Source for tunnel */
|
||||||
struct net_device *tdev; /* Device to other host */
|
struct net_device *tdev; /* Device to other host */
|
||||||
struct ipv6hdr *old_iph = ipv6_hdr(skb);
|
__u8 next_protocol = 0;
|
||||||
|
__u32 payload_len = 0;
|
||||||
|
__u8 dsfield = 0;
|
||||||
|
__u8 ttl = 0;
|
||||||
struct ipv6hdr *iph; /* Our new IP header */
|
struct ipv6hdr *iph; /* Our new IP header */
|
||||||
unsigned int max_headroom; /* The extra header space needed */
|
unsigned int max_headroom; /* The extra header space needed */
|
||||||
int ret, local;
|
int ret, local;
|
||||||
|
@ -928,7 +1033,7 @@ ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
EnterFunction(10);
|
EnterFunction(10);
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
local = __ip_vs_get_out_rt_v6(skb, cp->dest, &cp->daddr.in6,
|
local = __ip_vs_get_out_rt_v6(cp->af, skb, cp->dest, &cp->daddr.in6,
|
||||||
&saddr, ipvsh, 1,
|
&saddr, ipvsh, 1,
|
||||||
IP_VS_RT_MODE_LOCAL |
|
IP_VS_RT_MODE_LOCAL |
|
||||||
IP_VS_RT_MODE_NON_LOCAL |
|
IP_VS_RT_MODE_NON_LOCAL |
|
||||||
|
@ -948,19 +1053,14 @@ ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
*/
|
*/
|
||||||
max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(struct ipv6hdr);
|
max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(struct ipv6hdr);
|
||||||
|
|
||||||
if (skb_headroom(skb) < max_headroom || skb_cloned(skb)) {
|
skb = ip_vs_prepare_tunneled_skb(skb, cp->af, max_headroom,
|
||||||
struct sk_buff *new_skb =
|
&next_protocol, &payload_len,
|
||||||
skb_realloc_headroom(skb, max_headroom);
|
&dsfield, &ttl, NULL);
|
||||||
|
if (IS_ERR(skb))
|
||||||
|
goto tx_error;
|
||||||
|
|
||||||
if (!new_skb)
|
skb = iptunnel_handle_offloads(
|
||||||
goto tx_error;
|
skb, false, __tun_gso_type_mask(AF_INET6, cp->af));
|
||||||
consume_skb(skb);
|
|
||||||
skb = new_skb;
|
|
||||||
old_iph = ipv6_hdr(skb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* GSO: we need to provide proper SKB_GSO_ value for IPv6 */
|
|
||||||
skb = iptunnel_handle_offloads(skb, false, 0); /* SKB_GSO_SIT/IPV6 */
|
|
||||||
if (IS_ERR(skb))
|
if (IS_ERR(skb))
|
||||||
goto tx_error;
|
goto tx_error;
|
||||||
|
|
||||||
|
@ -975,14 +1075,13 @@ ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
*/
|
*/
|
||||||
iph = ipv6_hdr(skb);
|
iph = ipv6_hdr(skb);
|
||||||
iph->version = 6;
|
iph->version = 6;
|
||||||
iph->nexthdr = IPPROTO_IPV6;
|
iph->nexthdr = next_protocol;
|
||||||
iph->payload_len = old_iph->payload_len;
|
iph->payload_len = htons(payload_len);
|
||||||
be16_add_cpu(&iph->payload_len, sizeof(*old_iph));
|
|
||||||
memset(&iph->flow_lbl, 0, sizeof(iph->flow_lbl));
|
memset(&iph->flow_lbl, 0, sizeof(iph->flow_lbl));
|
||||||
ipv6_change_dsfield(iph, 0, ipv6_get_dsfield(old_iph));
|
ipv6_change_dsfield(iph, 0, dsfield);
|
||||||
iph->daddr = cp->daddr.in6;
|
iph->daddr = cp->daddr.in6;
|
||||||
iph->saddr = saddr;
|
iph->saddr = saddr;
|
||||||
iph->hop_limit = old_iph->hop_limit;
|
iph->hop_limit = ttl;
|
||||||
|
|
||||||
/* Another hack: avoid icmp_send in ip_fragment */
|
/* Another hack: avoid icmp_send in ip_fragment */
|
||||||
skb->ignore_df = 1;
|
skb->ignore_df = 1;
|
||||||
|
@ -1021,10 +1120,10 @@ ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
EnterFunction(10);
|
EnterFunction(10);
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
local = __ip_vs_get_out_rt(skb, cp->dest, cp->daddr.ip,
|
local = __ip_vs_get_out_rt(cp->af, skb, cp->dest, cp->daddr.ip,
|
||||||
IP_VS_RT_MODE_LOCAL |
|
IP_VS_RT_MODE_LOCAL |
|
||||||
IP_VS_RT_MODE_NON_LOCAL |
|
IP_VS_RT_MODE_NON_LOCAL |
|
||||||
IP_VS_RT_MODE_KNOWN_NH, NULL);
|
IP_VS_RT_MODE_KNOWN_NH, NULL, ipvsh);
|
||||||
if (local < 0)
|
if (local < 0)
|
||||||
goto tx_error;
|
goto tx_error;
|
||||||
if (local) {
|
if (local) {
|
||||||
|
@ -1060,8 +1159,8 @@ ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
EnterFunction(10);
|
EnterFunction(10);
|
||||||
|
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
local = __ip_vs_get_out_rt_v6(skb, cp->dest, &cp->daddr.in6, NULL,
|
local = __ip_vs_get_out_rt_v6(cp->af, skb, cp->dest, &cp->daddr.in6,
|
||||||
ipvsh, 0,
|
NULL, ipvsh, 0,
|
||||||
IP_VS_RT_MODE_LOCAL |
|
IP_VS_RT_MODE_LOCAL |
|
||||||
IP_VS_RT_MODE_NON_LOCAL);
|
IP_VS_RT_MODE_NON_LOCAL);
|
||||||
if (local < 0)
|
if (local < 0)
|
||||||
|
@ -1128,7 +1227,8 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL |
|
IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL |
|
||||||
IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL;
|
IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL;
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
local = __ip_vs_get_out_rt(skb, cp->dest, cp->daddr.ip, rt_mode, NULL);
|
local = __ip_vs_get_out_rt(cp->af, skb, cp->dest, cp->daddr.ip, rt_mode,
|
||||||
|
NULL, iph);
|
||||||
if (local < 0)
|
if (local < 0)
|
||||||
goto tx_error;
|
goto tx_error;
|
||||||
rt = skb_rtable(skb);
|
rt = skb_rtable(skb);
|
||||||
|
@ -1219,8 +1319,8 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
|
||||||
IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL |
|
IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL |
|
||||||
IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL;
|
IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL;
|
||||||
rcu_read_lock();
|
rcu_read_lock();
|
||||||
local = __ip_vs_get_out_rt_v6(skb, cp->dest, &cp->daddr.in6, NULL,
|
local = __ip_vs_get_out_rt_v6(cp->af, skb, cp->dest, &cp->daddr.in6,
|
||||||
ipvsh, 0, rt_mode);
|
NULL, ipvsh, 0, rt_mode);
|
||||||
if (local < 0)
|
if (local < 0)
|
||||||
goto tx_error;
|
goto tx_error;
|
||||||
rt = (struct rt6_info *) skb_dst(skb);
|
rt = (struct rt6_info *) skb_dst(skb);
|
||||||
|
|
Loading…
Reference in New Issue