net: fix ListenMulitcastUDP to work properly when interface has no IPv4

The existing implementation would either fail or bind to the wrong interface
when the requested interface had no IPv4 address, such as when the Ethernet cable
was unplugged.

Now on Linux, it will always bind to the requested interface.
On other operating systems, it will consistently fail if the requested interface
has no IPv4 address.

Fixes #70132

Change-Id: I22ec7f9d4adaa4b5afb21fc448050fb4219cacee
Reviewed-on: https://go-review.googlesource.com/c/go/+/644375
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Commit-Queue: Ian Lance Taylor <iant@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
This commit is contained in:
Jesse Rittner 2025-01-25 16:21:53 -05:00 committed by Gopher Robot
parent 65004c7bf4
commit 0da7fafac4
6 changed files with 62 additions and 40 deletions

View file

@ -7,7 +7,6 @@
package net
import (
"internal/bytealg"
"runtime"
"syscall"
)
@ -43,35 +42,6 @@ func interfaceToIPv4Addr(ifi *Interface) (IP, error) {
return nil, errNoSuchInterface
}
func setIPv4MreqToInterface(mreq *syscall.IPMreq, ifi *Interface) error {
if ifi == nil {
return nil
}
ifat, err := ifi.Addrs()
if err != nil {
return err
}
for _, ifa := range ifat {
switch v := ifa.(type) {
case *IPAddr:
if a := v.IP.To4(); a != nil {
copy(mreq.Interface[:], a)
goto done
}
case *IPNet:
if a := v.IP.To4(); a != nil {
copy(mreq.Interface[:], a)
goto done
}
}
}
done:
if bytealg.Equal(mreq.Multiaddr[:], IPv4zero.To4()) {
return errNoSuchMulticastInterface
}
return nil
}
func setReadBuffer(fd *netFD, bytes int) error {
err := fd.pfd.SetsockoptInt(syscall.SOL_SOCKET, syscall.SO_RCVBUF, bytes)
runtime.KeepAlive(fd)

View file

@ -9,6 +9,16 @@ import (
"syscall"
)
func joinIPv4Group(fd *netFD, ifi *Interface, ip IP) error {
mreq := &syscall.IPMreqn{Multiaddr: [4]byte{ip[0], ip[1], ip[2], ip[3]}}
if ifi != nil {
mreq.Ifindex = int32(ifi.Index)
}
err := fd.pfd.SetsockoptIPMreqn(syscall.IPPROTO_IP, syscall.IP_ADD_MEMBERSHIP, mreq)
runtime.KeepAlive(fd)
return wrapSyscallError("setsockopt", err)
}
func setIPv4MulticastInterface(fd *netFD, ifi *Interface) error {
var v int32
if ifi != nil {

View file

@ -0,0 +1,52 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (unix && !linux) || windows
package net
import (
"internal/bytealg"
"runtime"
"syscall"
)
func joinIPv4Group(fd *netFD, ifi *Interface, ip IP) error {
mreq := &syscall.IPMreq{Multiaddr: [4]byte{ip[0], ip[1], ip[2], ip[3]}}
if err := setIPv4MreqToInterface(mreq, ifi); err != nil {
return err
}
err := fd.pfd.SetsockoptIPMreq(syscall.IPPROTO_IP, syscall.IP_ADD_MEMBERSHIP, mreq)
runtime.KeepAlive(fd)
return wrapSyscallError("setsockopt", err)
}
func setIPv4MreqToInterface(mreq *syscall.IPMreq, ifi *Interface) error {
if ifi == nil {
return nil
}
ifat, err := ifi.Addrs()
if err != nil {
return err
}
for _, ifa := range ifat {
switch v := ifa.(type) {
case *IPAddr:
if a := v.IP.To4(); a != nil {
copy(mreq.Interface[:], a)
goto done
}
case *IPNet:
if a := v.IP.To4(); a != nil {
copy(mreq.Interface[:], a)
goto done
}
}
}
done:
if bytealg.Equal(mreq.Interface[:], IPv4zero.To4()) {
return errNoSuchMulticastInterface
}
return nil
}

View file

@ -11,16 +11,6 @@ import (
"syscall"
)
func joinIPv4Group(fd *netFD, ifi *Interface, ip IP) error {
mreq := &syscall.IPMreq{Multiaddr: [4]byte{ip[0], ip[1], ip[2], ip[3]}}
if err := setIPv4MreqToInterface(mreq, ifi); err != nil {
return err
}
err := fd.pfd.SetsockoptIPMreq(syscall.IPPROTO_IP, syscall.IP_ADD_MEMBERSHIP, mreq)
runtime.KeepAlive(fd)
return wrapSyscallError("setsockopt", err)
}
func setIPv6MulticastInterface(fd *netFD, ifi *Interface) error {
var v int
if ifi != nil {