summaryrefslogtreecommitdiff
path: root/translate.c
blob: 4092bccfb2ba0de1942bf8fb48f46646273c82c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
/*
 * Copyright 2011 Daniel Drown
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * translate.c - CLAT functions / partial implementation of rfc6145
 */
#include <string.h>

#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <linux/icmp.h>

#include "translate.h"
#include "checksum.h"
#include "clatd.h"
#include "config.h"
#include "logging.h"
#include "debug.h"

/* function: packet_checksum
 * calculates the checksum over all the packet components starting from pos
 * checksum - checksum of packet components before pos
 * packet   - packet to calculate the checksum of
 * pos      - position to start counting from
 * returns  - the completed 16-bit checksum, ready to write into a checksum header field
 */
uint16_t packet_checksum(uint32_t checksum, clat_packet packet, int pos) {
  int i;
  for (i = pos; i < CLAT_POS_MAX; i++) {
    if (packet[i].iov_len > 0) {
      checksum = ip_checksum_add(checksum, packet[i].iov_base, packet[i].iov_len);
    }
  }
  return ip_checksum_finish(checksum);
}

/* function: packet_length
 * returns the total length of all the packet components after pos
 * packet - packet to calculate the length of
 * pos    - position to start counting from
 * returns: the total length of the packet components after pos
 */
uint16_t packet_length(clat_packet packet, int pos) {
  size_t len = 0;
  int i;
  for (i = pos + 1; i < CLAT_POS_MAX; i++) {
    len += packet[i].iov_len;
  }
  return len;
}

/* function: is_in_plat_subnet
 * returns true iff the given IPv6 address is in the plat subnet.
 * addr - IPv6 address
 */
int is_in_plat_subnet(const struct in6_addr *addr6) {
  // Assumes a /96 plat subnet.
  return (addr6 != NULL) && (memcmp(addr6, &Global_Clatd_Config.plat_subnet, 12) == 0);
}

/* function: ipv6_addr_to_ipv4_addr
 * return the corresponding ipv4 address for the given ipv6 address
 * addr6 - ipv6 address
 * returns: the IPv4 address
 */
uint32_t ipv6_addr_to_ipv4_addr(const struct in6_addr *addr6) {

  if (is_in_plat_subnet(addr6)) {
    // Assumes a /96 plat subnet.
    return addr6->s6_addr32[3];
  } else {
    // Currently this can only be our own address; other packets are dropped by ipv6_packet.
    return Global_Clatd_Config.ipv4_local_subnet.s_addr;
  }
}

/* function: ipv4_addr_to_ipv6_addr
 * return the corresponding ipv6 address for the given ipv4 address
 * addr4 - ipv4 address
 */
struct in6_addr ipv4_addr_to_ipv6_addr(uint32_t addr4) {
  struct in6_addr addr6;
  // Both addresses are in network byte order (addr4 comes from a network packet, and the config
  // file entry is read using inet_ntop).
  if (addr4 == Global_Clatd_Config.ipv4_local_subnet.s_addr) {
    return Global_Clatd_Config.ipv6_local_subnet;
  } else {
    // Assumes a /96 plat subnet.
    addr6 = Global_Clatd_Config.plat_subnet;
    addr6.s6_addr32[3] = addr4;
    return addr6;
  }
}

/* function: fill_tun_header
 * fill in the header for the tun fd
 * tun_header - tunnel header, already allocated
 * proto      - ethernet protocol id: ETH_P_IP(ipv4) or ETH_P_IPV6(ipv6)
 */
void fill_tun_header(struct tun_pi *tun_header, uint16_t proto) {
  tun_header->flags = 0;
  tun_header->proto = htons(proto);
}

/* function: fill_ip_header
 * generate an ipv4 header from an ipv6 header
 * ip_targ     - (ipv4) target packet header, source: original ipv4 addr, dest: local subnet addr
 * payload_len - length of other data inside packet
 * protocol    - protocol number (tcp, udp, etc)
 * old_header  - (ipv6) source packet header, source: nat64 prefix, dest: local subnet prefix
 */
void fill_ip_header(struct iphdr *ip, uint16_t payload_len, uint8_t protocol,
                    const struct ip6_hdr *old_header) {
  memset(ip, 0, sizeof(struct iphdr));

  ip->ihl = 5;
  ip->version = 4;
  ip->tos = 0;
  ip->tot_len = htons(sizeof(struct iphdr) + payload_len);
  ip->id = 0;
  ip->frag_off = htons(IP_DF);
  ip->ttl = old_header->ip6_hlim;
  ip->protocol = protocol;
  ip->check = 0;

  ip->saddr = ipv6_addr_to_ipv4_addr(&old_header->ip6_src);
  ip->daddr = ipv6_addr_to_ipv4_addr(&old_header->ip6_dst);
}

/* function: fill_ip6_header
 * generate an ipv6 header from an ipv4 header
 * ip6         - (ipv6) target packet header, source: local subnet prefix, dest: nat64 prefix
 * payload_len - length of other data inside packet
 * protocol    - protocol number (tcp, udp, etc)
 * old_header  - (ipv4) source packet header, source: local subnet addr, dest: internet's ipv4 addr
 */
void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol,
                     const struct iphdr *old_header) {
  memset(ip6, 0, sizeof(struct ip6_hdr));

  ip6->ip6_vfc = 6 << 4;
  ip6->ip6_plen = htons(payload_len);
  ip6->ip6_nxt = protocol;
  ip6->ip6_hlim = old_header->ttl;

  ip6->ip6_src = ipv4_addr_to_ipv6_addr(old_header->saddr);
  ip6->ip6_dst = ipv4_addr_to_ipv6_addr(old_header->daddr);
}

/* function: icmp_to_icmp6
 * translate ipv4 icmp to ipv6 icmp (only currently supports echo/echo reply)
 * out          - output packet
 * icmp         - source packet icmp header
 * checksum     - pseudo-header checksum
 * payload      - icmp payload
 * payload_size - size of payload
 * returns: the highest position in the output clat_packet that's filled in
 */
int icmp_to_icmp6(clat_packet out, int pos, const struct icmphdr *icmp, uint32_t checksum,
                  const char *payload, size_t payload_size) {
  struct icmp6_hdr *icmp6_targ = out[pos].iov_base;
  uint32_t checksum_temp;

  if((icmp->type != ICMP_ECHO) && (icmp->type != ICMP_ECHOREPLY)) {
    logmsg_dbg(ANDROID_LOG_WARN,"icmp_to_icmp6/unhandled icmp type: 0x%x", icmp->type);
    return 0;
  }

  memset(icmp6_targ, 0, sizeof(struct icmp6_hdr));
  icmp6_targ->icmp6_type = (icmp->type == ICMP_ECHO) ? ICMP6_ECHO_REQUEST : ICMP6_ECHO_REPLY;
  icmp6_targ->icmp6_code = 0;
  icmp6_targ->icmp6_id = icmp->un.echo.id;
  icmp6_targ->icmp6_seq = icmp->un.echo.sequence;

  out[pos].iov_len = sizeof(struct icmp6_hdr);
  out[CLAT_POS_PAYLOAD].iov_base = (char *) payload;
  out[CLAT_POS_PAYLOAD].iov_len = payload_size;

  icmp6_targ->icmp6_cksum = 0;  // Checksum field must be 0 when calculating checksum.
  icmp6_targ->icmp6_cksum = packet_checksum(checksum, out, pos);

  return CLAT_POS_PAYLOAD + 1;
}

/* function: icmp6_to_icmp
 * translate ipv6 icmp to ipv4 icmp (only currently supports echo/echo reply)
 * out          - output packet
 * icmp6        - source packet icmp6 header
 * checksum     - pseudo-header checksum (unused)
 * payload      - icmp6 payload
 * payload_size - size of payload
 * returns: the highest position in the output clat_packet that's filled in
 */
int icmp6_to_icmp(clat_packet out, int pos, const struct icmp6_hdr *icmp6, uint32_t checksum,
                  const char *payload, size_t payload_size) {
  struct icmphdr *icmp_targ = out[pos].iov_base;

  if((icmp6->icmp6_type != ICMP6_ECHO_REQUEST) && (icmp6->icmp6_type != ICMP6_ECHO_REPLY)) {
    logmsg_dbg(ANDROID_LOG_WARN,"icmp6_to_icmp/unhandled icmp6 type: 0x%x",icmp6->icmp6_type);
    return 0;
  }

  memset(icmp_targ, 0, sizeof(struct icmphdr));

  icmp_targ->type = (icmp6->icmp6_type == ICMP6_ECHO_REQUEST) ? ICMP_ECHO : ICMP_ECHOREPLY;
  icmp_targ->code = 0x0;
  icmp_targ->un.echo.id = icmp6->icmp6_id;
  icmp_targ->un.echo.sequence = icmp6->icmp6_seq;

  out[pos].iov_len = sizeof(struct icmphdr);
  out[CLAT_POS_PAYLOAD].iov_base = (char *) payload;
  out[CLAT_POS_PAYLOAD].iov_len = payload_size;

  icmp_targ->checksum = 0;  // Checksum field must be 0 when calculating checksum.
  icmp_targ->checksum = packet_checksum(0, out, pos);

  return CLAT_POS_PAYLOAD + 1;
}

/* function: udp_packet
 * takes a udp packet and sets it up for translation
 * out      - output packet
 * udp      - pointer to udp header in packet
 * checksum - pseudo-header checksum
 * len      - size of ip payload
 */
int udp_packet(clat_packet out, int pos, const struct udphdr *udp, uint32_t checksum, size_t len) {
  const char *payload;
  size_t payload_size;

  if(len < sizeof(struct udphdr)) {
    logmsg_dbg(ANDROID_LOG_ERROR,"udp_packet/(too small)");
    return 0;
  }

  payload = (const char *) (udp + 1);
  payload_size = len - sizeof(struct udphdr);

  return udp_translate(out, pos, udp, checksum, payload, payload_size);
}

/* function: tcp_packet
 * takes a tcp packet and sets it up for translation
 * out      - output packet
 * tcp      - pointer to tcp header in packet
 * checksum - pseudo-header checksum
 * len      - size of ip payload
 * returns: the highest position in the output clat_packet that's filled in
 */
int tcp_packet(clat_packet out, int pos, const struct tcphdr *tcp, uint32_t checksum, size_t len) {
  const char *payload;
  size_t payload_size, header_size;

  if(len < sizeof(struct tcphdr)) {
    logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/(too small)");
    return 0;
  }

  if(tcp->doff < 5) {
    logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/tcp header length set to less than 5: %x", tcp->doff);
    return 0;
  }

  if((size_t) tcp->doff*4 > len) {
    logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/tcp header length set too large: %x", tcp->doff);
    return 0;
  }

  header_size = tcp->doff * 4;
  payload = ((const char *) tcp) + header_size;
  payload_size = len - header_size;

  return tcp_translate(out, pos, tcp, header_size, checksum, payload, payload_size);
}

/* function: udp_translate
 * common between ipv4/ipv6 - setup checksum and send udp packet
 * out          - output packet
 * udp          - udp header
 * checksum     - pseudo-header checksum
 * payload      - tcp payload
 * payload_size - size of payload
 * returns: the highest position in the output clat_packet that's filled in
 */
int udp_translate(clat_packet out, int pos, const struct udphdr *udp, uint32_t checksum,
                  const char *payload, size_t payload_size) {
  struct udphdr *udp_targ = out[pos].iov_base;

  memcpy(udp_targ, udp, sizeof(struct udphdr));

  out[pos].iov_len = sizeof(struct udphdr);
  out[CLAT_POS_PAYLOAD].iov_base = (char *) payload;
  out[CLAT_POS_PAYLOAD].iov_len = payload_size;

  udp_targ->check = 0;  // Checksum field must be 0 when calculating checksum.
  udp_targ->check = packet_checksum(checksum, out, pos);

  return CLAT_POS_PAYLOAD + 1;
}

/* function: tcp_translate
 * common between ipv4/ipv6 - setup checksum and send tcp packet
 * out          - output packet
 * tcp          - tcp header
 * header_size  - size of tcp header including options
 * checksum     - partial checksum covering ipv4/ipv6 header
 * payload      - tcp payload
 * payload_size - size of payload
 * returns: the highest position in the output clat_packet that's filled in
 *
 * TODO: mss rewrite
 * TODO: hosts without pmtu discovery - non DF packets will rely on fragmentation (unimplemented)
 */
int tcp_translate(clat_packet out, int pos, const struct tcphdr *tcp, size_t header_size,
                  uint32_t checksum, const char *payload, size_t payload_size) {
  struct tcphdr *tcp_targ = out[pos].iov_base;
  out[pos].iov_len = header_size;

  if (header_size > MAX_TCP_HDR) {
    // A TCP header cannot be more than MAX_TCP_HDR bytes long because it's a 4-bit field that
    // counts in 4-byte words. So this can never happen unless there is a bug in the caller.
    logmsg(ANDROID_LOG_ERROR, "tcp_translate: header too long %d > %d, truncating",
           header_size, MAX_TCP_HDR);
    header_size = MAX_TCP_HDR;
  }

  memcpy(tcp_targ, tcp, header_size);

  out[CLAT_POS_PAYLOAD].iov_base = (char *)payload;
  out[CLAT_POS_PAYLOAD].iov_len = payload_size;

  tcp_targ->check = 0;  // Checksum field must be 0 when calculating checksum.
  tcp_targ->check = packet_checksum(checksum, out, pos);

  return CLAT_POS_PAYLOAD + 1;
}