cmd/packet-sender/main.go

package main

import (
	"crypto/rand"
	"flag"
	"fmt"
	"math/big"
	"net"
	"os"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/icmp"
	"golang.org/x/net/ipv4"
)

func sendTCPSYN(ip string, port uint16) {
	address := net.JoinHostPort(ip, strconv.Itoa(int(port)))
	conn, err := net.DialTimeout("tcp", address, 2*time.Second)
	if err != nil {
		fmt.Println("TCP SYN sent (no connection):", err)
		return
	}
	defer conn.Close()
	fmt.Println("TCP connection established (SYN sent)")
}

func sendUDP(ip string, port uint16) {
	address := net.JoinHostPort(ip, strconv.Itoa(int(port)))
	conn, err := net.Dial("udp", address)
	if err != nil {
		fmt.Println("UDP error:", err)
		return
	}
	defer conn.Close()

	conn.Write([]byte("hello"))
	fmt.Println("UDP packet sent")
}

func sendICMP(ip string) {
	conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
	if err != nil {
		fmt.Println("ICMP listen error:", err)
		return
	}
	defer conn.Close()

	msg := icmp.Message{
		Type: ipv4.ICMPTypeEcho,
		Code: 0,
		Body: &icmp.Echo{
			ID:   os.Getpid() & 0xffff,
			Seq:  1,
			Data: []byte("ping"),
		},
	}

	data, _ := msg.Marshal(nil)
	_, err = conn.WriteTo(data, &net.IPAddr{IP: net.ParseIP(ip)})
	if err != nil {
		fmt.Println("ICMP send error:", err)
		return
	}

	fmt.Println("ICMP echo sent")
}

func parsePort(port string) []uint16 {
	if port == "" {
		return []uint16{}
	}

	// parse port range
	if strings.Contains(port, "-") {
		ports := strings.Split(port, "-")
		if len(ports) != 2 {
			return []uint16{}
		}
		start, _ := strconv.Atoi(ports[0])
		end, _ := strconv.Atoi(ports[1])
		if start < 1 || start > 65535 || end < 1 || end > 65535 || start > end {
			return []uint16{}
		}
		portsInt := make([]uint16, end-start+1)
		for i := start; i <= end; i++ {
			portsInt[i-start] = uint16(i)
		}
		return portsInt
	}

	// parse comma separated ports
	if strings.Contains(port, ",") {
		ports := strings.Split(port, ",")
		portsInt := []uint16{}
		for _, p := range ports {
			portInt, err := strconv.Atoi(p)
			if err == nil && portInt >= 1 && portInt <= 65535 {
				portsInt = append(portsInt, uint16(portInt))
			}
		}
		return portsInt
	}

	// parse single port
	portInt, _ := strconv.Atoi(port)
	if portInt < 1 || portInt > 65535 {
		return []uint16{}
	}
	return []uint16{uint16(portInt)}
}

func cryptoIntN(n int) int {
	if n <= 0 {
		return 0
	}
	val, err := rand.Int(rand.Reader, big.NewInt(int64(n)))
	if err != nil {
		return 0
	}
	return int(val.Int64())
}

func getPortFromPorts(ports []uint16) uint16 {
	if len(ports) == 0 {
		return 0
	}
	return ports[cryptoIntN(len(ports))]
}

// sleepDuration returns the duration between packets based on the packets per second
// with a jitter in percent of the sleep duration
func sleepDuration(pps, jitter int) time.Duration {
	duration := time.Second / time.Duration(pps)
	jitterDuration := time.Duration(jitter) * duration / 100
	return duration + jitterDuration - time.Duration(cryptoIntN(int(2*jitterDuration)))
}

func main() {
	ip := flag.String("ip", "", "IP address to send packets to")
	pps := flag.Int("pps", 2, "Packets per second")
	jitter := flag.Int("jitter", 30, "in percent of the sleep duration")
	port := flag.String("port", "1-65535", "Port to send packets to")
	icmpChance := flag.Int("icmp-chance", 180, "Chance to send ICMP packets (0-10000)")
	udpChance := flag.Int("udp-chance", 822, "Chance to send UDP packets (0-10000)")
	flag.Parse()

	// validate flags
	if *ip == "" {
		fmt.Println("IP address is required")
		flag.PrintDefaults()
		os.Exit(1)
	}
	if *pps <= 0 {
		fmt.Println("Packets per second must be greater than 0")
		flag.PrintDefaults()
		os.Exit(1)
	}
	if *jitter < 0 || *jitter > 100 {
		fmt.Println("Jitter must be between 0 and 100")
		flag.PrintDefaults()
		os.Exit(1)
	}
	if *port == "" {
		fmt.Println("Port is required")
		flag.PrintDefaults()
		os.Exit(1)
	}
	if *icmpChance < 0 || *icmpChance > 10000 {
		fmt.Println("ICMP chance must be between 0 and 10000")
		flag.PrintDefaults()
		os.Exit(1)
	}
	if *udpChance < 0 || *udpChance > 10000 {
		fmt.Println("UDP chance must be between 0 and 10000")
		flag.PrintDefaults()
		os.Exit(1)
	}

	// parse ports
	ports := parsePort(*port)

	// send packets
	for {
		r := cryptoIntN(10000)

		if r < *icmpChance {
			sendICMP(*ip)
		} else if r < *udpChance+*icmpChance {
			sendUDP(*ip, getPortFromPorts(ports))
		} else {
			sendTCPSYN(*ip, getPortFromPorts(ports))
		}

		time.Sleep(sleepDuration(*pps, *jitter))
	}
}