Good day.

I wrote for tests the simplest UDP server on Go. I launch one copy - everything is fine, it catches broadcast packages, it works. But! When you start the second instance of the application, it gives a listen udp 0.0.0.0:10001: bind: address already in use error to listen udp 0.0.0.0:10001: bind: address already in use .

Googling and climbing the source code of the net package, I found out that SO_REUSEADDR is installed and everything should work well, but it was not there.

Tell me what am I doing wrong?

Listing:

 package main import ( "fmt" "net" "os" ) /* A Simple function to verify error */ func CheckError(err error) { if err != nil { fmt.Println("Error: ", err) os.Exit(0) } } func main() { /* Lets prepare a address at any address at port 10001*/ ServerAddr, err := net.ResolveUDPAddr("udp", "0.0.0.0:10001") CheckError(err) /* Now listen at selected port */ ServerConn, err := net.ListenUDP("udp", ServerAddr) CheckError(err) defer ServerConn.Close() buf := make([]byte, 1024) for { n, addr, err := ServerConn.ReadFromUDP(buf) fmt.Println("Received ", string(buf[0:n]), " from ", addr) if err != nil { fmt.Println("Error: ", err) } } } 

go version:

 go version go1.6.2 linux/amd64 

lsb_release -a:

 No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 16.04.2 LTS Release: 16.04 Codename: xenial 

uname -a:

 Linux home-PC 4.4.0-83-generic #106-Ubuntu SMP Mon Jun 26 17:54:43 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 

UPD: A little pohamaniv, I found that it is possible to define the variable SO_REUSEPORT and on my Linux it is equal to 15 (so-so solution, but I don’t see any more options). I determined, changed the example to this:

 package main import ( "fmt" "log" "os" "syscall" "time" ) const ( SO_REUSEPORT int = 15 ) func main() { s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0) if err != nil { log.Fatal(err) } syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) syscall.SetsockoptInt(s, syscall.SOL_SOCKET, SO_REUSEPORT, 1) // syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1) // remove this line when you run linux lsa := &syscall.SockaddrInet4{Port: 12345, Addr: [4]byte{0, 0, 0, 0}} err = syscall.Bind(s, lsa) if err != nil { log.Fatal(err) } syscall.SetNonblock(s, true) rsa := &syscall.SockaddrInet4{Port: 12345, Addr: [4]byte{0, 0, 0, 0}} fin, ack := make(chan bool), make(chan bool) go reader(s, fin, ack) for i := 1; i <= 30; i++ { time.Sleep(time.Second) syscall.Sendto(s, []byte(fmt.Sprintf("%v (%.02d)", os.Getpid(), i)), 0, rsa) } fin <- true <-ack fmt.Println("\nbye then") } func reader(s int, fin <-chan bool, ack chan<- bool) { rb := make([]byte, 32) for { n, _, err := syscall.Recvfrom(s, rb, 0) // n, _, err := syscall.Recvfrom(s, rb, syscall.MSG_PEEK) if err != nil { time.Sleep(time.Second / 1000000) } else { fmt.Print(string(rb[:n]), "\n") } select { case <-fin: ack <- true return default: } } } 

Result: two instances of the application are successfully bundled to the same port. I looked closely at the output and found that the last instance launched captures the socket for reading. It turns out that no one except him can read from the socket anymore.

Again the question: What am I missing?

  • As far as I know, you can only bind once. SO_REUSEADDR is not about that. - Ainar-G
  • @ Ainar-G, As far as I know, you are wrong. At least in C / C ++ you can adjust this moment with the help of SO_REUSEADDR - Nikita Semikov

1 answer 1

To be able to reuse ports, you need to set the socket options. If you are writing under Linux, you can use this structure:

 package main import ( "fmt" "log" "net" "os" "strconv" "strings" "syscall" "golang.org/x/net/ipv4" ) func main() { prog := "mcast_listener" if len(os.Args) != 5 { fmt.Printf("usage: %s interface protocol group address:port\n", prog) fmt.Printf("example: %s eth2 udp 224.0.0.9 0.0.0.0:2000\n", prog) return } ifname := os.Args[1] proto := os.Args[2] group := os.Args[3] addrPort := os.Args[4] mcastRead(ifname, proto, group, addrPort) } func mcastRead(ifname, proto, group, addrPort string) { addr, port := splitHostPort(addrPort) p, err1 := strconv.Atoi(port) if err1 != nil { log.Fatal(err1) } a := net.ParseIP(addr) if a == nil { log.Fatal(fmt.Errorf("bad address: '%s'", addr)) } g := net.ParseIP(group) if g == nil { log.Fatal(fmt.Errorf("bad group: '%s'", group)) } ifi, err2 := net.InterfaceByName(ifname) if err2 != nil { log.Fatal(err2) } c, err3 := mcastOpen(a, p, ifname) if err3 != nil { log.Fatal(err3) } if err := c.JoinGroup(ifi, &net.UDPAddr{IP: g}); err != nil { log.Fatal(err) } if err := c.SetControlMessage(ipv4.FlagTTL|ipv4.FlagSrc|ipv4.FlagDst|ipv4.FlagInterface, true); err != nil { log.Fatal(err) } readLoop(c) c.Close() } func splitHostPort(hostPort string) (string, string) { s := strings.Split(hostPort, ":") host := s[0] if host == "" { host = "0.0.0.0" } if len(s) == 1 { return host, "" } return host, s[1] } func mcastOpen(bindAddr net.IP, port int, ifname string) (*ipv4.PacketConn, error) { s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP) if err != nil { log.Fatal(err) } if err := syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { log.Fatal(err) } // syscall.SetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1) if err := syscall.SetsockoptString(s, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, ifname); err != nil { log.Fatal(err) } lsa := syscall.SockaddrInet4{Port: port} copy(lsa.Addr[:], bindAddr.To4()) if err := syscall.Bind(s, &lsa); err != nil { syscall.Close(s) log.Fatal(err) } f := os.NewFile(uintptr(s), "") c, err := net.FilePacketConn(f) f.Close() if err != nil { log.Fatal(err) } p := ipv4.NewPacketConn(c) return p, nil } func readLoop(c *ipv4.PacketConn) { log.Printf("readLoop: reading") buf := make([]byte, 10000) for { n, cm, _, err1 := c.ReadFrom(buf) if err1 != nil { log.Printf("readLoop: ReadFrom: error %v", err1) break } var name string ifi, err2 := net.InterfaceByIndex(cm.IfIndex) if err2 != nil { log.Printf("readLoop: unable to solve ifIndex=%d: error: %v", cm.IfIndex, err2) } if ifi == nil { name = "ifname?" } else { name = ifi.Name } log.Printf("readLoop: recv %d bytes from %s to %s on %s", n, cm.Src, cm.Dst, name) } log.Printf("readLoop: exiting") } 

This code can be run in several instances and they can all listen to the same port.

  • A good example, but ... Multicast works the same way, and it’s exactly the broadcast that you need - Nikita Semikov