Sending out UDP packets fast without context (connection)?

Hi all,

I’m looking for a way to send out UDP packets in a fast way. What’s special in my case is that i MUST be able to define the source port number (not only the destination port), and that I DO NOT expect any reply back via UDP (so no context is required). Also, the source port number is identical for all these packets, while the destination IP varies. I’m using several parallel goroutines (worker threads) to produce the UDP packets.

The only “standard” way I found to do this I found at http://ipengineer.net/2016/05/golang-net-package-udp-client-with-specific-source-port/, and it looks like this, shortened a bit to the essential (sorry if it’s not very elegant, but I’m new to Go…):

func sendUDP(dstIP string, dstPort int, localIP string, localPort uint, data []byte) {
	RemoteEP := net.UDPAddr{IP: net.ParseIP(dstIP), Port: dstPort}

	localAddrString := fmt.Sprintf("%s:%d", localIP, localPort)
	LocalAddr, err := net.ResolveUDPAddr("udp", localAddrString)
	if err != nil {...}

	mutex.Lock()
	defer mutex.Unlock()
	conn, err := net.DialUDP("udp", LocalAddr, &RemoteEP)
	if err != nil {...}
	conn.Write(data)
	conn.Close()
}

This works, but requires a UDP connection (without this I have no idea how I could define the UDP source port, it is defined by the OS), which is time consuming. Worse, because the source port is identical for all packets, I must serialize the sequence from opening until closing the connection by using a mutex; otherwise it would complain about not being able to bind to the port, as a UDP connection creates a listening server, and the source port number is the same for all packets. So, this approach becomes really slow.

One alternative I figured out is using the gopacket package to write directly to the network interface, then the code looks more or less like this (note that Ethernet, ip and udp headers as well as payloads are precalculated in a scanner object here):

func (s *scanner) sendUDPHW(dstIP string, idx int, ipPtr *layers.IPv4, udpPtr *layers.UDP, buf gopacket.SerializeBuffer) {
	srcPort = s.srcPorts[idx]
	pldPtr = &s.payloads[idx]
	ipPtr.DstIP = net.ParseIP(dstIP)
	udpPtr.SrcPort = layers.UDPPort(srcPort)

	if err := gopacket.SerializeLayers(buf, s.opts, &s.eth, ipPtr, udpPtr, pldPtr); err != nil {...}
	s.handle.WritePacketData(buf.Bytes())
}

This works very well, and is about 30 times faster than the standard approach. But of course it is highly hardware dependent, involves MAC addresses to construct the Ethernet layer, requires root privileges, and not all virtualizations allow this approach. Also, the user must supply at least the target MAC address (as the trick using a router object is only supported under linux). Even finding out the device names to send out plus the local MAC addresses are quite complicated, mainly under a Windows platform. I would prefer to be able to do this in the standard package… is there any way?

Thanks in advance,

Klaymen

Dial the connection once, use it in parallel for all your sends, no mutexes etc?

Assuming the payload is identical (as source and destination ports already are), have you considered a multicast?

That does not work because the destination IP varies. In the DialUp call, the destination IP if given, in teh RemoteEP value, and can’t be changed for the same connection (in my understanding).

I did find one solution using raw sockets, but I still have to find out how fast it is. It solved the mac address issue, but loads the burden to calculate IP and UDP checksums yourself. The code (trial and not clean yet) looks more or less this:

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"net"
	"syscall"

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

const UDP_HEADER_LEN = 8

type UdpHeader struct {
	SourcePort      uint16
	DestinationPort uint16
	Length          uint16
	Checksum        uint16
}

// manual checksum calculation
func csum(sum int, data []byte) int {
	for i, b := range data {
		if i&1 == 0 {
			sum += (int(b) << 8)
		} else {
			sum += int(b)
		}
	}
	for sum > 0xffff {
		sum += (sum >> 16)
		sum &= 0xffff
	}
	return sum
}

func main() {
	destIP := net.ParseIP("1.2.3.4")
	srcIP := net.ParseIP("10.1.1.197")
	proto := 17 // UDP
	data := []byte("testdata")

	udpHeader_t := UdpHeader{
		SourcePort:      1111,
		DestinationPort: 2222,
		Length:          uint16(UDP_HEADER_LEN + len(data)),
	}

	buf := bytes.NewBuffer([]byte{})
	if err := binary.Write(buf, binary.BigEndian, &udpHeader_t); err != nil {...}

	udpHeader := buf.Bytes()
	dataWithHeader := append(udpHeader, data...)

	h := &ipv4.Header{
		Version:  ipv4.Version,
		Len:      ipv4.HeaderLen,
		TotalLen: ipv4.HeaderLen + UDP_HEADER_LEN + len(data),
		ID:       12345,
		Protocol: proto,
		TTL:      64,
		Dst:      destIP.To4(),
		Src:      srcIP.To4(),
	}
	ipH, _ := h.Marshal()
	ipH[2], ipH[3] = ipH[3], ipH[2] // no idea why this is required, but these bytes are swapped after marshalling...
	sum := csum(0, ipH)
	sum ^= 0xffff
	h.Checksum = sum

	// for UDP checksum, calculate over IP pseudoheader
	sum = csum(0, ipH[12:20]) // src and dest IP
	sum = csum(sum, []byte{0, byte(proto), 0, byte(UDP_HEADER_LEN + len(data))})
	sum = csum(sum, dataWithHeader)
	sum ^= 0xffff
	// update checksum in marshalled stream
	dataWithHeader[6] = byte(sum >> 8)
	dataWithHeader[7] = byte(sum)

	if fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW); err != nil {...}
	if err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1); err != nil {...}

	if out, err := h.Marshal(); err != nil {...}
	packet := append(out, dataWithHeader...)

	// destination address a second time, but here it is irrelevant
	addr := syscall.SockaddrInet4{}
	if err = syscall.Sendto(fd, packet, 0, &addr); err != nil {...}
	syscall.Close(fd)
}

PS: According to https://www.darkcoding.net/software/raw-sockets-in-go-link-layer/, it should be possible to let the system make checksum calculations (basically by skipping the setsockopt call and using destination IP in the final “addr” variable), but this did not work for me - the IP protocol (17) was also ignored (set to 255). And the code directly suggested in above link did not work due to the “internal” import of …golang/net/master/ipv4/header.go

You can use UDPConn.WriteTo or UDPConn.WriteMsgUDP, both of which take a destination address.

Thanks… WriteMsgUDP probably does not work, the manpage says "WriteMsgUDP writes a message to addr via c if c isn’t connected, or to c’s remote address if c is connected (in which case addr must be nil). " - so teh destination address of a connected UDPConn can’t be changed (and I can’t use it unconnected, because I need the DialUDP call). WriteToUDP might work though, I wasn’t aware of that… will try it out asap :slight_smile:

Note that you can also get a socket on a given port by listening, which is then not bound to a specific remote.

Unfortunately WriteToUDP fails with the error message “use of WriteTo with pre-connected connection” - so it seems this can’t change the target of a connection either. Somehow it makes sense, because otherwise the server part of UDPconn would not know which UDP packets to accept without memorizing all the peer IPs (a feature I’m not interested in, but which I assume UDPconn is doing under the hood).

Listening to a socket does not solve the problem - I don’t even need to do that, as I do not expect and UDP packets back. I ONLY need to send out UDP packets, but with (many) varying destination IPs. You can visualize this as a “UDP bomb” (and no, this has nothing to do with DDOS attacks, it’s just a picture :slight_smile: ) The only reason I would require the UDPconn object is because I must be able to define a source port number (in the outgoing UDP packet, as mentioned I do not need to listen on it at all). Without this need, I could just send out UDP packets without worrying about a dialUDP call.

Multicasting would not work neither, as I can’t influence the destination side, and so I can’t build up a multicast group with those.

1 Like

I think you miss my point.

func main() {
	conn, err := net.ListenUDP("udp", &net.UDPAddr{Port: 1234})
	if err != nil {
		log.Fatal("Listen:", err)
	}
	sendHello(conn, &net.UDPAddr{IP: net.IP{8, 8, 8, 8}, Port: 53})
	sendHello(conn, &net.UDPAddr{IP: net.IP{8, 8, 4, 4}, Port: 53})
}

func sendHello(conn *net.UDPConn, addr *net.UDPAddr) {
	n, err := conn.WriteTo([]byte("hello"), addr)
	if err != nil {
		log.Fatal("Write:", err)
	}
	fmt.Println("Sent", n, "bytes", conn.LocalAddr(), "->", addr)
}
$ go run main.go 
Sent 5 bytes [::]:1234 -> 8.8.8.8:53
Sent 5 bytes [::]:1234 -> 8.8.4.4:53
1 Like

Thanks again, indeed I missed your point :slight_smile:

That’s perfect, this works indeed, which is kind of a surprise! Only drawback, it is still a lot slower than the gopacket approach, but not that extreme anymore (around 6 times slower, as opposed to around 30 times slower before, measured on a 64 bit Ubuntu onder Go1.9.2)

UDP sends are rather slow in general, and even more so in Go, what with the syscall overhead on every packet.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.