1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11 cfg = config.services.pppd;
12in
13{
14 meta = {
15 maintainers = [ ];
16 };
17
18 options = {
19 services.pppd = {
20 enable = mkEnableOption "pppd";
21
22 package = mkPackageOption pkgs "ppp" { };
23
24 peers = mkOption {
25 default = { };
26 description = "pppd peers.";
27 type = types.attrsOf (
28 types.submodule (
29 { name, ... }:
30 {
31 options = {
32 name = mkOption {
33 type = types.str;
34 default = name;
35 example = "dialup";
36 description = "Name of the PPP peer.";
37 };
38
39 enable = mkOption {
40 type = types.bool;
41 default = true;
42 example = false;
43 description = "Whether to enable this PPP peer.";
44 };
45
46 autostart = mkOption {
47 type = types.bool;
48 default = true;
49 example = false;
50 description = "Whether the PPP session is automatically started at boot time.";
51 };
52
53 config = mkOption {
54 type = types.lines;
55 default = "";
56 description = "pppd configuration for this peer, see the {manpage}`pppd(8)` man page.";
57 };
58 };
59 }
60 )
61 );
62 };
63 };
64 };
65
66 config =
67 let
68 enabledConfigs = filter (f: f.enable) (attrValues cfg.peers);
69
70 mkEtc = peerCfg: {
71 name = "ppp/peers/${peerCfg.name}";
72 value.text = peerCfg.config;
73 };
74
75 mkSystemd = peerCfg: {
76 name = "pppd-${peerCfg.name}";
77 value = {
78 restartTriggers = [ config.environment.etc."ppp/peers/${peerCfg.name}".source ];
79 before = [ "network.target" ];
80 wants = [ "network.target" ];
81 after = [ "network-pre.target" ];
82 environment = {
83 # pppd likes to write directly into /var/run. This is rude
84 # on a modern system, so we use libredirect to transparently
85 # move those files into /run/pppd.
86 LD_PRELOAD = "${pkgs.libredirect}/lib/libredirect.so";
87 NIX_REDIRECTS = "/var/run=/run/pppd";
88 };
89 serviceConfig =
90 let
91 capabilities = [
92 "CAP_BPF"
93 "CAP_SYS_TTY_CONFIG"
94 "CAP_NET_ADMIN"
95 "CAP_NET_RAW"
96 ];
97 in
98 {
99 ExecStart = "${getBin cfg.package}/sbin/pppd call ${peerCfg.name} nodetach nolog";
100 Restart = "always";
101 RestartSec = 5;
102
103 AmbientCapabilities = capabilities;
104 CapabilityBoundingSet = capabilities;
105 KeyringMode = "private";
106 LockPersonality = true;
107 MemoryDenyWriteExecute = true;
108 NoNewPrivileges = true;
109 PrivateMounts = true;
110 PrivateTmp = true;
111 ProtectControlGroups = true;
112 ProtectHome = true;
113 ProtectHostname = true;
114 ProtectKernelModules = true;
115 # pppd can be configured to tweak kernel settings.
116 ProtectKernelTunables = false;
117 ProtectSystem = "strict";
118 RemoveIPC = true;
119 RestrictAddressFamilies = [
120 "AF_ATMPVC"
121 "AF_ATMSVC"
122 "AF_INET"
123 "AF_INET6"
124 "AF_IPX"
125 "AF_NETLINK"
126 "AF_PACKET"
127 "AF_PPPOX"
128 "AF_UNIX"
129 ];
130 RestrictNamespaces = true;
131 RestrictRealtime = true;
132 RestrictSUIDSGID = true;
133 SecureBits = "no-setuid-fixup-locked noroot-locked";
134 SystemCallFilter = "@system-service";
135 SystemCallArchitectures = "native";
136
137 # All pppd instances on a system must share a runtime
138 # directory in order for PPP multilink to work correctly. So
139 # we give all instances the same /run/pppd directory to store
140 # things in.
141 #
142 # For the same reason, we can't set PrivateUsers=true, because
143 # all instances need to run as the same user to access the
144 # multilink database.
145 RuntimeDirectory = "pppd";
146 RuntimeDirectoryPreserve = true;
147 };
148 wantedBy = mkIf peerCfg.autostart [ "multi-user.target" ];
149 };
150 };
151
152 etcFiles = listToAttrs (map mkEtc enabledConfigs);
153 systemdConfigs = listToAttrs (map mkSystemd enabledConfigs);
154
155 in
156 mkIf cfg.enable {
157 environment.etc = etcFiles;
158 systemd.services = systemdConfigs;
159 };
160}