1/* nf_nat_helper.c - generic support functions for NAT helpers 2 * 3 * (C) 2000-2002 Harald Welte <laforge@netfilter.org> 4 * (C) 2003-2006 Netfilter Core Team <coreteam@netfilter.org> 5 * (C) 2007-2012 Patrick McHardy <kaber@trash.net> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 */ 11#include <linux/module.h> 12#include <linux/gfp.h> 13#include <linux/types.h> 14#include <linux/skbuff.h> 15#include <linux/tcp.h> 16#include <linux/udp.h> 17#include <net/tcp.h> 18 19#include <net/netfilter/nf_conntrack.h> 20#include <net/netfilter/nf_conntrack_helper.h> 21#include <net/netfilter/nf_conntrack_ecache.h> 22#include <net/netfilter/nf_conntrack_expect.h> 23#include <net/netfilter/nf_conntrack_seqadj.h> 24#include <net/netfilter/nf_nat.h> 25#include <net/netfilter/nf_nat_l3proto.h> 26#include <net/netfilter/nf_nat_l4proto.h> 27#include <net/netfilter/nf_nat_core.h> 28#include <net/netfilter/nf_nat_helper.h> 29 30/* Frobs data inside this packet, which is linear. */ 31static void mangle_contents(struct sk_buff *skb, 32 unsigned int dataoff, 33 unsigned int match_offset, 34 unsigned int match_len, 35 const char *rep_buffer, 36 unsigned int rep_len) 37{ 38 unsigned char *data; 39 40 BUG_ON(skb_is_nonlinear(skb)); 41 data = skb_network_header(skb) + dataoff; 42 43 /* move post-replacement */ 44 memmove(data + match_offset + rep_len, 45 data + match_offset + match_len, 46 skb_tail_pointer(skb) - (skb_network_header(skb) + dataoff + 47 match_offset + match_len)); 48 49 /* insert data from buffer */ 50 memcpy(data + match_offset, rep_buffer, rep_len); 51 52 /* update skb info */ 53 if (rep_len > match_len) { 54 pr_debug("nf_nat_mangle_packet: Extending packet by " 55 "%u from %u bytes\n", rep_len - match_len, skb->len); 56 skb_put(skb, rep_len - match_len); 57 } else { 58 pr_debug("nf_nat_mangle_packet: Shrinking packet from " 59 "%u from %u bytes\n", match_len - rep_len, skb->len); 60 __skb_trim(skb, skb->len + rep_len - match_len); 61 } 62 63 if (nf_ct_l3num((struct nf_conn *)skb->nfct) == NFPROTO_IPV4) { 64 /* fix IP hdr checksum information */ 65 ip_hdr(skb)->tot_len = htons(skb->len); 66 ip_send_check(ip_hdr(skb)); 67 } else 68 ipv6_hdr(skb)->payload_len = 69 htons(skb->len - sizeof(struct ipv6hdr)); 70} 71 72/* Unusual, but possible case. */ 73static int enlarge_skb(struct sk_buff *skb, unsigned int extra) 74{ 75 if (skb->len + extra > 65535) 76 return 0; 77 78 if (pskb_expand_head(skb, 0, extra - skb_tailroom(skb), GFP_ATOMIC)) 79 return 0; 80 81 return 1; 82} 83 84/* Generic function for mangling variable-length address changes inside 85 * NATed TCP connections (like the PORT XXX,XXX,XXX,XXX,XXX,XXX 86 * command in FTP). 87 * 88 * Takes care about all the nasty sequence number changes, checksumming, 89 * skb enlargement, ... 90 * 91 * */ 92int __nf_nat_mangle_tcp_packet(struct sk_buff *skb, 93 struct nf_conn *ct, 94 enum ip_conntrack_info ctinfo, 95 unsigned int protoff, 96 unsigned int match_offset, 97 unsigned int match_len, 98 const char *rep_buffer, 99 unsigned int rep_len, bool adjust) 100{ 101 const struct nf_nat_l3proto *l3proto; 102 struct tcphdr *tcph; 103 int oldlen, datalen; 104 105 if (!skb_make_writable(skb, skb->len)) 106 return 0; 107 108 if (rep_len > match_len && 109 rep_len - match_len > skb_tailroom(skb) && 110 !enlarge_skb(skb, rep_len - match_len)) 111 return 0; 112 113 SKB_LINEAR_ASSERT(skb); 114 115 tcph = (void *)skb->data + protoff; 116 117 oldlen = skb->len - protoff; 118 mangle_contents(skb, protoff + tcph->doff*4, 119 match_offset, match_len, rep_buffer, rep_len); 120 121 datalen = skb->len - protoff; 122 123 l3proto = __nf_nat_l3proto_find(nf_ct_l3num(ct)); 124 l3proto->csum_recalc(skb, IPPROTO_TCP, tcph, &tcph->check, 125 datalen, oldlen); 126 127 if (adjust && rep_len != match_len) 128 nf_ct_seqadj_set(ct, ctinfo, tcph->seq, 129 (int)rep_len - (int)match_len); 130 131 return 1; 132} 133EXPORT_SYMBOL(__nf_nat_mangle_tcp_packet); 134 135/* Generic function for mangling variable-length address changes inside 136 * NATed UDP connections (like the CONNECT DATA XXXXX MESG XXXXX INDEX XXXXX 137 * command in the Amanda protocol) 138 * 139 * Takes care about all the nasty sequence number changes, checksumming, 140 * skb enlargement, ... 141 * 142 * XXX - This function could be merged with nf_nat_mangle_tcp_packet which 143 * should be fairly easy to do. 144 */ 145int 146nf_nat_mangle_udp_packet(struct sk_buff *skb, 147 struct nf_conn *ct, 148 enum ip_conntrack_info ctinfo, 149 unsigned int protoff, 150 unsigned int match_offset, 151 unsigned int match_len, 152 const char *rep_buffer, 153 unsigned int rep_len) 154{ 155 const struct nf_nat_l3proto *l3proto; 156 struct udphdr *udph; 157 int datalen, oldlen; 158 159 if (!skb_make_writable(skb, skb->len)) 160 return 0; 161 162 if (rep_len > match_len && 163 rep_len - match_len > skb_tailroom(skb) && 164 !enlarge_skb(skb, rep_len - match_len)) 165 return 0; 166 167 udph = (void *)skb->data + protoff; 168 169 oldlen = skb->len - protoff; 170 mangle_contents(skb, protoff + sizeof(*udph), 171 match_offset, match_len, rep_buffer, rep_len); 172 173 /* update the length of the UDP packet */ 174 datalen = skb->len - protoff; 175 udph->len = htons(datalen); 176 177 /* fix udp checksum if udp checksum was previously calculated */ 178 if (!udph->check && skb->ip_summed != CHECKSUM_PARTIAL) 179 return 1; 180 181 l3proto = __nf_nat_l3proto_find(nf_ct_l3num(ct)); 182 l3proto->csum_recalc(skb, IPPROTO_UDP, udph, &udph->check, 183 datalen, oldlen); 184 185 return 1; 186} 187EXPORT_SYMBOL(nf_nat_mangle_udp_packet); 188 189/* Setup NAT on this expected conntrack so it follows master. */ 190/* If we fail to get a free NAT slot, we'll get dropped on confirm */ 191void nf_nat_follow_master(struct nf_conn *ct, 192 struct nf_conntrack_expect *exp) 193{ 194 struct nf_nat_range range; 195 196 /* This must be a fresh one. */ 197 BUG_ON(ct->status & IPS_NAT_DONE_MASK); 198 199 /* Change src to where master sends to */ 200 range.flags = NF_NAT_RANGE_MAP_IPS; 201 range.min_addr = range.max_addr 202 = ct->master->tuplehash[!exp->dir].tuple.dst.u3; 203 nf_nat_setup_info(ct, &range, NF_NAT_MANIP_SRC); 204 205 /* For DST manip, map port here to where it's expected. */ 206 range.flags = (NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED); 207 range.min_proto = range.max_proto = exp->saved_proto; 208 range.min_addr = range.max_addr 209 = ct->master->tuplehash[!exp->dir].tuple.src.u3; 210 nf_nat_setup_info(ct, &range, NF_NAT_MANIP_DST); 211} 212EXPORT_SYMBOL(nf_nat_follow_master); 213