import of upstream 2.4.34.4 from kernel.org
[linux-2.4.git] / net / ipv4 / ip_nat_dumb.c
1 /*
2  * INET         An implementation of the TCP/IP protocol suite for the LINUX
3  *              operating system.  INET is implemented using the  BSD Socket
4  *              interface as the means of communication with the user level.
5  *
6  *              Dumb Network Address Translation.
7  *
8  * Version:     $Id: ip_nat_dumb.c,v 1.11 2000/12/13 18:31:48 davem Exp $
9  *
10  * Authors:     Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
11  *
12  *              This program is free software; you can redistribute it and/or
13  *              modify it under the terms of the GNU General Public License
14  *              as published by the Free Software Foundation; either version
15  *              2 of the License, or (at your option) any later version.
16  *
17  * Fixes:
18  *              Rani Assaf      :       A zero checksum is a special case
19  *                                      only in UDP
20  *              Rani Assaf      :       Added ICMP messages rewriting
21  *              Rani Assaf      :       Repaired wrong changes, made by ANK.
22  *
23  *
24  * NOTE:        It is just working model of real NAT.
25  */
26
27 #include <linux/config.h>
28 #include <linux/types.h>
29 #include <linux/mm.h>
30 #include <linux/sched.h>
31 #include <linux/skbuff.h>
32 #include <linux/ip.h>
33 #include <linux/icmp.h>
34 #include <linux/netdevice.h>
35 #include <net/sock.h>
36 #include <net/ip.h>
37 #include <net/icmp.h>
38 #include <linux/tcp.h>
39 #include <linux/udp.h>
40 #include <net/checksum.h>
41 #include <linux/route.h>
42 #include <net/route.h>
43 #include <net/ip_fib.h>
44
45
46 int
47 ip_do_nat(struct sk_buff *skb)
48 {
49         struct rtable *rt = (struct rtable*)skb->dst;
50         struct iphdr *iph = skb->nh.iph;
51         u32 odaddr = iph->daddr;
52         u32 osaddr = iph->saddr;
53         u16     check;
54
55         IPCB(skb)->flags |= IPSKB_TRANSLATED;
56
57         /* Rewrite IP header */
58         iph->daddr = rt->rt_dst_map;
59         iph->saddr = rt->rt_src_map;
60         iph->check = 0;
61         iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
62
63         /* If it is the first fragment, rewrite protocol headers */
64
65         if (!(iph->frag_off & htons(IP_OFFSET))) {
66                 u16     *cksum;
67
68                 switch(iph->protocol) {
69                 case IPPROTO_TCP:
70                         cksum  = (u16*)&((struct tcphdr*)(((char*)iph) + (iph->ihl<<2)))->check;
71                         if ((u8*)(cksum+1) > skb->tail)
72                                 goto truncated;
73                         check = *cksum;
74                         if (skb->ip_summed != CHECKSUM_HW)
75                                 check = ~check;
76                         check = csum_tcpudp_magic(iph->saddr, iph->daddr, 0, 0, check);
77                         check = csum_tcpudp_magic(~osaddr, ~odaddr, 0, 0, ~check);
78                         if (skb->ip_summed == CHECKSUM_HW)
79                                 check = ~check;
80                         *cksum = check;
81                         break;
82                 case IPPROTO_UDP:
83                         cksum  = (u16*)&((struct udphdr*)(((char*)iph) + (iph->ihl<<2)))->check;
84                         if ((u8*)(cksum+1) > skb->tail)
85                                 goto truncated;
86                         if ((check = *cksum) != 0) {
87                                 check = csum_tcpudp_magic(iph->saddr, iph->daddr, 0, 0, ~check);
88                                 check = csum_tcpudp_magic(~osaddr, ~odaddr, 0, 0, ~check);
89                                 *cksum = check ? : 0xFFFF;
90                         }
91                         break;
92                 case IPPROTO_ICMP:
93                 {
94                         struct icmphdr *icmph = (struct icmphdr*)((char*)iph + (iph->ihl<<2));
95                         struct   iphdr *ciph;
96                         u32 idaddr, isaddr;
97                         int updated;
98
99                         if ((icmph->type != ICMP_DEST_UNREACH) &&
100                             (icmph->type != ICMP_TIME_EXCEEDED) &&
101                             (icmph->type != ICMP_PARAMETERPROB))
102                                 break;
103
104                         ciph = (struct iphdr *) (icmph + 1);
105
106                         if ((u8*)(ciph+1) > skb->tail)
107                                 goto truncated;
108
109                         isaddr = ciph->saddr;
110                         idaddr = ciph->daddr;
111                         updated = 0;
112
113                         if (rt->rt_flags&RTCF_DNAT && ciph->saddr == odaddr) {
114                                 ciph->saddr = iph->daddr;
115                                 updated = 1;
116                         }
117                         if (rt->rt_flags&RTCF_SNAT) {
118                                 if (ciph->daddr != osaddr) {
119                                         struct   fib_result res;
120                                         struct   rt_key key;
121                                         unsigned flags = 0;
122
123                                         key.src = ciph->daddr;
124                                         key.dst = ciph->saddr;
125                                         key.iif = skb->dev->ifindex;
126                                         key.oif = 0;
127 #ifdef CONFIG_IP_ROUTE_TOS
128                                         key.tos = RT_TOS(ciph->tos);
129 #endif
130 #ifdef CONFIG_IP_ROUTE_FWMARK
131                                         key.fwmark = 0;
132 #endif
133                                         /* Use fib_lookup() until we get our own
134                                          * hash table of NATed hosts -- Rani
135                                          */
136                                         if (fib_lookup(&key, &res) == 0) {
137                                                 if (res.r) {
138                                                         ciph->daddr = fib_rules_policy(ciph->daddr, &res, &flags);
139                                                         if (ciph->daddr != idaddr)
140                                                                 updated = 1;
141                                                 }
142                                                 fib_res_put(&res);
143                                         }
144                                 } else {
145                                         ciph->daddr = iph->saddr;
146                                         updated = 1;
147                                 }
148                         }
149                         if (updated) {
150                                 cksum  = &icmph->checksum;
151                                 /* Using tcpudp primitive. Why not? */
152                                 check  = csum_tcpudp_magic(ciph->saddr, ciph->daddr, 0, 0, ~(*cksum));
153                                 *cksum = csum_tcpudp_magic(~isaddr, ~idaddr, 0, 0, ~check);
154                         }
155                         break;
156                 }
157                 default:
158                         break;
159                 }
160         }
161         return NET_RX_SUCCESS;
162
163 truncated:
164         /* should be return NET_RX_BAD; */
165         return -EINVAL;
166 }