106 lines
3.4 KiB
Nix
106 lines
3.4 KiB
Nix
# TODO: Dir creation and file permissions in nix
|
|
{
|
|
pkgs,
|
|
config,
|
|
lib,
|
|
...
|
|
}:
|
|
with lib; let
|
|
cfg = config.util-nixarr.upnp;
|
|
in {
|
|
options.util-nixarr.upnp = {
|
|
enable = mkEnableOption "Enable port forwarding using UPNP.";
|
|
|
|
openTcpPorts = mkOption {
|
|
type = with types; listOf port;
|
|
default = [];
|
|
description = ''
|
|
What TCP ports to open using UPNP.
|
|
'';
|
|
example = [46382 38473];
|
|
};
|
|
|
|
openUdpPorts = mkOption {
|
|
type = with types; listOf port;
|
|
default = [];
|
|
description = ''
|
|
What UDP ports to open using UPNP.
|
|
'';
|
|
example = [46382 38473];
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
# UPNPC firewall access, if not set, then upnpc will fail with "No IGD
|
|
# UPnP Device found !"
|
|
#
|
|
# Alternatively, I also tried allowing all traffic from the router. But
|
|
# I assume that the official way is cleaner/more secure:
|
|
# ```nix
|
|
# networking.firewall.extraCommands = ''
|
|
# iptables -I INPUT -p udp -s 192.168.1.1 -j ACCEPT
|
|
# iptables -I OUTPUT -p udp -d 192.168.1.1 -j ACCEPT
|
|
# '';
|
|
# ```
|
|
#
|
|
# See:
|
|
# https://github.com/miniupnp/miniupnp/blob/8ced59d384de13689d3b1c32405bcb562030b241/miniupnpc/README
|
|
#
|
|
# TODO: Understand this properly
|
|
networking.firewall.extraCommands = ''
|
|
# Rules for IPv4:
|
|
${pkgs.ipset}/bin/ipset -exist create upnp hash:ip,port timeout 3
|
|
iptables -A OUTPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j SET --add-set upnp src,src --exist
|
|
iptables -A INPUT -p udp -m set --match-set upnp dst,dst -j ACCEPT
|
|
iptables -A INPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j ACCEPT
|
|
|
|
# Rules for IPv6:
|
|
${pkgs.ipset}/bin/ipset -exist create upnp6 hash:ip,port timeout 3 family inet6
|
|
ip6tables -A OUTPUT -d ff02::c/128 -p udp -m udp --dport 1900 -j SET --add-set upnp6 src,src --exist
|
|
ip6tables -A OUTPUT -d ff05::c/128 -p udp -m udp --dport 1900 -j SET --add-set upnp6 src,src --exist
|
|
ip6tables -A INPUT -p udp -m set --match-set upnp6 dst,dst -j ACCEPT
|
|
ip6tables -A INPUT -d ff02::c/128 -p udp -m udp --dport 1900 -j ACCEPT
|
|
ip6tables -A INPUT -d ff05::c/128 -p udp -m udp --dport 1900 -j ACCEPT
|
|
'';
|
|
|
|
systemd = {
|
|
services.upnpc = let
|
|
upnp-ports = pkgs.writeShellApplication {
|
|
name = "upnp-ports";
|
|
|
|
runtimeInputs = with pkgs; [miniupnpc];
|
|
|
|
text = (
|
|
strings.concatMapStrings (x: "upnpc -r ${builtins.toString x} UDP" + "\n") cfg.openUpdPorts
|
|
++ strings.concatMapStrings (x: "upnpc -r ${builtins.toString x} TCP" + "\n") cfg.openTcpPorts
|
|
++ ''echo "Successfully requested upnp ports to be opened".''
|
|
);
|
|
};
|
|
in
|
|
mkIf cfg.enable {
|
|
enable = true;
|
|
description = "Sets port on router";
|
|
script = "${upnp-ports}/bin/upnp-ports";
|
|
|
|
serviceConfig = {
|
|
User = "root";
|
|
Type = "oneshot";
|
|
};
|
|
};
|
|
|
|
timers = {
|
|
upnpc = mkIf cfg.enable {
|
|
description = "Sets port on router";
|
|
wantedBy = ["timers.target"];
|
|
|
|
timerConfig = {
|
|
OnCalendar = "hourly";
|
|
Persistent = "true"; # Run service immediately if last window was missed
|
|
RandomizedDelaySec = "5m"; # Run service OnCalendar +- 1h
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
}
|