/* * It is hooked at the NF_IP_FORWARD chain, used only for VS/NAT. * Check if outgoing packet belongs to the established ip_vs_conn, * rewrite addresses of the packet and send it on its way... */ static unsigned int ip_vs_out(unsigned int hooknum, struct sk_buff **skb_p, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct sk_buff *skb = *skb_p; struct iphdr *iph; union ip_vs_tphdr h; struct ip_vs_conn *cp; int size; int ihl; EnterFunction(11); if (skb->nfcache & NFC_IPVS_PROPERTY) return NF_ACCEPT; iph = skb->nh.iph; if (iph->protocol == IPPROTO_ICMP) return ip_vs_out_icmp(skb_p); /* let it go if other IP protocols */ if (iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_UDP) return NF_ACCEPT; /* reassemble IP fragments */ if (iph->frag_off & __constant_htons(IP_MF|IP_OFFSET)) { skb = ip_defrag(skb, IP_DEFRAG_VS_OUT); if (!skb) return NF_STOLEN; iph = skb->nh.iph; *skb_p = skb; } /* make sure that protocol header available in skb data area, note that skb data area may be reallocated. */ ihl = iph->ihl << 2; if (ip_vs_header_check(skb, iph->protocol, ihl) == -1) return NF_DROP; iph = skb->nh.iph; h.raw = (char*) iph + ihl; /* * Check if the packet belongs to an old entry */ cp = ip_vs_conn_out_get(iph->protocol, iph->saddr, h.portp[0], iph->daddr, h.portp[1]); if (!cp) { if (sysctl_ip_vs_nat_icmp_send && ip_vs_lookup_real_service(iph->protocol, iph->saddr, h.portp[0])) { /* * Notify the real server: there is no existing * entry if it is not RST packet or not TCP packet. */ if (!h.th->rst || iph->protocol != IPPROTO_TCP) { icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0); kfree_skb(skb); return NF_STOLEN; } } IP_VS_DBG(12, "packet for %s %d.%d.%d.%d:%d " "continue traversal as normal.\n", ip_vs_proto_name(iph->protocol), NIPQUAD(iph->daddr), ntohs(h.portp[1])); if (skb_is_nonlinear(skb)) ip_send_check(iph); return NF_ACCEPT; } /* * If it has ip_vs_app helper, the helper may change the payload, * so it needs full checksum checking and checksum calculation. * If not, only the header (addr/port) is changed, so it is fast * to do incremental checksum update, and let the destination host * do final checksum checking. */ if (cp->app && skb_is_nonlinear(skb)) { if (skb_linearize(skb, GFP_ATOMIC) != 0) { ip_vs_conn_put(cp); return NF_DROP; } iph = skb->nh.iph; h.raw = (char*) iph + ihl; } size = skb->len - ihl; IP_VS_DBG(11, "O-pkt: %s size=%d\n", ip_vs_proto_name(iph->protocol), size); /* do TCP/UDP checksum checking if it has application helper */ if (cp->app && (iph->protocol != IPPROTO_UDP || h.uh->check != 0)) { switch (skb->ip_summed) { case CHECKSUM_NONE: skb->csum = csum_partial(h.raw, size, 0); case CHECKSUM_HW: if (csum_tcpudp_magic(iph->saddr, iph->daddr, size, iph->protocol, skb->csum)) { ip_vs_conn_put(cp); IP_VS_DBG_RL("Outgoing failed %s checksum " "from %d.%d.%d.%d (size=%d)!\n", ip_vs_proto_name(iph->protocol), NIPQUAD(iph->saddr), size); return NF_DROP; } break; default: /* CHECKSUM_UNNECESSARY */ break; } } IP_VS_DBG(11, "Outgoing %s %u.%u.%u.%u:%d->%u.%u.%u.%u:%d\n", ip_vs_proto_name(iph->protocol), NIPQUAD(iph->saddr), ntohs(h.portp[0]), NIPQUAD(iph->daddr), ntohs(h.portp[1])); /* mangle the packet */ iph->saddr = cp->vaddr; h.portp[0] = cp->vport; /* * Call application helper if needed */ if (ip_vs_app_pkt_out(cp, skb) != 0) { /* skb data has probably changed, update pointers */ iph = skb->nh.iph; h.raw = (char*)iph + ihl; size = skb->len - ihl; } /* * Adjust TCP/UDP checksums */ if (!cp->app && (iph->protocol != IPPROTO_UDP || h.uh->check != 0)) { /* Only port and addr are changed, do fast csum update */ ip_vs_fast_check_update(&h, cp->daddr, cp->vaddr, cp->dport, cp->vport, iph->protocol); if (skb->ip_summed == CHECKSUM_HW) skb->ip_summed = CHECKSUM_NONE; } else { /* full checksum calculation */ switch (iph->protocol) { case IPPROTO_TCP: h.th->check = 0; skb->csum = csum_partial(h.raw, size, 0); h.th->check = csum_tcpudp_magic(iph->saddr, iph->daddr, size, iph->protocol, skb->csum); IP_VS_DBG(11, "O-pkt: %s O-csum=%d (+%d)\n", ip_vs_proto_name(iph->protocol), h.th->check, (char*)&(h.th->check) - (char*)h.raw); break; case IPPROTO_UDP: h.uh->check = 0; skb->csum = csum_partial(h.raw, size, 0); h.uh->check = csum_tcpudp_magic(iph->saddr, iph->daddr, size, iph->protocol, skb->csum); if (h.uh->check == 0) h.uh->check = 0xFFFF; IP_VS_DBG(11, "O-pkt: %s O-csum=%d (+%d)\n", ip_vs_proto_name(iph->protocol), h.uh->check, (char*)&(h.uh->check) - (char*)h.raw); break; } } ip_send_check(iph); ip_vs_out_stats(cp, skb); ip_vs_set_state(cp, VS_STATE_OUTPUT, iph, h.portp); ip_vs_conn_put(cp); skb->nfcache |= NFC_IPVS_PROPERTY; LeaveFunction(11); return NF_ACCEPT; }
/* * It is hooked at the NF_IP_FORWARD chain, used only for VS/NAT. * Check if outgoing packet belongs to the established ip_vs_conn, * rewrite addresses of the packet and send it on its way... */ static unsigned int ip_vs_out(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct sk_buff *skb = *pskb; struct iphdr *iph; struct ip_vs_protocol *pp; struct ip_vs_conn *cp; int ihl; EnterFunction(11); if (skb->ipvs_property) return NF_ACCEPT; iph = ip_hdr(skb); if (unlikely(iph->protocol == IPPROTO_ICMP)) { int related, verdict = ip_vs_out_icmp(pskb, &related); if (related) return verdict; skb = *pskb; iph = ip_hdr(skb); } pp = ip_vs_proto_get(iph->protocol); if (unlikely(!pp)) return NF_ACCEPT; /* reassemble IP fragments */ if (unlikely(iph->frag_off & htons(IP_MF|IP_OFFSET) && !pp->dont_defrag)) { skb = ip_vs_gather_frags(skb, IP_DEFRAG_VS_OUT); if (!skb) return NF_STOLEN; iph = ip_hdr(skb); *pskb = skb; } ihl = iph->ihl << 2; /* * Check if the packet belongs to an existing entry */ cp = pp->conn_out_get(skb, pp, iph, ihl, 0); if (unlikely(!cp)) { if (sysctl_ip_vs_nat_icmp_send && (pp->protocol == IPPROTO_TCP || pp->protocol == IPPROTO_UDP)) { __be16 _ports[2], *pptr; pptr = skb_header_pointer(skb, ihl, sizeof(_ports), _ports); if (pptr == NULL) return NF_ACCEPT; /* Not for me */ if (ip_vs_lookup_real_service(iph->protocol, iph->saddr, pptr[0])) { /* * Notify the real server: there is no * existing entry if it is not RST * packet or not TCP packet. */ if (iph->protocol != IPPROTO_TCP || !is_tcp_reset(skb)) { icmp_send(skb,ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0); return NF_DROP; } } } IP_VS_DBG_PKT(12, pp, skb, 0, "packet continues traversal as normal"); return NF_ACCEPT; } IP_VS_DBG_PKT(11, pp, skb, 0, "Outgoing packet"); if (!ip_vs_make_skb_writable(pskb, ihl)) goto drop; /* mangle the packet */ if (pp->snat_handler && !pp->snat_handler(pskb, pp, cp)) goto drop; skb = *pskb; ip_hdr(skb)->saddr = cp->vaddr; ip_send_check(ip_hdr(skb)); /* For policy routing, packets originating from this * machine itself may be routed differently to packets * passing through. We want this packet to be routed as * if it came from this machine itself. So re-compute * the routing information. */ if (ip_route_me_harder(pskb, RTN_LOCAL) != 0) goto drop; skb = *pskb; IP_VS_DBG_PKT(10, pp, skb, 0, "After SNAT"); ip_vs_out_stats(cp, skb); ip_vs_set_state(cp, IP_VS_DIR_OUTPUT, skb, pp); ip_vs_conn_put(cp); skb->ipvs_property = 1; LeaveFunction(11); return NF_ACCEPT; drop: ip_vs_conn_put(cp); kfree_skb(*pskb); return NF_STOLEN; }
/* * It is hooked at the NF_IP_FORWARD chain, used only for VS/NAT. * Check if outgoing packet belongs to the established ip_vs_conn, * rewrite addresses of the packet and send it on its way... */ static unsigned int ip_vs_out(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct sk_buff *skb = *pskb; struct iphdr *iph; struct ip_vs_protocol *pp; struct ip_vs_conn *cp; int ihl; EnterFunction(11); if (skb->nfcache & NFC_IPVS_PROPERTY) return NF_ACCEPT; if (skb->ip_summed == CHECKSUM_HW) { if (skb_checksum_help(pskb, (out == NULL))) return NF_DROP; if (skb != *pskb) skb = *pskb; } iph = skb->nh.iph; if (unlikely(iph->protocol == IPPROTO_ICMP)) { int related, verdict = ip_vs_out_icmp(pskb, &related); if (related) return verdict; skb = *pskb; iph = skb->nh.iph; } pp = ip_vs_proto_get(iph->protocol); if (unlikely(!pp)) return NF_ACCEPT; /* reassemble IP fragments */ if (unlikely(iph->frag_off & __constant_htons(IP_MF|IP_OFFSET) && !pp->dont_defrag)) { skb = ip_vs_gather_frags(skb); if (!skb) return NF_STOLEN; iph = skb->nh.iph; *pskb = skb; } ihl = iph->ihl << 2; /* * Check if the packet belongs to an existing entry */ cp = pp->conn_out_get(skb, pp, iph, ihl, 0); if (unlikely(!cp)) { if (sysctl_ip_vs_nat_icmp_send && (pp->protocol == IPPROTO_TCP || pp->protocol == IPPROTO_UDP)) { __u16 ports[2]; if (skb_copy_bits(skb, ihl, ports, sizeof(ports)) < 0) return NF_ACCEPT; /* Not for me */ if (ip_vs_lookup_real_service(iph->protocol, iph->saddr, ports[0])) { /* * Notify the real server: there is no * existing entry if it is not RST * packet or not TCP packet. */ if (iph->protocol != IPPROTO_TCP || !is_tcp_reset(skb)) { icmp_send(skb,ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0); return NF_DROP; } } } IP_VS_DBG_PKT(12, pp, skb, 0, "packet continues traversal as normal"); return NF_ACCEPT; } IP_VS_DBG_PKT(11, pp, skb, 0, "Outgoing packet"); if (!ip_vs_make_skb_writable(pskb, ihl)) goto drop; /* mangle the packet */ if (pp->snat_handler && !pp->snat_handler(pskb, pp, cp)) goto drop; skb = *pskb; skb->nh.iph->saddr = cp->vaddr; ip_send_check(skb->nh.iph); IP_VS_DBG_PKT(10, pp, skb, 0, "After SNAT"); ip_vs_out_stats(cp, skb); ip_vs_set_state(cp, IP_VS_DIR_OUTPUT, skb, pp); ip_vs_conn_put(cp); skb->nfcache |= NFC_IPVS_PROPERTY; LeaveFunction(11); return NF_ACCEPT; drop: ip_vs_conn_put(cp); kfree_skb(*pskb); return NF_STOLEN; }