Skip to content

Instantly share code, notes, and snippets.

@krtx
Last active September 17, 2020 15:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krtx/094ff4859c69515ddcd3ff4e18d6b262 to your computer and use it in GitHub Desktop.
Save krtx/094ff4859c69515ddcd3ff4e18d6b262 to your computer and use it in GitHub Desktop.
practice
package main
import (
"errors"
"fmt"
"math/rand"
"net"
"os"
"os/signal"
"strconv"
"time"
)
type PingPacket struct {
Identifier uint16
SequenceNumber uint16
Data []byte
}
func checkSum(b []byte) uint16 {
if len(b)%2 == 1 {
b = append(b, 0)
}
// FIXME: Is it correct to use int?
sum := 0
for i := 0; i < len(b); i += 2 {
sum += 0xffff - ((int(b[i]) << 8) | int(b[i+1]))
sum = sum&0xffff + ((sum & 0x10000) >> 16)
}
return uint16(sum)
}
// See the comment in the middle of this function for information of
// `enableTimestamp`.
func (p *PingPacket) Encode(enableTimestamp bool) []byte {
var headerSize int
if enableTimestamp {
headerSize = 16
} else {
headerSize = 8
}
payload := make([]byte, headerSize+len(p.Data))
payload[0] = 8 // 8 means Echo Message
payload[4] = uint8(p.Identifier >> 8)
payload[5] = uint8(p.Identifier & 0xff)
payload[6] = uint8(p.SequenceNumber >> 8)
payload[7] = uint8(p.SequenceNumber & 0xff)
// When I see a ping packet via Wireshark, there exits 'Timestamp from icmp
// data' field in the tail of ICMP header, although it is not an timestamp
// message. This is an easy implementation of embed the current time as
// 8 bytes data. FIXME: It seems that this code contains some bugs.
if enableTimestamp {
now := time.Now().Unix()
payload[8] = byte((now >> 56) & 0xff)
payload[9] = byte((now >> 48) & 0xff)
payload[10] = byte((now >> 40) & 0xff)
payload[11] = byte((now >> 32) & 0xff)
payload[12] = byte((now >> 24) & 0xff)
payload[13] = byte((now >> 16) & 0xff)
payload[14] = byte((now >> 8) & 0xff)
payload[15] = byte(now & 0xff)
}
copy(payload[headerSize:], p.Data)
c := checkSum(payload)
copy(payload[2:4], []byte{uint8(c >> 8), uint8(c & 0xff)})
return payload
}
func Validate(b []byte) bool {
if ^checkSum(b) != 0 {
return false
}
if len(b) < 8 {
return false
}
return true
}
func Decode(b []byte) (*PingPacket, error) {
if !Validate(b) {
return nil, errors.New("A malformed ping packet.")
}
return &PingPacket{
Identifier: uint16(b[4])<<8 | uint16(b[5]),
SequenceNumber: uint16(b[6])<<8 | uint16(b[7]),
Data: b[8:],
}, nil
}
func (p *PingPacket) Send(laddr, raddr *net.IPAddr) (int, error) {
conn, err := net.DialIP("ip4:icmp", laddr, raddr)
if err != nil {
return 0, err
}
n, err := conn.Write(p.Encode(false))
if err != nil {
return 0, err
}
return n, nil
}
func (p *PingPacket) SendAndReceiveResponse(laddr, raddr *net.IPAddr, timeout time.Duration) (time.Duration, error) {
sendTime := time.Now()
sendBytes, err := p.Send(laddr, raddr)
if err != nil {
return 0, err
}
var done = make(chan struct{})
var fail = make(chan error)
// Receive the response.
go func(bufSize int) {
// FIXME: This implementation that listens and closes the connection
// each time the ping packets send may degrades performance. We should
// listen the connection just once in one PingRepeatedly invocation.
ln, err := net.ListenIP("ip4", laddr)
if err != nil {
fail <- err
return
}
defer ln.Close()
buf := make([]byte, bufSize)
// Read packets until the corresponding pong found.
for {
// FIXME: WHY Read returns irrelevant bytes???
readBytes, err := ln.Read(buf)
if err != nil {
fail <- err
return
}
packet, err := Decode(buf[readBytes-sendBytes : readBytes])
if err != nil {
fail <- err
return
}
if packet.Identifier == p.Identifier && packet.SequenceNumber == p.SequenceNumber {
done <- struct{}{}
break
} else {
continue
}
}
}(sendBytes + 100) // FIXME: I don't know the exact size of the incoming packet.
select {
case <-done:
return time.Since(sendTime), err
case err := <-fail:
return 0, err
case <-time.After(timeout):
return 0, errors.New("Timeout")
}
}
func PingRepeatedly(times int, laddr, raddr *net.IPAddr, timeout time.Duration) {
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
packet := &PingPacket{
Identifier: uint16(rand.Int() & 0xffff),
SequenceNumber: 0,
Data: []byte("DROP YOU VIVID COLORS"),
}
total, success := 0, 0
done := make(chan struct{})
go func() {
for i := 0; times < 0 || i < times; i++ {
t, err := packet.SendAndReceiveResponse(laddr, raddr, timeout)
if err != nil {
fmt.Printf("%2d: error: %s\n", i+1, err)
} else {
fmt.Printf("%2d: time = %s\n", i+1, t)
success++
}
total++
packet.SequenceNumber++
<-time.After(500 * time.Millisecond)
}
done <- struct{}{}
}()
select {
case <-done:
fmt.Printf("received / send = %d / %d\n", success, total)
case <-interrupt:
fmt.Printf("\nreceived / send = %d / %d\n", success, total)
os.Exit(0)
}
}
func Help() {
fmt.Fprintf(os.Stderr, "usage: %s host [times]\n", os.Args[0])
os.Exit(1)
}
func main() {
if len(os.Args) < 2 {
Help()
}
host := os.Args[1]
times := -1
if len(os.Args) >= 3 {
var err error
times, err = strconv.Atoi(os.Args[2])
if err != nil {
fmt.Fprintf(os.Stderr, "error: [%s] is not an integer.", os.Args[2])
Help()
}
}
laddr, err := net.ResolveIPAddr("ip4", "0.0.0.0")
if err != nil {
fmt.Fprintln(os.Stderr, "ResolveIPAddr failed:", err)
os.Exit(1)
}
raddr, err := net.ResolveIPAddr("ip4", host)
if err != nil {
fmt.Fprintf(os.Stderr, "ResolveIPAddr for %s failed: %s\n", host, err)
os.Exit(1)
}
PingRepeatedly(times, laddr, raddr, time.Second)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment