Receive broadcast packets on interested endpoints

When a broadcast packet is received by the stack, the packet should be
delivered to each endpoint that may be interested in the packet. This
includes all any address and specified broadcast address listeners.

Test: integration_test.TestReuseAddrAndBroadcast
PiperOrigin-RevId: 332060652
This commit is contained in:
Ghanan Gowripalan 2020-09-16 12:19:06 -07:00 committed by gVisor bot
parent 326a1dbb73
commit 87c5c0ad25
8 changed files with 405 additions and 78 deletions

View File

@ -165,7 +165,7 @@ func (epsByNIC *endpointsByNIC) handlePacket(r *Route, id TransportEndpointID, p
// If this is a broadcast or multicast datagram, deliver the datagram to all
// endpoints bound to the right device.
if isMulticastOrBroadcast(id.LocalAddress) {
if isInboundMulticastOrBroadcast(r) {
mpep.handlePacketAll(r, id, pkt)
epsByNIC.mu.RUnlock() // Don't use defer for performance reasons.
return
@ -526,7 +526,7 @@ func (d *transportDemuxer) deliverPacket(r *Route, protocol tcpip.TransportProto
// If the packet is a UDP broadcast or multicast, then find all matching
// transport endpoints.
if protocol == header.UDPProtocolNumber && isMulticastOrBroadcast(id.LocalAddress) {
if protocol == header.UDPProtocolNumber && isInboundMulticastOrBroadcast(r) {
eps.mu.RLock()
destEPs := eps.findAllEndpointsLocked(id)
eps.mu.RUnlock()
@ -546,7 +546,7 @@ func (d *transportDemuxer) deliverPacket(r *Route, protocol tcpip.TransportProto
// If the packet is a TCP packet with a non-unicast source or destination
// address, then do nothing further and instruct the caller to do the same.
if protocol == header.TCPProtocolNumber && (!isUnicast(r.LocalAddress) || !isUnicast(r.RemoteAddress)) {
if protocol == header.TCPProtocolNumber && (!isInboundUnicast(r) || !isOutboundUnicast(r)) {
// TCP can only be used to communicate between a single source and a
// single destination; the addresses must be unicast.
r.Stats().TCP.InvalidSegmentsReceived.Increment()
@ -677,10 +677,14 @@ func (d *transportDemuxer) unregisterRawEndpoint(netProto tcpip.NetworkProtocolN
eps.mu.Unlock()
}
func isMulticastOrBroadcast(addr tcpip.Address) bool {
return addr == header.IPv4Broadcast || header.IsV4MulticastAddress(addr) || header.IsV6MulticastAddress(addr)
func isInboundMulticastOrBroadcast(r *Route) bool {
return r.IsInboundBroadcast() || header.IsV4MulticastAddress(r.LocalAddress) || header.IsV6MulticastAddress(r.LocalAddress)
}
func isUnicast(addr tcpip.Address) bool {
return addr != header.IPv4Any && addr != header.IPv6Any && !isMulticastOrBroadcast(addr)
func isInboundUnicast(r *Route) bool {
return r.LocalAddress != header.IPv4Any && r.LocalAddress != header.IPv6Any && !isInboundMulticastOrBroadcast(r)
}
func isOutboundUnicast(r *Route) bool {
return r.RemoteAddress != header.IPv4Any && r.RemoteAddress != header.IPv6Any && !r.IsOutboundBroadcast() && !header.IsV4MulticastAddress(r.RemoteAddress) && !header.IsV6MulticastAddress(r.RemoteAddress)
}

View File

@ -23,6 +23,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
"gvisor.dev/gvisor/pkg/tcpip/link/loopback"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
@ -436,3 +437,122 @@ func TestIncomingMulticastAndBroadcast(t *testing.T) {
})
}
}
// TestReuseAddrAndBroadcast makes sure broadcast packets are received by all
// interested endpoints.
func TestReuseAddrAndBroadcast(t *testing.T) {
const (
nicID = 1
localPort = 9000
loopbackBroadcast = tcpip.Address("\x7f\xff\xff\xff")
)
data := tcpip.SlicePayload([]byte{1, 2, 3, 4})
tests := []struct {
name string
broadcastAddr tcpip.Address
}{
{
name: "Subnet directed broadcast",
broadcastAddr: loopbackBroadcast,
},
{
name: "IPv4 broadcast",
broadcastAddr: header.IPv4Broadcast,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocol{ipv4.NewProtocol(), ipv6.NewProtocol()},
TransportProtocols: []stack.TransportProtocol{udp.NewProtocol()},
})
if err := s.CreateNIC(nicID, loopback.New()); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
protoAddr := tcpip.ProtocolAddress{
Protocol: header.IPv4ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: "\x7f\x00\x00\x01",
PrefixLen: 8,
},
}
if err := s.AddProtocolAddress(nicID, protoAddr); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v): %s", nicID, protoAddr, err)
}
s.SetRouteTable([]tcpip.Route{
tcpip.Route{
// We use the empty subnet instead of just the loopback subnet so we
// also have a route to the IPv4 Broadcast address.
Destination: header.IPv4EmptySubnet,
NIC: nicID,
},
})
// We create endpoints that bind to both the wildcard address and the
// broadcast address to make sure both of these types of "broadcast
// interested" endpoints receive broadcast packets.
wq := waiter.Queue{}
var eps []tcpip.Endpoint
for _, bindWildcard := range []bool{false, true} {
// Create multiple endpoints for each type of "broadcast interested"
// endpoint so we can test that all endpoints receive the broadcast
// packet.
for i := 0; i < 2; i++ {
ep, err := s.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
if err != nil {
t.Fatalf("(eps[%d]) NewEndpoint(%d, %d, _): %s", len(eps), udp.ProtocolNumber, ipv4.ProtocolNumber, err)
}
defer ep.Close()
if err := ep.SetSockOptBool(tcpip.ReuseAddressOption, true); err != nil {
t.Fatalf("eps[%d].SetSockOptBool(tcpip.ReuseAddressOption, true): %s", len(eps), err)
}
if err := ep.SetSockOptBool(tcpip.BroadcastOption, true); err != nil {
t.Fatalf("eps[%d].SetSockOptBool(tcpip.BroadcastOption, true): %s", len(eps), err)
}
bindAddr := tcpip.FullAddress{Port: localPort}
if bindWildcard {
if err := ep.Bind(bindAddr); err != nil {
t.Fatalf("eps[%d].Bind(%+v): %s", len(eps), bindAddr, err)
}
} else {
bindAddr.Addr = test.broadcastAddr
if err := ep.Bind(bindAddr); err != nil {
t.Fatalf("eps[%d].Bind(%+v): %s", len(eps), bindAddr, err)
}
}
eps = append(eps, ep)
}
}
for i, wep := range eps {
writeOpts := tcpip.WriteOptions{
To: &tcpip.FullAddress{
Addr: test.broadcastAddr,
Port: localPort,
},
}
if n, _, err := wep.Write(data, writeOpts); err != nil {
t.Fatalf("eps[%d].Write(_, _): %s", i, err)
} else if want := int64(len(data)); n != want {
t.Fatalf("got eps[%d].Write(_, _) = (%d, nil, nil), want = (%d, nil, nil)", i, n, want)
}
for j, rep := range eps {
if gotPayload, _, err := rep.Read(nil); err != nil {
t.Errorf("(eps[%d] write) eps[%d].Read(nil): %s", i, j, err)
} else if diff := cmp.Diff(buffer.View(data), gotPayload); diff != "" {
t.Errorf("(eps[%d] write) got UDP payload from eps[%d] mismatch (-want +got):\n%s", i, j, diff)
}
}
}
})
}
}

View File

@ -5214,6 +5214,8 @@ func TestListenBacklogFull(t *testing.T) {
func TestListenNoAcceptNonUnicastV4(t *testing.T) {
multicastAddr := tcpip.Address("\xe0\x00\x01\x02")
otherMulticastAddr := tcpip.Address("\xe0\x00\x01\x03")
subnet := context.StackAddrWithPrefix.Subnet()
subnetBroadcastAddr := subnet.Broadcast()
tests := []struct {
name string
@ -5221,53 +5223,59 @@ func TestListenNoAcceptNonUnicastV4(t *testing.T) {
dstAddr tcpip.Address
}{
{
"SourceUnspecified",
header.IPv4Any,
context.StackAddr,
name: "SourceUnspecified",
srcAddr: header.IPv4Any,
dstAddr: context.StackAddr,
},
{
"SourceBroadcast",
header.IPv4Broadcast,
context.StackAddr,
name: "SourceBroadcast",
srcAddr: header.IPv4Broadcast,
dstAddr: context.StackAddr,
},
{
"SourceOurMulticast",
multicastAddr,
context.StackAddr,
name: "SourceOurMulticast",
srcAddr: multicastAddr,
dstAddr: context.StackAddr,
},
{
"SourceOtherMulticast",
otherMulticastAddr,
context.StackAddr,
name: "SourceOtherMulticast",
srcAddr: otherMulticastAddr,
dstAddr: context.StackAddr,
},
{
"DestUnspecified",
context.TestAddr,
header.IPv4Any,
name: "DestUnspecified",
srcAddr: context.TestAddr,
dstAddr: header.IPv4Any,
},
{
"DestBroadcast",
context.TestAddr,
header.IPv4Broadcast,
name: "DestBroadcast",
srcAddr: context.TestAddr,
dstAddr: header.IPv4Broadcast,
},
{
"DestOurMulticast",
context.TestAddr,
multicastAddr,
name: "DestOurMulticast",
srcAddr: context.TestAddr,
dstAddr: multicastAddr,
},
{
"DestOtherMulticast",
context.TestAddr,
otherMulticastAddr,
name: "DestOtherMulticast",
srcAddr: context.TestAddr,
dstAddr: otherMulticastAddr,
},
{
name: "SrcSubnetBroadcast",
srcAddr: subnetBroadcastAddr,
dstAddr: context.StackAddr,
},
{
name: "DestSubnetBroadcast",
srcAddr: context.TestAddr,
dstAddr: subnetBroadcastAddr,
},
}
for _, test := range tests {
test := test // capture range variable
t.Run(test.name, func(t *testing.T) {
t.Parallel()
c := context.New(t, defaultMTU)
defer c.Cleanup()
@ -5367,11 +5375,7 @@ func TestListenNoAcceptNonUnicastV6(t *testing.T) {
}
for _, test := range tests {
test := test // capture range variable
t.Run(test.name, func(t *testing.T) {
t.Parallel()
c := context.New(t, defaultMTU)
defer c.Cleanup()

View File

@ -53,11 +53,11 @@ const (
TestPort = 4096
// StackV6Addr is the IPv6 address assigned to the stack.
StackV6Addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
StackV6Addr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
// TestV6Addr is the source address for packets sent to the stack via
// the link layer endpoint.
TestV6Addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
TestV6Addr = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
// StackV4MappedAddr is StackAddr as a mapped v6 address.
StackV4MappedAddr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" + StackAddr
@ -73,6 +73,18 @@ const (
testInitialSequenceNumber = 789
)
// StackAddrWithPrefix is StackAddr with its associated prefix length.
var StackAddrWithPrefix = tcpip.AddressWithPrefix{
Address: StackAddr,
PrefixLen: 24,
}
// StackV6AddrWithPrefix is StackV6Addr with its associated prefix length.
var StackV6AddrWithPrefix = tcpip.AddressWithPrefix{
Address: StackV6Addr,
PrefixLen: header.IIDOffsetInIPv6Address * 8,
}
// Headers is used to represent the TCP header fields when building a
// new packet.
type Headers struct {
@ -184,12 +196,20 @@ func New(t *testing.T, mtu uint32) *Context {
t.Fatalf("CreateNICWithOptions(_, _, %+v) failed: %v", opts2, err)
}
if err := s.AddAddress(1, ipv4.ProtocolNumber, StackAddr); err != nil {
t.Fatalf("AddAddress failed: %v", err)
v4ProtocolAddr := tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: StackAddrWithPrefix,
}
if err := s.AddProtocolAddress(1, v4ProtocolAddr); err != nil {
t.Fatalf("AddProtocolAddress(1, %#v): %s", v4ProtocolAddr, err)
}
if err := s.AddAddress(1, ipv6.ProtocolNumber, StackV6Addr); err != nil {
t.Fatalf("AddAddress failed: %v", err)
v6ProtocolAddr := tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: StackV6AddrWithPrefix,
}
if err := s.AddProtocolAddress(1, v6ProtocolAddr); err != nil {
t.Fatalf("AddProtocolAddress(1, %#v): %s", v6ProtocolAddr, err)
}
s.SetRouteTable([]tcpip.Route{

View File

@ -2430,6 +2430,7 @@ cc_library(
":socket_netlink_route_util",
":socket_test_util",
"//test/util:capability_util",
"//test/util:cleanup",
gtest,
],
alwayslink = 1,

View File

@ -15,10 +15,12 @@
#include "test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h"
#include <arpa/inet.h>
#include <poll.h>
#include "gtest/gtest.h"
#include "test/syscalls/linux/socket_netlink_route_util.h"
#include "test/util/capability_util.h"
#include "test/util/cleanup.h"
namespace gvisor {
namespace testing {
@ -33,9 +35,23 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) {
// Add an IP address to the loopback interface.
Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
struct in_addr addr;
EXPECT_EQ(1, inet_pton(AF_INET, "192.0.2.1", &addr));
EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET,
ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.1", &addr));
ASSERT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET,
/*prefixlen=*/24, &addr, sizeof(addr)));
Cleanup defer_addr_removal = Cleanup(
[loopback_link = std::move(loopback_link), addr = std::move(addr)] {
if (IsRunningOnGvisor()) {
// TODO(gvisor.dev/issue/3921): Remove this once deleting addresses
// via netlink is supported.
EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET,
/*prefixlen=*/24, &addr, sizeof(addr)),
PosixErrorIs(EOPNOTSUPP, ::testing::_));
} else {
EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
/*prefixlen=*/24, &addr,
sizeof(addr)));
}
});
auto snd_sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto rcv_sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
@ -45,10 +61,10 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) {
TestAddress sender_addr("V4NotAssignd1");
sender_addr.addr.ss_family = AF_INET;
sender_addr.addr_len = sizeof(sockaddr_in);
EXPECT_EQ(1, inet_pton(AF_INET, "192.0.2.2",
ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.2",
&(reinterpret_cast<sockaddr_in*>(&sender_addr.addr)
->sin_addr.s_addr)));
EXPECT_THAT(
ASSERT_THAT(
bind(snd_sock->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
sender_addr.addr_len),
SyscallSucceeds());
@ -58,10 +74,10 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) {
TestAddress receiver_addr("V4NotAssigned2");
receiver_addr.addr.ss_family = AF_INET;
receiver_addr.addr_len = sizeof(sockaddr_in);
EXPECT_EQ(1, inet_pton(AF_INET, "192.0.2.254",
ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.254",
&(reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)
->sin_addr.s_addr)));
EXPECT_THAT(
ASSERT_THAT(
bind(rcv_sock->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
receiver_addr.addr_len),
SyscallSucceeds());
@ -70,10 +86,10 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) {
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
&receiver_addr_len),
SyscallSucceeds());
EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
ASSERT_EQ(receiver_addr_len, receiver_addr.addr_len);
char send_buf[kSendBufSize];
RandomizeBuffer(send_buf, kSendBufSize);
EXPECT_THAT(
ASSERT_THAT(
RetryEINTR(sendto)(snd_sock->get(), send_buf, kSendBufSize, 0,
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
receiver_addr.addr_len),
@ -83,7 +99,126 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) {
char recv_buf[kSendBufSize] = {};
ASSERT_THAT(RetryEINTR(recv)(rcv_sock->get(), recv_buf, kSendBufSize, 0),
SyscallSucceedsWithValue(kSendBufSize));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, kSendBufSize));
ASSERT_EQ(0, memcmp(send_buf, recv_buf, kSendBufSize));
}
// Tests that broadcast packets are delivered to all interested sockets
// (wildcard and broadcast address specified sockets).
//
// Note, we cannot test the IPv4 Broadcast (255.255.255.255) because we do
// not have a route to it.
TEST_P(IPv4UDPUnboundSocketNetlinkTest, ReuseAddrSubnetDirectedBroadcast) {
constexpr uint16_t kPort = 9876;
// Wait up to 20 seconds for the data.
constexpr int kPollTimeoutMs = 20000;
// Number of sockets per socket type.
constexpr int kNumSocketsPerType = 2;
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
// Add an IP address to the loopback interface.
Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
struct in_addr addr;
ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.1", &addr));
ASSERT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET,
24 /* prefixlen */, &addr, sizeof(addr)));
Cleanup defer_addr_removal = Cleanup(
[loopback_link = std::move(loopback_link), addr = std::move(addr)] {
if (IsRunningOnGvisor()) {
// TODO(gvisor.dev/issue/3921): Remove this once deleting addresses
// via netlink is supported.
EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET,
/*prefixlen=*/24, &addr, sizeof(addr)),
PosixErrorIs(EOPNOTSUPP, ::testing::_));
} else {
EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
/*prefixlen=*/24, &addr,
sizeof(addr)));
}
});
TestAddress broadcast_address("SubnetBroadcastAddress");
broadcast_address.addr.ss_family = AF_INET;
broadcast_address.addr_len = sizeof(sockaddr_in);
auto broadcast_address_in =
reinterpret_cast<sockaddr_in*>(&broadcast_address.addr);
ASSERT_EQ(1, inet_pton(AF_INET, "192.0.2.255",
&broadcast_address_in->sin_addr.s_addr));
broadcast_address_in->sin_port = htons(kPort);
TestAddress any_address = V4Any();
reinterpret_cast<sockaddr_in*>(&any_address.addr)->sin_port = htons(kPort);
// We create sockets bound to both the wildcard address and the broadcast
// address to make sure both of these types of "broadcast interested" sockets
// receive broadcast packets.
std::vector<std::unique_ptr<FileDescriptor>> socks;
for (bool bind_wildcard : {false, true}) {
// Create multiple sockets for each type of "broadcast interested"
// socket so we can test that all sockets receive the broadcast packet.
for (int i = 0; i < kNumSocketsPerType; i++) {
auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto idx = socks.size();
ASSERT_THAT(setsockopt(sock->get(), SOL_SOCKET, SO_REUSEADDR, &kSockOptOn,
sizeof(kSockOptOn)),
SyscallSucceedsWithValue(0))
<< "socks[" << idx << "]";
ASSERT_THAT(setsockopt(sock->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
sizeof(kSockOptOn)),
SyscallSucceedsWithValue(0))
<< "socks[" << idx << "]";
if (bind_wildcard) {
ASSERT_THAT(
bind(sock->get(), reinterpret_cast<sockaddr*>(&any_address.addr),
any_address.addr_len),
SyscallSucceeds())
<< "socks[" << idx << "]";
} else {
ASSERT_THAT(bind(sock->get(),
reinterpret_cast<sockaddr*>(&broadcast_address.addr),
broadcast_address.addr_len),
SyscallSucceeds())
<< "socks[" << idx << "]";
}
socks.push_back(std::move(sock));
}
}
char send_buf[kSendBufSize];
RandomizeBuffer(send_buf, kSendBufSize);
// Broadcasts from each socket should be received by every socket (including
// the sending socket).
for (int w = 0; w < socks.size(); w++) {
auto& w_sock = socks[w];
ASSERT_THAT(
RetryEINTR(sendto)(w_sock->get(), send_buf, kSendBufSize, 0,
reinterpret_cast<sockaddr*>(&broadcast_address.addr),
broadcast_address.addr_len),
SyscallSucceedsWithValue(kSendBufSize))
<< "write socks[" << w << "]";
// Check that we received the packet on all sockets.
for (int r = 0; r < socks.size(); r++) {
auto& r_sock = socks[r];
struct pollfd poll_fd = {r_sock->get(), POLLIN, 0};
EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, kPollTimeoutMs),
SyscallSucceedsWithValue(1))
<< "write socks[" << w << "] & read socks[" << r << "]";
char recv_buf[kSendBufSize] = {};
EXPECT_THAT(RetryEINTR(recv)(r_sock->get(), recv_buf, kSendBufSize, 0),
SyscallSucceedsWithValue(kSendBufSize))
<< "write socks[" << w << "] & read socks[" << r << "]";
EXPECT_EQ(0, memcmp(send_buf, recv_buf, kSendBufSize))
<< "write socks[" << w << "] & read socks[" << r << "]";
}
}
}
} // namespace testing

View File

@ -26,6 +26,62 @@ namespace {
constexpr uint32_t kSeq = 12345;
// Types of address modifications that may be performed on an interface.
enum class LinkAddrModification {
kAdd,
kDelete,
};
// Populates |hdr| with appripriate values for the modification type.
PosixError PopulateNlmsghdr(LinkAddrModification modification,
struct nlmsghdr* hdr) {
switch (modification) {
case LinkAddrModification::kAdd:
hdr->nlmsg_type = RTM_NEWADDR;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
return NoError();
case LinkAddrModification::kDelete:
hdr->nlmsg_type = RTM_DELADDR;
hdr->nlmsg_flags = NLM_F_REQUEST;
return NoError();
}
return PosixError(EINVAL);
}
// Adds or removes the specified address from the specified interface.
PosixError LinkModifyLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen,
LinkAddrModification modification) {
ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE));
struct request {
struct nlmsghdr hdr;
struct ifaddrmsg ifaddr;
char attrbuf[512];
};
struct request req = {};
PosixError err = PopulateNlmsghdr(modification, &req.hdr);
if (!err.ok()) {
return err;
}
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifaddr));
req.hdr.nlmsg_seq = kSeq;
req.ifaddr.ifa_index = index;
req.ifaddr.ifa_family = family;
req.ifaddr.ifa_prefixlen = prefixlen;
struct rtattr* rta = reinterpret_cast<struct rtattr*>(
reinterpret_cast<int8_t*>(&req) + NLMSG_ALIGN(req.hdr.nlmsg_len));
rta->rta_type = IFA_LOCAL;
rta->rta_len = RTA_LENGTH(addrlen);
req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(addrlen);
memcpy(RTA_DATA(rta), addr, addrlen);
return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len);
}
} // namespace
PosixError DumpLinks(
@ -84,31 +140,14 @@ PosixErrorOr<Link> LoopbackLink() {
PosixError LinkAddLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen) {
ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE));
return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen,
LinkAddrModification::kAdd);
}
struct request {
struct nlmsghdr hdr;
struct ifaddrmsg ifaddr;
char attrbuf[512];
};
struct request req = {};
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifaddr));
req.hdr.nlmsg_type = RTM_NEWADDR;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req.hdr.nlmsg_seq = kSeq;
req.ifaddr.ifa_index = index;
req.ifaddr.ifa_family = family;
req.ifaddr.ifa_prefixlen = prefixlen;
struct rtattr* rta = reinterpret_cast<struct rtattr*>(
reinterpret_cast<int8_t*>(&req) + NLMSG_ALIGN(req.hdr.nlmsg_len));
rta->rta_type = IFA_LOCAL;
rta->rta_len = RTA_LENGTH(addrlen);
req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(addrlen);
memcpy(RTA_DATA(rta), addr, addrlen);
return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len);
PosixError LinkDelLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen) {
return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen,
LinkAddrModification::kDelete);
}
PosixError LinkChangeFlags(int index, unsigned int flags, unsigned int change) {

View File

@ -43,6 +43,10 @@ PosixErrorOr<Link> LoopbackLink();
PosixError LinkAddLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen);
// LinkDelLocalAddr removes IFA_LOCAL attribute on the interface.
PosixError LinkDelLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen);
// LinkChangeFlags changes interface flags. E.g. IFF_UP.
PosixError LinkChangeFlags(int index, unsigned int flags, unsigned int change);