Kernel+ifconfig: Allow setting an IPv6 address on an interface

Since we take a socket address with an address
type, this allows us to support setting an IPv6
address using an IPv4 socket without a
particularly hacky API. This deviates from Linux's
behavior (see
https://www.man7.org/linux/man-pages/man7/netdevice.7.html
) where AF_INET6 uses a completely different
control structure, but this doesn't seem
necessary.

This requires changing the sockaddr size to fit
sockaddr_in6, as the network ioctl's are the only
place where sockaddr is used with a fixed size
(and not with variable size data like in POSIX
APIs, which would support sockaddr_in6 without
changes).

ifconfig takes a new parameter for setting the
IPv6 address of an interface. The IPv4 address
short option '-i' is removed, as it only yields
confusion (which IP version is the default? and
usually such options are called -4 or -6, if they
exist) and isn't necessary thanks to the brief
long option name.

This commit's main purpose for now is to allow
participating in IPv6 NDP and pings without
requiring SLAC.

Co-authored-by: Dominique Liberda <ja@sdomi.pl>
This commit is contained in:
kleines Filmröllchen
2024-08-25 23:46:46 +02:00
committed by Nico Weber
parent 81eae154b7
commit ac44ec5ebc
3 changed files with 40 additions and 6 deletions

View File

@@ -109,7 +109,8 @@ static inline void* CMSG_DATA(struct cmsghdr* cmsg)
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
// For network interface ioctl(), this needs to fit all sockaddr_* structures (excluding Unix domain sockets).
char sa_data[26];
};
struct ucred {

View File

@@ -755,17 +755,22 @@ ErrorOr<void> IPv4Socket::ioctl(OpenFileDescription&, unsigned request, Userspac
case SIOCSIFADDR:
if (!current_process_credentials->is_superuser())
return EPERM;
if (ifr.ifr_addr.sa_family != AF_INET)
return EAFNOSUPPORT;
adapter->set_ipv4_address(IPv4Address(((sockaddr_in&)ifr.ifr_addr).sin_addr.s_addr));
if (ifr.ifr_addr.sa_family == AF_INET) {
adapter->set_ipv4_address(IPv4Address(bit_cast<sockaddr_in*>(&ifr.ifr_addr)->sin_addr.s_addr));
return {};
} else if (ifr.ifr_addr.sa_family == AF_INET6) {
adapter->set_ipv6_address(IPv6Address(bit_cast<sockaddr_in6*>(&ifr.ifr_addr)->sin6_addr.s6_addr));
return {};
} else {
return EAFNOSUPPORT;
}
case SIOCSIFNETMASK:
if (!current_process_credentials->is_superuser())
return EPERM;
if (ifr.ifr_addr.sa_family != AF_INET)
return EAFNOSUPPORT;
adapter->set_ipv4_netmask(IPv4Address(((sockaddr_in&)ifr.ifr_netmask).sin_addr.s_addr));
adapter->set_ipv4_netmask(IPv4Address(bit_cast<sockaddr_in*>(&ifr.ifr_netmask)->sin_addr.s_addr));
return {};
case SIOCGIFADDR: {

View File

@@ -7,6 +7,7 @@
#include <AK/Assertions.h>
#include <AK/IPv4Address.h>
#include <AK/IPv6Address.h>
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/NumberFormat.h>
@@ -20,12 +21,14 @@
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
StringView value_ipv4 {};
StringView value_ipv6 {};
StringView value_adapter {};
StringView value_mask {};
Core::ArgsParser args_parser;
args_parser.set_general_help("Display or modify the configuration of each network interface.");
args_parser.add_option(value_ipv4, "Set the IP address of the selected network", "ipv4", 'i', "ip");
args_parser.add_option(value_ipv4, "Set the IPv4 address of the selected network", "ipv4", 0, "ipv4");
args_parser.add_option(value_ipv6, "Set the IPv6 address of the selected network", "ipv6", 0, "ipv6");
args_parser.add_option(value_adapter, "Select a specific network adapter to configure", "adapter", 'a', "adapter");
args_parser.add_option(value_mask, "Set the network mask of the selected network", "mask", 'm', "mask");
args_parser.parse(arguments);
@@ -92,6 +95,31 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
TRY(Core::System::ioctl(fd, SIOCSIFADDR, &ifr));
}
if (!value_ipv6.is_empty()) {
auto address = IPv6Address::from_string(value_ipv6);
if (!address.has_value()) {
warnln("Invalid IPv6 address: '{}'", value_ipv6);
return 1;
}
// FIXME: should be an AF_INET6 socket (once we support it), but the Kernel doesn't care either way.
auto fd = TRY(Core::System::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP));
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
bool fits = ifname.copy_characters_to_buffer(ifr.ifr_name, IFNAMSIZ);
if (!fits) {
warnln("Interface name '{}' is too long", ifname);
return 1;
}
ifr.ifr_addr.sa_family = AF_INET6;
AK::TypedTransfer<u8>::copy(((sockaddr_in6&)ifr.ifr_addr).sin6_addr.s6_addr, address.value().to_in6_addr_t(), sizeof(in6_addr));
TRY(Core::System::ioctl(fd, SIOCSIFADDR, &ifr));
}
if (!value_mask.is_empty()) {
auto address = IPv4Address::from_string(value_mask);