From 7af17c2672f54deea02b84eeb1fdacc895d84a98 Mon Sep 17 00:00:00 2001 From: lijian Date: Sat, 17 May 2014 09:47:30 +0800 Subject: [PATCH 1/3] add snat gateway support for lvs-v2 --- docs/snat_gateway_user_manual.pdf | Bin 0 -> 106627 bytes kernel/.config | 4 +- kernel/include/linux/ip_vs.h | 74 +- kernel/include/net/ip_vs.h | 54 ++ kernel/net/netfilter/ipvs/Kconfig | 14 + kernel/net/netfilter/ipvs/Makefile | 2 + kernel/net/netfilter/ipvs/ip_vs_conn.c | 78 ++- kernel/net/netfilter/ipvs/ip_vs_core.c | 70 +- kernel/net/netfilter/ipvs/ip_vs_ctl.c | 660 +++++++++++++++++- kernel/net/netfilter/ipvs/ip_vs_proto.c | 3 + kernel/net/netfilter/ipvs/ip_vs_proto_icmp.c | 210 ++++++ kernel/net/netfilter/ipvs/ip_vs_proto_tcp.c | 21 +- kernel/net/netfilter/ipvs/ip_vs_proto_udp.c | 161 +++++ kernel/net/netfilter/ipvs/ip_vs_snat_sched.c | 493 +++++++++++++ kernel/net/netfilter/ipvs/ip_vs_synproxy.c | 30 + kernel/net/netfilter/ipvs/ip_vs_xmit.c | 423 ++++++++++- kernel/patches | 1 - kernel/refresh_patch.sh | 1 - kernel/run_oldconfig.py | 1 - tools/ipvsadm/ipvsadm.c | 582 ++++++++++++--- .../doc/samples/keepalived.conf.snat_gateway | 29 + tools/keepalived/keepalived/check/check_api.c | 102 +++ .../keepalived/check/check_daemon.c | 5 +- .../keepalived/keepalived/check/check_data.c | 49 +- .../keepalived/check/check_parser.c | 294 +++++++- .../keepalived/keepalived/check/ipvswrapper.c | 92 ++- tools/keepalived/keepalived/check/ipwrapper.c | 356 +++++++++- .../keepalived/etc/init.d/keepalived.init | 24 +- .../keepalived/keepalived/include/check_api.h | 2 + .../keepalived/include/check_data.h | 64 ++ .../keepalived/include/ipvswrapper.h | 2 + .../keepalived/keepalived/include/ipwrapper.h | 4 + .../keepalived/keepalived/libipvs-2.6/ip_vs.h | 82 ++- .../keepalived/libipvs-2.6/ip_vs_nl_policy.c | 17 + .../keepalived/libipvs-2.6/libipvs.c | 205 +++++- .../keepalived/libipvs-2.6/libipvs.h | 19 +- tools/quagga/redhat/quagga.spec | 2 +- tools/quagga/vtysh/extract.pl | 2 +- 38 files changed, 4019 insertions(+), 213 deletions(-) create mode 100644 docs/snat_gateway_user_manual.pdf create mode 100644 kernel/net/netfilter/ipvs/ip_vs_proto_icmp.c create mode 100644 kernel/net/netfilter/ipvs/ip_vs_snat_sched.c delete mode 120000 kernel/patches delete mode 120000 kernel/refresh_patch.sh delete mode 120000 kernel/run_oldconfig.py create mode 100644 tools/keepalived/doc/samples/keepalived.conf.snat_gateway diff --git a/docs/snat_gateway_user_manual.pdf b/docs/snat_gateway_user_manual.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4b96b6273b75d24f913492772712b4020560bcf0 GIT binary patch literal 106627 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 z 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 literal 0 HcmV?d00001 diff --git a/kernel/.config b/kernel/.config index 5eb4948b..9a3b2fc2 100644 --- a/kernel/.config +++ b/kernel/.config @@ -1,7 +1,7 @@ # # Automatically generated make config: don't edit # Linux kernel version: 2.6.32 -# Mon Feb 20 19:35:45 2012 +# Mon Jun 30 15:19:27 2014 # CONFIG_64BIT=y # CONFIG_X86_32 is not set @@ -763,6 +763,7 @@ CONFIG_IP_VS_PROTO_UDP=y CONFIG_IP_VS_PROTO_AH_ESP=y CONFIG_IP_VS_PROTO_ESP=y CONFIG_IP_VS_PROTO_AH=y +CONFIG_IP_VS_PROTO_ICMP=y # # IPVS scheduler @@ -777,6 +778,7 @@ CONFIG_IP_VS_DH=m CONFIG_IP_VS_SH=m CONFIG_IP_VS_SED=m CONFIG_IP_VS_NQ=m +CONFIG_IP_VS_SNAT_SCHED=m # # IPVS application helper diff --git a/kernel/include/linux/ip_vs.h b/kernel/include/linux/ip_vs.h index 17f51e7d..eaa1fcb4 100644 --- a/kernel/include/linux/ip_vs.h +++ b/kernel/include/linux/ip_vs.h @@ -8,7 +8,7 @@ #include /* For __beXX types in userland */ -#define IP_VS_VERSION_CODE 0x010201 +#define IP_VS_VERSION_CODE 0x010202 #define NVERSION(version) \ (version >> 16) & 0xFF, \ (version >> 8) & 0xFF, \ @@ -57,7 +57,10 @@ #define IP_VS_SO_SET_ZERO (IP_VS_BASE_CTL+15) #define IP_VS_SO_SET_ADDLADDR (IP_VS_BASE_CTL+16) #define IP_VS_SO_SET_DELLADDR (IP_VS_BASE_CTL+17) -#define IP_VS_SO_SET_MAX IP_VS_SO_SET_DELLADDR +#define IP_VS_SO_SET_ADDSNAT (IP_VS_BASE_CTL + 18) +#define IP_VS_SO_SET_DELSNAT (IP_VS_BASE_CTL + 19) +#define IP_VS_SO_SET_EDITSNAT (IP_VS_BASE_CTL + 20) +#define IP_VS_SO_SET_MAX IP_VS_SO_SET_EDITSNAT #define IP_VS_SO_GET_VERSION IP_VS_BASE_CTL #define IP_VS_SO_GET_INFO (IP_VS_BASE_CTL+1) @@ -68,7 +71,8 @@ #define IP_VS_SO_GET_TIMEOUT (IP_VS_BASE_CTL+6) #define IP_VS_SO_GET_DAEMON (IP_VS_BASE_CTL+7) #define IP_VS_SO_GET_LADDRS (IP_VS_BASE_CTL+8) -#define IP_VS_SO_GET_MAX IP_VS_SO_GET_LADDRS +#define IP_VS_SO_GET_SNAT (IP_VS_BASE_CTL + 9) /* not used now */ +#define IP_VS_SO_GET_MAX IP_VS_SO_GET_SNAT /* * IPVS Connection Flags @@ -127,6 +131,26 @@ struct ip_vs_dest_user { __u32 l_threshold; /* lower threshold */ }; +/* SNAT ip pool select algorithm */ +enum { + IPVS_SNAT_IPS_NORMAL = 0, /* src-ip/dst-ip */ + IPVS_SNAT_IPS_PERSITENT, /* src-ip */ + IPVS_SNAT_IPS_RANDOM, /* src-ip/dst-ip/src-port */ +}; + +struct ip_vs_dest_snat_user { + __be32 saddr; /* SNAT source address */ + __be16 smask; /* SNAT source network mask */ + __be32 daddr; /* SNAT dest address */ + __be16 dmask; /* SNAT dest network mask */ + __be32 gw; /* SNAT orign gateway */ + __be32 min_source_ip, max_source_ip; /* SNAT ip pool */ + __u8 algo; /* SNAT ip pool select algorithm */ + unsigned conn_flags; + __be32 new_gw; /* SNAT new next gateway */ + char out_dev[IP_VS_IFNAME_MAXLEN]; +}; + struct ip_vs_laddr_user { __be32 addr; /* ipv4 address */ }; @@ -313,6 +337,11 @@ enum { IPVS_CMD_DEL_LADDR, /* del local address */ IPVS_CMD_GET_LADDR, /* dump local address */ + IPVS_CMD_NEW_SNATDEST, /* add snat rule */ + IPVS_CMD_SET_SNATDEST, /* edit snat rule */ + IPVS_CMD_DEL_SNATDEST, /* del snat rule */ + IPVS_CMD_GET_SNATDEST, /* dump snat rule */ + __IPVS_CMD_MAX, }; @@ -328,10 +357,11 @@ enum { IPVS_CMD_ATTR_TIMEOUT_TCP_FIN, /* TCP FIN wait timeout */ IPVS_CMD_ATTR_TIMEOUT_UDP, /* UDP timeout */ IPVS_CMD_ATTR_LADDR, /* nested local address attribute */ + IPVS_CMD_ATTR_SNATDEST, /* nested snat rule attribute */ __IPVS_CMD_ATTR_MAX, }; -#define IPVS_CMD_ATTR_MAX (__IPVS_SVC_ATTR_MAX - 1) +#define IPVS_CMD_ATTR_MAX (__IPVS_CMD_ATTR_MAX - 1) /* * Attributes used to describe a service @@ -352,6 +382,7 @@ enum { IPVS_SVC_ATTR_NETMASK, /* persistent netmask */ IPVS_SVC_ATTR_STATS, /* nested attribute for service stats */ + __IPVS_SVC_ATTR_MAX, }; @@ -378,16 +409,42 @@ enum { IPVS_DEST_ATTR_PERSIST_CONNS, /* persistent connections */ IPVS_DEST_ATTR_STATS, /* nested attribute for dest stats */ + + IPVS_DEST_ATTR_SNATRULE, /* nested attribute for dest snat rule */ + __IPVS_DEST_ATTR_MAX, }; #define IPVS_DEST_ATTR_MAX (__IPVS_DEST_ATTR_MAX - 1) -/* - * * Attirbutes used to describe a local address - * * - * */ +/** + * Attribute used to describe a snat dest (snat rule) + * Used inside nested attribute IPVS_CMD_ATTR_SNATDEST and IPVS_DEST_ATTR_SNATRULE + */ +enum { + IPVS_SNAT_DEST_ATTR_UNSPEC = 0, + IPVS_SNAT_DEST_ATTR_FADDR, + IPVS_SNAT_DEST_ATTR_FMASK, + IPVS_SNAT_DEST_ATTR_DADDR, + IPVS_SNAT_DEST_ATTR_DMASK, + IPVS_SNAT_DEST_ATTR_GW, + IPVS_SNAT_DEST_ATTR_MINIP, + IPVS_SNAT_DEST_ATTR_MAXIP, + IPVS_SNAT_DEST_ATTR_ALGO, + IPVS_SNAT_DEST_ATTR_NEWGW, + IPVS_SNAT_DEST_ATTR_CONNFLAG, + IPVS_SNAT_DEST_ATTR_OUTDEV, + + __IPVS_SNAT_DEST_ATTR_MAX, +}; + +#define IPVS_SNAT_DEST_ATTR_MAX (__IPVS_SNAT_DEST_ATTR_MAX - 1) + + +/* + * Attirbutes used to describe a local address + */ enum { IPVS_LADDR_ATTR_UNSPEC = 0, IPVS_LADDR_ATTR_ADDR, @@ -447,3 +504,4 @@ enum { #define IPVS_INFO_ATTR_MAX (__IPVS_INFO_ATTR_MAX - 1) #endif /* _IP_VS_H */ + diff --git a/kernel/include/net/ip_vs.h b/kernel/include/net/ip_vs.h index 3b6ef202..715de8a8 100644 --- a/kernel/include/net/ip_vs.h +++ b/kernel/include/net/ip_vs.h @@ -426,6 +426,9 @@ struct ip_vs_conn { struct net_device *indev; unsigned char src_hwaddr[MAX_ADDR_LEN]; unsigned char dst_hwaddr[MAX_ADDR_LEN]; + struct net_device *dev_inside; + unsigned char src_hwaddr_inside[ETH_ALEN]; + unsigned char dst_hwaddr_inside[ETH_ALEN]; }; /* @@ -465,6 +468,20 @@ struct ip_vs_dest_user_kern { u32 l_threshold; /* lower threshold */ }; +struct ip_vs_snat_dest_user_kern { + //struct ip_vs_dest_user_kern dest; + union nf_inet_addr saddr; /* source address */ + u32 smask; /* soure network mask */ + union nf_inet_addr daddr; /* dest address */ + u32 dmask; /* dest network mask */ + union nf_inet_addr gw;/* isp gateway */ + union nf_inet_addr minip, maxip; /* snat ip */ + u8 algo; /* snat source ip address choice algo */ + union nf_inet_addr new_gw; /* dest gateway */ + unsigned conn_flags; /* connection flags */ + char out_dev[IP_VS_IFNAME_MAXLEN]; +}; + struct ip_vs_laddr_user_kern { union nf_inet_addr addr; /* ip address */ }; @@ -545,6 +562,33 @@ struct ip_vs_dest { __u32 vfwmark; /* firewall mark of service */ }; +struct ip_vs_dest_snat { + struct ip_vs_dest dest; + + /* snat rule */ + union nf_inet_addr saddr; + union nf_inet_addr smask; + union nf_inet_addr daddr; + union nf_inet_addr dmask; + union nf_inet_addr minip, maxip; /* snat ip */ + u8 ip_sel_algo; + union nf_inet_addr new_gateway; + char out_dev[IP_VS_IFNAME_MAXLEN]; + unsigned char out_dev_mask[IP_VS_IFNAME_MAXLEN]; + struct list_head rule_list; +}; + +#define IS_SNAT_CP(cp) ((cp)->dest && \ + (cp)->dest->svc && \ + (cp)->dest->svc->fwmark == 1) + +#define NOT_SNAT_CP(cp) (!(cp)->dest || \ + !(cp)->dest->svc || \ + (cp)->dest->svc->fwmark != 1) + +#define IS_SNAT_SVC(svc) ((svc)->fwmark == 1) +#define NOT_SNAT_SVC(svc) ((svc)->fwmark != 1) + /* * Local ip address object, now only used in FULL NAT model */ @@ -702,14 +746,20 @@ enum { SYNPROXY_CONN_REUSED_CLOSEWAIT, SYNPROXY_CONN_REUSED_LASTACK, DEFENCE_IP_FRAG_DROP, + DEFENCE_IP_FRAG_GATHER, DEFENCE_TCP_DROP, DEFENCE_UDP_DROP, FAST_XMIT_REJECT, FAST_XMIT_PASS, + FAST_XMIT_FAILED, FAST_XMIT_SKB_COPY, FAST_XMIT_NO_MAC, FAST_XMIT_SYNPROXY_SAVE, FAST_XMIT_DEV_LOST, + FAST_XMIT_REJECT_INSIDE, + FAST_XMIT_PASS_INSIDE, + FAST_XMIT_FAILED_INSIDE, + FAST_XMIT_SYNPROXY_SAVE_INSIDE, RST_IN_SYN_SENT, RST_OUT_SYN_SENT, RST_IN_ESTABLISHED, @@ -954,6 +1004,7 @@ extern int sysctl_ip_vs_tcp_drop_entry; extern int sysctl_ip_vs_udp_drop_entry; extern int sysctl_ip_vs_conn_expire_tcp_rst; extern int sysctl_ip_vs_fast_xmit; +extern int sysctl_ip_vs_fast_xmit_inside; extern struct ip_vs_service *ip_vs_service_get(int af, __u32 fwmark, __u16 protocol, @@ -1055,6 +1106,9 @@ extern int ip_vs_fnat_response_icmp_xmit(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int offset); +extern int ip_vs_snat_out_xmit + (struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp); + #ifdef CONFIG_IP_VS_IPV6 extern int ip_vs_bypass_xmit_v6 (struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp); diff --git a/kernel/net/netfilter/ipvs/Kconfig b/kernel/net/netfilter/ipvs/Kconfig index e1e44018..23b713e9 100644 --- a/kernel/net/netfilter/ipvs/Kconfig +++ b/kernel/net/netfilter/ipvs/Kconfig @@ -100,6 +100,12 @@ config IP_VS_PROTO_AH This option enables support for load balancing AH (Authentication Header) transport protocol. Say Y if unsure. +config IP_VS_PROTO_ICMP + bool "ICMP snat gateway support" + ---help--- + This option enables support for snat gateway ICMP transport + protocol. Say Y if unsure. + comment "IPVS scheduler" config IP_VS_RR @@ -222,6 +228,14 @@ config IP_VS_NQ 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_SNAT_SCHED + tristate "snat gateway scheduling" + ---help--- + The snat-gateway scheduling match rules like iptables`s rules. + + If you want to compile it in kernel, say Y. To compile it as a + module, choose M here. If unsure, say N. + comment 'IPVS application helper' config IP_VS_FTP diff --git a/kernel/net/netfilter/ipvs/Makefile b/kernel/net/netfilter/ipvs/Makefile index f7493c5b..f18e5433 100644 --- a/kernel/net/netfilter/ipvs/Makefile +++ b/kernel/net/netfilter/ipvs/Makefile @@ -4,6 +4,7 @@ # IPVS transport protocol load balancing support ip_vs_proto-objs-y := +ip_vs_proto-objs-$(CONFIG_IP_VS_PROTO_ICMP) += ip_vs_proto_icmp.o ip_vs_proto-objs-$(CONFIG_IP_VS_PROTO_TCP) += ip_vs_proto_tcp.o ip_vs_proto-objs-$(CONFIG_IP_VS_PROTO_UDP) += ip_vs_proto_udp.o ip_vs_proto-objs-$(CONFIG_IP_VS_PROTO_AH_ESP) += ip_vs_proto_ah_esp.o @@ -29,6 +30,7 @@ obj-$(CONFIG_IP_VS_DH) += ip_vs_dh.o obj-$(CONFIG_IP_VS_SH) += ip_vs_sh.o obj-$(CONFIG_IP_VS_SED) += ip_vs_sed.o obj-$(CONFIG_IP_VS_NQ) += ip_vs_nq.o +obj-$(CONFIG_IP_VS_SNAT_SCHED) += ip_vs_snat_sched.o # IPVS application helpers obj-$(CONFIG_IP_VS_FTP) += ip_vs_ftp.o diff --git a/kernel/net/netfilter/ipvs/ip_vs_conn.c b/kernel/net/netfilter/ipvs/ip_vs_conn.c index b5370ca3..9448b727 100644 --- a/kernel/net/netfilter/ipvs/ip_vs_conn.c +++ b/kernel/net/netfilter/ipvs/ip_vs_conn.c @@ -429,6 +429,9 @@ static inline void ip_vs_bind_xmit(struct ip_vs_conn *cp) break; case IP_VS_CONN_F_FULLNAT: + if (IS_SNAT_CP(cp)) + cp->packet_xmit = ip_vs_snat_out_xmit; + else cp->packet_xmit = ip_vs_fnat_xmit; break; @@ -648,6 +651,59 @@ static struct ip_vs_laddr *ip_vs_get_laddr(struct ip_vs_service *svc) return local; } +/* + * get a local address from given dest + */ +static int +ip_vs_get_laddr_snat(struct ip_vs_conn *cp, + struct ip_vs_laddr *local) +{ + struct ip_vs_dest_snat *rule = (struct ip_vs_dest_snat *)cp->dest; + u32 minip, maxip, j; + u32 k2, k3; + + if (cp->af != AF_INET) + return 1; + + if (!rule || !local) + return 1; + + atomic64_set(&local->port, cp->cport); + + if (rule->minip.ip == 0 || rule->maxip.ip == 0) + return 1; + + if (rule->minip.ip == rule->maxip.ip) { + local->addr.ip = rule->minip.ip; + return 0; + } + + switch (rule->ip_sel_algo) { + case IPVS_SNAT_IPS_PERSITENT: + k2 = 0; + k3 = 0; + break; + + case IPVS_SNAT_IPS_RANDOM: + k2 = (__force u32)cp->vaddr.ip; + k3 = ((__force u32)cp->cport) << 16 | (__force u32)cp->cport; + break; + + default: + k2 = (__force u32)cp->vaddr.ip; + k3 = 0; + break; + } + + minip = ntohl(rule->minip.ip); + maxip = ntohl(rule->maxip.ip); + + j = jhash_3words((__force u32)cp->caddr.ip, k2, k3, 0); + j = ((u64)j * (maxip - minip + 1)) >> 32; + local->addr.ip = htonl(minip + j); + return 0; +} + /* * Bind a connection entry with a local address * and hashed it in connection table. @@ -659,6 +715,7 @@ static inline int ip_vs_hbind_laddr(struct ip_vs_conn *cp) struct ip_vs_dest *dest = cp->dest; struct ip_vs_service *svc = dest->svc; struct ip_vs_laddr *local; + struct ip_vs_laddr snat_local; int ret = 0; int remaining, i, tport, hit = 0; unsigned ihash, ohash; @@ -695,7 +752,14 @@ static inline int ip_vs_hbind_laddr(struct ip_vs_conn *cp) * fwd methods: IP_VS_CONN_F_FULLNAT */ /* choose a local address by round-robin */ - local = ip_vs_get_laddr(svc); + if (IS_SNAT_SVC(svc)) { + if (ip_vs_get_laddr_snat(cp, &snat_local) == 0) + local = &snat_local; + else + local = NULL; + } else + local = ip_vs_get_laddr(svc); + if (local != NULL) { /*OUTside2INside: hashed by client address and port, virtual address and port */ ihash = @@ -703,6 +767,7 @@ static inline int ip_vs_hbind_laddr(struct ip_vs_conn *cp) &cp->vaddr, cp->vport); /* increase the refcnt counter of the local address */ + if (NOT_SNAT_SVC(svc)) ip_vs_laddr_hold(local); ip_vs_addr_copy(cp->af, &cp->out_idx->d_addr, &local->addr); ip_vs_addr_copy(cp->af, &cp->laddr, &local->addr); @@ -712,7 +777,7 @@ static inline int ip_vs_hbind_laddr(struct ip_vs_conn *cp) tport = sysctl_ip_vs_lport_min + atomic64_inc_return(&local->port) % remaining; - cp->out_idx->d_port = cp->lport = htons(tport); + cp->out_idx->d_port = cp->lport = (IPPROTO_ICMP != cp->protocol) ? htons(tport) : cp->cport; /* init hit everytime before lookup the tuple */ hit = 0; @@ -737,16 +802,21 @@ static inline int ip_vs_hbind_laddr(struct ip_vs_conn *cp) && cp->lport == cidx->d_port && cp->protocol == cidx->protocol) { /* HIT */ + if (NOT_SNAT_SVC(svc)) atomic64_inc(&local->port_conflict); hit = 1; break; } } if (hit == 0) { + if (NOT_SNAT_SVC(svc)) cp->local = local; + else + cp->local = NULL; /* hashed */ __ip_vs_conn_hash(cp, ihash, ohash); ip_vs_conn_unlock2(ihash, ohash); + if (NOT_SNAT_SVC(svc)) atomic_inc(&local->conn_counts); ret = 1; goto out; @@ -754,6 +824,7 @@ static inline int ip_vs_hbind_laddr(struct ip_vs_conn *cp) ip_vs_conn_unlock2(ihash, ohash); } if (ret == 0) { + if (NOT_SNAT_SVC(svc)) ip_vs_laddr_put(local); } } @@ -962,6 +1033,9 @@ static void ip_vs_conn_expire(unsigned long data) if (cp->indev != NULL) dev_put(cp->indev); + if (cp->dev_inside != NULL) + dev_put(cp->dev_inside); + kmem_cache_free(ip_vs_conn_cachep, cp); return; } diff --git a/kernel/net/netfilter/ipvs/ip_vs_core.c b/kernel/net/netfilter/ipvs/ip_vs_core.c index b3128798..63779c7d 100644 --- a/kernel/net/netfilter/ipvs/ip_vs_core.c +++ b/kernel/net/netfilter/ipvs/ip_vs_core.c @@ -346,6 +346,15 @@ struct ip_vs_conn *ip_vs_schedule(struct ip_vs_service *svc, /* * Create a connection entry. */ + + if (IS_SNAT_SVC(svc)) + cp = ip_vs_conn_new(svc->af, iph.protocol, + &iph.saddr, pptr[0], + &iph.daddr, pptr[1], + &iph.daddr, pptr[1], + ip_vs_onepacket_enabled(svc, &iph), + dest, skb, is_synproxy_on); + else cp = ip_vs_conn_new(svc->af, iph.protocol, &iph.saddr, pptr[0], &iph.daddr, pptr[1], @@ -745,7 +754,7 @@ handle_response(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, /* * Syn-proxy step 3 logic: receive syn-ack from rs. */ - if (ip_vs_synproxy_synack_rcv(skb, cp, pp, ihl, &ret) == 0) { + if (pp->protocol == IPPROTO_TCP && ip_vs_synproxy_synack_rcv(skb, cp, pp, ihl, &ret) == 0) { goto out; } @@ -779,6 +788,49 @@ handle_response(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, return ret; } +static unsigned int +ip_vs_snat_out(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, + int *v, struct ip_vs_conn *cp) +{ + if (af != AF_INET) + return 1; + + if (cp && NOT_SNAT_CP(cp)) + return 1; + + EnterFunction(11); + if (!cp) { + skb->mark = 1; + if (!pp->conn_schedule(af, skb, pp, v, &cp)) + return 0; + + if (unlikely(!cp)) { + /* sorry, all this trouble for a no-hit :) */ + IP_VS_DBG_PKT(12, pp, skb, 0, + "packet continues traversal as normal"); + *v = NF_ACCEPT; + return 0; + } + } + + IP_VS_DBG_PKT(11, pp, skb, 0, "Forward packet"); + ip_vs_in_stats(cp, skb); + + ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pp); + + if (cp->packet_xmit) + *v = cp->packet_xmit(skb, cp, pp); + /* do not touch skb anymore */ + else { + IP_VS_DBG_RL("warning: packet_xmit is null"); + *v = NF_ACCEPT; + } + + cp->old_state = cp->state; + ip_vs_conn_put(cp); + return 0; +} + /* * It is hooked at the NF_INET_FORWARD chain, used only for VS/NAT. * Check if outgoing packet belongs to the established ip_vs_conn. @@ -793,6 +845,7 @@ ip_vs_out(unsigned int hooknum, struct sk_buff *skb, struct ip_vs_conn *cp; int af; int res_dir; + int verdict; EnterFunction(11); @@ -851,6 +904,10 @@ ip_vs_out(unsigned int hooknum, struct sk_buff *skb, */ cp = pp->conn_out_get(af, skb, pp, &iph, iph.len, 0, &res_dir); + if (0 == ip_vs_snat_out(af, skb, pp, &verdict, cp)) { + return verdict; + } + if (unlikely(!cp)) { if (sysctl_ip_vs_nat_icmp_send && (pp->protocol == IPPROTO_TCP || @@ -1304,12 +1361,19 @@ ip_vs_pre_routing(unsigned int hooknum, struct sk_buff *skb, ip_vs_fill_iphdr(af, skb_network_header(skb), &iph); /* drop all ip fragment except ospf */ - if ((sysctl_ip_vs_frag_drop_entry == 1) - && (af == AF_INET) + if ((af == AF_INET) && (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) && (iph.protocol != IPPROTO_OSPF)) { + if(sysctl_ip_vs_frag_drop_entry == 1) { IP_VS_INC_ESTATS(ip_vs_esmib, DEFENCE_IP_FRAG_DROP); return NF_DROP; + } else { + if (ip_vs_gather_frags(skb, IP_DEFRAG_VS_IN)) + return NF_STOLEN; + + IP_VS_INC_ESTATS(ip_vs_esmib, DEFENCE_IP_FRAG_GATHER); + ip_vs_fill_iphdr(af, skb_network_header(skb), &iph); + } } /* drop udp packet which send to tcp-vip */ diff --git a/kernel/net/netfilter/ipvs/ip_vs_ctl.c b/kernel/net/netfilter/ipvs/ip_vs_ctl.c index c7adc827..d9250740 100644 --- a/kernel/net/netfilter/ipvs/ip_vs_ctl.c +++ b/kernel/net/netfilter/ipvs/ip_vs_ctl.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -150,6 +151,8 @@ int sysctl_ip_vs_udp_drop_entry = 1; int sysctl_ip_vs_conn_expire_tcp_rst = 1; /* L2 fast xmit, response only (to client) */ int sysctl_ip_vs_fast_xmit = 1; +/* L2 fast xmit, inside (to RS) */ +int sysctl_ip_vs_fast_xmit_inside = 1; #ifdef CONFIG_IP_VS_DEBUG static int sysctl_ip_vs_debug_level = 0; @@ -675,6 +678,40 @@ struct ip_vs_dest *ip_vs_lookup_real_service(int af, __u16 protocol, return NULL; } +/** + * Lookup snat desp by {saddr, smask, daddr, dmask, gw, outdev} in the given service + */ +static struct ip_vs_dest_snat *ip_vs_lookup_snat_dest(struct ip_vs_service *svc, + const union nf_inet_addr *saddr, + u32 smask, + const union nf_inet_addr *daddr, + u32 dmask, + const union nf_inet_addr* gw, + char *out_dev) +{ + struct ip_vs_dest *pure_dest; + struct ip_vs_dest_snat *snat_dest; + + EnterFunction(2); + if (IS_SNAT_SVC(svc)) { + list_for_each_entry(pure_dest, &svc->destinations, n_list) { + snat_dest = (struct ip_vs_dest_snat *)pure_dest; + if ((snat_dest->dest.af == svc->af) + && ip_vs_addr_equal(svc->af, &snat_dest->saddr, saddr) + && ip_vs_addr_equal(svc->af, &snat_dest->daddr, daddr) + && inet_mask_len(snat_dest->smask.ip) == smask + && inet_mask_len(snat_dest->dmask.ip) == dmask + && ip_vs_addr_equal(svc->af, &pure_dest->addr, gw) + && !strcmp(snat_dest->out_dev, out_dev)) { + LeaveFunction(2); + return snat_dest; + } + } + } + + return NULL; +} + /* * Lookup destination by {addr,port} in the given service */ @@ -727,6 +764,62 @@ struct ip_vs_dest *ip_vs_find_dest(int af, const union nf_inet_addr *daddr, return dest; } +static struct ip_vs_dest_snat *ip_vs_trash_get_snat_dest(struct ip_vs_service *svc, + const union nf_inet_addr *saddr, + u32 smask, + const union nf_inet_addr *daddr, + u32 dmask, + const union nf_inet_addr *gw, + char* out_dev) +{ + struct ip_vs_dest *dest, *nxt; + struct ip_vs_dest_snat *snat_dest = NULL; + + + EnterFunction(2); + /* Find the snat destination in trash */ + list_for_each_entry_safe(dest, nxt, &ip_vs_dest_trash, n_list) { + IP_VS_DBG_BUF(3, "Destination %u/%s:%u still in trash, " + "dest->refcnt=%d\n", + dest->vfwmark, + IP_VS_DBG_ADDR(svc->af, &dest->addr), + ntohs(dest->port), atomic_read(&dest->refcnt)); + + if (dest->svc && IS_SNAT_SVC(dest->svc)) { + snat_dest = (struct ip_vs_dest_snat *)dest; + if (dest->vfwmark == svc->fwmark /* the same service */ + && (snat_dest->dest.af == svc->af) + && ip_vs_addr_equal(svc->af, &snat_dest->saddr, saddr) + && ip_vs_addr_equal(svc->af, &snat_dest->daddr, daddr) + && inet_mask_len(snat_dest->smask.ip) == smask + && inet_mask_len(snat_dest->dmask.ip) == dmask + && ip_vs_addr_equal(svc->af, &dest->addr, gw) + && !strcmp(snat_dest->out_dev, out_dev)) { + return snat_dest; + } + } +/* + * Try to purge the destination from trash if not referenced + */ + if (atomic_read(&dest->refcnt) == 1) { + IP_VS_DBG_BUF(3, "Removing destination %u/%s:%u from trash\n", + dest->vfwmark, + IP_VS_DBG_ADDR(svc->af, &dest->addr), + ntohs(dest->port)); + list_del(&dest->n_list); + ip_vs_dst_reset(dest); + __ip_vs_unbind_svc(dest); + + /* Delete dest dedicated statistic varible which is percpu type */ + ip_vs_del_stats(dest->stats); + kfree(dest); + } + } + + return NULL; +} + + /* * Lookup dest by {svc,addr,port} in the destination trash. * The destination trash is used to hold the destinations that are removed @@ -809,6 +902,42 @@ static void ip_vs_trash_cleanup(void) } } +/* + * Update snat rule part of a snat dest + */ +static void __ip_vs_update_snat_dest(struct ip_vs_service *svc, + struct ip_vs_dest_snat *snat_dest, + struct ip_vs_snat_dest_user_kern *udest) +{ + union nf_inet_addr tmp; + + EnterFunction(2); + ip_vs_addr_copy(svc->af, &snat_dest->saddr, &udest->saddr); + tmp.ip = inet_make_mask(udest->smask); + ip_vs_addr_copy(svc->af, &snat_dest->smask, &tmp); + + ip_vs_addr_copy(svc->af, &snat_dest->daddr, &udest->daddr); + tmp.ip = inet_make_mask(udest->dmask); + ip_vs_addr_copy(svc->af, &snat_dest->dmask, &tmp); + + //ip_vs_addr_copy(svc->af, &snat_dest->gateway, &udest->gw); + + ip_vs_addr_copy(svc->af, &snat_dest->minip, &udest->minip); + + ip_vs_addr_copy(svc->af, &snat_dest->maxip, &udest->maxip); + + ip_vs_addr_copy(svc->af, &snat_dest->new_gateway, &udest->new_gw); + + snat_dest->ip_sel_algo = (u8)udest->algo; + + strcpy(snat_dest->out_dev, udest->out_dev); + + memset(snat_dest->out_dev_mask, 0, sizeof(snat_dest->out_dev_mask)); + memset(snat_dest->out_dev_mask, 0xFF, strlen(snat_dest->out_dev)); /* fix me */ + LeaveFunction(2); +} + + /* * Update a destination in the given service */ @@ -868,6 +997,7 @@ __ip_vs_update_dest(struct ip_vs_service *svc, dest->flags &= ~IP_VS_DEST_F_OVERLOAD; dest->u_threshold = udest->u_threshold; dest->l_threshold = udest->l_threshold; + } /* @@ -888,19 +1018,30 @@ ip_vs_new_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest, atype = ipv6_addr_type(&udest->addr.in6); if ((!(atype & IPV6_ADDR_UNICAST) || atype & IPV6_ADDR_LINKLOCAL) && - !__ip_vs_addr_is_local_v6(&udest->addr.in6)) + !__ip_vs_addr_is_local_v6(&udest->addr.in6)) { + IP_VS_ERR_RL("AF_INET6 address type error.\n"); return -EINVAL; + } } else #endif { + if (udest->addr.ip != 0) { atype = inet_addr_type(&init_net, udest->addr.ip); - if (atype != RTN_LOCAL && atype != RTN_UNICAST) + if (atype != RTN_LOCAL && atype != RTN_UNICAST) { + IP_VS_ERR_RL("AF_INET address type error.\n"); return -EINVAL; } + } + } + if (NOT_SNAT_SVC(svc)) { dest = kzalloc(sizeof(struct ip_vs_dest), GFP_ATOMIC); + } else { + dest = kzalloc(sizeof(struct ip_vs_dest_snat), GFP_ATOMIC); + } + if (dest == NULL) { - pr_err("%s(): no memory.\n", __func__); + IP_VS_ERR_RL(" no memory.\n"); return -ENOMEM; } @@ -917,17 +1058,22 @@ ip_vs_new_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest, atomic_set(&dest->persistconns, 0); atomic_set(&dest->refcnt, 0); + if (IS_SNAT_SVC(svc)) { + struct ip_vs_dest_snat *snat_dest = (struct ip_vs_dest_snat *)dest; + INIT_LIST_HEAD(&snat_dest->rule_list); + } INIT_LIST_HEAD(&dest->d_list); spin_lock_init(&dest->dst_lock); /* Init statistic */ ret = ip_vs_new_stats(&(dest->stats)); - if(ret) + if (ret) { + IP_VS_ERR_RL("ip_vs_new_stats fail [%d]\n", ret); goto out_err; + } __ip_vs_update_dest(svc, dest, udest); - *dest_p = dest; LeaveFunction(2); @@ -938,6 +1084,149 @@ ip_vs_new_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest, return ret; } +/* + * Create a snat destination for the given service + */ +static int +ip_vs_new_snat_dest(struct ip_vs_service *svc, + struct ip_vs_snat_dest_user_kern *udest, + struct ip_vs_dest_snat **dest_p) +{ + int ret = 0; + struct ip_vs_dest_user_kern pure_dest; + EnterFunction(2); + memset(&pure_dest, 0, sizeof(pure_dest)); + pure_dest.conn_flags = udest->conn_flags; + /* udest->saddr or udest->daddr may be net address, not host ip address */ + ip_vs_addr_copy(svc->af, &pure_dest.addr, &udest->gw); + ret = ip_vs_new_dest(svc, &pure_dest, (struct ip_vs_dest **)dest_p); + if (ret) { + IP_VS_ERR_RL("[snat] ip_vs_new_dest failed, [%d]\n", ret); + return ret; + } + __ip_vs_update_snat_dest(svc, *dest_p, udest); + LeaveFunction(2); + return 0; +} + + +/** + * add a snat dest into an existing service + */ +static int +ip_vs_add_snat_dest(struct ip_vs_service *svc, + struct ip_vs_snat_dest_user_kern *usnat_dest_data) +{ + int ret; + struct ip_vs_dest_snat *snat_dest; + struct ip_vs_dest *pure_dest; + union nf_inet_addr saddr; + union nf_inet_addr daddr; + union nf_inet_addr gw; + u32 smask, dmask; + char out_dev[IP_VS_IFNAME_MAXLEN] = {0}; + + struct ip_vs_dest_user_kern tmp_dest; + + EnterFunction(2); + if (NOT_SNAT_SVC(svc)) { + IP_VS_ERR_RL("[snat] isn't snat service\n"); + return -EINVAL; + } + + ip_vs_addr_copy(svc->af, &saddr, &usnat_dest_data->saddr); + smask = usnat_dest_data->smask; + ip_vs_addr_copy(svc->af, &daddr, &usnat_dest_data->daddr); + dmask = usnat_dest_data->dmask; + ip_vs_addr_copy(svc->af, &gw, &usnat_dest_data->gw); + strcpy(out_dev, usnat_dest_data->out_dev); + /* Check if the dest already exists in the list */ + snat_dest = ip_vs_lookup_snat_dest(svc, &saddr, smask, &daddr, dmask, &gw, out_dev); + if (snat_dest != NULL) { + IP_VS_ERR_RL("[snat] snat dest already exists\n"); + return -EEXIST; + } + + /* + * Check if the dest already exists in the trash and + * is from the same service + */ + snat_dest = ip_vs_trash_get_snat_dest(svc, &saddr, smask, &daddr, dmask, &gw, out_dev); + if (snat_dest != NULL) { + pure_dest = (struct ip_vs_dest *)snat_dest; + IP_VS_DBG_BUF(3, "Get snat destination -F %s/%u -T %s/%u -W %s --oif %s from trash, " + "dest->refcnt=%d, service -f [%u]\n", + IP_VS_DBG_ADDR(svc->af, &saddr), smask, + IP_VS_DBG_ADDR(svc->af, &daddr), dmask, + IP_VS_DBG_ADDR(svc->af, &gw), + out_dev, + atomic_read(&pure_dest->refcnt), + pure_dest->vfwmark); + + memset(&tmp_dest, 0, sizeof(tmp_dest)); + /* set connection flag to ip_vs_dest.conn_flags */ + tmp_dest.conn_flags = usnat_dest_data->conn_flags; + /* set gateway address to ip_vs_dest.addr */ + ip_vs_addr_copy(svc->af, &tmp_dest.addr, &usnat_dest_data->gw); + /* update pure dest parts */ + __ip_vs_update_dest(svc, pure_dest, &tmp_dest); + /* update snat rule dest parts */ + __ip_vs_update_snat_dest(svc, snat_dest, usnat_dest_data); + + /* Get the destination from the trash */ + list_del(&pure_dest->n_list); + + /* Reset the statistic value */ + ip_vs_zero_stats(pure_dest->stats); + write_lock_bh(&__ip_vs_svc_lock); + /* Wait until all other svc users go away.*/ + IP_VS_WAIT_WHILE(atomic_read(&svc->usecnt) > 1); + list_add(&pure_dest->n_list, &svc->destinations); + svc->num_dests++; + + /* call the update_service function of its scheduler */ + if (svc->scheduler->update_service) + svc->scheduler->update_service(svc); + + write_unlock_bh(&__ip_vs_svc_lock); + LeaveFunction(2); + return 0; + } + + /* + * Allocate and initialize the dest structure + */ + ret = ip_vs_new_snat_dest(svc, usnat_dest_data, &snat_dest); + if (ret) { + return ret; + } + pure_dest = (struct ip_vs_dest *)snat_dest; + /* + * Add the dest entry into the list + */ + atomic_inc(&pure_dest->refcnt); + + write_lock_bh(&__ip_vs_svc_lock); + + /* + * Wait until all other svc users go away. + */ + IP_VS_WAIT_WHILE(atomic_read(&svc->usecnt) > 1); + + list_add(&pure_dest->n_list, &svc->destinations); + svc->num_dests++; + + /* call the update_service function of its scheduler */ + if (svc->scheduler->update_service) + svc->scheduler->update_service(svc); + + write_unlock_bh(&__ip_vs_svc_lock); + + LeaveFunction(2); + + return 0; +} + /* * Add a destination into an existing service */ @@ -1051,6 +1340,59 @@ ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest) return 0; } + +/* + * Edit a snat destination in the given service + */ +static int +ip_vs_edit_snat_dest(struct ip_vs_service *svc, + struct ip_vs_snat_dest_user_kern *usnat_dest_data) +{ + struct ip_vs_dest_snat *snat_dest; + struct ip_vs_dest *pure_dest; + union nf_inet_addr daddr; + union nf_inet_addr saddr; + union nf_inet_addr gw; + char out_dev[IP_VS_IFNAME_MAXLEN] = {0}; + struct ip_vs_dest_user_kern tmp_pure_dest; + + u32 dmask = usnat_dest_data->dmask; + u32 smask = usnat_dest_data->smask; + + EnterFunction(2); + ip_vs_addr_copy(svc->af, &saddr, &usnat_dest_data->saddr); + ip_vs_addr_copy(svc->af, &daddr, &usnat_dest_data->daddr); + ip_vs_addr_copy(svc->af, &gw, &usnat_dest_data->gw); + strcpy(out_dev, usnat_dest_data->out_dev); + + /* Lookup the destination list */ + snat_dest = ip_vs_lookup_snat_dest(svc, &saddr, smask, &daddr, dmask, &gw, out_dev); + if (snat_dest == NULL) { + IP_VS_ERR_RL("[snat] dest doesn't exist\n"); + return -ENOENT; + } + pure_dest = (struct ip_vs_dest *)snat_dest; + memset(&tmp_pure_dest, 0, sizeof(tmp_pure_dest)); + tmp_pure_dest.conn_flags = usnat_dest_data->conn_flags; + __ip_vs_update_dest(svc, pure_dest, &tmp_pure_dest); + __ip_vs_update_snat_dest(svc, snat_dest, usnat_dest_data); + + write_lock_bh(&__ip_vs_svc_lock); + + /* Wait until all other svc users go away */ + IP_VS_WAIT_WHILE(atomic_read(&svc->usecnt) > 1); + + /* call the update_service, because server weight may be changed */ + if (svc->scheduler->update_service) + svc->scheduler->update_service(svc); + + write_unlock_bh(&__ip_vs_svc_lock); + + LeaveFunction(2); + + return 0; +} + /* * Edit a destination in the given service */ @@ -1165,6 +1507,63 @@ static void __ip_vs_unlink_dest(struct ip_vs_service *svc, svc->scheduler->update_service(svc); } +/* + * Delete a snat destination server in the given service + */ +static int +ip_vs_del_snat_dest(struct ip_vs_service *svc, + struct ip_vs_snat_dest_user_kern *usnat_dest_data) +{ + struct ip_vs_dest_snat *snat_dest; + struct ip_vs_dest *pure_dest; + union nf_inet_addr daddr; + union nf_inet_addr saddr; + union nf_inet_addr gw; + char out_dev[IP_VS_IFNAME_MAXLEN] = {0}; + + u32 dmask = usnat_dest_data->dmask; + u32 smask = usnat_dest_data->smask; + + EnterFunction(2); + ip_vs_addr_copy(svc->af, &saddr, &usnat_dest_data->saddr); + ip_vs_addr_copy(svc->af, &daddr, &usnat_dest_data->daddr); + ip_vs_addr_copy(svc->af, &gw, &usnat_dest_data->gw); + strcpy(out_dev, usnat_dest_data->out_dev); + + /* + * Lookup the destination list + */ + snat_dest = ip_vs_lookup_snat_dest(svc, &saddr, smask, &daddr, dmask, &gw, out_dev); + if (snat_dest == NULL) { + IP_VS_ERR_RL("[snat] snat dest not exist\n"); + return -ENOENT; + } + pure_dest = (struct ip_vs_dest *)snat_dest; + + write_lock_bh(&__ip_vs_svc_lock); + + /* + * Wait until all other svc users go away. + */ + IP_VS_WAIT_WHILE(atomic_read(&svc->usecnt) > 1); + + /* + * Unlink dest from the service + */ + __ip_vs_unlink_dest(svc, pure_dest, 1); + + write_unlock_bh(&__ip_vs_svc_lock); + + /* + * Delete the destination + */ + __ip_vs_del_dest(pure_dest); + + LeaveFunction(2); + + return 0; +} + /* * Delete a destination server in the given service */ @@ -1179,7 +1578,7 @@ ip_vs_del_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest) dest = ip_vs_lookup_dest(svc, &udest->addr, dport); if (dest == NULL) { - IP_VS_DBG(1, "%s(): destination not found!\n", __func__); + IP_VS_ERR_RL(" dest not exist\n"); return -ENOENT; } @@ -2199,6 +2598,16 @@ static struct ctl_table vs_vars[] = { .extra1 = &ip_vs_entry_min, /* zero */ .extra2 = &ip_vs_entry_max, /* one */ }, + { + .procname = "fast_response_xmit_inside", + .data = &sysctl_ip_vs_fast_xmit_inside, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = &proc_dointvec_minmax, + .strategy = &sysctl_intvec, + .extra1 = &ip_vs_entry_min, /* zero */ + .extra2 = &ip_vs_entry_max, /* one */ + }, {.ctl_name = 0} }; @@ -2519,14 +2928,19 @@ static struct ip_vs_estats_entry ext_stats[] = { IP_VS_ESTATS_ITEM("synproxy_conn_reused_lastack", SYNPROXY_CONN_REUSED_LASTACK), IP_VS_ESTATS_ITEM("defence_ip_frag_drop", DEFENCE_IP_FRAG_DROP), + IP_VS_ESTATS_ITEM("defence_ip_frag_gather", DEFENCE_IP_FRAG_GATHER), IP_VS_ESTATS_ITEM("defence_tcp_drop", DEFENCE_TCP_DROP), IP_VS_ESTATS_ITEM("defence_udp_drop", DEFENCE_UDP_DROP), IP_VS_ESTATS_ITEM("fast_xmit_reject", FAST_XMIT_REJECT), IP_VS_ESTATS_ITEM("fast_xmit_pass", FAST_XMIT_PASS), + IP_VS_ESTATS_ITEM("fast_xmit_failed", FAST_XMIT_FAILED), IP_VS_ESTATS_ITEM("fast_xmit_skb_copy", FAST_XMIT_SKB_COPY), IP_VS_ESTATS_ITEM("fast_xmit_no_mac", FAST_XMIT_NO_MAC), IP_VS_ESTATS_ITEM("fast_xmit_synproxy_save", FAST_XMIT_SYNPROXY_SAVE), IP_VS_ESTATS_ITEM("fast_xmit_dev_lost", FAST_XMIT_DEV_LOST), + IP_VS_ESTATS_ITEM("fast_xmit_reject_inside", FAST_XMIT_REJECT_INSIDE), + IP_VS_ESTATS_ITEM("fast_xmit_pass_inside", FAST_XMIT_PASS_INSIDE), + IP_VS_ESTATS_ITEM("fast_xmit_failed_inside", FAST_XMIT_FAILED_INSIDE), IP_VS_ESTATS_ITEM("rst_in_syn_sent", RST_IN_SYN_SENT), IP_VS_ESTATS_ITEM("rst_out_syn_sent", RST_OUT_SYN_SENT), IP_VS_ESTATS_ITEM("rst_in_established", RST_IN_ESTABLISHED), @@ -3219,6 +3633,7 @@ static const struct nla_policy ip_vs_cmd_policy[IPVS_CMD_ATTR_MAX + 1] = { [IPVS_CMD_ATTR_TIMEOUT_TCP_FIN] = {.type = NLA_U32}, [IPVS_CMD_ATTR_TIMEOUT_UDP] = {.type = NLA_U32}, [IPVS_CMD_ATTR_LADDR] = {.type = NLA_NESTED}, + [IPVS_CMD_ATTR_SNATDEST] = {.type = NLA_NESTED}, }; /* Policy used for attributes in nested attribute IPVS_CMD_ATTR_DAEMON */ @@ -3259,8 +3674,32 @@ 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_PERSIST_CONNS] = {.type = NLA_U32}, [IPVS_DEST_ATTR_STATS] = {.type = NLA_NESTED}, + [IPVS_DEST_ATTR_SNATRULE] = {.type = NLA_NESTED}, +}; + +/* Policy used for attributes in nested attribute IPVS_CMD_ATTR_SNAT_DEAST */ +static const struct nla_policy ip_vs_snat_dest_policy[IPVS_SNAT_DEST_ATTR_MAX + 1] = { + [IPVS_SNAT_DEST_ATTR_FADDR] = {.type = NLA_BINARY, + .len = sizeof(union nf_inet_addr)}, + [IPVS_SNAT_DEST_ATTR_FMASK] = {.type = NLA_U32}, + [IPVS_SNAT_DEST_ATTR_DADDR] = {.type = NLA_BINARY, + .len = sizeof(union nf_inet_addr)}, + [IPVS_SNAT_DEST_ATTR_DMASK] = {.type = NLA_U32}, + [IPVS_SNAT_DEST_ATTR_GW] = {.type = NLA_BINARY, + .len = sizeof(union nf_inet_addr)}, + [IPVS_SNAT_DEST_ATTR_MINIP] = {.type = NLA_BINARY, + .len = sizeof(union nf_inet_addr)}, + [IPVS_SNAT_DEST_ATTR_MAXIP] = {.type = NLA_BINARY, + .len = sizeof(union nf_inet_addr)}, + [IPVS_SNAT_DEST_ATTR_ALGO] = {.type = NLA_U8}, + [IPVS_SNAT_DEST_ATTR_NEWGW] = {.type = NLA_BINARY, + .len = sizeof(union nf_inet_addr)}, + [IPVS_SNAT_DEST_ATTR_CONNFLAG] = {.type = NLA_U32}, + [IPVS_SNAT_DEST_ATTR_OUTDEV] = {.type = NLA_STRING, + .len = IP_VS_IFNAME_MAXLEN}, }; + static const struct nla_policy ip_vs_laddr_policy[IPVS_LADDR_ATTR_MAX + 1] = { [IPVS_LADDR_ATTR_ADDR] = {.type = NLA_BINARY, .len = sizeof(union nf_inet_addr)}, @@ -3268,6 +3707,38 @@ static const struct nla_policy ip_vs_laddr_policy[IPVS_LADDR_ATTR_MAX + 1] = { [IPVS_LADDR_ATTR_CONN_COUNTS] = {.type = NLA_U32}, }; +static int ip_vs_genl_fill_snat_rule(struct sk_buff *skb, int container_type, + struct ip_vs_dest_snat *snat_dest) +{ + struct ip_vs_dest *udest = (struct ip_vs_dest *)snat_dest; + struct nlattr *nl_stats = nla_nest_start(skb, container_type); + EnterFunction(2); + if (!nl_stats) { + IP_VS_ERR_RL("nl_stats == NULL.\n"); + return -EMSGSIZE; + } + + NLA_PUT(skb, IPVS_SNAT_DEST_ATTR_FADDR, sizeof(snat_dest->saddr), &snat_dest->saddr); + NLA_PUT_U32(skb, IPVS_SNAT_DEST_ATTR_FMASK, inet_mask_len(snat_dest->smask.ip)); + NLA_PUT(skb, IPVS_SNAT_DEST_ATTR_DADDR, sizeof(snat_dest->saddr), &snat_dest->daddr); + NLA_PUT_U32(skb, IPVS_SNAT_DEST_ATTR_DMASK, inet_mask_len(snat_dest->dmask.ip)); + NLA_PUT(skb, IPVS_SNAT_DEST_ATTR_GW, sizeof(udest->addr), &udest->addr); + NLA_PUT(skb, IPVS_SNAT_DEST_ATTR_MINIP, sizeof(snat_dest->minip), &snat_dest->minip); + NLA_PUT(skb, IPVS_SNAT_DEST_ATTR_MAXIP, sizeof(snat_dest->maxip), &snat_dest->maxip); + NLA_PUT_U8(skb, IPVS_SNAT_DEST_ATTR_ALGO, snat_dest->ip_sel_algo); + NLA_PUT(skb, IPVS_SNAT_DEST_ATTR_NEWGW, sizeof(snat_dest->new_gateway), &snat_dest->new_gateway); + NLA_PUT_U32(skb, IPVS_SNAT_DEST_ATTR_CONNFLAG, atomic_read(&snat_dest->dest.conn_flags)); + NLA_PUT_STRING(skb, IPVS_SNAT_DEST_ATTR_OUTDEV, snat_dest->out_dev); + + nla_nest_end(skb, nl_stats); + LeaveFunction(2); + return 0; + +nla_put_failure: + nla_nest_cancel(skb, nl_stats); + return -EMSGSIZE; +} + static int ip_vs_genl_fill_stats(struct sk_buff *skb, int container_type, struct ip_vs_stats *stats) { @@ -3501,13 +3972,15 @@ static struct ip_vs_service *ip_vs_genl_find_service(struct nlattr *nla) &usvc.addr, usvc.port); } -static int ip_vs_genl_fill_dest(struct sk_buff *skb, struct ip_vs_dest *dest) +static int ip_vs_genl_fill_dest(struct sk_buff *skb, struct ip_vs_dest *dest, int is_snat) { struct nlattr *nl_dest; + EnterFunction(2); nl_dest = nla_nest_start(skb, IPVS_CMD_ATTR_DEST); - if (!nl_dest) + if (!nl_dest) { return -EMSGSIZE; + } NLA_PUT(skb, IPVS_DEST_ATTR_ADDR, sizeof(dest->addr), &dest->addr); NLA_PUT_U16(skb, IPVS_DEST_ATTR_PORT, dest->port); @@ -3524,11 +3997,20 @@ static int ip_vs_genl_fill_dest(struct sk_buff *skb, struct ip_vs_dest *dest) NLA_PUT_U32(skb, IPVS_DEST_ATTR_PERSIST_CONNS, atomic_read(&dest->persistconns)); - 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; + } - nla_nest_end(skb, nl_dest); + if (is_snat) { + struct ip_vs_dest_snat* snat_dest = (struct ip_vs_dest_snat *)dest; + if (ip_vs_genl_fill_snat_rule(skb, IPVS_DEST_ATTR_SNATRULE, snat_dest)) { + IP_VS_ERR_RL(" ip_vs_genl_fill_snat_rule error.\n"); + goto nla_put_failure; + } + } + nla_nest_end(skb, nl_dest); + LeaveFunction(2); return 0; nla_put_failure: @@ -3537,18 +4019,22 @@ static int ip_vs_genl_fill_dest(struct sk_buff *skb, struct ip_vs_dest *dest) } static int ip_vs_genl_dump_dest(struct sk_buff *skb, struct ip_vs_dest *dest, - struct netlink_callback *cb) + struct netlink_callback *cb, int is_snat) { void *hdr; - + EnterFunction(2); hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq, &ip_vs_genl_family, NLM_F_MULTI, IPVS_CMD_NEW_DEST); - if (!hdr) + if (!hdr) { + IP_VS_ERR_RL("%s(): genlmsg_put error.\n", __func__); return -EMSGSIZE; + } - if (ip_vs_genl_fill_dest(skb, dest) < 0) + if (ip_vs_genl_fill_dest(skb, dest, is_snat) < 0) { + IP_VS_ERR_RL("%s(): ip_vs_genl_fill_dest error.\n", __func__); goto nla_put_failure; - + } + LeaveFunction(2); return genlmsg_end(skb, hdr); nla_put_failure: @@ -3560,6 +4046,7 @@ static int ip_vs_genl_dump_dests(struct sk_buff *skb, struct netlink_callback *cb) { int idx = 0; + int is_snat = 0; int start = cb->args[0]; struct ip_vs_service *svc; struct ip_vs_dest *dest; @@ -3569,18 +4056,24 @@ static int ip_vs_genl_dump_dests(struct sk_buff *skb, /* Try to find the service for which to dump destinations */ if (nlmsg_parse(cb->nlh, GENL_HDRLEN, attrs, - IPVS_CMD_ATTR_MAX, ip_vs_cmd_policy)) + IPVS_CMD_ATTR_MAX, ip_vs_cmd_policy)) { goto out_err; + } svc = ip_vs_genl_find_service(attrs[IPVS_CMD_ATTR_SERVICE]); - if (IS_ERR(svc) || svc == NULL) + if (IS_ERR(svc) || svc == NULL) { goto out_err; + } + + if (IS_SNAT_SVC(svc)) { + is_snat = 1; + } /* Dump the destinations */ list_for_each_entry(dest, &svc->destinations, n_list) { if (++idx <= start) continue; - if (ip_vs_genl_dump_dest(skb, dest, cb) < 0) { + if (ip_vs_genl_dump_dest(skb, dest, cb, is_snat) < 0) { idx--; goto nla_put_failure; } @@ -3704,6 +4197,83 @@ static int ip_vs_genl_parse_laddr(struct ip_vs_laddr_user_kern *uladdr, return 0; } +/* get snat dest info from ipvsadm tools */ +static int ip_vs_genl_parse_snat_dest(struct ip_vs_snat_dest_user_kern *usnat_dest, + struct nlattr* nla, int full_entry) +{ + struct nlattr *attrs[IPVS_SNAT_DEST_ATTR_MAX+ 1]; + struct nlattr *nal_saddr, *nal_daddr, *nal_smask, *nal_dmask; + struct nlattr *nal_gw, *nal_minip, *nal_maxip, *nal_algo, + *nal_newgw, *nal_conn_flags, *nal_out_dev; + int ret; + + EnterFunction(2); + if (NULL == nla) { + IP_VS_ERR_RL("[snat] nla == NULL\n"); + return -EINVAL; + } + + ret = nla_parse_nested(attrs, IPVS_SNAT_DEST_ATTR_MAX, nla, ip_vs_snat_dest_policy); + if (ret) { + IP_VS_ERR_RL("[snat] nla_parse_nested failed,[%d]\n", ret); + return -EINVAL; + } + + nal_saddr = attrs[IPVS_SNAT_DEST_ATTR_FADDR]; + nal_smask = attrs[IPVS_SNAT_DEST_ATTR_FMASK]; + nal_daddr = attrs[IPVS_SNAT_DEST_ATTR_DADDR]; + nal_dmask = attrs[IPVS_SNAT_DEST_ATTR_DMASK]; + nal_gw = attrs[IPVS_SNAT_DEST_ATTR_GW]; + nal_out_dev = attrs[IPVS_SNAT_DEST_ATTR_OUTDEV]; + + if (!(nal_saddr && nal_smask && nal_dmask && nal_daddr && nal_gw && nal_out_dev)) { + IP_VS_ERR_RL("[snat] basic return EINVAL\n"); + return -EINVAL; + } + + memset(usnat_dest, 0, sizeof(*usnat_dest)); + nla_memcpy(&usnat_dest->saddr, nal_saddr, sizeof(usnat_dest->saddr)); + usnat_dest->smask = nla_get_u32(nal_smask); + nla_memcpy(&usnat_dest->daddr, nal_daddr, sizeof(usnat_dest->daddr)); + usnat_dest->dmask = nla_get_u32(nal_dmask); + nla_memcpy(&usnat_dest->gw, nal_gw, sizeof(usnat_dest->gw)); + strcpy(usnat_dest->out_dev, nla_data(nal_out_dev)); + + IP_VS_DBG(6, "%s(): usnat_dest->saddr = %pI4\n", __func__, &usnat_dest->saddr.ip); + IP_VS_DBG(6, "%s(): usnat_dest->smask = %d\n", __func__, usnat_dest->smask); + IP_VS_DBG(6, "%s(): usnat_dest->daddr = %pI4\n", __func__, &usnat_dest->daddr.ip); + IP_VS_DBG(6, "%s(): usnat_dest->dmask = %d\n", __func__, usnat_dest->dmask); + IP_VS_DBG(6, "%s(): usnat_dest->gw = %pI4\n", __func__, &usnat_dest->gw.ip); + IP_VS_DBG(6, "%s(): usnat_dest->out_dev = [%s]\n", __func__, usnat_dest->out_dev); + + if (full_entry) { + nal_minip = attrs[IPVS_SNAT_DEST_ATTR_MINIP]; + nal_maxip = attrs[IPVS_SNAT_DEST_ATTR_MAXIP]; + nal_algo = attrs[IPVS_SNAT_DEST_ATTR_ALGO]; + nal_newgw = attrs[IPVS_SNAT_DEST_ATTR_NEWGW]; + nal_conn_flags = attrs[IPVS_SNAT_DEST_ATTR_CONNFLAG]; + + if (!(nal_minip && nal_maxip && nal_algo && nal_newgw && nal_conn_flags)) { + IP_VS_ERR_RL("[snat] full_entry return EINVAL\n"); + return -EINVAL; + } + + nla_memcpy(&usnat_dest->minip, nal_minip, sizeof(usnat_dest->minip)); + nla_memcpy(&usnat_dest->maxip, nal_maxip, sizeof(usnat_dest->maxip)); + nla_memcpy(&usnat_dest->new_gw, nal_newgw, sizeof(usnat_dest->new_gw)); + usnat_dest->conn_flags = nla_get_u16(nal_conn_flags) & IP_VS_CONN_F_FWD_MASK; + usnat_dest->algo = nla_get_u8(nal_algo); + + IP_VS_DBG(6, "%s(): usnat_dest->minip = %pI4\n", __func__, &usnat_dest->minip.ip); + IP_VS_DBG(6, "%s(): usnat_dest->maxip = %pI4\n", __func__,&usnat_dest->maxip.ip); + IP_VS_DBG(6, "%s(): usnat_dest->new_gw = %pI4\n", __func__, &usnat_dest->new_gw.ip); + IP_VS_DBG(6, "%s(): usnat_dest->conn_flags = %d\n", __func__, usnat_dest->conn_flags); + IP_VS_DBG(6, "%s(): usnat_dest->algo = %d\n", __func__, usnat_dest->algo); + } + LeaveFunction(2); + return 0; +} + static int ip_vs_genl_parse_dest(struct ip_vs_dest_user_kern *udest, struct nlattr *nla, int full_entry) { @@ -3863,10 +4433,11 @@ static int ip_vs_genl_set_cmd(struct sk_buff *skb, struct genl_info *info) struct ip_vs_service *svc = NULL; struct ip_vs_service_user_kern usvc; struct ip_vs_dest_user_kern udest; + struct ip_vs_snat_dest_user_kern usnat_dest; struct ip_vs_laddr_user_kern uladdr; int ret = 0, cmd; - int need_full_svc = 0, need_full_dest = 0; + int need_full_svc = 0, need_full_dest = 0, need_full_snat_dest = 0; cmd = info->genlhdr->cmd; @@ -3919,7 +4490,7 @@ static int ip_vs_genl_set_cmd(struct sk_buff *skb, struct genl_info *info) else svc = __ip_vs_svc_fwm_get(usvc.af, usvc.fwmark); - /* Unless we're adding a new service, the service must already exist */ + /* Unless we're adding a new service, or the service must already exist */ if ((cmd != IPVS_CMD_NEW_SERVICE) && (svc == NULL)) { ret = -ESRCH; goto out; @@ -3927,7 +4498,8 @@ static int ip_vs_genl_set_cmd(struct sk_buff *skb, struct genl_info *info) /* Destination commands require a valid destination argument. For * adding / editing a destination, we need a full destination - * specification. */ + * specification. + */ if (cmd == IPVS_CMD_NEW_DEST || cmd == IPVS_CMD_SET_DEST || cmd == IPVS_CMD_DEL_DEST) { if (cmd != IPVS_CMD_DEL_DEST) @@ -3948,6 +4520,24 @@ static int ip_vs_genl_set_cmd(struct sk_buff *skb, struct genl_info *info) goto out; } + /* Snat destination commands require a valid destination argument. For + * adding / editing a snat destination, we need a full destination + * specification. + */ + if (cmd == IPVS_CMD_NEW_SNATDEST || cmd == IPVS_CMD_SET_SNATDEST + || cmd == IPVS_CMD_DEL_SNATDEST) { + if (cmd != IPVS_CMD_DEL_SNATDEST) { + need_full_snat_dest = 1; + } + ret = ip_vs_genl_parse_snat_dest(&usnat_dest, + info->attrs[IPVS_CMD_ATTR_SNATDEST], + need_full_snat_dest); + if (ret) { + IP_VS_ERR_RL("[snat] ip_vs_genl_parse_snat_dest fail, [%d]\n", ret); + goto out; + } + } + switch (cmd) { case IPVS_CMD_NEW_SERVICE: if (svc == NULL) @@ -3979,6 +4569,15 @@ static int ip_vs_genl_set_cmd(struct sk_buff *skb, struct genl_info *info) case IPVS_CMD_DEL_LADDR: ret = ip_vs_del_laddr(svc, &uladdr); break; + case IPVS_CMD_NEW_SNATDEST: + ret = ip_vs_add_snat_dest(svc, &usnat_dest); + break; + case IPVS_CMD_SET_SNATDEST: + ret = ip_vs_edit_snat_dest(svc, &usnat_dest); + break; + case IPVS_CMD_DEL_SNATDEST: + ret = ip_vs_del_snat_dest(svc, &usnat_dest); + break; default: ret = -EINVAL; } @@ -4199,6 +4798,24 @@ static struct genl_ops ip_vs_genl_ops[] __read_mostly = { .policy = ip_vs_cmd_policy, .dumpit = ip_vs_genl_dump_laddrs, }, + { + .cmd = IPVS_CMD_NEW_SNATDEST, + .flags = GENL_ADMIN_PERM, + .policy = ip_vs_cmd_policy, + .doit = ip_vs_genl_set_cmd, + }, + { + .cmd = IPVS_CMD_SET_SNATDEST, + .flags = GENL_ADMIN_PERM, + .policy = ip_vs_cmd_policy, + .doit = ip_vs_genl_set_cmd, + }, + { + .cmd = IPVS_CMD_DEL_SNATDEST, + .flags = GENL_ADMIN_PERM, + .policy = ip_vs_cmd_policy, + .doit = ip_vs_genl_set_cmd, + }, }; static int __init ip_vs_genl_register(void) @@ -4294,3 +4911,4 @@ void ip_vs_control_cleanup(void) nf_unregister_sockopt(&ip_vs_sockopts); LeaveFunction(2); } + diff --git a/kernel/net/netfilter/ipvs/ip_vs_proto.c b/kernel/net/netfilter/ipvs/ip_vs_proto.c index c40552e9..975b9729 100644 --- a/kernel/net/netfilter/ipvs/ip_vs_proto.c +++ b/kernel/net/netfilter/ipvs/ip_vs_proto.c @@ -246,6 +246,9 @@ int __init ip_vs_protocol_init(void) #endif #ifdef CONFIG_IP_VS_PROTO_ESP REGISTER_PROTOCOL(&ip_vs_protocol_esp); +#endif +#ifdef CONFIG_IP_VS_PROTO_ICMP + REGISTER_PROTOCOL(&ip_vs_protocol_icmp); #endif pr_info("Registered protocols (%s)\n", &protocols[2]); diff --git a/kernel/net/netfilter/ipvs/ip_vs_proto_icmp.c b/kernel/net/netfilter/ipvs/ip_vs_proto_icmp.c new file mode 100644 index 00000000..8c7fd6a5 --- /dev/null +++ b/kernel/net/netfilter/ipvs/ip_vs_proto_icmp.c @@ -0,0 +1,210 @@ +#define KMSG_COMPONENT "IPVS" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include +#include +#include + +#include +#include + +static struct ip_vs_conn *icmp_conn_in_get(int af, const struct sk_buff *skb, + struct ip_vs_protocol *pp, + const struct ip_vs_iphdr *iph, + unsigned int proto_off, int inverse, + int *res_dir) { + struct ip_vs_conn *cp; + struct icmphdr _icmph, *ic; + + ic = skb_header_pointer(skb, proto_off, sizeof (_icmph), &_icmph); + if ((ic == NULL) || (ICMP_ECHOREPLY != ic->type)) { + return NULL; + } + + IP_VS_DBG(8, "%s %s (%d,%d) %pI4->%pI4\n", __func__, pp->name, ic->type, ntohs(ic->un.echo.id), &iph->saddr, &iph->daddr); + + if (likely(!inverse)) { + cp = ip_vs_conn_get(af, iph->protocol, + &iph->saddr, ic->un.echo.id, + &iph->daddr, ic->un.echo.id, res_dir); + } else { + cp = ip_vs_conn_get(af, iph->protocol, + &iph->daddr, ic->un.echo.id, + &iph->saddr, ic->un.echo.id, res_dir); + } + + return cp; +} + +static struct ip_vs_conn *icmp_conn_out_get(int af, const struct sk_buff *skb, + struct ip_vs_protocol *pp, + const struct ip_vs_iphdr *iph, + unsigned int proto_off, int inverse, + int *res_dir) { + struct ip_vs_conn *cp; + struct icmphdr _icmph, *ic; + + ic = skb_header_pointer(skb, proto_off, sizeof (_icmph), &_icmph); + if ((ic == NULL) || (ICMP_ECHO != ic->type)) { + return NULL; + } + + IP_VS_DBG(8, "%s %s (%d,%d) %pI4->%pI4\n", __func__, pp->name, ic->type, ntohs(ic->un.echo.id), &iph->saddr, &iph->daddr); + + if (likely(!inverse)) { + cp = ip_vs_conn_get(af, iph->protocol, + &iph->saddr, ic->un.echo.id, + &iph->daddr, ic->un.echo.id, res_dir); + } else { + cp = ip_vs_conn_get(af, iph->protocol, + &iph->daddr, ic->un.echo.id, + &iph->saddr, ic->un.echo.id, res_dir); + } + + return cp; +} + +static int +icmp_conn_schedule(int af, struct sk_buff *skb, struct ip_vs_protocol *pp, + int *verdict, struct ip_vs_conn **cpp) { + struct ip_vs_service *svc; + struct icmphdr _icmph, *ic; + struct ip_vs_dest *dest; + struct ip_vs_iphdr iph; + + *verdict = NF_DROP; + ip_vs_fill_iphdr(af, skb_network_header(skb), &iph); + + ic = skb_header_pointer(skb, iph.len, sizeof (_icmph), &_icmph); + if (ic == NULL) { + return 0; + } + else if (ICMP_ECHO != ic->type) { + *verdict = NF_ACCEPT; + return 0; + } + + svc = ip_vs_service_get(af, skb->mark, iph.protocol, &iph.daddr, 0); + if (svc && IS_SNAT_SVC(svc)) { + if (ip_vs_todrop()) { + /* + It seems that we are very loaded. + We have to drop this packet :( + */ + ip_vs_service_put(svc); + return 0; + } + + /* + Let the virtual server select a real server for the + incoming connection, and create a connection entry. + */ + dest = svc->scheduler->schedule(svc, skb); + if (dest == NULL) { + ip_vs_service_put(svc); + IP_VS_DBG(1, "Schedule: no dest found.\n"); + return 0; + } + + //*cpp = ip_vs_schedule(svc, skb, 0); + *cpp = ip_vs_conn_new(svc->af, iph.protocol, + &iph.saddr, ic->un.echo.id, + &iph.daddr, ic->un.echo.id, + &iph.daddr, ic->un.echo.id, + 0, + dest, skb, 0); + if (!*cpp) { + *verdict = ip_vs_leave(svc, skb, pp); + return 0; + } + ip_vs_service_put(svc); + } + + return 1; +} + +static int +icmp_snat_handler(struct sk_buff *skb, + struct ip_vs_protocol *pp, struct ip_vs_conn *cp) { + return 1; +} + +static int +icmp_dnat_handler(struct sk_buff *skb, + struct ip_vs_protocol *pp, struct ip_vs_conn *cp) { + return 1; +} + +void +ip_vs_icmp_debug_packet(struct ip_vs_protocol *pp, + const struct sk_buff *skb, + int offset, const char *msg) { + char buf[128]; + struct iphdr _iph, *ih; + + ih = skb_header_pointer(skb, offset, sizeof (_iph), &_iph); + if (ih == NULL) + sprintf(buf, "%s TRUNCATED", pp->name); + else + sprintf(buf, "%s %pI4->%pI4 dev %s", pp->name, &ih->saddr, &ih->daddr, netdev_name(skb->dev)); + + IP_VS_DBG(8, "%s %s %s\n", __func__, msg, buf); +} + +static int icmp_timeouts[IP_VS_ICMP_S_LAST + 1] = { + [IP_VS_ICMP_S_NORMAL] = 2 * 60 * HZ, + [IP_VS_ICMP_S_LAST] = 2 * HZ, +}; + +static const char *const icmp_state_name_table[IP_VS_ICMP_S_LAST + 1] = { + [IP_VS_ICMP_S_NORMAL] = "ICMP", + [IP_VS_ICMP_S_LAST] = "BUG!", +}; + +static int icmp_set_state_timeout(struct ip_vs_protocol *pp, char *sname, int to) { + return ip_vs_set_state_timeout(pp->timeout_table, IP_VS_ICMP_S_LAST, + icmp_state_name_table, sname, to); +} + +static const char *icmp_state_name(int state) { + if (state >= IP_VS_ICMP_S_LAST) + return "ERR!"; + return icmp_state_name_table[state] ? icmp_state_name_table[state] : "?"; +} + +static int +icmp_state_transition(struct ip_vs_conn *cp, int direction, + const struct sk_buff *skb, struct ip_vs_protocol *pp) { + cp->timeout = pp->timeout_table[IP_VS_ICMP_S_NORMAL]; + return 1; +} + +static void icmp_init(struct ip_vs_protocol *pp) { + pp->timeout_table = icmp_timeouts; +} + +static void icmp_exit(struct ip_vs_protocol *pp) { +} + +struct ip_vs_protocol ip_vs_protocol_icmp = { + .name = "ICMP", + .protocol = IPPROTO_ICMP, + .num_states = IP_VS_ICMP_S_LAST, + .dont_defrag = 1, + .init = icmp_init, + .exit = icmp_exit, + .conn_schedule = icmp_conn_schedule, + .conn_in_get = icmp_conn_in_get, + .conn_out_get = icmp_conn_out_get, + .snat_handler = icmp_snat_handler, + .dnat_handler = icmp_dnat_handler, + .csum_check = NULL, + .state_transition = icmp_state_transition, + .state_name = icmp_state_name, + .register_app = NULL, + .unregister_app = NULL, + .debug_packet = ip_vs_icmp_debug_packet, + .timeout_change = NULL, + .set_state_timeout = icmp_set_state_timeout, +}; + diff --git a/kernel/net/netfilter/ipvs/ip_vs_proto_tcp.c b/kernel/net/netfilter/ipvs/ip_vs_proto_tcp.c index 3cdc11e1..6a022114 100644 --- a/kernel/net/netfilter/ipvs/ip_vs_proto_tcp.c +++ b/kernel/net/netfilter/ipvs/ip_vs_proto_tcp.c @@ -712,8 +712,16 @@ static struct sk_buff *tcp_opt_add_toa(struct ip_vs_conn *cp, return old_skb; } + if (IS_SNAT_CP(cp)) + return old_skb; + /* skb length and tcp option length checking */ + if (old_skb->_skb_dst) mtu = dst_mtu((struct dst_entry *)old_skb->_skb_dst); + else /* fast_xmit can reach here */ + mtu = cp->dev_inside ? cp->dev_inside->mtu : + sizeof(struct ip_vs_tcpo_addr); + if (old_skb->len > (mtu - sizeof(struct ip_vs_tcpo_addr))) { IP_VS_INC_ESTATS(ip_vs_esmib, FULLNAT_ADD_TOA_FAIL_LEN); return old_skb; @@ -817,7 +825,12 @@ static struct sk_buff *tcp_opt_add_toa_v6(struct ip_vs_conn *cp, } /* skb length and tcph length checking */ + if (old_skb->_skb_dst) mtu = dst_mtu((struct dst_entry *)old_skb->_skb_dst); + else /* fast_xmit can reach here */ + mtu = cp->dev_inside ? cp->dev_inside->mtu : + sizeof(struct ip_vs_tcpo_addr_v6); + if (old_skb->len > (mtu - sizeof(struct ip_vs_tcpo_addr_v6))) { IP_VS_INC_ESTATS(ip_vs_esmib, FULLNAT_ADD_TOA_FAIL_LEN); return old_skb; @@ -1394,11 +1407,11 @@ int sysctl_ip_vs_tcp_timeouts[IP_VS_TCP_S_LAST + 1] = { [IP_VS_TCP_S_ESTABLISHED] = 90 * HZ, [IP_VS_TCP_S_SYN_SENT] = 3 * HZ, [IP_VS_TCP_S_SYN_RECV] = 30 * HZ, - [IP_VS_TCP_S_FIN_WAIT] = 3 * HZ, - [IP_VS_TCP_S_TIME_WAIT] = 3 * HZ, + [IP_VS_TCP_S_FIN_WAIT] = 7 * HZ, + [IP_VS_TCP_S_TIME_WAIT] = 7 * HZ, [IP_VS_TCP_S_CLOSE] = 3 * HZ, - [IP_VS_TCP_S_CLOSE_WAIT] = 3 * HZ, - [IP_VS_TCP_S_LAST_ACK] = 3 * HZ, + [IP_VS_TCP_S_CLOSE_WAIT] = 7 * HZ, + [IP_VS_TCP_S_LAST_ACK] = 7 * HZ, [IP_VS_TCP_S_LISTEN] = 2 * 60 * HZ, [IP_VS_TCP_S_SYNACK] = 30 * HZ, [IP_VS_TCP_S_LAST] = 2 * HZ, diff --git a/kernel/net/netfilter/ipvs/ip_vs_proto_udp.c b/kernel/net/netfilter/ipvs/ip_vs_proto_udp.c index c5d8206a..1c539129 100644 --- a/kernel/net/netfilter/ipvs/ip_vs_proto_udp.c +++ b/kernel/net/netfilter/ipvs/ip_vs_proto_udp.c @@ -176,6 +176,21 @@ udp_partial_csum_update(int af, struct udphdr *uhdr, check)))); } +/* Calculate UDP checksum, only for PARTICAL */ +static inline void +udp_partial_csum_reset(int af, int len, struct udphdr *uhdr, + const union nf_inet_addr *saddr, + const union nf_inet_addr *daddr) +{ +#ifdef CONFIG_IP_VS_IPV6 + if (af == AF_INET6) + uhdr->check = ~csum_ipv6_magic(&saddr->in6, &daddr->in6, + len, IPPROTO_UDP, 0); + else +#endif + uhdr->check = ~csum_tcpudp_magic(saddr->ip, daddr->ip, len, IPPROTO_UDP, 0); +} + static int udp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp) @@ -335,6 +350,150 @@ udp_dnat_handler(struct sk_buff *skb, return 1; } +static int +udp_fnat_in_handler(struct sk_buff **skb_p, + struct ip_vs_protocol *pp, struct ip_vs_conn *cp) +{ + struct udphdr *udph; + unsigned int udphoff; + int oldlen; + struct sk_buff *skb = *skb_p; + +#ifdef CONFIG_IP_VS_IPV6 + if (cp->af == AF_INET6) + udphoff = sizeof(struct ipv6hdr); + else +#endif + udphoff = ip_hdrlen(skb); + oldlen = skb->len - udphoff; + + /* csum_check requires unshared skb */ + if (!skb_make_writable(skb, udphoff + sizeof(*udph))) + return 0; + + if (unlikely(cp->app != NULL)) { + /* Some checks before mangling */ + if (pp->csum_check && !pp->csum_check(cp->af, skb, pp)) + return 0; + + /* + * Attempt ip_vs_app call. + * It will fix ip_vs_conn and iph ack_seq stuff + */ + if (!ip_vs_app_pkt_in(cp, skb)) + return 0; + } + + udph = (void *)skb_network_header(skb) + udphoff; + + /* adjust src/dst port */ + udph->source = cp->lport; + udph->dest = cp->dport; + + /* Adjust UDP checksums */ + if (skb->ip_summed == CHECKSUM_PARTIAL) { + udp_partial_csum_reset(cp->af, (skb->len - udphoff), + udph, &cp->laddr, &cp->daddr); + } else if (!cp->app && (udph->check != 0)) { + /* Only port and addr are changed, do fast csum update */ + udp_fast_csum_update(cp->af, udph, &cp->vaddr, &cp->daddr, + cp->vport, cp->dport); + udp_fast_csum_update(cp->af, udph, &cp->caddr, &cp->laddr, + cp->cport, cp->lport); + if (skb->ip_summed == CHECKSUM_COMPLETE) + skb->ip_summed = CHECKSUM_NONE; + } else { + /* full checksum calculation */ + udph->check = 0; + skb->csum = skb_checksum(skb, udphoff, skb->len - udphoff, 0); +#ifdef CONFIG_IP_VS_IPV6 + if (cp->af == AF_INET6) + udph->check = csum_ipv6_magic(&cp->laddr.in6, + &cp->daddr.in6, + skb->len - udphoff, + cp->protocol, skb->csum); + else +#endif + udph->check = csum_tcpudp_magic(cp->laddr.ip, + cp->daddr.ip, + skb->len - udphoff, + cp->protocol, skb->csum); + skb->ip_summed = CHECKSUM_UNNECESSARY; + } + return 1; +} + +static int +udp_fnat_out_handler(struct sk_buff *skb, + struct ip_vs_protocol *pp, struct ip_vs_conn *cp) +{ + struct udphdr *udph; + unsigned int udphoff; + int oldlen; + + +#ifdef CONFIG_IP_VS_IPV6 + if (cp->af == AF_INET6) + udphoff = sizeof(struct ipv6hdr); + else +#endif + udphoff = ip_hdrlen(skb); + oldlen = skb->len - udphoff; + + /* csum_check requires unshared skb */ + if (!skb_make_writable(skb, udphoff + sizeof(*udph))) + return 0; + + if (unlikely(cp->app != NULL)) { + /* Some checks before mangling */ + if (pp->csum_check && !pp->csum_check(cp->af, skb, pp)) + return 0; + + /* Call application helper if needed */ + if (!ip_vs_app_pkt_out(cp, skb)) + return 0; + } + + udph = (void *)skb_network_header(skb) + udphoff; + udph->source = cp->vport; + udph->dest = cp->cport; + + /* Adjust UDP checksums */ + if (skb->ip_summed == CHECKSUM_PARTIAL) { + udp_partial_csum_reset(cp->af, (skb->len - udphoff), + udph, &cp->vaddr, &cp->caddr); + } else if (!cp->app) { + /* Only port and addr are changed, do fast csum update */ + udp_fast_csum_update(cp->af, udph, &cp->daddr, &cp->vaddr, + cp->dport, cp->vport); + udp_fast_csum_update(cp->af, udph, &cp->laddr, &cp->caddr, + cp->lport, cp->cport); + if (skb->ip_summed == CHECKSUM_COMPLETE) + skb->ip_summed = CHECKSUM_NONE; + } else { + /* full checksum calculation */ + udph->check = 0; + skb->csum = skb_checksum(skb, udphoff, skb->len - udphoff, 0); +#ifdef CONFIG_IP_VS_IPV6 + if (cp->af == AF_INET6) + udph->check = csum_ipv6_magic(&cp->vaddr.in6, + &cp->caddr.in6, + skb->len - udphoff, + cp->protocol, skb->csum); + else +#endif + udph->check = csum_tcpudp_magic(cp->vaddr.ip, + cp->caddr.ip, + skb->len - udphoff, + cp->protocol, skb->csum); + + IP_VS_DBG(11, "O-pkt: %s O-csum=%d (+%zd)\n", + pp->name, udph->check, + (char *)&(udph->check) - (char *)udph); + } + return 1; +} + static int udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp) { @@ -535,6 +694,8 @@ struct ip_vs_protocol ip_vs_protocol_udp = { .conn_out_get = udp_conn_out_get, .snat_handler = udp_snat_handler, .dnat_handler = udp_dnat_handler, + .fnat_in_handler = udp_fnat_in_handler, + .fnat_out_handler = udp_fnat_out_handler, .csum_check = udp_csum_check, .state_transition = udp_state_transition, .state_name = udp_state_name, diff --git a/kernel/net/netfilter/ipvs/ip_vs_snat_sched.c b/kernel/net/netfilter/ipvs/ip_vs_snat_sched.c new file mode 100644 index 00000000..e698fa04 --- /dev/null +++ b/kernel/net/netfilter/ipvs/ip_vs_snat_sched.c @@ -0,0 +1,493 @@ +/* + * IPVS: SNAT gateway scheduling module + * Authors: lijian + * + * 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: + * + */ + + +/* + * IPVS SNAT Scheduler structure + */ + +#define KMSG_COMPONENT "IPVS" +#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt + +#include +#include +#include +#include +#include +#include + +#include + + +struct ip_vs_snat_node { + struct hlist_node n_hash; + __be32 n_key; + struct list_head rules; +}; + +struct ip_vs_snat_zone { + struct ip_vs_snat_zone *z_next; + struct hlist_head *z_hash; + int z_order; + __be32 z_mask; +#define Z_MASK(z) ((z)->z_mask) +}; + +struct ip_vs_snat_table { + struct ip_vs_snat_zone *zones[33]; + struct ip_vs_snat_zone *zone_list; +}; + +#define IP_VS_SNAT_TAB_BITS 8 +#define IP_VS_SNAT_TAB_SIZE (1 << IP_VS_SNAT_TAB_BITS) +#define IP_VS_SNAT_TAB_MASK (IP_VS_SNAT_TAB_SIZE - 1) + +static inline u32 ip_vs_node_hash(__be32 key, struct ip_vs_snat_zone *z) +{ + u32 h = ntohl(key)>>(32 - z->z_order); + h ^= (h>>20); + h ^= (h>>10); + h ^= (h>>5); + h &= IP_VS_SNAT_TAB_MASK; + return h; +} + +static inline __be32 ip_vs_snat_zone_key(__be32 addr, struct ip_vs_snat_zone *z) +{ + return addr & Z_MASK(z); +} + +static inline unsigned long ip_vs_ifname_cmp(const char *_a, + const char *_b, + const char *_mask) +{ + const unsigned long *a = (const unsigned long *)_a; + const unsigned long *b = (const unsigned long *)_b; + const unsigned long *mask = (const unsigned long *)_mask; + unsigned long ret; + + ret = (a[0] ^ b[0]) & mask[0]; + if (IP_VS_IFNAME_MAXLEN > sizeof(unsigned long)) + ret |= (a[1] ^ b[1]) & mask[1]; + if (IP_VS_IFNAME_MAXLEN > 2 * sizeof(unsigned long)) + ret |= (a[2] ^ b[2]) & mask[2]; + if (IP_VS_IFNAME_MAXLEN > 3 * sizeof(unsigned long)) + ret |= (a[3] ^ b[3]) & mask[3]; + BUILD_BUG_ON(IP_VS_IFNAME_MAXLEN > 4 * sizeof(unsigned long)); + return ret; +} + +static struct ip_vs_dest *ip_vs_snat_rule_find(struct list_head *head, + __be32 saddr, + __be32 daddr, + __be32 rt_gateway, + const char *out_dev) +{ + struct ip_vs_dest_snat *rule = NULL; + struct ip_vs_dest *dest = NULL; + + list_for_each_entry(rule, head, rule_list) { + dest = (struct ip_vs_dest *)rule; + + if ((saddr & rule->smask.ip) != rule->saddr.ip) + continue; + + if ((daddr & rule->dmask.ip) != rule->daddr.ip) + continue; + + if (out_dev && rule->out_dev_mask[0] && + !ip_vs_ifname_cmp(out_dev, rule->out_dev, rule->out_dev_mask)){ + IP_VS_DBG(7, "SNAT rule_find gw:%pI4 rt_gw:%pI4;new_gw:%pI4\n", + &dest->addr.ip, &rt_gateway, &rule->new_gateway.ip); + return dest; + } + + if (!rule->out_dev_mask[0] && + (rt_gateway == dest->addr.ip || dest->addr.ip == 0)) { + IP_VS_DBG(7, "SNAT rule_find gw:%pI4 rt_gw:%pI4;new_gw:%pI4\n", + &dest->addr.ip, &rt_gateway, &rule->new_gateway.ip); + return dest; + } + } + + return NULL; +} + +static struct ip_vs_dest * +ip_vs_snat_rule_find_by_skb(struct list_head *head, const struct sk_buff *skb) +{ + struct rtable *rt = skb_rtable(skb); + struct iphdr *iph = ip_hdr(skb); + __be32 rt_gateway = 0; + const char *out_dev = NULL; + + if (rt) { + rt_gateway = rt->rt_gateway; + if (rt->u.dst.dev) + out_dev = rt->u.dst.dev->name; + } + + IP_VS_DBG(6, "SNAT lookup rule s:%pI4 d:%pI4 g:%pI4 oif:%s\n", + &iph->saddr, &iph->daddr, &rt_gateway, out_dev); + + return ip_vs_snat_rule_find(head, iph->saddr, + iph->daddr, rt_gateway, out_dev); +} + + +static struct ip_vs_snat_node * +ip_vs_snat_node_find(struct ip_vs_snat_zone *z, __be32 key) +{ + struct hlist_head *head = &z->z_hash[ip_vs_node_hash(key, z)]; + struct hlist_node *hnode; + struct ip_vs_snat_node *node; + + hlist_for_each_entry(node, hnode, head, n_hash) { + IP_VS_DBG(6, "SNAT lookup node z:%d nk:%pI4 k:%pI4\n", + z->z_order, &node->n_key, &key); + if (node->n_key == key) + return node; + } + + return NULL; +} + +static struct ip_vs_snat_node * +ip_vs_snat_node_new(struct ip_vs_snat_zone *z, __be32 key) +{ + struct ip_vs_snat_node *node; + struct hlist_head *head = &z->z_hash[ip_vs_node_hash(key, z)]; + + node = kmalloc(sizeof(struct ip_vs_snat_node), GFP_ATOMIC); + if (!node) + return NULL; + + INIT_LIST_HEAD(&node->rules); + node->n_key = key; + + hlist_add_head(&node->n_hash, head); + return node; +} + +static struct ip_vs_snat_zone * +ip_vs_snat_zone_new(struct ip_vs_snat_table * tbl, int smask_len) +{ + int i; + struct ip_vs_snat_zone *z; + + if (!tbl) + return NULL; + + z = kmalloc(sizeof(struct ip_vs_snat_zone), GFP_ATOMIC); + + if (!z) + return NULL; + + z->z_hash = kzalloc(sizeof(struct hlist_head) * IP_VS_SNAT_TAB_SIZE, GFP_ATOMIC); + if (!z->z_hash) { + kfree(z); + return NULL; + } + + z->z_order = smask_len; + z->z_mask = inet_make_mask(smask_len); + + for (i = smask_len+1; i <= 32; i++) + if (tbl->zones[i]) + break; + + if (i > 32) { + z->z_next = tbl->zone_list; + tbl->zone_list = z; + } else { + z->z_next = tbl->zones[i]->z_next; + tbl->zones[i]->z_next = z; + } + tbl->zones[smask_len] = z; + return z; +} + +static void ip_vs_snat_node_free(struct hlist_head *head) +{ + struct hlist_node *hnode, *next; + struct ip_vs_snat_node *node; + + if (!head) + return; + + hlist_for_each_entry_safe(node, hnode, next, head, n_hash) { + hlist_del(hnode); + kfree(node); + } +} + +static void ip_vs_snat_zone_free(struct ip_vs_snat_zone *z) { + int i; + + if (!z) + return; + + if (z->z_hash) { + for (i = 0; i < IP_VS_SNAT_TAB_SIZE; i++) { + ip_vs_snat_node_free(&z->z_hash[i]); + } + kfree(z->z_hash); + } +} + +static void ip_vs_snat_table_free(struct ip_vs_snat_table *tbl) +{ + int i; + + if (!tbl) + return; + + for (i = 0; i <= 32; i++) { + ip_vs_snat_zone_free(tbl->zones[i]); + } + + kfree(tbl); +} + +static struct ip_vs_dest *ip_vs_snat_get(int af, + struct ip_vs_snat_table *tbl, + const struct sk_buff *skb) +{ + struct ip_vs_dest *dest; + struct ip_vs_snat_zone *z; + struct iphdr *iph = ip_hdr(skb); + + for (z = tbl->zone_list; z; z = z->z_next) { + struct ip_vs_snat_node *node; + __be32 key = ip_vs_snat_zone_key(iph->saddr, z); + + node = ip_vs_snat_node_find(z, key); + + IP_VS_DBG(6, "SNAT lookup zone i:%d mask:%pI4 k:%pI4 %s\n", + z->z_order, &z->z_mask, &key, + node?"hit":"not hit"); + + if (!node) + continue; + + if ((dest = ip_vs_snat_rule_find_by_skb(&node->rules, skb))) + return dest; + } + + return NULL; +} + +static void ip_vs_node_flush(struct hlist_head *head) +{ + struct hlist_node *hnode, *next; + struct ip_vs_snat_node *node; + + if (!head) + return; + + hlist_for_each_entry_safe(node, hnode, next, head, n_hash) { + struct ip_vs_dest_snat *rule, *rule_next; + + list_for_each_entry_safe(rule, rule_next, &node->rules, rule_list) { + atomic_dec(&rule->dest.refcnt); + list_del(&rule->rule_list); + } + + hlist_del(hnode); + kfree(node); + } +} + +static void ip_vs_zone_flush(struct ip_vs_snat_zone *z) +{ + int i; + + if (!z || !z->z_hash) + return; + + for (i = 0; i < IP_VS_SNAT_TAB_SIZE; i++) { + ip_vs_node_flush(&z->z_hash[i]); + } +} + +static void ip_vs_snat_flush(struct ip_vs_snat_table *tbl) +{ + int i; + + if (!tbl) + return; + + for (i = 0; i <= 32; i++) { + struct ip_vs_snat_zone *z = tbl->zones[i]; + + if (!z) + continue; + + ip_vs_zone_flush(z); + } +} + + +static inline void ip_vs_snat_rule_add(struct ip_vs_dest_snat *new_rule, + struct ip_vs_snat_node * node_head) +{ + __be32 dmask_ip = new_rule->dmask.ip; + struct ip_vs_dest_snat *rule_pt = NULL; + + list_for_each_entry(rule_pt, &node_head->rules, rule_list) { + if (dmask_ip >= rule_pt->dmask.ip) { + break; + } + } + + if (rule_pt) + list_add_tail(&new_rule->rule_list, &rule_pt->rule_list); +} + +static int +ip_vs_snat_assign(struct ip_vs_snat_table *tbl, struct ip_vs_service *svc) +{ + struct ip_vs_dest *dest; + + list_for_each_entry(dest, &svc->destinations, n_list) { + struct ip_vs_snat_zone *z; + struct ip_vs_snat_node *node; + struct ip_vs_dest_snat *rule = (struct ip_vs_dest_snat *)dest; + __be32 key = 0; + int smask_len = inet_mask_len(rule->smask.ip); + + z = tbl->zones[smask_len]; + if (!z && !(z = ip_vs_snat_zone_new(tbl, smask_len))) { + IP_VS_ERR_RL("ip_vs_snat_zone_new return NULL\n"); + return -ENOMEM; + } + + if (rule->saddr.ip) { + struct ip_vs_dest *old_dest; + + if (rule->saddr.ip & ~Z_MASK(z)) { + IP_VS_ERR_RL("SNAT rule saddr %pI4 not match zmask %pI4\n", + &rule->saddr.ip, &Z_MASK(z)); + return -EINVAL; + } + + key = ip_vs_snat_zone_key(rule->saddr.ip, z); + node = ip_vs_snat_node_find(z, key); + + if (!node) { + node = ip_vs_snat_node_new(z, key); + if (!node) { + IP_VS_ERR_RL("ip_vs_snat_zone_new return NULL\n"); + return -ENOMEM; + } + } + + old_dest = ip_vs_snat_rule_find(&node->rules, + rule->saddr.ip, rule->daddr.ip, + dest->addr.ip, rule->out_dev); + if (!old_dest) { + atomic_inc(&dest->refcnt); + //list_add(&rule->rule_list, &node->rules); + ip_vs_snat_rule_add(rule, node); + } + + IP_VS_DBG(6, "SNAT rule %s s:%pI4/%d d:%pI4/%d g:%pI4 k:%pI4 new_gw:%pI4\n", + old_dest?"exists":"added", &rule->saddr.ip, smask_len, + &rule->daddr.ip, inet_mask_len(rule->dmask.ip), + &dest->addr.ip, &key, &rule->new_gateway.ip); + } + } + + return 0; +} + +static int ip_vs_snat_init_svc(struct ip_vs_service *svc) +{ + struct ip_vs_snat_table *tbl; + + tbl = kzalloc(sizeof(struct ip_vs_snat_table), GFP_ATOMIC); + if (tbl == NULL) { + pr_err("%s(): no memory\n", __func__); + return -ENOMEM; + } + + IP_VS_DBG(6, "SNAT hash table allocated for current service\n"); + + svc->sched_data = tbl; + + return ip_vs_snat_assign(tbl, svc); +} + +static int ip_vs_snat_update_svc(struct ip_vs_service *svc) +{ + struct ip_vs_snat_table *tbl = svc->sched_data; + + IP_VS_DBG(6, "SNAT update hash table\n"); + ip_vs_snat_flush(tbl); + return ip_vs_snat_assign(tbl, svc); +} + +static int ip_vs_snat_done_svc(struct ip_vs_service *svc) +{ + struct ip_vs_snat_table *tbl = svc->sched_data; + + ip_vs_snat_flush(tbl); + ip_vs_snat_table_free(tbl); + IP_VS_DBG(6, "SNAT hash table released\n"); + return 0; +} + +static struct ip_vs_dest *ip_vs_snat_schedule(struct ip_vs_service *svc, + const struct sk_buff *skb) +{ + struct ip_vs_dest *dest; + struct ip_vs_snat_table *tbl; + + if (svc->af != AF_INET) + return NULL; + + tbl = (struct ip_vs_snat_table *)svc->sched_data; + dest = ip_vs_snat_get(svc->af, tbl, skb); + + if (!dest) { + IP_VS_ERR_RL("SNAT: no destination available\n"); + return NULL; + } + + return dest; +} + +static struct ip_vs_scheduler ip_vs_snat_scheduler = { + .name = "snat_sched", + .refcnt = ATOMIC_INIT(0), + .module = THIS_MODULE, + .n_list = LIST_HEAD_INIT(ip_vs_snat_scheduler.n_list), + .init_service = ip_vs_snat_init_svc, + .done_service = ip_vs_snat_done_svc, + .update_service = ip_vs_snat_update_svc, + .schedule = ip_vs_snat_schedule, +}; + +static int __init ip_vs_snat_init(void) +{ + return register_ip_vs_scheduler(&ip_vs_snat_scheduler); +} + +static void __exit ip_vs_snat_cleanup(void) +{ + unregister_ip_vs_scheduler(&ip_vs_snat_scheduler); +} + +module_init(ip_vs_snat_init); +module_exit(ip_vs_snat_cleanup); +MODULE_LICENSE("GPL"); diff --git a/kernel/net/netfilter/ipvs/ip_vs_synproxy.c b/kernel/net/netfilter/ipvs/ip_vs_synproxy.c index c5de198c..21171b68 100644 --- a/kernel/net/netfilter/ipvs/ip_vs_synproxy.c +++ b/kernel/net/netfilter/ipvs/ip_vs_synproxy.c @@ -755,6 +755,34 @@ void ip_vs_synproxy_dnat_handler(struct tcphdr *tcph, struct ip_vs_seq *sp_seq) } } +static inline void +ip_vs_synproxy_save_fast_xmit_info(struct sk_buff *skb, struct ip_vs_conn *cp) +{ + /* Save info for L2 fast xmit */ + if(sysctl_ip_vs_fast_xmit_inside && skb->dev && + likely(skb->dev->type == ARPHRD_ETHER) && + skb_mac_header_was_set(skb)) { + struct ethhdr *eth = (struct ethhdr *)skb_mac_header(skb); + + if(likely(cp->dev_inside == NULL)) { + cp->dev_inside = skb->dev; + dev_hold(cp->dev_inside); + } + + if (unlikely(cp->dev_inside != skb->dev)) { + dev_put(cp->dev_inside); + cp->dev_inside = skb->dev; + dev_hold(cp->dev_inside); + } + + memcpy(cp->src_hwaddr_inside, eth->h_source, ETH_ALEN); + memcpy(cp->dst_hwaddr_inside, eth->h_dest, ETH_ALEN); + IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_SYNPROXY_SAVE_INSIDE); + IP_VS_DBG_RL("synproxy_save_fast_xmit netdevice:%s\n", + netdev_name(skb->dev)); + } +} + /* * Syn-proxy step 3 logic: receive syn-ack from rs * Update syn_proxy_seq.delta and send stored ack skbs @@ -809,6 +837,8 @@ ip_vs_synproxy_synack_rcv(struct sk_buff *skb, struct ip_vs_conn *cp, ntohs(th->dest)); } + ip_vs_synproxy_save_fast_xmit_info(skb, cp); + /* First: free stored syn skb */ if ((tmp_skb = xchg(&cp->syn_skb, NULL)) != NULL) { kfree_skb(tmp_skb); diff --git a/kernel/net/netfilter/ipvs/ip_vs_xmit.c b/kernel/net/netfilter/ipvs/ip_vs_xmit.c index 7c096cc6..e18c0a1e 100644 --- a/kernel/net/netfilter/ipvs/ip_vs_xmit.c +++ b/kernel/net/netfilter/ipvs/ip_vs_xmit.c @@ -116,6 +116,74 @@ static struct rtable *__ip_vs_get_out_rt(struct ip_vs_conn *cp, u32 rtos) return rt; } +static struct rtable * +__ip_vs_get_snat_out_rt(struct rtable *old_rt, + struct ip_vs_conn *cp, u32 rtos) +{ + struct rtable *rt; /* Route to the other host */ + struct ip_vs_dest *dest = cp->dest; + struct ip_vs_dest_snat *rule = (struct ip_vs_dest_snat *)cp->dest; + + if (dest) { + __be32 dst_ip = rule->new_gateway.ip?rule->new_gateway.ip:dest->addr.ip; + + if (old_rt && + (old_rt->rt_gateway == rule->new_gateway.ip || + rule->new_gateway.ip == 0)) + return old_rt; + + if (!dst_ip) + dst_ip = cp->vaddr.ip; + + 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 = dst_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: %pI4\n", + &dest->addr.ip); + return NULL; + } + __ip_vs_dst_set(dest, rtos, dst_clone(&rt->u.dst)); + IP_VS_DBG(10, "SNAT old dst %pI4 new dst %pI4, refcnt=%d, rtos=%X\n", + old_rt?&old_rt->rt_gateway:0, + &rt->rt_gateway, + 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 (old_rt) + return old_rt; + + if (ip_route_output_key(&init_net, &rt, &fl)) { + IP_VS_DBG_RL("ip_route_output error, dest: %pI4\n", + &cp->daddr.ip); + return NULL; + } + } + + return rt; +} + struct rtable *ip_vs_get_rt(union nf_inet_addr *addr, u32 rtos) { struct rtable *rt; /* Route to the other host */ @@ -304,6 +372,7 @@ static void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp, } if (inout) { + if (NOT_SNAT_CP(cp)) iph->saddr = cp->vaddr.ip; ip_send_check(iph); ciph->daddr = cp->vaddr.ip; @@ -644,6 +713,7 @@ int ip_vs_fast_response_xmit(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp) { + int ret; struct ethhdr *eth; if (!cp->indev) @@ -671,12 +741,14 @@ ip_vs_fast_response_xmit(struct sk_buff *skb, struct ip_vs_protocol *pp, ip_hdr(skb)->saddr = cp->vaddr.ip; ip_hdr(skb)->daddr = cp->caddr.ip; } else { + /* IP_VS_ERR_RL("L2 fast xmit support fullnat only!\n"); goto err; - /*if (pp->snat_handler && !pp->snat_handler(skb, pp, cp)) + */ + if (pp->snat_handler && !pp->snat_handler(skb, pp, cp)) goto err; - ip_hdr(skb)->saddr = cp->vaddr.ip;*/ + ip_hdr(skb)->saddr = cp->vaddr.ip; } ip_send_check(ip_hdr(skb)); @@ -712,11 +784,12 @@ ip_vs_fast_response_xmit(struct sk_buff *skb, struct ip_vs_protocol *pp, IP_VS_DBG_RL("%s: send skb to client!\n", __func__); /* Send the packet out */ - do { - int ret = dev_queue_xmit(skb); - if (ret != 0) - IP_VS_ERR_RL("dev_queue_xmit failed! code:%d\n", ret); - }while(0); + ret = dev_queue_xmit(skb); + if (ret != 0) { + IP_VS_DBG_RL("dev_queue_xmit failed! code:%d\n", ret); + IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_FAILED); + return 0; + } IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_PASS); return 0; @@ -731,6 +804,7 @@ int ip_vs_fast_response_xmit_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp) { + int ret; struct ethhdr *eth; if (!cp->indev) @@ -796,11 +870,12 @@ ip_vs_fast_response_xmit_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, IP_VS_DBG_RL("%s: send skb to client!\n", __func__); /* Send the packet out */ - do { - int ret = dev_queue_xmit(skb); - if (ret != 0) - IP_VS_ERR_RL("dev_queue_xmit failed! code:%d\n", ret); - }while(0); + ret = dev_queue_xmit(skb); + if (ret != 0) { + IP_VS_DBG_RL("dev_queue_xmit failed! code:%d\n", ret); + IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_FAILED); + return 0; + } IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_PASS); return 0; @@ -810,6 +885,40 @@ ip_vs_fast_response_xmit_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, } #endif +static inline void +ip_vs_save_xmit_inside_info(struct sk_buff *skb, struct ip_vs_conn *cp) +{ + if(!sysctl_ip_vs_fast_xmit_inside) + return; + + if(!skb->dev) { + IP_VS_DBG_RL("%s(): skb->dev is NULL. \n", __func__); + return; + } + IP_VS_DBG_RL("%s(): netdevice:%s\n", netdev_name(skb->dev), __func__); + + if(likely((skb->dev->type == ARPHRD_ETHER) && + skb_mac_header_was_set(skb))) { + struct ethhdr *eth = (struct ethhdr *)skb_mac_header(skb); + + if(unlikely(cp->dev_inside == NULL)) { + cp->dev_inside = skb->dev; + dev_hold(cp->dev_inside); + } + + if (unlikely(cp->dev_inside != skb->dev)) { + dev_put(cp->dev_inside); + cp->dev_inside = skb->dev; + dev_hold(cp->dev_inside); + } + + memcpy(cp->src_hwaddr_inside, eth->h_source, ETH_ALEN); + memcpy(cp->dst_hwaddr_inside, eth->h_dest, ETH_ALEN); + } else { + IP_VS_DBG_RL("%s():save dev and mac failed!\n", __func__); + } +} + /* Response transmit to client * Used for NAT/Local. */ @@ -820,6 +929,11 @@ ip_vs_normal_response_xmit(struct sk_buff *skb, struct ip_vs_protocol *pp, struct rtable *rt; int mtu; + ip_vs_save_xmit_inside_info(skb, cp); + + if(sysctl_ip_vs_fast_xmit && !ip_vs_fast_response_xmit(skb, pp, cp)) + return NF_STOLEN; + /* copy-on-write the packet before mangling it */ if (!skb_make_writable(skb, ihl)) goto drop; @@ -953,6 +1067,8 @@ ip_vs_fnat_response_xmit(struct sk_buff *skb, struct ip_vs_protocol *pp, int mtu; struct iphdr *iph = ip_hdr(skb); + ip_vs_save_xmit_inside_info(skb, cp); + if(sysctl_ip_vs_fast_xmit && !ip_vs_fast_response_xmit(skb, pp, cp)) return NF_STOLEN; @@ -1017,6 +1133,8 @@ ip_vs_fnat_response_xmit_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, struct rt6_info *rt; /* Route to the other host */ int mtu; + ip_vs_save_xmit_inside_info(skb, cp); + if(sysctl_ip_vs_fast_xmit && !ip_vs_fast_response_xmit_v6(skb, pp, cp)) return NF_STOLEN; @@ -1219,6 +1337,182 @@ ip_vs_bypass_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, } #endif +/* fullnat mode */ +int +ip_vs_fast_xmit(struct sk_buff *skb, struct ip_vs_protocol *pp, + struct ip_vs_conn *cp) +{ + int ret; + struct ethhdr *eth; + + if (!cp->dev_inside) + goto err; + if (!gso_ok(skb, cp->dev_inside) && (skb->len > cp->dev_inside->mtu)) + goto err; + + /* Try to reuse skb */ + if (unlikely(skb_shared(skb) || skb_cloned(skb))) { + struct sk_buff *new_skb = skb_copy(skb, GFP_ATOMIC); + if(unlikely(new_skb == NULL)) + goto err; + + /* Drop old skb */ + kfree_skb(skb); + skb = new_skb; + IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_SKB_COPY); + } + + /* change ip, port. */ + if ((cp->flags & IP_VS_CONN_F_FWD_MASK) == IP_VS_CONN_F_FULLNAT) { + if (pp->fnat_in_handler && !pp->fnat_in_handler(&skb, pp, cp)) + goto err; + + ip_hdr(skb)->saddr = cp->laddr.ip; + ip_hdr(skb)->daddr = cp->daddr.ip; + } else { + /* + IP_VS_ERR_RL("L2 fast xmit support fullnat only!\n"); + goto err; + */ + if (pp->dnat_handler && !pp->dnat_handler(skb, pp, cp)) + goto err; + + ip_hdr(skb)->daddr = cp->daddr.ip; + } + + ip_send_check(ip_hdr(skb)); + + skb->dev = cp->dev_inside; + + if(unlikely(skb_headroom(skb) < LL_RESERVED_SPACE(skb->dev))){ + struct sk_buff *skb2; + + IP_VS_ERR_RL("need more headroom! realloc skb\n"); + skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(skb->dev)); + if (skb2 == NULL) + goto err; + kfree_skb(skb); + skb = skb2; + } + + if(likely(skb_mac_header_was_set(skb))) { + eth = eth_hdr(skb); + memcpy(eth->h_dest, cp->src_hwaddr_inside, ETH_ALEN); + memcpy(eth->h_source, cp->dst_hwaddr_inside, ETH_ALEN); + skb->data = (unsigned char *)eth_hdr(skb); + skb->len += sizeof(struct ethhdr); + } else { + eth = (struct ethhdr *)skb_push(skb, sizeof(struct ethhdr)); + skb_reset_mac_header(skb); + memcpy(eth->h_dest, cp->src_hwaddr_inside, ETH_ALEN); + memcpy(eth->h_source, cp->dst_hwaddr_inside, ETH_ALEN); + } + skb->protocol = eth->h_proto = htons(ETH_P_IP); + skb->pkt_type = PACKET_OUTGOING; + + IP_VS_DBG_RL("%s: send skb to RS!\n", __func__); + /* Send the packet out */ + ret = dev_queue_xmit(skb); + if (ret != 0) { + IP_VS_DBG_RL("dev_queue_xmit failed! code:%d\n", ret); + IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_FAILED_INSIDE); + return 0; + } + + IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_PASS_INSIDE); + return 0; +err: + IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_REJECT_INSIDE); + return 1; +} + +#ifdef CONFIG_IP_VS_IPV6 +/* just for fullnat mode */ +int +ip_vs_fast_xmit_v6(struct sk_buff *skb, struct ip_vs_protocol *pp, + struct ip_vs_conn *cp) +{ + int ret; + struct ethhdr *eth; + + if (!cp->dev_inside) + goto err; + if (!gso_ok(skb, cp->dev_inside) && (skb->len > cp->dev_inside->mtu)) + goto err; + + /* Try to reuse skb if possible */ + if (unlikely(skb_shared(skb) || skb_cloned(skb))) { + struct sk_buff *new_skb = skb_copy(skb, GFP_ATOMIC); + if(unlikely(new_skb == NULL)) + goto err; + + /* Drop old skb */ + kfree_skb(skb); + skb = new_skb; + IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_SKB_COPY); + } + + /* change ip, port. */ + if ((cp->flags & IP_VS_CONN_F_FWD_MASK) == IP_VS_CONN_F_FULLNAT) { + if (pp->fnat_in_handler && !pp->fnat_in_handler(&skb, pp, cp)) + goto err; + + ipv6_hdr(skb)->saddr = cp->laddr.in6; + ipv6_hdr(skb)->daddr = cp->daddr.in6; + } else { + IP_VS_ERR_RL("L2 fast xmit support fullnat only!\n"); + goto err; + /*if (pp->dnat_handler && !pp->dnat_handler(skb, pp, cp)) + goto err; + + ipv6_hdr(skb)->daddr = cp->daddr.in6;*/ + } + + skb->dev = cp->dev_inside; + + if(unlikely(skb_headroom(skb) < LL_RESERVED_SPACE(skb->dev))){ + struct sk_buff *skb2; + + IP_VS_ERR_RL("need more headroom! realloc skb\n"); + skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(skb->dev)); + if (skb2 == NULL) + goto err; + kfree_skb(skb); + skb = skb2; + } + + if(likely(skb_mac_header_was_set(skb))) { + eth = eth_hdr(skb); + memcpy(eth->h_dest, cp->src_hwaddr_inside, ETH_ALEN); + memcpy(eth->h_source, cp->dst_hwaddr_inside, ETH_ALEN); + skb->data = (unsigned char *)eth_hdr(skb); + skb->len += sizeof(struct ethhdr); + } else { + eth = (struct ethhdr *)skb_push(skb, sizeof(struct ethhdr)); + skb_reset_mac_header(skb); + memcpy(eth->h_dest, cp->src_hwaddr_inside, ETH_ALEN); + memcpy(eth->h_source, cp->dst_hwaddr_inside, ETH_ALEN); + } + skb->protocol = eth->h_proto = htons(ETH_P_IPV6); + skb->pkt_type = PACKET_OUTGOING; + + IP_VS_DBG_RL("%s: send skb to RS!\n", __func__); + /* Send the packet out */ + ret = dev_queue_xmit(skb); + if (ret != 0) { + IP_VS_DBG_RL("dev_queue_xmit failed! code:%d\n", ret); + IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_FAILED_INSIDE); + return 0; + } + + IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_PASS_INSIDE); + return 0; +err: + IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_REJECT_INSIDE); + return 1; +} +#endif + void ip_vs_save_xmit_info(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp) @@ -1280,6 +1574,11 @@ ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, IP_VS_DBG(10, "filled cport=%d\n", ntohs(*p)); } + ip_vs_save_xmit_info(skb, pp, cp); + + if(sysctl_ip_vs_fast_xmit_inside && !ip_vs_fast_xmit(skb, pp, cp)) + return NF_STOLEN; + if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos)))) goto tx_error_icmp; @@ -1438,6 +1737,11 @@ ip_vs_fnat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, IP_VS_DBG(10, "filled cport=%d\n", ntohs(*p)); } + ip_vs_save_xmit_info(skb, pp, cp); + + if(sysctl_ip_vs_fast_xmit_inside && !ip_vs_fast_xmit(skb, pp, cp)) + return NF_STOLEN; + if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos)))) goto tx_error_icmp; @@ -1453,8 +1757,6 @@ ip_vs_fnat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, goto tx_error; } - ip_vs_save_xmit_info(skb, pp, cp); - /* copy-on-write the packet before mangling it */ if (!skb_make_writable(skb, sizeof(struct iphdr))) goto tx_error_put; @@ -1519,6 +1821,11 @@ ip_vs_fnat_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, IP_VS_DBG(10, "filled cport=%d\n", ntohs(*p)); } + ip_vs_save_xmit_info(skb, pp, cp); + + if(sysctl_ip_vs_fast_xmit_inside && !ip_vs_fast_xmit_v6(skb, pp, cp)) + return NF_STOLEN; + rt = __ip_vs_get_out_rt_v6(cp); if (!rt) goto tx_error_icmp; @@ -1534,8 +1841,6 @@ ip_vs_fnat_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, goto tx_error; } - ip_vs_save_xmit_info(skb, pp, cp); - /* copy-on-write the packet before mangling it */ if (!skb_make_writable(skb, sizeof(struct ipv6hdr))) goto tx_error_put; @@ -2086,3 +2391,89 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, goto tx_error; } #endif + +int +ip_vs_snat_out_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, + struct ip_vs_protocol *pp) +{ + struct rtable *rt; /* Route to the other host */ + struct rtable *old_rt = skb_rtable(skb); + int mtu; + struct iphdr *iph = ip_hdr(skb); + + EnterFunction(10); + + /* check if it is a connection of no-client-port */ + if (unlikely(cp->flags & IP_VS_CONN_F_NO_CPORT)) { + __be16 _pt, *p; + p = skb_header_pointer(skb, iph->ihl * 4, sizeof(_pt), &_pt); + if (p == NULL) + goto tx_error; + ip_vs_conn_fill_cport(cp, *p); + IP_VS_DBG(10, "filled cport=%d\n", ntohs(*p)); + } + + ip_vs_save_xmit_info(skb, pp, cp); + + if(sysctl_ip_vs_fast_xmit_inside && !ip_vs_fast_xmit(skb, pp, cp)) + return NF_STOLEN; + + if (!(rt = __ip_vs_get_snat_out_rt(old_rt, cp, RT_TOS(iph->tos)))) + goto tx_error_icmp; + + /* MTU checking */ + mtu = dst_mtu(&rt->u.dst); + if (!gso_ok(skb, rt->u.dst.dev) && (skb->len > mtu) && + (iph->frag_off & htons(IP_DF))) { + ip_rt_put(rt); + IP_VS_INC_ESTATS(ip_vs_esmib, XMIT_UNEXPECTED_MTU); + icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu)); + IP_VS_DBG_RL_PKT(0, pp, skb, 0, + "ip_vs_snat_out_xmit(): frag needed for"); + goto tx_error; + } + + /* copy-on-write the packet before mangling it */ + if (!skb_make_writable(skb, sizeof(struct iphdr))) + goto tx_error_put; + + if (skb_cow(skb, rt->u.dst.dev->hard_header_len)) + goto tx_error_put; + + /* drop old route */ + if (rt != old_rt) { + skb_dst_drop(skb); + skb_dst_set(skb, &rt->u.dst); + } + + /* mangle the packet */ + if (pp->fnat_in_handler && !pp->fnat_in_handler(&skb, pp, cp)) + goto tx_error; + ip_hdr(skb)->saddr = cp->laddr.ip; + ip_hdr(skb)->daddr = cp->daddr.ip; + ip_send_check(ip_hdr(skb)); + + IP_VS_DBG_PKT(10, pp, skb, 0, "After SNAT-OUT"); + + /* FIXME: when application helper enlarges the packet and the length + is larger than the MTU of outgoing device, there will be still + MTU problem. */ + + /* Another hack: avoid icmp_send in ip_fragment */ + skb->local_df = 1; + + IP_VS_XMIT(PF_INET, skb, rt); + + LeaveFunction(10); + return NF_STOLEN; + + tx_error_icmp: + dst_link_failure(skb); + tx_error: + LeaveFunction(10); + kfree_skb(skb); + return NF_STOLEN; + tx_error_put: + ip_rt_put(rt); + goto tx_error; +} diff --git a/kernel/patches b/kernel/patches deleted file mode 120000 index 0868bf3d..00000000 --- a/kernel/patches +++ /dev/null @@ -1 +0,0 @@ -/home/pukong/git/lvs-kernel \ No newline at end of file diff --git a/kernel/refresh_patch.sh b/kernel/refresh_patch.sh deleted file mode 120000 index 29401d18..00000000 --- a/kernel/refresh_patch.sh +++ /dev/null @@ -1 +0,0 @@ -/home/pukong/git/lvs-kernel/scripts/refresh_patch.sh \ No newline at end of file diff --git a/kernel/run_oldconfig.py b/kernel/run_oldconfig.py deleted file mode 120000 index c3db9e6f..00000000 --- a/kernel/run_oldconfig.py +++ /dev/null @@ -1 +0,0 @@ -/home/pukong/git/lvs-kernel/scripts/run_oldconfig.py \ No newline at end of file diff --git a/tools/ipvsadm/ipvsadm.c b/tools/ipvsadm/ipvsadm.c index 6d694d64..38118e18 100644 --- a/tools/ipvsadm/ipvsadm.c +++ b/tools/ipvsadm/ipvsadm.c @@ -141,7 +141,12 @@ #define CMD_ADDLADDR (CMD_NONE+15) #define CMD_DELLADDR (CMD_NONE+16) #define CMD_GETLADDR (CMD_NONE+17) -#define CMD_MAX CMD_GETLADDR +/* for lvs snat */ +#define CMD_ADDSNAT (CMD_NONE+18) +#define CMD_DELSNAT (CMD_NONE+19) +#define CMD_EDITSNAT (CMD_NONE+20) + +#define CMD_MAX CMD_EDITSNAT #define NUMBER_OF_CMD (CMD_MAX - CMD_NONE) static const char* cmdnames[] = { @@ -162,6 +167,9 @@ static const char* cmdnames[] = { "add-laddr" , "del-laddr" , "get-laddr" , + "add-snat", + "del-snat", + "edit-snat", }; static const char* optnames[] = { @@ -169,7 +177,6 @@ static const char* optnames[] = { "connection", "service-address", "scheduler", - "pe", "persistent", "netmask", "real-server", @@ -191,6 +198,13 @@ static const char* optnames[] = { "pe" , "local-address" , "synproxy" , + "source-address", + "dest-address", + "gateway", + "snat-ip", + "algo", + "new-gateway", + "oif", }; /* @@ -203,24 +217,28 @@ static const char* optnames[] = { */ static const char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = { - /* -n -c svc -s -p -M -r fwd -w -x -y -mc tot dmn -st -rt thr -pc srt sid -ex ops pe laddr syn*/ -/*ADD*/ {'x', 'x', '+', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', ' '}, -/*EDIT*/ {'x', 'x', '+', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', ' '}, -/*DEL*/ {'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'}, -/*FLUSH*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'}, -/*LIST*/ {' ', '1', '1', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', '1', '1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'x', 'x', 'x', 'x'}, -/*ADDSRV*/ {'x', 'x', '+', 'x', 'x', 'x', '+', ' ', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'}, -/*DELSRV*/ {'x', 'x', '+', 'x', 'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'}, -/*EDITSRV*/ {'x', 'x', '+', 'x', 'x', 'x', '+', ' ', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'}, -/*TIMEOUT*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'}, -/*STARTD*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x'}, -/*STOPD*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x'}, -/*RESTORE*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'}, -/*SAVE*/ {' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'}, -/*ZERO*/ {'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'}, -/*ADDLADDR*/{'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', '+', 'x'}, -/*DELLADDR*/{'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', '+', 'x'}, -/*GETLADDR*/{'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'}, +/* -n -c svc -s -p -M -r fwd -w -x -y -mc tot dmn -st -rt thr -pc srt sid -ex ops pe laddr syn -F -T -W -U -O -N -oif */ +/*ADD*/ {'x', 'x', '+', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*EDIT*/ {'x', 'x', '+', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*DEL*/ {'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*FLUSH*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*LIST*/ {' ', '1', '1', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', '1', '1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*ADDSRV*/ {'x', 'x', '+', 'x', 'x', 'x', '+', ' ', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*DELSRV*/ {'x', 'x', '+', 'x', 'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*EDITSRV*/ {'x', 'x', '+', 'x', 'x', 'x', '+', ' ', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*TIMEOUT*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*STARTD*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*STOPD*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*RESTORE*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*SAVE*/ {' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*ZERO*/ {'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*ADDLADDR*/{'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*DELLADDR*/{'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, +/*GETLADDR*/{'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x','x', 'x'}, + +/*ADDSNAT*/ {'x', 'x', '+', 'x', 'x', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', '+', ' ', ' ', '+', ' ',' ', ' '}, +/*DELSNAT*/ {'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', '+', ' ', ' ', ' ', ' ',' ', ' '}, +/*EDITSNAT*/ {'x', 'x', '+', 'x', 'x', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', '+', ' ', ' ', '+', ' ',' ', ' '}, }; /* printing format flags */ @@ -233,14 +251,23 @@ static const char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = #define FMT_PERSISTENTCONN 0x0020 #define FMT_NOSORT 0x0040 #define FMT_EXACT 0x0080 +#define FMT_SNAT_RULE 0x0100 #define SERVICE_NONE 0x0000 #define SERVICE_ADDR 0x0001 #define SERVICE_PORT 0x0002 +#define SNAT_NONE 0x0000 +#define SNAT_ADDR 0x0001 +#define SNAT_MASK 0x0002 + /* default scheduler */ #define DEF_SCHED "wlc" +/* default snat src gateway */ +#define DEF_SNAT_SRC_GW 0 /* all isp */ +#define DEF_SANT_NEW_GW 0 + /* default multicast interface name */ #define DEF_MCAST_IFN "eth0" @@ -250,11 +277,19 @@ struct ipvs_command_entry { int cmd; ipvs_service_t svc; ipvs_dest_t dest; + ipvs_snat_dest_t snat_dest; ipvs_timeout_t timeout; ipvs_daemon_t daemon; ipvs_laddr_t laddr; }; + /* common use */ +struct ipvs_snat_rule_parse { + union nf_inet_addr addr; + int mask; + u_int16_t af; +}; + /* Use values outside ASCII range so that if an option has * a short name it can be used as the tag */ @@ -272,6 +307,7 @@ enum { TAG_SORT, TAG_NO_SORT, TAG_PERSISTENCE_ENGINE, + TAG_OUT_DEV, }; /* various parsing helpers & parsing functions */ @@ -289,11 +325,11 @@ static int parse_service(char *buf, ipvs_service_t *svc); static int parse_netmask(char *buf, u_int32_t *addr); static int parse_timeout(char *buf, int min, int max); static unsigned int parse_fwmark(char *buf); - +static int parse_address_mask(char* buf, struct ipvs_snat_rule_parse *rule); /* check the options based on the commands_v_options table */ -static void generic_opt_check(int command, int options); +static void generic_opt_check(int command, unsigned long options); static void set_command(int *cmd, const int newcmd); -static void set_option(unsigned int *options, unsigned int option); +static void set_option(unsigned long *options, unsigned long option); static void tryhelp_exit(const char *program, const int exit_status); static void usage_exit(const char *program, const int exit_status); @@ -314,6 +350,11 @@ static int modprobe_ipvs(void); static void check_ipvs_version(void); static int process_options(int argc, char **argv, int reading_stdin); +static void addrmask_to_str(int af, const union nf_inet_addr * addr, unsigned short mask, char * output); +static void addrpool_to_str(int af, const union nf_inet_addr * minaddr, const union nf_inet_addr * maxaddr, char * output); +static void addr_to_str(int af, const union nf_inet_addr * addr, char * output); +static inline char* ip_select_algo_name(unsigned algo); + int main(int argc, char **argv) { @@ -348,7 +389,7 @@ int main(int argc, char **argv) static int parse_options(int argc, char **argv, struct ipvs_command_entry *ce, - unsigned int *options, unsigned int *format) + unsigned long *options, unsigned int *format) { int c, parse; poptContext context; @@ -369,66 +410,60 @@ parse_options(int argc, char **argv, struct ipvs_command_entry *ce, { "version", 'v', POPT_ARG_NONE, NULL, 'v', NULL, NULL }, { "restore", 'R', POPT_ARG_NONE, NULL, 'R', NULL, NULL }, { "save", 'S', POPT_ARG_NONE, NULL, 'S', NULL, NULL }, - { "start-daemon", '\0', POPT_ARG_STRING, &optarg, - TAG_START_DAEMON, NULL, NULL }, - { "stop-daemon", '\0', POPT_ARG_STRING, &optarg, - TAG_STOP_DAEMON, NULL, NULL }, + { "start-daemon", '\0', POPT_ARG_STRING, &optarg, TAG_START_DAEMON, NULL, NULL }, + { "stop-daemon", '\0', POPT_ARG_STRING, &optarg, TAG_STOP_DAEMON, NULL, NULL }, { "add-laddr", 'P', POPT_ARG_NONE, NULL, 'P', NULL, NULL }, { "del-laddr", 'Q', POPT_ARG_NONE, NULL, 'Q', NULL, NULL }, { "get-laddr", 'G', POPT_ARG_NONE, NULL, 'G', NULL, NULL }, - - { "tcp-service", 't', POPT_ARG_STRING, &optarg, 't', - NULL, NULL }, - { "udp-service", 'u', POPT_ARG_STRING, &optarg, 'u', - NULL, NULL }, - { "fwmark-service", 'f', POPT_ARG_STRING, &optarg, 'f', - NULL, NULL }, + { "tcp-service", 't', POPT_ARG_STRING, &optarg, 't', NULL, NULL }, + { "udp-service", 'u', POPT_ARG_STRING, &optarg, 'u', NULL, NULL }, + { "fwmark-service", 'f', POPT_ARG_STRING, &optarg, 'f', NULL, NULL }, { "scheduler", 's', POPT_ARG_STRING, &optarg, 's', NULL, NULL }, - { "persistent", 'p', POPT_ARG_STRING|POPT_ARGFLAG_OPTIONAL, - &optarg, 'p', NULL, NULL }, + { "persistent", 'p', POPT_ARG_STRING|POPT_ARGFLAG_OPTIONAL,&optarg, 'p', NULL, NULL }, { "netmask", 'M', POPT_ARG_STRING, &optarg, 'M', NULL, NULL }, - { "real-server", 'r', POPT_ARG_STRING, &optarg, 'r', - NULL, NULL }, + { "real-server", 'r', POPT_ARG_STRING, &optarg, 'r', NULL, NULL }, { "masquerading", 'm', POPT_ARG_NONE, NULL, 'm', NULL, NULL }, { "ipip", 'i', POPT_ARG_NONE, NULL, 'i', NULL, NULL }, { "gatewaying", 'g', POPT_ARG_NONE, NULL, 'g', NULL, NULL }, { "fullnat" , 'b' , POPT_ARG_NONE, NULL, 'b', NULL, NULL }, { "weight", 'w', POPT_ARG_STRING, &optarg, 'w', NULL, NULL }, - { "u-threshold", 'x', POPT_ARG_STRING, &optarg, 'x', - NULL, NULL }, - { "l-threshold", 'y', POPT_ARG_STRING, &optarg, 'y', - NULL, NULL }, + { "u-threshold", 'x', POPT_ARG_STRING, &optarg, 'x', NULL, NULL }, + { "l-threshold", 'y', POPT_ARG_STRING, &optarg, 'y', NULL, NULL }, { "numeric", 'n', POPT_ARG_NONE, NULL, 'n', NULL, NULL }, { "connection", 'c', POPT_ARG_NONE, NULL, 'c', NULL, NULL }, - { "mcast-interface", '\0', POPT_ARG_STRING, &optarg, - TAG_MCAST_INTERFACE, NULL, NULL }, + { "mcast-interface", '\0', POPT_ARG_STRING, &optarg, TAG_MCAST_INTERFACE, NULL, NULL }, { "syncid", '\0', POPT_ARG_STRING, &optarg, 'I', NULL, NULL }, - { "timeout", '\0', POPT_ARG_NONE, NULL, TAG_TIMEOUT, - NULL, NULL }, + { "timeout", '\0', POPT_ARG_NONE, NULL, TAG_TIMEOUT, NULL, NULL }, { "daemon", '\0', POPT_ARG_NONE, NULL, TAG_DAEMON, NULL, NULL }, { "stats", '\0', POPT_ARG_NONE, NULL, TAG_STATS, NULL, NULL }, { "rate", '\0', POPT_ARG_NONE, NULL, TAG_RATE, NULL, NULL }, - { "thresholds", '\0', POPT_ARG_NONE, NULL, - TAG_THRESHOLDS, NULL, NULL }, - { "persistent-conn", '\0', POPT_ARG_NONE, NULL, - TAG_PERSISTENTCONN, NULL, NULL }, - { "nosort", '\0', POPT_ARG_NONE, NULL, - TAG_NO_SORT, NULL, NULL }, + { "thresholds", '\0', POPT_ARG_NONE, NULL,TAG_THRESHOLDS, NULL, NULL }, + { "persistent-conn", '\0', POPT_ARG_NONE, NULL, TAG_PERSISTENTCONN, NULL, NULL }, + { "nosort", '\0', POPT_ARG_NONE, NULL, TAG_NO_SORT, NULL, NULL }, { "sort", '\0', POPT_ARG_NONE, NULL, TAG_SORT, NULL, NULL }, { "exact", 'X', POPT_ARG_NONE, NULL, 'X', NULL, NULL }, { "ipv6", '6', POPT_ARG_NONE, NULL, '6', NULL, NULL }, { "ops", 'o', POPT_ARG_NONE, NULL, 'o', NULL, NULL }, - { "pe", '\0', POPT_ARG_STRING, &optarg, TAG_PERSISTENCE_ENGINE, - NULL, NULL }, + { "pe", '\0', POPT_ARG_STRING, &optarg, TAG_PERSISTENCE_ENGINE, NULL, NULL }, { "laddr", 'z', POPT_ARG_STRING, &optarg, 'z', NULL, NULL }, { "synproxy", 'j' , POPT_ARG_STRING, &optarg, 'j', NULL, NULL }, + { "add-snat", 'K', POPT_ARG_NONE, NULL, 'K', NULL, NULL}, + { "edit-snat", 'k', POPT_ARG_NONE, NULL, 'k', NULL, NULL}, + { "delete-snat", 'H', POPT_ARG_NONE, NULL, 'H', NULL, NULL}, + { "from", 'F', POPT_ARG_STRING, &optarg, 'F', NULL, NULL }, /* --from */ + { "to", 'T', POPT_ARG_STRING, &optarg, 'T', NULL, NULL }, /* --to */ + { "gw", 'W', POPT_ARG_STRING, &optarg, 'W', NULL, NULL }, /* --gw */ + { "snat-ip", 'U', POPT_ARG_STRING, &optarg, 'U', NULL, NULL }, /* --source */ + { "algo", 'O', POPT_ARG_STRING, &optarg, 'O', NULL, NULL }, /* --algo */ + { "new-gw", 'N', POPT_ARG_STRING, &optarg, 'N', NULL, NULL }, /* --new_gw */ + { "oif", '\0', POPT_ARG_STRING, &optarg, TAG_OUT_DEV, NULL, NULL}, /* --oif */ { NULL, 0, 0, NULL, 0, NULL, NULL } }; context = poptGetContext("ipvsadm", argc, (const char **)argv, options_table, 0); - - if ((c = poptGetNextOpt(context)) < 0) + c = poptGetNextOpt(context); + if (c < 0) tryhelp_exit(argv[0], -1); switch (c) { @@ -500,6 +535,15 @@ parse_options(int argc, char **argv, struct ipvs_command_entry *ce, case 'G': set_command(&ce->cmd, CMD_GETLADDR); break; + case 'K': + set_command(&ce->cmd, CMD_ADDSNAT); + break; + case 'k': + set_command(&ce->cmd, CMD_EDITSNAT); + break; + case 'H': + set_command(&ce->cmd, CMD_DELSNAT); + break; default: tryhelp_exit(argv[0], -1); } @@ -511,10 +555,10 @@ parse_options(int argc, char **argv, struct ipvs_command_entry *ce, set_option(options, OPT_SERVICE); ce->svc.protocol = (c=='t' ? IPPROTO_TCP : IPPROTO_UDP); + /* get vip, port info after -t/-u options */ parse = parse_service(optarg, &ce->svc); if (!(parse & SERVICE_ADDR)) - fail(2, "illegal virtual server " - "address[:port] specified"); + fail(2, "illegal virtual server address[:port] specified"); break; case 'f': set_option(options, OPT_SERVICE); @@ -525,25 +569,25 @@ parse_options(int argc, char **argv, struct ipvs_command_entry *ce, ce->svc.af = AF_INET; ce->svc.protocol = IPPROTO_TCP; ce->svc.fwmark = parse_fwmark(optarg); + if (ce->svc.fwmark == 1) { + *format |= FMT_SNAT_RULE; + } break; case 's': set_option(options, OPT_SCHEDULER); - strncpy(ce->svc.sched_name, - optarg, IP_VS_SCHEDNAME_MAXLEN); + strncpy(ce->svc.sched_name, optarg, IP_VS_SCHEDNAME_MAXLEN); break; case 'p': set_option(options, OPT_PERSISTENT); ce->svc.flags |= IP_VS_SVC_F_PERSISTENT; - ce->svc.timeout = - parse_timeout(optarg, 1, MAX_TIMEOUT); + ce->svc.timeout = parse_timeout(optarg, 1, MAX_TIMEOUT); break; case 'M': set_option(options, OPT_NETMASK); if (ce->svc.af != AF_INET6) { parse = parse_netmask(optarg, &ce->svc.netmask); if (parse != 1) - fail(2, "illegal virtual server " - "persistent mask specified"); + fail(2, "illegal virtual server persistent mask specified"); } else { ce->svc.netmask = atoi(optarg); if ((ce->svc.netmask < 1) || (ce->svc.netmask > 128)) @@ -557,9 +601,9 @@ parse_options(int argc, char **argv, struct ipvs_command_entry *ce, ce->dest.af = t_dest.af; ce->dest.addr = t_dest.addr; ce->dest.port = t_dest.port; - if (!(parse & SERVICE_ADDR)) - fail(2, "illegal real server " - "address[:port] specified"); + if (!(parse & SERVICE_ADDR)) { + fail (2, "illegal real server ddress[:port] specified"); + } /* copy vport to dport if not specified */ if (parse == 1) ce->dest.port = ce->svc.port; @@ -567,18 +611,22 @@ parse_options(int argc, char **argv, struct ipvs_command_entry *ce, case 'i': set_option(options, OPT_FORWARD); ce->dest.conn_flags = IP_VS_CONN_F_TUNNEL; + ce->snat_dest.conn_flags = ce->dest.conn_flags; break; case 'g': set_option(options, OPT_FORWARD); ce->dest.conn_flags = IP_VS_CONN_F_DROUTE; + ce->snat_dest.conn_flags = ce->dest.conn_flags; break; case 'b': set_option(options, OPT_FORWARD); ce->dest.conn_flags = IP_VS_CONN_F_FULLNAT; + ce->snat_dest.conn_flags = ce->dest.conn_flags; break; case 'm': set_option(options, OPT_FORWARD); ce->dest.conn_flags = IP_VS_CONN_F_MASQ; + ce->snat_dest.conn_flags = ce->dest.conn_flags; break; case 'w': set_option(options, OPT_WEIGHT); @@ -607,8 +655,7 @@ parse_options(int argc, char **argv, struct ipvs_command_entry *ce, break; case TAG_MCAST_INTERFACE: set_option(options, OPT_MCAST); - strncpy(ce->daemon.mcast_ifn, - optarg, IP_VS_IFNAME_MAXLEN); + strncpy(ce->daemon.mcast_ifn, optarg, IP_VS_IFNAME_MAXLEN); break; case 'I': set_option(options, OPT_SYNCID); @@ -692,6 +739,137 @@ parse_options(int argc, char **argv, struct ipvs_command_entry *ce, break; } + case 'F': + { + struct ipvs_snat_rule_parse rule; + set_option(options, OPT_SNAT_FROM); + parse = parse_address_mask(optarg, &rule); + if (!parse & SNAT_ADDR) { + fail(2, "illegal from address"); + } + ce->snat_dest.saddr = rule.addr; + if (parse & SNAT_MASK) { + ce->snat_dest.smask = rule.mask; + } + ce->snat_dest.af = rule.af; + break; + } + case 'T': + { + /* rule from address and mask */ + struct ipvs_snat_rule_parse rule; + set_option(options, OPT_SNAT_TO); + parse = parse_address_mask(optarg, &rule); + if (!parse & SNAT_ADDR) { + fail(2, "illegal dest address"); + } + ce->snat_dest.daddr = rule.addr; + if (ce->snat_dest.af != rule.af) { + fail(2, "Address family not consistent"); + } + if (parse & SNAT_MASK) { + ce->snat_dest.dmask = rule.mask; + } + break; + } + case 'W': + { + struct ipvs_snat_rule_parse rule; + set_option(options, OPT_SNAT_GW); + parse = parse_address_mask(optarg, &rule); + if (!parse & SNAT_ADDR) { + fail(2, "illegal orign gateway address"); + } + ce->snat_dest.gw = rule.addr; + if (ce->snat_dest.af != rule.af) { + fail(2, "Address family not consistent"); + } + break; + } + case 'U': + { + struct ipvs_snat_rule_parse rule; + set_option(options, OPT_SNAT_SOURCE); + if (optarg) { + char *portp = NULL; + portp = strchr(optarg, '-'); + if (portp == NULL) { + parse = parse_address_mask(optarg, &rule); + if (!parse & SNAT_ADDR) { + fail(2, "illegal snatip address"); + } + if (ce->snat_dest.af != rule.af) { + fail(2, "Address family not consistent"); + } + ce->snat_dest.min_ip = rule.addr; + ce->snat_dest.max_ip = ce->snat_dest.min_ip; + } else { + *portp = '\0'; + portp++; + parse = parse_address_mask(optarg, &rule); + if (!parse & SNAT_ADDR) { + fail(2, "illegal min ip address"); + } + if (ce->snat_dest.af != rule.af) { + fail(2, "Address family not consistent"); + } + ce->snat_dest.min_ip = rule.addr; + parse = parse_address_mask(portp, &rule); + if (!parse & SNAT_ADDR) { + fail(2, "illegal max ip address"); + } + if (ce->snat_dest.af != rule.af) { + fail(2, "Address family not consistent"); + } + ce->snat_dest.max_ip = rule.addr; + if (ce->snat_dest.af == AF_INET) { + if (ce->snat_dest.max_ip.ip < ce->snat_dest.min_ip.ip) { + fail(2, "illegal snat source address:max ip smaller than min ip"); + } + } + } + } + break; + } + case 'O': + { + set_option(options, OPT_SNAT_ALGO); + if (!memcmp(optarg , "sh" , strlen("sh"))) { + ce->snat_dest.algo = IPVS_SNAT_IPS_PERSITENT; + } else if(!memcmp(optarg , "sdh" , strlen("sdh"))) { + ce->snat_dest.algo = IPVS_SNAT_IPS_NORMAL; + } else if (!memcmp(optarg, "random", strlen("random"))) { + ce->snat_dest.algo = IPVS_SNAT_IPS_RANDOM; + } else { + fail(2 , "unkown ip select algo, shoule be one of [sh, sdh, ramdom]\n"); + } + break; + } + case 'N': + { + struct ipvs_snat_rule_parse rule; + set_option(options, OPT_SNAT_NEWGW); + parse = parse_address_mask(optarg, &rule); + if (!parse & SNAT_ADDR) { + fail(2, "illegal new next gateway address"); + } + if (ce->snat_dest.af != rule.af) { + fail(2, "Address family not consistent"); + } + ce->snat_dest.new_gw = rule.addr; + break; + } + case TAG_OUT_DEV: + { + set_option(options, OPT_SNAT_OUTDEV); + if (optarg) { + if (strlen(optarg) >= IP_VS_IFNAME_MAXLEN) { + fail(2, "snat out device name too long"); + } + strncpy(ce->snat_dest.out_dev, optarg, IP_VS_IFNAME_MAXLEN); + } + } + break; default: fail(2, "invalid option `%s'", poptBadOption(context, POPT_BADOPTION_NOALIAS)); @@ -732,7 +910,6 @@ parse_options(int argc, char **argv, struct ipvs_command_entry *ce, } - static int restore_table(int argc, char **argv, int reading_stdin) { int result = 0; @@ -756,7 +933,7 @@ static int restore_table(int argc, char **argv, int reading_stdin) static int process_options(int argc, char **argv, int reading_stdin) { struct ipvs_command_entry ce; - unsigned int options = OPT_NONE; + unsigned long options = OPT_NONE; unsigned int format = FMT_NONE; int result = 0; @@ -769,8 +946,13 @@ static int process_options(int argc, char **argv, int reading_stdin) /* Set the default persistent granularity to /32 mask */ ce.svc.netmask = ((u_int32_t) 0xffffffff); - if (parse_options(argc, argv, &ce, &options, &format)) + /* lvs snat default value */ + ce.snat_dest.algo = IPVS_SNAT_IPS_NORMAL; + ce.snat_dest.conn_flags = IP_VS_CONN_F_FULLNAT; + + if (parse_options(argc, argv, &ce, &options, &format)) { return -1; + } generic_opt_check(ce.cmd, options); @@ -778,8 +960,7 @@ static int process_options(int argc, char **argv, int reading_stdin) /* Make sure that port zero service is persistent */ if (!ce.svc.fwmark && !ce.svc.port && !(ce.svc.flags & IP_VS_SVC_F_PERSISTENT)) - fail(2, "Zero port specified " - "for non-persistent service"); + fail(2, "Zero port specified for non-persistent service"); if (ce.svc.flags & IP_VS_SVC_F_ONEPACKET && !ce.svc.fwmark && ce.svc.protocol != IPPROTO_UDP) @@ -866,6 +1047,16 @@ static int process_options(int argc, char **argv, int reading_stdin) result = ipvs_del_dest(&ce.svc, &ce.dest); break; + case CMD_ADDSNAT: + result = ipvs_add_snat_dest(&ce.svc, &ce.snat_dest); + break; + case CMD_EDITSNAT: + result = ipvs_update_snat_dest(&ce.svc, &ce.snat_dest); + break; + case CMD_DELSNAT: + result = ipvs_del_snat_dest(&ce.svc, &ce.snat_dest); + break; + case CMD_TIMEOUT: result = ipvs_set_timeout(&ce.timeout); break; @@ -975,6 +1166,67 @@ static int parse_netmask(char *buf, u_int32_t *addr) return 1; } +/* + * Get source ip and mask from the argument + */ +static int parse_address_mask(char* buf, struct ipvs_snat_rule_parse *rule) +{ + char *portp = NULL; + long portn; + int result=SNAT_NONE; + struct in_addr inaddr; + struct in6_addr inaddr6; + + if (buf == NULL || str_is_digit(buf)) + return SNAT_NONE; + + if (buf[0] == '[') { + buf++; + portp = strchr(buf, ']'); + if (portp == NULL) { + return SNAT_NONE; + } + *portp = '\0'; + portp++; + if (*portp == '/') { + *portp = '\0'; + } else { + return SNAT_NONE; + } + } + + if (inet_pton(AF_INET6, buf, &inaddr6) > 0) { + rule->addr.in6 = inaddr6; + rule->mask = 128; + rule->af = AF_INET6; + fail(2, "Not support IPv6"); + } else { + portp = strrchr(buf, '/'); + if (portp != NULL) { + *portp = '\0'; + } + rule->af = AF_INET; + if (inet_aton(buf, &inaddr) != 0) { + rule->addr.ip = inaddr.s_addr; + } else if (host_to_addr(buf, &inaddr) != -1) { + rule->addr.ip = inaddr.s_addr; + } else { + return SNAT_NONE; + } + } + + result |= SNAT_ADDR; + if (portp != NULL) { + result |= SNAT_MASK; + if ((portn = string_to_number(portp+1, 0, 32)) != -1) { + rule->mask= portn; + } else { + return SNAT_NONE; + } + } + + return result; +} /* * Get IP address and port from the argument. @@ -1043,33 +1295,30 @@ parse_service(char *buf, ipvs_service_t *svc) static void -generic_opt_check(int command, int options) +generic_opt_check(int command, unsigned long options) { int i, j; int last = 0, count = 0; /* Check that commands are valid with options. */ i = command - CMD_NONE -1; - for (j = 0; j < NUMBER_OF_OPT; j++) { if (!(options & (1< 1; option >>= 1, ptr++); @@ -1094,7 +1343,7 @@ set_command(int *cmd, const int newcmd) } static void -set_option(unsigned int *options, unsigned int option) +set_option(unsigned long *options, unsigned long option) { if (*options & option) fail(2, "multiple '%s' options specified", opt2name(option)); @@ -1136,11 +1385,13 @@ static void usage_exit(const char *program, const int exit_status) " %s --set tcp tcpfin udp\n" " %s --start-daemon state [--mcast-interface interface] [--syncid sid]\n" " %s --stop-daemon state\n" + " %s -K|k -f service-address -F src-addrress/mask [-T dest-address/mask] [-W gw] -U minip-maxip [-O algo] [--oif dev] [-N new-gw] [-b]\n" + " %s -H -f service-address -F src-address/mask [-T dest-address/mask] [-W gw] [-U minip-maxip] [-O algo] [--oif dev] [-N new-gw] [-b]\n" " %s -h\n\n", program, program, program, program, program, program, program, program, program, program, - program, program, program, program, program); + program, program, program, program, program, program, program); fprintf(stream, "Commands:\n" @@ -1162,6 +1413,9 @@ static void usage_exit(const char *program, const int exit_status) " --set tcp tcpfin udp set connection timeout values\n" " --start-daemon start connection sync daemon\n" " --stop-daemon stop connection sync daemon\n" + " --add-snat -K add lvs snat rule\n" + " --edit-snat -k edit lvs snat rule\n" + " --delete-snat -H delete lvs snat rule\n" " --help -h display this help message\n\n" ); @@ -1198,7 +1452,14 @@ static void usage_exit(const char *program, const int exit_status) " --nosort disable sorting output of service/server entries\n" " --sort does nothing, for backwards compatibility\n" " --ops -o one-packet scheduling\n" - " --numeric -n numeric output of addresses and ports\n", + " --numeric -n numeric output of addresses and ports\n" + " --from -F lvs snat rule source address/mask\n" + " --to -T lvs snat rule dest address/mask\n" + " --gw -W lvs snat rule orign gateway\n" + " --snat-ip -U lvs snat rule snat ip pool\n" + " --algo -O lvs snat rule source ip choice algo, must be one of (sh, sdh, random), default sdh\n" + " --new-gw -N lvs snat rule new next gateway\n" + " --oif lvs snat output dev\n", DEF_SCHED); exit(exit_status); @@ -1481,6 +1742,11 @@ static void print_title(unsigned int format) " -> RemoteAddress:Port\n", "Prot LocalAddress:Port", "Weight", "PersistConn", "ActiveConn", "InActConn"); + else if (format & FMT_SNAT_RULE) { + printf("Prot LocalAddress:Port Scheduler Flags\n" + " -> %-20s%-20s%-17s%-16s%-32s%-8s%-17s%-10s%-10s\n", + "SourceAddr", "DestAddr", "GW", "Oif", "SnatIp", "Algo", "NewGW", "ActConn", "InActConn"); + } else if (!(format & FMT_RULE)) printf("Prot LocalAddress:Port Scheduler Flags\n" " -> RemoteAddress:Port Forward Weight ActiveConn InActConn\n"); @@ -1594,7 +1860,6 @@ print_service_entry(ipvs_service_entry_t *se, unsigned int format) for (i = 0; i < d->num_dests; i++) { char *dname; ipvs_dest_entry_t *e = &d->entrytable[i]; - if (!(dname = addrport_to_anyname(se->af, &(e->addr), ntohs(e->port), se->protocol, format))) { fprintf(stderr, "addrport_to_anyname fails\n"); @@ -1604,8 +1869,29 @@ print_service_entry(ipvs_service_entry_t *se, unsigned int format) dname[28] = '\0'; if (format & FMT_RULE) { - printf("-a %s -r %s %s -w %d\n", svc_name, dname, - fwd_switch(e->conn_flags), e->weight); + if (se->fwmark == 1) { + char tmp_rule[512] = {0}; + char src_net[128] = {0}; + char dst_net[128] = {0}; + char gw[128] = {0}; + char ip_pool[256] = {0}; + char new_gw[128] = {0}; + addrmask_to_str(se->af, &e->snat_rule.saddr, e->snat_rule.smask, src_net); + addrmask_to_str(se->af, &e->snat_rule.daddr, e->snat_rule.dmask, dst_net); + addr_to_str(se->af, &e->snat_rule.gw, gw); + addrpool_to_str(se->af, &e->snat_rule.min_ip, &e->snat_rule.max_ip, ip_pool); + addr_to_str(se->af, &e->snat_rule.new_gw, new_gw); + + sprintf(tmp_rule, "-K %s -F %s -T %s -W %s -U %s -O %s -N %s", svc_name, src_net, dst_net, gw, ip_pool, + ip_select_algo_name(e->snat_rule.algo), new_gw); + if (strlen(e->snat_rule.out_dev)) { + printf("%s --oif %s\n",tmp_rule, e->snat_rule.out_dev); + } else { + printf("%s\n", tmp_rule); + } + } else { + printf("-a %s -r %s %s -w %d\n", svc_name, dname, fwd_switch(e->conn_flags), e->weight); + } } else if (format & FMT_STATS) { printf(" -> %-28s", dname); print_largenum(e->stats.conns, format); @@ -1630,10 +1916,32 @@ print_service_entry(ipvs_service_entry_t *se, unsigned int format) printf(" -> %-28s %-9u %-11u %-10u %-10u\n", dname, e->weight, e->persistconns, e->activeconns, e->inactconns); - } else + } else if (format & FMT_SNAT_RULE) { + char src_net[128] = {0}; + char dst_net[128] = {0}; + char gw[128] = {0}; + char ip_pool[256] = {0}; + char new_gw[128] = {0}; + addrmask_to_str(se->af, &e->snat_rule.saddr, e->snat_rule.smask, src_net); + addrmask_to_str(se->af, &e->snat_rule.daddr, e->snat_rule.dmask, dst_net); + addr_to_str(se->af, &e->snat_rule.gw, gw); + addrpool_to_str(se->af, &e->snat_rule.min_ip, &e->snat_rule.max_ip, ip_pool); + addr_to_str(se->af, &e->snat_rule.new_gw, new_gw); + printf(" -> %-20s%-20s%-17s%-16s%-32s%-8s%-17s%-10u%-10u\n", + src_net, + dst_net, + gw, + e->snat_rule.out_dev, + ip_pool, + ip_select_algo_name(e->snat_rule.algo), + new_gw, + e->activeconns, + e->inactconns); + } else { printf(" -> %-28s %-7s %-6d %-10u %-10u\n", dname, fwd_name(e->conn_flags), e->weight, e->activeconns, e->inactconns); + } free(dname); } free(d); @@ -1644,7 +1952,7 @@ static void list_laddrs_print_title(void) printf("%-20s %-8s %-20s %-10s %-10s\n" , "VIP:VPORT" , "TOTAL" , - "SNAT_IP", + "LADDR", "CONFLICTS", "CONNS" ); } @@ -1775,6 +2083,8 @@ static void list_service(ipvs_service_t *svc, unsigned int format) static void list_all(unsigned int format) { struct ip_vs_get_services *get; + struct ip_vs_service_entry *snat_service = NULL; + int print_able = 0; int i; if (!(format & FMT_RULE)) @@ -1789,9 +2099,28 @@ static void list_all(unsigned int format) if (!(format & FMT_NOSORT)) ipvs_sort_services(get, ipvs_cmp_services); + for (i = 0; i < get->num_services; i++) { + if (get->entrytable[i].fwmark == 1) { + snat_service = &get->entrytable[i]; + continue; + } + if (print_able == 0) { + print_able = 1; print_title(format); - for (i = 0; i < get->num_services; i++) + } print_service_entry(&get->entrytable[i], format); + } + + if (snat_service) { + if (!(format & FMT_RULE)) { /* if none ./ipvsadm -S */ + int tmp_format = format; + tmp_format |= FMT_SNAT_RULE; + print_title(tmp_format); + print_service_entry(snat_service, tmp_format); + } else { + print_service_entry(snat_service, format); + } + } free(get); } @@ -1935,6 +2264,65 @@ addrport_to_anyname(int af, const void *addr, unsigned short port, return buf; } +static inline char *ip_select_algo_name(unsigned algo) +{ + char *algo_name = NULL; + + switch (algo) { + case IPVS_SNAT_IPS_NORMAL: + algo_name = "sdh"; + break; + case IPVS_SNAT_IPS_PERSITENT: + algo_name = "sh"; + break; + case IPVS_SNAT_IPS_RANDOM: + algo_name = "random"; + break; + } + + return algo_name; +} + +static void addrmask_to_str(int af, const union nf_inet_addr *addr, unsigned short mask, char *output) +{ + char pbuf[INET6_ADDRSTRLEN] = {0}; + if (af == AF_INET) { + inet_ntop(af, &addr->in, pbuf, sizeof(pbuf)); + sprintf(output, "%s/%d", pbuf, mask); + } else { + inet_ntop(af, &addr->in6, pbuf, sizeof(pbuf)); + sprintf(output, "[%s]/%d", pbuf, mask); + } +} + +static void addr_to_str(int af, const union nf_inet_addr *addr, char *output) +{ + char pbuf[INET6_ADDRSTRLEN]; + if (af == AF_INET) { + sprintf(output, "%s", inet_ntop(af, (void *)&(addr->in), pbuf, sizeof(pbuf))); + } else { + sprintf(output, "[%s]", inet_ntop(af, (void *)&(addr->in6), pbuf, sizeof(pbuf))); + } +} + +static void addrpool_to_str(int af, const union nf_inet_addr* minaddr, const union nf_inet_addr* maxaddr, char *output) +{ + char min_buf[INET6_ADDRSTRLEN] = {0}; + char max_buf[INET6_ADDRSTRLEN] = {0}; + if (af == AF_INET) { + inet_ntop(af, (void *)&(minaddr->in), min_buf, sizeof(min_buf)); + inet_ntop(af, (void *)&(maxaddr->in), max_buf, sizeof(max_buf)); + } else { + inet_ntop(af, (void *)&(minaddr->in6), min_buf, sizeof(min_buf)); + inet_ntop(af, (void *)&(maxaddr->in6), max_buf, sizeof(max_buf)); + } + + if (!strcmp(min_buf, max_buf)) { + sprintf(output, "%s", min_buf); + } else { + sprintf(output, "%s-%s", min_buf, max_buf); + } +} static int str_is_digit(const char *str) { diff --git a/tools/keepalived/doc/samples/keepalived.conf.snat_gateway b/tools/keepalived/doc/samples/keepalived.conf.snat_gateway new file mode 100644 index 00000000..d2a11a06 --- /dev/null +++ b/tools/keepalived/doc/samples/keepalived.conf.snat_gateway @@ -0,0 +1,29 @@ +virtual_server fwmark 1 { + snat_rule { + from 192.168.40.0/24 + oif eth2 + snat_ip 1.1.3.252-1.1.3.254 + algo random + } + + snat_rule { + from 10.0.0.0/8 + oif eth1 + snat_ip 1.1.2.252-1.1.2.254 + algo sdh + } + + snat_rule { + from 10.0.0.0/8 + to 1.1.0.0/16 + snat_ip 1.1.2.252-1.1.2.254 + algo sdh + } + + snat_rule { + from 10.1.0.0/16 + new_gw 1.1.2.1 + snat_ip 1.1.2.252-1.1.2.254 + algo sh + } +} diff --git a/tools/keepalived/keepalived/check/check_api.c b/tools/keepalived/keepalived/check/check_api.c index a208690d..dec78839 100644 --- a/tools/keepalived/keepalived/check/check_api.c +++ b/tools/keepalived/keepalived/check/check_api.c @@ -217,3 +217,105 @@ install_checkers_keyword(void) install_http_check_keyword(); install_ssl_check_keyword(); } + +static char * +ip_select_algo_name(unsigned algo) +{ + char *algo_name = NULL; + + switch (algo) { + case IPVS_SNAT_IPS_NORMAL: + algo_name = "sdh"; + break; + case IPVS_SNAT_IPS_PERSITENT: + algo_name = "sh"; + break; + case IPVS_SNAT_IPS_RANDOM: + algo_name = "random"; + break; + } + + return algo_name; +} + +static void +addrmask_to_str(int af, const union nf_inet_addr *addr, + unsigned short mask, char *output) +{ + char pbuf[INET6_ADDRSTRLEN] = {0}; + if (af == AF_INET) { + inet_ntop(af, &addr->in, pbuf, sizeof(pbuf)); + sprintf(output, "%s/%d", pbuf, mask); + } else { + inet_ntop(af, &addr->in6, pbuf, sizeof(pbuf)); + sprintf(output, "[%s]/%d", pbuf, mask); + } +} + +static void +addr_to_str(int af, const union nf_inet_addr *addr, char *output) +{ + char pbuf[INET6_ADDRSTRLEN]; + if (af == AF_INET) { + sprintf(output, "%s", inet_ntop(af, (void *)&(addr->in), pbuf, sizeof(pbuf))); + } else { + sprintf(output, "[%s]", inet_ntop(af, (void *)&(addr->in6), pbuf, sizeof(pbuf))); + } +} + +static void +addrpool_to_str(int af, const union nf_inet_addr* minaddr, + const union nf_inet_addr* maxaddr, char *output) +{ + char min_buf[INET6_ADDRSTRLEN] = {0}; + char max_buf[INET6_ADDRSTRLEN] = {0}; + if (af == AF_INET) { + inet_ntop(af, (void *)&(minaddr->in), min_buf, sizeof(min_buf)); + inet_ntop(af, (void *)&(maxaddr->in), max_buf, sizeof(max_buf)); + } else { + inet_ntop(af, (void *)&(minaddr->in6), min_buf, sizeof(min_buf)); + inet_ntop(af, (void *)&(maxaddr->in6), max_buf, sizeof(max_buf)); + } + + if (!strcmp(min_buf, max_buf)) { + sprintf(output, "%s", min_buf); + } else { + sprintf(output, "%s-%s", min_buf, max_buf); + } +} + +void +print_snat_rule(int cmd, snat_rule *rs) +{ + char output[512] = {0}; + + char src_mask[128] = {0}; + char dst_mask[128] = {0}; + char gw[128] = {0}; + char new_gw[128] = {0}; + char snatip[256] = {0}; + + addrmask_to_str(rs->af, &rs->saddr, rs->smask, src_mask); + addrmask_to_str(rs->af, &rs->daddr, rs->dmask, dst_mask); + addr_to_str(rs->af, &rs->gw, gw); + addrpool_to_str(rs->af, &rs->minip, &rs->maxip, snatip); + addr_to_str(rs->af, &rs->new_gw, new_gw); + sprintf(output, + "snat rule[-F %s -T %s -W %s --oif %s -U %s -O %s -N %s]", + src_mask, + dst_mask, + gw, + rs->out_dev, + snatip, + ip_select_algo_name(rs->algo), + new_gw); + + if (cmd == LVS_CMD_DEL_SNATDEST) { + log_message(LOG_INFO, "Removing %s", output); + } else if (cmd == LVS_CMD_ADD_SNATDEST) { + log_message(LOG_INFO, "Adding %s", output); + } else { + log_message(LOG_INFO, "%s", output); + } +} + diff --git a/tools/keepalived/keepalived/check/check_daemon.c b/tools/keepalived/keepalived/check/check_daemon.c index 7587a7d9..ef0da1ca 100644 --- a/tools/keepalived/keepalived/check/check_daemon.c +++ b/tools/keepalived/keepalived/check/check_daemon.c @@ -107,10 +107,10 @@ start_check(void) stop_check(); return; } - /* Processing differential configuration parsing */ - if (reload) + if (reload) { clear_diff_services(); + } /* Initialize IPVS topology */ if (!init_services()) { @@ -169,7 +169,6 @@ reload_check_thread(thread_t * thread) { /* set the reloading flag */ SET_RELOAD; - /* Signals handling */ signal_reset(); signal_handler_destroy(); diff --git a/tools/keepalived/keepalived/check/check_data.c b/tools/keepalived/keepalived/check/check_data.c index 80c18284..c3b9ffae 100644 --- a/tools/keepalived/keepalived/check/check_data.c +++ b/tools/keepalived/keepalived/check/check_data.c @@ -332,10 +332,23 @@ alloc_vs(char *ip, char *port) new->quorum_down = NULL; new->quorum = 1; new->hysteresis = 0; + new->abs_priority = 0; + new->cur_max_weight = -1; new->quorum_state = UP; new->local_addr_gname = NULL; new->vip_bind_dev = NULL; + /* snat vs init special info */ + if (IS_SNAT_SVC(new)) { + new->loadbalancing_kind = IP_VS_CONN_F_FULLNAT; + int tmp_size = sizeof (new->sched); + int str_len = strlen(DEFAULT_SNAT_SCHED); + if (tmp_size > str_len) { + tmp_size = str_len; + } + strncpy(new->sched, DEFAULT_SNAT_SCHED, tmp_size); + } + list_add(check_data->vs, new); } @@ -344,13 +357,26 @@ void alloc_ssvr(char *ip, char *port) { virtual_server *vs = LIST_TAIL_DATA(check_data->vs); - vs->s_svr = (real_server *) MALLOC(sizeof (real_server)); vs->s_svr->weight = 1; vs->s_svr->iweight = 1; inet_stosockaddr(ip, port, &vs->s_svr->addr); } +static void +free_snat_rule(void *data) +{ + snat_rule *rule = data; + FREE(rule); +} + +static void +dump_snat_rule(void *data) +{ + snat_rule *rs = (snat_rule *)data; + print_snat_rule(999, rs); +} + /* Real server facility functions */ static void free_rs(void *data) @@ -392,6 +418,10 @@ alloc_rs(char *ip, char *port) virtual_server *vs = LIST_TAIL_DATA(check_data->vs); real_server *new; + if (IS_SNAT_SVC(vs)) { + return; + } + new = (real_server *) MALLOC(sizeof (real_server)); inet_stosockaddr(ip, port, &new->addr); @@ -404,6 +434,23 @@ alloc_rs(char *ip, char *port) list_add(vs->rs, new); } +void +alloc_snat_rule(void) +{ + virtual_server *vs = LIST_TAIL_DATA(check_data->vs); + if (NOT_SNAT_SVC(vs)) { + return; + } + snat_rule *new = (snat_rule *)MALLOC(sizeof(snat_rule)); + new->algo = IPVS_SNAT_IPS_NORMAL; + new->conn_flags = IP_VS_CONN_F_FULLNAT; + + if (LIST_ISEMPTY(vs->rs)) { + vs->rs = alloc_list(free_snat_rule, dump_snat_rule); + } + list_add(vs->rs, new); +} + /* data facility functions */ check_conf_data * alloc_check_data(void) diff --git a/tools/keepalived/keepalived/check/check_parser.c b/tools/keepalived/keepalived/check/check_parser.c index e491cc06..b4ebcb38 100644 --- a/tools/keepalived/keepalived/check/check_parser.c +++ b/tools/keepalived/keepalived/check/check_parser.c @@ -33,6 +33,106 @@ #include "utils.h" #include "ipwrapper.h" +static int +str2number(const char *s, int min, int max) +{ + int number; + char *end; + + number = (int) strtol(s, &end, 10); + if (*end == '\0' && end != s) { + /* + * We parsed a number, let's see if we want this. + * If max <= min then ignore ranges + */ + if (max <= min || (min <= number && number <= max)) { + return number; + } else { + return -1; + } + } else { + return -1; + } +} + +static int str_is_digit(const char *str) +{ + size_t offset; + size_t top; + + top = strlen(str); + for (offset=0; offset 0) { + //addrmask->addr.in6 = inaddr6; + //addrmask->mask = 128; + //addrmask->af = AF_INET6; + log_message(LOG_ERR, "Not support IPv6"); + return SNAT_NONE; + } else { + portp = strrchr(buf, '/'); + if (portp != NULL) { + *portp = '\0'; + } + addrmask->af = AF_INET; + if (inet_aton(buf, &inaddr) != 0) { + addrmask->addr.ip = inaddr.s_addr; + } else { + return SNAT_NONE; + } + } + + result |= SNAT_ADDR; + if (portp != NULL) { + if ((portn = str2number(portp+1, 0, 32)) != -1) { + addrmask->mask= portn; + result |= SNAT_MASK; + } else { + return SNAT_NONE; + } + } + + return result; +} + + /* SSL handlers */ static void ssl_handler(vector strvec) @@ -83,8 +183,18 @@ static void delay_handler(vector strvec) { virtual_server *vs = LIST_TAIL_DATA(check_data->vs); - vs->delay_loop = atoi(VECTOR_SLOT(strvec, 1)) * TIMER_HZ; + vs->delay_loop = atoi(VECTOR_SLOT(strvec, 1)) * TIMER_HZ;; +} + +/* new add 20140319 : for keyword abs_priority */ +static void +abspriority_handler(vector strvec) +{ + virtual_server *vs = LIST_TAIL_DATA(check_data->vs); + log_message(LOG_INFO, "abs_priority mode open"); + vs->abs_priority = 1; } + static void lbalgo_handler(vector strvec) { @@ -147,6 +257,7 @@ static void proto_handler(vector strvec) { virtual_server *vs = LIST_TAIL_DATA(check_data->vs); + char *str = VECTOR_SLOT(strvec, 1); vs->service_type = (!strcmp(str, "TCP")) ? IPPROTO_TCP : IPPROTO_UDP; } @@ -170,6 +281,173 @@ ssvr_handler(vector strvec) alloc_ssvr(VECTOR_SLOT(strvec, 1), VECTOR_SLOT(strvec, 2)); } +static void +snat_rule_handler(vector strvec) +{ + alloc_snat_rule(); +} + +static void +snat_from_handler(vector strvec) +{ + snat_rule *rule = NULL; + snat_rule_addr_mask addrmask; + + char *str = VECTOR_SLOT(strvec, 1); + virtual_server *vs = LIST_TAIL_DATA(check_data->vs); + if (IS_SNAT_SVC(vs)) { + rule = LIST_TAIL_DATA(vs->rs); + int result = parse_address_mask(str, &addrmask); + if (result & SNAT_ADDR) { + rule->saddr = addrmask.addr; + } + if (result & SNAT_MASK) { + rule->smask = addrmask.mask; + } + rule->af = addrmask.af; + } +} + +static void +snat_to_handler(vector strvec) +{ + snat_rule *rule = NULL; + snat_rule_addr_mask addrmask; + + char *str = VECTOR_SLOT(strvec, 1); + virtual_server *vs = LIST_TAIL_DATA(check_data->vs); + if (IS_SNAT_SVC(vs)) { + rule = LIST_TAIL_DATA(vs->rs); + int result = parse_address_mask(str, &addrmask); + if (result & SNAT_ADDR) { + rule->daddr = addrmask.addr; + } + if (result & SNAT_MASK) { + rule->dmask = addrmask.mask; + } + } +} + +static void +snat_gw_handler(vector strvec) +{ + snat_rule *rule = NULL; + snat_rule_addr_mask addrmask; + + char *str = VECTOR_SLOT(strvec, 1); + virtual_server *vs = LIST_TAIL_DATA(check_data->vs); + if (IS_SNAT_SVC(vs)) { + rule = LIST_TAIL_DATA(vs->rs); + int result = parse_address_mask(str, &addrmask); + if (result & SNAT_ADDR) { + rule->gw = addrmask.addr; + } + } +} + +static void +snat_oif_handler(vector strvec) +{ + snat_rule *rule = NULL; + char *str = VECTOR_SLOT(strvec, 1); + virtual_server *vs = LIST_TAIL_DATA(check_data->vs); + if (IS_SNAT_SVC(vs)) { + rule = LIST_TAIL_DATA(vs->rs); + if (strlen(str) < IP_VS_IFNAME_MAXLEN) { + strcpy(rule->out_dev, str); + } else { + log_message(LOG_ERR, "out dev name too long\n"); + } + } +} + +static void +snat_algo_handler(vector strvec) +{ + snat_rule *rule = NULL; + char *str = VECTOR_SLOT(strvec, 1); + virtual_server *vs = LIST_TAIL_DATA(check_data->vs); + if (IS_SNAT_SVC(vs)) { + rule = LIST_TAIL_DATA(vs->rs); + if (!memcmp(str , "sh" , strlen("sh"))) { + rule->algo = IPVS_SNAT_IPS_PERSITENT; + } else if(!memcmp(str , "sdh" , strlen("sdh"))) { + rule->algo = IPVS_SNAT_IPS_NORMAL; + } else if (!memcmp(str, "random", strlen("random"))) { + rule->algo = IPVS_SNAT_IPS_RANDOM; + } else { + log_message(LOG_ERR, "unkown algo,shoule be one of [sh, sdh, ramdom]\n"); + } + } +} + +static void +snat_newgw_handler(vector strvec) +{ + snat_rule *rule = NULL; + snat_rule_addr_mask addrmask; + + char *str = VECTOR_SLOT(strvec, 1); + virtual_server *vs = LIST_TAIL_DATA(check_data->vs); + if (IS_SNAT_SVC(vs)) { + rule = LIST_TAIL_DATA(vs->rs); + int result = parse_address_mask(str, &addrmask); + if (result & SNAT_ADDR) { + rule->new_gw= addrmask.addr; + } + } +} + +static void +snat_snatip_handler(vector strvec) +{ + char *portp = NULL; + snat_rule *rule = NULL; + snat_rule_addr_mask addrmask; + int result; + + char *str = VECTOR_SLOT(strvec, 1); + virtual_server *vs = LIST_TAIL_DATA(check_data->vs); + if (IS_SNAT_SVC(vs)) { + rule = LIST_TAIL_DATA(vs->rs); + portp = strchr(str, '-'); + if (portp == NULL) { + result = parse_address_mask(str, &addrmask); + if (result & SNAT_ADDR) { + rule->minip = addrmask.addr; + rule->maxip = rule->minip; + } else { + log_message(LOG_ERR, "snatip illegal\n"); + return; + } + } else { + *portp = '\0'; + portp++; + result = parse_address_mask(str, &addrmask); + if (result & SNAT_ADDR) { + rule->minip = addrmask.addr; + } else { + log_message(LOG_ERR, "snatip minip illegal\n"); + return; + } + + result = parse_address_mask(portp, &addrmask); + if (result & SNAT_ADDR) { + rule->maxip = addrmask.addr; + } else { + log_message(LOG_ERR, "snatip maxip illegal\n"); + return; + } + + if (rule->af == AF_INET) { + if (rule->maxip.ip < rule->minip.ip) { + log_message(LOG_ERR, "maxip smaller than minip\n"); + } + } + } + } +} + /* Real Servers handlers */ static void rs_handler(vector strvec) @@ -277,7 +555,6 @@ static void laddr_gname_handler(vector strvec) { virtual_server *vs = LIST_TAIL_DATA(check_data->vs); - vs->local_addr_gname = set_value(strvec); } static void @@ -312,6 +589,7 @@ check_init_keywords(void) /* Virtual server mapping */ install_keyword_root("virtual_server_group", &vsg_handler); install_keyword_root("virtual_server", &vs_handler); + install_keyword("abs_priority", &abspriority_handler); /* new add 20140319 */ install_keyword("delay_loop", &delay_handler); install_keyword("lb_algo", &lbalgo_handler); install_keyword("lvs_sched", &lbalgo_handler); @@ -332,6 +610,18 @@ check_init_keywords(void) install_keyword("quorum", &quorum_handler); install_keyword("hysteresis", &hysteresis_handler); + /* snat rule mapping */ + install_keyword("snat_rule", &snat_rule_handler); + install_sublevel(); + install_keyword("from", &snat_from_handler); + install_keyword("to", &snat_to_handler); + install_keyword("gw", &snat_gw_handler); + install_keyword("oif", &snat_oif_handler); + install_keyword("snat_ip", &snat_snatip_handler); + install_keyword("algo", &snat_algo_handler); + install_keyword("new_gw", &snat_newgw_handler); + install_sublevel_end(); + /* Real server mapping */ install_keyword("sorry_server", &ssvr_handler); install_keyword("real_server", &rs_handler); diff --git a/tools/keepalived/keepalived/check/ipvswrapper.c b/tools/keepalived/keepalived/check/ipvswrapper.c index f9e41544..15f9339b 100644 --- a/tools/keepalived/keepalived/check/ipvswrapper.c +++ b/tools/keepalived/keepalived/check/ipvswrapper.c @@ -344,6 +344,7 @@ static ipvs_service_t *srule; static ipvs_dest_t *drule; static ipvs_daemon_t *daemonrule; static ipvs_laddr_t *laddr_rule; +static ipvs_snat_dest_t *sdrule; /* Initialization helpers */ int @@ -364,6 +365,7 @@ ipvs_start(void) drule = (ipvs_dest_t *) MALLOC(sizeof(ipvs_dest_t)); daemonrule = (ipvs_daemon_t *) MALLOC(sizeof(ipvs_daemon_t)); laddr_rule = (ipvs_laddr_t *) MALLOC(sizeof(ipvs_laddr_t)); + sdrule = (ipvs_snat_dest_t *)MALLOC(sizeof(ipvs_snat_dest_t)); return IPVS_SUCCESS; } @@ -375,6 +377,7 @@ ipvs_stop(void) FREE(drule); FREE(daemonrule); FREE(laddr_rule); + FREE(sdrule); ipvs_close(); } @@ -417,14 +420,28 @@ ipvs_talk(int cmd) break; case IP_VS_SO_SET_EDITDEST: if ((result = ipvs_update_dest(srule, drule)) && - (errno == ENOENT)) + (errno == ENOENT)) { result = ipvs_add_dest(srule, drule); + } + break; + case IP_VS_SO_SET_ADDSNAT: + result = ipvs_add_snat_dest(srule, sdrule); + break; + case IP_VS_SO_SET_EDITSNAT: + if ((result = ipvs_update_snat_dest(srule, sdrule)) && + (errno == ENOENT)) { + result = ipvs_add_snat_dest(srule, sdrule); + } + break; + case IP_VS_SO_SET_DELSNAT: + result = ipvs_del_snat_dest(srule, sdrule); break; } - if (result) + if (result) { log_message(LOG_INFO, "IPVS: %s", ipvs_strerror(errno)); } +} int ipvs_syncd_cmd(int cmd, char *ifname, int state, int syncid) @@ -494,11 +511,13 @@ ipvs_group_cmd(int cmd, list vs_group, real_server * rs, char * vsgname) vsg_entry = ELEMENT_DATA(e); srule->af = vsg_entry->addr.ss_family; if (vsg_entry->addr.ss_family == AF_INET6) { - if (srule->netmask == 0xffffffff) + if (srule->netmask == 0xffffffff) { srule->netmask = 128; + } inet_sockaddrip6(&vsg_entry->addr, &srule->addr.in6); - } else + } else { srule->addr.ip = inet_sockaddrip4(&vsg_entry->addr); + } srule->port = inet_sockaddrport(&vsg_entry->addr); /* Talk to the IPVS channel */ @@ -539,6 +558,47 @@ ipvs_group_cmd(int cmd, list vs_group, real_server * rs, char * vsgname) } } + +void +ipvs_set_snat_rule(int cmd, virtual_server *vs, snat_rule *rs) +{ + memset(sdrule, 0, sizeof(ipvs_snat_dest_t)); + strncpy(srule->sched_name, vs->sched, IP_VS_SCHEDNAME_MAXLEN); + srule->netmask = (vs->addr.ss_family == AF_INET6) ? 128 : ((u_int32_t) 0xffffffff); + srule->protocol = vs->service_type; + + if (!parse_timeout(vs->timeout_persistence, &srule->timeout)) { + log_message(LOG_INFO, "IPVS : Virtual service -f [%d] illegal timeout\n", vs->vfwmark); + } + + if (srule->timeout != 0 || vs->granularity_persistence) { + srule->flags = IP_VS_SVC_F_PERSISTENT; + } + + if (vs->syn_proxy) { + srule->flags |= IP_VS_CONN_F_SYNPROXY; + } + + if (rs) { + if (cmd == IP_VS_SO_SET_ADDSNAT || + cmd == IP_VS_SO_SET_DELSNAT || + cmd == IP_VS_SO_SET_EDITSNAT) { + sdrule->af = rs->af; + sdrule->saddr = rs->saddr; + sdrule->smask = rs->smask; + sdrule->daddr = rs->daddr; + sdrule->dmask = rs->dmask; + sdrule->gw = rs->gw; + sdrule->conn_flags = rs->conn_flags; + sdrule->algo = rs->algo; + sdrule->new_gw = rs->new_gw; + strcpy(sdrule->out_dev, rs->out_dev); + sdrule->min_ip = rs->minip; + sdrule->max_ip = rs->maxip; + } + } +} + /* Fill IPVS rule with root vs infos */ void ipvs_set_rule(int cmd, virtual_server * vs, real_server * rs) @@ -733,6 +793,30 @@ ipvs_laddr_cmd(int cmd, list vs_group, virtual_server * vs) return IPVS_SUCCESS; } +int +ipvs_snat_cmd(int cmd, virtual_server *vs, snat_rule *rs) +{ + memset(srule, 0, sizeof(ipvs_service_t)); + ipvs_set_snat_rule(cmd, vs, rs); + + /* Set flag */ + if (cmd == IP_VS_SO_SET_ADDSNAT && !rs->set) { + rs->set = 1; + } + + if (cmd == IP_VS_SO_SET_DELSNAT && rs->set) { + rs->set = 0; + } + + srule->af = AF_INET; + srule->fwmark = vs->vfwmark; + + /* Talk to the IPVS channel */ + ipvs_talk(cmd); + + return IPVS_SUCCESS; +} + /* Set/Remove a RS or a local address group from a VS */ int ipvs_cmd(int cmd, list vs_group, virtual_server * vs, real_server * rs) diff --git a/tools/keepalived/keepalived/check/ipwrapper.c b/tools/keepalived/keepalived/check/ipwrapper.c index f9986543..fb3a8de1 100644 --- a/tools/keepalived/keepalived/check/ipwrapper.c +++ b/tools/keepalived/keepalived/check/ipwrapper.c @@ -27,11 +27,10 @@ #include "utils.h" #include "notify.h" #include "main.h" - +#include "check_api.h" #include "vrrp_if.h" #include "vrrp_netlink.h" - static struct { struct nlmsghdr n; struct ifaddrmsg ifa; @@ -311,11 +310,26 @@ clear_service_rs(list vs_group, virtual_server * vs, list l) real_server *rs; char rsip[INET6_ADDRSTRLEN]; + if (IS_SNAT_SVC(vs)) { + snat_rule *sr; + for (e = LIST_HEAD(l); e; ELEMENT_NEXT(e)) { + sr = ELEMENT_DATA(e); + if (ISALIVE(sr)) { + if (!ipvs_snat_cmd(LVS_CMD_DEL_SNATDEST, vs, sr)) { + return 0; + } + UNSET_ALIVE(sr); + } + } + return 1; + } + for (e = LIST_HEAD(l); e; ELEMENT_NEXT(e)) { rs = ELEMENT_DATA(e); if (ISALIVE(rs)) { if (!ipvs_cmd(LVS_CMD_DEL_DEST, vs_group, vs, rs)) return 0; + UNSET_ALIVE(rs); if (!vs->omega) continue; @@ -365,10 +379,11 @@ clear_service_vs(list vs_group, virtual_server * vs) if (ISALIVE(vs->s_svr)) if (!ipvs_cmd(LVS_CMD_DEL_DEST, vs_group, vs, vs->s_svr)) return 0; - } else if (!clear_service_rs(vs_group, vs, vs->rs)) + } else if (!clear_service_rs(vs_group, vs, vs->rs)) { return 0; /* The above will handle Omega case for VS as well. */ } + } if (!ipvs_cmd(LVS_CMD_DEL, vs_group, vs, NULL)) return 0; @@ -395,6 +410,52 @@ clear_services(void) return 1; } +/* select max weight of rs from vs + * flag == 1: select max weight of alive rs from vs + */ +int +get_max_weight(int flag, list rs) +{ + element e; + real_server *crs; + int max_weight = -1; + + for (e = LIST_HEAD(rs); e; ELEMENT_NEXT(e)) { + crs = ELEMENT_DATA(e); + if (flag == 1 && crs->alive == 0) { + continue; + } + if (max_weight > -1) { + max_weight = crs->weight > max_weight ? crs->weight : max_weight; + } else { + max_weight = crs->weight; + } + } + + return max_weight; +} + + +static int +init_service_snat_rs(virtual_server *vs) +{ + element e; + snat_rule *rs; + + for (e = LIST_HEAD(vs->rs); e; ELEMENT_NEXT(e)) { + rs = ELEMENT_DATA(e); + if (!ISALIVE(rs)) { + print_snat_rule(LVS_CMD_ADD_SNATDEST, rs); + if (!ipvs_snat_cmd(LVS_CMD_ADD_SNATDEST, vs, rs)) { + return 0; + } + SET_ALIVE(rs); + } + } + + return 1; +} + /* Set a realserver IPVS rules */ static int init_service_rs(virtual_server * vs) @@ -402,6 +463,7 @@ init_service_rs(virtual_server * vs) element e; real_server *rs; + if (vs->abs_priority == 0) { for (e = LIST_HEAD(vs->rs); e; ELEMENT_NEXT(e)) { rs = ELEMENT_DATA(e); /* In alpha mode, be pessimistic (or realistic?) and don't @@ -424,6 +486,34 @@ init_service_rs(virtual_server * vs) SET_ALIVE(rs); } } + } else { + if (!vs->alpha) { + vs->cur_max_weight = get_max_weight(0, vs->rs); + } + for (e = LIST_HEAD(vs->rs); e; ELEMENT_NEXT(e)) { + rs = ELEMENT_DATA(e); + if (vs->alpha) { + UNSET_ALIVE(rs); + continue; + } + + if (!ISALIVE(rs)) { + if (rs->weight == vs->cur_max_weight && + !ipvs_cmd(LVS_CMD_ADD_DEST, check_data->vs_group, vs, rs)) { + return 0; + } else { + SET_ALIVE(rs); + } + } else if (vs->vsgname) { + UNSET_ALIVE(rs); + if (rs->weight == vs->cur_max_weight && + !ipvs_cmd(LVS_CMD_ADD_DEST, check_data->vs_group, vs, rs)) { + return 0; + } + SET_ALIVE(rs); + } + } + } return 1; } @@ -434,11 +524,12 @@ init_service_vs(virtual_server * vs) { /* Init the VS root */ if (!ISALIVE(vs) || vs->vsgname) { - if (!ipvs_cmd(LVS_CMD_ADD, check_data->vs_group, vs, NULL)) + if (!ipvs_cmd(LVS_CMD_ADD, check_data->vs_group, vs, NULL)) { return 0; - else + } else { SET_ALIVE(vs); } + } /*Set local ip address in "FNAT" mode of IPVS */ if ((vs->loadbalancing_kind == IP_VS_CONN_F_FULLNAT) && vs->local_addr_gname) { @@ -447,14 +538,24 @@ init_service_vs(virtual_server * vs) } /* Processing real server queue */ - if (!LIST_ISEMPTY(vs->rs)) { - if (!init_service_rs(vs)) + if (NOT_SNAT_SVC(vs) && !LIST_ISEMPTY(vs->rs)) { + if (!init_service_rs(vs)) { return 0; - if (vs->alpha) + } + + if (vs->alpha) { vs->quorum_state = DOWN; - else + } else { netlink_vipaddress(check_data->vs_group, vs, UP); } + } + + if (IS_SNAT_SVC(vs) && !LIST_ISEMPTY(vs->rs)) { + //log_message(LOG_INFO, "before init_service_snat_rs\n"); + if (!init_service_snat_rs(vs)) { + return 0; + } + } return 1; } @@ -579,12 +680,134 @@ update_quorum_state(virtual_server * vs) } } +static void +handle_abspriority_rs_down2up(virtual_server *vs, real_server *rs) +{ + element e; + real_server *tmp_rs; + char rsip[INET6_ADDRSTRLEN]; + + log_message(LOG_INFO, "down2up: vs.alive=%d, vs.max_weight=%d, rs.weight=%d", + vs->alive, vs->cur_max_weight, rs->weight); + if ((rs->weight == vs->cur_max_weight || vs->cur_max_weight == -1) && !ISALIVE(rs)) { + log_message(LOG_INFO, "down2up: add(%s:%d, %d)", + inet_sockaddrtos2(&rs->addr, rsip), + ntohs(inet_sockaddrport(&rs->addr)), rs->weight); + if (vs->cur_max_weight == -1) { + vs->cur_max_weight = rs->weight; + } + ipvs_cmd(LVS_CMD_ADD_DEST, check_data->vs_group, vs, rs); + } else if (rs->weight > vs->cur_max_weight) { + /* first: del all rs in lvs */ + log_message(LOG_INFO, "down2up: del all alive and setted rs in lvs"); + for (e = LIST_HEAD(vs->rs); e; ELEMENT_NEXT(e)) { + tmp_rs = ELEMENT_DATA(e); + if (ISALIVE(tmp_rs) && (tmp_rs->set == 1) && tmp_rs->weight == vs->cur_max_weight) { + log_message(LOG_INFO, "down2up: del(%s:%d, %d)", + inet_sockaddrtos2(&tmp_rs->addr, rsip), + ntohs(inet_sockaddrport(&tmp_rs->addr)), tmp_rs->weight); + ipvs_cmd(LVS_CMD_DEL_DEST, check_data->vs_group, vs, tmp_rs); + } + } + + /*then: add current rs of max weight to lvs */ + vs->cur_max_weight = rs->weight; + log_message(LOG_INFO, "down2up: add(%s:%d, %d)", + inet_sockaddrtos2(&rs->addr, rsip), + ntohs(inet_sockaddrport(&rs->addr)), + rs->weight); + ipvs_cmd(LVS_CMD_ADD_DEST, check_data->vs_group, vs, rs); + } else { + log_message(LOG_INFO, "down2up: nothing todo"); + } + + SET_ALIVE(rs); + log_message(LOG_INFO, "ALLRS_STAT:"); + for (e = LIST_HEAD(vs->rs); e; ELEMENT_NEXT(e)) { + tmp_rs = ELEMENT_DATA(e); + log_message(LOG_INFO, " (%s:%d), alived=%d, weight=%d, set=%d", + inet_sockaddrtos2(&tmp_rs->addr, rsip), + ntohs(inet_sockaddrport(&tmp_rs->addr)), + tmp_rs->alive, tmp_rs->weight, tmp_rs->set); + } + return; +} + +/* Returns the num of alive rs */ +static int +alive_num_with_weight(virtual_server *vs, int weight) +{ + element e; + real_server *svr; + int count = 0; + + for (e = LIST_HEAD(vs->rs); e; ELEMENT_NEXT(e)) { + svr = ELEMENT_DATA(e); + if (ISALIVE(svr) && svr->weight == weight) { + count += 1; + } + } + + return count; +} + +static void +handle_abspriority_rs_up2down(virtual_server *vs, real_server *rs) +{ + element e; + real_server *svr; + int max_weight = -1; + char rsip[INET6_ADDRSTRLEN]; + + log_message(LOG_INFO, "up2down: vs.alive=%d, vs.max_weight=%d, rs.weight=%d", + vs->alive, vs->cur_max_weight, rs->weight); + if (ISALIVE(rs) && (rs->set == 1) && rs->weight == vs->cur_max_weight) { + log_message(LOG_INFO, "up2down: del(%s:%d, %d)", inet_sockaddrtos2(&rs->addr, rsip), + ntohs(inet_sockaddrport(&rs->addr)), rs->weight); + ipvs_cmd(LVS_CMD_DEL_DEST, check_data->vs_group, vs, rs); + UNSET_ALIVE(rs); + if (alive_num_with_weight(vs, vs->cur_max_weight) == 0) { + max_weight = get_max_weight(1, vs->rs); + if (max_weight != -1) { + log_message(LOG_INFO, "up2down: max weight of cur alive rs: %d", max_weight); + for (e = LIST_HEAD(vs->rs); e; ELEMENT_NEXT(e)) { + svr = ELEMENT_DATA(e); + if (ISALIVE(svr) && (svr->set == 0) && svr->weight == max_weight) { + UNSET_ALIVE(svr); + log_message(LOG_INFO, "up2down: add(%s:%d, %d)", + inet_sockaddrtos2(&svr->addr, rsip), + ntohs(inet_sockaddrport(&svr->addr)), + svr->weight); + ipvs_cmd(LVS_CMD_ADD_DEST, check_data->vs_group, vs, svr); + SET_ALIVE(svr); + } + } + } else { + log_message(LOG_INFO, "up2down: all rs unusable"); + } + vs->cur_max_weight = max_weight; + } + } else { + UNSET_ALIVE(rs); + log_message(LOG_INFO, "up2down: nothing todo"); + } + + log_message(LOG_INFO, "ALLRS_STAT:"); + for (e = LIST_HEAD(vs->rs); e; ELEMENT_NEXT(e)) { + svr = ELEMENT_DATA(e); + log_message(LOG_INFO, " (%s:%d) alived=%d, weight=%d, set=%d", + inet_sockaddrtos2(&svr->addr, rsip), + ntohs(inet_sockaddrport(&svr->addr)), + svr->alive, svr->weight, svr->set); + } + return; +} + /* manipulate add/remove rs according to alive state */ void perform_svr_state(int alive, virtual_server * vs, real_server * rs) { char rsip[INET6_ADDRSTRLEN]; - /* * | ISALIVE(rs) | alive | context * | 0 | 0 | first check failed under alpha mode, unreachable here @@ -601,9 +824,15 @@ perform_svr_state(int alive, virtual_server * vs, real_server * rs) , ntohs(inet_sockaddrport(&vs->addr))); /* Add only if we have quorum or no sorry server */ if (vs->quorum_state == UP || !vs->s_svr || !ISALIVE(vs->s_svr)) { + if (vs->abs_priority == 0) { ipvs_cmd(LVS_CMD_ADD_DEST, check_data->vs_group, vs, rs); - } rs->alive = alive; + } else { + log_message(LOG_INFO, "abs_priority mode: down2up"); + handle_abspriority_rs_down2up(vs, rs); + } + } + if (rs->notify_up) { log_message(LOG_INFO, "Executing [%s] for service [%s]:%d in VS [%s]:%d" , rs->notify_up @@ -630,9 +859,15 @@ perform_svr_state(int alive, virtual_server * vs, real_server * rs) * Remove only if we have quorum or no sorry server */ if (vs->quorum_state == UP || !vs->s_svr || !ISALIVE(vs->s_svr)) { + if (vs->abs_priority == 0) { ipvs_cmd(LVS_CMD_DEL_DEST, check_data->vs_group, vs, rs); - } rs->alive = alive; + } else { + log_message(LOG_INFO, "abs_priority mode:up2down"); + handle_abspriority_rs_up2down(vs, rs); + } + } + if (rs->notify_down) { log_message(LOG_INFO, "Executing [%s] for service [%s]:%d in VS [%s]:%d" , rs->notify_down @@ -870,6 +1105,30 @@ vs_exist(virtual_server * old_vs) return 0; } + +static int +snat_rs_exist(snat_rule *old_rs, list l) +{ + element e; + snat_rule *rs; + + if (LIST_ISEMPTY(l)) { + return 0; + } + + for (e = LIST_HEAD(l); e; ELEMENT_NEXT(e)) { + rs = ELEMENT_DATA(e); + if (SNAT_RS_ISEQ(rs, old_rs)) { + rs->alive = old_rs->alive; + rs->set = old_rs->set; + return 1; + } + } + + return 0; +} + + /* Check if rs is in new vs data */ static int rs_exist(real_server * old_rs, list l) @@ -924,12 +1183,40 @@ get_rs_list(virtual_server * vs) return NULL; } +/* Clear the diff rs of the old snat vs */ +static int +clear_diff_snat_rs(virtual_server *old_vs) +{ + element e; + list l = old_vs->rs; + list new = get_rs_list(old_vs); + snat_rule *rs; + + if (LIST_ISEMPTY(l)) { + return 1; + } + + for (e = LIST_HEAD(l); e; ELEMENT_NEXT(e)) { + rs = ELEMENT_DATA(e); + if (!snat_rs_exist(rs, new)) { + print_snat_rule(LVS_CMD_DEL_SNATDEST, rs); + /* Set alive flag to delete the failed inhibit entries */ + if (!ipvs_snat_cmd(LVS_CMD_DEL_SNATDEST, old_vs, rs)) { + return 0; + } + } + } + + return 1; +} + /* Clear the diff rs of the old vs */ static int clear_diff_rs(virtual_server * old_vs) { element e; list l = old_vs->rs; + int new_max_weight = -1; list new = get_rs_list(old_vs); real_server *rs; char rsip[INET6_ADDRSTRLEN]; @@ -938,13 +1225,23 @@ clear_diff_rs(virtual_server * old_vs) if (LIST_ISEMPTY(l)) return 1; + if (old_vs->abs_priority) { + new_max_weight = get_max_weight(0, new); + log_message(LOG_INFO, "abs_priority_mode: reload: max_weight=%d", new_max_weight); + } for (e = LIST_HEAD(l); e; ELEMENT_NEXT(e)) { rs = ELEMENT_DATA(e); - if (!rs_exist(rs, new)) { + if (((old_vs->abs_priority == 1) && ISALIVE(rs) && (rs->set == 1) && rs->weight < new_max_weight) + || !rs_exist(rs, new)) { + if ((old_vs->abs_priority == 1) && ISALIVE(rs) && (rs->set == 1) && rs->weight < new_max_weight) { + log_message(LOG_INFO, "abs_priority_mode:%d(weight of rs[%s:%d]) < %d(weight of new_rs_list)", rs->weight, + inet_sockaddrtos(&rs->addr), ntohs(inet_sockaddrport(&rs->addr)), new_max_weight); + } else { /* Reset inhibit flag to delete inhibit entries */ log_message(LOG_INFO, "service [%s]:%d no longer exist" , inet_sockaddrtos(&rs->addr) , ntohs(inet_sockaddrport(&rs->addr))); + } log_message(LOG_INFO, "Removing service [%s]:%d from VS [%s]:%d" , inet_sockaddrtos2(&rs->addr, rsip) , ntohs(inet_sockaddrport(&rs->addr)) @@ -1052,21 +1349,34 @@ clear_diff_services(void) * reloaded. */ if (!vs_exist(vs)) { - if (vs->vsgname) - log_message(LOG_INFO, "Removing Virtual Server Group [%s]" - , vs->vsgname); - else - log_message(LOG_INFO, "Removing Virtual Server [%s]:%d" - , inet_sockaddrtos(&vs->addr) - , ntohs(inet_sockaddrport(&vs->addr))); + if (vs->vsgname) { + log_message(LOG_INFO, "Removing Virtual Server Group [%s]", + vs->vsgname); + } else { + if (vs->vfwmark) { + log_message(LOG_INFO, "Removing Virtual Server -f [%d]", + vs->vfwmark); + } else { + log_message(LOG_INFO, "Removing Virtual Server [%s]:%d", + inet_sockaddrtos(&vs->addr), + ntohs(inet_sockaddrport(&vs->addr))); + } + } /* Clear VS entry */ - if (!clear_service_vs(old_check_data->vs_group, vs)) + if (!clear_service_vs(old_check_data->vs_group, vs)) { return 0; + } } else { /* If vs exist, perform rs pool diff */ - if (!clear_diff_rs(vs)) + if (NOT_SNAT_SVC(vs) && !clear_diff_rs(vs)) { + return 0; + } + + if (IS_SNAT_SVC(vs) && !clear_diff_snat_rs(vs)) { return 0; + } + if (vs->s_svr) if (ISALIVE(vs->s_svr)) if (!ipvs_cmd(LVS_CMD_DEL_DEST @@ -1074,6 +1384,7 @@ clear_diff_services(void) , vs , vs->s_svr)) return 0; + /* perform local address diff */ if (!clear_diff_laddr(vs)) return 0; @@ -1082,3 +1393,4 @@ clear_diff_services(void) return 1; } + diff --git a/tools/keepalived/keepalived/etc/init.d/keepalived.init b/tools/keepalived/keepalived/etc/init.d/keepalived.init index 0724c4c1..7a399afb 100755 --- a/tools/keepalived/keepalived/etc/init.d/keepalived.init +++ b/tools/keepalived/keepalived/etc/init.d/keepalived.init @@ -17,7 +17,8 @@ RETVAL=0 prog="keepalived" - +VRRP_PID_FILE=/var/run/vrrp.pid +CHECKERS_PID_FILE=/var/run/checkers.pid start() { echo -n $"Starting $prog: " daemon keepalived ${KEEPALIVED_OPTIONS} @@ -26,11 +27,32 @@ start() { [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog } +check_stop() { + local cnt=0 + while checkpid $1 2>&1 + do + let "cnt++" + if [ $cnt -gt 2 ];then + break + fi + sleep 1 + done +} + stop() { + local vrrppid checkerpid + if [ -f "$VRRP_PID_FILE" ];then + vrrppid=`cat $VRRP_PID_FILE` + fi + if [ -f "$CHECKERS_PID_FILE" ]; then + chkerspid=`cat $CHECKERS_PID_FILE` + fi echo -n $"Stopping $prog: " killproc keepalived RETVAL=$? echo + check_stop "$chkerspid" + check_stop "$vrrppid" [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog } diff --git a/tools/keepalived/keepalived/include/check_api.h b/tools/keepalived/keepalived/include/check_api.h index 2a002610..3d0feb55 100644 --- a/tools/keepalived/keepalived/include/check_api.h +++ b/tools/keepalived/keepalived/include/check_api.h @@ -67,5 +67,7 @@ extern void install_checkers_keyword(void); extern void update_checker_activity(sa_family_t, void *, int); extern void checker_set_dst(struct sockaddr_storage *); extern void checker_set_dst_port(struct sockaddr_storage *, uint16_t); +extern void print_snat_rule(int, snat_rule *); #endif + diff --git a/tools/keepalived/keepalived/include/check_data.h b/tools/keepalived/keepalived/include/check_data.h index 27bf7b6f..5f22b2dc 100644 --- a/tools/keepalived/keepalived/include/check_data.h +++ b/tools/keepalived/keepalived/include/check_data.h @@ -87,6 +87,25 @@ typedef struct _real_server { int reload_alive; /* alpha mode will reset rs to unalive. So save the status before reload here */ } real_server; + +/* snat rule definetion */ +typedef struct __snat_rule { + union nf_inet_addr saddr; + uint32_t smask; + union nf_inet_addr daddr; + uint32_t dmask; + union nf_inet_addr gw; + union nf_inet_addr minip; + union nf_inet_addr maxip; + uint32_t conn_flags; + uint16_t af; + uint8_t algo; + union nf_inet_addr new_gw; + char out_dev[IP_VS_IFNAME_MAXLEN]; + int alive; + int set; +} snat_rule; + /* local ip address group definition */ typedef struct _local_addr_entry { struct sockaddr_storage addr; @@ -123,6 +142,8 @@ typedef struct _virtual_server { uint16_t service_type; long delay_loop; int ha_suspend; + int abs_priority; + int cur_max_weight; char sched[SCHED_MAX_LENGTH]; char timeout_persistence[MAX_TIMEOUT_LENGTH]; unsigned loadbalancing_kind; @@ -163,6 +184,23 @@ static inline int __ip6_addr_equal(const struct in6_addr *a1, (a1->s6_addr32[3] ^ a2->s6_addr32[3])) == 0); } + +static inline int addr_equal(int af, const union nf_inet_addr *s1, + const union nf_inet_addr *s2) +{ + if (af == AF_INET) { + if (s1->in.s_addr == s2->in.s_addr) { + return 1; + } + } else if (af == AF_INET6) { + if (__ip6_addr_equal(&s1->in6, &s2->in6)) { + return 1; + } + } + + return 0; +} + static inline int sockstorage_equal(const struct sockaddr_storage *s1, const struct sockaddr_storage *s2) { @@ -211,16 +249,32 @@ static inline int inaddr_equal(sa_family_t family, void *addr1, void *addr2) return 0; } +#define SNAT_NONE 0x0000 +#define SNAT_ADDR 0x0001 +#define SNAT_MASK 0x0002 + +typedef struct _snat_rule_addr_mask { + union nf_inet_addr addr; + uint16_t af; + uint32_t mask; +} snat_rule_addr_mask; + /* macro utility */ +#define IS_SNAT_SVC(S) (((S)->vfwmark) == 1) +#define NOT_SNAT_SVC(s) (((s)->vfwmark) != 1) + #define ISALIVE(S) ((S)->alive) #define SET_ALIVE(S) ((S)->alive = 1) #define UNSET_ALIVE(S) ((S)->alive = 0) #define VHOST(V) ((V)->virtualhost) +#define DEFAULT_SNAT_SCHED "snat_sched" + #define VS_ISEQ(X,Y) (sockstorage_equal(&(X)->addr,&(Y)->addr) &&\ (X)->vfwmark == (Y)->vfwmark &&\ (X)->service_type == (Y)->service_type &&\ (X)->loadbalancing_kind == (Y)->loadbalancing_kind &&\ + (X)->abs_priority == (Y)->abs_priority &&\ (X)->nat_mask == (Y)->nat_mask &&\ (X)->granularity_persistence == (Y)->granularity_persistence &&\ (X)->syn_proxy == (Y)->syn_proxy &&\ @@ -240,6 +294,14 @@ static inline int inaddr_equal(sa_family_t family, void *addr1, void *addr2) #define RS_ISEQ(X,Y) (sockstorage_equal(&(X)->addr,&(Y)->addr) && \ (X)->iweight == (Y)->iweight) +#define SNAT_RS_ISEQ(X, Y) (addr_equal((X)->af, &(X)->saddr, &(Y)->saddr) && (X)->smask == (Y)->smask && \ + addr_equal((X)->af, &(X)->daddr, &(Y)->daddr) && (X)->dmask == (Y)->dmask && \ + addr_equal((X)->af, &(X)->gw, &(Y)->gw) && !strcmp((X)->out_dev, (Y)->out_dev) && \ + addr_equal((X)->af, &(X)->minip, &(Y)->minip) && \ + addr_equal((X)->af, &(X)->maxip, &(Y)->maxip) && \ + addr_equal((X)->af, &(X)->new_gw, &(Y)->new_gw) && \ + (X)->algo == (Y)->algo) + /* Global vars exported */ extern check_conf_data *check_data; extern check_conf_data *old_check_data; @@ -261,4 +323,6 @@ extern check_conf_data *alloc_check_data(void); extern void free_check_data(check_conf_data *); extern void dump_check_data(check_conf_data *); +extern void alloc_snat_rule(void); + #endif diff --git a/tools/keepalived/keepalived/include/ipvswrapper.h b/tools/keepalived/keepalived/include/ipvswrapper.h index fadb5748..ab2cddb5 100644 --- a/tools/keepalived/keepalived/include/ipvswrapper.h +++ b/tools/keepalived/keepalived/include/ipvswrapper.h @@ -100,4 +100,6 @@ extern int ipvs_syncd_cmd(int, char *, int, int); extern void ipvs_syncd_master(char *, int); extern void ipvs_syncd_backup(char *, int); +extern int ipvs_snat_cmd(int, virtual_server *, snat_rule *); + #endif diff --git a/tools/keepalived/keepalived/include/ipwrapper.h b/tools/keepalived/keepalived/include/ipwrapper.h index 7899cbff..89ffdb74 100644 --- a/tools/keepalived/keepalived/include/ipwrapper.h +++ b/tools/keepalived/keepalived/include/ipwrapper.h @@ -49,6 +49,8 @@ #define LVS_CMD_EDIT_DEST IP_VS_SO_SET_EDITDEST #define LVS_CMD_ADD_LADDR IP_VS_SO_SET_ADDLADDR #define LVS_CMD_DEL_LADDR IP_VS_SO_SET_DELLADDR +#define LVS_CMD_ADD_SNATDEST IP_VS_SO_SET_ADDSNAT +#define LVS_CMD_DEL_SNATDEST IP_VS_SO_SET_DELSNAT /* prototypes */ extern void perform_svr_state(int, virtual_server *, real_server *); @@ -59,4 +61,6 @@ extern int init_services(void); extern int clear_services(void); extern int clear_diff_services(void); +extern int get_max_weight(int flag, list rs); + #endif diff --git a/tools/keepalived/keepalived/libipvs-2.6/ip_vs.h b/tools/keepalived/keepalived/libipvs-2.6/ip_vs.h index f15bfd4e..a195b57c 100644 --- a/tools/keepalived/keepalived/libipvs-2.6/ip_vs.h +++ b/tools/keepalived/keepalived/libipvs-2.6/ip_vs.h @@ -17,7 +17,7 @@ #include #endif -#define IP_VS_VERSION_CODE 0x010201 +#define IP_VS_VERSION_CODE 0x010202 #define NVERSION(version) \ (version >> 16) & 0xFF, \ (version >> 8) & 0xFF, \ @@ -61,7 +61,10 @@ #define IP_VS_SO_SET_ZERO (IP_VS_BASE_CTL+15) #define IP_VS_SO_SET_ADDLADDR (IP_VS_BASE_CTL+16) #define IP_VS_SO_SET_DELLADDR (IP_VS_BASE_CTL+17) -#define IP_VS_SO_SET_MAX IP_VS_SO_SET_DELLADDR +#define IP_VS_SO_SET_ADDSNAT (IP_VS_BASE_CTL + 18) +#define IP_VS_SO_SET_DELSNAT (IP_VS_BASE_CTL + 19) +#define IP_VS_SO_SET_EDITSNAT (IP_VS_BASE_CTL + 20) +#define IP_VS_SO_SET_MAX IP_VS_SO_SET_EDITSNAT #define IP_VS_SO_GET_VERSION IP_VS_BASE_CTL #define IP_VS_SO_GET_INFO (IP_VS_BASE_CTL+1) @@ -72,7 +75,8 @@ #define IP_VS_SO_GET_TIMEOUT (IP_VS_BASE_CTL+6) #define IP_VS_SO_GET_DAEMON (IP_VS_BASE_CTL+7) #define IP_VS_SO_GET_LADDRS (IP_VS_BASE_CTL+8) -#define IP_VS_SO_GET_MAX IP_VS_SO_GET_LADDRS +#define IP_VS_SO_GET_SNAT (IP_VS_BASE_CTL + 9) /* not used now */ +#define IP_VS_SO_GET_MAX IP_VS_SO_GET_SNAT /* @@ -175,6 +179,36 @@ struct ip_vs_dest_user { union nf_inet_addr addr; }; +struct ip_vs_dest_snat_user { + union nf_inet_addr saddr; + u_int32_t smask; + union nf_inet_addr daddr; + u_int32_t dmask; + union nf_inet_addr gw; + union nf_inet_addr min_ip; + union nf_inet_addr max_ip; + u_int8_t algo; + union nf_inet_addr new_gw; + u_int16_t af; + unsigned conn_flags; /* connection flags */ + char out_dev[IP_VS_IFNAME_MAXLEN]; +}; + +struct ip_vs_dest_snat_kern { + union nf_inet_addr saddr; + u_int32_t smask; + union nf_inet_addr daddr; + u_int32_t dmask; + union nf_inet_addr gw; + union nf_inet_addr min_ip; + union nf_inet_addr max_ip; + u_int8_t algo; + union nf_inet_addr new_gw; + u_int16_t af; + unsigned conn_flags; /* connection flags */ + char out_dev[IP_VS_IFNAME_MAXLEN]; +}; + struct ip_vs_laddr_kern { __be32 addr; /* ipv4 address */ }; @@ -283,6 +317,8 @@ struct ip_vs_dest_entry_kern { /* statistics */ struct ip_vs_stats_user stats; + /* snat rule */ + struct ip_vs_dest_snat_user snat_rule; }; struct ip_vs_dest_entry { @@ -300,6 +336,7 @@ struct ip_vs_dest_entry { /* statistics */ struct ip_vs_stats_user stats; + struct ip_vs_dest_snat_user snat_rule; u_int16_t af; union nf_inet_addr addr; }; @@ -466,6 +503,11 @@ enum { IPVS_CMD_DEL_LADDR , IPVS_CMD_GET_LADDR , + IPVS_CMD_NEW_SNATDEST, + IPVS_CMD_SET_SNATDEST, + IPVS_CMD_DEL_SNATDEST, + IPVS_CMD_GET_SNATDEST, + __IPVS_CMD_MAX, }; @@ -481,6 +523,7 @@ enum { IPVS_CMD_ATTR_TIMEOUT_TCP_FIN, /* TCP FIN wait timeout */ IPVS_CMD_ATTR_TIMEOUT_UDP, /* UDP timeout */ IPVS_CMD_ATTR_LADDR , /* local address */ + IPVS_CMD_ATTR_SNATDEST, /*nested snat rule attribute*/ __IPVS_CMD_ATTR_MAX, }; @@ -534,16 +577,39 @@ enum { IPVS_DEST_ATTR_PERSIST_CONNS, /* persistent connections */ IPVS_DEST_ATTR_STATS, /* nested attribute for dest stats */ + + IPVS_DEST_ATTR_SNATRULE, /* nested attribute for dest snat rule */ __IPVS_DEST_ATTR_MAX, }; #define IPVS_DEST_ATTR_MAX (__IPVS_DEST_ATTR_MAX - 1) +/** + * Attribute used to describe a snat dest (snat rule) + * Used inside nested attribute IPVS_CMD_ATTR_SNATDEST and IPVS_CMD_ATTR_DEST + */ +enum { + IPVS_SNAT_DEST_ATTR_UNSPEC = 0, + IPVS_SNAT_DEST_ATTR_FADDR, + IPVS_SNAT_DEST_ATTR_FMASK, + IPVS_SNAT_DEST_ATTR_DADDR, + IPVS_SNAT_DEST_ATTR_DMASK, + IPVS_SNAT_DEST_ATTR_GW, + IPVS_SNAT_DEST_ATTR_MINIP, + IPVS_SNAT_DEST_ATTR_MAXIP, + IPVS_SNAT_DEST_ATTR_ALGO, + IPVS_SNAT_DEST_ATTR_NEWGW, + IPVS_SNAT_DEST_ATTR_CONNFLAG, + IPVS_SNAT_DEST_ATTR_OUTDEV, + __IPVS_SNAT_DEST_ATTR_MAX, +}; + +#define IPVS_SNAT_DEST_ATTR_MAX (__IPVS_SNAT_DEST_ATTR_MAX - 1) + /* * Attirbutes used to describe a local address * */ - enum { IPVS_LADDR_ATTR_UNSPEC = 0 , IPVS_LADDR_ATTR_ADDR, @@ -600,6 +666,13 @@ enum { __IPVS_INFO_ATTR_MAX, }; +/* SNAT ip pool select algorithm */ +enum { + IPVS_SNAT_IPS_NORMAL = 0, /* src-ip/dst-ip */ + IPVS_SNAT_IPS_PERSITENT, /* src-ip */ + IPVS_SNAT_IPS_RANDOM, /* src-ip/dst-ip/src-port */ +}; + #define IPVS_INFO_ATTR_MAX (__IPVS_INFO_ATTR_MAX - 1) #ifdef LIBIPVS_USE_NL @@ -610,6 +683,7 @@ extern struct nla_policy ipvs_stats_policy[IPVS_STATS_ATTR_MAX + 1]; extern struct nla_policy ipvs_info_policy[IPVS_INFO_ATTR_MAX + 1]; extern struct nla_policy ipvs_daemon_policy[IPVS_DAEMON_ATTR_MAX + 1]; extern struct nla_policy ipvs_laddr_policy[IPVS_LADDR_ATTR_MAX + 1]; +extern struct nla_policy ip_vs_snat_dest_policy[IPVS_SNAT_DEST_ATTR_MAX + 1]; #endif /* End of Generic Netlink interface definitions */ diff --git a/tools/keepalived/keepalived/libipvs-2.6/ip_vs_nl_policy.c b/tools/keepalived/keepalived/libipvs-2.6/ip_vs_nl_policy.c index 045bcdc8..8964392c 100644 --- a/tools/keepalived/keepalived/libipvs-2.6/ip_vs_nl_policy.c +++ b/tools/keepalived/keepalived/libipvs-2.6/ip_vs_nl_policy.c @@ -10,6 +10,7 @@ struct nla_policy ipvs_cmd_policy[IPVS_CMD_ATTR_MAX + 1] = { [IPVS_CMD_ATTR_TIMEOUT_TCP_FIN] = { .type = NLA_U32 }, [IPVS_CMD_ATTR_TIMEOUT_UDP] = { .type = NLA_U32 }, [IPVS_CMD_ATTR_LADDR] = { .type = NLA_NESTED}, + [IPVS_CMD_ATTR_SNATDEST] = { .type = NLA_NESTED}, }; struct nla_policy ipvs_service_policy[IPVS_SVC_ATTR_MAX + 1] = { @@ -41,6 +42,22 @@ struct nla_policy ipvs_dest_policy[IPVS_DEST_ATTR_MAX + 1] = { [IPVS_DEST_ATTR_INACT_CONNS] = { .type = NLA_U32 }, [IPVS_DEST_ATTR_PERSIST_CONNS] = { .type = NLA_U32 }, [IPVS_DEST_ATTR_STATS] = { .type = NLA_NESTED }, + [IPVS_DEST_ATTR_SNATRULE] = {.type = NLA_NESTED}, +}; + +/* Policy used for attributes in nested attribute IPVS_CMD_ATTR_SNAT_DEAST */ +struct nla_policy ip_vs_snat_dest_policy[IPVS_SNAT_DEST_ATTR_MAX + 1] = { + [IPVS_SNAT_DEST_ATTR_FADDR] = {.type = NLA_UNSPEC, .maxlen = sizeof(struct in6_addr)}, + [IPVS_SNAT_DEST_ATTR_FMASK] = {.type = NLA_U32}, + [IPVS_SNAT_DEST_ATTR_DADDR] = {.type = NLA_UNSPEC, .maxlen = sizeof(struct in6_addr)}, + [IPVS_SNAT_DEST_ATTR_DMASK] = {.type = NLA_U32}, + [IPVS_SNAT_DEST_ATTR_GW] = {.type = NLA_UNSPEC, .maxlen = sizeof(struct in6_addr)}, + [IPVS_SNAT_DEST_ATTR_MINIP] = {.type = NLA_UNSPEC, .maxlen = sizeof(struct in6_addr)}, + [IPVS_SNAT_DEST_ATTR_MAXIP] = {.type = NLA_UNSPEC, .maxlen = sizeof(struct in6_addr)}, + [IPVS_SNAT_DEST_ATTR_ALGO] = {.type = NLA_U8}, + [IPVS_SNAT_DEST_ATTR_NEWGW] = {.type = NLA_UNSPEC, .maxlen = sizeof(struct in6_addr)}, + [IPVS_SNAT_DEST_ATTR_CONNFLAG] = {.type = NLA_U32}, + [IPVS_SNAT_DEST_ATTR_OUTDEV] = {.type = NLA_STRING, .maxlen = IP_VS_IFNAME_MAXLEN}, }; struct nla_policy ipvs_laddr_policy[IPVS_LADDR_ATTR_MAX + 1] = { diff --git a/tools/keepalived/keepalived/libipvs-2.6/libipvs.c b/tools/keepalived/keepalived/libipvs-2.6/libipvs.c index 8170d497..d37cb47e 100644 --- a/tools/keepalived/keepalived/libipvs-2.6/libipvs.c +++ b/tools/keepalived/keepalived/libipvs-2.6/libipvs.c @@ -80,19 +80,20 @@ static int ipvs_nl_noop_cb(struct nl_msg *msg, void *arg) int ipvs_nl_send_message(struct nl_msg *msg, nl_recvmsg_msg_cb_t func, void *arg) { int err = EINVAL; - sock = nl_handle_alloc(); if (!sock) { nlmsg_free(msg); return -1; } - if (genl_connect(sock) < 0) + if (genl_connect(sock) < 0) { goto fail_genl; + } family = genl_ctrl_resolve(sock, IPVS_GENL_NAME); - if (family < 0) + if (family < 0) { goto fail_genl; + } /* To test connections and set the family */ if (msg == NULL) { @@ -101,19 +102,21 @@ int ipvs_nl_send_message(struct nl_msg *msg, nl_recvmsg_msg_cb_t func, void *arg return 0; } - if (nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, func, arg) != 0) + if (nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, func, arg) != 0) { goto fail_genl; + } - if (nl_send_auto_complete(sock, msg) < 0) + if (nl_send_auto_complete(sock, msg) < 0) { goto fail_genl; + } - if ((err = -nl_recvmsgs_default(sock)) > 0) + if ((err = -nl_recvmsgs_default(sock)) > 0) { goto fail_genl; + } nlmsg_free(msg); nl_handle_destroy(sock); - return 0; fail_genl: @@ -220,8 +223,9 @@ static int ipvs_nl_fill_service_attr(struct nl_msg *msg, ipvs_service_t *svc) .mask = ~0 }; nl_service = nla_nest_start(msg, IPVS_CMD_ATTR_SERVICE); - if (!nl_service) + if (!nl_service) { return -1; + } NLA_PUT_U16(msg, IPVS_SVC_ATTR_AF, svc->af); @@ -297,8 +301,8 @@ int ipvs_update_service_by_options(ipvs_service_t *svc, unsigned int options) fprintf(stderr, "%s\n", ipvs_strerror(errno)); exit(1); } - ipvs_service_entry_2_user(entry, &user); + ipvs_service_entry_2_user(entry, &user); if( options & OPT_SCHEDULER ) { strcpy(user.sched_name, svc->sched_name); } @@ -417,7 +421,122 @@ static int ipvs_nl_fill_dest_attr(struct nl_msg *msg, ipvs_dest_t *dst) nla_put_failure: return -1; } + +static int ipvs_nl_fill_snat_dest_attr(struct nl_msg *msg, ipvs_snat_dest_t *dst) +{ + struct nlattr *nl_snat_dest; + + nl_snat_dest = nla_nest_start(msg, IPVS_CMD_ATTR_SNATDEST); + if (!nl_snat_dest) { + return -1; + } + + /* add special attr */ + NLA_PUT(msg, IPVS_SNAT_DEST_ATTR_FADDR, sizeof(dst->saddr), &dst->saddr); + NLA_PUT_U32(msg, IPVS_SNAT_DEST_ATTR_FMASK, dst->smask); + NLA_PUT(msg, IPVS_SNAT_DEST_ATTR_DADDR, sizeof(dst->daddr), &dst->daddr); + NLA_PUT_U32(msg, IPVS_SNAT_DEST_ATTR_DMASK, dst->dmask); + NLA_PUT(msg, IPVS_SNAT_DEST_ATTR_GW, sizeof(dst->gw), &dst->gw); + NLA_PUT(msg, IPVS_SNAT_DEST_ATTR_MINIP, sizeof(dst->min_ip), &dst->min_ip); + NLA_PUT(msg, IPVS_SNAT_DEST_ATTR_MAXIP, sizeof(dst->max_ip), &dst->max_ip); + NLA_PUT_U8(msg, IPVS_SNAT_DEST_ATTR_ALGO, dst->algo); + NLA_PUT(msg, IPVS_SNAT_DEST_ATTR_NEWGW, sizeof(dst->new_gw), &dst->new_gw); + NLA_PUT_U32(msg, IPVS_SNAT_DEST_ATTR_CONNFLAG, dst->conn_flags & IP_VS_CONN_F_FWD_MASK); + NLA_PUT_STRING(msg, IPVS_SNAT_DEST_ATTR_OUTDEV, dst->out_dev); + + nla_nest_end(msg, nl_snat_dest); + return 0; + +nla_put_failure: + return -1; +} +#endif + +int ipvs_add_snat_dest(ipvs_service_t *svc, ipvs_snat_dest_t *snat_dest) +{ + ipvs_func = ipvs_add_snat_dest; + #ifdef LIBIPVS_USE_NL + if (try_nl) { + struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_NEW_SNATDEST, 0); + if (!msg) { + return -1; + } + + if (ipvs_nl_fill_service_attr(msg, svc)) { + goto nla_put_failure; + } + + if (ipvs_nl_fill_snat_dest_attr(msg, snat_dest)) { + goto nla_put_failure; + } + + int ret = ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL); + return ret; + +nla_put_failure: + nlmsg_free(msg); + return -1; + } +#endif + + return -1; +} + +int ipvs_update_snat_dest(ipvs_service_t *svc, ipvs_snat_dest_t *snat_dest) +{ + ipvs_func = ipvs_update_snat_dest; +#ifdef LIBIPVS_USE_NL + if (try_nl) { + struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_SET_SNATDEST, 0); + if (!msg) { + return -1; + } + + if (ipvs_nl_fill_service_attr(msg, svc)) { + goto nla_put_failure; + } + + if (ipvs_nl_fill_snat_dest_attr(msg, snat_dest)) { + goto nla_put_failure; + } + + return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL); + +nla_put_failure: + nlmsg_free(msg); + return -1; + } +#endif + + return -1; +} + +int ipvs_del_snat_dest(ipvs_service_t *svc, ipvs_snat_dest_t *snat_dest) +{ + ipvs_func = ipvs_del_snat_dest; +#ifdef LIBIPVS_USE_NL + if (try_nl) { + struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_DEL_SNATDEST, 0); + if (!msg) { + return -1; + } + if (ipvs_nl_fill_service_attr(msg, svc)) { + goto nla_put_failure; + } + + if (ipvs_nl_fill_snat_dest_attr(msg, snat_dest)) { + goto nla_put_failure; + } + + return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL); + +nla_put_failure: + nlmsg_free(msg); + return -1; + } #endif + return -1; +} int ipvs_add_dest(ipvs_service_t *svc, ipvs_dest_t *dest) { @@ -709,6 +828,41 @@ static int ipvs_parse_stats(struct ip_vs_stats_user *stats, struct nlattr *nla) } +static int ipvs_parse_snat_rule(struct ip_vs_dest_snat_user* snat_rule, struct nlattr *nla) +{ + struct nlattr *attrs[IPVS_SNAT_DEST_ATTR_MAX + 1]; + if (nla_parse_nested(attrs, IPVS_SNAT_DEST_ATTR_MAX, nla, ip_vs_snat_dest_policy)) { + return -1; + } + + if (!(attrs[IPVS_SNAT_DEST_ATTR_FADDR] && + attrs[IPVS_SNAT_DEST_ATTR_FMASK] && + attrs[IPVS_SNAT_DEST_ATTR_DADDR] && + attrs[IPVS_SNAT_DEST_ATTR_DMASK] && + attrs[IPVS_SNAT_DEST_ATTR_GW] && + attrs[IPVS_SNAT_DEST_ATTR_MINIP] && + attrs[IPVS_SNAT_DEST_ATTR_MAXIP] && + attrs[IPVS_SNAT_DEST_ATTR_ALGO] && + attrs[IPVS_SNAT_DEST_ATTR_NEWGW] && + attrs[IPVS_SNAT_DEST_ATTR_OUTDEV] && + attrs[IPVS_SNAT_DEST_ATTR_CONNFLAG])) { + return -1; + } + + memcpy(&snat_rule->saddr, nla_data(attrs[IPVS_SNAT_DEST_ATTR_FADDR]), sizeof(snat_rule->saddr)); + snat_rule->smask= nla_get_u32(attrs[IPVS_SNAT_DEST_ATTR_FMASK]); + memcpy(&snat_rule->daddr, nla_data(attrs[IPVS_SNAT_DEST_ATTR_DADDR]), sizeof(snat_rule->daddr)); + snat_rule->dmask= nla_get_u32(attrs[IPVS_SNAT_DEST_ATTR_DMASK]); + memcpy(&snat_rule->gw, nla_data(attrs[IPVS_SNAT_DEST_ATTR_GW]), sizeof(snat_rule->gw)); + memcpy(&snat_rule->min_ip, nla_data(attrs[IPVS_SNAT_DEST_ATTR_MINIP]), sizeof(snat_rule->min_ip)); + memcpy(&snat_rule->max_ip, nla_data(attrs[IPVS_SNAT_DEST_ATTR_MAXIP]), sizeof(snat_rule->max_ip)); + snat_rule->algo= nla_get_u8(attrs[IPVS_SNAT_DEST_ATTR_ALGO]); + memcpy(&snat_rule->new_gw, nla_data(attrs[IPVS_SNAT_DEST_ATTR_NEWGW]), sizeof(snat_rule->new_gw)); + snat_rule->conn_flags = nla_get_u32(attrs[IPVS_SNAT_DEST_ATTR_CONNFLAG]); + strncpy(snat_rule->out_dev, nla_get_string(attrs[IPVS_SNAT_DEST_ATTR_OUTDEV]), IP_VS_IFNAME_MAXLEN); + return 0; +} + static int ipvs_services_parse_cb(struct nl_msg *msg, void *arg) { struct nlmsghdr *nlh = nlmsg_hdr(msg); @@ -885,20 +1039,29 @@ ipvs_sort_services(struct ip_vs_get_services *s, ipvs_service_cmp_t f) static int ipvs_dests_parse_cb(struct nl_msg *msg, void *arg) { struct nlmsghdr *nlh = nlmsg_hdr(msg); - struct nlattr *attrs[IPVS_DEST_ATTR_MAX + 1]; - struct nlattr *dest_attrs[IPVS_SVC_ATTR_MAX + 1]; + //struct nlattr *attrs[IPVS_DEST_ATTR_MAX + 1]; + //struct nlattr *dest_attrs[IPVS_SVC_ATTR_MAX + 1]; + + struct nlattr *attrs[IPVS_CMD_ATTR_MAX + 1]; + struct nlattr *dest_attrs[IPVS_DEST_ATTR_MAX + 1]; + struct ip_vs_get_dests **dp = (struct ip_vs_get_dests **)arg; struct ip_vs_get_dests *d = (struct ip_vs_get_dests *)*dp; int i = d->num_dests; - if (genlmsg_parse(nlh, 0, attrs, IPVS_CMD_ATTR_MAX, ipvs_cmd_policy) != 0) + if (genlmsg_parse(nlh, 0, attrs, + IPVS_CMD_ATTR_MAX, ipvs_cmd_policy) != 0) { return -1; + } - if (!attrs[IPVS_CMD_ATTR_DEST]) + if (!attrs[IPVS_CMD_ATTR_DEST]) { return -1; + } - if (nla_parse_nested(dest_attrs, IPVS_DEST_ATTR_MAX, attrs[IPVS_CMD_ATTR_DEST], ipvs_dest_policy)) + if (nla_parse_nested(dest_attrs, IPVS_DEST_ATTR_MAX, + attrs[IPVS_CMD_ATTR_DEST], ipvs_dest_policy)) { return -1; + } memset(&(d->entrytable[i]), 0, sizeof(d->entrytable[i])); @@ -910,8 +1073,9 @@ static int ipvs_dests_parse_cb(struct nl_msg *msg, void *arg) dest_attrs[IPVS_DEST_ATTR_L_THRESH] && dest_attrs[IPVS_DEST_ATTR_ACTIVE_CONNS] && dest_attrs[IPVS_DEST_ATTR_INACT_CONNS] && - dest_attrs[IPVS_DEST_ATTR_PERSIST_CONNS])) + dest_attrs[IPVS_DEST_ATTR_PERSIST_CONNS])) { return -1; + } memcpy(&(d->entrytable[i].addr), nla_data(dest_attrs[IPVS_DEST_ATTR_ADDR]), @@ -927,11 +1091,18 @@ static int ipvs_dests_parse_cb(struct nl_msg *msg, void *arg) d->entrytable[i].af = d->af; if (ipvs_parse_stats(&(d->entrytable[i].stats), - dest_attrs[IPVS_DEST_ATTR_STATS]) != 0) + dest_attrs[IPVS_DEST_ATTR_STATS]) != 0) { return -1; + } - i++; + if (d->fwmark == 1 && dest_attrs[IPVS_DEST_ATTR_SNATRULE] ) { + if (ipvs_parse_snat_rule(&(d->entrytable[i].snat_rule), + dest_attrs[IPVS_DEST_ATTR_SNATRULE]) != 0) { + return -1; + } + } + i++; d->num_dests = i; d = realloc(d, sizeof(*d) + sizeof(ipvs_dest_entry_t) * (d->num_dests + 1)); *dp = d; diff --git a/tools/keepalived/keepalived/libipvs-2.6/libipvs.h b/tools/keepalived/keepalived/libipvs-2.6/libipvs.h index 60a617f4..fc2a622e 100644 --- a/tools/keepalived/keepalived/libipvs-2.6/libipvs.h +++ b/tools/keepalived/keepalived/libipvs-2.6/libipvs.h @@ -38,7 +38,15 @@ #define OPT_PERSISTENCE_ENGINE 0x400000 #define OPT_LOCAL_ADDRESS 0x800000 #define OPT_SYNPROXY 0x1000000 -#define NUMBER_OF_OPT 25 +#define OPT_SNAT_FROM 0x2000000 +#define OPT_SNAT_TO 0x4000000 +#define OPT_SNAT_GW 0x8000000 +#define OPT_SNAT_SOURCE 0x10000000 +#define OPT_SNAT_ALGO 0x20000000 +#define OPT_SNAT_NEWGW 0x40000000 +#define OPT_SNAT_OUTDEV 0x80000000 + +#define NUMBER_OF_OPT 32 #define MINIMUM_IPVS_VERSION_MAJOR 1 #define MINIMUM_IPVS_VERSION_MINOR 1 @@ -60,9 +68,9 @@ */ #define IPVS_SVC_PERSISTENT_TIMEOUT (6*60) - typedef struct ip_vs_service_user ipvs_service_t; typedef struct ip_vs_dest_user ipvs_dest_t; +typedef struct ip_vs_dest_snat_user ipvs_snat_dest_t; typedef struct ip_vs_laddr_user ipvs_laddr_t; typedef struct ip_vs_timeout_user ipvs_timeout_t; typedef struct ip_vs_daemon_user ipvs_daemon_t; @@ -113,10 +121,17 @@ extern int ipvs_update_dest(ipvs_service_t *svc, ipvs_dest_t *dest); /* remove a destination server from a service */ extern int ipvs_del_dest(ipvs_service_t *svc, ipvs_dest_t *dest); +/* for lvs snat dest */ +extern int ipvs_add_snat_dest(ipvs_service_t *svc, ipvs_snat_dest_t *snat_dest); +extern int ipvs_update_snat_dest(ipvs_service_t *svc, ipvs_snat_dest_t *snat_dest); +extern int ipvs_del_snat_dest(ipvs_service_t *svc, ipvs_snat_dest_t *snat_dest); + extern int ipvs_add_laddr(ipvs_service_t *svc, ipvs_laddr_t * laddr); extern int ipvs_del_laddr(ipvs_service_t *svc, ipvs_laddr_t * laddr); extern struct ip_vs_get_laddrs *ipvs_get_laddrs(ipvs_service_entry_t *svc); +extern void ipvs_service_entry_2_user(const ipvs_service_entry_t *entry, ipvs_service_t *user); + /* set timeout */ extern int ipvs_set_timeout(ipvs_timeout_t *to); diff --git a/tools/quagga/redhat/quagga.spec b/tools/quagga/redhat/quagga.spec index d7f6390a..b65d8588 100644 --- a/tools/quagga/redhat/quagga.spec +++ b/tools/quagga/redhat/quagga.spec @@ -90,7 +90,7 @@ Summary: Routing daemon Name: quagga Version: 0.99.20 -Release: 20110929%{release_rev} +Release: 20140403%{release_rev} License: GPL Group: System Environment/Daemons Source0: http://www.quagga.net/snapshots/cvs/%{name}-%{version}.tar.gz diff --git a/tools/quagga/vtysh/extract.pl b/tools/quagga/vtysh/extract.pl index 9728a7f9..b58e09dd 100755 --- a/tools/quagga/vtysh/extract.pl +++ b/tools/quagga/vtysh/extract.pl @@ -1,4 +1,4 @@ -#! +#! /usr/bin/perl ## ## vtysh/extract.pl. Generated from extract.pl.in by configure. ## From f28f3921e2616c359c847a96de16ef6e0f6e2932 Mon Sep 17 00:00:00 2001 From: jlijian3 Date: Mon, 13 Jul 2015 17:14:06 +0800 Subject: [PATCH 2/3] fix fullnat udp no checksum error --- kernel/net/netfilter/ipvs/ip_vs_proto_udp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/net/netfilter/ipvs/ip_vs_proto_udp.c b/kernel/net/netfilter/ipvs/ip_vs_proto_udp.c index 1c539129..2b4f0b0b 100644 --- a/kernel/net/netfilter/ipvs/ip_vs_proto_udp.c +++ b/kernel/net/netfilter/ipvs/ip_vs_proto_udp.c @@ -462,7 +462,7 @@ udp_fnat_out_handler(struct sk_buff *skb, if (skb->ip_summed == CHECKSUM_PARTIAL) { udp_partial_csum_reset(cp->af, (skb->len - udphoff), udph, &cp->vaddr, &cp->caddr); - } else if (!cp->app) { + } else if (!cp->app && (udph->check != 0)) { /* Only port and addr are changed, do fast csum update */ udp_fast_csum_update(cp->af, udph, &cp->daddr, &cp->vaddr, cp->dport, cp->vport); From fa7e3d25855c5d2aca8219af21379fef07b1a58f Mon Sep 17 00:00:00 2001 From: monkeyerKong Date: Mon, 1 Jul 2019 21:05:41 +0800 Subject: [PATCH 3/3] fix issue ipvsadm -lnc show incorrect state of TCP/UDP connections. --- tools/ipvsadm/ipvsadm.c | 70 ++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/tools/ipvsadm/ipvsadm.c b/tools/ipvsadm/ipvsadm.c index 38118e18..4975fad0 100644 --- a/tools/ipvsadm/ipvsadm.c +++ b/tools/ipvsadm/ipvsadm.c @@ -1546,29 +1546,27 @@ static void check_ipvs_version(void) static void print_conn(char *buf, unsigned int format) { - char protocol[8]; + int n; + char protocol[8]; + char state[16]; + char expire_str[12]; + unsigned short af = AF_INET; + unsigned int expires; + unsigned int minutes, seconds; unsigned short proto; + unsigned short cport, vport, lport, dport; union nf_inet_addr caddr; - unsigned short cport; union nf_inet_addr vaddr; - unsigned short vport; + union nf_inet_addr laddr; union nf_inet_addr daddr; - unsigned short dport; - char state[16]; - unsigned int expires; - unsigned short af = AF_INET; char pe_name[IP_VS_PENAME_MAXLEN]; char pe_data[IP_VS_PEDATA_MAXLEN]; + char cip[INET6_ADDRSTRLEN], vip[INET6_ADDRSTRLEN], lip[INET6_ADDRSTRLEN], dip[INET6_ADDRSTRLEN]; + char *cname, *vname, *lname, *dname; - int n; - char temp1[INET6_ADDRSTRLEN], temp2[INET6_ADDRSTRLEN], temp3[INET6_ADDRSTRLEN]; - char *cname, *vname, *dname; - unsigned int minutes, seconds; - char expire_str[12]; - - if ((n = sscanf(buf, "%s %s %hX %s %hX %s %hX %s %d %s %s", - protocol, temp1, &cport, temp2, &vport, - temp3, &dport, state, &expires, + if ((n = sscanf(buf, "%s %s %hX %s %hX %s %hX %s %hX %s %d %s %s", + protocol, cip, &cport, vip, &vport, lip, &lport, + dip, &dport, state, &expires, pe_name, pe_data)) == -1) exit(1); @@ -1579,23 +1577,28 @@ static void print_conn(char *buf, unsigned int format) else proto = 0; - if (inet_pton(AF_INET6, temp1, &caddr.in6) > 0) { - inet_pton(AF_INET6, temp2, &vaddr.in6); - inet_pton(AF_INET6, temp3, &daddr.in6); + if (inet_pton(AF_INET6, cip, &caddr.in6) > 0) { + inet_pton(AF_INET6, vip, &vaddr.in6); + inet_pton(AF_INET6, lip, &laddr.in6); + inet_pton(AF_INET6, dip, &daddr.in6); af = AF_INET6; - } else if (inet_pton(AF_INET, temp1, &caddr.ip) > 0) { - inet_pton(AF_INET, temp2, &vaddr.ip); - inet_pton(AF_INET, temp3, &daddr.ip); + } else if (inet_pton(AF_INET, cip, &caddr.ip) > 0) { + inet_pton(AF_INET, vip, &vaddr.ip); + inet_pton(AF_INET, lip, &laddr.ip); + inet_pton(AF_INET, dip, &daddr.ip); } else { - caddr.ip = (__u32) htonl(strtoul(temp1, NULL, 16)); - vaddr.ip = (__u32) htonl(strtoul(temp2, NULL, 16)); - daddr.ip = (__u32) htonl(strtoul(temp3, NULL, 16)); + caddr.ip = (__u32) htonl(strtoul(cip, NULL, 16)); + vaddr.ip = (__u32) htonl(strtoul(vip, NULL, 16)); + laddr.ip = (__u32) htonl(strtoul(lip, NULL, 16)); + daddr.ip = (__u32) htonl(strtoul(dip, NULL, 16)); } if (!(cname = addrport_to_anyname(af, &caddr, cport, proto, format))) exit(1); if (!(vname = addrport_to_anyname(af, &vaddr, vport, proto, format))) exit(1); + if (!(lname = addrport_to_anyname(af, &laddr, lport, proto, format))) + exit(1); if (!(dname = addrport_to_anyname(af, &daddr, dport, proto, format))) exit(1); @@ -1604,15 +1607,16 @@ static void print_conn(char *buf, unsigned int format) sprintf(expire_str, "%02d:%02d", minutes, seconds); if (format & FMT_PERSISTENTCONN && n == 11) - printf("%-3s %-6s %-11s %-18s %-18s %-16s %-18s %s\n", - protocol, expire_str, state, cname, vname, dname, + printf("%-3s %-6s %-11s %-30s %-30s %-30s %-30s %-30s %s\n", + protocol, expire_str, state, cname, vname, lname, dname, pe_name, pe_data); else - printf("%-3s %-6s %-11s %-18s %-18s %s\n", - protocol, expire_str, state, cname, vname, dname); + printf("%-3s %-6s %-11s %-30s %-30s %-30s %-30s\n", + protocol, expire_str, state, cname, vname, lname, dname); free(cname); free(vname); + free(lname); free(dname); } @@ -1636,12 +1640,12 @@ void list_conn(unsigned int format) } printf("IPVS connection entries\n"); if (format & FMT_PERSISTENTCONN) - printf("pro expire %-11s %-18s %-18s %-18s %-16s %s\n", - "state", "source", "virtual", "destination", + printf("pro expire %-11s %-30s %-30s %-30s %-30s %-30s %s\n", + "state", "source", "virtual", "local", "destination", "pe name", "pe_data"); else - printf("pro expire %-11s %-18s %-18s %s\n", - "state", "source", "virtual", "destination"); + printf("pro expire %-11s %-30s %-30s %-30s %-30s\n", + "state", "source", "virtual", "local", "destination"); /* * Print the VS information according to the format