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 "pppd";
16
17 package = mkOption {
18 default = pkgs.ppp;
19 defaultText = "pkgs.ppp";
20 type = types.package;
21 description = "pppd package to use.";
22 };
23
24 peers = mkOption {
25 default = {};
26 description = "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 = "Name of the PPP peer.";
36 };
37
38 enable = mkOption {
39 type = types.bool;
40 default = true;
41 example = false;
42 description = "Whether to enable this PPP peer.";
43 };
44
45 autostart = mkOption {
46 type = types.bool;
47 default = true;
48 example = false;
49 description = "Whether the PPP session is automatically started at boot time.";
50 };
51
52 config = mkOption {
53 type = types.lines;
54 default = "";
55 description = "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 = {
86 ExecStart = "${getBin cfg.package}/sbin/pppd call ${peerCfg.name} nodetach nolog";
87 Restart = "always";
88 RestartSec = 5;
89
90 AmbientCapabilities = "CAP_SYS_TTY_CONFIG CAP_NET_ADMIN CAP_NET_RAW CAP_SYS_ADMIN";
91 CapabilityBoundingSet = "CAP_SYS_TTY_CONFIG CAP_NET_ADMIN CAP_NET_RAW CAP_SYS_ADMIN";
92 KeyringMode = "private";
93 LockPersonality = true;
94 MemoryDenyWriteExecute = true;
95 NoNewPrivileges = true;
96 PrivateMounts = true;
97 PrivateTmp = true;
98 ProtectControlGroups = true;
99 ProtectHome = true;
100 ProtectHostname = true;
101 ProtectKernelModules = true;
102 # pppd can be configured to tweak kernel settings.
103 ProtectKernelTunables = false;
104 ProtectSystem = "strict";
105 RemoveIPC = true;
106 RestrictAddressFamilies = "AF_PACKET AF_UNIX AF_PPPOX AF_ATMPVC AF_ATMSVC AF_INET AF_INET6 AF_IPX";
107 RestrictNamespaces = true;
108 RestrictRealtime = true;
109 RestrictSUIDSGID = true;
110 SecureBits = "no-setuid-fixup-locked noroot-locked";
111 SystemCallFilter = "@system-service";
112 SystemCallArchitectures = "native";
113
114 # All pppd instances on a system must share a runtime
115 # directory in order for PPP multilink to work correctly. So
116 # we give all instances the same /run/pppd directory to store
117 # things in.
118 #
119 # For the same reason, we can't set PrivateUsers=true, because
120 # all instances need to run as the same user to access the
121 # multilink database.
122 RuntimeDirectory = "pppd";
123 RuntimeDirectoryPreserve = true;
124 };
125 wantedBy = mkIf peerCfg.autostart [ "multi-user.target" ];
126 };
127 };
128
129 etcFiles = listToAttrs (map mkEtc enabledConfigs);
130 systemdConfigs = listToAttrs (map mkSystemd enabledConfigs);
131
132 in mkIf cfg.enable {
133 environment.etc = etcFiles;
134 systemd.services = systemdConfigs;
135 };
136}