1
2
3
4
5 package icmp_test
6
7 import (
8 "errors"
9 "flag"
10 "fmt"
11 "net"
12 "os"
13 "runtime"
14 "sync"
15 "testing"
16 "time"
17
18 "golang.org/x/net/icmp"
19 "golang.org/x/net/internal/iana"
20 "golang.org/x/net/ipv4"
21 "golang.org/x/net/ipv6"
22 "golang.org/x/net/nettest"
23 )
24
25 var testDiag = flag.Bool("diag", false, "whether to test ICMP message exchange with external network")
26
27 type diagTest struct {
28 network, address string
29 protocol int
30 m icmp.Message
31 }
32
33 func TestDiag(t *testing.T) {
34 if !*testDiag {
35 t.Skip("avoid external network")
36 }
37
38 t.Run("Ping/NonPrivileged", func(t *testing.T) {
39 if m, ok := supportsNonPrivilegedICMP(); !ok {
40 t.Skip(m)
41 }
42 for i, dt := range []diagTest{
43 {
44 "udp4", "0.0.0.0", iana.ProtocolICMP,
45 icmp.Message{
46 Type: ipv4.ICMPTypeEcho, Code: 0,
47 Body: &icmp.Echo{
48 ID: os.Getpid() & 0xffff,
49 Data: []byte("HELLO-R-U-THERE"),
50 },
51 },
52 },
53
54 {
55 "udp6", "::", iana.ProtocolIPv6ICMP,
56 icmp.Message{
57 Type: ipv6.ICMPTypeEchoRequest, Code: 0,
58 Body: &icmp.Echo{
59 ID: os.Getpid() & 0xffff,
60 Data: []byte("HELLO-R-U-THERE"),
61 },
62 },
63 },
64 } {
65 if err := doDiag(dt, i); err != nil {
66 t.Error(err)
67 }
68 }
69 })
70 t.Run("Ping/Privileged", func(t *testing.T) {
71 if !nettest.SupportsRawSocket() {
72 t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
73 }
74 for i, dt := range []diagTest{
75 {
76 "ip4:icmp", "0.0.0.0", iana.ProtocolICMP,
77 icmp.Message{
78 Type: ipv4.ICMPTypeEcho, Code: 0,
79 Body: &icmp.Echo{
80 ID: os.Getpid() & 0xffff,
81 Data: []byte("HELLO-R-U-THERE"),
82 },
83 },
84 },
85
86 {
87 "ip6:ipv6-icmp", "::", iana.ProtocolIPv6ICMP,
88 icmp.Message{
89 Type: ipv6.ICMPTypeEchoRequest, Code: 0,
90 Body: &icmp.Echo{
91 ID: os.Getpid() & 0xffff,
92 Data: []byte("HELLO-R-U-THERE"),
93 },
94 },
95 },
96 } {
97 if err := doDiag(dt, i); err != nil {
98 t.Error(err)
99 }
100 }
101 })
102 t.Run("Probe/Privileged", func(t *testing.T) {
103 if !nettest.SupportsRawSocket() {
104 t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
105 }
106 for i, dt := range []diagTest{
107 {
108 "ip4:icmp", "0.0.0.0", iana.ProtocolICMP,
109 icmp.Message{
110 Type: ipv4.ICMPTypeExtendedEchoRequest, Code: 0,
111 Body: &icmp.ExtendedEchoRequest{
112 ID: os.Getpid() & 0xffff,
113 Local: true,
114 Extensions: []icmp.Extension{
115 &icmp.InterfaceIdent{
116 Class: 3, Type: 1,
117 Name: "doesnotexist",
118 },
119 },
120 },
121 },
122 },
123
124 {
125 "ip6:ipv6-icmp", "::", iana.ProtocolIPv6ICMP,
126 icmp.Message{
127 Type: ipv6.ICMPTypeExtendedEchoRequest, Code: 0,
128 Body: &icmp.ExtendedEchoRequest{
129 ID: os.Getpid() & 0xffff,
130 Local: true,
131 Extensions: []icmp.Extension{
132 &icmp.InterfaceIdent{
133 Class: 3, Type: 1,
134 Name: "doesnotexist",
135 },
136 },
137 },
138 },
139 },
140 } {
141 if err := doDiag(dt, i); err != nil {
142 t.Error(err)
143 }
144 }
145 })
146 }
147
148 func doDiag(dt diagTest, seq int) error {
149 c, err := icmp.ListenPacket(dt.network, dt.address)
150 if err != nil {
151 return err
152 }
153 defer c.Close()
154
155 dst, err := googleAddr(c, dt.protocol)
156 if err != nil {
157 return err
158 }
159
160 if dt.network != "udp6" && dt.protocol == iana.ProtocolIPv6ICMP {
161 var f ipv6.ICMPFilter
162 f.SetAll(true)
163 f.Accept(ipv6.ICMPTypeDestinationUnreachable)
164 f.Accept(ipv6.ICMPTypePacketTooBig)
165 f.Accept(ipv6.ICMPTypeTimeExceeded)
166 f.Accept(ipv6.ICMPTypeParameterProblem)
167 f.Accept(ipv6.ICMPTypeEchoReply)
168 f.Accept(ipv6.ICMPTypeExtendedEchoReply)
169 if err := c.IPv6PacketConn().SetICMPFilter(&f); err != nil {
170 return err
171 }
172 }
173
174 switch m := dt.m.Body.(type) {
175 case *icmp.Echo:
176 m.Seq = 1 << uint(seq)
177 case *icmp.ExtendedEchoRequest:
178 m.Seq = 1 << uint(seq)
179 }
180 wb, err := dt.m.Marshal(nil)
181 if err != nil {
182 return err
183 }
184 if n, err := c.WriteTo(wb, dst); err != nil {
185 return err
186 } else if n != len(wb) {
187 return fmt.Errorf("got %v; want %v", n, len(wb))
188 }
189
190 rb := make([]byte, 1500)
191 if err := c.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil {
192 return err
193 }
194 n, peer, err := c.ReadFrom(rb)
195 if err != nil {
196 return err
197 }
198 rm, err := icmp.ParseMessage(dt.protocol, rb[:n])
199 if err != nil {
200 return err
201 }
202 switch {
203 case dt.m.Type == ipv4.ICMPTypeEcho && rm.Type == ipv4.ICMPTypeEchoReply:
204 fallthrough
205 case dt.m.Type == ipv6.ICMPTypeEchoRequest && rm.Type == ipv6.ICMPTypeEchoReply:
206 fallthrough
207 case dt.m.Type == ipv4.ICMPTypeExtendedEchoRequest && rm.Type == ipv4.ICMPTypeExtendedEchoReply:
208 fallthrough
209 case dt.m.Type == ipv6.ICMPTypeExtendedEchoRequest && rm.Type == ipv6.ICMPTypeExtendedEchoReply:
210 return nil
211 default:
212 return fmt.Errorf("got %+v from %v; want echo reply or extended echo reply", rm, peer)
213 }
214 }
215
216 func googleAddr(c *icmp.PacketConn, protocol int) (net.Addr, error) {
217 host := "ipv4.google.com"
218 if protocol == iana.ProtocolIPv6ICMP {
219 host = "ipv6.google.com"
220 }
221 ips, err := net.LookupIP(host)
222 if err != nil {
223 return nil, err
224 }
225 netaddr := func(ip net.IP) (net.Addr, error) {
226 switch c.LocalAddr().(type) {
227 case *net.UDPAddr:
228 return &net.UDPAddr{IP: ip}, nil
229 case *net.IPAddr:
230 return &net.IPAddr{IP: ip}, nil
231 default:
232 return nil, errors.New("neither UDPAddr nor IPAddr")
233 }
234 }
235 if len(ips) > 0 {
236 return netaddr(ips[0])
237 }
238 return nil, errors.New("no A or AAAA record")
239 }
240
241 func TestConcurrentNonPrivilegedListenPacket(t *testing.T) {
242 if testing.Short() {
243 t.Skip("avoid external network")
244 }
245 if m, ok := supportsNonPrivilegedICMP(); !ok {
246 t.Skip(m)
247 }
248
249 network, address := "udp4", "127.0.0.1"
250 if !nettest.SupportsIPv4() {
251 network, address = "udp6", "::1"
252 }
253 const N = 1000
254 var wg sync.WaitGroup
255 wg.Add(N)
256 for i := 0; i < N; i++ {
257 go func() {
258 defer wg.Done()
259 c, err := icmp.ListenPacket(network, address)
260 if err != nil {
261 t.Error(err)
262 return
263 }
264 c.Close()
265 }()
266 }
267 wg.Wait()
268 }
269
270 var (
271 nonPrivOnce sync.Once
272 nonPrivMsg string
273 nonPrivICMP bool
274 )
275
276 func supportsNonPrivilegedICMP() (string, bool) {
277 nonPrivOnce.Do(func() {
278 switch runtime.GOOS {
279 case "darwin", "ios":
280 nonPrivICMP = true
281 case "linux":
282 for _, t := range []struct{ network, address string }{
283 {"udp4", "127.0.0.1"},
284 {"udp6", "::1"},
285 } {
286 c, err := icmp.ListenPacket(t.network, t.address)
287 if err != nil {
288 nonPrivMsg = "you may need to adjust the net.ipv4.ping_group_range kernel state"
289 return
290 }
291 c.Close()
292 }
293 nonPrivICMP = true
294 default:
295 nonPrivMsg = "not supported on " + runtime.GOOS
296 }
297 })
298 return nonPrivMsg, nonPrivICMP
299 }
300
View as plain text