package failover import ( "fmt" "net" "os" "time" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" ) var ( // mtuV4Overhead defines the total MTU overhead for an ICMP ECHO message: 20 bytes IP header + 8 bytes ICMP header mtuV4Overhead = 28 // mtuV6Overhead defines the total MTU v6 overhead for an ICMP ECHO message: 40 bytes IP header + 8 bytes ICMP header mtuV6Overhead = 48 ) // Pinger sends pings type Pinger struct { listener net.PacketConn buffer []byte gateway net.Addr v4 bool } // Read reads from the ping listener with deadline `deadline` func (p Pinger) Read(deadline time.Time) error { // First set the deadline to read err := p.listener.SetReadDeadline(deadline) if err != nil { return err } r := make([]byte, 1500) n, _, err := p.listener.ReadFrom(r) if err != nil { return err } var t icmp.Type if p.v4 { t = ipv4.ICMPTypeEchoReply } else { t = ipv6.ICMPTypeEchoReply } got, err := icmp.ParseMessage(t.Protocol(), r[:n]) if err != nil { return err } switch got.Type { case t: return nil default: return fmt.Errorf("not a ping echo reply, got: %+v", got) } } // Send sends a single ping func (p Pinger) Send(seq int) error { errorMessage := fmt.Sprintf("failed sending ping, seq %d", seq) // Make a new ICMP message var t icmp.Type if p.v4 { t = ipv4.ICMPTypeEcho } else { t = ipv6.ICMPTypeEchoRequest } m := icmp.Message{ Type: t, Code: 0, Body: &icmp.Echo{ ID: os.Getpid() & 0xffff, Seq: seq, Data: p.buffer, }, } // Marshal the message to bytes b, err := m.Marshal(nil) if err != nil { return fmt.Errorf("%s with error: %w", errorMessage, err) } // And send it to the gateway IP! _, err = p.listener.WriteTo(b, p.gateway) if err != nil { return fmt.Errorf("%s with error: %w", errorMessage, err) } return nil }