blob: 2955527ccd0904d868b0280bcd28464456a6da6e [file] [log] [blame]
developer05f3b2b2024-08-19 19:17:34 +08001From f25cbdd28ff46a7beedd07f575614da7c7d269d7 Mon Sep 17 00:00:00 2001
2From: Lorenzo Bianconi <lorenzo@kernel.org>
3Date: Fri, 17 May 2024 11:50:25 +0200
4Subject: [PATCH 005/126] hostapd: afcd: add AFC daemon support
5
6Introduce Automated Frequency Coordination Daemon (AFCD) support
7for UNII-5 and UNII-7 6GHz bands.
8AFCD will be used by hostapd AFC client in order to forward the AFC
9request to the AFC coordinator and decouple AFC connection management
10from hostapd.
11AFC is required for Standard Power Devices (SPDs) to determine a lists
12of channels and EIRP/PSD powers that are available in the 6GHz spectrum.
13AFCD is tested with AFC DUT Test Harness [0].
14Add afc-reply.json as reference for replies from the AFC coordinator.
15
16[0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main
17
18Tested-by: Felix Fietkau <nbd@nbd.name>
19Tested-by: Allen Ye <allen.ye@mediatek.com>
20Tested-by: Krishna Chaitanya <chaitanya.mgit@gmail.com>
21Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
22---
23 afc/.gitignore | 1 +
24 afc/Makefile | 31 +++++
25 afc/afc-reply.txt | 219 +++++++++++++++++++++++++++++++++
26 afc/afcd.c | 305 ++++++++++++++++++++++++++++++++++++++++++++++
27 4 files changed, 556 insertions(+)
28 create mode 100644 afc/.gitignore
29 create mode 100644 afc/Makefile
30 create mode 100644 afc/afc-reply.txt
31 create mode 100644 afc/afcd.c
32
33diff --git a/afc/.gitignore b/afc/.gitignore
34new file mode 100644
35index 000000000..8d8cca905
36--- /dev/null
37+++ b/afc/.gitignore
38@@ -0,0 +1 @@
39+afcd
40diff --git a/afc/Makefile b/afc/Makefile
41new file mode 100644
42index 000000000..a83bd01db
43--- /dev/null
44+++ b/afc/Makefile
45@@ -0,0 +1,31 @@
46+ALL=afcd
47+
48+include ../src/build.rules
49+
50+CFLAGS += -I../src/utils
51+CFLAGS += -I../src
52+
53+OBJS=afcd.o
54+OBJS += ../src/utils/common.o
55+OBJS += ../src/utils/wpa_debug.o
56+OBJS += ../src/utils/wpabuf.o
57+
58+ifndef CONFIG_OS
59+ifdef CONFIG_NATIVE_WINDOWS
60+CONFIG_OS=win32
61+else
62+CONFIG_OS=unix
63+endif
64+endif
65+OBJS += ../src/utils/os_$(CONFIG_OS).o
66+
67+LIBS += -lcurl
68+
69+_OBJS_VAR := OBJS
70+include ../src/objs.mk
71+afcd: $(OBJS)
72+ $(Q)$(LDO) $(LDFLAGS) -o afcd $(OBJS) $(LIBS)
73+ @$(E) " LD " $@
74+
75+clean: common-clean
76+ rm -f core *~
77diff --git a/afc/afc-reply.txt b/afc/afc-reply.txt
78new file mode 100644
79index 000000000..aaa4f8956
80--- /dev/null
81+++ b/afc/afc-reply.txt
82@@ -0,0 +1,219 @@
83+HTTP/1.1 200 OK
84+Content-Type: application/json
85+Content-Length: 4843
86+
87+{
88+ "availableSpectrumInquiryResponses":[
89+ {
90+ "availabilityExpireTime":"2023-02-23T12:53:18Z",
91+ "availableChannelInfo":[
92+ {
93+ "channelCfi":[
94+ 1,
95+ 5,
96+ 9,
97+ 13,
98+ 17,
99+ 21,
100+ 25,
101+ 29,
102+ 33,
103+ 37,
104+ 41,
105+ 45,
106+ 49,
107+ 53,
108+ 57,
109+ 61,
110+ 65,
111+ 69,
112+ 73,
113+ 77,
114+ 81,
115+ 85,
116+ 89,
117+ 93,
118+ 117,
119+ 121,
120+ 125,
121+ 129,
122+ 133,
123+ 137,
124+ 141,
125+ 145,
126+ 149,
127+ 153,
128+ 157,
129+ 161,
130+ 165,
131+ 169,
132+ 173,
133+ 177,
134+ 181
135+ ],
136+ "globalOperatingClass":131,
137+ "maxEirp":[
138+ 5,
139+ 5,
140+ 5,
141+ 5,
142+ 5,
143+ 5,
144+ 5,
145+ 5,
146+ 5,
147+ 5,
148+ 5,
149+ 5,
150+ 5,
151+ 5,
152+ 5,
153+ 5,
154+ 5,
155+ 5,
156+ 5,
157+ 5,
158+ 5,
159+ 5,
160+ 5,
161+ 5,
162+ 5,
163+ 5,
164+ 5,
165+ 5,
166+ 5,
167+ 5,
168+ 5,
169+ 5,
170+ 5,
171+ 5,
172+ 5,
173+ 5,
174+ 5,
175+ 5,
176+ 5,
177+ 5,
178+ 5
179+ ]
180+ },
181+ {
182+ "channelCfi":[
183+ 3,
184+ 11,
185+ 19,
186+ 27,
187+ 35,
188+ 43,
189+ 51,
190+ 59,
191+ 67,
192+ 75,
193+ 83,
194+ 91,
195+ 123,
196+ 131,
197+ 139,
198+ 147,
199+ 155,
200+ 163,
201+ 171,
202+ 179
203+ ],
204+ "globalOperatingClass":132,
205+ "maxEirp":[
206+ 5,
207+ 5,
208+ 5,
209+ 5,
210+ 5,
211+ 5,
212+ 5,
213+ 5,
214+ 5,
215+ 5,
216+ 5,
217+ 5,
218+ 5,
219+ 5,
220+ 5,
221+ 5,
222+ 5,
223+ 5,
224+ 5,
225+ 5
226+ ]
227+ },
228+ {
229+ "channelCfi":[
230+ 7,
231+ 23,
232+ 39,
233+ 55,
234+ 71,
235+ 87,
236+ 135,
237+ 151,
238+ 167
239+ ],
240+ "globalOperatingClass":133,
241+ "maxEirp":[
242+ 5,
243+ 5,
244+ 5,
245+ 5,
246+ 5,
247+ 5,
248+ 5,
249+ 5,
250+ 5
251+ ]
252+ },
253+ {
254+ "channelCfi":[
255+ 15,
256+ 47,
257+ 79,
258+ 143
259+ ],
260+ "globalOperatingClass":134,
261+ "maxEirp":[
262+ 5,
263+ 5,
264+ 5,
265+ 5
266+ ]
267+ },
268+ {
269+ "channelCfi":[
270+ ],
271+ "globalOperatingClass":135,
272+ "maxEirp":[
273+ ]
274+ }
275+ ],
276+ "availableFrequencyInfo":[
277+ {
278+ "frequencyRange":{
279+ "highFrequency":6425,
280+ "lowFrequency":5925
281+ },
282+ "maxPSD":3.98970004336019
283+ },
284+ {
285+ "frequencyRange":{
286+ "highFrequency":6865,
287+ "lowFrequency":6525
288+ },
289+ "maxPSD":3.98970004336019
290+ }
291+ ],
292+ "requestId":"11235813",
293+ "response":{
294+ "responseCode":0,
295+ "shortDescription":"Success"
296+ },
297+ "rulesetId":"US_47_CFR_PART_15_SUBPART_E"
298+ }
299+ ],
300+ "version":"1.1"
301+}
302diff --git a/afc/afcd.c b/afc/afcd.c
303new file mode 100644
304index 000000000..2b99940ae
305--- /dev/null
306+++ b/afc/afcd.c
307@@ -0,0 +1,305 @@
308+/*
309+ * Automated Frequency Coordination Daemon
310+ * Copyright (c) 2024, Lorenzo Bianconi <lorenzo@kernel.org>
311+ *
312+ * This software may be distributed under the terms of the BSD license.
313+ * See README for more details.
314+ */
315+
316+#include <curl/curl.h>
317+#include <sys/un.h>
318+#include <sys/stat.h>
319+
320+#include "utils/includes.h"
321+#include "utils/common.h"
322+
323+#define CURL_TIMEOUT 60
324+#define AFCD_SOCK "afcd.sock"
325+
326+struct curl_ctx {
327+ char *buf;
328+ size_t buf_len;
329+};
330+
331+static volatile bool exiting;
332+
333+static char *path = "/var/run";
334+static char *bearer_token;
335+static char *url;
336+static int port = 443;
337+
338+
339+static size_t afcd_curl_cb_write(void *ptr, size_t size, size_t nmemb,
340+ void *userdata)
341+{
342+ struct curl_ctx *ctx = userdata;
343+ char *buf;
344+
345+ buf = os_realloc(ctx->buf, ctx->buf_len + size * nmemb + 1);
346+ if (!buf)
347+ return 0;
348+
349+ ctx->buf = buf;
350+ os_memcpy(buf + ctx->buf_len, ptr, size * nmemb);
351+ buf[ctx->buf_len + size * nmemb] = '\0';
352+ ctx->buf_len += size * nmemb;
353+
354+ return size * nmemb;
355+}
356+
357+
358+static int afcd_send_request(struct curl_ctx *ctx, unsigned char *request)
359+{
360+ struct curl_slist *headers = NULL, *tmp;
361+ int ret = CURLE_FAILED_INIT;
362+ CURL *curl;
363+
364+ wpa_printf(MSG_DEBUG, "Sending AFC request to %s", url);
365+
366+ curl_global_init(CURL_GLOBAL_ALL);
367+ curl = curl_easy_init();
368+ if (!curl)
369+ goto out_global_cleanup;
370+
371+ headers = curl_slist_append(headers, "Accept: application/json");
372+ if (!headers)
373+ goto out_easy_cleanup;
374+
375+ tmp = curl_slist_append(headers, "Content-Type: application/json");
376+ if (!tmp)
377+ goto out_slist_free_all;
378+ headers = tmp;
379+
380+ tmp = curl_slist_append(headers, "charset: utf-8");
381+ if (!tmp)
382+ goto out_slist_free_all;
383+ headers = tmp;
384+
385+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
386+ curl_easy_setopt(curl, CURLOPT_URL, url);
387+ curl_easy_setopt(curl, CURLOPT_PORT, port);
388+ curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
389+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
390+ afcd_curl_cb_write);
391+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
392+ curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcrp/0.1");
393+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, CURL_TIMEOUT);
394+ curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
395+ if (bearer_token)
396+ curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, bearer_token);
397+ curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BEARER);
398+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
399+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L);
400+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request);
401+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYSTATUS, 1L);
402+
403+ ret = curl_easy_perform(curl);
404+ if (ret != CURLE_OK)
405+ wpa_printf(MSG_ERROR, "curl_easy_perform failed: %s",
406+ curl_easy_strerror(ret));
407+
408+out_slist_free_all:
409+ curl_slist_free_all(headers);
410+out_easy_cleanup:
411+ curl_easy_cleanup(curl);
412+out_global_cleanup:
413+ curl_global_cleanup();
414+
415+ return ret == CURLE_OK ? 0 : -EINVAL;
416+}
417+
418+
419+static void handle_term(int sig)
420+{
421+ wpa_printf(MSG_ERROR, "Received signal %d", sig);
422+ exiting = true;
423+}
424+
425+
426+static void usage(void)
427+{
428+ wpa_printf(MSG_ERROR,
429+ "%s:\n"
430+ "afcd -u<url> [-p<port>][-t<token>][-D<unix-sock dir>][-P<PID file>][-dB]",
431+ __func__);
432+}
433+
434+
435+#define BUFSIZE 8192
436+static int afcd_server_run(void)
437+{
438+ size_t len = os_strlen(path) + 1 + os_strlen(AFCD_SOCK);
439+ struct sockaddr_un addr = {
440+ .sun_family = AF_UNIX,
441+#ifdef __FreeBSD__
442+ .sun_len = sizeof(addr),
443+#endif /* __FreeBSD__ */
444+ };
445+ int sockfd, ret = 0;
446+ char *fname = NULL;
447+ unsigned char *buf;
448+ fd_set read_set;
449+
450+ if (len >= sizeof(addr.sun_path))
451+ return -EINVAL;
452+
453+ if (mkdir(path, S_IRWXU | S_IRWXG) < 0 && errno != EEXIST)
454+ return -EINVAL;
455+
456+ buf = os_malloc(BUFSIZE);
457+ if (!buf)
458+ return -ENOMEM;
459+
460+ fname = os_malloc(len + 1);
461+ if (!fname) {
462+ ret = -ENOMEM;
463+ goto free_buf;
464+ }
465+
466+ os_snprintf(fname, len + 1, "%s/%s", path, AFCD_SOCK);
467+ fname[len] = '\0';
468+ os_strlcpy(addr.sun_path, fname, sizeof(addr.sun_path));
469+
470+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
471+ if (sockfd < 0) {
472+ wpa_printf(MSG_ERROR, "Failed creating socket");
473+ ret = -errno;
474+ goto unlink;
475+ }
476+
477+ if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
478+ wpa_printf(MSG_ERROR, "Failed to bind socket");
479+ ret = -errno;
480+ goto close;
481+ }
482+
483+ if (listen(sockfd, 10) < 0) {
484+ wpa_printf(MSG_ERROR, "Failed to listen on socket");
485+ ret = -errno;
486+ goto close;
487+ }
488+
489+ FD_ZERO(&read_set);
490+ while (!exiting) {
491+ socklen_t addr_len = sizeof(addr);
492+ struct sockaddr_in6 c_addr;
493+ struct timeval timeout = {
494+ .tv_sec = 1,
495+ };
496+ struct curl_ctx ctx = {};
497+ int fd;
498+
499+ FD_SET(sockfd, &read_set);
500+ if (select(sockfd + 1, &read_set, NULL, NULL, &timeout) < 0) {
501+ if (errno != EINTR) {
502+ wpa_printf(MSG_ERROR,
503+ "Select failed on socket");
504+ ret = -errno;
505+ break;
506+ }
507+ continue;
508+ }
509+
510+ if (!FD_ISSET(sockfd, &read_set))
511+ continue;
512+
513+ fd = accept(sockfd, (struct sockaddr *)&c_addr,
514+ &addr_len);
515+ if (fd < 0) {
516+ if (errno != EINTR) {
517+ wpa_printf(MSG_ERROR,
518+ "Failed accepting connections");
519+ ret = -errno;
520+ break;
521+ }
522+ continue;
523+ }
524+
525+ os_memset(buf, 0, BUFSIZE);
526+ if (recv(fd, buf, BUFSIZE - 1, 0) <= 0) {
527+ close(fd);
528+ continue;
529+ }
530+
531+ wpa_printf(MSG_DEBUG, "Received request: %s", buf);
532+ if (!afcd_send_request(&ctx, buf)) {
533+ wpa_printf(MSG_DEBUG, "Received reply: %s", ctx.buf);
534+ send(fd, ctx.buf, ctx.buf_len, MSG_NOSIGNAL);
535+ free(ctx.buf);
536+ }
537+ close(fd);
538+ }
539+close:
540+ close(sockfd);
541+unlink:
542+ unlink(fname);
543+ os_free(fname);
544+free_buf:
545+ os_free(buf);
546+
547+ return ret;
548+}
549+
550+
551+int main(int argc, char **argv)
552+{
553+ bool daemonize = false;
554+ char *pid_file = NULL;
555+
556+ if (os_program_init())
557+ return -1;
558+
559+ for (;;) {
560+ int c = getopt(argc, argv, "u:p:t:D:P:hdB");
561+
562+ if (c < 0)
563+ break;
564+
565+ switch (c) {
566+ case 'h':
567+ usage();
568+ return 0;
569+ case 'B':
570+ daemonize = true;
571+ break;
572+ case 'D':
573+ path = optarg;
574+ break;
575+ case 'P':
576+ os_free(pid_file);
577+ pid_file = os_rel2abs_path(optarg);
578+ break;
579+ case 'u':
580+ url = optarg;
581+ break;
582+ case 'p':
583+ port = atoi(optarg);
584+ break;
585+ case 'd':
586+ if (wpa_debug_level > 0)
587+ wpa_debug_level--;
588+ break;
589+ case 't':
590+ bearer_token = optarg;
591+ break;
592+ default:
593+ usage();
594+ return -EINVAL;
595+ }
596+ }
597+
598+ if (!url) {
599+ usage();
600+ return -EINVAL;
601+ }
602+
603+ if (daemonize && os_daemonize(pid_file)) {
604+ wpa_printf(MSG_ERROR, "daemon: %s", strerror(errno));
605+ return -EINVAL;
606+ }
607+
608+ signal(SIGTERM, handle_term);
609+ signal(SIGINT, handle_term);
610+
611+ return afcd_server_run();
612+}
613--
6142.18.0
615