在Golang中,如何使用套接字绑定到特定的地址/端口来接收组播数据包?

huangapple go评论91阅读模式
英文:

In Golang, how to receive multicast packets with socket bound to specific address/port?

问题

任务是将套接字绑定到特定的地址1.0.0.2:520(分配给eth2),然后读取发送到224.0.0.9:520的多播UDP数据包。

根据https://godoc.org/golang.org/x/net/ipv4上的代码,我尝试了下面的代码。

不幸的是,永远不会达到这个调试消息的结果:log.Printf("udpReader: recv %d bytes from %s to %s on %s", n, cm.Src, cm.Dst, ifname)

我知道eth2正在接收所需的数据包,因为我在其上运行了这个数据包嗅探器:

sudo tcpdump -n -i eth2
18:40:28.571456 IP 1.0.0.1.520 > 224.0.0.9.520: RIPv2, Request, length: 24
18:40:29.556503 IP 1.0.0.1.520 > 224.0.0.9.520: RIPv2, Response, length: 64

这是示例代码。你能找出为什么它不起作用吗?

package main

import (
	"fmt"
	"log"
	"net"

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

func main() {
	if err := interfaceAdd("eth2"); err != nil {
		log.Printf("main: error: %v", err)
	}

	log.Printf("main: waiting forever")
	<-make(chan int)
}

func interfaceAdd(s string) error {

	iface, err1 := net.InterfaceByName(s)
	if err1 != nil {
		return err1
	}

	addrList, err2 := iface.Addrs()
	if err2 != nil {
		return err2
	}

	for _, a := range addrList {
		addr, _, err3 := net.ParseCIDR(a.String())
		if err3 != nil {
			log.Printf("interfaceAdd: parse CIDR error for '%s' on '%s': %v", addr, s, err3)
			continue
		}
		if err := join(iface, addr); err != nil {
			log.Printf("interfaceAdd: join error for '%s' on '%s': %v", addr, s, err)
		}
	}

	return nil
}

func join(iface *net.Interface, addr net.IP) error {
	proto := "udp"
	var a string
	if addr.To4() == nil {
		// IPv6
		a = fmt.Sprintf("[%s]", addr.String())
	} else {
		// IPv4
		a = addr.String()
	}

	hostPort := fmt.Sprintf("%s:520", a) // rip multicast port

	// open socket (connection)
	conn, err2 := net.ListenPacket(proto, hostPort)
	if err2 != nil {
		return fmt.Errorf("join: %s/%s listen error: %v", proto, hostPort, err2)
	}

	// join multicast address
	pc := ipv4.NewPacketConn(conn)
	if err := pc.JoinGroup(iface, &net.UDPAddr{IP: net.IPv4(224, 0, 0, 9)}); err != nil {
		conn.Close()
		return fmt.Errorf("join: join error: %v", err)
	}

	// request control messages
	/*
		if err := pc.SetControlMessage(ipv4.FlagTTL|ipv4.FlagSrc|ipv4.FlagDst|ipv4.FlagInterface, true); err != nil {
			// warning only
			log.Printf("join: control message flags error: %v", err)
		}
	*/

	go udpReader(pc, iface.Name, addr.String())

	return nil
}

func udpReader(c *ipv4.PacketConn, ifname, ifaddr string) {

	log.Printf("udpReader: reading from '%s' on '%s'", ifaddr, ifname)

	defer c.Close()

	buf := make([]byte, 10000)

	for {
		n, cm, _, err := c.ReadFrom(buf)
		if err != nil {
			log.Printf("udpReader: ReadFrom: error %v", err)
			break
		}

		// make a copy because we will overwrite buf
		b := make([]byte, n)
		copy(b, buf)

		log.Printf("udpReader: recv %d bytes from %s to %s on %s", n, cm.Src, cm.Dst, ifname)
	}

	log.Printf("udpReader: exiting '%s'", ifname)
}

输出:

2016/02/09 18:44:20 interfaceAdd: join error for 'fe80::a00:27ff:fe52:9575' on 'eth2': join: udp/[fe80::a00:27ff:fe52:9575]:520 listen error: listen udp [fe80::a00:27ff:fe52:9575]:520: bind: invalid argument
2016/02/09 18:44:20 main: waiting forever
2016/02/09 18:44:20 udpReader: reading from '1.0.0.2' on 'eth2'
英文:

Task at hand is to bind a socket specifically to address 1.0.0.2:520 (assigned to eth2), then read multicast UDP packets addressed to 224.0.0.9:520.

I am trying the code below, based on https://godoc.org/golang.org/x/net/ipv4

Unfortunately, result is this debbuging message is never reached: log.Printf(&quot;udpReader: recv %d bytes from %s to %s on %s&quot;, n, cm.Src, cm.Dst, ifname)

I know eth2 is receiving the desired packets because I have this packet sniffer running on it:

sudo tcpdump -n -i eth2
18:40:28.571456 IP 1.0.0.1.520 &gt; 224.0.0.9.520: RIPv2, Request, length: 24
18:40:29.556503 IP 1.0.0.1.520 &gt; 224.0.0.9.520: RIPv2, Response, length: 64

This is the sample code. Can you spot why doesn't it work?

package main
import (
&quot;fmt&quot;
&quot;log&quot;
&quot;net&quot;
&quot;golang.org/x/net/ipv4&quot;
)
func main() {
if err := interfaceAdd(&quot;eth2&quot;); err != nil {
log.Printf(&quot;main: error: %v&quot;, err)
}
log.Printf(&quot;main: waiting forever&quot;)
&lt;-make(chan int)
}
func interfaceAdd(s string) error {
iface, err1 := net.InterfaceByName(s)
if err1 != nil {
return err1
}
addrList, err2 := iface.Addrs()
if err2 != nil {
return err2
}
for _, a := range addrList {
addr, _, err3 := net.ParseCIDR(a.String())
if err3 != nil {
log.Printf(&quot;interfaceAdd: parse CIDR error for &#39;%s&#39; on &#39;%s&#39;: %v&quot;, addr, s, err3)
continue
}
if err := join(iface, addr); err != nil {
log.Printf(&quot;interfaceAdd: join error for &#39;%s&#39; on &#39;%s&#39;: %v&quot;, addr, s, err)
}
}
return nil
}
func join(iface *net.Interface, addr net.IP) error {
proto := &quot;udp&quot;
var a string
if addr.To4() == nil {
// IPv6
a = fmt.Sprintf(&quot;[%s]&quot;, addr.String())
} else {
// IPv4
a = addr.String()
}
hostPort := fmt.Sprintf(&quot;%s:520&quot;, a) // rip multicast port
// open socket (connection)
conn, err2 := net.ListenPacket(proto, hostPort)
if err2 != nil {
return fmt.Errorf(&quot;join: %s/%s listen error: %v&quot;, proto, hostPort, err2)
}
// join multicast address
pc := ipv4.NewPacketConn(conn)
if err := pc.JoinGroup(iface, &amp;net.UDPAddr{IP: net.IPv4(224, 0, 0, 9)}); err != nil {
conn.Close()
return fmt.Errorf(&quot;join: join error: %v&quot;, err)
}
// request control messages
/*
if err := pc.SetControlMessage(ipv4.FlagTTL|ipv4.FlagSrc|ipv4.FlagDst|ipv4.FlagInterface, true); err != nil {
// warning only
log.Printf(&quot;join: control message flags error: %v&quot;, err)
}
*/
go udpReader(pc, iface.Name, addr.String())
return nil
}
func udpReader(c *ipv4.PacketConn, ifname, ifaddr string) {
log.Printf(&quot;udpReader: reading from &#39;%s&#39; on &#39;%s&#39;&quot;, ifaddr, ifname)
defer c.Close()
buf := make([]byte, 10000)
for {
n, cm, _, err := c.ReadFrom(buf)
if err != nil {
log.Printf(&quot;udpReader: ReadFrom: error %v&quot;, err)
break
}
// make a copy because we will overwrite buf
b := make([]byte, n)
copy(b, buf)
log.Printf(&quot;udpReader: recv %d bytes from %s to %s on %s&quot;, n, cm.Src, cm.Dst, ifname)
}
log.Printf(&quot;udpReader: exiting &#39;%s&#39;&quot;, ifname)
}

Output:

2016/02/09 18:44:20 interfaceAdd: join error for &#39;fe80::a00:27ff:fe52:9575&#39; on &#39;eth2&#39;: join: udp/[fe80::a00:27ff:fe52:9575]:520 listen error: listen udp [fe80::a00:27ff:fe52:9575]:520: bind: invalid argument
2016/02/09 18:44:20 main: waiting forever
2016/02/09 18:44:20 udpReader: reading from &#39;1.0.0.2&#39; on &#39;eth2&#39;

答案1

得分: 2

为了创建多个特定接口的套接字以接收发送到相同多播地址(224.0.0.9:520)的数据包,我的原始Go代码存在三个主要问题:

  1. 为了将多个套接字绑定到相同的UDP端口,需要设置syscall.SO_REUSEADDR。
  2. 为了将套接字限制在特定接口上,需要设置syscall.SO_BINDTODEVICE。
  3. 将UDP套接字绑定到0.0.0.0:520。

完整的示例代码可以在这里找到:http://play.golang.org/p/NprsZPHQmj

英文:

In order to create multiple interface-specific sockets to receive packets addressed to the same multicast address (224.0.0.9:520), my original Go code was missing three major issues:

  1. In order to bind multiple sockets to same UDP port, set syscall.SO_REUSEADDR
  2. In order to restrict socket to specific interface, set syscall.SO_BINDTODEVICE
  3. Bind the UDP sockets to 0.0.0.0:520

Find full sample code here: http://play.golang.org/p/NprsZPHQmj

huangapple
  • 本文由 发表于 2016年2月10日 02:57:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/35300039.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定