-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnat.go
More file actions
273 lines (255 loc) · 7.94 KB
/
Copy pathnat.go
File metadata and controls
273 lines (255 loc) · 7.94 KB
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
package main
import (
"fmt"
)
type natEntry struct {
globalIpAddr uint32
localIpAddr uint32
globalPort uint16 //icmpのidentifierとしても使う
localPort uint16
}
type natPacketHeader struct {
packet []byte
}
//UDPとTCPのNATテーブルセット
type natEntryList struct {
tcp []*natEntry
udp []*natEntry
icmp []*natEntry
}
type natProtocolType uint8
type natDirectionType uint8
const (
NAT_GLOBAL_PORT_MIN = 20000
NAT_GLOBAL_PORT_MAX = 59999
NAT_GLOBAL_PORT_SIZE = (NAT_GLOBAL_PORT_MAX - NAT_GLOBAL_PORT_MIN + 1)
NAT_ICMP_ID_SIZE = 0xffff
)
const (
outgoing natDirectionType = iota
incoming
)
const (
tcp natProtocolType = iota
udp
icmp
)
// ip_deviceが持つNATデバイス
type natDevice struct {
outsideIpAddr uint32
natEntry *natEntryList
}
func configureIPNat(inside string, outside uint32) {
for _, dev := range netDeviceList {
if inside == dev.name {
dev.ipdev.natdev = natDevice{
outsideIpAddr: outside,
natEntry: &natEntryList{
tcp: make([]*natEntry, NAT_GLOBAL_PORT_SIZE),
udp: make([]*natEntry, NAT_GLOBAL_PORT_SIZE),
icmp: make([]*natEntry, NAT_ICMP_ID_SIZE),
},
}
fmt.Printf("Set nat to %s, outside ip addr is %s\n", inside, printIPAddr(outside))
}
}
}
// 空いているポートを探してNATエントリを作る
func (entry *natEntryList) createNatEntry(protoType natProtocolType) *natEntry {
switch protoType {
case udp:
//udpの場合、空いているエントリを見つけてグローバルポートを設定しエントリを返す
for i, v := range entry.udp {
if v == nil {
entry.udp[i] = &natEntry{
globalPort: uint16(NAT_GLOBAL_PORT_MIN + i),
}
return entry.udp[i]
}
}
case tcp:
//tcpも同様、空いているエントリを見つけてグローバルポートを設定しエントリを返す
for i, v := range entry.tcp {
if v == nil {
entry.tcp[i] = &natEntry{
globalPort: uint16(NAT_GLOBAL_PORT_MIN + i),
}
return entry.tcp[i]
}
}
case icmp:
//icmpも同様であるが、空いているエントリを見つけてグローバルポートにidentifierを設定しエントリを返す
for i, v := range entry.icmp {
if v == nil {
entry.icmp[i] = &natEntry{
globalPort: uint16(i), //identifier
}
return entry.icmp[i]
}
}
}
return nil
}
func (entry *natEntryList) getNatEntryByGlobal(prototype natProtocolType, ipaddr uint32, port uint16) *natEntry {
switch prototype {
case udp:
for _, v := range entry.udp {
if v != nil && ipaddr == v.globalIpAddr && port == v.globalPort {
return v
}
}
case tcp:
for _, v := range entry.tcp {
if v != nil && ipaddr == v.globalIpAddr && port == v.globalPort {
return v
}
}
case icmp:
for _, v := range entry.icmp {
if v != nil && ipaddr == v.globalIpAddr && port == v.globalPort {
return v
}
}
}
return nil
}
func (entry *natEntryList) getNatEntryByLocal(prototype natProtocolType, ipaddr uint32, port uint16) *natEntry {
switch prototype {
case udp:
for _, v := range entry.udp {
if v != nil && ipaddr == v.localIpAddr && port == v.localPort {
return v
}
}
case tcp:
for _, v := range entry.tcp {
if v != nil && ipaddr == v.localIpAddr && port == v.localPort {
return v
}
}
case icmp:
for _, v := range entry.icmp {
if v != nil && ipaddr == v.localIpAddr && port == v.localPort {
return v
}
}
}
return nil
}
func natExec(ipHeader *ipHeader, natPacket natPacketHeader, natdevice natDevice, proto natProtocolType, direction natDirectionType) ([]byte, error) {
var udph udpHeader
var tcph tcpHeader
var icmpm icmpMessage
var srcPort, destPort uint16
var packet []byte
//プロトコルごとのパース (RFC 3022 Section 4.1)
switch proto {
case udp:
udph = udph.ParsePacket(natPacket.packet)
srcPort = udph.srcPort
destPort = udph.destPort
case tcp:
tcph = tcph.ParsePacket(natPacket.packet)
srcPort = tcph.srcPort
destPort = tcph.destPort
case icmp:
icmpm = icmpm.ParsePacket(natPacket.packet)
// RFC 5508: NAT対象は Echo Request / Echo Reply のみ
if icmpm.icmpHeader.icmpType != ICMP_TYPE_ECHO_REPLY && icmpm.icmpHeader.icmpType != ICMP_TYPE_ECHO_REQUEST {
return nil, fmt.Errorf("NAT icmpMEssage.icmpHeader.icmpType is not allowed type : %d\n", icmpm.icmpHeader.icmpType)
}
// RFC 5508: ICMP Identifier フィールドをポート番号の代替として使用
srcPort = icmpm.icmpEcho.identity
destPort = icmpm.icmpEcho.identity
}
var entry *natEntry
//テーブル検索とヘッダ書き換え (RFC 3022)
//NATテーブル検索 & アドレス・ポート書き換え
if direction == incoming {
//外から中 (DNAT 宛先IP/PORTをローカルに変換)
entry = natdevice.natEntry.getNatEntryByGlobal(proto, ipHeader.destAddr, destPort)
if entry == nil {
return nil, fmt.Errorf("No nat entry")
}
fmt.Printf("incoming nat from %s:%d to %s:%d\n", printIPAddr(entry.globalIpAddr), entry.globalPort, printIPAddr(entry.localIpAddr), entry.localPort)
fmt.Printf("incoming ip header src is %s, dest is %s\n", printIPAddr(ipHeader.srcAddr), printIPAddr(ipHeader.destAddr))
//IPヘッダの送信元アドレスを内側のアドレスにする
ipHeader.destAddr = entry.localIpAddr
switch proto {
case udp:
udph.destPort = entry.localPort
case tcp:
tcph.destPort = entry.localPort
case icmp:
icmpm.icmpEcho.identity = entry.localPort
}
} else {
//中から外 (SNAT 宛先IP/PORTをグローバルに変換)
entry = natdevice.natEntry.getNatEntryByLocal(proto, ipHeader.srcAddr, srcPort)
if entry == nil {
entry = natdevice.natEntry.createNatEntry(proto)
if entry == nil {
return nil, fmt.Errorf("NAT table is full")
}
entry.globalIpAddr = natdevice.outsideIpAddr
entry.localIpAddr = ipHeader.srcAddr
entry.localPort = srcPort
}
//IPヘッダの送信元アドレスを外側のアドレスにする
ipHeader.srcAddr = entry.globalIpAddr
switch proto {
case udp:
udph.srcPort = entry.globalPort
case tcp:
tcph.srcPort = entry.globalPort
case icmp:
icmpm.icmpEcho.identity = entry.globalPort
}
}
//L4パケットのシリアライズ & チェックサム再計算 (RFC 793, RFC 768, RFC 5508)
// RFC 793: チェックサムフィールドを 0x0000 に初期化して擬似ヘッダ付きで計算
// RFC 768: 元が 0x0000(チェックサム無効)の場合は再計算せず 0x0000 を維持
// RFC 768: 計算結果が 0x0000 になった場合は 0xFFFF を格納する(必須要件)
// RFC 792 / RFC 5508: ICMPは擬似ヘッダを含めない(L3情報は検査対象外)
var l4Payload []byte
packet = natPacket.packet
switch proto {
case udp:
if len(natPacket.packet) < 8 {
return nil, fmt.Errorf("udp packet too short")
}
l4Payload = natPacket.packet[8:]
//RFC 768 元が0なら送信側が計算していないので0のまま
if udph.checkSum != 0 {
//ゼロクリア
udph.checkSum = 0
packet = append(udph.toPacket(), l4Payload...)
//疑似ヘッダとパケット全体で計算する
//疑似ヘッダは送信元・宛先アドレスとtcp・udp 6か17 L4パケット全体
sum := pseudoHeaderSum(ipHeader.srcAddr, ipHeader.destAddr, IP_PROTOCOL_NUM_UDP, len(packet))
sum += sumByteArr(packet)
cksum := foldChecksum(sum)
if cksum == 0 {
cksum = 0xffff
}
udph.checkSum = cksum
}
packet = append(udph.toPacket(), l4Payload...)
case tcp:
tcph.checkSum = 0
packet = tcph.toPacket()
sum := pseudoHeaderSum(ipHeader.srcAddr, ipHeader.destAddr, IP_PROTOCOL_NUM_TCP, len(packet))
sum += sumByteArr(packet)
tcph.checkSum = foldChecksum(sum)
packet = tcph.toPacket()
case icmp:
packet = natPacket.packet
//identityは4-5バイト目
copy(packet[4:6], uint16ToByte(icmpm.icmpEcho.identity))
//チェックサムは2-3バイト目、ゼロクリアして計算
packet[2], packet[3] = 0, 0
//icmpなら疑似ヘッダは足さない
copy(packet[2:4], uint16ToByte(foldChecksum(sumByteArr(packet))))
}
return packet, nil
}