Implement Asynchrounous Requests via UDP/TCP#93
Implement Asynchrounous Requests via UDP/TCP#93mriemensberger wants to merge 6 commits intoradcli:masterfrom
Conversation
Moves code needed by both sendserver.c and sendserver_async.c to a separate file. Adds an associated header.
Radius clients, for example, splash portal applications, frequently perform lots of networking-related tasks like serving HTTP. Those applications are often written using an event loop (based on select, poll, etc.) and non-blocking async processing. In order to integrate radcli into such an event loop architecture it also needs to support non-blocking async radius exchanges. Implements async requests by splitting the original send_server_ctx() function and its wrappers into multiple pieces that are driven by a simple state machine and events on the associated socket. The rc_async_handle represents on async request, holds the state and the result when the request is done. Caveats: - Only UDP and TCP are supported (no TLD/DTLS) - Automatic retries are not supported; Users can retry by creating a new handle and retry the request using the new handle. - Only one configured server is supported. rc_async_prepare_handle can be used to specify different servers for a different handle with otherwise the same request data.
…quests Implements parallel handling of multiple async requests in multihandle.c with direct support for select and poll event loop structures. This change was co-authored by Philipp Stanner.
44bc5c5 to
7acb752
Compare
| switch (vp->attribute) { | ||
| case PW_USER_PASSWORD: | ||
|
|
||
| /* Encrypt the password */ | ||
|
|
||
| /* Chop off password at AUTH_PASS_LEN */ | ||
| length = vp->lvalue; | ||
| if (length > AUTH_PASS_LEN) | ||
| length = AUTH_PASS_LEN; | ||
|
|
||
| /* Calculate the padded length */ | ||
| padded_length = | ||
| (length + | ||
| (AUTH_VECTOR_LEN - 1)) & ~(AUTH_VECTOR_LEN - 1); | ||
|
|
||
| /* Record the attribute length */ | ||
| *buf++ = padded_length + 2; | ||
| if (vsa_length_ptr != NULL) | ||
| *vsa_length_ptr += padded_length + 2; | ||
|
|
||
| /* Pad the password with zeros */ | ||
| memset((char *)passbuf, '\0', AUTH_PASS_LEN); | ||
| memcpy((char *)passbuf, vp->strvalue, (size_t) length); | ||
|
|
||
| secretlen = strlen(secret); | ||
| vector = (unsigned char *)auth->vector; | ||
| for (i = 0; i < padded_length; i += AUTH_VECTOR_LEN) { | ||
| /* Calculate the MD5 digest */ | ||
| strcpy((char *)md5buf, secret); | ||
| memcpy((char *)md5buf + secretlen, vector, | ||
| AUTH_VECTOR_LEN); | ||
| rc_md5_calc(buf, md5buf, | ||
| secretlen + AUTH_VECTOR_LEN); | ||
|
|
||
| /* Remeber the start of the digest */ | ||
| vector = buf; | ||
|
|
||
| /* Xor the password into the MD5 digest */ | ||
| for (pc = i; pc < (i + AUTH_VECTOR_LEN); pc++) { | ||
| *buf++ ^= passbuf[pc]; | ||
| } | ||
| } | ||
|
|
||
| total_length += padded_length + 2; | ||
|
|
||
| break; | ||
| default: | ||
| switch (vp->type) { | ||
| case PW_TYPE_STRING: | ||
| length = vp->lvalue; | ||
| *buf++ = length + 2; | ||
| if (vsa_length_ptr != NULL) | ||
| *vsa_length_ptr += length + 2; | ||
| memcpy(buf, vp->strvalue, (size_t) length); | ||
| buf += length; | ||
| total_length += length + 2; | ||
| break; | ||
|
|
||
| case PW_TYPE_IPV6ADDR: | ||
| length = 16; | ||
| *buf++ = length + 2; | ||
| if (vsa_length_ptr != NULL) | ||
| *vsa_length_ptr += length + 2; | ||
| memcpy(buf, vp->strvalue, (size_t) length); | ||
| buf += length; | ||
| total_length += length + 2; | ||
| break; | ||
|
|
||
| case PW_TYPE_IPV6PREFIX: | ||
| length = vp->lvalue; | ||
| *buf++ = length + 2; | ||
| if (vsa_length_ptr != NULL) | ||
| *vsa_length_ptr += length + 2; | ||
| memcpy(buf, vp->strvalue, (size_t) length); | ||
| buf += length; | ||
| total_length += length + 2; | ||
| break; | ||
|
|
||
| case PW_TYPE_INTEGER: | ||
| case PW_TYPE_IPADDR: | ||
| case PW_TYPE_DATE: | ||
| *buf++ = sizeof(uint32_t) + 2; | ||
| if (vsa_length_ptr != NULL) | ||
| *vsa_length_ptr += sizeof(uint32_t) + 2; | ||
| lvalue = htonl(vp->lvalue); | ||
| memcpy(buf, (char *)&lvalue, sizeof(uint32_t)); | ||
| buf += sizeof(uint32_t); | ||
| total_length += sizeof(uint32_t) + 2; | ||
| break; | ||
|
|
||
| default: | ||
| break; | ||
| } | ||
| break; | ||
| } |
Check notice
Code scanning / CodeQL
Long switch case
There was a problem hiding this comment.
This is code that was just moved without changes to enable reuse in the async implementation.
src/radembedded_async.c
Outdated
| /* | ||
| VALUE_PAIR *vp = NULL; | ||
| DICT_VALUE *dval = NULL; | ||
| */ |
Check notice
Code scanning / CodeQL
Commented-out code
| switch (vp->attribute) { | ||
| case PW_USER_PASSWORD: | ||
|
|
||
| /* Encrypt the password */ | ||
|
|
||
| /* Chop off password at AUTH_PASS_LEN */ | ||
| length = vp->lvalue; | ||
| if (length > AUTH_PASS_LEN) | ||
| length = AUTH_PASS_LEN; | ||
|
|
||
| /* Calculate the padded length */ | ||
| padded_length = | ||
| (length + | ||
| (AUTH_VECTOR_LEN - 1)) & ~(AUTH_VECTOR_LEN - 1); | ||
|
|
||
| /* Record the attribute length */ | ||
| *buf++ = padded_length + 2; | ||
| if (vsa_length_ptr != NULL) | ||
| *vsa_length_ptr += padded_length + 2; | ||
|
|
||
| /* Pad the password with zeros */ | ||
| memset((char *)passbuf, '\0', AUTH_PASS_LEN); | ||
| memcpy((char *)passbuf, vp->strvalue, (size_t) length); | ||
|
|
||
| secretlen = strlen(secret); | ||
| vector = (unsigned char *)auth->vector; | ||
| for (i = 0; i < padded_length; i += AUTH_VECTOR_LEN) { | ||
| /* Calculate the MD5 digest */ | ||
| strcpy((char *)md5buf, secret); | ||
| memcpy((char *)md5buf + secretlen, vector, | ||
| AUTH_VECTOR_LEN); | ||
| rc_md5_calc(buf, md5buf, | ||
| secretlen + AUTH_VECTOR_LEN); | ||
|
|
||
| /* Remeber the start of the digest */ | ||
| vector = buf; | ||
|
|
||
| /* Xor the password into the MD5 digest */ | ||
| for (pc = i; pc < (i + AUTH_VECTOR_LEN); pc++) { | ||
| *buf++ ^= passbuf[pc]; | ||
| } | ||
| } | ||
|
|
||
| total_length += padded_length + 2; | ||
|
|
||
| break; | ||
| default: | ||
| switch (vp->type) { | ||
| case PW_TYPE_STRING: | ||
| length = vp->lvalue; | ||
| *buf++ = length + 2; | ||
| if (vsa_length_ptr != NULL) | ||
| *vsa_length_ptr += length + 2; | ||
| memcpy(buf, vp->strvalue, (size_t) length); | ||
| buf += length; | ||
| total_length += length + 2; | ||
| break; | ||
|
|
||
| case PW_TYPE_IPV6ADDR: | ||
| length = 16; | ||
| *buf++ = length + 2; | ||
| if (vsa_length_ptr != NULL) | ||
| *vsa_length_ptr += length + 2; | ||
| memcpy(buf, vp->strvalue, (size_t) length); | ||
| buf += length; | ||
| total_length += length + 2; | ||
| break; | ||
|
|
||
| case PW_TYPE_IPV6PREFIX: | ||
| length = vp->lvalue; | ||
| *buf++ = length + 2; | ||
| if (vsa_length_ptr != NULL) | ||
| *vsa_length_ptr += length + 2; | ||
| memcpy(buf, vp->strvalue, (size_t) length); | ||
| buf += length; | ||
| total_length += length + 2; | ||
| break; | ||
|
|
||
| case PW_TYPE_INTEGER: | ||
| case PW_TYPE_IPADDR: | ||
| case PW_TYPE_DATE: | ||
| *buf++ = sizeof(uint32_t) + 2; | ||
| if (vsa_length_ptr != NULL) | ||
| *vsa_length_ptr += sizeof(uint32_t) + 2; | ||
| lvalue = htonl(vp->lvalue); | ||
| memcpy(buf, (char *)&lvalue, sizeof(uint32_t)); | ||
| buf += sizeof(uint32_t); | ||
| total_length += sizeof(uint32_t) + 2; | ||
| break; | ||
|
|
||
| default: | ||
| break; | ||
| } | ||
| break; | ||
| } |
Check notice
Code scanning / CodeQL
No trivial switch statements
There was a problem hiding this comment.
This is code that was just moved without changes to enable reuse in the async implementation.
This change was co-authored by Philipp Stanner.
7acb752 to
27fd8ba
Compare
| University of Michigan and Merit Network, Inc. shall not be liable for any | ||
| special, indirect, incidental or consequential damages with respect to any | ||
| claim by Licensee or any third party arising from use of the software. | ||
| ------------------------------------------------------------------------------ |
There was a problem hiding this comment.
Please no new license. Just add the Copyright at the beginning of the file.
There was a problem hiding this comment.
Just to clarify: After line 8 in this file, right? Will do.
|
Sorry for getting to it with such a big delay. 2023 was a crazy year for me. I like the idea, and I would like to review. At the same time there are not any functional tests and for introducing a new API, tests are necessary to ensure that it works as expected, and that future changes do not modify this functionality. If I get to review are you still interested to enhance it with functional tests? |
No worries. I have a small amount of time for upstreaming the changes. Regarding the functional tests. There is the |
| /* | ||
| * Copyright (C) 2022 Cadami GmbH [email protected] | ||
| * | ||
| * radembedded.c - a sample c program showing how to embed the configuration of a radius |
There was a problem hiding this comment.
s/rademedded.c/radembedded_async.c
To run the new code during CI and validate the expected output, for example as in tests/radembedded-tests.sh |
|
I see that no github workflows were run in this PR. |
Sure. I'll add a similar wrapper for the async version of those tests. |
That is a configuration issue of github actions. I've tried to fix it in master, please try rebasing. |
| * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan | ||
| * and Merit Network, Inc. All Rights Reserved | ||
| * | ||
| * Copyright (C) 2022 Cadami GmbH, [email protected] |
There was a problem hiding this comment.
This seem to be on a strange position. I'd expect it to be on the top together with the BSD2 boilerplate.
| exit(EXIT_FAILURE); | ||
| } | ||
|
|
||
| /* ======================== Prepare New Request ============================= */ |
There was a problem hiding this comment.
It seems the function is too long. Should we split into different functions for easier readbility?
| }; | ||
|
|
||
|
|
||
| struct rc_multihandle { |
There was a problem hiding this comment.
It is not clear to me what is the intention of the multihandle. Some documentation is needed here to explain why this exists and what it does.
|
|
||
| for (; received_responses < count;) { | ||
| /* Get all the currently enqueued pollable file descriptors. */ | ||
| nr_of_poll_fds = rc_multi_get_pollfds(mhdl, polli, count); |
There was a problem hiding this comment.
What is my concern is that the API is very poll-system-call-centric. Today common ways to do it is also with epoll, kqueue or libev. How does the API accomodate these?
There was a problem hiding this comment.
There is also rc_multi_get_fd_set and rc_multi_process_fd_set to be used with select.
Also, the entire multihandle API is a thin layer on top of the rc_async_* API to provide an easy way to handle multiple radius requests with poll or select. Those syscalls always report the entire set and multihandle helps with walking and processing the pollfds array or select fdset. It's loosely inspired by libcurl but much simpler.
rc_async_* can also directly be used with and event multiplexer syscall as it exposes the associated fd and the expected events in any state.
Extending the multihandle API to more complex event multiplexer syscalls like epoll and kqueue might be possible although I'm not sure how useful that would be given those syscalls report events not for the entire set of descriptors but only for descriptors that saw events.
There was a problem hiding this comment.
Indeed, we should not provide wrappers for all possibilities, just make sure that it is possible to use the API with them. Documentation seem to be important towards that, so that the async API is not perceived as poll-only.
|
Taking a note for me of what remains for final review:
|
|
Thanks @nmav! It will however take some time until I find the time to go through those points and address them. |
Radius clients, for example, splash portal applications, frequently perform
lots of networking-related tasks like serving HTTP. Those applications are
often written using an event loop (based on select, poll, etc.) and non-blocking
async processing. In order to integrate radcli into such an event loop
architecture it also needs to support non-blocking async radius exchanges.
function and its wrappers into multiple pieces that are driven by a
simple state machine and events on the associated socket. The
rc_async_handle represents on async request, holds the state
and the result when the request is done.
with direct support for select and poll event loop structures.
Caveats:
handle and retry the request using the new handle.
be used to specify different servers for a different handle with
otherwise the same request data.