/* * Check and stop ack storm. * Return 0 if ack storm is found. */ static int syn_proxy_is_ack_storm(struct tcphdr *tcph, struct ip_vs_conn *cp) { /* only for syn-proxy sessions */ if (!(cp->flags & IP_VS_CONN_F_SYNPROXY) || !tcph->ack) return 1; if (unlikely(sysctl_ip_vs_synproxy_dup_ack_thresh == 0)) return 1; if (unlikely(tcph->seq == cp->last_seq && tcph->ack_seq == cp->last_ack_seq)) { atomic_inc(&cp->dup_ack_cnt); if (atomic_read(&cp->dup_ack_cnt) >= sysctl_ip_vs_synproxy_dup_ack_thresh) { atomic_set(&cp->dup_ack_cnt, sysctl_ip_vs_synproxy_dup_ack_thresh); /* update statistics */ IP_VS_INC_ESTATS(ip_vs_esmib, SYNPROXY_ACK_STORM); return 0; } return 1; } cp->last_seq = tcph->seq; cp->last_ack_seq = tcph->ack_seq; atomic_set(&cp->dup_ack_cnt, 0); return 1; }
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; }
/* * 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 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; }
static int tcp_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 tcphdr _tcph, *th; struct ip_vs_iphdr iph; ip_vs_fill_iphdr(af, skb_network_header(skb), &iph); th = skb_header_pointer(skb, iph.len, sizeof(_tcph), &_tcph); if (th == NULL) { *verdict = NF_DROP; return 0; } /* * Syn-proxy step 2 logic: receive client's * 3-handshake Ack packet */ if (ip_vs_synproxy_ack_rcv(af, skb, th, pp, cpp, &iph, verdict) == 0) { return 0; } if (th->syn && !th->ack && !th->fin && !th->rst && (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; } /* * Let the virtual server select a real server for the * incoming connection, and create a connection entry. */ *cpp = ip_vs_schedule(svc, skb, 0); if (!*cpp) { *verdict = ip_vs_leave(svc, skb, pp); return 0; } /* * Set private establish state timeout into cp from svc, * due cp may use its user establish state timeout * different from sysctl_ip_vs_tcp_timeouts */ (*cpp)->est_timeout = svc->est_timeout; ip_vs_service_put(svc); return 1; } /* drop tcp packet which send to vip and !vport */ if (sysctl_ip_vs_tcp_drop_entry && (svc = ip_vs_lookup_vip(af, iph.protocol, &iph.daddr))) { IP_VS_INC_ESTATS(ip_vs_esmib, DEFENCE_TCP_DROP); *verdict = NF_DROP; return 0; } return 1; }