On Thu, Feb 22, 2024 at 09:16:24AM +0100, Lucas Bonnet wrote: > > Hello dear Debian maintainers, > > I can confirm that the 2020 patch from Carsten Schoenert still applies > to d-i/netcfg releases 1.187 and 1.188, and result in a debian installer > that knows about VLANs and can be fully automated with a preseed config > like the following: > > d-i netcfg/enable boolean true > d-i netcfg/use_vlan boolean true > d-i netcfg/vlan_id string 1001 > d-i netcfg/choose_interface select ens3f0np0 > > > We would be interested in helping push the patch upstream so that other > users can enjoy this without having to build and make their own initrd, > how can we help? > > > Regards, > -- > Lucas Bonnet > Bearstech - http://bearstech.com >
Sorry, I have to disagree with you here -- the patch certainly does not apply cleanly anymore. Even after fixing it up, I faced installation issues as it attempts to add the VLAN subinterface twice, and fails the second time due to it already existing. The VLAN code does not use ioctls/netlink and instead shells out to ip link/ ifconfig for Linux and FreeBSD respectively. I took a quick look at what it would take to shift this code over to ioctls, but it seems there are inherent differences in the ioctl interface between the two OS-es that would make it non-trivial to support both at that level. I would be curious to hear the maintainers feedback as to whether or not this is acceptable. I'm attaching a patch that applies cleanly against 1.188 and was tested against a patched bookworm initrd with success. Cheers, Tyler
>From d7127bafc4520d3675061ec7225966ce204d9495 Mon Sep 17 00:00:00 2001 From: "Tyler J. Stachecki" <stachecki.ty...@gmail.com> Date: Mon, 27 May 2024 13:01:44 -0400 Subject: [PATCH] Add VLAN support to d-i/netcfg --- Makefile | 2 +- debian/netcfg-common.templates | 28 ++++++++ dhcp.c | 2 +- netcfg-common.c | 32 ++++++++- netcfg.c | 17 +++-- netcfg.h | 10 +++ nm-conf.c | 33 ++++++++++ nm-conf.h | 10 ++- static.c | 12 ++-- vlan.c | 114 +++++++++++++++++++++++++++++++++ wireless.c | 4 +- write_interface.c | 18 ++++++ 12 files changed, 262 insertions(+), 20 deletions(-) create mode 100644 vlan.c diff --git a/Makefile b/Makefile index a15d476f..03343c94 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ TARGETS ?= netcfg-static netcfg LDOPTS = -ldebconfclient -ldebian-installer CFLAGS = -W -Wall -Werror -DNDEBUG -DNETCFG_VERSION="\"$(NETCFG_VERSION)\"" -I. -COMMON_OBJS = netcfg-common.o wireless.o write_interface.o ipv6.o +COMMON_OBJS = netcfg-common.o wireless.o write_interface.o ipv6.o vlan.o NETCFG_O = netcfg.o dhcp.o static.o ethtool-lite.o wpa.o wpa_ctrl.o rdnssd.o autoconfig.o NETCFG_STATIC_O = netcfg-static.o static.o ethtool-lite.o diff --git a/debian/netcfg-common.templates b/debian/netcfg-common.templates index 7cba22ec..4f848df5 100644 --- a/debian/netcfg-common.templates +++ b/debian/netcfg-common.templates @@ -26,6 +26,34 @@ _Description: Domain name: If you are setting up a home network, you can make something up, but make sure you use the same domain name on all your computers. +Template: netcfg/use_vlan +Type: boolean +Default: false +# :sl6: +_Description: Are you connected to an IEEE 802.1Q VLAN trunk port? + Virtual LAN (VLAN) is a concept of partitioning a physical network to create + distinct broadcast domains. Packets can be marked for different IDs by + tagging, so that a single interconnect (trunk) may be used to transport + data for various VLANs. + . + If your network interface is directly connected to a VLAN trunk port, + specifying a VLAN ID may be necessary to get a working connection. + +Template: netcfg/vlan_id +Type: string +# :sl6: +_Description: VLAN ID (1-4094): + If your network interface is directly connected to a VLAN trunk port, + specifying a VLAN ID may be necessary to get a working connection. + +Template: netcfg/vlan_failed +Type: error +# :sl6: +_Description: Error setting up VLAN + The command used to set up VLAN during the installation returned an + error, please check installer logs, or go back and try another + configuration. + Template: netcfg/get_nameservers Type: string # :sl1: diff --git a/dhcp.c b/dhcp.c index 0ef37736..bb0369e4 100644 --- a/dhcp.c +++ b/dhcp.c @@ -459,7 +459,7 @@ int netcfg_activate_dhcp (struct debconfclient *client, struct netcfg_interface kill_dhcp_client(); loop_setup(); - interface_up(interface->name); + netcfg_interface_up(interface); for (;;) { di_debug("State is now %i", state); diff --git a/netcfg-common.c b/netcfg-common.c index 11334531..5ca9560e 100644 --- a/netcfg-common.c +++ b/netcfg-common.c @@ -277,7 +277,7 @@ int is_raw_80211(const char *iface) #if defined(__s390__) // Layer 3 qeth on s390(x) cannot do arping to test gateway reachability. -int is_layer3_qeth(const char *iface) +int is_layer3_qeth(const struct netcfg_interface *interface) { const int bufsize = 1024; int retval = 0; @@ -287,6 +287,12 @@ int is_layer3_qeth(const char *iface) ssize_t slen; char* driver; int fd; + char *iface; + + if (interface->parentif) + iface = interface->parentif; + else + iface = interface->name; // This is sufficient for both /driver and /layer2. len = strlen(SYSCLASSNET) + strlen(iface) + strlen("/device/driver") + 1; @@ -339,7 +345,7 @@ out: return retval; } #else -int is_layer3_qeth(const char *iface __attribute__((unused))) +int is_layer3_qeth(const struct netcfg_interface *interface __attribute__((unused))) { return 0; } @@ -1351,6 +1357,24 @@ void interface_down (const char *if_name) } } +void netcfg_interface_up (const struct netcfg_interface *iface) +{ + if (iface->parentif) + interface_up (iface->parentif); + + if (iface->name) + interface_up (iface->name); +} + +void netcfg_interface_down (const struct netcfg_interface *iface) +{ + if (iface->name) + interface_down (iface->name); + + if (iface->parentif) + interface_down (iface->parentif); +} + void parse_args (int argc, char ** argv) { if (argc == 2) { @@ -1541,7 +1565,7 @@ int netcfg_detect_link(struct debconfclient *client, const struct netcfg_interfa if (ethtool_lite(if_name) == 1) /* ethtool-lite's CONNECTED */ { di_info("Found link on %s", if_name); - if (!empty_str(gateway) && !is_wireless_iface(if_name) && !is_layer3_qeth(if_name)) { + if (!empty_str(gateway) && !is_wireless_iface(if_name) && !is_layer3_qeth(interface)) { for (count = 0; count < gw_tries; count++) { if (di_exec_shell_log(arping) == 0) break; @@ -1577,6 +1601,8 @@ void netcfg_interface_init(struct netcfg_interface *iface) iface->v6_stateless_config = -1; iface->loopback = -1; iface->mode = MANAGED; + iface->parentif = NULL; + iface->vlanid = -1; } /* Parse an IP address (v4 or v6), with optional CIDR netmask, into diff --git a/netcfg.c b/netcfg.c index 195681ba..619269bd 100644 --- a/netcfg.c +++ b/netcfg.c @@ -67,6 +67,7 @@ int main(int argc, char *argv[]) GET_METHOD, GET_DHCP, GET_STATIC, + GET_VLAN, WCONFIG, WCONFIG_ESSID, WCONFIG_SECURITY_TYPE, @@ -216,12 +217,16 @@ int main(int argc, char *argv[]) state = BACKUP; else if (! interface.name || ! num_interfaces) state = GET_HOSTNAME_ONLY; - else { - if (is_wireless_iface (interface.name)) - state = WCONFIG; - else - state = GET_METHOD; - } + else if (is_wireless_iface (interface.name)) + state = WCONFIG; + else + state = GET_VLAN; + break; + case GET_VLAN: + if (netcfg_set_vlan(client, &interface) == GO_BACK) + state = BACKUP; + else + state = GET_METHOD; break; case GET_HOSTNAME_ONLY: if(netcfg_get_hostname(client, "netcfg/get_hostname", hostname, 0)) diff --git a/netcfg.h b/netcfg.h index 00a2cea3..03a42929 100644 --- a/netcfg.h +++ b/netcfg.h @@ -151,6 +151,10 @@ struct netcfg_interface { /* WPA */ wpa_t wpa_supplicant_status; char *passphrase; + + /* VLAN */ + char *parentif; + int vlanid; }; /* Somewhere we can store both in_addr and in6_addr; convenient for all those @@ -221,6 +225,9 @@ extern void deconfigure_network(struct netcfg_interface *iface); extern void interface_up (const char *if_name); extern void interface_down (const char *if_name); +extern void netcfg_interface_up (const struct netcfg_interface *iface); +extern void netcfg_interface_down (const struct netcfg_interface *iface); + extern void loop_setup(void); extern int get_hostname_from_dns(const struct netcfg_interface *interface, char *hostname, const size_t max_hostname_len); @@ -270,4 +277,7 @@ extern void cleanup_dhcpv6_client(void); extern int start_dhcpv6_client(struct debconfclient *client, const struct netcfg_interface *interface); extern int netcfg_autoconfig(struct debconfclient *client, struct netcfg_interface *interface); +/* vlan.c */ +extern int netcfg_set_vlan(struct debconfclient *client, struct netcfg_interface *interface); + #endif /* _NETCFG_H_ */ diff --git a/nm-conf.c b/nm-conf.c index aeab0318..e285b303 100644 --- a/nm-conf.c +++ b/nm-conf.c @@ -32,11 +32,28 @@ static void get_uuid(char* target) static void nm_write_connection(FILE *config_file, nm_connection connection) { + static char *type = NM_DEFAULT_WIRED; + if (connection.type == WIFI) { + type = NM_DEFAULT_WIRELESS; + } + if (connection.type == VLAN) { + type = NM_DEFAULT_VLAN; + } fprintf(config_file, "\n%s\n", NM_SETTINGS_CONNECTION); fprintf(config_file, "id=%s\n", connection.id); fprintf(config_file, "uuid=%s\n", connection.uuid); fprintf(config_file, "type=%s\n", (connection.type == WIFI) ? NM_DEFAULT_WIRELESS : NM_DEFAULT_WIRED); + fprintf(config_file, "type=%s\n", type); +} + +static void nm_write_vlan_specific_options(FILE *config_file, + struct nm_config_info *nmconf) +{ + nm_vlan vlan = nmconf->vlan; + fprintf(config_file, "\n%s\n", NM_SETTINGS_VLAN); + fprintf(config_file, "parent=%s\n", vlan.parent); + fprintf(config_file, "parent=%i\n", vlan.id); } #ifdef WIRELESS @@ -176,6 +193,9 @@ static void nm_write_connection_type(struct nm_config_info nmconf) if (nmconf.connection.type == WIFI) { fprintf(f, "connection type: wireless\n"); } + else if (nmconf.connection.type == VLAN) { + fprintf(f, "connection type: vlan\n"); + } else { fprintf(f, "connection type: wired\n"); } @@ -229,6 +249,9 @@ void nm_write_configuration(struct nm_config_info nmconf) if (nmconf.connection.type == WIRED) { nm_write_wired_specific_options(config_file, &nmconf); } + else if (nmconf.connection.type == VLAN) { + nm_write_vlan_specific_options(config_file, &nmconf); + } #ifdef WIRELESS else { nm_write_wireless_specific_options(config_file, &nmconf); @@ -420,6 +443,15 @@ static void nm_get_ipv6(struct netcfg_interface *niface, nm_ipvX *ipv6) } +static void nm_get_vlan(struct netcfg_interface *niface, nm_connection *connection, nm_vlan *nmvlan) +{ + if (niface->vlanid > 0) { + connection->type = VLAN; + nmvlan->id = niface->vlanid; + nmvlan->parent = niface->parentif; + } +} + /* Extract all configs for a wireless interface, from both global netcfg * values and other resources. */ #ifdef WIRELESS @@ -434,6 +466,7 @@ static void nm_get_wireless_config(struct netcfg_interface *niface, struct nm_co nm_get_ipv4(niface, &(nmconf->ipv4)); nm_get_ipv6(niface, &(nmconf->ipv6)); + nm_get_vlan(niface, &(nmconf->connection), &(nmconf->vlan)); } #endif diff --git a/nm-conf.h b/nm-conf.h index 63402224..9a6b6952 100644 --- a/nm-conf.h +++ b/nm-conf.h @@ -20,6 +20,7 @@ #define NM_DEFAULT_WIRED_NAME "Wired connection 1" #define NM_DEFAULT_WIRELESS "802-11-wireless" #define NM_DEFAULT_WIRELESS_SECURITY "802-11-wireless-security" +#define NM_DEFAULT_VLAN "vlan" #define NM_DEFAULT_PATH_FOR_MAC "/sys/class/net/%s/address" #define NM_CONFIG_FILE_PATH "/etc/NetworkManager/system-connections" #define NM_CONNECTION_FILE "/tmp/connection_type" @@ -30,6 +31,7 @@ #define NM_SETTINGS_WIRELESS_SECURITY "["NM_DEFAULT_WIRELESS_SECURITY"]" #define NM_SETTINGS_IPV4 "[ipv4]" #define NM_SETTINGS_IPV6 "[ipv6]" +#define NM_SETTINGS_VLAN "[vlan]" /* Minimalist structures for storing basic elements in order to write a Network * Manager format config file. @@ -43,7 +45,7 @@ typedef struct nm_connection { char id[NM_MAX_LEN_ID]; char uuid[NM_MAX_LEN_UUID]; - enum {WIRED, WIFI} type; + enum {WIRED, WIFI, VLAN} type; int manual; /* 1 = true, 0 = false */ } nm_connection; @@ -86,6 +88,11 @@ typedef struct nm_ipvX unsigned int masklen; } nm_ipvX; +typedef struct nm_vlan +{ + char * parent; + int id; +} nm_vlan; typedef struct nm_config_info { @@ -95,6 +102,7 @@ typedef struct nm_config_info nm_wireless_security wireless_security; nm_ipvX ipv4; nm_ipvX ipv6; + nm_vlan vlan; } nm_config_info; /* Public functions */ diff --git a/static.c b/static.c index 87c3d449..ccb8a796 100644 --- a/static.c +++ b/static.c @@ -311,7 +311,7 @@ static int netcfg_activate_static_ipv4(struct debconfclient *client, deconfigure_network(NULL); loop_setup(); - interface_up(interface->name); + netcfg_interface_up(interface); /* Flush all previous addresses, routes */ snprintf(buf, sizeof(buf), "ifconfig %s inet 0 down", interface->name); @@ -356,7 +356,7 @@ static int netcfg_activate_static_ipv4(struct debconfclient *client, deconfigure_network(NULL); loop_setup(); - interface_up(interface->name); + netcfg_interface_up(interface); /* Flush all previous addresses, routes */ snprintf(buf, sizeof(buf), "ip -f inet addr flush dev %s", interface->name); @@ -445,7 +445,7 @@ static int netcfg_activate_static_ipv6(struct debconfclient *client, deconfigure_network(NULL); loop_setup(); - interface_up(interface->name); + netcfg_interface_up(interface); /* Flush all previous addresses, routes */ snprintf(buf, sizeof(buf), "ifconfig %s inet 0 down", interface->name); @@ -477,7 +477,7 @@ static int netcfg_activate_static_ipv6(struct debconfclient *client, deconfigure_network(NULL); loop_setup(); - interface_up(interface->name); + netcfg_interface_up(interface); /* Flush all previous addresses, routes */ snprintf(buf, sizeof(buf), "ip -f inet6 addr flush dev %s", interface->name); @@ -491,8 +491,8 @@ static int netcfg_activate_static_ipv6(struct debconfclient *client, /* Now down and up the interface, to get LL and SLAAC addresses back, * since flushing the addresses and routes gets rid of all that * sort of thing. */ - interface_down(interface->name); - interface_up(interface->name); + netcfg_interface_down(interface); + netcfg_interface_up(interface); /* Add the new IP address and netmask */ snprintf(buf, sizeof(buf), "ip addr add %s/%d dev %s", diff --git a/vlan.c b/vlan.c new file mode 100644 index 00000000..447e0e4a --- /dev/null +++ b/vlan.c @@ -0,0 +1,114 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <cdebconf/debconfclient.h> +#include <debian-installer.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include "netcfg.h" + +static char* get_vlan_command(const char* parentif, const char* vlanif, int vlanid) { +#if defined(__linux__) + const char* vlan_command = "ip link add link %s name %s type vlan id %d"; + int len = strlen(vlan_command) + strlen(parentif) + strlen(vlanif) + 4 + 1; + char* buf = malloc(len); + snprintf(buf, len, vlan_command, parentif, vlanif, vlanid); + return buf; +#elif defined(__FreeBSD_kernel__) + const char* vlan_command = "ifconfig %s vlan %d vlandev %s"; + int len = strlen(vlan_command) + strlen(parentif) + strlen(vlanif) + 4 + 1; + char* buf = malloc(len); + snprintf(buf, len, vlan_command, vlanif, vlanid, parentif); + return buf; +#endif +} + +/* Create a new VLAN interface attached to the currently selected + * network interface. + */ +int netcfg_set_vlan (struct debconfclient *client, struct netcfg_interface *interface) { +#if defined(__linux__) || defined(__FreeBSD_kernel__) + int vlanid; + + debconf_get(client, "netcfg/vlan_id"); + /* Empty string: no VLAN preseeded, ask if we should configure VLAN */ + if (strlen(client->value) == 0) { + debconf_input (client, "medium", "netcfg/use_vlan"); + if (debconf_go(client) == GO_BACK) { + return GO_BACK; + } + + debconf_get(client, "netcfg/use_vlan"); + + if (!strcmp(client->value, "false")) { + return 0; + } + + debconf_input(client, "critical", "netcfg/vlan_id"); + if (debconf_go(client) == GO_BACK) { + return GO_BACK; + } + debconf_get(client, "netcfg/vlan_id"); + } + + for (;;) { + vlanid = strtol(client->value, NULL, 10); + /* Valid range: 1-4094 (0 and 4095 are reserved.) + * 0 will be returned by strtol if the value cannot be parsed. + */ + if (vlanid < 1 || vlanid > 4094) { + di_error("VLAN ID \"%s\" is invalid.", client->value); + debconf_subst(client, "netcfg/vlan_id_invalid", "vlan_id", client->value); + debconf_input(client, "critical", "netcfg/invalid_vlan"); + debconf_capb(client); + debconf_go(client); + + debconf_capb(client, "backup"); + debconf_input(client, "critical", "netcfg/vlan_id"); + if (debconf_go(client) == GO_BACK) { + return GO_BACK; + } + } else { + break; + } + } + + int vlaniflen = strlen(interface->name) + 1 + 4 + 1; + char* vlanif = malloc(vlaniflen); + snprintf(vlanif, vlaniflen, "%s.%d", interface->name, vlanid); + + /* Check if VLAN interface already exists; skip setting it up if so */ + struct ifreq ifr; + strncpy(ifr.ifr_name, vlanif, IFNAMSIZ-1); + ifr.ifr_name[IFNAMSIZ-1]='\0'; + + di_info("Check if VLAN interface %s already exists...", vlanif); + + if (skfd && ioctl(skfd, SIOCGIFFLAGS, &ifr) >=0) { + di_info("VLAN interface %s already exists, skipping creation", vlanif); + } else { + char *vlan_command = get_vlan_command(interface->name, vlanif, vlanid); + int rc = di_exec_shell_log(vlan_command); + if (rc != 0) { + di_error("\"%s\" failed to create VLAN interface; return code = %d", vlan_command, rc); + free(vlan_command); + debconf_input(client, "critical", "netcfg/vlan_failed"); + debconf_go(client); + return GO_BACK; + } + free(vlan_command); + di_exec_shell_log("apt-install vlan"); + } + + interface->parentif = interface->name; + interface->name = vlanif; + interface->vlanid = vlanid; +#else + /* This platform does not support VLANs. */ + debconf_get(client, "netcfg/vlan_id"); + if (strlen(client->value) > 0) { + di_error("netcfg/vlan_id specified, yet VLAN is not supported on this platfrom"); + } +#endif + return 0; +} diff --git a/wireless.c b/wireless.c index 566b0326..83b1d1df 100644 --- a/wireless.c +++ b/wireless.c @@ -121,7 +121,7 @@ int netcfg_wireless_show_essids(struct debconfclient *client, struct netcfg_inte int essid_list_len = 1; iw_get_basic_config (wfd, interface->name, &wconf); - interface_up(interface->name); + netcfg_interface_up(interface); if (iw_scan(wfd, interface->name, iw_get_kernel_we_version(), &network_list) >= 0 ) { @@ -205,7 +205,7 @@ int netcfg_wireless_show_essids(struct debconfclient *client, struct netcfg_inte } iw_set_basic_config(wfd, interface->name, &wconf); - interface_down(interface->name); + netcfg_interface_down(interface); di_info("Network chosen: %s. Proceeding to connect.", interface->essid); diff --git a/write_interface.c b/write_interface.c index f0617d43..cd9fadbb 100644 --- a/write_interface.c +++ b/write_interface.c @@ -44,6 +44,19 @@ static int nc_wi_loopback(const struct netcfg_interface *interface, FILE *fd) return 1; } +/* Write VLAN settings, such as: vlan_raw_device eth0 + */ +static int nc_wi_vlan(const struct netcfg_interface *interface, FILE *fd) +{ + int rv; + rv = 1; + if (interface && interface->parentif && + (fprintf(fd, "\tvlan_raw_device %s\n", interface->parentif) < 0)) { + rv = 0; + } + return rv; +} + static int nc_wi_wireless_options(const struct netcfg_interface *interface, FILE *fd) { /* @@ -270,6 +283,11 @@ int netcfg_write_interface(const struct netcfg_interface *interface) di_debug("Writing static IPv6 stanza for %s", interface->name); rv = nc_wi_static_ipv6(interface, fd); } + + if (rv && interface && interface->parentif) { + di_debug("Writing VLAN: %s", interface->name); + rv = nc_wi_vlan(interface, fd); + } if (rv && interface && is_wireless_iface(interface->name)) { di_debug("Writing wireless options for %s", interface->name); -- 2.30.2