1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-24 22:17:42 +00:00

Kernel: Add the SO_BINDTODEVICE socket option

This patch adds a way for a socket to ask to be routed through a
specific interface.
Currently, this option only applies to sending, however, it should also
apply to receiving...somehow :^)
This commit is contained in:
AnotherTest 2020-04-05 01:16:45 +04:30 committed by Andreas Kling
parent 7d0bf9b5a9
commit 77191d82dc
10 changed files with 186 additions and 13 deletions

View file

@ -210,7 +210,7 @@ ssize_t IPv4Socket::sendto(FileDescription&, const void* data, size_t data_lengt
m_peer_port = ntohs(ia.sin_port);
}
auto routing_decision = route_to(m_peer_address, m_local_address);
auto routing_decision = route_to(m_peer_address, m_local_address, bound_interface());
if (routing_decision.is_zero())
return -EHOSTUNREACH;

View file

@ -46,10 +46,22 @@ bool RoutingDecision::is_zero() const
return adapter.is_null() || next_hop.is_zero();
}
RoutingDecision route_to(const IPv4Address& target, const IPv4Address& source)
RoutingDecision route_to(const IPv4Address& target, const IPv4Address& source, const RefPtr<NetworkAdapter> through)
{
auto matches = [&](auto& adapter) {
if (!through)
return true;
return through == adapter;
};
auto if_matches = [&](auto& adapter, const auto& mac) -> RoutingDecision {
if (!matches(adapter))
return { nullptr, {} };
return { adapter, mac };
};
if (target[0] == 127)
return { LoopbackAdapter::the(), LoopbackAdapter::the().mac_address() };
return if_matches(LoopbackAdapter::the(), LoopbackAdapter::the().mac_address());
auto target_addr = target.to_u32();
auto source_addr = source.to_u32();
@ -57,17 +69,17 @@ RoutingDecision route_to(const IPv4Address& target, const IPv4Address& source)
RefPtr<NetworkAdapter> local_adapter = nullptr;
RefPtr<NetworkAdapter> gateway_adapter = nullptr;
NetworkAdapter::for_each([source_addr, &target_addr, &local_adapter, &gateway_adapter](auto& adapter) {
NetworkAdapter::for_each([source_addr, &target_addr, &local_adapter, &gateway_adapter, &matches](auto& adapter) {
auto adapter_addr = adapter.ipv4_address().to_u32();
auto adapter_mask = adapter.ipv4_netmask().to_u32();
if (source_addr != 0 && source_addr != adapter_addr)
return;
if ((target_addr & adapter_mask) == (adapter_addr & adapter_mask))
if ((target_addr & adapter_mask) == (adapter_addr & adapter_mask) && matches(adapter))
local_adapter = adapter;
if (adapter.ipv4_gateway().to_u32() != 0)
if (adapter.ipv4_gateway().to_u32() != 0 && matches(adapter))
gateway_adapter = adapter;
});

View file

@ -37,7 +37,7 @@ struct RoutingDecision {
bool is_zero() const;
};
RoutingDecision route_to(const IPv4Address& target, const IPv4Address& source);
RoutingDecision route_to(const IPv4Address& target, const IPv4Address& source, const RefPtr<NetworkAdapter> through = nullptr);
Lockable<HashMap<IPv4Address, MACAddress>>& arp_table();

View file

@ -25,6 +25,7 @@
*/
#include <AK/StringBuilder.h>
#include <AK/StringView.h>
#include <Kernel/FileSystem/FileDescription.h>
#include <Kernel/Net/IPv4Socket.h>
#include <Kernel/Net/LocalSocket.h>
@ -114,6 +115,16 @@ KResult Socket::setsockopt(int level, int option, const void* value, socklen_t v
return KResult(-EINVAL);
m_receive_timeout = *(const timeval*)value;
return KSuccess;
case SO_BINDTODEVICE: {
if (value_size != IFNAMSIZ)
return KResult(-EINVAL);
StringView ifname { (const char*)value };
auto device = NetworkAdapter::lookup_by_name(ifname);
if (!device)
return KResult(-ENODEV);
m_bound_interface = device;
return KSuccess;
}
default:
dbg() << "setsockopt(" << option << ") at SOL_SOCKET not implemented.";
return KResult(-ENOPROTOOPT);
@ -143,6 +154,19 @@ KResult Socket::getsockopt(FileDescription&, int level, int option, void* value,
*(int*)value = 0;
*value_size = sizeof(int);
return KSuccess;
case SO_BINDTODEVICE:
if (*value_size < IFNAMSIZ)
return KResult(-EINVAL);
if (m_bound_interface) {
const auto& name = m_bound_interface->name();
auto length = name.length() + 1;
memcpy(value, name.characters(), length);
*value_size = length;
return KSuccess;
} else {
*value_size = 0;
return KResult(-EFAULT);
}
default:
dbg() << "getsockopt(" << option << ") at SOL_SOCKET not implemented.";
return KResult(-ENOPROTOOPT);

View file

@ -33,6 +33,7 @@
#include <Kernel/FileSystem/File.h>
#include <Kernel/KResult.h>
#include <Kernel/Lock.h>
#include <Kernel/Net/NetworkAdapter.h>
#include <Kernel/UnixTypes.h>
namespace Kernel {
@ -118,6 +119,7 @@ public:
pid_t acceptor_pid() const { return m_acceptor.pid; }
uid_t acceptor_uid() const { return m_acceptor.uid; }
gid_t acceptor_gid() const { return m_acceptor.gid; }
const RefPtr<NetworkAdapter> bound_interface() const { return m_bound_interface; }
Lock& lock() { return m_lock; }
@ -165,6 +167,8 @@ private:
bool m_shut_down_for_reading { false };
bool m_shut_down_for_writing { false };
RefPtr<NetworkAdapter> m_bound_interface { nullptr };
timeval m_receive_timeout { 0, 0 };
timeval m_send_timeout { 0, 0 };

View file

@ -212,7 +212,7 @@ void TCPSocket::send_tcp_packet(u16 flags, const void* payload, size_t payload_s
return;
}
auto routing_decision = route_to(peer_address(), local_address());
auto routing_decision = route_to(peer_address(), local_address(), bound_interface());
ASSERT(!routing_decision.is_zero());
routing_decision.adapter->send_ipv4(
@ -225,7 +225,7 @@ void TCPSocket::send_tcp_packet(u16 flags, const void* payload, size_t payload_s
void TCPSocket::send_outgoing_packets()
{
auto routing_decision = route_to(peer_address(), local_address());
auto routing_decision = route_to(peer_address(), local_address(), bound_interface());
ASSERT(!routing_decision.is_zero());
auto now = kgettimeofday();

View file

@ -92,7 +92,7 @@ int UDPSocket::protocol_receive(const KBuffer& packet_buffer, void* buffer, size
int UDPSocket::protocol_send(const void* data, size_t data_length)
{
auto routing_decision = route_to(peer_address(), local_address());
auto routing_decision = route_to(peer_address(), local_address(), bound_interface());
if (routing_decision.is_zero())
return -EHOSTUNREACH;
auto buffer = ByteBuffer::create_zeroed(sizeof(UDPPacket) + data_length);

View file

@ -401,6 +401,7 @@ struct pollfd {
#define SO_ERROR 4
#define SO_PEERCRED 5
#define SO_REUSEADDR 6
#define SO_BINDTODEVICE 7
#define IPPROTO_IP 0
#define IPPROTO_ICMP 1

View file

@ -80,6 +80,7 @@ struct ucred {
#define SO_ERROR 4
#define SO_PEERCRED 5
#define SO_REUSEADDR 6
#define SO_BINDTODEVICE 7
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr* addr, socklen_t);

View file

@ -0,0 +1,131 @@
#include <AK/Function.h>
#include <AK/IPv4Address.h>
#include <net/if.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
void test_invalid(int);
void test_no_route(int);
void test_valid(int);
void test_send(int);
void test(AK::Function<void(int)> test_fn)
{
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (fd < 0) {
perror("socket");
return;
}
test_fn(fd);
// be a responsible boi
close(fd);
}
auto main() -> int
{
test(test_invalid);
test(test_valid);
test(test_no_route);
test(test_send);
}
void test_invalid(int fd)
{
// bind to an interface that does not exist
char buf[IFNAMSIZ];
socklen_t buflen = IFNAMSIZ;
memcpy(buf, "foodev", 7);
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) {
perror("setsockopt(SO_BINDTODEVICE) :: invalid (Should fail with ENODEV)");
puts("PASS invalid");
} else {
puts("FAIL invalid");
}
}
void test_valid(int fd)
{
// bind to an interface that exists
char buf[IFNAMSIZ];
socklen_t buflen = IFNAMSIZ;
memcpy(buf, "loop0", 6);
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) {
perror("setsockopt(SO_BINDTODEVICE) :: valid");
puts("FAIL valid");
} else {
puts("PASS valid");
}
}
void test_no_route(int fd)
{
// bind to an interface that cannot deliver
char buf[IFNAMSIZ];
socklen_t buflen = IFNAMSIZ;
memcpy(buf, "loop0", 6);
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) {
perror("setsockopt(SO_BINDTODEVICE) :: no_route");
puts("FAIL no_route");
return;
}
sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_addr.s_addr = IPv4Address { 10, 0, 2, 15 }.to_u32();
sin.sin_port = 8080;
sin.sin_family = AF_INET;
if (bind(fd, (sockaddr*)&sin, sizeof(sin)) < 0) {
perror("bind() :: no_route");
puts("FAIL no_route");
return;
}
if (sendto(fd, "TEST", 4, 0, (sockaddr*)&sin, sizeof(sin)) < 0) {
perror("sendto() :: no_route (Should fail with EHOSTUNREACH)");
puts("PASS no_route");
} else
puts("FAIL no_route");
}
void test_send(int fd)
{
// bind to an interface that cannot deliver
char buf[IFNAMSIZ];
socklen_t buflen = IFNAMSIZ;
memcpy(buf, "e1k0", 5);
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) {
perror("setsockopt(SO_BINDTODEVICE) :: send");
puts("FAIL send");
return;
}
sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_addr.s_addr = IPv4Address { 10, 0, 2, 15 }.to_u32();
sin.sin_port = 8080;
sin.sin_family = AF_INET;
if (bind(fd, (sockaddr*)&sin, sizeof(sin)) < 0) {
perror("bind() :: send");
puts("FAIL send");
return;
}
if (sendto(fd, "TEST", 4, 0, (sockaddr*)&sin, sizeof(sin)) < 0) {
perror("sendto() :: send");
puts("FAIL send");
return;
}
puts("PASS send");
}