<commit_before>package iputil

import (
	"bytes"
	"encoding/binary"
	"github.com/oopschen/xtunnel/sys"
	"net"
	"time"
)

const (
	// 5 seconds
	timeoutNanoMS = 5 * time.Second
	tryCount      = 4
	minTimeMS     = measureTime(30)
)

type measureTime uint16
type ipMeta struct {
	ip    string
	avgMS measureTime
}

type icmpEchoRequest struct {
	typ      uint8
	code     uint8
	checksum uint16
	id       uint16
	seq      uint16
}

/**
* <p>find the nearest ip address in ips</p>
* @param ips the list of ip
* @return the chosen ip in the ips
 */
func GetBestIP(ips []string) string {
	if nil == ips || 1 > len(ips) {
		return ""

	}

	ipTimeMap := make(map[string]measureTime)
	for _, ip := range ips {
		if 1 > len(ip) {
			continue
		}

		meta := checkPermform(ip)
		if "" == meta.ip {
			continue
		}

		// if <= minTime then return it directly
		if minTimeMS <= meta.avgMS {
			return meta.ip
		}

		ipTimeMap[meta.ip] = meta.avgMS

	}

	bestIP := ""
	bestTime := measureTime(10000)
	for ip, time := range ipTimeMap {
		if bestTime > time {
			bestTime = time
			bestIP = ip

		}

	}

	return bestIP
}

func checkPermform(ip string) (resultMeta *ipMeta) {
	resultMeta = &ipMeta{}
	conn, err := net.DialTimeout("ip4:icmp", ip, timeoutNanoMS)
	if nil != err {
		sys.Logger.Printf("socket %s", err)
		return

	}
	// always close conn if opened
	defer conn.Close()

	var (
		tolTime uint64 = 0
		pId     uint16 = 0x78
		pSeq    uint16 = 0x45
		recvBuf        = make([]byte, 8) // only type code header id seq
	)

	// start time
	timeStart := time.Now()

	// send pkts
	for i := 0; i < tryCount; i++ {
		pkt := createPkt(ip, pId, pSeq)
		if nil == pkt {
			sys.Logger.Printf("create packet %s", err)
			return

		}

		_, err := conn.Write(pkt)
		if nil != err {
			sys.Logger.Printf("write packet %s", err)
			return

		}
	}

	// recv pkts
	conn.SetReadDeadline(time.Now().Add(timeoutNanoMS))
	for i := 0; i < tryCount; i++ {
		_, err = conn.Read(recvBuf)
		if nil != err {
			sys.Logger.Printf("read packet %s", err)
			return
		}

	}

	tolTime = uint64(time.Now().Sub(timeStart).Nanoseconds())

	// send result to channel
	resultMeta.ip = ip
	// ms
	resultMeta.avgMS = measureTime(uint16(tolTime/1000000) / tryCount)
	return
}

func createPkt(ip string, id uint16, seq uint16) []byte {
	out := new(bytes.Buffer)
	// type, code, checksum
	var icmpReq icmpEchoRequest
	icmpReq.typ = 8
	icmpReq.code = 0
	icmpReq.checksum = 0
	icmpReq.id = id
	icmpReq.seq = seq

	err := binary.Write(out, binary.BigEndian, icmpReq)
	if nil != err {
		return nil

	}

	// set checksum
	icmpReq.checksum = checksum(out.Bytes())

	// reset buf
	out.Reset()

	err = binary.Write(out, binary.BigEndian, icmpReq)
	if nil != err {
		return nil

	}

	return out.Bytes()
}

func checksum(data []byte) uint16 {
	var (
		sum    uint32
		length int = len(data)
		index  int
	)
	for length > 1 {
		sum += uint32(data[index])<<8 + uint32(data[index+1])
		index += 2
		length -= 2
	}
	if length > 0 {
		sum += uint32(data[index])
	}
	sum += (sum >> 16)

	return uint16(^sum)
}<commit_msg>improve getBestIP<commit_after>package iputil

import (
	"bytes"
	"encoding/binary"
	"github.com/oopschen/xtunnel/sys"
	"net"
	"time"
)

const (
	// 5 seconds
	timeoutNanoMS = 1 * time.Second
	tryCount      = 4
	minTimeMS     = measureTime(30)
	defaultIP     = "0.0.0.0"
)

type measureTime uint16
type ipMeta struct {
	ip    string
	mTime measureTime
}

type icmpEchoRequest struct {
	typ       uint8
	code      uint8
	checksum  uint16
	id        uint16
	seq       uint16
	timestamp uint64
}

/**
* <p>find the nearest ip address in ips</p>
* @param ips the list of ip
* @return the chosen ip in the ips
 */
func GetBestIP(ips []string) string {
	if nil == ips || 1 > len(ips) {
		return ""

	}

	ipMetaList := make([]chan ipMeta, len(ips))

	// send start
	for i, ip := range ips {
		if 1 > len(ip) {
			continue
		}

		ipMetaList[i] = make(chan ipMeta)
		go sendRecvPackets(ipMetaList[i], ip)
	}

	// recv packets
	bestIP := ""
	bestTime := measureTime(65535)
	for _, ipChan := range ipMetaList {
		recvRes := <-ipChan

		if defaultIP == recvRes.ip {
			continue
		}

		if bestTime > recvRes.mTime {
			bestTime = recvRes.mTime
			bestIP = recvRes.ip
		}

	}

	return bestIP
}

func sendPkt(ip string, conn net.Conn) {
	var (
		pId  uint16 = 0x78
		pSeq uint16 = 0x45
	)

	// send pkts
	for i := 0; i < tryCount; i++ {
		pkt := createPkt(ip, pId, pSeq)
		if nil == pkt {
			return

		}

		_, err := conn.Write(pkt)
		if nil != err {
			sys.Logger.Printf("write packet %s", err)
			return

		}
		pId++
		pSeq++
	}

}

func createPkt(ip string, id uint16, seq uint16) []byte {
	out := new(bytes.Buffer)
	// type, code, checksum
	var icmpReq icmpEchoRequest
	icmpReq.typ = 8
	icmpReq.code = 0
	icmpReq.checksum = 0
	icmpReq.id = id
	icmpReq.seq = seq
	icmpReq.timestamp = uint64(time.Now().UnixNano())

	err := binary.Write(out, binary.BigEndian, icmpReq)
	if nil != err {
		return nil

	}

	// set checksum
	icmpReq.checksum = checksum(out.Bytes())

	// reset buf
	out.Reset()

	err = binary.Write(out, binary.BigEndian, icmpReq)
	if nil != err {
		return nil

	}

	return out.Bytes()
}

func checksum(data []byte) uint16 {
	var (
		sum    uint32
		length int = len(data)
		index  int
	)
	for length > 1 {
		sum += uint32(data[index])<<8 + uint32(data[index+1])
		index += 2
		length -= 2
	}
	if length > 0 {
		sum += uint32(data[index])
	}
	sum += (sum >> 16)

	return uint16(^sum)
}

func sendRecvPackets(result chan ipMeta, ip string) {
	var (
		returnIp = defaultIP
		mTime    = measureTime(0)
		recvChan = make(chan int16)
		conn     net.Conn
	)

	// always close conn if opened
	defer func() {
		if nil != conn {
			conn.Close()
		}
		result <- ipMeta{ip: returnIp, mTime: mTime}
	}()

	// init connection
	conn, err := net.Dial("ip4:icmp", ip)
	if nil != err {
		sys.Logger.Printf("socket %s", err)
		return
	}

	// recvs
	go recvPkt(recvChan, conn)

	// sends
	sendPkt(ip, conn)

	// check recvs
	recvMs := <-recvChan
	if 0 > recvMs {
		return
	}

	mTime = measureTime(recvMs)

	returnIp = ip
}

func recvPkt(result chan int16, conn net.Conn) {
	// recv pkts
	var (
		recvBuf         = make([]byte, 72) // only type code header id seq and timestamp: 60 bytes ip header + 12 icmp
		ms        int16 = -1
		startNano int64
		icmpBuf   []byte
	)

	defer func() {
		result <- ms
	}()

	for cnt := 0; cnt < tryCount; {
		conn.SetReadDeadline(time.Now().Add(timeoutNanoMS))
		byteNum, err := conn.Read(recvBuf)
		if nil != err {
			if errT, ok := err.(net.Error); ok && errT.Timeout() {
				break

			}

			sys.Logger.Printf("wait for packet %s", err)
			continue

		} else if len(recvBuf) > byteNum {
			// check is icmp, 10th bytes
			if 1 != recvBuf[9] {
				sys.Logger.Printf("wait for packet %x", recvBuf[8])
				continue
			}

			// parse ip header
			icmpBuf = recvBuf[uint16(0x0F&recvBuf[0])*4:]

			// not icmp echo reply and not enough bytes recv
			if 0 != icmpBuf[0] || 0 != icmpBuf[1] {
				sys.Logger.Printf("invalid packet, bytes=%d, type=0x%x, code=0x%x", byteNum, icmpBuf[0], icmpBuf[1])
				continue
			}

		}

		// parse timestamp and ip
		readBuf := bytes.NewReader(icmpBuf[8:])
		err = binary.Read(readBuf, binary.BigEndian, &startNano)
		if nil == err {
			cnt += 1
			ms = int16((time.Now().UnixNano() - startNano) / 1000000)
		} else {
			sys.Logger.Printf("read timestamp %s", err)

		}

	}

}
{'fraction_non_alphanumeric': 0.13741182854042322, 'fraction_numerical': 0.02631578947368421, 'mean_word_length': 2.782965623396614, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3513399', 'n_tokens_mistral': 3247, 'n_tokens_neox': 2880, 'n_words': 756}