David Uhden Collado wrote:
Hello,Please find attached a patch updating net/i2pd from 2.61.0 to 3.0.0. https://github.com/PurpleI2P/i2pd/releases/tag/3.0.0 Besides the version update, the patch includes: - LibreSSL compatibility fixes, including ML-KEM, Ed25519ph and OpenSSL 3 API detection. - Improved cryptographic error handling in NTCP2, SSU2 and ECIES. - OpenBSD pledge/unveil initialization using resolved paths and the effective configuration. - Synchronous signal handling to avoid unsafe configuration reloads. - SSU2 MTU detection using getifaddrs(). - UDP tunnel cleanup and shutdown fixes. - Updated PLIST, configuration samples and rc.d integration. The changes preserve OpenBSD's existing package layout and service configuration. I would appreciate review, particularly of the pledge/unveil changes and LibreSSL compatibility. Thanks, David.
Hello, I overlooked that i2pd 3.0.0 now includes its own BitTorrent client, similar to the Java implementation. I've updated the patch to include an example BitTorrent configuration and the web interface files. I also moved the configuration samples into the port's files/ directory instead of patching the upstream files in contrib/. I think this is cleaner and easier to maintain, especially when updating to newer versions of i2pd. Please find the revised patch attached. Thanks, David.
Index: net/i2pd/Makefile =================================================================== RCS file: /cvs/ports/net/i2pd/Makefile,v diff -u -p -u -p -r1.34 Makefile --- net/i2pd/Makefile 17 Oct 2026 08:47:19 -0000 1.34 +++ net/i2pd/Makefile 23 Oct 2026 12:54:03 -0000 @@ -1,8 +1,9 @@ COMMENT = client for the I2P anonymous network +REVISION = 0 GH_ACCOUNT = PurpleI2P GH_PROJECT = i2pd -GH_TAGNAME = 2.61.0 +GH_TAGNAME = 3.0.0 CATEGORIES = net HOMEPAGE = https://i2pd.website @@ -12,9 +13,11 @@ MAINTAINER = SystemFailure <openbsd@syst # BSD PERMIT_PACKAGE = Yes +# uses pledge() and unveil() WANTLIB += ${COMPILER_LIBCXX} -WANTLIB += boost_atomic-mt boost_container-mt boost_filesystem-mt -WANTLIB += boost_program_options-mt c crypto m miniupnpc ssl z +WANTLIB += boost_filesystem-mt boost_program_options-mt +WANTLIB += boost_atomic-mt boost_container-mt boost_json-mt boost_url-mt +WANTLIB += c crypto m miniupnpc ssl z COMPILER = base-clang ports-gcc MODULES = devel/cmake @@ -32,16 +35,21 @@ post-install: ${INSTALL_DATA_DIR} ${PREFIX}/include/i2pd \ ${PREFIX}/share/examples/i2pd ${INSTALL_DATA} ${WRKDIST}/libi2pd{,_client}/*.h \ - ${PREFIX}/include/i2pd + ${PREFIX}/include/i2pd .for dir in family reseed - ${INSTALL_DATA_DIR} ${WRKINST}/var/i2pd/certificates/${dir} + ${INSTALL_DATA_DIR} ${WRKINST}${LOCALSTATEDIR}/i2pd/certificates/${dir} ${INSTALL_DATA} ${WRKDIST}/contrib/certificates/${dir}/* \ - ${WRKINST}/var/i2pd/certificates/${dir} + ${WRKINST}${LOCALSTATEDIR}/i2pd/certificates/${dir} .endfor - ${SUBST_DATA} ${WRKDIST}/contrib/i2pd.conf \ + ${SUBST_DATA} ${FILESDIR}/i2pd.conf \ ${PREFIX}/share/examples/i2pd/i2pd.conf - ${INSTALL_DATA} ${WRKDIST}/contrib/tunnels.conf \ + ${INSTALL_DATA} ${FILESDIR}/tunnels.conf \ ${PREFIX}/share/examples/i2pd/tunnels.conf + ${INSTALL_DATA} ${FILESDIR}/torrents.conf \ + ${PREFIX}/share/examples/i2pd/torrents.conf + ${INSTALL_DATA_DIR} ${WRKINST}${LOCALSTATEDIR}/i2pd/torrents/web + ${INSTALL_DATA} ${WRKDIST}/contrib/torrents/web/* \ + ${WRKINST}${LOCALSTATEDIR}/i2pd/torrents/web do-test: cd ${WRKDIST}/tests && ${MAKE_PROGRAM} CXX="${CXX}" \ Index: net/i2pd/distinfo =================================================================== RCS file: /cvs/ports/net/i2pd/distinfo,v diff -u -p -u -p -r1.27 distinfo --- net/i2pd/distinfo 17 Oct 2026 08:47:19 -0000 1.27 +++ net/i2pd/distinfo 23 Oct 2026 12:54:03 -0000 @@ -1,2 +1,2 @@ -SHA256 (i2pd-2.61.0.tar.gz) = QJzTwCV0kShmEatqr2kJQMckj7iYN3wT+ttlqDbioKs= -SIZE (i2pd-2.61.0.tar.gz) = 779272 +SHA256 (i2pd-3.0.0.tar.gz) = O+vJVnZYlE04jDgvvsYbe8WD36mYICi0K5bus1T3m9M= +SIZE (i2pd-3.0.0.tar.gz) = 861084 Index: net/i2pd/files/i2pd.conf =================================================================== RCS file: net/i2pd/files/i2pd.conf diff -N net/i2pd/files/i2pd.conf --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/files/i2pd.conf 23 Oct 2026 12:54:03 -0000 @@ -0,0 +1,290 @@ +## Configuration file for a typical i2pd user +## See https://i2pd.readthedocs.io/en/latest/user-guide/configuration/ +## for more options you can use in this file. + +## Lines that begin with "## " try to explain what's going on. Lines +## that begin with just "#" are disabled commands: you can enable them +## by removing the "#" symbol. + +## Tunnels config file +## Default: ~/.i2pd/tunnels.conf or ${LOCALSTATEDIR}/i2pd/tunnels.conf +tunconf = ${SYSCONFDIR}/i2pd/tunnels.conf + +## Tunnels config files path +## Use that path to store separated tunnels in different config files. +## Default: ~/.i2pd/tunnels.d or ${LOCALSTATEDIR}/i2pd/tunnels.d +tunnelsdir = ${SYSCONFDIR}/i2pd/tunnels.d + +## Path to certificates used for verifying .su3, families +## Default: ~/.i2pd/certificates or ${LOCALSTATEDIR}/i2pd/certificates +certsdir = ${LOCALSTATEDIR}/i2pd/certificates + +## Where to write pidfile (default: /run/i2pd.pid, not used in Windows) +# pidfile = /run/i2pd.pid + +## Logging configuration section +## By default logs go to stdout with level 'info' and higher +## For Windows OS by default logs go to file with level 'warn' and higher +## +## Logs destination (valid values: stdout, file, syslog) +## * stdout - print log entries to stdout +## * file - log entries to a file +## * syslog - use syslog, see man 3 syslog +log = syslog +## Path to logfile (default: autodetect) +# logfile = /var/log/i2pd/i2pd.log +## Log messages above this level (debug, info, *warn, error, critical, none) +## If you set it to none, logging will be disabled +# loglevel = warn +## Write full CLF-formatted date and time to log (default: write only time) +# logclftime = true + +## Daemon mode. Router will go to background after start. Ignored on Windows +## (default: true) +# daemon = true + +## Specify a family, router belongs to (default - none) +# family = + +## Network interface to bind to +## Updates address4/6 options if they are not set +# ifname = +## You can specify different interfaces for IPv4 and IPv6 +# ifname4 = +# ifname6 = + +## Local address to bind transport sockets to +## Overrides host option if: +## For ipv4: if ipv4 = true and nat = false +## For ipv6: if 'host' is not set or ipv4 = true +# address4 = +# address6 = + +## External IPv4 or IPv6 address to listen for connections +## By default i2pd sets IP automatically +## Sets published NTCP2v4/SSUv4 address to 'host' value if nat = true +## Sets published NTCP2v6/SSUv6 address to 'host' value if ipv4 = false +# host = 1.2.3.4 + +## Port to listen for connections +## By default i2pd picks random port and listens on it. +## If you want to change the port, you MUST pick a random number too, +## don't just uncomment this +# port = 4567 + +## Enable communication through ipv4 (default: true) +ipv4 = true +## Enable communication through ipv6 (default: false) +ipv6 = false + +## Bandwidth configuration +## L limit bandwidth to 32 KB/sec, O - to 256 KB/sec, P - to 2048 KB/sec, +## X - unlimited +## Default is L (regular node) and X if floodfill mode enabled. +## If you want to share more bandwidth without floodfill mode, uncomment +## that line and adjust value to your possibilities. Value can be set to +## integer in kilobytes, it will apply that limit and flag will be used +## from next upper limit (example: if you set 4096 flag will be X, but real +## limit will be 4096 KB/s). Same can be done when floodfill mode is used, +## but keep in mind that low values may be negatively evaluated by Java +## router algorithms. +# bandwidth = L +## Max % of bandwidth limit for transit. 0-100 (default: 100) +# share = 100 + +## Router will not accept transit tunnels, disabling transit traffic completely +## (default: false) +# notransit = true + +## Router will be floodfill (default: false) +## Note: that mode uses much more network connections and CPU! +# floodfill = true + +## For places with limited connectivity (default: false) +# stan = true + +[ntcp2] +## Enable NTCP2 transport (default: true) +# enabled = true +## Publish address in RouterInfo (default: true) +# published = true +## Port for incoming connections (default is global port option value) +# port = 4567 + +[ssu2] +## Enable SSU2 transport (default: true) +# enabled = true +## Publish address in RouterInfo (default: true) +# published = true +## Port for incoming connections (default is global port option value) +# port = 4567 + +[http] +## Web Console settings +## Enable the Web Console (default: true) +enabled = false +## Address and port service will listen on (default: 127.0.0.1:7070) +# address = 127.0.0.1 +# port = 7070 +## Path to web console (default: /) +# webroot = / +## Enable Web Console authentication (default: false) +## You should not use Web Console via public networks without additional encryption. +## HTTP authentication is not encryption layer! +# auth = true +# user = i2pd +# pass = changeme +## Select webconsole language +## Currently supported english (default), afrikaans, armenian, chinese, czech, french, +## german, italian, polish, portuguese, russian, spanish, turkish, turkmen, ukrainian +## and uzbek languages +# lang = english + +[httpproxy] +## Enable the HTTP proxy (default: true) +# enabled = true +## Address and port service will listen on (default: 127.0.0.1:4444) +# address = 127.0.0.1 +# port = 4444 +## Optional keys file for proxy local destination (default: transient-proxy) +## "Transient" means it changes on every new app launch +## If you want not change address then set like http-proxy-keys.dat +# keys = http-proxy-keys.dat +## Enable address helper for adding .i2p domains with "jump URLs" (default: true) +## You should disable this feature if your i2pd HTTP Proxy is public, +## because anyone could spoof the short domain via addresshelper and forward other users to phishing links +# addresshelper = true +## Address of a proxy server inside I2P, which is used to visit regular Internet +# outproxy = http://false.i2p +## httpproxy section also accepts I2CP parameters, like "inbound.length" etc. + +[socksproxy] +## Enable the SOCKS proxy (default: true) +# enabled = true +## Address and port service will listen on (default: 127.0.0.1:4447) +# address = 127.0.0.1 +# port = 4447 +## Optional keys file for proxy local destination (default: transient-proxy) +## "Transient" means it changes on every new app launch +## If you don't want the address to change, set a file name like socks-proxy-keys.dat +# keys = socks-proxy-keys.dat +## Socks outproxy. Example below is set to use Tor for all connections except i2p +## Enable SOCKS outproxy (works only with SOCKS4, default: false) +# outproxy.enabled = false +## Address and port of outproxy +# outproxy = 127.0.0.1 +# outproxyport = 9050 +## socksproxy section also accepts I2CP parameters, like "inbound.length" etc. + +[sam] +## Enable the SAM bridge (default: true) +# enabled = false +## Address and ports service will listen on (default: 127.0.0.1:7656, udp: 7655) +# address = 127.0.0.1 +# port = 7656 +# portudp = 7655 + +[bob] +## Enable the BOB command channel (default: false) +# enabled = false +## Address and port service will listen on (default: 127.0.0.1:2827) +# address = 127.0.0.1 +# port = 2827 + +[i2cp] +## Enable the I2CP protocol (default: false) +# enabled = false +## Address and port service will listen on (default: 127.0.0.1:7654) +# address = 127.0.0.1 +# port = 7654 + +[i2pcontrol] +## Enable the I2PControl protocol (default: false) +# enabled = false +## Address and port service will listen on (default: 127.0.0.1:7650) +# address = 127.0.0.1 +# port = 7650 +## Authentication password (default: itoopie) +# password = itoopie + +[precomputation] +## Enable or disable elgamal precomputation table +## By default, enabled on i386 hosts +# elgamal = true + +[upnp] +## Enable or disable UPnP: automatic port forwarding (enabled by default in WINDOWS, ANDROID) +# enabled = false +## Name i2pd appears in UPnP forwardings list (default: I2Pd) +# name = I2Pd + +[meshnets] +## Enable connectivity over the Yggdrasil network (default: false) +# yggdrasil = false +## You can bind address from your Yggdrasil subnet 300::/64 +## The address must first be added to the network interface +# yggaddress = + +[reseed] +## Options for bootstrapping into I2P network, aka reseeding +## Enable reseed data verification (default: false) +verify = true +## URLs to request reseed data from, separated by comma +## When not set, the built-in list of "mainline" I2P Network reseeds is used +# urls = https://reseed2.i2p.net/,https://reseed.diva.exchange/ +## Reseed URLs through the Yggdrasil, separated by comma +## When not set, the built-in list is used +# yggurls = http://[324:71e:281a:9ed3::ace]:7070/ +## Path to local reseed data file (.su3) for manual reseeding +# file = /path/to/i2pseeds.su3 +## or HTTPS URL to reseed from +# file = https://legit-website.com/i2pseeds.su3 +## Path to local ZIP file or HTTPS URL to reseed from +# zipfile = /path/to/netDb.zip +## If you run i2pd behind a proxy server, set proxy server for reseeding here +## Should be http://address:port or socks://address:port +# proxy = http://127.0.0.1:8118 +## Minimum number of known routers, below which i2pd triggers reseeding (default: 25) +# threshold = 25 +## Follow redirects when reseeding from URLs (default: false) +# followredirect = false + +[addressbook] +## AddressBook subscription URL for initial setup +## Default: reg.i2p at "mainline" I2P Network +# defaulturl = http://shx5vqsw7usdaunyzr2qmes2fq37oumybpudrd4jjj4e4vk4uusa.b32.i2p/hosts.txt +## Optional subscriptions URLs, separated by comma +# subscriptions = http://reg.i2p/hosts.txt,http://identiguy.i2p/hosts.txt,http://stats.i2p/cgi-bin/newhosts.txt + +[limits] +## Maximum active transit sessions (default: 25000) +## This value is doubled if floodfill mode is enabled! +# transittunnels = 25000 +## Limit number of open file descriptors (0 - use system limit) +# openfiles = 0 +## Maximum size of corefile in Kb (0 - use system limit) +# coresize = 0 + +[trust] +## Enable explicit trust options. (default: false) +# enabled = true +## Make direct I2P connections only to routers in specified Family. +# family = MyFamily +## Make direct I2P connections only to routers specified here. Comma separated list of base64 identities. +# routers = +## Should we hide our router from other routers? (default: false) +## Note: this is not an overall hidden mode +# hidden = true + +[exploratory] +## Exploratory tunnels settings with default values +# inbound.length = 2 +# inbound.quantity = 3 +# outbound.length = 2 +# outbound.quantity = 3 + +[persist] +## Save peer profiles on disk (default: true) +# profiles = true +## Save full addresses on disk (default: true) +# addressbook = true Index: net/i2pd/files/torrents.conf =================================================================== RCS file: net/i2pd/files/torrents.conf diff -N net/i2pd/files/torrents.conf --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/files/torrents.conf 23 Oct 2026 12:54:03 -0000 @@ -0,0 +1,14 @@ +# Optional BitTorrent tunnel configuration for i2pd. +# Merge these settings into /etc/i2pd/tunnels.conf to enable it. +# The daemon reads tunnel definitions from one file via --tunconf. +# The RPC interface below is bound to localhost for local access only. +# Leave it commented until you intend to run the torrent client. +# +#[torrents] +#type = torrents +#torrentsdir = /var/i2pd/torrents +#keys = torrents-keys.dat +#dht = true +#web = true +#rpcaddress = 127.0.0.1 +#rpcport = 7652 Index: net/i2pd/files/tunnels.conf =================================================================== RCS file: net/i2pd/files/tunnels.conf diff -N net/i2pd/files/tunnels.conf --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/files/tunnels.conf 23 Oct 2026 12:54:03 -0000 @@ -0,0 +1,34 @@ +#[IRC-ILITA] +#type = client +#address = 127.0.0.1 +#port = 6668 +#destination = irc.ilita.i2p +#destinationport = 6667 +#keys = irc-keys.dat +#i2p.streaming.profile=2 + +#[IRC-IRC2P] +#type = client +#address = 127.0.0.1 +#port = 6669 +#destination = irc.postman.i2p +#destinationport = 6667 +#keys = irc-keys.dat + +#[SMTP] +#type = client +#address = 127.0.0.1 +#port = 7659 +#destination = smtp.postman.i2p +#destinationport = 25 +#keys = smtp-keys.dat + +#[POP3] +#type = client +#address = 127.0.0.1 +#port = 7660 +#destination = pop.postman.i2p +#destinationport = 110 +#keys = pop3-keys.dat + +# see more examples at https://i2pd.readthedocs.io/en/latest/user-guide/tunnels/ Index: net/i2pd/patches/patch-contrib_i2pd_conf =================================================================== RCS file: net/i2pd/patches/patch-contrib_i2pd_conf diff -N net/i2pd/patches/patch-contrib_i2pd_conf --- net/i2pd/patches/patch-contrib_i2pd_conf 17 Oct 2026 08:47:19 -0000 1.1 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,41 +0,0 @@ -Index: contrib/i2pd.conf ---- contrib/i2pd.conf.orig -+++ contrib/i2pd.conf -@@ -8,16 +8,16 @@ - - ## Tunnels config file - ## Default: ~/.i2pd/tunnels.conf or /var/lib/i2pd/tunnels.conf --# tunconf = /var/lib/i2pd/tunnels.conf -+tunconf = ${SYSCONFDIR}/i2pd/tunnels.conf - - ## Tunnels config files path - ## Use that path to store separated tunnels in different config files. - ## Default: ~/.i2pd/tunnels.d or /var/lib/i2pd/tunnels.d --# tunnelsdir = /var/lib/i2pd/tunnels.d -+tunnelsdir = ${SYSCONFDIR}/i2pd/tunnels.d - - ## Path to certificates used for verifying .su3, families - ## Default: ~/.i2pd/certificates or /var/lib/i2pd/certificates --# certsdir = /var/lib/i2pd/certificates -+certsdir = ${SYSCONFDIR}/i2pd/certificates - - ## Where to write pidfile (default: /run/i2pd.pid, not used in Windows) - # pidfile = /run/i2pd.pid -@@ -30,7 +30,7 @@ - ## * stdout - print log entries to stdout - ## * file - log entries to a file - ## * syslog - use syslog, see man 3 syslog --# log = file -+log = syslog - ## Path to logfile (default: autodetect) - # logfile = /var/log/i2pd/i2pd.log - ## Log messages above this level (debug, info, *warn, error, critical, none) -@@ -122,7 +122,7 @@ ipv6 = false - [http] - ## Web Console settings - ## Enable the Web Console (default: true) --# enabled = true -+enabled = false - ## Address and port service will listen on (default: 127.0.0.1:7070) - # address = 127.0.0.1 - # port = 7070 Index: net/i2pd/patches/patch-daemon_Daemon_cpp =================================================================== RCS file: /cvs/ports/net/i2pd/patches/patch-daemon_Daemon_cpp,v diff -u -p -u -p -r1.1 patch-daemon_Daemon_cpp --- net/i2pd/patches/patch-daemon_Daemon_cpp 17 Oct 2026 08:47:19 -0000 1.1 +++ net/i2pd/patches/patch-daemon_Daemon_cpp 23 Oct 2026 12:54:04 -0000 @@ -1,14 +1,266 @@ -neuter broken pledge/unveil +Resolve configured paths before daemon startup changes directories, and +prepare OpenBSD sandbox settings using those absolute paths. Index: daemon/Daemon.cpp --- daemon/Daemon.cpp.orig +++ daemon/Daemon.cpp -@@ -107,7 +107,7 @@ namespace util - i2p::config::ParseConfig(config); +@@ -11,7 +11,12 @@ + #include <regex> + + #ifdef __OpenBSD__ +-# include<unistd.h> ++#include <unistd.h> ++#include <limits.h> ++#include <stdexcept> ++#include <cerrno> ++#include <cstring> ++#include <cstdlib> + #endif + + #include "Daemon.h" +@@ -43,6 +48,19 @@ namespace i2p + { + namespace util + { ++#ifdef __OpenBSD__ ++ // Keep paths stable across DaemonUnix::start()'s chdir(). The leaf need ++ // not exist yet (log files, for example), so realpath() is unsuitable. ++ static std::string AbsolutePath (const std::string& path) ++ { ++ if (path.empty () || path.front () == '/') return path; ++ char cwd[PATH_MAX]; ++ if (!getcwd (cwd, sizeof (cwd))) ++ throw std::runtime_error ("Daemon: Can't resolve working directory"); ++ return std::string (cwd) + "/" + path; ++ } ++#endif ++ + class Daemon_Singleton::Daemon_Singleton_Private + { + public: +@@ -91,7 +109,11 @@ namespace util + } + + i2p::fs::DetectDataDir(datadir, IsService()); ++#ifdef __OpenBSD__ ++ i2p::fs::DetectDataDir (AbsolutePath (i2p::fs::GetDataDir ()), IsService ()); ++#else + i2p::fs::Init(); ++#endif + + datadir = i2p::fs::GetDataDir(); + +@@ -108,156 +130,23 @@ namespace util i2p::config::Finalize(); + #ifdef __OpenBSD__ +- auto init_pledge = []() { +- std::string pledge_file; i2p::config::GetOption("openbsd.pledge_file", pledge_file); +- if (pledge_file == "") +- { +- LogPrint(eLogDebug, "Default pledge values are set"); +- // TODO: remove that not need +- // proc used in daemonunix, flock used in daemonunix, unix using +- pledge("stdio rpath wpath cpath inet dns unix recvfd sendfd proc error mcast chown flock",nullptr); +- } else { +- std::ifstream f(pledge_file); +- if(!f) { +- std::cerr << "Can't open pledge file " << pledge_file<<std::endl; +- exit(1); +- } +- std::string line; +- std::vector<std::string> rules; +- while(std::getline(f, line)){ +- rules.push_back(line); +- } +- if(f.bad()) { +- std::cerr << "IO error with pledge file" << std::endl; +- } +- std::ostringstream out; +- for(auto r : rules) +- out << r << " "; +- pledge(out.str().c_str(), nullptr); +- } +- +- +- }; +- auto unveil_path = [](std::string path, std::string rules = "r") ++ // A datadir in the configuration is not available during config lookup. ++ std::string configuredDataDir = DaemonDataDir; ++ if (configuredDataDir.empty ()) ++ i2p::config::GetOption ("datadir", configuredDataDir); ++ i2p::fs::DetectDataDir (configuredDataDir, IsService ()); ++ i2p::fs::DetectDataDir (AbsolutePath (i2p::fs::GetDataDir ()), IsService ()); ++ datadir = i2p::fs::GetDataDir (); ++ i2p::fs::Init (); ++ i2p::config::SetOption ("conf", AbsolutePath (config)); ++ for (const char * option : {"tunconf", "tunnelsdir", "certsdir", "logfile", "pidfile", ++ "reseed.file", "reseed.zipfile", "addressbook.hostsfile", ++ "openbsd.pledge_file", "openbsd.unevil_file"}) + { +- if(path=="") +- { +- LogPrint(eLogDebug, "empty path for unveil"); +- return; +- } +- if ( unveil(path.c_str(), rules.c_str()) == -1 ) +- { +- LogPrint(eLogError, "Can't unveil " + path + " with rules " + rules); +- throw std::runtime_error("can't unveil"); +- } +- LogPrint(eLogInfo, "unveil " + path + " with rules " + rules); +- }; +- auto init_unveil = [unveil_path](std::string datadir, std::string config) { +- +- std::string tunnelsdir, certsdir, logfile, reseed_file, openbsd_pledge_file, +- tunconf, pidfile, i2pcontrol_cert, i2pcontrol_key, unveil_file; +- +- i2p::config::GetOption("openbsd.unveil_file",unveil_file); +- i2p::config::GetOption("certsdir", certsdir); +- if(certsdir == "") +- { +- certsdir = datadir+"/certificates"; +- } +- i2p::config::GetOption("tunnelsdir", tunnelsdir); +- if(tunnelsdir == "") +- { +- tunnelsdir = datadir+"/tunnels.d"; +- } +- i2p::config::GetOption("reseed.file", reseed_file); +- i2p::config::GetOption("openbsd.pledge_file", openbsd_pledge_file); +- i2p::config::GetOption("tunconf", tunconf); +- if(tunconf == "") +- { +- tunconf = datadir+"/tunnels.conf"; +- } +- if(config == "") +- { +- config = datadir+"/i2pd.conf"; +- } +- i2p::config::GetOption("pidfile", pidfile); +- if(pidfile == "") +- { +- pidfile = datadir+"/i2pd.pid"; +- } +- i2p::config::GetOption("logfile", logfile); +- if(logfile == "") +- { +- logfile = datadir+"/logfile"; //for --service +- } +- i2p::config::GetOption("i2pcontrol.cert", i2pcontrol_cert); +- i2p::config::GetOption("i2pcontrol.key", i2pcontrol_key); +- if(i2pcontrol_cert == "i2pcontrol.crt.pem") +- { +- i2pcontrol_cert=datadir+"/"+i2pcontrol_cert; +- } +- if(i2pcontrol_key == "i2pcontrol.key.pem") +- { +- i2pcontrol_key=datadir+"/"+i2pcontrol_key; +- } +- //constexpr can't be here and it's eat a lot of memory... +-/* +- auto rules = std::map<std::string,std::string>{ +- {datadir,"rwc"}, +- {"/tmp","rwc"}, +- {unveil_file, "r"}, +- {tunnelsdir, "r"}, +- {certsdir, "r"}, +- {reseed_file, "r"}, +- {openbsd_pledge_file, "r"}, +- {tunconf, "r"}, +- {config, "r"}, +- {pidfile, "rwc"}, +- {logfile, "rwc"}, +- {i2pcontrol_cert, "rwc"}, +- {i2pcontrol_key, "rwc"} +- }; +- +- for (const auto& [path, privilegies] : rules) +- { +- unveil_path(path, privilegies); +- } +-*/ +- unveil_path(datadir, "rwc"); +- unveil_path(unveil_file, "r"); +- unveil_path(tunnelsdir, "r"); +- unveil_path(certsdir, "r"); +- unveil_path(reseed_file, "r"); +- unveil_path(openbsd_pledge_file, "r"); +- unveil_path(tunconf, "r"); +- unveil_path(config, "r"); +- unveil_path(pidfile, "rwc"); +- unveil_path(logfile, "rwc"); +- unveil_path(i2pcontrol_cert, "rwc"); +- unveil_path(i2pcontrol_key, "rwc"); +- +- +- if( unveil_file.length() ) +- { +- std::ifstream f(unveil_file); +- if (!f) { +- std::cerr << "Can't open unveil file" << std::endl; +- exit(1); +- } +- std::string line; +- while(std::getline(f, line)){ +- //UNVEIL_DIR(line);// maybe not need c +- unveil_path(line.c_str(), "rwc"); +- } +- } +- +- if( unveil(NULL, NULL) == -1 ) +- { +- throw std::runtime_error("Can't unveil something"); +- } +- }; +- bool openbsd_unveil_enabled; i2p::config::GetOption("openbsd.unveil_enabled", openbsd_unveil_enabled); +- bool openbsd_pledge_enabled; i2p::config::GetOption("openbsd.pledge_enabled", openbsd_pledge_enabled); +- if(openbsd_unveil_enabled) init_unveil(datadir, config); ++ std::string path; ++ i2p::config::GetOption (option, path); ++ if (!path.empty ()) i2p::config::SetOption (option, AbsolutePath (path)); ++ } + #endif + + i2p::config::GetOption("daemon", isDaemon); +@@ -291,6 +180,9 @@ namespace util + } else if (logs == "file") { + if (logfile == "") + logfile = i2p::fs::DataDirPath("i2pd.log"); ++#ifdef __OpenBSD__ ++ i2p::config::SetOption ("logfile", logfile); ++#endif + LogPrint(eLogInfo, "Log: Sending messages to ", logfile); + i2p::log::Logger().SendTo (logfile); + #ifndef _WIN32 +@@ -311,6 +203,25 @@ namespace util + bool ssu; i2p::config::GetOption("ssu", ssu); + if (!ssu && i2p::config::IsDefault ("precomputation.elgamal")) + precomputation = false; // we don't elgamal table if no ssu, unless it's specified explicitly ++#ifdef __OpenBSD__ ++ // Keep startup-only promises until fork/setsid/setrlimit and unveil. ++ // Explicit pledge policies retain their existing override semantics. ++ bool usePledge; i2p::config::GetOption ("openbsd.pledge_enabled", usePledge); ++ std::string policy; i2p::config::GetOption ("openbsd.pledge_file", policy); ++ if (usePledge && policy.empty ()) ++ { ++ std::string promises = "stdio rpath wpath cpath fattr inet dns unix flock proc"; ++ bool useUnveil; i2p::config::GetOption ("openbsd.unevil_enabled", useUnveil); ++ if (useUnveil) promises += " unveil"; ++ bool upnp; i2p::config::GetOption ("upnp.enabled", upnp); ++ if (upnp) promises += " mcast"; ++ if (pledge (promises.c_str (), nullptr) == -1) ++ { ++ std::cerr << "Daemon: startup pledge: " << strerror (errno) << std::endl; ++ std::exit (EXIT_FAILURE); ++ } ++ } ++#endif + i2p::crypto::InitCrypto (precomputation); + + i2p::transport::InitAddressFromIface (); // get address4/6 from interfaces +@@ -472,4 +382,0 @@ namespace util -#ifdef __OpenBSD__ -+#if 0 - auto init_pledge = []() { - std::string pledge_file; i2p::config::GetOption("openbsd.pledge_file", pledge_file); - if (pledge_file == "") +-//SIOCGIFMTU is not found, SOCK DGRAM not found +- if(openbsd_pledge_enabled) init_pledge(); +-#endif Index: net/i2pd/patches/patch-daemon_I2PControl_cpp =================================================================== RCS file: net/i2pd/patches/patch-daemon_I2PControl_cpp diff -N net/i2pd/patches/patch-daemon_I2PControl_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-daemon_I2PControl_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,15 @@ +Use the OpenSSL 3 RSA helper only with OpenSSL; LibreSSL follows the +compatible legacy key creation path. + +Index: daemon/I2PControl.cpp +--- daemon/I2PControl.cpp.orig ++++ daemon/I2PControl.cpp +@@ -485,7 +485,7 @@ namespace client + void I2PControlService::CreateCertificate (const char *crt_path, const char *key_path) + { + FILE *f = NULL; +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + EVP_PKEY * pkey = EVP_RSA_gen(4096); // e = 65537 + #else + EVP_PKEY * pkey = EVP_PKEY_new (); Index: net/i2pd/patches/patch-daemon_UnixDaemon_cpp =================================================================== RCS file: net/i2pd/patches/patch-daemon_UnixDaemon_cpp diff -N net/i2pd/patches/patch-daemon_UnixDaemon_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-daemon_UnixDaemon_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,263 @@ +Handle OpenBSD signals in the daemon loop and install pledge and unveil +rules after startup-only operations have finished. + +Index: daemon/UnixDaemon.cpp +--- daemon/UnixDaemon.cpp.orig ++++ daemon/UnixDaemon.cpp +@@ -19,6 +19,18 @@ + #include <sys/stat.h> + #include <sys/resource.h> + ++#ifdef __OpenBSD__ ++#include <fstream> ++#include <vector> ++#include <cerrno> ++#include <cstring> ++#include <cstdlib> ++#include <limits.h> ++#include <map> ++#include <syslog.h> ++#include <pthread.h> ++#endif ++ + #include "Config.h" + #include "FS.h" + #include "Log.h" +@@ -33,6 +45,18 @@ const int GRACEFUL_SHUTDOWN_INTERVAL = 1 + // a handler may only touch volatile sig_atomic_t, everything else is done by the daemon loop + static volatile sig_atomic_t g_PendingSignals[NSIG] = {}; + ++#ifdef __OpenBSD__ ++// Blocked before service threads are created; consumed only by run(). ++static sigset_t daemonSignals; ++ ++static bool SignalError (const char * operation, int error) ++{ ++ std::cerr << "Daemon: " << operation << ": " << strerror (error) << std::endl; ++ syslog (LOG_DAEMON | LOG_ERR, "Daemon: %s: %s", operation, strerror (error)); ++ return false; ++} ++#endif ++ + void handle_signal(int sig) + { + int savedErrno = errno; +@@ -210,8 +234,29 @@ namespace i2p + gracefulShutdownInterval = 0; // not specified + + // handle signal TSTP +- bool handleTSTP; i2p::config::GetOption("unix.handle_sigtstp", handleTSTP); ++ bool handleTSTP = false; i2p::config::GetOption("unix.handle_sigtstp", handleTSTP); + ++#ifdef __OpenBSD__ ++ // ReloadConfig() can stop and join destination threads. Never run ++ // it from an asynchronous handler on an arbitrary service thread. ++ sigemptyset (&daemonSignals); ++ for (int sig : {SIGHUP, SIGUSR1, SIGABRT, SIGTERM, SIGINT, SIGPIPE}) ++ sigaddset (&daemonSignals, sig); ++ if (handleTSTP) ++ { ++ sigaddset (&daemonSignals, SIGTSTP); ++ sigaddset (&daemonSignals, SIGCONT); ++ } ++ int error = pthread_sigmask (SIG_BLOCK, &daemonSignals, nullptr); ++ if (error) return SignalError ("pthread_sigmask", error); ++ // Inherited SIG_IGN dispositions could discard a blocked signal. ++ struct sigaction sa{}; ++ sa.sa_handler = SIG_DFL; ++ sigemptyset (&sa.sa_mask); ++ for (int sig = 1; sig < NSIG; ++sig) ++ if (sigismember (&daemonSignals, sig) == 1 && sigaction (sig, &sa, nullptr) == -1) ++ return SignalError ("sigaction", errno); ++#else + // Signal handler + struct sigaction sa; + sa.sa_handler = handle_signal; +@@ -229,13 +274,150 @@ namespace i2p + sigaction(SIGCONT, &sa, 0); + } + ++#endif ++ ++#ifdef __OpenBSD__ ++ // Resolve/read sandbox policy before the first unveil hides it. ++ bool useUnveil, usePledge; ++ i2p::config::GetOption ("openbsd.unevil_enabled", useUnveil); ++ i2p::config::GetOption ("openbsd.pledge_enabled", usePledge); ++ // CreatePrivateFile() uses chmod when saving router and destination keys. ++ std::string promises = "stdio rpath wpath cpath fattr inet dns unix flock"; ++ bool upnp; i2p::config::GetOption ("upnp.enabled", upnp); ++ if (upnp) promises += " mcast"; ++ std::vector<std::string> extraPaths; ++ auto fail = [](const std::string& message) { ++ std::cerr << message << std::endl; ++ // The logger thread has not started, and daemon stderr is /dev/null. ++ syslog (LOG_DAEMON | LOG_ERR, "%s", message.c_str ()); ++ return false; ++ }; ++ auto readPolicy = [&fail](const char * option, std::vector<std::string>& lines) { ++ std::string path; i2p::config::GetOption (option, path); ++ if (path.empty ()) return true; ++ std::ifstream f (path); ++ if (!f) ++ { ++ return fail (std::string ("Daemon: Can't open ") + option + ": " + path); ++ } ++ std::string line; ++ while (std::getline (f, line)) ++ if (!line.empty ()) lines.push_back (line); ++ if (f.bad ()) ++ { ++ return fail (std::string ("Daemon: Can't read ") + option + ": " + path); ++ } ++ return true; ++ }; ++ if (usePledge) ++ { ++ std::string policy; i2p::config::GetOption ("openbsd.pledge_file", policy); ++ if (!policy.empty ()) ++ { ++ std::vector<std::string> lines; ++ if (!readPolicy ("openbsd.pledge_file", lines)) return false; ++ promises.clear (); ++ for (const auto& line : lines) promises += line + " "; ++ } ++ } ++ if (useUnveil && !readPolicy ("openbsd.unevil_file", extraPaths)) return false; ++ if (useUnveil) ++ { ++ // Coalesce aliases before unveil: repeated rules may only reduce ++ // permissions. For example, certsdir may equal datadir. ++ std::map<std::string, std::string> rules; ++ auto allow = [&rules, &fail](std::string path, const char * permissions) { ++ if (path.empty ()) return true; ++ while (path.size () > 1 && path.back () == '/') path.pop_back (); ++ char resolved[PATH_MAX]; ++ if (realpath (path.c_str (), resolved)) path = resolved; ++ else ++ { ++ if (errno != ENOENT) ++ return fail ("Daemon: resolve " + path + ": " + strerror (errno)); ++ // A missing leaf is remembered in its existing parent by ++ // unveil, without giving access to sibling files. ++ auto slash = path.find_last_of ('/'); ++ std::string parent = slash == std::string::npos ? "." : path.substr (0, slash + 1); ++ if (!realpath (parent.c_str (), resolved)) ++ return fail ("Daemon: resolve parent of " + path + ": " + strerror (errno)); ++ path = std::string (resolved) + "/" + path.substr (slash == std::string::npos ? 0 : slash + 1); ++ } ++ auto& mask = rules[path]; ++ for (const char * p = permissions; *p; ++p) ++ if (mask.find (*p) == std::string::npos) mask += *p; ++ return true; ++ }; ++ if (!allow (i2p::fs::GetDataDir (), "rwc") || ++ !allow (i2p::fs::GetCertsDir (), "r") || !allow (pidfile, "rwc")) return false; ++ for (const char * option : {"conf", "tunconf", "tunnelsdir", "reseed.file", "reseed.zipfile"}) ++ { ++ std::string path; i2p::config::GetOption (option, path); ++ if (!allow (path, "r")) return false; ++ } ++ for (const char * option : {"logfile", "addressbook.hostsfile"}) ++ { ++ std::string path; i2p::config::GetOption (option, path); ++ if (!allow (path, "rwc")) return false; ++ } ++ bool control; i2p::config::GetOption ("i2pcontrol.enabled", control); ++ if (control) ++ { ++ for (const char * option : {"i2pcontrol.cert", "i2pcontrol.key"}) ++ { ++ std::string path; i2p::config::GetOption (option, path); ++ if (!path.empty () && path.front () != '/') path = i2p::fs::DataDirPath (path); ++ if (!allow (path, "rwc")) return false; ++ } ++ uint16_t port; i2p::config::GetOption ("i2pcontrol.port", port); ++ if (!port) ++ { ++ std::string path; i2p::config::GetOption ("i2pcontrol.address", path); ++ if (!allow (path, "rwc")) return false; ++ } ++ } ++ bool outproxy; i2p::config::GetOption ("socksproxy.outproxy.enabled", outproxy); ++ uint16_t port; i2p::config::GetOption ("socksproxy.outproxyport", port); ++ if (outproxy && !port) ++ { ++ std::string path; i2p::config::GetOption ("socksproxy.outproxy", path); ++ if (!allow (path, "w")) return false; ++ } ++ // miniupnpc consults MiniSSDPD before multicast discovery. ++ if (upnp && !allow ("/var/run/minissdpd.sock", "w")) return false; ++ for (const auto& path : extraPaths) ++ if (!allow (path, "rwc")) return false; ++ for (const auto& rule : rules) ++ if (unveil (rule.first.c_str (), rule.second.c_str ()) == -1) ++ return fail ("Daemon: unveil " + rule.first + ": " + strerror (errno)); ++ if (unveil (nullptr, nullptr) == -1) ++ { ++ return fail (std::string ("Daemon: unveil lock: ") + strerror (errno)); ++ } ++ } ++ // fork/setsid/setrlimit and PID locking are complete; no proc, ++ // id, chown, descriptor passing or error promise is needed. ++ if (usePledge && pledge (promises.c_str (), nullptr) == -1) ++ { ++ return fail (std::string ("Daemon: pledge: ") + strerror (errno)); ++ } ++#endif ++ + return Daemon_Singleton::start(); + } + + bool DaemonUnix::stop() + { ++#ifdef __OpenBSD__ ++ // Keep the PID lock while router data is still being saved. ++ bool stopped = Daemon_Singleton::stop(); ++ i2p::fs::Remove(pidfile); ++ close (pidFH); ++ return stopped; ++#else + i2p::fs::Remove(pidfile); + return Daemon_Singleton::stop(); ++#endif + } + + void DaemonUnix::run () +@@ -244,6 +426,36 @@ namespace i2p + while (running) + { + std::this_thread::sleep_for (std::chrono::seconds(1)); ++#ifdef __OpenBSD__ ++ // Poll at the existing one-second shutdown tick. Only this thread ++ // consumes these signals, so a pending signal cannot be stolen ++ // between sigpending() and sigwait(). ++ sigset_t pending; ++ if (sigpending (&pending) == -1) ++ { ++ SignalError ("sigpending", errno); ++ running = false; ++ break; ++ } ++ for (int sig = 1; sig < NSIG; ++sig) ++ if (sigismember (&daemonSignals, sig) == 1 && sigismember (&pending, sig) == 1) ++ { ++ sigset_t one; ++ sigemptyset (&one); ++ sigaddset (&one, sig); ++ int received; ++ int error = sigwait (&one, &received); ++ if (error) ++ { ++ SignalError ("sigwait", error); ++ running = false; ++ break; ++ } ++ handle_signal (received); ++ if (!running) break; ++ } ++ if (!running) break; ++#endif + HandlePendingSignals(); + if (gracefulShutdownInterval) + { Index: net/i2pd/patches/patch-libi2pd_Config_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_Config_cpp diff -N net/i2pd/patches/patch-libi2pd_Config_cpp --- net/i2pd/patches/patch-libi2pd_Config_cpp 17 Oct 2026 08:47:19 -0000 1.1 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,23 +0,0 @@ -neuter broken pledge/unveil - -Index: libi2pd/Config.cpp ---- libi2pd/Config.cpp.orig -+++ libi2pd/Config.cpp -@@ -396,7 +396,7 @@ namespace config { - ; - #endif - --#ifdef __OpenBSD__ -+#if 0 - options_description openbsd_specific("OpenBSD specific options"); - openbsd_specific.add_options() - ("openbsd.pledge_file", value<std::string>()->default_value(""), "OpenbSD file with pledge rules") -@@ -433,7 +433,7 @@ namespace config { - #ifdef __linux__ - .add(unix_specific) - #endif --#ifdef __OpenBSD__ -+#if 0 - .add(openbsd_specific) - #endif - ; Index: net/i2pd/patches/patch-libi2pd_Crypto_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_Crypto_cpp diff -N net/i2pd/patches/patch-libi2pd_Crypto_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_Crypto_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,100 @@ +Select OpenSSL 3 APIs only for OpenSSL and validate cryptographic key +and parameter operations on the supported crypto backends. + +Index: libi2pd/Crypto.cpp +--- libi2pd/Crypto.cpp.orig ++++ libi2pd/Crypto.cpp +@@ -11,13 +11,14 @@ + #include <vector> + #include <mutex> + #include <memory> ++#include <limits> + #include <openssl/dh.h> + #include <openssl/md5.h> + #include <openssl/crypto.h> + #include "TunnelBase.h" + #include <openssl/ssl.h> + #include <openssl/kdf.h> +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + #include <openssl/param_build.h> + #include <openssl/core_names.h> + #endif +@@ -149,7 +150,7 @@ namespace crypto + #define dsap GetCryptoConstants ().dsap + #define dsaq GetCryptoConstants ().dsaq + #define dsag GetCryptoConstants ().dsag +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + EVP_PKEY * CreateDSA (BIGNUM * pubKey, BIGNUM * privKey) + { + EVP_PKEY * pkey = nullptr; +@@ -650,40 +651,39 @@ namespace crypto + static bool AEADChaCha20Poly1305 (EVP_CIPHER_CTX * ctx, const uint8_t * msg, size_t msgLen, + const uint8_t * ad, size_t adLen, const uint8_t * key, const uint8_t * nonce, uint8_t * buf, size_t len, bool encrypt) + { +- if (!ctx || len < msgLen) return false; +- if (encrypt && len < msgLen + 16) return false; +- bool ret = true; +- int outlen = 0; ++ if (!ctx || len < msgLen || (encrypt && len - msgLen < 16)) return false; ++ // EVP update lengths are int in both OpenSSL and LibreSSL. ++ if (msgLen > (size_t)std::numeric_limits<int>::max () || ++ adLen > (size_t)std::numeric_limits<int>::max ()) return false; ++ int outlen = 0, finalLen = 0; + if (encrypt) + { +- EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0); +- EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0); +- EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce); +- EVP_EncryptUpdate(ctx, NULL, &outlen, ad, adLen); +- EVP_EncryptUpdate(ctx, buf, &outlen, msg, msgLen); +- EVP_EncryptFinal_ex(ctx, buf + outlen, &outlen); +- EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, buf + msgLen); ++ if (EVP_EncryptInit_ex (ctx, EVP_chacha20_poly1305 (), nullptr, nullptr, nullptr) != 1 || ++ EVP_CIPHER_CTX_ctrl (ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr) != 1 || ++ EVP_EncryptInit_ex (ctx, nullptr, nullptr, key, nonce) != 1) return false; ++ if (adLen && EVP_EncryptUpdate (ctx, nullptr, &outlen, ad, (int)adLen) != 1) return false; ++ outlen = 0; ++ if (msgLen && EVP_EncryptUpdate (ctx, buf, &outlen, msg, (int)msgLen) != 1) return false; ++ if (outlen != (int)msgLen || EVP_EncryptFinal_ex (ctx, buf + outlen, &finalLen) != 1 || finalLen != 0) return false; ++ return EVP_CIPHER_CTX_ctrl (ctx, EVP_CTRL_AEAD_GET_TAG, 16, buf + msgLen) == 1; + } +- else +- { + #if defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x4000000fL +- std::vector<uint8_t> m(msgLen + 16); +- if (msg == buf) +- { +- // we have to use different buffers otherwise verification fails +- memcpy (m.data (), msg, msgLen + 16); +- msg = m.data (); +- } +-#endif +- EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), 0, 0, 0); +- EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, 0); +- EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, (uint8_t *)(msg + msgLen)); +- EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce); +- EVP_DecryptUpdate(ctx, NULL, &outlen, ad, adLen); +- EVP_DecryptUpdate(ctx, buf, &outlen, msg, msgLen); +- ret = EVP_DecryptFinal_ex(ctx, buf + outlen, &outlen) > 0; ++ std::vector<uint8_t> m; ++ if (msg == buf) ++ { ++ // Older LibreSSL cannot authenticate in-place decryption. ++ m.assign (msg, msg + msgLen + 16); ++ msg = m.data (); + } +- return ret; ++#endif ++ if (EVP_DecryptInit_ex (ctx, EVP_chacha20_poly1305 (), nullptr, nullptr, nullptr) != 1 || ++ EVP_CIPHER_CTX_ctrl (ctx, EVP_CTRL_AEAD_SET_IVLEN, 12, nullptr) != 1 || ++ EVP_DecryptInit_ex (ctx, nullptr, nullptr, key, nonce) != 1 || ++ EVP_CIPHER_CTX_ctrl (ctx, EVP_CTRL_AEAD_SET_TAG, 16, (void *)(msg + msgLen)) != 1) return false; ++ if (adLen && EVP_DecryptUpdate (ctx, nullptr, &outlen, ad, (int)adLen) != 1) return false; ++ outlen = 0; ++ if (msgLen && EVP_DecryptUpdate (ctx, buf, &outlen, msg, (int)msgLen) != 1) return false; ++ return outlen == (int)msgLen && EVP_DecryptFinal_ex (ctx, buf + outlen, &finalLen) == 1 && finalLen == 0; + } + + bool AEADChaCha20Poly1305 (const uint8_t * msg, size_t msgLen, const uint8_t * ad, size_t adLen, Index: net/i2pd/patches/patch-libi2pd_Crypto_h =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_Crypto_h diff -N net/i2pd/patches/patch-libi2pd_Crypto_h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_Crypto_h 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,32 @@ +Detect post-quantum and Ed25519ph support separately for OpenSSL and +LibreSSL. + +Index: libi2pd/Crypto.h +--- libi2pd/Crypto.h.orig ++++ libi2pd/Crypto.h +@@ -31,10 +31,15 @@ + #if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER != 0x030000000)) // 3.0.0, regression in SipHash, not implemented in LibreSSL + # define OPENSSL_SIPHASH 1 + #endif +-#if (OPENSSL_VERSION_NUMBER >= 0x030500000) || ( (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x4030000fL)) ) // 3.5.0 openssl, 4.3.0 libressl ++#if (!defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x030500000) || ( (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x4030000fL)) ) // 3.5.0 openssl, 4.3.0 libressl + # define OPENSSL_PQ 1 + #endif + ++// The Ed25519ph instance parameter first appeared in OpenSSL 3.2. ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30200000L ++# define OPENSSL_ED25519PH 1 ++#endif ++ + namespace i2p + { + namespace crypto +@@ -42,7 +47,7 @@ namespace crypto + bool bn2buf (const BIGNUM * bn, uint8_t * buf, size_t len); + + // DSA +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + EVP_PKEY * CreateDSA (BIGNUM * pubKey = nullptr, BIGNUM * privKey = nullptr); + #else + DSA * CreateDSA (); Index: net/i2pd/patches/patch-libi2pd_Destination_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_Destination_cpp diff -N net/i2pd/patches/patch-libi2pd_Destination_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_Destination_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,36 @@ +Reject unsupported post-quantum encryption key types when parsing +destinations. + +Index: libi2pd/Destination.cpp +--- libi2pd/Destination.cpp.orig ++++ libi2pd/Destination.cpp +@@ -15,6 +15,7 @@ + #include <future> + #include <boost/algorithm/string.hpp> + #include "Crypto.h" ++#include "PostQuantum.h" + #include "ECIESX25519AEADRatchetSession.h" + #include "Log.h" + #include "FS.h" +@@ -1051,7 +1052,10 @@ namespace client + try + { + i2p::data::CryptoKeyType cryptoType = std::stoi(it1); +-#if !OPENSSL_PQ ++#if OPENSSL_PQ ++ if (cryptoType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD || ++ i2p::crypto::IsMLKEMSupported (cryptoType)) ++#else + if (cryptoType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported + #endif + { +@@ -1085,6 +1089,9 @@ namespace client + + for (auto& it: encryptionKeyTypes) + { ++#if OPENSSL_PQ ++ if (it > i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && !i2p::crypto::IsMLKEMSupported (it)) continue; ++#endif + auto encryptionKey = std::make_shared<i2p::crypto::LocalEncryptionKey> (it); + if (IsPublic ()) + PersistTemporaryKeys (encryptionKey); Index: net/i2pd/patches/patch-libi2pd_ECIESX25519AEADRatchetSession_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_ECIESX25519AEADRatchetSession_cpp diff -N net/i2pd/patches/patch-libi2pd_ECIESX25519AEADRatchetSession_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_ECIESX25519AEADRatchetSession_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,60 @@ +Check ML-KEM key creation and key operations before continuing ECIES +ratchet handshakes. + +Index: libi2pd/ECIESX25519AEADRatchetSession.cpp +--- libi2pd/ECIESX25519AEADRatchetSession.cpp.orig ++++ libi2pd/ECIESX25519AEADRatchetSession.cpp +@@ -308,7 +308,7 @@ namespace garlic + len -= keyLen + 16; + + m_PQKeys = i2p::crypto::CreateMLKEMKeys (cryptoType); +- m_PQKeys->SetPublicKey (encapsKey.data ()); ++ if (!m_PQKeys || !m_PQKeys->SetPublicKey (encapsKey.data ())) return false; + } + } + // if PQ failed it's valid situation meaning regular ECIES_X25519(type 4) +@@ -589,7 +589,7 @@ namespace garlic + { + i2p::crypto::InitNoiseIKStateMLKEM (GetNoiseState (), m_RemoteStaticKeyType, m_RemoteStaticKey); // bpk + m_PQKeys = i2p::crypto::CreateMLKEMKeys (m_RemoteStaticKeyType); +- m_PQKeys->GenerateKeys (); ++ if (!m_PQKeys || !m_PQKeys->GenerateKeys ()) return false; + } + else + #endif +@@ -607,7 +607,7 @@ namespace garlic + { + auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (m_RemoteStaticKeyType); + std::vector<uint8_t> encapsKey(keyLen); +- m_PQKeys->GetPublicKey (encapsKey.data ()); ++ if (!m_PQKeys || !m_PQKeys->GetPublicKey (encapsKey.data ())) return false; + // encrypt encapsKey + if (!Encrypt (encapsKey.data (), out + offset, keyLen)) + { +@@ -698,7 +698,7 @@ namespace garlic + memcpy (m_NSRCK->data (), m_CK, 64); + size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType); + std::vector<uint8_t> kemCiphertext(cipherTextLen); +- m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret); ++ if (!m_PQKeys || !m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret)) return false; + + if (!Encrypt (kemCiphertext.data (), out + offset, cipherTextLen)) + { +@@ -770,7 +770,7 @@ namespace garlic + size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_RemoteStaticKeyType); + std::vector<uint8_t> kemCiphertext(cipherTextLen); + uint8_t sharedSecret[32]; +- m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret); ++ if (!m_PQKeys || !m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret)) return false; + + memcpy (m_CK, m_NSRCK->data (), 64); // restore key + if (!Encrypt (kemCiphertext.data (), out + offset, cipherTextLen)) +@@ -854,7 +854,7 @@ namespace garlic + buf += cipherTextLen + 16; + len -= cipherTextLen + 16; + // decaps +- m_PQKeys->Decaps (kemCiphertext.data (), sharedSecret); ++ if (!m_PQKeys || !m_PQKeys->Decaps (kemCiphertext.data (), sharedSecret)) return false; + MixKey (sharedSecret); + } + #endif Index: net/i2pd/patches/patch-libi2pd_FS_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_FS_cpp diff -N net/i2pd/patches/patch-libi2pd_FS_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_FS_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,18 @@ +Use /var/i2pd for the OpenBSD service data directory. + +Index: libi2pd/FS.cpp +--- libi2pd/FS.cpp.orig ++++ libi2pd/FS.cpp +@@ -108,8 +108,12 @@ namespace fs { + else + dataDir = (fs_lib::path(commonAppData)/appName).string (); + #else ++#ifdef __OpenBSD__ ++ dataDir = "/var/" + appName; ++#else + dataDir = "/var/lib/" + appName; + #endif ++#endif + return; + } + #endif Index: net/i2pd/patches/patch-libi2pd_Family_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_Family_cpp diff -N net/i2pd/patches/patch-libi2pd_Family_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_Family_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,74 @@ +Identify supported family signing curves on both OpenSSL and LibreSSL, +and reject invalid or oversized family names. + +Index: libi2pd/Family.cpp +--- libi2pd/Family.cpp.orig ++++ libi2pd/Family.cpp +@@ -18,6 +18,20 @@ namespace i2p + { + namespace data + { ++ static int GetFamilyKeyCurve (EVP_PKEY * pkey) ++ { ++ if (!pkey || EVP_PKEY_base_id (pkey) != EVP_PKEY_EC) return -1; ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L ++ char groupName[80]; ++ if (EVP_PKEY_get_group_name (pkey, groupName, sizeof (groupName), nullptr) != 1) return -1; ++ return OBJ_txt2nid (groupName); ++#else ++ const EC_KEY * ec = EVP_PKEY_get0_EC_KEY (pkey); ++ const EC_GROUP * group = ec ? EC_KEY_get0_group (ec) : nullptr; ++ return group ? EC_GROUP_get_curve_name (group) : -1; ++#endif ++ } ++ + Families::Families () + { + } +@@ -51,15 +65,8 @@ namespace data + auto pkey = X509_get_pubkey (cert); + if (pkey) + { +- int curve = 0; +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 +- char groupName[20]; +- if (EVP_PKEY_get_group_name(pkey, groupName, sizeof(groupName), NULL) == 1) +- curve = OBJ_txt2nid (groupName); +- else +- curve = -1; +-#endif +- if (!curve || curve == NID_X9_62_prime256v1) ++ int curve = GetFamilyKeyCurve (pkey); ++ if (curve == NID_X9_62_prime256v1) + { + if (!m_SigningKeys.emplace (cn, std::make_pair(pkey, (int)m_SigningKeys.size () + 1)).second) + { +@@ -68,7 +75,10 @@ namespace data + } + } + else ++ { ++ EVP_PKEY_free (pkey); + LogPrint (eLogWarning, "Family: elliptic curve ", curve, " is not supported"); ++ } + } + } + } +@@ -156,15 +166,8 @@ namespace data + { + SSL * ssl = SSL_new (ctx); + EVP_PKEY * pkey = SSL_get_privatekey (ssl); +- int curve = 0; +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 +- char groupName[20]; +- if (EVP_PKEY_get_group_name(pkey, groupName, sizeof(groupName), NULL) == 1) +- curve = OBJ_txt2nid (groupName); +- else +- curve = -1; +-#endif +- if ((!curve || curve == NID_X9_62_prime256v1) && family.length () + 32 <= 100) ++ int curve = GetFamilyKeyCurve (pkey); ++ if (curve == NID_X9_62_prime256v1 && family.length () + 32 <= 100) + { + uint8_t buf[100], sign[72], signature[64]; + size_t len = family.length (); Index: net/i2pd/patches/patch-libi2pd_LeaseSet_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_LeaseSet_cpp diff -N net/i2pd/patches/patch-libi2pd_LeaseSet_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_LeaseSet_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,26 @@ +Validate post-quantum key types when choosing encryption keys for lease +sets. + +Index: libi2pd/LeaseSet.cpp +--- libi2pd/LeaseSet.cpp.orig ++++ libi2pd/LeaseSet.cpp +@@ -9,6 +9,7 @@ + #include <string.h> + #include "I2PEndian.h" + #include "Crypto.h" ++#include "PostQuantum.h" + #include "Log.h" + #include "Tag.h" + #include "Timestamp.h" +@@ -420,7 +421,10 @@ namespace data + i2p::crypto::GetCryptoPublicKeyLen (keyType) == encryptionKeyLen) // key length mismatch or unknown key type + { + // we pick max key type if preferred not found +-#if !OPENSSL_PQ ++#if OPENSSL_PQ ++ if (keyType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD || ++ i2p::crypto::IsMLKEMSupported (keyType)) ++#else + if (keyType <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) // skip PQ keys if not supported + #endif + { Index: net/i2pd/patches/patch-libi2pd_NTCP2_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_NTCP2_cpp diff -N net/i2pd/patches/patch-libi2pd_NTCP2_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_NTCP2_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,78 @@ +Check ML-KEM setup and key operations during NTCP2 handshakes so +failures abort the handshake instead of using invalid key material. + +Index: libi2pd/NTCP2.cpp +--- libi2pd/NTCP2.cpp.orig ++++ libi2pd/NTCP2.cpp +@@ -163,7 +163,7 @@ namespace transport + encryption.Encrypt (pub, 32, m_IV, m_Buffer); // X + // ML-KEM encap_key + m_PQKeys = i2p::crypto::CreateMLKEMKeys (m_CryptoType); +- m_PQKeys->GenerateKeys (); ++ if (!m_PQKeys || !m_PQKeys->GenerateKeys ()) return false; + } + else + encryption.Encrypt (GetPub (), 32, m_IV, m_Buffer); // X +@@ -183,7 +183,7 @@ namespace transport + // ML-KEM frame + auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (m_CryptoType); + std::vector<uint8_t> encapsKey(keyLen); +- m_PQKeys->GetPublicKey (encapsKey.data ()); ++ if (!m_PQKeys || !m_PQKeys->GetPublicKey (encapsKey.data ())) return false; + // encrypt encapsKey + if (!Encrypt (encapsKey.data (), m_Buffer + offset, keyLen)) + { +@@ -255,12 +255,13 @@ namespace transport + maxPaddingLength -= 64; + size_t maxMsgSize = m_MaxMsgSize; + #if OPENSSL_PQ +- if (m_PQKeys) ++ if (m_CryptoType > i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + { ++ if (!m_PQKeys) return false; + size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_CryptoType); + std::vector<uint8_t> kemCiphertext(cipherTextLen); + uint8_t sharedSecret[32]; +- m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret); ++ if (!m_PQKeys || !m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret)) return false; + if (!Encrypt (kemCiphertext.data (), m_Buffer + offset, cipherTextLen)) + { + LogPrint (eLogWarning, "NTCP2: SessionCreated ML-KEM ciphertext section AEAD encryption failed"); +@@ -378,7 +379,7 @@ namespace transport + MixHash (m_Buffer + offset, keyLen + 16); + offset += keyLen + 16; + m_PQKeys = i2p::crypto::CreateMLKEMKeys (m_CryptoType); +- m_PQKeys->SetPublicKey (encapsKey.data ()); ++ if (!m_PQKeys || !m_PQKeys->SetPublicKey (encapsKey.data ())) return false; + } + } + #endif +@@ -450,8 +451,9 @@ namespace transport + return false; + } + #if OPENSSL_PQ +- if (m_CryptoType > i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD && m_PQKeys) ++ if (m_CryptoType > i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD) + { ++ if (!m_PQKeys) return false; + // decrypt kem_ciphertext frame + size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (m_CryptoType); + std::vector<uint8_t> kemCiphertext(cipherTextLen); +@@ -465,7 +467,7 @@ namespace transport + offset += cipherTextLen + 16; + // decaps + uint8_t sharedSecret[32]; +- m_PQKeys->Decaps (kemCiphertext.data (), sharedSecret); ++ if (!m_PQKeys || !m_PQKeys->Decaps (kemCiphertext.data (), sharedSecret)) return false; + MixKey (sharedSecret); + } + #endif +@@ -2318,7 +2320,7 @@ namespace transport + { + #if OPENSSL_PQ + #ifdef LIBRESSL_VERSION_NUMBER +- if (version == 3 || version == 5) version = 2; // 3 and 5 are not supported by LibreSSL ++ if (version == 3) version = 2; // ML-KEM-512 is not supported by LibreSSL + #endif + m_Version = version; + #endif Index: net/i2pd/patches/patch-libi2pd_PostQuantum_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_PostQuantum_cpp diff -N net/i2pd/patches/patch-libi2pd_PostQuantum_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_PostQuantum_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,351 @@ +Use rank-specific LibreSSL ML-KEM keys and securely release operation +buffers; propagate failures from key generation and encapsulation +operations. + +Index: libi2pd/PostQuantum.cpp +--- libi2pd/PostQuantum.cpp.orig ++++ libi2pd/PostQuantum.cpp +@@ -6,6 +6,8 @@ + * See full license text in LICENSE file at top of project tree + */ + ++#include <cstring> ++#include <openssl/crypto.h> + #include "Log.h" + #include "PostQuantum.h" + +@@ -14,227 +16,173 @@ + #ifndef LIBRESSL_VERSION_NUMBER + #include <openssl/param_build.h> + #include <openssl/core_names.h> +-#else +-#include<openssl/mlkem.h> + #endif + + namespace i2p + { + namespace crypto + { ++#ifdef LIBRESSL_VERSION_NUMBER ++ // LibreSSL allocates operation outputs. Always wipe and release them, ++ // including when an operation or a subsequent length check fails. ++ struct MLKEMBuffer ++ { ++ MLKEMBuffer () = default; ++ MLKEMBuffer (const MLKEMBuffer&) = delete; ++ MLKEMBuffer& operator= (const MLKEMBuffer&) = delete; ++ uint8_t * data = nullptr; ++ size_t len = 0; ++ ~MLKEMBuffer () ++ { ++ if (data) OPENSSL_cleanse (data, len); ++ OPENSSL_free (data); ++ } ++ }; ++#endif ++ + MLKEMKeys::MLKEMKeys (MLKEMTypes type): +-#ifndef LIBRESSL_VERSION_NUMBER +- m_Name (std::get<0>(MLKEMS[type])), m_KeyLen (std::get<1>(MLKEMS[type])), +- m_CTLen (std::get<2>(MLKEMS[type])), +-#else +- m_Name (std::get<0>(MLKEMS[eMLKEM768])), m_KeyLen (std::get<1>(MLKEMS[eMLKEM768])), +- m_CTLen (std::get<2>(MLKEMS[eMLKEM768])), ++ m_Name (std::get<0>(MLKEMS.at (type))), m_KeyLen (std::get<1>(MLKEMS.at (type))), ++ m_CTLen (std::get<2>(MLKEMS.at (type))), m_Pkey (nullptr) ++#ifdef LIBRESSL_VERSION_NUMBER ++ , m_PublicKey (nullptr), ++ m_Rank (type == eMLKEM768 ? MLKEM768_RANK : type == eMLKEM1024 ? MLKEM1024_RANK : 0) + #endif +- m_Pkey (nullptr) + { + } + + MLKEMKeys::~MLKEMKeys () + { +- FreeKeys(); ++ FreeKeys (); + } + + void MLKEMKeys::FreeKeys () + { + #ifndef LIBRESSL_VERSION_NUMBER +- if (m_Pkey) EVP_PKEY_free (m_Pkey); ++ EVP_PKEY_free (m_Pkey); + #else +- if (m_Pkey) MLKEM_private_key_free (m_Pkey); ++ MLKEM_private_key_free (m_Pkey); ++ MLKEM_public_key_free (m_PublicKey); ++ m_PublicKey = nullptr; + #endif +- if (m_Pkey) m_Pkey = nullptr; ++ m_Pkey = nullptr; + } + +- void MLKEMKeys::GenerateKeys () ++ bool MLKEMKeys::GenerateKeys () + { +- FreeKeys(); ++ FreeKeys (); + #ifndef LIBRESSL_VERSION_NUMBER +- m_Pkey = EVP_PKEY_Q_keygen(NULL, NULL, m_Name.c_str ()); +- LogPrint(eLogDebug, "MLKEM: GenerateKeys [ openssl ]"); ++ m_Pkey = EVP_PKEY_Q_keygen (nullptr, nullptr, m_Name.c_str ()); ++ if (m_Pkey) return true; + #else +- m_Pkey = MLKEM_private_key_new( MLKEM768_RANK); +- +- uint8_t * pub_key = nullptr; +- size_t pub_key_len = 0; +- uint8_t * seed = nullptr; +- size_t seed_len = 0; +- +- if (MLKEM_generate_key(m_Pkey, &pub_key, &pub_key_len, &seed, &seed_len) == 1) +- { +- LogPrint(eLogDebug, "MLKEM: GenerateKeys [ libressl ] success"); +- if (pub_key_len <= sizeof(m_CachedPub)) +- { +- memcpy(m_CachedPub, pub_key, pub_key_len); +- m_IsPubCached = true; +- LogPrint(eLogDebug, "MLKEM [libressl] cache the pub success"); +- } else +- { +- LogPrint(eLogError, "MLKEM: can't cache private key [libressl]"); +- } +- OPENSSL_free(pub_key); +- if (seed) OPENSSL_free(seed); +- } else +- { +- LogPrint(eLogError, "MLKEM: GenerateKeys [ libressl ] failed"); +- MLKEM_private_key_free(m_Pkey); +- m_Pkey = nullptr; +- } ++ MLKEMBuffer pub; ++ // No seed is needed: LibreSSL wipes its temporary seed internally. ++ m_Pkey = MLKEM_private_key_new (m_Rank); ++ m_PublicKey = MLKEM_public_key_new (m_Rank); ++ if (m_Pkey && m_PublicKey && ++ MLKEM_generate_key (m_Pkey, &pub.data, &pub.len, nullptr, nullptr) == 1 && ++ pub.len == m_KeyLen && ++ MLKEM_public_from_private (m_Pkey, m_PublicKey) == 1) return true; + #endif +- } // end method ++ FreeKeys (); ++ LogPrint (eLogError, "MLKEM: Failed to generate keys"); ++ return false; ++ } + +- void MLKEMKeys::GetPublicKey (uint8_t * pub) const ++ bool MLKEMKeys::GetPublicKey (uint8_t * pub) const + { +- if (m_Pkey) +- { + #ifndef LIBRESSL_VERSION_NUMBER +- size_t len = m_KeyLen; +- EVP_PKEY_get_octet_string_param (m_Pkey, OSSL_PKEY_PARAM_PUB_KEY, pub, m_KeyLen, &len); ++ size_t len = 0; ++ if (m_Pkey && EVP_PKEY_get_octet_string_param (m_Pkey, ++ OSSL_PKEY_PARAM_PUB_KEY, pub, m_KeyLen, &len) == 1 && len == m_KeyLen) return true; + #else +- if (!m_Pkey) return; +- +- LogPrint(eLogDebug, "MLKEM: GetPublicKey [ libressl ]"); +- +- if (m_IsPubCached) +- { +- memcpy(pub, m_CachedPub, MLKEM768_KEY_LENGTH); +- LogPrint(eLogDebug,"MLKEM [libressl]: copy pubkey"); +- } +- else +- LogPrint(eLogError, "MLKEM: Public key not cached!"); +-#endif ++ MLKEMBuffer encoded; ++ if (m_PublicKey && MLKEM_marshal_public_key (m_PublicKey, &encoded.data, &encoded.len) == 1 && ++ encoded.len == m_KeyLen) ++ { ++ memcpy (pub, encoded.data, m_KeyLen); ++ return true; + } ++#endif ++ LogPrint (eLogError, "MLKEM: Failed to export public key"); ++ return false; + } + +- +- void MLKEMKeys::SetPublicKey (const uint8_t * pub) ++ bool MLKEMKeys::SetPublicKey (const uint8_t * pub) + { ++ // A failed import must not leave a previous key available for use. ++ FreeKeys (); + #ifndef LIBRESSL_VERSION_NUMBER +- if (m_Pkey) +- { +- EVP_PKEY_free (m_Pkey); +- m_Pkey = nullptr; +- } + OSSL_PARAM params[] = + { +- OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, (void*)pub, m_KeyLen), ++ OSSL_PARAM_octet_string (OSSL_PKEY_PARAM_PUB_KEY, (void *)pub, m_KeyLen), + OSSL_PARAM_END + }; +- +- EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, m_Name.c_str(), NULL); +- if (ctx) +- { +- EVP_PKEY_fromdata_init(ctx); +- if (EVP_PKEY_fromdata(ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params) <= 0) +- LogPrint(eLogError, "MLKEM: Failed to set public key data"); +- EVP_PKEY_CTX_free(ctx); +- } +- else +- LogPrint(eLogError, "MLKEM: can't create PKEY context"); ++ auto ctx = EVP_PKEY_CTX_new_from_name (nullptr, m_Name.c_str (), nullptr); ++ bool ok = ctx && EVP_PKEY_fromdata_init (ctx) == 1 && ++ EVP_PKEY_fromdata (ctx, &m_Pkey, OSSL_KEYMGMT_SELECT_PUBLIC_KEY, params) == 1; ++ EVP_PKEY_CTX_free (ctx); ++ if (ok) return true; + #else +- MLKEM_public_key * pub_key = MLKEM_public_key_new(MLKEM768_RANK); +- if (MLKEM_parse_public_key(pub_key, pub, m_KeyLen)) +- { +- memcpy(m_CachedPub, pub, m_KeyLen); +- m_IsPubCached = true; +- LogPrint(eLogDebug, "MLKEM: SetPublicKey [ libressl ] success"); +- } +- else +- LogPrint(eLogError, "MLKEM: failed to parse public key"); +- if (pub_key) MLKEM_public_key_free(pub_key); ++ m_PublicKey = MLKEM_public_key_new (m_Rank); ++ if (m_PublicKey && MLKEM_parse_public_key (m_PublicKey, pub, m_KeyLen) == 1) return true; + #endif ++ FreeKeys (); ++ LogPrint (eLogError, "MLKEM: Failed to parse public key"); ++ return false; + } + +- void MLKEMKeys::Encaps (uint8_t * ciphertext, uint8_t * shared) ++ bool MLKEMKeys::Encaps (uint8_t * ciphertext, uint8_t * shared) + { + #ifndef LIBRESSL_VERSION_NUMBER +- if (!m_Pkey) +- { +- LogPrint(eLogError, "MLKEM: No public key for Encaps"); +- return; +- } +- auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); +- if (ctx) +- { +- EVP_PKEY_encapsulate_init (ctx, NULL); +- size_t len = m_CTLen, sharedLen = 32; +- EVP_PKEY_encapsulate (ctx, ciphertext, &len, shared, &sharedLen); +- EVP_PKEY_CTX_free (ctx); +- } +- else +- LogPrint (eLogError, "MLKEM can't create PKEY context"); ++ auto ctx = m_Pkey ? EVP_PKEY_CTX_new_from_pkey (nullptr, m_Pkey, nullptr) : nullptr; ++ size_t len = m_CTLen, sharedLen = 32; ++ bool ok = ctx && EVP_PKEY_encapsulate_init (ctx, nullptr) == 1 && ++ EVP_PKEY_encapsulate (ctx, ciphertext, &len, shared, &sharedLen) == 1 && ++ len == m_CTLen && sharedLen == 32; ++ EVP_PKEY_CTX_free (ctx); ++ if (ok) return true; + #else +- if (!m_IsPubCached) +- { +- LogPrint(eLogError, "MLKEM: No public key for Encaps"); +- return; +- } +- auto pub_key = MLKEM_public_key_new(MLKEM768_RANK); +- if (MLKEM_parse_public_key (pub_key, m_CachedPub, m_KeyLen) != 1) ++ MLKEMBuffer ct, secret; ++ if (m_PublicKey && MLKEM_encap (m_PublicKey, &ct.data, &ct.len, &secret.data, &secret.len) == 1 && ++ ct.len == m_CTLen && secret.len == 32) + { +- LogPrint(eLogError, "MLKEM can't parse cached public key"); +- return; ++ memcpy (ciphertext, ct.data, m_CTLen); ++ memcpy (shared, secret.data, 32); ++ return true; + } +- uint8_t * out_ct = nullptr; +- size_t out_ct_len = 0; +- uint8_t * out_ss = nullptr; +- size_t out_ss_len = 0; +- if (MLKEM_encap(pub_key, &out_ct, &out_ct_len, &out_ss, &out_ss_len) == 1) +- { +- memcpy(ciphertext, out_ct, out_ct_len); +- memcpy(shared, out_ss, out_ss_len); +- OPENSSL_cleanse(out_ct, out_ct_len); +- OPENSSL_cleanse(out_ss, out_ss_len); +- OPENSSL_free(out_ct); +- OPENSSL_free(out_ss); +- LogPrint(eLogDebug, "MLKEM [libressl] successfully encaps"); +- } +- else +- LogPrint(eLogError, "MLKEM [libressl]: encapsulation failed"); +- MLKEM_public_key_free(pub_key); + #endif ++ OPENSSL_cleanse (shared, 32); ++ LogPrint (eLogError, "MLKEM: Encapsulation failed"); ++ return false; + } + +- void MLKEMKeys::Decaps (const uint8_t * ciphertext, uint8_t * shared) ++ bool MLKEMKeys::Decaps (const uint8_t * ciphertext, uint8_t * shared) + { +- if (!m_Pkey) return; + #ifndef LIBRESSL_VERSION_NUMBER +- auto ctx = EVP_PKEY_CTX_new_from_pkey (NULL, m_Pkey, NULL); +- if (ctx) ++ auto ctx = m_Pkey ? EVP_PKEY_CTX_new_from_pkey (nullptr, m_Pkey, nullptr) : nullptr; ++ size_t sharedLen = 32; ++ bool ok = ctx && EVP_PKEY_decapsulate_init (ctx, nullptr) == 1 && ++ EVP_PKEY_decapsulate (ctx, shared, &sharedLen, ciphertext, m_CTLen) == 1 && sharedLen == 32; ++ EVP_PKEY_CTX_free (ctx); ++ if (ok) return true; ++#else ++ MLKEMBuffer secret; ++ // Success includes implicit rejection. The following Noise AEAD ++ // authentication detects an invalid, correctly sized ciphertext. ++ if (m_Pkey && MLKEM_decap (m_Pkey, ciphertext, m_CTLen, &secret.data, &secret.len) == 1 && secret.len == 32) + { +- EVP_PKEY_decapsulate_init (ctx, NULL); +- size_t sharedLen = 32; +- EVP_PKEY_decapsulate (ctx, shared, &sharedLen, ciphertext, m_CTLen); +- EVP_PKEY_CTX_free (ctx); ++ memcpy (shared, secret.data, 32); ++ return true; + } +- else +- LogPrint (eLogError, "MLKEM can't create PKEY context"); +-#else +- uint8_t * out_shared_secret = nullptr; +- size_t out_shared_secret_len = 0; +- if (MLKEM_decap(m_Pkey, ciphertext, m_CTLen, &out_shared_secret, &out_shared_secret_len) == 1) +- { +- memcpy(shared, out_shared_secret, out_shared_secret_len); +- +- OPENSSL_cleanse(out_shared_secret, out_shared_secret_len); +- +- OPENSSL_free(out_shared_secret); +- LogPrint(eLogDebug, "MLKEM [libressl] successfully decrypt"); +- } +- else +- { +- LogPrint(eLogError, "MLKEM [libressl]: decapsulation failed"); +- } + #endif ++ OPENSSL_cleanse (shared, 32); ++ LogPrint (eLogError, "MLKEM: Decapsulation failed"); ++ return false; + } + + std::unique_ptr<MLKEMKeys> CreateMLKEMKeys (i2p::data::CryptoKeyType type) + { +- if (type <= i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD || +- type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD > (int)MLKEMS.size ()) return nullptr; ++ if (!IsMLKEMSupported (type)) return nullptr; + return std::make_unique<MLKEMKeys>((MLKEMTypes)(type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1)); + } + Index: net/i2pd/patches/patch-libi2pd_PostQuantum_h =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_PostQuantum_h diff -N net/i2pd/patches/patch-libi2pd_PostQuantum_h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_PostQuantum_h 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,67 @@ +Expose ML-KEM only when the selected crypto library supports it and +retain separate public and private keys for LibreSSL. + +Index: libi2pd/PostQuantum.h +--- libi2pd/PostQuantum.h.orig ++++ libi2pd/PostQuantum.h +@@ -15,7 +15,7 @@ + #include <tuple> + #include "Crypto.h" + #include "Identity.h" +-#ifdef LIBRESSL_VERSION_NUMBER ++#if OPENSSL_PQ && defined(LIBRESSL_VERSION_NUMBER) + # include <openssl/mlkem.h> + #endif + +@@ -60,6 +60,16 @@ namespace crypto + return std::get<2>(MLKEMS[type - i2p::data::CRYPTO_KEY_TYPE_ECIES_X25519_AEAD - 1]); + } + ++ constexpr bool IsMLKEMSupported (i2p::data::CryptoKeyType type) ++ { ++ if (!GetMLKEMPublicKeyLen (type)) return false; ++#ifdef LIBRESSL_VERSION_NUMBER ++ return type != i2p::data::CRYPTO_KEY_TYPE_ECIES_MLKEM512_X25519_AEAD; ++#else ++ return true; ++#endif ++ } ++ + class MLKEMKeys + { + public: +@@ -67,13 +77,18 @@ namespace crypto + MLKEMKeys (MLKEMTypes type); + ~MLKEMKeys (); + +- void GenerateKeys (); +- void GetPublicKey (uint8_t * pub) const; +- void SetPublicKey (const uint8_t * pub); ++ MLKEMKeys (const MLKEMKeys&) = delete; ++ MLKEMKeys& operator= (const MLKEMKeys&) = delete; ++ ++ // Buffers must hold GetKeyLen(), GetCTLen(), or 32 bytes as appropriate. ++ // On false, callers must abort the handshake and discard outputs. ++ [[nodiscard]] bool GenerateKeys (); ++ [[nodiscard]] bool GetPublicKey (uint8_t * pub) const; ++ [[nodiscard]] bool SetPublicKey (const uint8_t * pub); + size_t GetKeyLen () const { return m_KeyLen; }; + size_t GetCTLen () const { return m_CTLen; }; +- void Encaps (uint8_t * ciphertext, uint8_t * shared); +- void Decaps (const uint8_t * ciphertext, uint8_t * shared); ++ [[nodiscard]] bool Encaps (uint8_t * ciphertext, uint8_t * shared); ++ [[nodiscard]] bool Decaps (const uint8_t * ciphertext, uint8_t * shared); + + + private: +@@ -88,8 +103,8 @@ namespace crypto + EVP_PKEY * m_Pkey; + #else + MLKEM_private_key * m_Pkey; +- uint8_t m_CachedPub[MLKEM768_KEY_LENGTH]; +- bool m_IsPubCached = false; ++ MLKEM_public_key * m_PublicKey; ++ const int m_Rank; + #endif + }; + Index: net/i2pd/patches/patch-libi2pd_Reseed_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_Reseed_cpp diff -N net/i2pd/patches/patch-libi2pd_Reseed_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_Reseed_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,33 @@ +Use OpenSSL 3 certificate APIs only with OpenSSL, preserving LibreSSL +support. + +Index: libi2pd/Reseed.cpp +--- libi2pd/Reseed.cpp.orig ++++ libi2pd/Reseed.cpp +@@ -16,7 +16,7 @@ + #include <boost/algorithm/string.hpp> + #include <openssl/ssl.h> + #include <openssl/err.h> +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + #include <openssl/core_names.h> + #endif + #include <zlib.h> +@@ -537,7 +537,7 @@ namespace data + // extract RSA key (we need n only, e = 65537) + EVP_PKEY * pubKey = X509_get_pubkey (cert); + const BIGNUM * n = nullptr; +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + BIGNUM * n1 = BN_new (); + if (EVP_PKEY_get_bn_param (pubKey, OSSL_PKEY_PARAM_RSA_N, &n1) > 0) + n = n1; +@@ -557,7 +557,7 @@ namespace data + } + else + LogPrint (eLogError, "Reseed: Can't extract RSA key from ", filename); +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + BN_free (n1); + #endif + } Index: net/i2pd/patches/patch-libi2pd_RouterContext_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_RouterContext_cpp diff -N net/i2pd/patches/patch-libi2pd_RouterContext_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_RouterContext_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,20 @@ +Preserve an explicitly configured SSU2 MTU when the router address +changes. + +Index: libi2pd/RouterContext.cpp +--- libi2pd/RouterContext.cpp.orig ++++ libi2pd/RouterContext.cpp +@@ -591,7 +591,13 @@ namespace i2p + if (addr && (addr->host != host || !addr->ssu->mtu)) + { + addr->host = host; ++#ifdef __OpenBSD__ ++ // Address updates must not replace an explicitly configured MTU. ++ uint16_t configuredMTU; i2p::config::GetOption ("ssu2.mtu6", configuredMTU); ++ if (!configuredMTU && m_StatusV6 != eRouterStatusProxy) ++#else + if (m_StatusV6 != eRouterStatusProxy) ++#endif + { + // update MTU + auto mtu = i2p::util::net::GetMTU (host); Index: net/i2pd/patches/patch-libi2pd_SSU2Session_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_SSU2Session_cpp diff -N net/i2pd/patches/patch-libi2pd_SSU2Session_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_SSU2Session_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,104 @@ +Check ML-KEM key generation, export and encapsulation before sending +SSU2 data. + +Index: libi2pd/SSU2Session.cpp +--- libi2pd/SSU2Session.cpp.orig ++++ libi2pd/SSU2Session.cpp +@@ -754,7 +754,7 @@ namespace transport + { + i2p::data::CryptoKeyType cryptoType = (i2p::data::CryptoKeyType)(m_Version + 2); + m_PQKeys = i2p::crypto::CreateMLKEMKeys (cryptoType); +- m_PQKeys->GenerateKeys (); ++ if (!m_PQKeys || !m_PQKeys->GenerateKeys ()) return false; + offset = m_PQKeys->GetKeyLen () + 16; + payloadSize += offset; + } +@@ -804,7 +804,7 @@ namespace transport + { + size_t keyLen = m_PQKeys->GetKeyLen (); + std::vector<uint8_t> encapsKey(keyLen); +- m_PQKeys->GetPublicKey (encapsKey.data ()); ++ if (!m_PQKeys || !m_PQKeys->GetPublicKey (encapsKey.data ())) return false; + if (!m_NoiseState->Encrypt (encapsKey.data (), payload, keyLen)) + { + LogPrint (eLogWarning, "SSU2: SessionRequest ML-KEM encap_key frame AEAD encryption failed "); +@@ -917,6 +917,7 @@ namespace transport + { + auto cryptoType = (i2p::data::CryptoKeyType)(m_Version + 2); + auto keyLen = i2p::crypto::GetMLKEMPublicKeyLen (cryptoType); ++ if (!i2p::crypto::IsMLKEMSupported (cryptoType) || offset + keyLen + 16 > len) return false; + std::vector<uint8_t> encapsKey(keyLen); + if (!m_NoiseState->Decrypt (buf + offset, encapsKey.data (), keyLen)) + { +@@ -926,7 +927,7 @@ namespace transport + m_NoiseState->MixHash (buf + offset, keyLen + 16); + offset += keyLen + 16; + m_PQKeys = i2p::crypto::CreateMLKEMKeys (cryptoType); +- m_PQKeys->SetPublicKey (encapsKey.data ()); ++ if (!m_PQKeys || !m_PQKeys->SetPublicKey (encapsKey.data ())) return false; + } + #endif + if (offset + 16 > len) +@@ -989,11 +990,12 @@ namespace transport + size_t maxPayloadSize = m_MaxPayloadSize - 48; + size_t payloadSize = 0, offset = 0; + #if OPENSSL_PQ +- if (m_Version > 2 && m_PQKeys) ++ if (m_Version > 2) + { ++ if (!m_PQKeys) return; + size_t cipherTextLen = m_PQKeys->GetCTLen (); + std::vector<uint8_t> kemCiphertext(cipherTextLen); +- m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret); ++ if (!m_PQKeys || !m_PQKeys->Encaps (kemCiphertext.data (), sharedSecret)) return; + if (!m_NoiseState->Encrypt (kemCiphertext.data (), payload, cipherTextLen)) + { + LogPrint (eLogWarning, "SSU2: SessionCreated ML-KEM ciphertext section AEAD encryption failed"); +@@ -1060,6 +1062,11 @@ namespace transport + LogPrint (eLogWarning, "SSU2: Unexpected SessionCreated message"); + return false; + } ++ if (len < 80) ++ { ++ LogPrint (eLogWarning, "SSU2: SessionCreated message too short ", len); ++ return false; ++ } + Header header; + memcpy (header.buf, buf, 16); + header.ll[0] ^= CreateHeaderMask (m_Address->i, buf + (len - 24)); +@@ -1069,11 +1076,7 @@ namespace transport + if (header.h.type != eSSU2SessionCreated) + // this situation is valid, because it might be Retry with different encryption + return false; +- if (len < 80) +- { +- LogPrint (eLogWarning, "SSU2: SessionCreated message too short ", len); +- return false; +- } ++ + m_HandshakeInterval = i2p::util::GetMillisecondsSinceEpoch () - m_HandshakeInterval; + const uint8_t nonce[12] = {0}; + uint8_t headerX[48]; +@@ -1086,10 +1089,12 @@ namespace transport + m_NoiseState->MixKey (sharedSecret); + size_t offset = 64; + #if OPENSSL_PQ +- if (m_Version > 2 && m_PQKeys) ++ if (m_Version > 2) + { ++ if (!m_PQKeys) return false; + i2p::data::CryptoKeyType cryptoType = (i2p::data::CryptoKeyType)(m_Version + 2); + size_t cipherTextLen = i2p::crypto::GetMLKEMCipherTextLen (cryptoType); ++ if (!i2p::crypto::IsMLKEMSupported (cryptoType) || offset + cipherTextLen + 16 > len) return false; + std::vector<uint8_t> kemCiphertext(cipherTextLen); + if (!m_NoiseState->Decrypt (buf + offset, kemCiphertext.data (), cipherTextLen)) + { +@@ -1098,7 +1103,7 @@ namespace transport + } + m_NoiseState->MixHash (buf + offset, cipherTextLen + 16); + offset += cipherTextLen + 16; +- m_PQKeys->Decaps (kemCiphertext.data (), sharedSecret); ++ if (!m_PQKeys || !m_PQKeys->Decaps (kemCiphertext.data (), sharedSecret)) return false; + m_NoiseState->MixKey (sharedSecret); + } + #endif Index: net/i2pd/patches/patch-libi2pd_Signature_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_Signature_cpp diff -N net/i2pd/patches/patch-libi2pd_Signature_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_Signature_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,105 @@ +Gate OpenSSL 3 signature APIs away from LibreSSL and safely handle +Ed25519ph signing and verification. + +Index: libi2pd/Signature.cpp +--- libi2pd/Signature.cpp.orig ++++ libi2pd/Signature.cpp +@@ -8,7 +8,8 @@ + + #include <memory> + #include <openssl/evp.h> +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#include <openssl/crypto.h> ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + #include <openssl/core_names.h> + #include <openssl/param_build.h> + #endif +@@ -19,7 +20,7 @@ namespace i2p + { + namespace crypto + { +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + DSAVerifier::DSAVerifier (): + m_PublicKey (nullptr) + { +@@ -172,7 +173,7 @@ namespace crypto + } + #endif + +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + ECDSAVerifier::ECDSAVerifier (int curve, size_t keyLen, const EVP_MD * hash): + m_Curve(curve), m_KeyLen (keyLen), m_Hash (hash), m_PublicKey (nullptr) + { +@@ -405,7 +406,7 @@ namespace crypto + LogPrint (eLogError, "EdDSA signing key is not set"); + } + +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) ++#if OPENSSL_ED25519PH + static const OSSL_PARAM EDDSA25519phParams[] = + { + OSSL_PARAM_utf8_string ("instance", (char *)"Ed25519ph", 9), +@@ -415,19 +416,13 @@ namespace crypto + bool EDDSA25519phVerifier::Verify (const uint8_t * buf, size_t len, const uint8_t * signature) const + { + auto pkey = GetPkey (); +- if (pkey) +- { +- uint8_t digest[64]; +- SHA512 (buf, len, digest); +- EVP_MD_CTX * ctx = EVP_MD_CTX_create (); +- EVP_DigestVerifyInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams); +- auto ret = EVP_DigestVerify (ctx, signature, 64, digest, 64); +- EVP_MD_CTX_destroy (ctx); +- return ret; +- } +- else +- LogPrint (eLogError, "EdDSA verification key is not set"); +- return false; ++ EVP_MD_CTX * ctx = EVP_MD_CTX_new (); ++ // The provider applies SHA-512 for the Ed25519ph instance. ++ bool ret = pkey && ctx && ++ EVP_DigestVerifyInit_ex (ctx, nullptr, nullptr, nullptr, nullptr, pkey, EDDSA25519phParams) == 1 && ++ EVP_DigestVerify (ctx, signature, 64, buf, len) == 1; ++ EVP_MD_CTX_free (ctx); ++ return ret; + } + + EDDSA25519phSigner::EDDSA25519phSigner (const uint8_t * signingPrivateKey): +@@ -438,21 +433,21 @@ namespace crypto + void EDDSA25519phSigner::Sign (const uint8_t * buf, int len, uint8_t * signature) const + { + auto pkey = GetPkey (); +- if (pkey) ++ EVP_MD_CTX * ctx = EVP_MD_CTX_new (); ++ size_t l = 64; ++ uint8_t sig[64]; ++ bool ok = pkey && ctx && len >= 0 && ++ EVP_DigestSignInit_ex (ctx, nullptr, nullptr, nullptr, nullptr, pkey, EDDSA25519phParams) == 1 && ++ EVP_DigestSign (ctx, sig, &l, buf, len) == 1 && l == sizeof (sig); ++ if (ok) ++ memcpy (signature, sig, sizeof (sig)); ++ else + { +- uint8_t digest[64]; +- SHA512 (buf, len, digest); +- EVP_MD_CTX * ctx = EVP_MD_CTX_create (); +- size_t l = 64; +- uint8_t sig[64]; +- EVP_DigestSignInit_ex (ctx, NULL, NULL, NULL, NULL, pkey, EDDSA25519phParams); +- if (!EVP_DigestSign (ctx, sig, &l, digest, 64)) +- LogPrint (eLogError, "EdDSA signing failed"); +- memcpy (signature, sig, 64); +- EVP_MD_CTX_destroy (ctx); ++ memset (signature, 0, sizeof (sig)); ++ LogPrint (eLogError, "Ed25519ph signing failed"); + } +- else +- LogPrint (eLogError, "EdDSA signing key is not set"); ++ OPENSSL_cleanse (sig, sizeof (sig)); ++ EVP_MD_CTX_free (ctx); + } + #endif + } Index: net/i2pd/patches/patch-libi2pd_Signature_h =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_Signature_h diff -N net/i2pd/patches/patch-libi2pd_Signature_h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_Signature_h 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,51 @@ +Select signature key classes according to the APIs available in OpenSSL +and LibreSSL, including optional Ed25519ph support. + +Index: libi2pd/Signature.h +--- libi2pd/Signature.h.orig ++++ libi2pd/Signature.h +@@ -62,7 +62,7 @@ namespace crypto + + private: + +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + EVP_PKEY * m_PublicKey; + #else + DSA * m_PublicKey; +@@ -82,7 +82,7 @@ namespace crypto + + private: + +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + EVP_PKEY * m_PrivateKey; + #else + DSA * m_PrivateKey; +@@ -96,7 +96,7 @@ namespace crypto + constexpr size_t ECDSAP384_KEY_LENGTH = 96; + constexpr size_t ECDSAP521_KEY_LENGTH = 132; + +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L // since 3.0.0 + class ECDSAVerifier: public Verifier + { + public: +@@ -382,7 +382,7 @@ namespace crypto + EVP_PKEY * GetPkey () const { return m_Pkey; }; + }; + +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if OPENSSL_ED25519PH + class EDDSA25519phVerifier: public EDDSA25519Verifier + { + public: +@@ -428,7 +428,7 @@ namespace crypto + EDDSA25519SignerCompat * m_Fallback; + }; + +-#if (OPENSSL_VERSION_NUMBER >= 0x030000000) // since 3.0.0 ++#if OPENSSL_ED25519PH + class EDDSA25519phSigner: public EDDSA25519Signer + { + public: Index: net/i2pd/patches/patch-libi2pd_client_UDPTunnel_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_client_UDPTunnel_cpp diff -N net/i2pd/patches/patch-libi2pd_client_UDPTunnel_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_client_UDPTunnel_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,50 @@ +Serialize cancellation and rearming of the UDP server cleanup timer. +Keep only a weak reference in its callback so queued handlers cannot +access a destroyed tunnel or keep a stopped tunnel alive. + +Index: libi2pd_client/UDPTunnel.cpp +--- libi2pd_client/UDPTunnel.cpp.orig ++++ libi2pd_client/UDPTunnel.cpp +@@ -484,7 +484,7 @@ namespace client + + void I2PUDPServerTunnel::Start () + { +- m_IsStarted = true; ++ m_IsStarted.store (true, std::memory_order_release); + m_LocalDest->Start (); + + auto dgram = m_LocalDest->CreateDatagramDestination (m_Gzip); +@@ -505,11 +505,14 @@ namespace client + + void I2PUDPServerTunnel::ScheduleCleanupTimer () + { +- if (m_CleanupTimer) ++ std::lock_guard<std::mutex> lock (m_CleanupTimerMutex); ++ if (m_IsStarted.load (std::memory_order_acquire) && m_CleanupTimer) + { + m_CleanupTimer->expires_after (std::chrono::seconds (I2P_UDP_SESSION_CLEANUP_INTERVAL)); +- m_CleanupTimer->async_wait (std::bind (&I2PUDPServerTunnel::HandleCleanupTimer, +- this, std::placeholders::_1)); ++ std::weak_ptr<I2PUDPServerTunnel> weak = shared_from_this (); ++ m_CleanupTimer->async_wait ([weak](const boost::system::error_code& ecode) { ++ if (auto self = weak.lock ()) self->HandleCleanupTimer (ecode); ++ }); + } + } + +@@ -558,10 +561,12 @@ namespace client + // a config reload builds a second tunnel object and drops it when the same + // one is already there. Its destructor must not reset the receiver of the + // live tunnel: they share the datagram destination +- if (!m_IsStarted) return; +- m_IsStarted = false; ++ if (!m_IsStarted.exchange (false, std::memory_order_acq_rel)) return; + if (m_StatsTimer) m_StatsTimer->cancel (); +- if (m_CleanupTimer) m_CleanupTimer->cancel (); ++ { ++ std::lock_guard<std::mutex> lock (m_CleanupTimerMutex); ++ if (m_CleanupTimer) m_CleanupTimer->cancel (); ++ } + auto dgram = m_LocalDest->GetDatagramDestination (); + if (dgram) { + dgram->ResetReceiver (m_inPort); Index: net/i2pd/patches/patch-libi2pd_client_UDPTunnel_h =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_client_UDPTunnel_h diff -N net/i2pd/patches/patch-libi2pd_client_UDPTunnel_h --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_client_UDPTunnel_h 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,34 @@ +Track UDP server tunnel shutdown atomically and guard cleanup timer +operations shared by the event loop and configuration reload. + +Index: libi2pd_client/UDPTunnel.h +--- libi2pd_client/UDPTunnel.h.orig ++++ libi2pd_client/UDPTunnel.h +@@ -13,6 +13,8 @@ + #include <string> + #include <string_view> + #include <memory> ++#include <atomic> ++#include <mutex> + #include <thread> + #include <vector> + #include <list> +@@ -148,7 +150,7 @@ namespace client + typedef std::shared_ptr<UDPSession> UDPSessionPtr; + + /** server side udp tunnel, many i2p inbound to 1 ip outbound */ +- class I2PUDPServerTunnel ++ class I2PUDPServerTunnel: public std::enable_shared_from_this<I2PUDPServerTunnel> + { + public: + +@@ -194,7 +196,8 @@ namespace client + UDPSessionPtr m_LastSession; + uint16_t m_inPort; + bool m_Gzip; +- bool m_IsStarted = false; // a tunnel that never started shares nothing to tear down ++ std::atomic<bool> m_IsStarted {false}; // a tunnel that never started shares nothing to tear down ++ std::mutex m_CleanupTimerMutex; + uint32_t m_NumRawNoSession = 0; + size_t m_MaxWindow = I2P_UDP_MIN_MAX_NUM_UNACKED_DATAGRAMS; + std::unique_ptr<boost::asio::steady_timer> m_StatsTimer; Index: net/i2pd/patches/patch-libi2pd_util_cpp =================================================================== RCS file: net/i2pd/patches/patch-libi2pd_util_cpp diff -N net/i2pd/patches/patch-libi2pd_util_cpp --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-libi2pd_util_cpp 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,43 @@ +Read interface MTU data through getifaddrs on OpenBSD, avoiding an ioctl +that is unavailable under the port pledge. + +Index: libi2pd/util.cpp +--- libi2pd/util.cpp.orig ++++ libi2pd/util.cpp +@@ -26,6 +26,10 @@ + #include <pthread_np.h> + #endif + ++#ifdef __OpenBSD__ ++#include <net/if.h> ++#endif ++ + #if defined(__APPLE__) + # include <AvailabilityMacros.h> + #endif +@@ -570,6 +574,17 @@ namespace net + int mtu = fallback; + if (ifa && family) + { // interface found? ++#ifdef __OpenBSD__ ++ // getifaddrs supplies if_data for AF_LINK entries. NET_RT_IFLIST ++ // is permitted by pledge("inet"), unlike SIOCGIFMTU. ++ for (auto link = ifaddr; link; link = link->ifa_next) ++ if (link->ifa_addr && link->ifa_addr->sa_family == AF_LINK && ++ link->ifa_data && !strcmp (link->ifa_name, ifa->ifa_name)) ++ { ++ mtu = static_cast<const struct if_data *> (link->ifa_data)->ifi_mtu; ++ break; ++ } ++#else + int fd = socket(family, SOCK_DGRAM, 0); + if (fd > 0) + { +@@ -583,6 +598,7 @@ namespace net + } + else + LogPrint(eLogError, "NetIface: Failed to create datagram socket"); ++#endif + } + else + LogPrint(eLogWarning, "NetIface: Interface for local address", localAddress.to_string(), " not found"); Index: net/i2pd/patches/patch-test_Makefile =================================================================== RCS file: net/i2pd/patches/patch-test_Makefile diff -N net/i2pd/patches/patch-test_Makefile --- net/i2pd/patches/patch-test_Makefile 17 Oct 2026 08:47:19 -0000 1.1 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,11 +0,0 @@ -Index: tests/Makefile ---- tests/Makefile.orig -+++ tests/Makefile -@@ -18,7 +18,6 @@ ifneq (, $(findstring mingw, $(SYS))$(findstring windo - endif - - LDLIBS = \ -- -lboost_system$(BOOST_SUFFIX) \ - -lboost_program_options$(BOOST_SUFFIX) \ - -lssl \ - -lcrypto \ Index: net/i2pd/patches/patch-tests_Makefile =================================================================== RCS file: net/i2pd/patches/patch-tests_Makefile diff -N net/i2pd/patches/patch-tests_Makefile --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ net/i2pd/patches/patch-tests_Makefile 23 Oct 2026 12:54:04 -0000 @@ -0,0 +1,14 @@ +Drop the separate Boost.System link dependency, which is not needed by +the current Boost configuration. + +Index: tests/Makefile +--- tests/Makefile.orig ++++ tests/Makefile +@@ -18,7 +18,6 @@ ifneq (, $(findstring mingw, $(SYS))$(fi + endif + + LDLIBS = \ +- -lboost_system$(BOOST_SUFFIX) \ + -lboost_program_options$(BOOST_SUFFIX) \ + -lssl \ + -lcrypto \ Index: net/i2pd/pkg/PLIST =================================================================== RCS file: /cvs/ports/net/i2pd/pkg/PLIST,v diff -u -p -u -p -r1.19 PLIST --- net/i2pd/pkg/PLIST 17 Oct 2026 08:47:19 -0000 1.19 +++ net/i2pd/pkg/PLIST 23 Oct 2026 12:54:04 -0000 @@ -1,5 +1,5 @@ @newgroup _i2pd:838 -@newuser _i2pd:838:838::i2pd account:/var/i2pd:/sbin/nologin +@newuser _i2pd:838:838::i2pd account:${LOCALSTATEDIR}/i2pd:/sbin/nologin @rcscript ${RCDIR}/i2pd @bin bin/i2pd include/i2pd/ @@ -7,6 +7,7 @@ include/i2pd/AddressBook.h include/i2pd/BOB.h include/i2pd/Base.h include/i2pd/Blinding.h +include/i2pd/BoostStream.h include/i2pd/CPU.h include/i2pd/ClientContext.h include/i2pd/Config.h @@ -56,6 +57,9 @@ include/i2pd/Streaming.h include/i2pd/Tag.h include/i2pd/Timestamp.h include/i2pd/Torrents.h +include/i2pd/TorrentsDHT.h +include/i2pd/TorrentsRPC.h +include/i2pd/TorrentsTunnel.h include/i2pd/TransitTunnel.h include/i2pd/TransportSession.h include/i2pd/Transports.h @@ -69,12 +73,92 @@ include/i2pd/UDPTunnel.h include/i2pd/api.h include/i2pd/util.h include/i2pd/version.h +include/libi2pd/ +include/libi2pd/Base.h +include/libi2pd/Blinding.h +include/libi2pd/CPU.h +include/libi2pd/Config.h +include/libi2pd/Crypto.h +include/libi2pd/CryptoKey.h +include/libi2pd/Datagram.h +include/libi2pd/Destination.h +include/libi2pd/ECIESX25519AEADRatchetSession.h +include/libi2pd/Ed25519.h +include/libi2pd/Elligator.h +include/libi2pd/FS.h +include/libi2pd/Family.h +include/libi2pd/Garlic.h +include/libi2pd/Gost.h +include/libi2pd/Gzip.h +include/libi2pd/HTTP.h +include/libi2pd/I2NPProtocol.h +include/libi2pd/I2PEndian.h +include/libi2pd/IdentMetrics.h +include/libi2pd/Identity.h +include/libi2pd/KadDHT.h +include/libi2pd/LeaseSet.h +include/libi2pd/LittleBigEndian.h +include/libi2pd/Log.h +include/libi2pd/NTCP2.h +include/libi2pd/NetDb.hpp +include/libi2pd/NetDbRequests.h +include/libi2pd/PostQuantum.h +include/libi2pd/Profiling.h +include/libi2pd/Queue.h +include/libi2pd/Reseed.h +include/libi2pd/RouterContext.h +include/libi2pd/RouterInfo.h +include/libi2pd/SSU2.h +include/libi2pd/SSU2OutOfSession.h +include/libi2pd/SSU2Session.h +include/libi2pd/Signature.h +include/libi2pd/Siphash.h +include/libi2pd/Socks5.h +include/libi2pd/Streaming.h +include/libi2pd/Tag.h +include/libi2pd/Timestamp.h +include/libi2pd/TransitTunnel.h +include/libi2pd/TransportSession.h +include/libi2pd/Transports.h +include/libi2pd/Tunnel.h +include/libi2pd/TunnelBase.h +include/libi2pd/TunnelConfig.h +include/libi2pd/TunnelEndpoint.h +include/libi2pd/TunnelGateway.h +include/libi2pd/TunnelPool.h +include/libi2pd/api.h +include/libi2pd/util.h +include/libi2pd/version.h +include/libi2pdclient/ +include/libi2pdclient/AddressBook.h +include/libi2pdclient/BOB.h +include/libi2pdclient/BoostStream.h +include/libi2pdclient/ClientContext.h +include/libi2pdclient/HTTPProxy.h +include/libi2pdclient/I2CP.h +include/libi2pdclient/I2PService.h +include/libi2pdclient/I2PTunnel.h +include/libi2pdclient/MatchedDestination.h +include/libi2pdclient/SAM.h +include/libi2pdclient/SOCKS.h +include/libi2pdclient/Torrents.h +include/libi2pdclient/TorrentsDHT.h +include/libi2pdclient/TorrentsRPC.h +include/libi2pdclient/TorrentsTunnel.h +include/libi2pdclient/UDPTunnel.h +include/libi2pdlang/ +include/libi2pdlang/I18N.h +include/libi2pdlang/I18N_langs.h +lib/cmake/i2pd/ +lib/cmake/i2pd/i2pdConfig.cmake +lib/cmake/i2pd/i2pdConfigVersion.cmake @static-lib lib/libi2pd.a @static-lib lib/libi2pdclient.a @static-lib lib/libi2pdlang.a share/doc/pkg-readmes/${PKGSTEM} share/examples/i2pd/ share/examples/i2pd/i2pd.conf +share/examples/i2pd/torrents.conf @mode 0750 @owner _i2pd @group _i2pd @@ -112,15 +196,23 @@ i2pd/certificates/reseed/acetone_at_mail i2pd/certificates/reseed/admin_at_likogan.dev.crt i2pd/certificates/reseed/admin_at_stormycloud.org.crt i2pd/certificates/reseed/creativecowpat_at_mail.i2p.crt +i2pd/certificates/reseed/damian.buho_at_proton.me.crt i2pd/certificates/reseed/diyarciftci_at_protonmail.com.crt i2pd/certificates/reseed/echelon3_at_mail.i2p.crt -i2pd/certificates/reseed/hankhill19580_at_gmail.com.crt +i2pd/certificates/reseed/freedompeer_at_skhron.eu.crt i2pd/certificates/reseed/i2p-reseed_at_mk16.de.crt +i2pd/certificates/reseed/idk_at_stormycloud.org.crt i2pd/certificates/reseed/igor_at_novg.net.crt i2pd/certificates/reseed/lazygravy_at_mail.i2p.crt i2pd/certificates/reseed/orignal_at_mail.i2p.crt i2pd/certificates/reseed/r4sas-reseed_at_mail.i2p.crt i2pd/certificates/reseed/rambler_at_mail.i2p.crt i2pd/certificates/reseed/reseed_at_diva.exchange.crt +i2pd/certificates/reseed/reseedserver_at_mail.i2p.crt i2pd/certificates/reseed/sahil_at_mail.i2p.crt i2pd/certificates/reseed/vserod1488_at_proton.me.crt +i2pd/torrents/ +i2pd/torrents/web/ +i2pd/torrents/web/index.html +i2pd/torrents/web/main.js +i2pd/torrents/web/rpc.js Index: net/i2pd/pkg/README =================================================================== RCS file: /cvs/ports/net/i2pd/pkg/README,v diff -u -p -u -p -r1.5 README --- net/i2pd/pkg/README 17 Oct 2026 08:47:19 -0000 1.5 +++ net/i2pd/pkg/README 23 Oct 2026 12:54:04 -0000 @@ -6,13 +6,13 @@ Important: upgrading from i2pd < 2.61.0 ======================================= A major change in this package starting with version 2.61.0 requires -an intervention from all upgrading users: the running directory of i2pd, -which is also the $HOME directory of the _i2pd user, moves from -/var/lib/i2pd to /var/i2pd. +an intervention from most users: the running directory of i2pd, which +is also the $HOME directory of the _i2pd user, moves from /var/lib/i2pd +to ${LOCALSTATEDIR}/i2pd. If you installed a version of this package older than 2.61.0, you'll -change the _i2pd user's home directory and move all files under -/var/lib/i2pd to /var/i2pd. +probably have to change the _i2pd user $HOME directory and move all +files under /var/lib/i2pd to ${LOCALSTATEDIR}/i2pd. Unless you installed or configured i2pd in a non-standard way, here's how most users should proceed: @@ -25,12 +25,12 @@ how most users should proceed: - delete the _i2pd user and group: userdel _i2pd groupdel _i2pd -- move /var/lib/i2pd to /var/i2pd: - mv /var/lib/i2pd /var/i2pd +- move /var/lib/i2pd to ${LOCALSTATEDIR}/i2pd: + mv /var/lib/i2pd ${LOCALSTATEDIR}/i2pd - install the new package: pkg_add i2pd -- edit /etc/i2pd/i2pd.conf (you can take inspiration from - /usr/local/share/examples/i2pd/i2pd.conf) +- edit ${SYSCONFDIR}/i2pd/i2pd.conf (you can take inspiration from + ${TRUEPREFIX}/share/examples/i2pd/i2pd.conf) - restart the i2pd process: rcctl restart i2pd @@ -60,7 +60,7 @@ If you intend to run a floodfill, you sh # sysctl kern.maxfiles=16000 # echo "kern.maxfiles=16000" >> /etc/sysctl.conf -and also edit /etc/login.conf.d/i2pd: +and also edit ${SYSCONFDIR}/login.conf.d/i2pd: i2pd:\ :openfiles-cur=8192:\ @@ -80,6 +80,18 @@ If you want to use this interface anyway ${SYSCONFDIR}/i2pd/i2pd.conf under the [http] section. +BitTorrent client +================= + +The optional BitTorrent tunnel configuration is installed at +${TRUEPREFIX}/share/examples/i2pd/torrents.conf. To enable the client, +add its [torrents] section to ${SYSCONFDIR}/i2pd/tunnels.conf and +uncomment the settings you want. The RPC example listens on localhost. +The package installs the web interface files under +${LOCALSTATEDIR}/i2pd/torrents/web. + + + Graceful shutdown ================= @@ -90,7 +102,7 @@ your peers and affect the overall operat You can initiate a graceful shutdown without the HTTP interface by sending a signal to the i2pd daemon like this: - kill -INT $(cat /var/i2pd/i2pd.pid) + kill -INT $(cat ${LOCALSTATEDIR}/i2pd/i2pd.pid) When it shuts down gracefully, the i2pd daemon waits for all transit tunnels to expire, which usually takes 10 minutes. @@ -107,15 +119,16 @@ want to reduce this log verbosity by cha parameter in ${SYSCONFDIR}/i2pd/i2pd.conf. If you want log messages to be written to another file, e.g. -/var/i2pd/i2pd.log, you can change the "log" and "logfile" +${LOCALSTATEDIR}/i2pd/i2pd.log, you can change the "log" and "logfile" + parameters in ${SYSCONFDIR}/i2pd/i2pd.conf. To have this log file rotated automatically, you can add an entry to /etc/newsyslog.conf -using the i2pd pid file so that newsyslog(8) can send SIGHUP to the +using the i2pd pid file so that newsyslog(8) can send SIGUSR1 to the daemon after rotation. For example: - /var/i2pd/i2pd.log _i2pd:_i2pd 644 6 * $D13 Z /var/i2pd/i2pd.pid + ${LOCALSTATEDIR}/i2pd/i2pd.log _i2pd:_i2pd 644 6 * $D13 Z ${LOCALSTATEDIR}/i2pd/i2pd.pid SIGUSR1 -Sending SIGHUP is enough for log rotation, and also makes i2pd reload -its tunnel configuration and rotate transient keys. +Send SIGUSR1 to reopen the log file. Send SIGHUP to reload tunnel +configuration and rotate transient keys. Index: net/i2pd/pkg/i2pd.rc =================================================================== RCS file: /cvs/ports/net/i2pd/pkg/i2pd.rc,v diff -u -p -u -p -r1.5 i2pd.rc --- net/i2pd/pkg/i2pd.rc 17 Oct 2026 08:47:19 -0000 1.5 +++ net/i2pd/pkg/i2pd.rc 23 Oct 2026 12:54:04 -0000 @@ -3,12 +3,12 @@ daemon="${TRUEPREFIX}/bin/i2pd --daemon" daemon_user="_i2pd" daemon_flags="--service \ - --datadir=/var/i2pd \ + --datadir=${LOCALSTATEDIR}/i2pd \ --conf=${SYSCONFDIR}/i2pd/i2pd.conf \ --tunconf=${SYSCONFDIR}/i2pd/tunnels.conf \ --tunnelsdir=${SYSCONFDIR}/i2pd/tunnels.d \ - --certsdir=/var/i2pd/certificates" + --certsdir=${LOCALSTATEDIR}/i2pd/certificates" . /etc/rc.d/rc.subr -rc_cmd $1 +rc_cmd "$1"
