]> git.ipfire.org Git - thirdparty/iproute2.git/commitdiff
iplink: add support for setting IPv4 devconf parameters
authorFernando Fernandez Mancera <fmancera@suse.de>
Mon, 1 Jun 2026 14:32:42 +0000 (16:32 +0200)
committerDavid Ahern <dsahern@kernel.org>
Thu, 4 Jun 2026 14:58:52 +0000 (08:58 -0600)
Currently, modifying IPv4 interface configuration parameters requires
interacting with sysctl (/proc/sys/net/ipv4/conf/*). While
ip-netconf exists for monitoring these values, there has been no native
way to set them directly through iproute2.

This patch introduces the support for that via 'inet' at the 'ip link
set'. It uses IFLA_INET_CONF netlink attribute to set the parameter.

Example to enable forwarding on an interface:

ip link set dev enp8s0 inet forwarding on

Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
Signed-off-by: David Ahern <dsahern@kernel.org>
(Code length fixups and s/matches/strcmp/ when applying)

ip/iplink.c
man/man8/ip-link.8.in

index 2c052d2970cadd8d6310edb322d66af41279d419..9799530f15f737d7fd6fee63a01f7ad6b632665b 100644 (file)
@@ -19,6 +19,7 @@
 #include <arpa/inet.h>
 #include <linux/if.h>
 #include <linux/if_ether.h>
+#include <linux/ip.h>
 
 #include "rt_names.h"
 #include "utils.h"
@@ -102,6 +103,23 @@ void iplink_usage(void)
                "               [ master DEVICE ][ vrf NAME ]\n"
                "               [ nomaster ]\n"
                "               [ addrgenmode { eui64 | none | stable_secret | random } ]\n"
+               "               [ inet [ { forwarding | proxy_arp | accept_redirects |\n"
+               "                          secure_redirects | send_redirects | shared_media |\n"
+               "                          accept_source_route | bootp_relay | log_martians |\n"
+               "                          arp_filter | disable_xfrm | disable_policy |\n"
+               "                          promote_secondaries | arp_notify | accept_local |\n"
+               "                          src_vmark | proxy_arp_pvlan | route_localnet |\n"
+               "                          bc_forwarding | ignore_routes_with_linkdown |\n"
+               "                          drop_unicast_in_l2_multicast |\n"
+               "                          drop_gratuitous_arp | arp_evict_nocarrier } { on | off } ]\n"
+               "                      [ rp_filter { 0 | 1 | 2 } ]\n"
+               "                      [ arp_announce { 0 | 1 | 2 } ]\n"
+               "                      [ arp_ignore { 0 | 1 | ... | 8 } ]\n"
+               "                      [ arp_accept { 0 | 1 | 2 } ]\n"
+               "                      [ force_igmp_version { 0 | 1 | 2 | 3 } ]\n"
+               "                      [ tag TAG ] [ medium_id ID ]\n"
+               "                      [ igmpv2_unsolicited_report_interval INTERVAL ]\n"
+               "                      [ igmpv3_unsolicited_report_interval INTERVAL ] ]\n"
                "               [ protodown { on | off } ]\n"
                "               [ protodown_reason PREASON { on | off } ]\n"
                "               [ gso_max_size BYTES ] [ gso_ipv4_max_size BYTES ] [ gso_max_segs PACKETS ]\n"
@@ -551,6 +569,296 @@ static int iplink_parse_vf(int vf, int *argcp, char ***argvp,
        return 0;
 }
 
+static int iplink_parse_inet(int *argcp, char ***argvp, struct iplink_req *req)
+{
+       struct rtattr *afs, *afinet, *inet_devconf;
+       char **argv = *argvp;
+       int argc = *argcp;
+       bool val;
+       int ret;
+
+       afs = addattr_nest(&req->n, sizeof(*req), IFLA_AF_SPEC);
+       afinet = addattr_nest(&req->n, sizeof(*req), AF_INET);
+       inet_devconf = addattr_nest(&req->n, sizeof(*req), IFLA_INET_CONF);
+
+       while (NEXT_ARG_OK()) {
+               NEXT_ARG();
+               if (strcmp(*argv, "forwarding") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("forwarding", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_FORWARDING, val);
+               } else if (strcmp(*argv, "proxy_arp") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("proxy_arp", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_PROXY_ARP, val);
+               } else if (strcmp(*argv, "accept_redirects") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("accept_redirects", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_ACCEPT_REDIRECTS, val);
+               } else if (strcmp(*argv, "secure_redirects") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("secure_redirects", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_SECURE_REDIRECTS, val);
+               } else if (strcmp(*argv, "send_redirects") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("send_redirects", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_SEND_REDIRECTS, val);
+               } else if (strcmp(*argv, "shared_media") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("shared_media", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_SHARED_MEDIA, val);
+               } else if (strcmp(*argv, "rp_filter") == 0) {
+                       int rp_filter;
+
+                       NEXT_ARG();
+                       if (get_integer(&rp_filter, *argv, 0)) {
+                               invarg("Invalid \"rp_filter\" value\n", *argv);
+                               return -1;
+                       }
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_RP_FILTER, rp_filter);
+               } else if (strcmp(*argv, "accept_source_route") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("accept_source_route", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_ACCEPT_SOURCE_ROUTE, val);
+               } else if (strcmp(*argv, "bootp_relay") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("bootp_relay", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_BOOTP_RELAY, val);
+               } else if (strcmp(*argv, "log_martians") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("log_martians", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_LOG_MARTIANS, val);
+               } else if (strcmp(*argv, "tag") == 0) {
+                       int tag;
+
+                       NEXT_ARG();
+                       if (get_integer(&tag, *argv, 0)) {
+                               invarg("Invalid \"tag\" value\n", *argv);
+                               return -1;
+                       }
+                       addattr32(&req->n, sizeof(*req), IPV4_DEVCONF_TAG, tag);
+               } else if (strcmp(*argv, "arp_filter") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("arp_filter", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_ARPFILTER, val);
+               } else if (strcmp(*argv, "medium_id") == 0) {
+                       int medium_id;
+
+                       NEXT_ARG();
+                       if (get_integer(&medium_id, *argv, 0)) {
+                               invarg("Invalid \"medium_id\" value\n", *argv);
+                               return -1;
+                       }
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_MEDIUM_ID, medium_id);
+               } else if (strcmp(*argv, "disable_xfrm") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("disable_xfrm", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_NOXFRM, val);
+               } else if (strcmp(*argv, "disable_policy") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("disable_policy", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_NOPOLICY, val);
+               } else if (strcmp(*argv, "force_igmp_version") == 0) {
+                       int force_igmp_ver;
+
+                       NEXT_ARG();
+                       if (get_integer(&force_igmp_ver, *argv, 0)) {
+                               invarg("Invalid \"force_igmp_version\" value\n",
+                                      *argv);
+                               return -1;
+                       }
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_FORCE_IGMP_VERSION, force_igmp_ver);
+               } else if (strcmp(*argv, "arp_announce") == 0) {
+                       int arp_announce;
+
+                       NEXT_ARG();
+                       if (get_integer(&arp_announce, *argv, 0)) {
+                               invarg("Invalid \"arp_announce\" value\n", *argv);
+                               return -1;
+                       }
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_ARP_ANNOUNCE, arp_announce);
+               } else if (strcmp(*argv, "arp_ignore") == 0) {
+                       int arp_ignore;
+
+                       NEXT_ARG();
+                       if (get_integer(&arp_ignore, *argv, 0)) {
+                               invarg("Invalid \"arp_ignore\" value\n", *argv);
+                               return -1;
+                       }
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_ARP_IGNORE, arp_ignore);
+               } else if (strcmp(*argv, "promote_secondaries") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("promote_secondaries", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_PROMOTE_SECONDARIES, val);
+               } else if (strcmp(*argv, "arp_accept") == 0) {
+                       int arp_accept;
+
+                       NEXT_ARG();
+                       if (get_integer(&arp_accept, *argv, 0)) {
+                               invarg("Invalid \"arp_accept\" value\n", *argv);
+                               return -1;
+                       }
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_ARP_ACCEPT, arp_accept);
+               } else if (strcmp(*argv, "arp_notify") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("arp_notify", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_ARP_NOTIFY, val);
+               } else if (strcmp(*argv, "accept_local") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("accept_local", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_ACCEPT_LOCAL, val);
+               } else if (strcmp(*argv, "src_vmark") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("src_vmark", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_SRC_VMARK, val);
+               } else if (strcmp(*argv, "proxy_arp_pvlan") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("proxy_arp_pvlan", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_PROXY_ARP_PVLAN, val);
+               } else if (strcmp(*argv, "route_localnet") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("route_localnet", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_ROUTE_LOCALNET, val);
+               } else if (strcmp(*argv, "bc_forwarding") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("bc_forwarding", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_BC_FORWARDING, val);
+               } else if (!strcmp(*argv, "igmpv2_unsolicited_report_interval")) {
+                       int igmpv2_interval;
+
+                       NEXT_ARG();
+                       if (get_integer(&igmpv2_interval, *argv, 0)) {
+                               invarg("Invalid \"igmpv2_unsolicited_report_interval\" value\n",
+                                      *argv);
+                               return -1;
+                       }
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL,
+                                 igmpv2_interval);
+               } else if (!strcmp(*argv, "igmpv3_unsolicited_report_interval")) {
+                       int igmpv3_interval;
+
+                       NEXT_ARG();
+                       if (get_integer(&igmpv3_interval, *argv, 0)) {
+                               invarg("Invalid \"igmpv3_unsolicited_report_interval\" value\n",
+                                      *argv);
+                               return -1;
+                       }
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL,
+                                 igmpv3_interval);
+               } else if (strcmp(*argv, "ignore_routes_with_linkdown") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("ignore_routes_with_linkdown",
+                                          *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_IGNORE_ROUTES_WITH_LINKDOWN, val);
+               } else if (strcmp(*argv, "drop_unicast_in_l2_multicast") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("drop_unicast_in_l2_multicast",
+                                          *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_DROP_UNICAST_IN_L2_MULTICAST, val);
+               } else if (strcmp(*argv, "drop_gratuitous_arp") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("drop_gratuitous_arp", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_DROP_GRATUITOUS_ARP, val);
+               } else if (strcmp(*argv, "arp_evict_nocarrier") == 0) {
+                       NEXT_ARG();
+                       val = parse_on_off("arp_evict_nocarrier", *argv, &ret);
+                       if (ret)
+                               return ret;
+                       addattr32(&req->n, sizeof(*req),
+                                 IPV4_DEVCONF_ARP_EVICT_NOCARRIER, val);
+               } else {
+                       /* rewind arg */
+                       PREV_ARG();
+                       break;
+               }
+       }
+
+       if (argc == *argcp)
+               incomplete_command();
+
+       addattr_nest_end(&req->n, inet_devconf);
+       addattr_nest_end(&req->n, afinet);
+       addattr_nest_end(&req->n, afs);
+
+       *argcp = argc;
+       *argvp = argv;
+       return 0;
+}
+
 int iplink_parse(int argc, char **argv, struct iplink_req *req, char **type)
 {
        bool move_netns = false;
@@ -634,6 +942,9 @@ int iplink_parse(int argc, char **argv, struct iplink_req *req, char **type)
                        if (get_integer(&mtu, *argv, 0))
                                invarg("Invalid \"mtu\" value\n", *argv);
                        addattr_l(&req->n, sizeof(*req), IFLA_MTU, &mtu, 4);
+               } else if (strcmp(*argv, "inet") == 0) {
+                       if (iplink_parse_inet(&argc, &argv, req) < 0)
+                               return -1;
                } else if (strcmp(*argv, "xdpgeneric") == 0 ||
                           strcmp(*argv, "xdpdrv") == 0 ||
                           strcmp(*argv, "xdpoffload") == 0 ||
index e89b2db3be92336ce4d3faa5ab3d052e0ae12920..a55b09fca6257d184337a9209da361807e14bf19 100644 (file)
@@ -179,6 +179,43 @@ ip-link \- network device configuration
 .br
 .RB "[ " addrgenmode " { " eui64 " | " none " | " stable_secret " | " random " } ]"
 .br
+.RB "[ " inet " [ { " forwarding " | " proxy_arp " | " accept_redirects " |"
+.br
+.in +8
+.BR secure_redirects " | " send_redirects " | " shared_media " |"
+.br
+.BR accept_source_route " | " bootp_relay " | " log_martians " |"
+.br
+.BR arp_filter " | " disable_xfrm " | " disable_policy " |"
+.br
+.BR promote_secondaries " | " arp_notify " | " accept_local " |"
+.br
+.BR src_vmark " | " proxy_arp_pvlan " | " route_localnet " |"
+.br
+.BR bc_forwarding " | " ignore_routes_with_linkdown " |"
+.br
+.BR drop_unicast_in_l2_multicast " |"
+.br
+.BR drop_gratuitous_arp " | " arp_evict_nocarrier " } { " on " | " off " } ]"
+.br
+.RB "[ " rp_filter " { " 0 " | " 1 " | " 2 " } ]"
+.br
+.RB "[ " arp_announce " { " 0 " | " 1 " | " 2 " } ]"
+.br
+.RB "[ " arp_ignore " { " 0 " | " 1 " | " ... " | " 8 " } ]"
+.br
+.RB "[ " arp_accept " { " 0 " | " 1 " | " 2 " } ]"
+.br
+.RB "[ " force_igmp_version " { " 0 " | " 1 " | " 2 " | " 3 " } ]"
+.br
+.RB "[ " tag " " TAG " ] [ " medium_id " " ID " ]"
+.br
+.RB "[ " igmpv2_unsolicited_report_interval " " INTERVAL " ]"
+.br
+.RB "[ " igmpv3_unsolicited_report_interval " " INTERVAL " ] ]"
+.br
+.in -10
+.br
 .RB "[ " macaddr
 .RI "[ " MACADDR " ]"
 .br
@@ -2617,6 +2654,50 @@ set the IPv6 address generation mode
 .I random
 - like stable_secret, but auto-generate a new random secret if none is set
 
+.TP
+.B inet
+set the IPv4 devconf network parameters for the specific device. These parameters map directly to the kernel's IPv4 interface sysctls.
+
+.in +8
+.BR forwarding " { " on " | " off " }"
+- enable or disable IPv4 forwarding on the device.
+
+.BR proxy_arp ", " accept_redirects ", " secure_redirects ", " send_redirects ", "
+.BR shared_media ", " accept_source_route ", " bootp_relay ", " log_martians ", "
+.BR arp_filter ", " disable_xfrm ", " disable_policy ", " promote_secondaries ", "
+.BR arp_notify ", " accept_local ", " src_vmark ", " proxy_arp_pvlan ", "
+.BR route_localnet ", " bc_forwarding ", " ignore_routes_with_linkdown ", "
+.BR drop_unicast_in_l2_multicast ", " drop_gratuitous_arp ", " arp_evict_nocarrier ", "
+.BR "{ " on " | " off " }"
+- toggle the corresponding boolean network parameter.
+
+.BR rp_filter " { " 0 " | " 1 " | " 2 " }"
+- set reverse path filtering.
+
+.BR arp_announce " { " 0 " | " 1 " | " 2 " }"
+- define different restriction levels for announcing the local source IP address from IP packets in ARP requests.
+
+.BR arp_ignore " { " 0 " | " 1 " | " ... " | " 8 " }"
+- define different modes for replying to ARP requests that resolve local target IP addresses.
+
+.BR arp_accept " { " 0 " | " 1 " | " 2 " }"
+- define behavior for gratuitous ARP frames.
+
+.BR force_igmp_version " { " 0 " | " 1 " | " 2 " | " 3 " }"
+- force IGMP version.
+
+.BI tag " TAG "
+- set the interface tag.
+
+.BI medium_id " ID "
+- set the interface medium ID.
+
+.BI igmpv2_unsolicited_report_interval " INTERVAL "
+.br
+.BI igmpv3_unsolicited_report_interval " INTERVAL "
+- set the IGMP unsolicited report interval in milliseconds.
+.in -8
+
 .TP
 .BR "link-netnsid "
 set peer netnsid for a cross-netns interface