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