/*
* ip_vs_xmit.c: various packet transmitters for IPVS
*
* Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
* Julian Anastasov <ja@ssi.bg>
*
* 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:
*
*/
#include <linux/kernel.h>
#include <linux/tcp.h> /* for tcphdr */
#include <net/ip.h>
#include <net/tcp.h> /* for csum_tcpudp_magic */
#include <net/udp.h>
#include <net/icmp.h> /* for icmp_send */
#include <net/route.h> /* for ip_route_output */
#include <net/ipv6.h>
#include <net/ip6_route.h>
#include <linux/icmpv6.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <net/ip_vs.h>
/*
* Destination cache to speed up outgoing route lookup
*/
static inline void
__ip_vs_dst_set(struct ip_vs_dest *dest, u32 rtos, struct dst_entry *dst)
{
struct dst_entry *old_dst;
old_dst = dest->dst_cache;
dest->dst_cache = dst;
dest->dst_rtos = rtos;
dst_release(old_dst);
}
static inline struct dst_entry *
__ip_vs_dst_check(struct ip_vs_dest *dest, u32 rtos, u32 cookie)
{
struct dst_entry *dst = dest->dst_cache;
if (!dst)
return NULL;
if ((dst->obsolete
|| (dest->af == AF_INET && rtos != dest->dst_rtos)) &&
dst->ops->check(dst, cookie) == NULL) {
dest->dst_cache = NULL;
dst_release(dst);
return NULL;
}
dst_hold(dst);
return dst;
}
static struct rtable *
__ip_vs_get_out_rt(struct ip_vs_conn *cp, u32 rtos)
{
struct rtable *rt; /* Route to the other host */
struct ip_vs_dest *dest = cp->dest;
if (dest) {
spin_lock(&dest->dst_lock);
if (!(rt = (struct rtable *)
__ip_vs_dst_check(dest, rtos, 0))) {
struct flowi fl = {
.oif = 0,
.nl_u = {
.ip4_u = {
.daddr = dest->addr.ip,
.saddr = 0,
.tos = rtos, } },
};
if (ip_route_output_key(&init_net, &rt, &fl)) {
spin_unlock(&dest->dst_lock);
IP_VS_DBG_RL("ip_route_output error, "
"dest: %u.%u.%u.%u\n",
NIPQUAD(dest->addr.ip));
return NULL;
}
__ip_vs_dst_set(dest, rtos, dst_clone(&rt->u.dst));
IP_VS_DBG(10, "new dst %u.%u.%u.%u, refcnt=%d, rtos=%X\n",
NIPQUAD(dest->addr.ip),
atomic_read(&rt->u.dst.__refcnt), rtos);
}
spin_unlock(&dest->dst_lock);
} else {
struct flowi fl = {
.oif = 0,
.nl_u = {
.ip4_u = {
.daddr = cp->daddr.ip,
.saddr = 0,
.tos = rtos, } },
};
if (ip_route_output_key(&init_net, &rt, &fl)) {
IP_VS_DBG_RL("ip_route_output error, dest: "
"%u.%u.%u.%u\n", NIPQUAD(cp->daddr.ip));
return NULL;
}
}
return rt;
}
#ifdef CONFIG_IP_VS_IPV6
static struct rt6_info *
__ip_vs_get_out_rt_v6(struct ip_vs_conn *cp)
{
struct rt6_info *rt; /* Route to the other host */
struct ip_vs_dest *dest = cp->dest;
if (dest) {
spin_lock(&dest->dst_lock);
rt = (struct rt6_info *)__ip_vs_dst_check(dest, 0, 0);
if (!rt) {
struct flowi fl = {
.oif = 0,
.nl_u = {
.ip6_u = {
.daddr = dest->addr.in6,
.saddr = {
.s6_addr32 =
{ 0, 0, 0, 0 },
},
},