1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib.modules) mkIf mkMerge;
10 inherit (lib.options) mkOption mkPackageOption mkEnableOption;
11 inherit (lib.lists) optional optionals;
12 inherit (lib.strings)
13 hasSuffix
14 escapeShellArgs
15 ;
16 inherit (lib) types;
17 cfg = config.services.vwifi;
18in
19{
20 options = {
21 services.vwifi =
22 let
23 mkOptionalPort =
24 name:
25 mkOption {
26 description = ''
27 The ${name} port. Set to null if we should leave it unset.
28 '';
29 type = with types; nullOr port;
30 default = null;
31 };
32 in
33 {
34 package = mkPackageOption pkgs "vwifi" { };
35 module = {
36 enable = mkEnableOption "mac80211_hwsim module";
37 numRadios = mkOption {
38 description = "The number of virtual radio interfaces to create.";
39 type = types.int;
40 default = 1;
41 };
42 macPrefix = mkOption {
43 description = ''
44 The prefix for MAC addresses to use, without the trailing ':'.
45 If one radio is created, you can specify the whole MAC address here.
46 The default is defined in vwifi/src/config.h.
47 '';
48 type = types.strMatching "^(([0-9A-Fa-f]{2}:){0,5}[0-9A-Fa-f]{2})$";
49 default = "74:F8:F6";
50 };
51 };
52 client = {
53 enable = mkEnableOption "vwifi client";
54 spy = mkEnableOption "spy mode, useful for wireless monitors";
55 serverAddress = mkOption {
56 description = ''
57 The address of the server. If set to null, will try to use the vsock protocol.
58 Note that this assumes that the server is spawned on the host and passed through to
59 QEMU, with something like:
60
61 -device vhost-vsock-pci,id=vwifi0,guest-cid=42
62 '';
63 type = with types; nullOr str;
64 default = null;
65 };
66 serverPort = mkOptionalPort "server port";
67 extraArgs = mkOption {
68 description = ''
69 Extra arguments to pass to vwifi-client. You can use this if you want to bring
70 the radios up using vwifi-client instead of at boot.
71 '';
72 type = with types; listOf str;
73 default = [ ];
74 example = [
75 "--number"
76 "3"
77 ];
78 };
79 };
80 server = {
81 enable = mkEnableOption "vwifi server";
82 vsock.enable = mkEnableOption "vsock kernel module";
83 ports = {
84 vhost = mkOptionalPort "vhost";
85 tcp = mkOptionalPort "TCP server";
86 spy = mkOptionalPort "spy interface";
87 control = mkOptionalPort "control interface";
88 };
89 openFirewall = mkEnableOption "opening the firewall for the TCP and spy ports";
90 extraArgs = mkOption {
91 description = ''
92 Extra arguments to pass to vwifi-server. You can use this for things including
93 changing the ports or inducing packet loss.
94 '';
95 type = with types; listOf str;
96 default = [ ];
97 example = [ "--lost-packets" ];
98 };
99 };
100 };
101 };
102
103 config = mkMerge [
104 (mkIf cfg.module.enable {
105 boot.kernelModules = [
106 "mac80211_hwsim"
107 ];
108 boot.extraModprobeConfig = ''
109 # We'll add more radios using vwifi-add-interfaces in the systemd unit.
110 options mac80211_hwsim radios=0
111 '';
112 systemd.services.vwifi-add-interfaces = mkIf (cfg.module.numRadios > 0) {
113 description = "vwifi interface bringup";
114 wantedBy = [ "network-pre.target" ];
115 serviceConfig = {
116 Type = "oneshot";
117 ExecStart =
118 let
119 args = [
120 (toString cfg.module.numRadios)
121 cfg.module.macPrefix
122 ];
123 in
124 "${cfg.package}/bin/vwifi-add-interfaces ${escapeShellArgs args}";
125 };
126 };
127 assertions = [
128 {
129 assertion = !(hasSuffix ":" cfg.module.macPrefix);
130 message = ''
131 services.vwifi.module.macPrefix should not have a trailing ":".
132 '';
133 }
134 ];
135 })
136 (mkIf cfg.client.enable {
137 systemd.services.vwifi-client =
138 let
139 clientArgs =
140 optional cfg.client.spy "--spy"
141 ++ optional (cfg.client.serverAddress != null) cfg.client.serverAddress
142 ++ optionals (cfg.client.serverPort != null) [
143 "--port"
144 cfg.client.serverPort
145 ]
146 ++ cfg.client.extraArgs;
147 in
148 rec {
149 description = "vwifi client";
150 wantedBy = [ "multi-user.target" ];
151 after = [ "network.target" ];
152 requires = after;
153 serviceConfig = {
154 ExecStart = "${cfg.package}/bin/vwifi-client ${escapeShellArgs clientArgs}";
155 };
156 };
157 })
158 (mkIf cfg.server.enable {
159 boot.kernelModules = mkIf cfg.server.vsock.enable [
160 "vhost_vsock"
161 ];
162 networking.firewall.allowedTCPPorts = mkIf cfg.server.openFirewall (
163 optional (cfg.server.ports.tcp != null) cfg.server.ports.tcp
164 ++ optional (cfg.server.ports.spy != null) cfg.server.ports.spy
165 );
166 systemd.services.vwifi-server =
167 let
168 serverArgs =
169 optionals (cfg.server.ports.vhost != null) [
170 "--port-vhost"
171 (toString cfg.server.ports.vhost)
172 ]
173 ++ optionals (cfg.server.ports.tcp != null) [
174 "--port-tcp"
175 (toString cfg.server.ports.tcp)
176 ]
177 ++ optionals (cfg.server.ports.spy != null) [
178 "--port-spy"
179 (toString cfg.server.ports.spy)
180 ]
181 ++ optionals (cfg.server.ports.control != null) [
182 "--port-ctrl"
183 (toString cfg.server.ports.control)
184 ]
185 ++ cfg.server.extraArgs;
186 in
187 rec {
188 description = "vwifi server";
189 wantedBy = [ "multi-user.target" ];
190 after = [ "network.target" ];
191 requires = after;
192 serviceConfig = {
193 ExecStart = "${cfg.package}/bin/vwifi-server ${escapeShellArgs serverArgs}";
194 };
195 };
196 })
197 ];
198
199 meta.maintainers = with lib.maintainers; [ numinit ];
200}