1// Copyright 2016 Cong Ding
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package stun
16
17import (
18	"errors"
19	"net"
20)
21
22// Follow RFC 3489 and RFC 5389.
23// Figure 2: Flow for type discovery process (from RFC 3489).
24//                        +--------+
25//                        |  Test  |
26//                        |   I    |
27//                        +--------+
28//                             |
29//                             |
30//                             V
31//                            /\              /\
32//                         N /  \ Y          /  \ Y             +--------+
33//          UDP     <-------/Resp\--------->/ IP \------------->|  Test  |
34//          Blocked         \ ?  /          \Same/              |   II   |
35//                           \  /            \? /               +--------+
36//                            \/              \/                    |
37//                                             | N                  |
38//                                             |                    V
39//                                             V                    /\
40//                                         +--------+  Sym.      N /  \
41//                                         |  Test  |  UDP    <---/Resp\
42//                                         |   II   |  Firewall   \ ?  /
43//                                         +--------+              \  /
44//                                             |                    \/
45//                                             V                     |Y
46//                  /\                         /\                    |
47//   Symmetric  N  /  \       +--------+   N  /  \                   V
48//      NAT  <--- / IP \<-----|  Test  |<--- /Resp\               Open
49//                \Same/      |   I    |     \ ?  /               Internet
50//                 \? /       +--------+      \  /
51//                  \/                         \/
52//                  |Y                          |Y
53//                  |                           |
54//                  |                           V
55//                  |                           Full
56//                  |                           Cone
57//                  V              /\
58//              +--------+        /  \ Y
59//              |  Test  |------>/Resp\---->Restricted
60//              |   III  |       \ ?  /
61//              +--------+        \  /
62//                                 \/
63//                                  |N
64//                                  |       Port
65//                                  +------>Restricted
66func (c *Client) discover(conn net.PacketConn, addr *net.UDPAddr) (NATType, *Host, error) {
67	// Perform test1 to check if it is under NAT.
68	c.logger.Debugln("Do Test1")
69	c.logger.Debugln("Send To:", addr)
70	resp, err := c.test1(conn, addr)
71	if err != nil {
72		return NATError, nil, err
73	}
74	c.logger.Debugln("Received:", resp)
75	if resp == nil {
76		return NATBlocked, nil, nil
77	}
78	// identical used to check if it is open Internet or not.
79	identical := resp.identical
80	// changedAddr is used to perform second time test1 and test3.
81	changedAddr := resp.changedAddr
82	// mappedAddr is used as the return value, its IP is used for tests
83	mappedAddr := resp.mappedAddr
84	// Make sure IP and port are not changed.
85	if resp.serverAddr.IP() != addr.IP.String() ||
86		resp.serverAddr.Port() != uint16(addr.Port) {
87		return NATError, mappedAddr, errors.New("Server error: response IP/port")
88	}
89	// if changedAddr is not available, use otherAddr as changedAddr,
90	// which is updated in RFC 5780
91	if changedAddr == nil {
92		changedAddr = resp.otherAddr
93	}
94	// changedAddr shall not be nil
95	if changedAddr == nil {
96		return NATError, mappedAddr, errors.New("Server error: no changed address.")
97	}
98	// Perform test2 to see if the client can receive packet sent from
99	// another IP and port.
100	c.logger.Debugln("Do Test2")
101	c.logger.Debugln("Send To:", addr)
102	resp, err = c.test2(conn, addr)
103	if err != nil {
104		return NATError, mappedAddr, err
105	}
106	c.logger.Debugln("Received:", resp)
107	// Make sure IP and port are changed.
108	if resp != nil &&
109		(resp.serverAddr.IP() == addr.IP.String() ||
110			resp.serverAddr.Port() == uint16(addr.Port)) {
111		return NATError, mappedAddr, errors.New("Server error: response IP/port")
112	}
113	if identical {
114		if resp == nil {
115			return SymmetricUDPFirewall, mappedAddr, nil
116		}
117		return NATNone, mappedAddr, nil
118	}
119	if resp != nil {
120		return NATFull, mappedAddr, nil
121	}
122	// Perform test1 to another IP and port to see if the NAT use the same
123	// external IP.
124	c.logger.Debugln("Do Test1")
125	c.logger.Debugln("Send To:", changedAddr)
126	caddr, err := net.ResolveUDPAddr("udp", changedAddr.String())
127	if err != nil {
128		c.logger.Debugf("ResolveUDPAddr error: %v", err)
129	}
130	resp, err = c.test1(conn, caddr)
131	if err != nil {
132		return NATError, mappedAddr, err
133	}
134	c.logger.Debugln("Received:", resp)
135	if resp == nil {
136		// It should be NAT_BLOCKED, but will be detected in the first
137		// step. So this will never happen.
138		return NATUnknown, mappedAddr, nil
139	}
140	// Make sure IP/port is not changed.
141	if resp.serverAddr.IP() != caddr.IP.String() ||
142		resp.serverAddr.Port() != uint16(caddr.Port) {
143		return NATError, mappedAddr, errors.New("Server error: response IP/port")
144	}
145	if mappedAddr.IP() == resp.mappedAddr.IP() && mappedAddr.Port() == resp.mappedAddr.Port() {
146		// Perform test3 to see if the client can receive packet sent
147		// from another port.
148		c.logger.Debugln("Do Test3")
149		c.logger.Debugln("Send To:", caddr)
150		resp, err = c.test3(conn, caddr)
151		if err != nil {
152			return NATError, mappedAddr, err
153		}
154		c.logger.Debugln("Received:", resp)
155		if resp == nil {
156			return NATPortRestricted, mappedAddr, nil
157		}
158		// Make sure IP is not changed, and port is changed.
159		if resp.serverAddr.IP() != caddr.IP.String() ||
160			resp.serverAddr.Port() == uint16(caddr.Port) {
161			return NATError, mappedAddr, errors.New("Server error: response IP/port")
162		}
163		return NATRestricted, mappedAddr, nil
164	}
165	return NATSymmetric, mappedAddr, nil
166}
167