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