int ip_vs_synproxy_filter_ack(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, int *verdict) { struct tcphdr _tcph, *th; th = skb_header_pointer(skb, iph->len, sizeof(_tcph), &_tcph); if (unlikely(NULL == th)) { IP_VS_ERR_RL("skb has a invalid tcp header\n"); *verdict = NF_DROP; return 0; } spin_lock(&cp->lock); if ((cp->flags & IP_VS_CONN_F_SYNPROXY) && cp->state == IP_VS_TCP_S_SYN_SENT) { /* * Not a ack packet, drop it. */ if (!th->ack) { spin_unlock(&cp->lock); *verdict = NF_DROP; return 0; } if (sysctl_ip_vs_synproxy_skb_store_thresh < skb_queue_len(&cp->ack_skb)) { spin_unlock(&cp->lock); /* update statistics */ IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_SYNSEND_QLEN); *verdict = NF_DROP; return 0; } /* * Still some space left, store it. */ skb_queue_tail(&cp->ack_skb, skb); spin_unlock(&cp->lock); *verdict = NF_STOLEN; return 0; } spin_unlock(&cp->lock); return 1; }
int ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp) { struct rt6_info *rt; /* Route to the other host */ struct in6_addr saddr; /* Source for tunnel */ struct net_device *tdev; /* Device to other host */ struct ipv6hdr *old_iph = ipv6_hdr(skb); struct ipv6hdr *iph; /* Our new IP header */ unsigned int max_headroom; /* The extra header space needed */ int mtu; int ret; EnterFunction(10); if (!(rt = __ip_vs_get_out_rt_v6(skb, cp->dest, &cp->daddr.in6, &saddr, 1, 1|2))) goto tx_error_icmp; if (__ip_vs_is_local_route6(rt)) { dst_release(&rt->dst); IP_VS_XMIT(NFPROTO_IPV6, skb, cp, 1); } tdev = rt->dst.dev; mtu = dst_mtu(&rt->dst) - sizeof(struct ipv6hdr); if (mtu < IPV6_MIN_MTU) { IP_VS_DBG_RL("%s(): mtu less than %d\n", __func__, IPV6_MIN_MTU); goto tx_error_put; } if (skb_dst(skb)) skb_dst(skb)->ops->update_pmtu(skb_dst(skb), mtu); if (mtu < ntohs(old_iph->payload_len) + sizeof(struct ipv6hdr) && !skb_is_gso(skb)) { if (!skb->dev) { struct net *net = dev_net(skb_dst(skb)->dev); skb->dev = net->loopback_dev; } icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu); IP_VS_DBG_RL("%s(): frag needed\n", __func__); goto tx_error_put; } /* * Okay, now see if we can stuff it in the buffer as-is. */ max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(struct ipv6hdr); if (skb_headroom(skb) < max_headroom || skb_cloned(skb) || skb_shared(skb)) { struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom); if (!new_skb) { dst_release(&rt->dst); kfree_skb(skb); IP_VS_ERR_RL("%s(): no memory\n", __func__); return NF_STOLEN; } kfree_skb(skb); skb = new_skb; old_iph = ipv6_hdr(skb); } skb->transport_header = skb->network_header; skb_push(skb, sizeof(struct ipv6hdr)); skb_reset_network_header(skb); memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); /* drop old route */ skb_dst_drop(skb); skb_dst_set(skb, &rt->dst); /* * Push down and install the IPIP header. */ iph = ipv6_hdr(skb); iph->version = 6; iph->nexthdr = IPPROTO_IPV6; iph->payload_len = old_iph->payload_len; be16_add_cpu(&iph->payload_len, sizeof(*old_iph)); iph->priority = old_iph->priority; memset(&iph->flow_lbl, 0, sizeof(iph->flow_lbl)); ipv6_addr_copy(&iph->daddr, &cp->daddr.in6); ipv6_addr_copy(&iph->saddr, &saddr); iph->hop_limit = old_iph->hop_limit; /* Another hack: avoid icmp_send in ip_fragment */ skb->local_df = 1; ret = IP_VS_XMIT_TUNNEL(skb, cp); if (ret == NF_ACCEPT) ip6_local_out(skb); else if (ret == NF_DROP) kfree_skb(skb); LeaveFunction(10); return NF_STOLEN; tx_error_icmp: dst_link_failure(skb); tx_error: kfree_skb(skb); LeaveFunction(10); return NF_STOLEN; tx_error_put: dst_release(&rt->dst); goto tx_error; }
/* * IP Tunneling transmitter * * This function encapsulates the packet in a new IP packet, its * destination will be set to cp->daddr. Most code of this function * is taken from ipip.c. * * It is used in VS/TUN cluster. The load balancer selects a real * server from a cluster based on a scheduling algorithm, * encapsulates the request packet and forwards it to the selected * server. For example, all real servers are configured with * "ifconfig tunl0 <Virtual IP Address> up". When the server receives * the encapsulated packet, it will decapsulate the packet, processe * the request and return the response packets directly to the client * without passing the load balancer. This can greatly increase the * scalability of virtual server. * * Used for ANY protocol */ int ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp) { struct rtable *rt; /* Route to the other host */ struct net_device *tdev; /* Device to other host */ struct iphdr *old_iph = ip_hdr(skb); u8 tos = old_iph->tos; __be16 df = old_iph->frag_off; struct iphdr *iph; /* Our new IP header */ unsigned int max_headroom; /* The extra header space needed */ int mtu; int ret; EnterFunction(10); if (!(rt = __ip_vs_get_out_rt(skb, cp->dest, cp->daddr.ip, RT_TOS(tos), IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL))) goto tx_error_icmp; if (rt->rt_flags & RTCF_LOCAL) { ip_rt_put(rt); IP_VS_XMIT(NFPROTO_IPV4, skb, cp, 1); } tdev = rt->dst.dev; mtu = dst_mtu(&rt->dst) - sizeof(struct iphdr); if (mtu < 68) { IP_VS_DBG_RL("%s(): mtu less than 68\n", __func__); goto tx_error_put; } if (skb_dst(skb)) skb_dst(skb)->ops->update_pmtu(skb_dst(skb), mtu); df |= (old_iph->frag_off & htons(IP_DF)); if ((old_iph->frag_off & htons(IP_DF) && mtu < ntohs(old_iph->tot_len) && !skb_is_gso(skb))) { icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu)); IP_VS_DBG_RL("%s(): frag needed\n", __func__); goto tx_error_put; } /* * Okay, now see if we can stuff it in the buffer as-is. */ max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(struct iphdr); if (skb_headroom(skb) < max_headroom || skb_cloned(skb) || skb_shared(skb)) { struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom); if (!new_skb) { ip_rt_put(rt); kfree_skb(skb); IP_VS_ERR_RL("%s(): no memory\n", __func__); return NF_STOLEN; } kfree_skb(skb); skb = new_skb; old_iph = ip_hdr(skb); } skb->transport_header = skb->network_header; /* fix old IP header checksum */ ip_send_check(old_iph); skb_push(skb, sizeof(struct iphdr)); skb_reset_network_header(skb); memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); /* drop old route */ skb_dst_drop(skb); skb_dst_set(skb, &rt->dst); /* * Push down and install the IPIP header. */ iph = ip_hdr(skb); iph->version = 4; iph->ihl = sizeof(struct iphdr)>>2; iph->frag_off = df; iph->protocol = IPPROTO_IPIP; iph->tos = tos; iph->daddr = rt->rt_dst; iph->saddr = rt->rt_src; iph->ttl = old_iph->ttl; ip_select_ident(iph, &rt->dst, NULL); /* Another hack: avoid icmp_send in ip_fragment */ skb->local_df = 1; ret = IP_VS_XMIT_TUNNEL(skb, cp); if (ret == NF_ACCEPT) ip_local_out(skb); else if (ret == NF_DROP) kfree_skb(skb); LeaveFunction(10); return NF_STOLEN; tx_error_icmp: dst_link_failure(skb); tx_error: kfree_skb(skb); LeaveFunction(10); return NF_STOLEN; tx_error_put: ip_rt_put(rt); goto tx_error; }
/* * Syn-proxy session reuse function. * Update syn_proxy_seq struct and clean syn-proxy related * members. */ int ip_vs_synproxy_reuse_conn(int af, struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, int *verdict) { struct tcphdr _tcph, *th = NULL; struct ip_vs_synproxy_opt opt; int res_cookie_check; u32 tcp_conn_reuse_states = 0; th = skb_header_pointer(skb, iph->len, sizeof(_tcph), &_tcph); if (unlikely(NULL == th)) { IP_VS_ERR_RL("skb has a invalid tcp header\n"); *verdict = NF_DROP; return 0; } tcp_conn_reuse_states = ((sysctl_ip_vs_synproxy_conn_reuse_cl << IP_VS_TCP_S_CLOSE) | (sysctl_ip_vs_synproxy_conn_reuse_tw << IP_VS_TCP_S_TIME_WAIT) | (sysctl_ip_vs_synproxy_conn_reuse_fw << IP_VS_TCP_S_FIN_WAIT) | (sysctl_ip_vs_synproxy_conn_reuse_cw << IP_VS_TCP_S_CLOSE_WAIT) | (sysctl_ip_vs_synproxy_conn_reuse_la << IP_VS_TCP_S_LAST_ACK)); if (((1 << (cp->state)) & tcp_conn_reuse_states) && (cp->flags & IP_VS_CONN_F_SYNPROXY) && (!th->syn && th->ack && !th->rst && !th->fin) && (cp->syn_proxy_seq.init_seq != htonl((__u32) ((ntohl(th->ack_seq) - 1))))) { /* * Import: set tcp hdr before cookie check, as it * will be used in cookie_check funcs. */ skb_set_transport_header(skb, iph->len); #ifdef CONFIG_IP_VS_IPV6 if (af == AF_INET6) { res_cookie_check = ip_vs_synproxy_v6_cookie_check(skb, ntohl (th-> ack_seq) - 1, &opt); } else #endif { res_cookie_check = ip_vs_synproxy_v4_cookie_check(skb, ntohl (th-> ack_seq) - 1, &opt); } if (!res_cookie_check) { /* update statistics */ IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_BAD_ACK); /* * Cookie check fail, let it go. */ return 1; } /* update statistics */ IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_OK_ACK); IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_CONN_REUSED); switch (cp->old_state) { case IP_VS_TCP_S_CLOSE: IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_CONN_REUSED_CLOSE); break; case IP_VS_TCP_S_TIME_WAIT: IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_CONN_REUSED_TIMEWAIT); break; case IP_VS_TCP_S_FIN_WAIT: IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_CONN_REUSED_FINWAIT); break; case IP_VS_TCP_S_CLOSE_WAIT: IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_CONN_REUSED_CLOSEWAIT); break; case IP_VS_TCP_S_LAST_ACK: IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_CONN_REUSED_LASTACK); break; } spin_lock(&cp->lock); __syn_proxy_reuse_conn(cp, skb, th, pp); spin_unlock(&cp->lock); if (unlikely(!syn_proxy_send_rs_syn(af, th, cp, skb, pp, &opt))) { IP_VS_ERR_RL ("syn_proxy_send_rs_syn failed when reuse conn!\n"); /* release conn immediately */ spin_lock(&cp->lock); cp->timeout = 0; spin_unlock(&cp->lock); } *verdict = NF_STOLEN; return 0; } return 1; }
/* * Syn-proxy step 3 logic: receive syn-ack from rs * Update syn_proxy_seq.delta and send stored ack skbs * to rs. */ int ip_vs_synproxy_synack_rcv(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, int ihl, int *verdict) { struct tcphdr _tcph, *th; struct sk_buff_head save_skb; struct sk_buff *tmp_skb = NULL; struct ip_vs_dest *dest = cp->dest; th = skb_header_pointer(skb, ihl, sizeof(_tcph), &_tcph); if (th == NULL) { *verdict = NF_DROP; return 0; } IP_VS_DBG(6, "in syn_proxy_synack_rcv, " "seq = %u ack_seq = %u %c%c%c cp->is_synproxy = %u cp->state = %u\n", ntohl(th->seq), ntohl(th->ack_seq), (th->syn) ? 'S' : '-', (th->ack) ? 'A' : '-', (th->rst) ? 'R' : '-', cp->flags & IP_VS_CONN_F_SYNPROXY, cp->state); skb_queue_head_init(&save_skb); spin_lock(&cp->lock); if ((th->syn) && (th->ack) && (!th->rst) && (cp->flags & IP_VS_CONN_F_SYNPROXY) && cp->state == IP_VS_TCP_S_SYN_SENT) { cp->syn_proxy_seq.delta = htonl(cp->syn_proxy_seq.init_seq) - htonl(th->seq); cp->timeout = pp->timeout_table[cp->state = IP_VS_TCP_S_ESTABLISHED]; if (dest) { atomic_inc(&dest->activeconns); atomic_dec(&dest->inactconns); cp->flags &= ~IP_VS_CONN_F_INACTIVE; } /* save tcp sequense for fullnat/nat, INside to OUTside */ if (sysctl_ip_vs_conn_expire_tcp_rst == 1) { cp->rs_end_seq = htonl(ntohl(th->seq) + 1); cp->rs_ack_seq = th->ack_seq; IP_VS_DBG_RL("packet from RS, seq:%u ack_seq:%u.", ntohl(th->seq), ntohl(th->ack_seq)); IP_VS_DBG_RL("port:%u->%u", ntohs(th->source), ntohs(th->dest)); } /* First: free stored syn skb */ if ((tmp_skb = xchg(&cp->syn_skb, NULL)) != NULL) { kfree_skb(tmp_skb); tmp_skb = NULL; } if (skb_queue_len(&cp->ack_skb) <= 0) { /* * FIXME: maybe a bug here, print err msg and go. * Attention: cp->state has been changed and we * should still DROP the Syn/Ack skb. */ IP_VS_ERR_RL ("Got ack_skb NULL pointer in syn_proxy_synack_rcv\n"); spin_unlock(&cp->lock); *verdict = NF_DROP; return 0; } while ((tmp_skb = skb_dequeue(&cp->ack_skb)) != NULL) { skb_queue_tail(&save_skb, tmp_skb); } /* * Release the lock, because we don't * touch session any more. */ spin_unlock(&cp->lock); while ((tmp_skb = skb_dequeue(&save_skb)) != NULL) { /* If xmit failed, syn_skb will be freed correctly. */ cp->packet_xmit(tmp_skb, cp, pp); } *verdict = NF_DROP; return 0; } else if ((th->rst) && (cp->flags & IP_VS_CONN_F_SYNPROXY) && cp->state == IP_VS_TCP_S_SYN_SENT) { __u32 temp_seq; temp_seq = ntohl(th->seq); IP_VS_DBG(6, "get rst from rs, seq = %u ack_seq= %u\n", ntohl(th->seq), ntohl(th->ack_seq)); /* coute the delta of seq */ cp->syn_proxy_seq.delta = ntohl(cp->syn_proxy_seq.init_seq) - ntohl(th->seq); cp->timeout = pp->timeout_table[cp->state = IP_VS_TCP_S_CLOSE]; spin_unlock(&cp->lock); th->seq = htonl(ntohl(th->seq) + 1); syn_proxy_seq_csum_update(th, htonl(temp_seq), th->seq); return 1; } spin_unlock(&cp->lock); return 1; }
/* * Syn-proxy step 2 logic * Receive client's 3-handshakes Ack packet, do cookie check * and then send syn to rs after creating a session. * */ int ip_vs_synproxy_ack_rcv(int af, struct sk_buff *skb, struct tcphdr *th, struct ip_vs_protocol *pp, struct ip_vs_conn **cpp, struct ip_vs_iphdr *iph, int *verdict) { struct ip_vs_synproxy_opt opt; struct ip_vs_service *svc; int res_cookie_check; /* * Don't check svc syn-proxy flag, as it may * be changed after syn-proxy step 1. */ if (!th->syn && th->ack && !th->rst && !th->fin && (svc = ip_vs_service_get(af, skb->mark, iph->protocol, &iph->daddr, th->dest))) { if (ip_vs_todrop()) { /* * It seems that we are very loaded. * We have to drop this packet :( */ ip_vs_service_put(svc); *verdict = NF_DROP; return 0; } if (sysctl_ip_vs_synproxy_defer && !syn_proxy_ack_has_data(skb, iph, th)) { /* update statistics */ IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_NULL_ACK); /* * When expecting ack packet with payload, * we get a pure ack, so have to drop it. */ ip_vs_service_put(svc); *verdict = NF_DROP; return 0; } /* * Import: set tcp hdr before cookie check, as it * will be used in cookie_check funcs. */ skb_set_transport_header(skb, iph->len); #ifdef CONFIG_IP_VS_IPV6 if (af == AF_INET6) { res_cookie_check = ip_vs_synproxy_v6_cookie_check(skb, ntohl (th-> ack_seq) - 1, &opt); } else #endif { res_cookie_check = ip_vs_synproxy_v4_cookie_check(skb, ntohl (th-> ack_seq) - 1, &opt); } if (!res_cookie_check) { /* update statistics */ IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_BAD_ACK); /* * Cookie check fail, drop it. */ IP_VS_DBG(6, "syn_cookie check failed seq=%u\n", ntohl(th->ack_seq) - 1); ip_vs_service_put(svc); *verdict = NF_DROP; return 0; } /* update statistics */ IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_OK_ACK); /* * Let the virtual server select a real server for the * incoming connection, and create a connection entry. */ *cpp = ip_vs_schedule(svc, skb, 1); if (!*cpp) { IP_VS_DBG(6, "ip_vs_schedule failed\n"); *verdict = ip_vs_leave(svc, skb, pp); return 0; } /* * Release service, we don't need it any more. */ ip_vs_service_put(svc); /* * Do anything but print a error msg when fail. * Because session will be correctly freed in ip_vs_conn_expire. */ if (!syn_proxy_send_rs_syn(af, th, *cpp, skb, pp, &opt)) { IP_VS_ERR_RL("syn_proxy_send_rs_syn failed!\n"); } /* count in the ack packet (STOLEN by synproxy) */ ip_vs_in_stats(*cpp, skb); /* * Active sesion timer, and dec refcnt. * Also stole the skb, and let caller return immediately. */ ip_vs_conn_put(*cpp); *verdict = NF_STOLEN; return 0; } return 1; }
/* * Create syn packet and send it to rs. * ATTENTION: we also store syn skb in cp if syn retransimition * is tured on. */ static int syn_proxy_send_rs_syn(int af, const struct tcphdr *th, struct ip_vs_conn *cp, struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_synproxy_opt *opt) { struct sk_buff *syn_skb; int tcp_hdr_size; __u8 tcp_flags = TCPCB_FLAG_SYN; unsigned int tcphoff; struct tcphdr *new_th; if (!cp->packet_xmit) { IP_VS_ERR_RL("warning: packet_xmit is null"); return 0; } syn_skb = alloc_skb(MAX_TCP_HEADER + 15, GFP_ATOMIC); if (unlikely(syn_skb == NULL)) { IP_VS_ERR_RL("alloc skb failed when send rs syn packet\n"); return 0; } /* Reserve space for headers */ skb_reserve(syn_skb, MAX_TCP_HEADER); tcp_hdr_size = (sizeof(struct tcphdr) + TCPOLEN_MSS + (opt->tstamp_ok ? TCPOLEN_TSTAMP_ALIGNED : 0) + (opt->wscale_ok ? TCPOLEN_WSCALE_ALIGNED : 0) + /* SACK_PERM is in the place of NOP NOP of TS */ ((opt->sack_ok && !opt->tstamp_ok) ? TCPOLEN_SACKPERM_ALIGNED : 0)); new_th = (struct tcphdr *)skb_push(syn_skb, tcp_hdr_size); /* Compose tcp header */ skb_reset_transport_header(syn_skb); syn_skb->csum = 0; /* Set tcp hdr */ new_th->source = th->source; new_th->dest = th->dest; new_th->seq = htonl(ntohl(th->seq) - 1); new_th->ack_seq = 0; *(((__u16 *) new_th) + 6) = htons(((tcp_hdr_size >> 2) << 12) | tcp_flags); /* FIX_ME: what window should we use */ new_th->window = htons(5000); new_th->check = 0; new_th->urg_ptr = 0; new_th->urg = 0; new_th->ece = 0; new_th->cwr = 0; syn_proxy_syn_build_options((__be32 *) (new_th + 1), opt); /* * Set ip hdr * Attention: set source and dest addr to ack skb's. * we rely on packet_xmit func to do NATs thing. */ #ifdef CONFIG_IP_VS_IPV6 if (af == AF_INET6) { struct ipv6hdr *ack_iph = ipv6_hdr(skb); struct ipv6hdr *iph = (struct ipv6hdr *)skb_push(syn_skb, sizeof(struct ipv6hdr)); tcphoff = sizeof(struct ipv6hdr); skb_reset_network_header(syn_skb); memcpy(&iph->saddr, &ack_iph->saddr, sizeof(struct in6_addr)); memcpy(&iph->daddr, &ack_iph->daddr, sizeof(struct in6_addr)); iph->version = 6; iph->nexthdr = NEXTHDR_TCP; iph->payload_len = htons(tcp_hdr_size); iph->hop_limit = IPV6_DEFAULT_HOPLIMIT; new_th->check = 0; syn_skb->csum = skb_checksum(syn_skb, tcphoff, syn_skb->len - tcphoff, 0); new_th->check = csum_ipv6_magic(&iph->saddr, &iph->daddr, syn_skb->len - tcphoff, IPPROTO_TCP, syn_skb->csum); } else #endif { struct iphdr *ack_iph = ip_hdr(skb); u32 rtos = RT_TOS(ack_iph->tos); struct iphdr *iph = (struct iphdr *)skb_push(syn_skb, sizeof(struct iphdr)); tcphoff = sizeof(struct iphdr); skb_reset_network_header(syn_skb); *((__u16 *) iph) = htons((4 << 12) | (5 << 8) | (rtos & 0xff)); iph->tot_len = htons(syn_skb->len); iph->frag_off = htons(IP_DF); /* FIX_ME: what ttl shoule we use */ iph->ttl = IPDEFTTL; iph->protocol = IPPROTO_TCP; iph->saddr = ack_iph->saddr; iph->daddr = ack_iph->daddr; ip_send_check(iph); new_th->check = 0; syn_skb->csum = skb_checksum(syn_skb, tcphoff, syn_skb->len - tcphoff, 0); new_th->check = csum_tcpudp_magic(iph->saddr, iph->daddr, syn_skb->len - tcphoff, IPPROTO_TCP, syn_skb->csum); } /* Save syn_skb if syn retransmission is on */ if (sysctl_ip_vs_synproxy_syn_retry > 0) { cp->syn_skb = skb_copy(syn_skb, GFP_ATOMIC); atomic_set(&cp->syn_retry_max, sysctl_ip_vs_synproxy_syn_retry); } /* Save info for fast_response_xmit */ if(sysctl_ip_vs_fast_xmit && 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->indev == NULL)) { cp->indev = skb->dev; dev_hold(cp->indev); } if (unlikely(cp->indev != skb->dev)) { dev_put(cp->indev); cp->indev = skb->dev; dev_hold(cp->indev); } memcpy(cp->src_hwaddr, eth->h_source, ETH_ALEN); memcpy(cp->dst_hwaddr, eth->h_dest, ETH_ALEN); IP_VS_INC_ESTATS(ip_vs_esmib, FAST_XMIT_SYNPROXY_SAVE); IP_VS_DBG_RL("syn_proxy_send_rs_syn netdevice:%s\n", netdev_name(skb->dev)); } /* count in the syn packet */ ip_vs_in_stats(cp, skb); /* If xmit failed, syn_skb will be freed correctly. */ cp->packet_xmit(syn_skb, cp, pp); return 1; }
/* * syn-proxy step 1 logic: * Check if synproxy is enabled for this skb, and * send Syn/Ack back. * * Synproxy is enabled when: * 1) skb is a Syn packet. * 2) And the service is synproxy-enable. * 3) And ip_vs_todrop return false. * * @return 0 means the caller should return at once and use * verdict as return value, return 1 for nothing. */ int ip_vs_synproxy_syn_rcv(int af, struct sk_buff *skb, struct ip_vs_iphdr *iph, int *verdict) { struct ip_vs_service *svc = NULL; struct tcphdr _tcph, *th; struct ip_vs_synproxy_opt tcp_opt; th = skb_header_pointer(skb, iph->len, sizeof(_tcph), &_tcph); if (unlikely(th == NULL)) { goto syn_rcv_out; } if (th->syn && !th->ack && !th->rst && !th->fin && (svc = ip_vs_service_get(af, skb->mark, iph->protocol, &iph->daddr, th->dest)) && (svc->flags & IP_VS_CONN_F_SYNPROXY)) { // release service here, because don't use it any all. ip_vs_service_put(svc); if (ip_vs_todrop()) { /* * It seems that we are very loaded. * We have to drop this packet :( */ goto syn_rcv_out; } } else { /* * release service. */ if (svc != NULL) { ip_vs_service_put(svc); } return 1; } /* update statistics */ IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_SYN_CNT); /* 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 syn_rcv_out; } /* Drop old skb */ kfree_skb(skb); skb = new_skb; } /* reuse skb here: deal with tcp options, exchage ip, port. */ syn_proxy_reuse_skb(af, skb, &tcp_opt); if (unlikely(skb->dev == NULL)) { IP_VS_ERR_RL("%s: skb->dev is null !!!\n", __func__); goto syn_rcv_out; } /* Send the packet out */ if (likely(skb->dev->type == ARPHRD_ETHER)) { unsigned char t_hwaddr[ETH_ALEN]; /* Move the data pointer to point to the link layer header */ struct ethhdr *eth = (struct ethhdr *)skb_mac_header(skb); skb->data = (unsigned char *)skb_mac_header(skb); skb->len += ETH_HLEN; //sizeof(skb->mac.ethernet); memcpy(t_hwaddr, (eth->h_dest), ETH_ALEN); memcpy((eth->h_dest), (eth->h_source), ETH_ALEN); memcpy((eth->h_source), t_hwaddr, ETH_ALEN); skb->pkt_type = PACKET_OUTGOING; } else if (skb->dev->type == ARPHRD_LOOPBACK) { /* set link layer */ if (likely(skb_mac_header_was_set(skb))) { skb->data = skb_mac_header(skb); skb->len += sizeof(struct ethhdr); } else { skb_push(skb, sizeof(struct ethhdr)); skb_reset_mac_header(skb); } } dev_queue_xmit(skb); *verdict = NF_STOLEN; return 0; syn_rcv_out: /* Drop the packet when all things are right also, * then we needn't to kfree_skb() */ *verdict = NF_DROP; return 0; }
/* * Handle ICMP messages in the outside-to-inside direction (incoming) * and sometimes in outgoing direction from ip_vs_forward_icmp. * Find any that might be relevant, check against existing connections, * forward to the right destination host if relevant. * Currently handles error types - unreachable, quench, ttl exceeded. */ static int ip_vs_in_icmp(struct sk_buff **skb_p) { struct sk_buff *skb = *skb_p; struct iphdr *iph; struct icmphdr *icmph; struct iphdr *ciph; /* The ip header contained within the ICMP */ __u16 *pptr; /* port numbers from TCP/UDP contained header */ unsigned short len; unsigned short clen, csize; struct ip_vs_conn *cp; struct rtable *rt; /* Route to the other host */ int mtu; if (skb_is_nonlinear(skb)) { if (skb_linearize(skb, GFP_ATOMIC) != 0) return NF_DROP; } iph = skb->nh.iph; ip_send_check(iph); icmph = (struct icmphdr *)((char *)iph + (iph->ihl << 2)); len = ntohs(iph->tot_len) - (iph->ihl<<2); if (len < sizeof(struct icmphdr)) return NF_DROP; IP_VS_DBG(12, "icmp in (%d,%d) %u.%u.%u.%u -> %u.%u.%u.%u\n", icmph->type, ntohs(icmp_id(icmph)), NIPQUAD(iph->saddr), NIPQUAD(iph->daddr)); if ((icmph->type != ICMP_DEST_UNREACH) && (icmph->type != ICMP_SOURCE_QUENCH) && (icmph->type != ICMP_TIME_EXCEEDED)) return NF_ACCEPT; /* * If we get here we have an ICMP error of one of the above 3 types * Now find the contained IP header */ clen = len - sizeof(struct icmphdr); if (clen < sizeof(struct iphdr)) return NF_DROP; ciph = (struct iphdr *) (icmph + 1); csize = ciph->ihl << 2; if (clen < csize) return NF_DROP; /* We are only interested ICMPs generated from TCP or UDP packets */ if (ciph->protocol != IPPROTO_UDP && ciph->protocol != IPPROTO_TCP) return NF_ACCEPT; /* Skip non-first embedded TCP/UDP fragments */ if (ciph->frag_off & __constant_htons(IP_OFFSET)) return NF_ACCEPT; /* We need at least TCP/UDP ports here */ if (clen < csize + sizeof(struct udphdr)) return NF_DROP; /* Ensure the checksum is correct */ if (ip_compute_csum((unsigned char *) icmph, len)) { /* Failed checksum! */ IP_VS_ERR_RL("incoming ICMP: failed checksum from " "%d.%d.%d.%d!\n", NIPQUAD(iph->saddr)); return NF_DROP; } pptr = (__u16 *)&(((char *)ciph)[csize]); IP_VS_DBG(11, "Handling incoming ICMP for " "%u.%u.%u.%u:%d -> %u.%u.%u.%u:%d\n", NIPQUAD(ciph->saddr), ntohs(pptr[0]), NIPQUAD(ciph->daddr), ntohs(pptr[1])); /* This is pretty much what ip_vs_conn_in_get() does, except parameters are in the reverse order */ cp = ip_vs_conn_in_get(ciph->protocol, ciph->daddr, pptr[1], ciph->saddr, pptr[0]); if (cp == NULL) return NF_ACCEPT; ip_vs_in_stats(cp, skb); /* The ICMP packet for VS/TUN, VS/DR and LOCALNODE will be forwarded directly here, because there is no need to translate address/port back */ if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) { int ret; if (cp->packet_xmit) ret = cp->packet_xmit(skb, cp); else ret = NF_ACCEPT; atomic_inc(&cp->in_pkts); ip_vs_conn_put(cp); return ret; } /* * mangle and send the packet here */ if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos)))) goto tx_error_icmp; /* MTU checking */ mtu = rt->u.dst.pmtu; if ((skb->len > mtu) && (iph->frag_off&__constant_htons(IP_DF))) { ip_rt_put(rt); icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu)); IP_VS_DBG_RL("ip_vs_in_icmp(): frag needed\n"); goto tx_error; } /* drop old route */ dst_release(skb->dst); skb->dst = &rt->u.dst; /* copy-on-write the packet before mangling it */ if (ip_vs_skb_cow(skb, rt->u.dst.dev->hard_header_len, &iph, (unsigned char**)&icmph)) { ip_vs_conn_put(cp); return NF_DROP; } ciph = (struct iphdr *) (icmph + 1); pptr = (__u16 *)&(((char *)ciph)[csize]); /* The ICMP packet for VS/NAT must be written to correct addresses before being forwarded to the right server */ /* First change the dest IP address, and recalc checksum */ iph->daddr = cp->daddr; ip_send_check(iph); /* Now change the *source* address in the contained IP */ ciph->saddr = cp->daddr; ip_send_check(ciph); /* the TCP/UDP source port - cannot redo check */ pptr[0] = cp->dport; /* And finally the ICMP checksum */ icmph->checksum = 0; icmph->checksum = ip_compute_csum((unsigned char *) icmph, len); skb->ip_summed = CHECKSUM_UNNECESSARY; IP_VS_DBG(11, "Forwarding incoming ICMP to " "%u.%u.%u.%u:%d -> %u.%u.%u.%u:%d\n", NIPQUAD(ciph->saddr), ntohs(pptr[0]), NIPQUAD(ciph->daddr), ntohs(pptr[1])); #ifdef CONFIG_NETFILTER_DEBUG skb->nf_debug = 1 << NF_IP_LOCAL_OUT; #endif /* CONFIG_NETFILTER_DEBUG */ ip_send(skb); ip_vs_conn_put(cp); return NF_STOLEN; tx_error_icmp: dst_link_failure(skb); tx_error: dev_kfree_skb(skb); ip_vs_conn_put(cp); return NF_STOLEN; }