Sfoglia il codice sorgente

Kernel: Properly support the SO_BROADCAST socket option

POSIX requires that broadcast sends will only be allowed if the
SO_BROADCAST socket option was set on the socket.
Also, broadcast sends to protocols that do not support broadcast (like
TCP), should always fail.
Idan Horowitz 1 anno fa
parent
commit
545f4b6cc1

+ 2 - 1
Kernel/Net/IPv4Socket.cpp

@@ -211,9 +211,10 @@ ErrorOr<size_t> IPv4Socket::sendto(OpenFileDescription&, UserOrKernelBuffer cons
     if (!is_connected() && m_peer_address.is_zero())
         return set_so_error(EPIPE);
 
+    auto allow_broadcast = m_broadcast_allowed ? AllowBroadcast::Yes : AllowBroadcast::No;
     auto allow_using_gateway = ((flags & MSG_DONTROUTE) || m_routing_disabled) ? AllowUsingGateway::No : AllowUsingGateway::Yes;
     auto adapter = bound_interface().with([](auto& bound_device) -> RefPtr<NetworkAdapter> { return bound_device; });
-    auto routing_decision = route_to(m_peer_address, m_local_address, adapter, allow_using_gateway);
+    auto routing_decision = route_to(m_peer_address, m_local_address, adapter, allow_broadcast, allow_using_gateway);
     if (routing_decision.is_zero())
         return set_so_error(EHOSTUNREACH);
 

+ 6 - 3
Kernel/Net/Routing.cpp

@@ -178,7 +178,7 @@ static MACAddress multicast_ethernet_address(IPv4Address const& address)
     return MACAddress { 0x01, 0x00, 0x5e, (u8)(address[1] & 0x7f), address[2], address[3] };
 }
 
-RoutingDecision route_to(IPv4Address const& target, IPv4Address const& source, RefPtr<NetworkAdapter> const through, AllowUsingGateway allow_using_gateway)
+RoutingDecision route_to(IPv4Address const& target, IPv4Address const& source, RefPtr<NetworkAdapter> const through, AllowBroadcast allow_broadcast, AllowUsingGateway allow_using_gateway)
 {
     auto matches = [&](auto& adapter) {
         if (!through)
@@ -291,8 +291,11 @@ RoutingDecision route_to(IPv4Address const& target, IPv4Address const& source, R
     // If it's a broadcast, we already know everything we need to know.
     // FIXME: We should also deal with the case where `target_addr` is
     //        a broadcast to a subnet rather than a full broadcast.
-    if (target_addr == 0xffffffff && matches(adapter))
-        return { adapter, { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } };
+    if (target_addr == 0xffffffff && matches(adapter)) {
+        if (allow_broadcast == AllowBroadcast::Yes)
+            return { adapter, { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } };
+        return { nullptr, {} };
+    }
 
     if (adapter == NetworkingManagement::the().loopback_adapter())
         return { adapter, adapter->mac_address() };

+ 6 - 1
Kernel/Net/Routing.h

@@ -64,7 +64,12 @@ enum class AllowUsingGateway {
     No,
 };
 
-RoutingDecision route_to(IPv4Address const& target, IPv4Address const& source, RefPtr<NetworkAdapter> const through = nullptr, AllowUsingGateway = AllowUsingGateway::Yes);
+enum class AllowBroadcast {
+    Yes,
+    No,
+};
+
+RoutingDecision route_to(IPv4Address const& target, IPv4Address const& source, RefPtr<NetworkAdapter> const through = nullptr, AllowBroadcast = AllowBroadcast::No, AllowUsingGateway = AllowUsingGateway::Yes);
 
 SpinlockProtected<HashMap<IPv4Address, MACAddress>, LockRank::None>& arp_table();
 SpinlockProtected<Route::RouteList, LockRank::None>& routing_table();

+ 14 - 0
Kernel/Net/Socket.cpp

@@ -130,6 +130,12 @@ ErrorOr<void> Socket::setsockopt(int level, int option, Userspace<void const*> u
     case SO_REUSEADDR:
         dbgln("FIXME: SO_REUSEADDR requested, but not implemented.");
         return {};
+    case SO_BROADCAST: {
+        if (user_value_size != sizeof(int))
+            return EINVAL;
+        m_broadcast_allowed = TRY(copy_typed_from_user(static_ptr_cast<int const*>(user_value))) != 0;
+        return {};
+    }
     default:
         dbgln("setsockopt({}) at SOL_SOCKET not implemented.", option);
         return ENOPROTOOPT;
@@ -235,6 +241,14 @@ ErrorOr<void> Socket::getsockopt(OpenFileDescription&, int level, int option, Us
         size = sizeof(routing_disabled);
         return copy_to_user(value_size, &size);
     }
+    case SO_BROADCAST: {
+        int broadcast_allowed = m_broadcast_allowed ? 1 : 0;
+        if (size < sizeof(broadcast_allowed))
+            return EINVAL;
+        TRY(copy_to_user(static_ptr_cast<int*>(value), &broadcast_allowed));
+        size = sizeof(broadcast_allowed);
+        return copy_to_user(value_size, &size);
+    }
     default:
         dbgln("getsockopt({}) at SOL_SOCKET not implemented.", option);
         return ENOPROTOOPT;

+ 1 - 0
Kernel/Net/Socket.h

@@ -156,6 +156,7 @@ protected:
     ucred m_origin { 0, 0, 0 };
     ucred m_acceptor { 0, 0, 0 };
     bool m_routing_disabled { false };
+    bool m_broadcast_allowed { false };
 
 private:
     virtual bool is_socket() const final { return true; }

+ 2 - 1
Kernel/Net/UDPSocket.cpp

@@ -85,7 +85,8 @@ ErrorOr<size_t> UDPSocket::protocol_receive(ReadonlyBytes raw_ipv4_packet, UserO
 ErrorOr<size_t> UDPSocket::protocol_send(UserOrKernelBuffer const& data, size_t data_length)
 {
     auto adapter = bound_interface().with([](auto& bound_device) -> RefPtr<NetworkAdapter> { return bound_device; });
-    auto routing_decision = route_to(peer_address(), local_address(), adapter);
+    auto allow_broadcast = m_broadcast_allowed ? AllowBroadcast::Yes : AllowBroadcast::No;
+    auto routing_decision = route_to(peer_address(), local_address(), adapter, allow_broadcast);
     if (routing_decision.is_zero())
         return set_so_error(EHOSTUNREACH);
     auto ipv4_payload_offset = routing_decision.adapter->ipv4_payload_offset();

+ 5 - 0
Userland/Services/DHCPClient/DHCPv4Client.cpp

@@ -49,6 +49,11 @@ static bool send(InterfaceDescriptor const& iface, DHCPv4Packet const& packet, C
         dbgln("ERROR: setsockopt(SO_BINDTODEVICE) :: {}", strerror(errno));
         return false;
     }
+    int allow_broadcast = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &allow_broadcast, sizeof(int)) < 0) {
+        dbgln("ERROR: setsockopt(SO_BROADCAST) :: {}", strerror(errno));
+        return false;
+    }
 
     sockaddr_in dst;
     memset(&dst, 0, sizeof(dst));