diff mbox series

[v4,1/4] hostapd: afcd: add AFC daemon support

Message ID 636f5b4c075621220e9dd8785a58d233701b3eb9.1712755468.git.lorenzo@kernel.org
State Superseded
Headers show
Series Introduce Automated Frequency Coordination (AFC) support | expand

Commit Message

lorenzo@kernel.org April 10, 2024, 1:32 p.m. UTC
Introduce Automated Frequency Coordination Daemon (AFCD) support
for UNII-5 and UNII-7 6GHz bands.
AFCD will be used by hostapd AFC client in order to forward the AFC
request to the AFC coordinator and decouple AFC connection management
from hostapd.
AFC is required for Standard Power Devices (SPDs) to determine a lists
of channels and EIRP/PSD powers that are available in the 6GHz spectrum.
AFCD is tested with AFC DUT Test Harness [0].
Add afc-reply.json as reference for replies from the AFC coordinator.

[0] https://github.com/Wi-FiTestSuite/AFC-DUT/tree/main

Tested-by: Felix Fietkau <nbd@nbd.name>
Tested-by: Allen Ye <allen.ye@mediatek.com>
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
---
 afc/.gitignore     |   1 +
 afc/Makefile       |  31 +++++
 afc/afc-reply.json | 215 +++++++++++++++++++++++++++++++++
 afc/afcd.c         | 292 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 539 insertions(+)
 create mode 100644 afc/.gitignore
 create mode 100644 afc/Makefile
 create mode 100644 afc/afc-reply.json
 create mode 100644 afc/afcd.c

Comments

Jesús Fernández Manzano April 14, 2024, 1:19 p.m. UTC | #1
Hi all,

El 10/4/24 a las 15:32, Lorenzo Bianconi escribió:
> +	headers  = curl_slist_append(headers, "charset: utf-8");

After building the headers with curl_slist_append() and sending the 
message with curl_easy_perform(), you must free them with 
curl_slist_free_all() or the memory will be leaked. See:

- https://curl.se/libcurl/c/curl_slist_append.html
- https://curl.se/libcurl/c/curl_slist_free_all.html

Regards,
Jesús
Krishna Chaitanya April 14, 2024, 5:28 p.m. UTC | #2
On Wed, Apr 10, 2024 at 7:05 PM Lorenzo Bianconi <lorenzo@kernel.org> wrote:
> +         "requestId":"11235814",
11235813 to match the default request ID
> +
> +static void usage(void)
> +{
> +       wpa_printf(MSG_ERROR,
> +                  "%s:\n"
> +                  "afcd -u<url> [-p<port>][-t<token>][-D<unix-sock dir>][-P<PID file>][-dB]",
> +                  __func__);
> +}
missing `-d` usage
> +
Also, for setting `Content-Length` I had to use the below script,
should we add this as well
or at least put it in a README?

$ cat afc_server_sim.sh
#!/bin/bash

# Define the JSON content
json_content=$(cat afc-reply.json)

# Calculate the length of the JSON content
content_length=$(echo "$json_content" | wc -m)

# Create the HTTP response
http_response="HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: $content_length

$json_content"

# Write the HTTP response to a file
echo -e "$http_response" > response.txt

# Use netcat to listen on a specific port and send the response to any
incoming requests
while true; do nc -l 9999 < response.txt; done
### END OF SCRIPT##
and then I run `sudo ./afcd -u http://localhost -p 9999 -ddd`
lorenzo@kernel.org April 16, 2024, 3:22 p.m. UTC | #3
> On Wed, Apr 10, 2024 at 7:05 PM Lorenzo Bianconi <lorenzo@kernel.org> wrote:
> > +         "requestId":"11235814",
> 11235813 to match the default request ID
> > +
> > +static void usage(void)
> > +{
> > +       wpa_printf(MSG_ERROR,
> > +                  "%s:\n"
> > +                  "afcd -u<url> [-p<port>][-t<token>][-D<unix-sock dir>][-P<PID file>][-dB]",
> > +                  __func__);
> > +}
> missing `-d` usage

what do you mean?

> > +
> Also, for setting `Content-Length` I had to use the below script,
> should we add this as well
> or at least put it in a README?

ack, thx. I will fix the afc-reply example.

Regards,
Lorenzo

> 
> $ cat afc_server_sim.sh
> #!/bin/bash
> 
> # Define the JSON content
> json_content=$(cat afc-reply.json)
> 
> # Calculate the length of the JSON content
> content_length=$(echo "$json_content" | wc -m)
> 
> # Create the HTTP response
> http_response="HTTP/1.1 200 OK
> Content-Type: application/json
> Content-Length: $content_length
> 
> $json_content"
> 
> # Write the HTTP response to a file
> echo -e "$http_response" > response.txt
> 
> # Use netcat to listen on a specific port and send the response to any
> incoming requests
> while true; do nc -l 9999 < response.txt; done
> ### END OF SCRIPT##
> and then I run `sudo ./afcd -u http://localhost -p 9999 -ddd`
lorenzo@kernel.org April 16, 2024, 3:23 p.m. UTC | #4
On Apr 14, Jesús Fernández Manzano wrote:
> Hi all,
> 
> El 10/4/24 a las 15:32, Lorenzo Bianconi escribió:
> > +	headers  = curl_slist_append(headers, "charset: utf-8");
> 
> After building the headers with curl_slist_append() and sending the message
> with curl_easy_perform(), you must free them with curl_slist_free_all() or
> the memory will be leaked. See:
> 
> - https://curl.se/libcurl/c/curl_slist_append.html
> - https://curl.se/libcurl/c/curl_slist_free_all.html

ack, thx. I will fix it.

Regartds,
Lorenzo

> 
> Regards,
> Jesús
Krishna Chaitanya April 17, 2024, 7:41 a.m. UTC | #5
On Tue, Apr 16, 2024 at 8:52 PM Lorenzo Bianconi <lorenzo@kernel.org> wrote:
>
> > On Wed, Apr 10, 2024 at 7:05 PM Lorenzo Bianconi <lorenzo@kernel.org> wrote:
> > > +         "requestId":"11235814",
> > 11235813 to match the default request ID
> > > +
> > > +static void usage(void)
> > > +{
> > > +       wpa_printf(MSG_ERROR,
> > > +                  "%s:\n"
> > > +                  "afcd -u<url> [-p<port>][-t<token>][-D<unix-sock dir>][-P<PID file>][-dB]",
> > > +                  __func__);
> > > +}
> > missing `-d` usage
>
> what do you mean?
a nit, just missing "-d" in the usage text.
diff mbox series

Patch

diff --git a/afc/.gitignore b/afc/.gitignore
new file mode 100644
index 000000000..8d8cca905
--- /dev/null
+++ b/afc/.gitignore
@@ -0,0 +1 @@ 
+afcd
diff --git a/afc/Makefile b/afc/Makefile
new file mode 100644
index 000000000..a83bd01db
--- /dev/null
+++ b/afc/Makefile
@@ -0,0 +1,31 @@ 
+ALL=afcd
+
+include ../src/build.rules
+
+CFLAGS += -I../src/utils
+CFLAGS += -I../src
+
+OBJS=afcd.o
+OBJS += ../src/utils/common.o
+OBJS += ../src/utils/wpa_debug.o
+OBJS += ../src/utils/wpabuf.o
+
+ifndef CONFIG_OS
+ifdef CONFIG_NATIVE_WINDOWS
+CONFIG_OS=win32
+else
+CONFIG_OS=unix
+endif
+endif
+OBJS += ../src/utils/os_$(CONFIG_OS).o
+
+LIBS += -lcurl
+
+_OBJS_VAR := OBJS
+include ../src/objs.mk
+afcd: $(OBJS)
+	$(Q)$(LDO) $(LDFLAGS) -o afcd $(OBJS) $(LIBS)
+	@$(E) "  LD " $@
+
+clean: common-clean
+	rm -f core *~
diff --git a/afc/afc-reply.json b/afc/afc-reply.json
new file mode 100644
index 000000000..35882c74c
--- /dev/null
+++ b/afc/afc-reply.json
@@ -0,0 +1,215 @@ 
+{
+   "availableSpectrumInquiryResponses":[
+      {
+         "availabilityExpireTime":"2023-02-23T12:53:18Z",
+         "availableChannelInfo":[
+            {
+               "channelCfi":[
+                  1,
+                  5,
+                  9,
+                  13,
+                  17,
+                  21,
+                  25,
+                  29,
+                  33,
+                  37,
+                  41,
+                  45,
+                  49,
+                  53,
+                  57,
+                  61,
+                  65,
+                  69,
+                  73,
+                  77,
+                  81,
+                  85,
+                  89,
+                  93,
+                  117,
+                  121,
+                  125,
+                  129,
+                  133,
+                  137,
+                  141,
+                  145,
+                  149,
+                  153,
+                  157,
+                  161,
+                  165,
+                  169,
+                  173,
+                  177,
+                  181
+               ],
+               "globalOperatingClass":131,
+               "maxEirp":[
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5
+               ]
+            },
+            {
+               "channelCfi":[
+                  3,
+                  11,
+                  19,
+                  27,
+                  35,
+                  43,
+                  51,
+                  59,
+                  67,
+                  75,
+                  83,
+                  91,
+                  123,
+                  131,
+                  139,
+                  147,
+                  155,
+                  163,
+                  171,
+                  179
+               ],
+               "globalOperatingClass":132,
+               "maxEirp":[
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5
+               ]
+            },
+            {
+               "channelCfi":[
+                  7,
+                  23,
+                  39,
+                  55,
+                  71,
+                  87,
+                  135,
+                  151,
+                  167
+               ],
+               "globalOperatingClass":133,
+               "maxEirp":[
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5,
+                  5
+               ]
+            },
+            {
+               "channelCfi":[
+                  15,
+                  47,
+                  79,
+                  143
+               ],
+               "globalOperatingClass":134,
+               "maxEirp":[
+                  5,
+                  5,
+                  5,
+                  5
+               ]
+            },
+            {
+               "channelCfi":[
+               ],
+               "globalOperatingClass":135,
+               "maxEirp":[
+               ]
+            }
+         ],
+         "availableFrequencyInfo":[
+            {
+               "frequencyRange":{
+                  "highFrequency":6425,
+                  "lowFrequency":5925
+               },
+               "maxPSD":3.98970004336019
+            },
+            {
+               "frequencyRange":{
+                  "highFrequency":6865,
+                  "lowFrequency":6525
+               },
+               "maxPSD":3.98970004336019
+            }
+         ],
+         "requestId":"11235814",
+         "response":{
+            "responseCode":0,
+            "shortDescription":"Success"
+         },
+         "rulesetId":"US_47_CFR_PART_15_SUBPART_E"
+      }
+   ],
+   "version":"1.1"
+}
diff --git a/afc/afcd.c b/afc/afcd.c
new file mode 100644
index 000000000..445db7c39
--- /dev/null
+++ b/afc/afcd.c
@@ -0,0 +1,292 @@ 
+/*
+ * Automated Frequency Coordination Daemon
+ * Copyright (c) 2024, Lorenzo Bianconi <lorenzo@kernel.org>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include <curl/curl.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+
+#include "utils/includes.h"
+#include "utils/common.h"
+
+#define CURL_TIMEOUT	60
+#define AFCD_SOCK	"afcd.sock"
+
+struct curl_ctx {
+	char *buf;
+	size_t buf_len;
+};
+
+static volatile bool exiting;
+
+static char *path = "/var/run";
+static char *bearer_token;
+static char *url;
+static int port = 443;
+
+
+static size_t afcd_curl_cb_write(void *ptr, size_t size, size_t nmemb,
+				 void *userdata)
+{
+	struct curl_ctx *ctx = userdata;
+	char *buf;
+
+	buf = os_realloc(ctx->buf, ctx->buf_len + size * nmemb + 1);
+	if (!buf)
+		return 0;
+
+	ctx->buf = buf;
+	os_memcpy(buf + ctx->buf_len, ptr, size * nmemb);
+	buf[ctx->buf_len + size * nmemb] = '\0';
+	ctx->buf_len += size * nmemb;
+
+	return size * nmemb;
+}
+
+
+static int afcd_send_request(struct curl_ctx *ctx, unsigned char *request)
+{
+	struct curl_slist *headers = NULL;
+	CURL *curl;
+	int ret;
+
+	wpa_printf(MSG_DEBUG, "Sending AFC request to %s", url);
+
+	curl_global_init(CURL_GLOBAL_ALL);
+	curl = curl_easy_init();
+	if (!curl)
+		return -ENOMEM;
+
+	headers  = curl_slist_append(headers, "Accept: application/json");
+	headers  = curl_slist_append(headers,
+				     "Content-Type: application/json");
+	headers  = curl_slist_append(headers, "charset: utf-8");
+
+	curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+	curl_easy_setopt(curl, CURLOPT_URL, url);
+	curl_easy_setopt(curl, CURLOPT_PORT, port);
+	curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+			 afcd_curl_cb_write);
+	curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
+	curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcrp/0.1");
+	curl_easy_setopt(curl, CURLOPT_TIMEOUT, CURL_TIMEOUT);
+	curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
+	if (bearer_token)
+		curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, bearer_token);
+	curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BEARER);
+	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
+	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L);
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request);
+	curl_easy_setopt(curl, CURLOPT_SSL_VERIFYSTATUS, 1L);
+
+	ret = curl_easy_perform(curl);
+	if (ret != CURLE_OK)
+		wpa_printf(MSG_ERROR, "curl_easy_perform failed: %s",
+			   curl_easy_strerror(ret));
+
+	curl_easy_cleanup(curl);
+	curl_global_cleanup();
+
+	return ret == CURLE_OK ? 0 : -EINVAL;
+}
+
+
+static void handle_term(int sig)
+{
+	wpa_printf(MSG_ERROR, "Received signal %d", sig);
+	exiting = true;
+}
+
+
+static void usage(void)
+{
+	wpa_printf(MSG_ERROR,
+		   "%s:\n"
+		   "afcd -u<url> [-p<port>][-t<token>][-D<unix-sock dir>][-P<PID file>][-dB]",
+		   __func__);
+}
+
+
+#define BUFSIZE		8192
+static int afcd_server_run(void)
+{
+	size_t len = os_strlen(path) + 1 + os_strlen(AFCD_SOCK);
+	struct sockaddr_un addr = {
+		.sun_family = AF_UNIX,
+#ifdef __FreeBSD__
+		.sun_len = sizeof(addr),
+#endif /* __FreeBSD__ */
+	};
+	int sockfd, ret = 0;
+	char *fname = NULL;
+	unsigned char *buf;
+	fd_set read_set;
+
+	if (len >= sizeof(addr.sun_path))
+		return -EINVAL;
+
+	if (mkdir(path, S_IRWXU | S_IRWXG) < 0 && errno != EEXIST)
+		return -EINVAL;
+
+	buf = os_malloc(BUFSIZE);
+	if (!buf)
+		return -ENOMEM;
+
+	fname = os_malloc(len + 1);
+	if (!fname) {
+		ret = -ENOMEM;
+		goto free_buf;
+	}
+
+	os_snprintf(fname, len + 1, "%s/%s", path, AFCD_SOCK);
+	fname[len] = '\0';
+	os_strlcpy(addr.sun_path, fname, sizeof(addr.sun_path));
+
+	sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (sockfd < 0) {
+		wpa_printf(MSG_ERROR, "Failed creating socket");
+		ret = -errno;
+		goto unlink;
+	}
+
+	if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+		wpa_printf(MSG_ERROR, "Failed to bind socket");
+		ret = -errno;
+		goto close;
+	}
+
+	if (listen(sockfd, 10) < 0) {
+		wpa_printf(MSG_ERROR, "Failed to listen on socket");
+		ret = -errno;
+		goto close;
+	}
+
+	FD_ZERO(&read_set);
+	while (!exiting) {
+		socklen_t addr_len = sizeof(addr);
+		struct sockaddr_in6 c_addr;
+		struct timeval timeout = {
+			.tv_sec = 1,
+		};
+		struct curl_ctx ctx = {};
+		int fd;
+
+		FD_SET(sockfd, &read_set);
+		if (select(sockfd + 1, &read_set, NULL, NULL, &timeout) < 0) {
+			if (errno != EINTR) {
+				wpa_printf(MSG_ERROR,
+					   "Select failed on socket");
+				ret = -errno;
+				break;
+			}
+			continue;
+		}
+
+		if (!FD_ISSET(sockfd, &read_set))
+			continue;
+
+		fd = accept(sockfd, (struct sockaddr *)&c_addr,
+			    &addr_len);
+		if (fd < 0) {
+			if (errno != EINTR) {
+				wpa_printf(MSG_ERROR,
+					   "Failed accepting connections");
+				ret = -errno;
+				break;
+			}
+			continue;
+		}
+
+		os_memset(buf, 0, BUFSIZE);
+		if (recv(fd, buf, BUFSIZE - 1, 0) <= 0) {
+			close(fd);
+			continue;
+		}
+
+		wpa_printf(MSG_DEBUG, "Received request: %s", buf);
+		if (!afcd_send_request(&ctx, buf)) {
+			wpa_printf(MSG_DEBUG, "Received reply: %s", ctx.buf);
+			send(fd, ctx.buf, ctx.buf_len, MSG_NOSIGNAL);
+			free(ctx.buf);
+		}
+		close(fd);
+	}
+close:
+	close(sockfd);
+unlink:
+	unlink(fname);
+	os_free(fname);
+free_buf:
+	os_free(buf);
+
+	return ret;
+}
+
+
+int main(int argc, char **argv)
+{
+	bool daemonize = false;
+	char *pid_file = NULL;
+
+	if (os_program_init())
+		return -1;
+
+	for (;;) {
+		int c = getopt(argc, argv, "u:p:t:D:P:hdB");
+
+		if (c < 0)
+			break;
+
+		switch (c) {
+		case 'h':
+			usage();
+			return 0;
+		case 'B':
+			daemonize = true;
+			break;
+		case 'D':
+			path = optarg;
+			break;
+		case 'P':
+			os_free(pid_file);
+			pid_file = os_rel2abs_path(optarg);
+			break;
+		case 'u':
+			url = optarg;
+			break;
+		case 'p':
+			port = atoi(optarg);
+			break;
+		case 'd':
+			if (wpa_debug_level > 0)
+				wpa_debug_level--;
+			break;
+		case 't':
+			bearer_token = optarg;
+			break;
+		default:
+			usage();
+			return -EINVAL;
+		}
+	}
+
+	if (!url) {
+		usage();
+		return -EINVAL;
+	}
+
+	if (daemonize && os_daemonize(pid_file)) {
+		wpa_printf(MSG_ERROR, "daemon: %s", strerror(errno));
+		return -EINVAL;
+	}
+
+	signal(SIGTERM, handle_term);
+	signal(SIGINT, handle_term);
+
+	return afcd_server_run();
+}