From af59a46a1a9ae7b7143b4be183b48301c4f81d36 Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Sun, 30 Aug 2026 02:05:32 +0200 Subject: openvpn: T8264: build 2.7.5 backport for the in-tree ovpn module The in-tree "ovpn" module requires OpenVPN 2.7 or later, while bookworm ships 2.6.14 which speaks only ovpn-dco-v2. DCO stays inert until userspace is upgraded. systemd-dev does not exist in bookworm, so it is stripped before mk-build-deps reads debian/control. The package is also added to the rebuild trigger, without which it would never reach the mirror. --- scripts/package-build/openvpn/.gitignore | 1 + scripts/package-build/openvpn/build.py | 1 + scripts/package-build/openvpn/package.toml | 10 ++++++++++ 3 files changed, 12 insertions(+) create mode 100644 scripts/package-build/openvpn/.gitignore create mode 120000 scripts/package-build/openvpn/build.py create mode 100644 scripts/package-build/openvpn/package.toml (limited to 'scripts') diff --git a/scripts/package-build/openvpn/.gitignore b/scripts/package-build/openvpn/.gitignore new file mode 100644 index 00000000..7c543c64 --- /dev/null +++ b/scripts/package-build/openvpn/.gitignore @@ -0,0 +1 @@ +/openvpn/ diff --git a/scripts/package-build/openvpn/build.py b/scripts/package-build/openvpn/build.py new file mode 120000 index 00000000..3c76af73 --- /dev/null +++ b/scripts/package-build/openvpn/build.py @@ -0,0 +1 @@ +../build.py \ No newline at end of file diff --git a/scripts/package-build/openvpn/package.toml b/scripts/package-build/openvpn/package.toml new file mode 100644 index 00000000..814b688a --- /dev/null +++ b/scripts/package-build/openvpn/package.toml @@ -0,0 +1,10 @@ +[[packages]] +name = "openvpn" +commit_id = "debian/2.7.5-1" +scm_url = "https://salsa.debian.org/debian/openvpn.git" + +# bookworm has no systemd-dev package, systemd itself ships the pkg-config +# files there. Must run before mk-build-deps reads debian/control. +pre_build_hook = '''git reset --hard HEAD && git clean -ffdx && sed -i '/ systemd-dev /d' debian/control''' + +build_cmd = "sed -i '1s/)/+vyos1)/' debian/changelog && dpkg-buildpackage -uc -us -tc -b" -- cgit v1.2.3 From 21c86f974e764087b0afef9c95b16372ae05fcad Mon Sep 17 00:00:00 2001 From: Antonio Quartulli Date: Fri, 11 Sep 2026 13:38:54 +0200 Subject: openvpn: T8264: adopt a pre-created DCO interface properly VyOS creates the "ovpn" device whenever the daemon cannot - a client whose server is not reachable yet never opens its tun - so OpenVPN has to adopt a device it did not make. It takes the device but not its ifindex today, so dco_new_peer() fails, the daemon exits and deletes the interface on its way out. systemd starts it again and the second attempt creates a device of its own, which hides the whole thing behind a tunnel that does come up. Carry Ralf's fix until it is released: it resolves the ifindex on the -EEXIST path, refuses a device whose operating mode does not match, and leaves a pre-existing interface alone on close. https://gerrit.openvpn.net/c/openvpn/+/1734 (patchset 2) --- .../1734-support-pre-existing-dco-interfaces.patch | 360 +++++++++++++++++++++ 1 file changed, 360 insertions(+) create mode 100644 scripts/package-build/openvpn/patches/openvpn/1734-support-pre-existing-dco-interfaces.patch (limited to 'scripts') diff --git a/scripts/package-build/openvpn/patches/openvpn/1734-support-pre-existing-dco-interfaces.patch b/scripts/package-build/openvpn/patches/openvpn/1734-support-pre-existing-dco-interfaces.patch new file mode 100644 index 00000000..c4878b13 --- /dev/null +++ b/scripts/package-build/openvpn/patches/openvpn/1734-support-pre-existing-dco-interfaces.patch @@ -0,0 +1,360 @@ +From 5c8c4bf4b9c7a4ab34c99e6b899c0d939dc6e479 Mon Sep 17 00:00:00 2001 +From: Ralf Lici +Date: Fri, 26 Jun 2026 17:45:44 +0200 +Subject: [PATCH] Support pre-existing Linux DCO interfaces + +When creating an ovpn interface returns -EEXIST, still retrieve the +ifindex and return the error to the generic DCO open path. This lets the +caller mark the interface as pre-existing and avoid deleting it on +close. + +Before accepting the existing interface, query its rtnetlink link info +and verify that it is an ovpn device with the expected mode. The ovpn +mode is fixed at interface creation time, so attaching to an interface +created for the other mode cannot work. + +Honor the pre-existing state on close by skipping net_iface_del() for +persistent interfaces. + +Github: closes OpenVPN/openvpn#1064 +Change-Id: I72302403bddee4b0b0ee2441ae9e246f48d0bc81 +Signed-off-by: Ralf Lici +--- + +diff --git a/src/openvpn/dco.h b/src/openvpn/dco.h +index 4e5aad5..4584004 100644 +--- a/src/openvpn/dco.h ++++ b/src/openvpn/dco.h +@@ -109,7 +109,8 @@ + bool ovpn_dco_init(struct context *c); + + /** +- * Open/create a DCO interface ++ * Open/create a DCO interface and store its ifindex. ++ * If the interface already exists, save the ifindex anyway and return -EEXIST. + * + * @param tt the tuntap context + * @param ctx the networking API context +diff --git a/src/openvpn/dco_linux.c b/src/openvpn/dco_linux.c +index 56f6259..777b43c 100644 +--- a/src/openvpn/dco_linux.c ++++ b/src/openvpn/dco_linux.c +@@ -502,6 +502,19 @@ + CLEAR(dco); + } + ++static const char * ++ovpn_mode_to_str(enum ovpn_mode mode) ++{ ++ switch (mode) ++ { ++ case OVPN_MODE_P2P: ++ return "p2p"; ++ case OVPN_MODE_MP: ++ return "server"; ++ } ++ return "unknown"; ++} ++ + int + open_tun_dco(struct tuntap *tt, openvpn_net_ctx_t *ctx, const char *dev) + { +@@ -509,11 +522,30 @@ + ASSERT(tt->type == DEV_TYPE_TUN); + + int ret = net_iface_new(ctx, dev, OVPN_FAMILY_NAME, &tt->dco); +- if (ret < 0) ++ if (ret < 0 && ret != -EEXIST) + { + msg(D_DCO_DEBUG, "Cannot create DCO interface %s: %d", dev, ret); + return ret; + } ++ if (ret == -EEXIST) ++ { ++ enum ovpn_mode mode; ++ int mode_ret = net_iface_ovpn_mode(ctx, dev, &mode); ++ ++ if (mode_ret < 0) ++ { ++ msg(M_WARN, "DCO: cannot retrieve mode of existing interface %s: %s (%d)", dev, ++ strerror(-mode_ret), mode_ret); ++ return mode_ret; ++ } ++ ++ if (mode != tt->dco.ifmode) ++ { ++ msg(M_WARN, "DCO: existing interface %s is in %s mode, expected %s mode", ++ dev, ovpn_mode_to_str(mode), ovpn_mode_to_str(tt->dco.ifmode)); ++ return -EINVAL; ++ } ++ } + + tt->dco.ifindex = if_nametoindex(dev); + if (!tt->dco.ifindex) +@@ -521,7 +553,7 @@ + msg(M_FATAL, "DCO: cannot retrieve ifindex for interface %s", dev); + } + +- return 0; ++ return ret; + } + + void +@@ -529,7 +561,10 @@ + { + msg(D_DCO_DEBUG, __func__); + +- net_iface_del(ctx, tt->actual_name); ++ if (!tt->persistent_if) ++ { ++ net_iface_del(ctx, tt->actual_name); ++ } + ovpn_dco_uninit_netlink(&tt->dco); + } + +diff --git a/src/openvpn/dco_linux.h b/src/openvpn/dco_linux.h +index e3e4824..42df09e 100644 +--- a/src/openvpn/dco_linux.h ++++ b/src/openvpn/dco_linux.h +@@ -24,6 +24,7 @@ + #if defined(ENABLE_DCO) && defined(TARGET_LINUX) + + #include "event.h" ++#include "networking_sitnl.h" + + #include "ovpn_dco_linux.h" + +@@ -38,28 +39,6 @@ + typedef enum ovpn_key_slot dco_key_slot_t; + typedef enum ovpn_cipher_alg dco_cipher_t; + +-/* OVPN section */ +- +-#ifndef IFLA_OVPN_MAX +- +-enum ovpn_mode +-{ +- OVPN_MODE_P2P, +- OVPN_MODE_MP, +-}; +- +-enum ovpn_ifla_attrs +-{ +- IFLA_OVPN_UNSPEC = 0, +- IFLA_OVPN_MODE, +- +- __IFLA_OVPN_MAX, +-}; +- +-#define IFLA_OVPN_MAX (__IFLA_OVPN_MAX - 1) +- +-#endif /* ifndef IFLA_OVPN_MAX */ +- + typedef struct + { + struct nl_sock *nl_sock; +diff --git a/src/openvpn/networking.h b/src/openvpn/networking.h +index bce0c19..cab560c 100644 +--- a/src/openvpn/networking.h ++++ b/src/openvpn/networking.h +@@ -115,6 +115,19 @@ + */ + int net_iface_type(openvpn_net_ctx_t *ctx, const char *iface, char type[IFACE_TYPE_LEN_MAX]); + ++#if defined(ENABLE_DCO) && defined(TARGET_LINUX) ++/** ++ * Retrieve the ovpn interface mode ++ * ++ * @param ctx the implementation specific context ++ * @param iface interface to query ++ * @param mode variable where the ovpn mode attribute will be stored ++ * ++ * @return 0 on success, a negative error code otherwise ++ */ ++int net_iface_ovpn_mode(openvpn_net_ctx_t *ctx, const char *iface, enum ovpn_mode *mode); ++#endif ++ + /** + * Remove an interface + * +diff --git a/src/openvpn/networking_iproute2.c b/src/openvpn/networking_iproute2.c +index a1f3525..85ab158 100644 +--- a/src/openvpn/networking_iproute2.c ++++ b/src/openvpn/networking_iproute2.c +@@ -82,6 +82,16 @@ + return -1; + } + ++#if defined(ENABLE_DCO) ++int ++net_iface_ovpn_mode(openvpn_net_ctx_t *ctx, const char *iface, enum ovpn_mode *mode) ++{ ++ /* not supported by iproute2 */ ++ msg(M_WARN, "%s: operation not supported by iproute2 backend", __func__); ++ return -EOPNOTSUPP; ++} ++#endif ++ + int + net_iface_del(openvpn_net_ctx_t *ctx, const char *iface) + { +diff --git a/src/openvpn/networking_sitnl.c b/src/openvpn/networking_sitnl.c +index a396255..9d07638 100644 +--- a/src/openvpn/networking_sitnl.c ++++ b/src/openvpn/networking_sitnl.c +@@ -1432,8 +1432,18 @@ + return 0; + } + +-int +-net_iface_type(openvpn_net_ctx_t *ctx, const char *iface, char type[IFACE_TYPE_LEN_MAX]) ++/** ++ * Issue an RTM_GETLINK query for an interface and feed the reply to the given ++ * parsing callback. ++ * ++ * @param iface name of the interface to query ++ * @param cb callback invoked with the netlink reply ++ * @param arg opaque argument passed through to the callback ++ * ++ * @return 0 on success, a negative error code otherwise ++ */ ++static int ++sitnl_link_get(const char *iface, sitnl_parse_reply_cb cb, void *arg) + { + struct sitnl_link_req req = {}; + int ifindex = if_nametoindex(iface); +@@ -1450,9 +1460,15 @@ + req.i.ifi_family = AF_PACKET; + req.i.ifi_index = ifindex; + ++ return sitnl_send(&req.n, 0, 0, cb, arg); ++} ++ ++int ++net_iface_type(openvpn_net_ctx_t *ctx, const char *iface, char type[IFACE_TYPE_LEN_MAX]) ++{ + memset(type, 0, IFACE_TYPE_LEN_MAX); + +- int ret = sitnl_send(&req.n, 0, 0, sitnl_type_save, type); ++ int ret = sitnl_link_get(iface, sitnl_type_save, type); + if (ret < 0) + { + msg(D_ROUTE, "%s: cannot retrieve iface %s: %s (%d)", __func__, iface, strerror(-ret), ret); +@@ -1464,6 +1480,82 @@ + return 0; + } + ++#if defined(ENABLE_DCO) ++static int ++sitnl_ovpn_mode_save(struct nlmsghdr *n, void *arg) ++{ ++ struct ifinfomsg *ifi = NLMSG_DATA(n); ++ struct rtattr *tb[IFLA_MAX + 1]; ++ struct rtattr *tb_link[IFLA_INFO_MAX + 1]; ++ struct rtattr *tb_data[IFLA_OVPN_MAX + 1]; ++ enum ovpn_mode *mode = arg; ++ uint8_t raw_mode; ++ ++ if (n->nlmsg_type != RTM_NEWLINK) ++ { ++ return -EINVAL; ++ } ++ ++ if (n->nlmsg_len < NLMSG_LENGTH(sizeof(*ifi))) ++ { ++ return -EINVAL; ++ } ++ ++ sitnl_parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n)); ++ ++ if (!tb[IFLA_LINKINFO]) ++ { ++ return -ENOENT; ++ } ++ ++ sitnl_parse_rtattr_nested(tb_link, IFLA_INFO_MAX, tb[IFLA_LINKINFO]); ++ ++ if (!tb_link[IFLA_INFO_KIND] ++ || strcmp(RTA_DATA(tb_link[IFLA_INFO_KIND]), OVPN_FAMILY_NAME) != 0) ++ { ++ return -EINVAL; ++ } ++ ++ if (!tb_link[IFLA_INFO_DATA]) ++ { ++ return -ENOENT; ++ } ++ ++ sitnl_parse_rtattr_nested(tb_data, IFLA_OVPN_MAX, tb_link[IFLA_INFO_DATA]); ++ ++ if (!tb_data[IFLA_OVPN_MODE]) ++ { ++ return -ENOENT; ++ } ++ ++ if (RTA_PAYLOAD(tb_data[IFLA_OVPN_MODE]) < sizeof(raw_mode)) ++ { ++ return -EINVAL; ++ } ++ ++ raw_mode = *(uint8_t *)RTA_DATA(tb_data[IFLA_OVPN_MODE]); ++ *mode = (enum ovpn_mode)raw_mode; ++ ++ return 0; ++} ++ ++int ++net_iface_ovpn_mode(openvpn_net_ctx_t *ctx, const char *iface, enum ovpn_mode *mode) ++{ ++ int ret = sitnl_link_get(iface, sitnl_ovpn_mode_save, mode); ++ if (ret < 0) ++ { ++ msg(D_ROUTE, "%s: cannot retrieve ovpn mode for iface %s: %s (%d)", __func__, iface, ++ strerror(-ret), ret); ++ return ret; ++ } ++ ++ msg(D_ROUTE, "%s: mode of %s: %d", __func__, iface, *mode); ++ ++ return 0; ++} ++#endif /* defined(ENABLE_DCO) */ ++ + int + net_iface_del(openvpn_net_ctx_t *ctx, const char *iface) + { +diff --git a/src/openvpn/networking_sitnl.h b/src/openvpn/networking_sitnl.h +index 481cc36..7a2d964 100644 +--- a/src/openvpn/networking_sitnl.h ++++ b/src/openvpn/networking_sitnl.h +@@ -24,4 +24,30 @@ + typedef char openvpn_net_iface_t; + typedef void *openvpn_net_ctx_t; + ++#if defined(TARGET_LINUX) ++ ++#include ++ ++#ifndef IFLA_OVPN_MAX ++ ++enum ovpn_mode ++{ ++ OVPN_MODE_P2P, ++ OVPN_MODE_MP, ++}; ++ ++enum ovpn_ifla_attrs ++{ ++ IFLA_OVPN_UNSPEC = 0, ++ IFLA_OVPN_MODE, ++ ++ __IFLA_OVPN_MAX, ++}; ++ ++#define IFLA_OVPN_MAX (__IFLA_OVPN_MAX - 1) ++ ++#endif /* ifndef IFLA_OVPN_MAX */ ++ ++#endif /* if defined(TARGET_LINUX) */ ++ + #endif /* NETWORKING_SITNL_H_ */ -- cgit v1.2.3