github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/net/icmp/diag_test.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 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 "github.com/hxx258456/ccgo/net/icmp" 19 "github.com/hxx258456/ccgo/net/internal/iana" 20 "github.com/hxx258456/ccgo/net/ipv4" 21 "github.com/hxx258456/ccgo/net/ipv6" 22 "github.com/hxx258456/ccgo/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 }