github.com/google/cloudprober@v0.11.3/probes/ping/icmpconn_unix.go (about) 1 // Copyright 2020 The Cloudprober Authors. 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 15 //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris 16 // +build aix darwin dragonfly freebsd linux netbsd openbsd solaris 17 18 package ping 19 20 import ( 21 "encoding/binary" 22 "fmt" 23 "net" 24 "os" 25 "runtime" 26 "syscall" 27 "time" 28 "unsafe" 29 ) 30 31 // NativeEndian is the machine native endian implementation of ByteOrder. 32 var NativeEndian binary.ByteOrder 33 34 const ( 35 protocolIP = 0 36 ) 37 38 func sockaddr(sourceIP net.IP, ipVer int) (syscall.Sockaddr, error) { 39 a := &net.IPAddr{IP: sourceIP} 40 41 // If sourceIP is unspecified, we bind to the 0 IP address (all). 42 if sourceIP == nil { 43 a.IP = map[int]net.IP{4: net.IPv4zero, 6: net.IPv6unspecified}[ipVer] 44 } 45 46 switch ipVer { 47 case 4: 48 sa := &syscall.SockaddrInet4{} 49 copy(sa.Addr[:], a.IP) 50 return sa, nil 51 case 6: 52 sa := &syscall.SockaddrInet6{} 53 copy(sa.Addr[:], a.IP) 54 return sa, nil 55 default: 56 return nil, net.InvalidAddrError("unexpected family") 57 } 58 } 59 60 // listenPacket listens for incoming ICMP packets addressed to sourceIP. 61 // We need to write our own listenPacket instead of using "net.ListenPacket" 62 // for the following reasons: 63 // 1. ListenPacket doesn't support ICMP for SOCK_DGRAM sockets. You create 64 // datagram sockets by specifying network as "udp", but UDP new connection 65 // implementation ignores the protocol field entirely. 66 // 2. ListenPacket doesn't support setting socket options (we need 67 // SO_TIMESTAMP) in a straightforward way. 68 func listenPacket(sourceIP net.IP, ipVer int, datagramSocket bool) (*icmpPacketConn, error) { 69 var family, proto int 70 71 switch ipVer { 72 case 4: 73 family, proto = syscall.AF_INET, protocolICMP 74 case 6: 75 family, proto = syscall.AF_INET6, protocolIPv6ICMP 76 } 77 78 sockType := syscall.SOCK_RAW 79 if datagramSocket { 80 sockType = syscall.SOCK_DGRAM 81 } 82 83 s, err := syscall.Socket(family, sockType, proto) 84 if err != nil { 85 return nil, os.NewSyscallError("socket", err) 86 } 87 88 if runtime.GOOS == "darwin" && family == syscall.AF_INET { 89 // 0x17 = IP_STRIPHDR -- this is required for darwin IPv4. 90 if err := syscall.SetsockoptInt(s, protocolIP, 0x17, 1); err != nil { 91 syscall.Close(s) 92 return nil, os.NewSyscallError("setsockopt", err) 93 } 94 } 95 96 // Set socket option to receive kernel's timestamp from each packet. 97 // Ref: https://man7.org/linux/man-pages/man7/socket.7.html (SO_TIMESTAMP) 98 if err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_TIMESTAMP, 1); err != nil { 99 syscall.Close(s) 100 return nil, os.NewSyscallError("setsockopt", err) 101 } 102 103 sa, err := sockaddr(sourceIP, ipVer) 104 if err != nil { 105 syscall.Close(s) 106 return nil, err 107 } 108 if err := syscall.Bind(s, sa); err != nil { 109 syscall.Close(s) 110 return nil, os.NewSyscallError("bind", err) 111 } 112 113 // FilePacketConn is you get a PacketConn from a socket descriptor. 114 // Behind the scene, FilePacketConn creates either an IPConn or UDPConn, 115 // based on socket's local address (it gets that from the fd). 116 f := os.NewFile(uintptr(s), "icmp") 117 c, cerr := net.FilePacketConn(f) 118 f.Close() 119 120 if cerr != nil { 121 syscall.Close(s) 122 return nil, cerr 123 } 124 125 ipc := &icmpPacketConn{c: c} 126 ipc.ipConn, _ = c.(*net.IPConn) 127 ipc.udpConn, _ = c.(*net.UDPConn) 128 129 return ipc, nil 130 } 131 132 type icmpPacketConn struct { 133 c net.PacketConn 134 135 // We use ipConn and udpConn for reading OOB data from the connection. 136 ipConn *net.IPConn 137 udpConn *net.UDPConn 138 } 139 140 func (ipc *icmpPacketConn) read(buf []byte) (n int, addr net.Addr, recvTime time.Time, err error) { 141 // We need to convert to IPConn/UDPConn so that we can read out-of-band data 142 // using ReadMsg<IP,UDP> functions. PacketConn interface doesn't have method 143 // that exposes OOB data. 144 oob := make([]byte, 64) 145 var oobn int 146 if ipc.ipConn != nil { 147 n, oobn, _, addr, err = ipc.ipConn.ReadMsgIP(buf, oob) 148 } 149 if ipc.udpConn != nil { 150 n, oobn, _, addr, err = ipc.udpConn.ReadMsgUDP(buf, oob) 151 } 152 153 if err != nil { 154 return 155 } 156 157 cmsgs, cmErr := syscall.ParseSocketControlMessage(oob[:oobn]) 158 if cmErr != nil { 159 err = cmErr 160 return 161 } 162 163 for _, m := range cmsgs { 164 // We are interested only in socket-level control messages 165 // (syscall.SOL_SOCKET) 166 if m.Header.Level != syscall.SOL_SOCKET { 167 continue 168 } 169 170 // SCM_TIMESTAMP is the type of the timestamp control message. 171 // Note that syscall.SO_TIMESTAMP == syscall.SCM_TIMESTAMP for linux, but 172 // that doesn't have to be true for other operating systems, e.g. Mac OS X. 173 if m.Header.Type == syscall.SCM_TIMESTAMP { 174 if len(m.Data) < 16 { 175 err = fmt.Errorf("timestamp control message data size (%d) is less than timestamp size (16 bytes)", len(m.Data)) 176 return 177 } 178 sec := NativeEndian.Uint64(m.Data) 179 usec := NativeEndian.Uint64(m.Data[8:]) 180 recvTime = time.Unix(int64(sec), int64(usec)*1e3) 181 } 182 } 183 184 return 185 } 186 187 // write writes the ICMP message b to dst. 188 func (ipc *icmpPacketConn) write(buf []byte, dst net.Addr) (int, error) { 189 return ipc.c.WriteTo(buf, dst) 190 } 191 192 // Close closes the endpoint. 193 func (ipc *icmpPacketConn) close() { 194 ipc.c.Close() 195 } 196 197 // setReadDeadline sets the read deadline associated with the 198 // endpoint. 199 func (ipc *icmpPacketConn) setReadDeadline(t time.Time) { 200 ipc.c.SetReadDeadline(t) 201 } 202 203 func newICMPConn(sourceIP net.IP, ipVer int, datagramSocket bool) (*icmpPacketConn, error) { 204 return listenPacket(sourceIP, ipVer, datagramSocket) 205 } 206 207 // Find out native endianness when this packages is loaded. 208 // This code is based on: 209 // https://github.com/golang/net/blob/master/internal/socket/sys.go 210 func init() { 211 i := uint32(1) 212 b := (*[4]byte)(unsafe.Pointer(&i)) 213 if b[0] == 1 { 214 NativeEndian = binary.LittleEndian 215 } else { 216 NativeEndian = binary.BigEndian 217 } 218 }