pinger.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. package pinger
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "errors"
  6. "fmt"
  7. "github.com/coroot/coroot-node-agent/proc"
  8. "github.com/vishvananda/netns"
  9. "golang.org/x/net/icmp"
  10. "golang.org/x/net/ipv4"
  11. "inet.af/netaddr"
  12. "k8s.io/klog/v2"
  13. "net"
  14. "os"
  15. "strings"
  16. "syscall"
  17. "time"
  18. )
  19. const (
  20. pingReplyPollTimeout = 10 * time.Millisecond
  21. protocolICMP = 1 // Internet Control Message
  22. SOF_TIMESTAMPING_TX_SOFTWARE = 1 << 1
  23. SOF_TIMESTAMPING_TX_SCHED = 1 << 8
  24. SOF_TIMESTAMPING_RX_SOFTWARE = 1 << 3
  25. )
  26. var (
  27. pingerID = os.Getpid() & 0xFFFF
  28. )
  29. type sentPacket struct {
  30. seq int
  31. txTimestamp time.Time
  32. }
  33. func Ping(ns netns.NsHandle, originNs netns.NsHandle, targets []netaddr.IP, timeout time.Duration) (map[netaddr.IP]float64, error) {
  34. if len(targets) < 1 {
  35. return nil, nil
  36. }
  37. var conn *net.IPConn
  38. err := proc.ExecuteInNetNs(ns, originNs, func() error {
  39. c, err := openConn()
  40. if err != nil {
  41. return err
  42. }
  43. conn = c
  44. return nil
  45. })
  46. if err != nil {
  47. return nil, fmt.Errorf("failed to open IPConn: %s", err)
  48. }
  49. defer conn.Close()
  50. f, err := conn.File()
  51. if err != nil {
  52. return nil, err
  53. }
  54. defer f.Close()
  55. fd := int(f.Fd())
  56. ids := make(map[netaddr.IP]*sentPacket, len(targets))
  57. for seq, ip := range targets {
  58. pkt := &sentPacket{seq: seq + 1, txTimestamp: time.Now()}
  59. if err := send(conn, pkt.seq, ip.IPAddr()); err != nil {
  60. if strings.HasPrefix(err.Error(), "resource temporarily unavailable") {
  61. continue
  62. }
  63. return nil, fmt.Errorf("failed to send packet to %s: %s", ip, err)
  64. }
  65. if pkt.txTimestamp, err = getTxTimestamp(fd); err != nil {
  66. if strings.HasPrefix(err.Error(), "resource temporarily unavailable") {
  67. continue
  68. }
  69. return nil, fmt.Errorf("failed to get RX timestamp: %s", err)
  70. }
  71. ids[ip] = pkt
  72. }
  73. timeoutTicker := time.NewTimer(timeout)
  74. defer timeoutTicker.Stop()
  75. rttByIp := make(map[netaddr.IP]float64, len(targets))
  76. for {
  77. select {
  78. case <-timeoutTicker.C:
  79. return rttByIp, nil
  80. default:
  81. if len(rttByIp) == len(targets) {
  82. return rttByIp, nil
  83. }
  84. remoteAddr, echoReply, rxTimestamp, err := receive(conn)
  85. if err != nil {
  86. if !strings.Contains(err.Error(), "interrupted system call") { // recvmsg timeout is not an issue
  87. klog.Errorln(err)
  88. }
  89. continue
  90. }
  91. if echoReply == nil {
  92. continue
  93. }
  94. if echoReply.ID != pingerID {
  95. continue
  96. }
  97. ip, ok := netaddr.FromStdIP(remoteAddr.IP)
  98. if !ok {
  99. continue
  100. }
  101. if pkt, ok := ids[ip]; ok && pkt.seq == echoReply.Seq {
  102. rtt := rxTimestamp.Sub(pkt.txTimestamp).Seconds()
  103. if rtt < 0 { // a small negative value is possible if the clock has adjusted by ntpd
  104. rtt = 0
  105. }
  106. rttByIp[ip] = rtt
  107. }
  108. }
  109. }
  110. }
  111. func send(conn *net.IPConn, seq int, ip net.Addr) error {
  112. msg := &icmp.Message{
  113. Type: ipv4.ICMPTypeEcho,
  114. Body: &icmp.Echo{
  115. ID: pingerID,
  116. Seq: seq,
  117. },
  118. }
  119. data, err := msg.Marshal(nil)
  120. if err != nil {
  121. return err
  122. }
  123. _, err = conn.WriteTo(data, ip)
  124. return err
  125. }
  126. func getTxTimestamp(socketFd int) (time.Time, error) {
  127. var t time.Time
  128. pktBuf := make([]byte, 1024)
  129. oob := make([]byte, 1024)
  130. _, oobn, _, _, err := syscall.Recvmsg(socketFd, pktBuf, oob, syscall.MSG_ERRQUEUE)
  131. if err != nil {
  132. return t, err
  133. }
  134. cms, err := syscall.ParseSocketControlMessage(oob[:oobn])
  135. if err != nil {
  136. return t, err
  137. }
  138. for _, cm := range cms {
  139. if cm.Header.Level == syscall.SOL_SOCKET && cm.Header.Type == syscall.SO_TIMESTAMP {
  140. var tv syscall.Timeval
  141. if err := binary.Read(bytes.NewBuffer(cm.Data), binary.LittleEndian, &tv); err != nil {
  142. return t, err
  143. }
  144. return time.Unix(tv.Unix()), nil
  145. }
  146. }
  147. return t, errors.New("empty response")
  148. }
  149. func receive(conn *net.IPConn) (*net.IPAddr, *icmp.Echo, time.Time, error) {
  150. pktBuf := make([]byte, 1024)
  151. oob := make([]byte, 1024)
  152. var ts time.Time
  153. _ = conn.SetReadDeadline(time.Now().Add(pingReplyPollTimeout))
  154. n, oobn, _, ra, err := conn.ReadMsgIP(pktBuf, oob)
  155. if err != nil {
  156. if neterr, ok := err.(*net.OpError); ok && neterr.Timeout() {
  157. return nil, nil, ts, nil
  158. }
  159. if strings.Contains(err.Error(), "no message of desired type") {
  160. return nil, nil, ts, nil
  161. }
  162. return nil, nil, ts, err
  163. }
  164. if ts, err = getRxTimestamp(oob, oobn); err != nil {
  165. return nil, nil, ts, fmt.Errorf("failed to get RX timestamp: %s", err)
  166. }
  167. echo, err := extractEchoFromPacket(pktBuf, n)
  168. if err != nil {
  169. return nil, nil, ts, fmt.Errorf("failed to extract ICMP Echo from IPv4 packet %s: %s", ra, err)
  170. }
  171. return ra, echo, ts, nil
  172. }
  173. func extractEchoFromPacket(pktBuf []byte, n int) (*icmp.Echo, error) {
  174. if n < ipv4.HeaderLen {
  175. return nil, errors.New("malformed IPv4 packet")
  176. }
  177. pktBuf = pktBuf[ipv4.HeaderLen:]
  178. var m *icmp.Message
  179. m, err := icmp.ParseMessage(protocolICMP, pktBuf)
  180. if err != nil {
  181. return nil, err
  182. }
  183. if m.Type != ipv4.ICMPTypeEchoReply {
  184. return nil, nil
  185. }
  186. echo, ok := m.Body.(*icmp.Echo)
  187. if !ok {
  188. return nil, fmt.Errorf("malformed ICMP message body: %T", m.Body)
  189. }
  190. return echo, nil
  191. }
  192. func getRxTimestamp(oob []byte, oobn int) (time.Time, error) {
  193. var ts time.Time
  194. cms, err := syscall.ParseSocketControlMessage(oob[:oobn])
  195. if err != nil {
  196. return ts, err
  197. }
  198. for _, cm := range cms {
  199. if cm.Header.Level == syscall.SOL_SOCKET && cm.Header.Type == syscall.SO_TIMESTAMP {
  200. var tv syscall.Timeval
  201. if err := binary.Read(bytes.NewBuffer(cm.Data), binary.LittleEndian, &tv); err != nil {
  202. return ts, err
  203. }
  204. return time.Unix(tv.Unix()), nil
  205. }
  206. }
  207. return ts, errors.New("invalid out-of-band data")
  208. }
  209. func openConn() (*net.IPConn, error) {
  210. conn, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
  211. if err != nil {
  212. return nil, err
  213. }
  214. ipconn := conn.(*net.IPConn)
  215. f, err := ipconn.File()
  216. if err != nil {
  217. return nil, err
  218. }
  219. defer f.Close()
  220. fd := int(f.Fd())
  221. flags := SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_TX_SCHED | SOF_TIMESTAMPING_RX_SOFTWARE
  222. if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_TIMESTAMPING, flags); err != nil {
  223. return nil, err
  224. }
  225. if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_TIMESTAMP, 1); err != nil {
  226. return nil, err
  227. }
  228. timeout := syscall.Timeval{Sec: 0, Usec: 1000}
  229. if err := syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &timeout); err != nil {
  230. return nil, err
  231. }
  232. if err := syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &timeout); err != nil {
  233. return nil, err
  234. }
  235. return ipconn, nil
  236. }