mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add support for SO_ERROR to packet sockets.
Packet sockets also seem to allow double binding and do not return an error on linux. This was tested by running the syscall test in a linux namespace as root and the current test DoubleBind fails@HEAD. Passes after this change. Updates #173 PiperOrigin-RevId: 321445137
This commit is contained in:
committed by
gVisor bot
parent
fef90c61c6
commit
857d03f258
@@ -22,7 +22,7 @@ import (
|
||||
// Mapping for tcpip.Error types.
|
||||
var (
|
||||
ErrUnknownProtocol = New(tcpip.ErrUnknownProtocol.String(), linux.EINVAL)
|
||||
ErrUnknownNICID = New(tcpip.ErrUnknownNICID.String(), linux.EINVAL)
|
||||
ErrUnknownNICID = New(tcpip.ErrUnknownNICID.String(), linux.ENODEV)
|
||||
ErrUnknownDevice = New(tcpip.ErrUnknownDevice.String(), linux.ENODEV)
|
||||
ErrUnknownProtocolOption = New(tcpip.ErrUnknownProtocolOption.String(), linux.ENOPROTOOPT)
|
||||
ErrDuplicateNICID = New(tcpip.ErrDuplicateNICID.String(), linux.EEXIST)
|
||||
|
||||
@@ -79,6 +79,11 @@ type endpoint struct {
|
||||
closed bool
|
||||
stats tcpip.TransportEndpointStats `state:"nosave"`
|
||||
bound bool
|
||||
boundNIC tcpip.NICID
|
||||
|
||||
// lastErrorMu protects lastError.
|
||||
lastErrorMu sync.Mutex `state:"nosave"`
|
||||
lastError *tcpip.Error `state:".(string)"`
|
||||
}
|
||||
|
||||
// NewEndpoint returns a new packet endpoint.
|
||||
@@ -229,12 +234,14 @@ func (ep *endpoint) Bind(addr tcpip.FullAddress) *tcpip.Error {
|
||||
ep.mu.Lock()
|
||||
defer ep.mu.Unlock()
|
||||
|
||||
if ep.bound {
|
||||
return tcpip.ErrAlreadyBound
|
||||
if ep.bound && ep.boundNIC == addr.NIC {
|
||||
// If the NIC being bound is the same then just return success.
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unregister endpoint with all the nics.
|
||||
ep.stack.UnregisterPacketEndpoint(0, ep.netProto, ep)
|
||||
ep.bound = false
|
||||
|
||||
// Bind endpoint to receive packets from specific interface.
|
||||
if err := ep.stack.RegisterPacketEndpoint(addr.NIC, ep.netProto, ep); err != nil {
|
||||
@@ -242,6 +249,7 @@ func (ep *endpoint) Bind(addr tcpip.FullAddress) *tcpip.Error {
|
||||
}
|
||||
|
||||
ep.bound = true
|
||||
ep.boundNIC = addr.NIC
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -336,8 +344,21 @@ func (ep *endpoint) SetSockOptInt(opt tcpip.SockOptInt, v int) *tcpip.Error {
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *endpoint) takeLastError() *tcpip.Error {
|
||||
ep.lastErrorMu.Lock()
|
||||
defer ep.lastErrorMu.Unlock()
|
||||
|
||||
err := ep.lastError
|
||||
ep.lastError = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSockOpt implements tcpip.Endpoint.GetSockOpt.
|
||||
func (ep *endpoint) GetSockOpt(opt interface{}) *tcpip.Error {
|
||||
switch opt.(type) {
|
||||
case tcpip.ErrorOption:
|
||||
return ep.takeLastError()
|
||||
}
|
||||
return tcpip.ErrNotSupported
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package packet
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
@@ -70,3 +71,21 @@ func (ep *endpoint) afterLoad() {
|
||||
panic(*err)
|
||||
}
|
||||
}
|
||||
|
||||
// saveLastError is invoked by stateify.
|
||||
func (ep *endpoint) saveLastError() string {
|
||||
if ep.lastError == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ep.lastError.String()
|
||||
}
|
||||
|
||||
// loadLastError is invoked by stateify.
|
||||
func (ep *endpoint) loadLastError(s string) {
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
ep.lastError = tcpip.StringToError(s)
|
||||
}
|
||||
|
||||
@@ -343,7 +343,7 @@ TEST_P(CookedPacketTest, BindReceive) {
|
||||
}
|
||||
|
||||
// Double Bind socket.
|
||||
TEST_P(CookedPacketTest, DoubleBind) {
|
||||
TEST_P(CookedPacketTest, DoubleBindSucceeds) {
|
||||
struct sockaddr_ll bind_addr = {};
|
||||
bind_addr.sll_family = AF_PACKET;
|
||||
bind_addr.sll_protocol = htons(GetParam());
|
||||
@@ -354,12 +354,11 @@ TEST_P(CookedPacketTest, DoubleBind) {
|
||||
SyscallSucceeds());
|
||||
|
||||
// Binding socket again should fail.
|
||||
ASSERT_THAT(
|
||||
bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr),
|
||||
sizeof(bind_addr)),
|
||||
// Linux 4.09 returns EINVAL here, but some time before 4.19 it switched
|
||||
// to EADDRINUSE.
|
||||
AnyOf(SyscallFailsWithErrno(EADDRINUSE), SyscallFailsWithErrno(EINVAL)));
|
||||
ASSERT_THAT(bind(socket_, reinterpret_cast<struct sockaddr*>(&bind_addr),
|
||||
sizeof(bind_addr)),
|
||||
// Linux 4.09 returns EINVAL here, but some time before 4.19 it
|
||||
// switched to EADDRINUSE.
|
||||
SyscallSucceeds());
|
||||
}
|
||||
|
||||
// Bind and verify we do not receive data on interface which is not bound
|
||||
|
||||
@@ -559,6 +559,64 @@ TEST_P(RawPacketTest, SetSocketSendBuf) {
|
||||
ASSERT_EQ(quarter_sz, val);
|
||||
}
|
||||
|
||||
TEST_P(RawPacketTest, GetSocketError) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
|
||||
|
||||
int val = 0;
|
||||
socklen_t val_len = sizeof(val);
|
||||
ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ERROR, &val, &val_len),
|
||||
SyscallSucceeds());
|
||||
ASSERT_EQ(val, 0);
|
||||
}
|
||||
|
||||
TEST_P(RawPacketTest, GetSocketErrorBind) {
|
||||
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
|
||||
|
||||
{
|
||||
// Bind to the loopback device.
|
||||
struct sockaddr_ll bind_addr = {};
|
||||
bind_addr.sll_family = AF_PACKET;
|
||||
bind_addr.sll_protocol = htons(GetParam());
|
||||
bind_addr.sll_ifindex = GetLoopbackIndex();
|
||||
|
||||
ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr),
|
||||
sizeof(bind_addr)),
|
||||
SyscallSucceeds());
|
||||
|
||||
// SO_ERROR should return no errors.
|
||||
int val = 0;
|
||||
socklen_t val_len = sizeof(val);
|
||||
ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ERROR, &val, &val_len),
|
||||
SyscallSucceeds());
|
||||
ASSERT_EQ(val, 0);
|
||||
}
|
||||
|
||||
{
|
||||
// Now try binding to an invalid interface.
|
||||
struct sockaddr_ll bind_addr = {};
|
||||
bind_addr.sll_family = AF_PACKET;
|
||||
bind_addr.sll_protocol = htons(GetParam());
|
||||
bind_addr.sll_ifindex = 0xffff; // Just pick a really large number.
|
||||
|
||||
// Binding should fail with EINVAL
|
||||
ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr),
|
||||
sizeof(bind_addr)),
|
||||
SyscallFailsWithErrno(ENODEV));
|
||||
|
||||
// SO_ERROR does not return error when the device is invalid.
|
||||
// On Linux there is just one odd ball condition where this can return
|
||||
// an error where the device was valid and then removed or disabled
|
||||
// between the first check for index and the actual registration of
|
||||
// the packet endpoint. On Netstack this is not possible as the stack
|
||||
// global mutex is held during registration and check.
|
||||
int val = 0;
|
||||
socklen_t val_len = sizeof(val);
|
||||
ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ERROR, &val, &val_len),
|
||||
SyscallSucceeds());
|
||||
ASSERT_EQ(val, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef __fuchsia__
|
||||
|
||||
TEST_P(RawPacketTest, SetSocketDetachFilterNoInstalledFilter) {
|
||||
|
||||
Reference in New Issue
Block a user