unofficial mirror of bug-guile@gnu.org 
 help / color / mirror / Atom feed
* bug#70645: Reliable HTTPS networking
@ 2024-04-29 10:51 Christopher Baines
  2024-04-29 11:57 ` bug#70645: [PATCH 1/2] Allow specifying the socket style for open-socket-for-uri Christopher Baines
  2024-04-29 12:05 ` bug#70645: Reliable HTTPS networking Christopher Baines
  0 siblings, 2 replies; 4+ messages in thread
From: Christopher Baines @ 2024-04-29 10:51 UTC (permalink / raw)
  To: 70645

[-- Attachment #1: Type: text/plain, Size: 1012 bytes --]

For years now I've been trying to work out how to do reliable HTTPS
networking with Guile, where reliable just means that it can't hang
indefinitely.

After a few wrong turns, I believe the way to do this is use
non-blocking ports as that combined with suspendable ports in Guile
allows you to provide current-read-waiter/current-write-waiter
procedures that will timeout at some point.

I think the final hurdle is to get tls-wrap in (web client) to support
Asynchronous operation with GnuTLS [1] and I think there are only a
couple of things missing. make-session needs passing
connection-flag/nonblock and error/again plus error/interrupted
exceptions need handling for the handshake using the information from
record-get-direction about whether Guile should wait to write or read.

1: https://gnutls.org/manual/html_node/Asynchronous-operation.html

I think I forgot to move things forward after guile-gnutls 4.0.0
released with record-get-direction, so I'm opening this bug to try and
keep track of things.

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 987 bytes --]

^ permalink raw reply	[flat|nested] 4+ messages in thread

* bug#70645: [PATCH 1/2] Allow specifying the socket style for open-socket-for-uri.
  2024-04-29 10:51 bug#70645: Reliable HTTPS networking Christopher Baines
@ 2024-04-29 11:57 ` Christopher Baines
  2024-04-29 11:57   ` bug#70645: [PATCH 2/2] web: Handle non-blocking ports in tls-wrap Christopher Baines
  2024-04-29 12:05 ` bug#70645: Reliable HTTPS networking Christopher Baines
  1 sibling, 1 reply; 4+ messages in thread
From: Christopher Baines @ 2024-04-29 11:57 UTC (permalink / raw)
  To: 70645

Since this allows specifying additional behaviours for the socket
through using SOCK_CLOEXEC and/or SOCK_NONBLOCK (when bitwise or'ed with
SOCK_STREAM).

Note that Guile/guile-gnutls currently doesn't support performing the
TLS handshake on a non-blocking socket, so this currently won't work.

* module/web/client.scm (open-socket-for-uri): Allow specifying the
socket style.
---
 module/web/client.scm | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/module/web/client.scm b/module/web/client.scm
index 6c54c5021..f26b5d259 100644
--- a/module/web/client.scm
+++ b/module/web/client.scm
@@ -317,9 +317,12 @@ host name without trailing dot."
   (read-response port))
 
 (define* (open-socket-for-uri uri-or-string
-                              #:key (verify-certificate? #t))
+                              #:key (verify-certificate? #t)
+                              (socket-style SOCK_STREAM))
   "Return an open input/output port for a connection to URI-OR-STRING.
-When VERIFY-CERTIFICATE? is true, verify HTTPS server certificates."
+When VERIFY-CERTIFICATE? is true, verify HTTPS server certificates.
+SOCKET-STYLE defaults to SOCK_STREAM, and can be bitwise or'ed with
+options like SOCK_CLOEXEC or SOCK_NONBLOCK."
   (define uri
     (ensure-uri-reference uri-or-string))
   (define https?
@@ -346,7 +349,9 @@ When VERIFY-CERTIFICATE? is true, verify HTTPS server certificates."
       (let* ((ai (car addresses))
              (s  (with-fluids ((%default-port-encoding #f))
                    ;; Restrict ourselves to TCP.
-                   (socket (addrinfo:fam ai) SOCK_STREAM IPPROTO_IP))))
+                   (socket (addrinfo:fam ai)
+                           socket-style
+                           IPPROTO_IP))))
         (catch 'system-error
           (lambda ()
             (connect s (addrinfo:addr ai))
-- 
2.41.0






^ permalink raw reply related	[flat|nested] 4+ messages in thread

* bug#70645: [PATCH 2/2] web: Handle non-blocking ports in tls-wrap.
  2024-04-29 11:57 ` bug#70645: [PATCH 1/2] Allow specifying the socket style for open-socket-for-uri Christopher Baines
@ 2024-04-29 11:57   ` Christopher Baines
  0 siblings, 0 replies; 4+ messages in thread
From: Christopher Baines @ 2024-04-29 11:57 UTC (permalink / raw)
  To: 70645

As described in the GnuTLS documentation on Asynchronous operation,
GNUTLS_NONBLOCK should be passed to gnutls_init, and the Guile
equivalent is passing connection-flag/nonblock to make-session.

Additionally, error/again or error/interrupted should lead to a retry of
the handshake, after waiting for the appropriate I/O on the port.  As
record-get-direction is new in Guile-GnuTLS, specifically check if this
is defined.

* module/web/client.scm (tls-wrap): Call make-session with
connection-flag/nonblock if the port is non-blocking, and handle waiting
for I/O when performing the handshake.
---
 module/web/client.scm | 24 ++++++++++++++++++++++--
 1 file changed, 22 insertions(+), 2 deletions(-)

diff --git a/module/web/client.scm b/module/web/client.scm
index f26b5d259..caf8e5f35 100644
--- a/module/web/client.scm
+++ b/module/web/client.scm
@@ -33,6 +33,7 @@
 
 (define-module (web client)
   #:use-module (rnrs bytevectors)
+  #:use-module (ice-9 suspendable-ports)
   #:use-module (ice-9 binary-ports)
   #:use-module (ice-9 copy-tree)
   #:use-module (ice-9 iconv)
@@ -225,7 +226,14 @@ host name without trailing dot."
 
   (load-gnutls)
 
-  (let ((session  (make-session connection-end/client))
+  (let ((session
+         (apply
+          make-session
+          (cons connection-end/client
+                (if (zero? (logand O_NONBLOCK (fcntl port F_GETFL)))
+                    '()
+                    ;; If the port is non-blocking, tell GnuTLS
+                    (list connection-flag/nonblock)))))
         (ca-certs (x509-certificate-directory)))
     ;; Some servers such as 'cloud.github.com' require the client to support
     ;; the 'SERVER NAME' extension.  However, 'set-session-server-name!' is
@@ -261,7 +269,19 @@ host name without trailing dot."
         (lambda ()
           (handshake session))
         (lambda (key err proc . rest)
-          (cond ((eq? err error/warning-alert-received)
+          (cond ((and
+                  (or (eq? err error/again)
+                      (eq? err error/interrupted))
+                  (module-defined? (resolve-interface '(gnutls))
+                                   'record-get-direction)) ; Guile-GnuTLS >= 4.0.0
+                 (if (= 0 (record-get-direction session))
+                     ((current-read-waiter) port)
+                     ((current-write-waiter) port))
+
+                 ;; These errors are expected and just signal that
+                 ;; GnuTLS was interrupted, so don't count the retry
+                 (loop retries))
+                ((eq? err error/warning-alert-received)
                  ;; Like Wget, do no stop upon non-fatal alerts such as
                  ;; 'alert-description/unrecognized-name'.
                  (format (current-error-port)
-- 
2.41.0






^ permalink raw reply related	[flat|nested] 4+ messages in thread

* bug#70645: Reliable HTTPS networking
  2024-04-29 10:51 bug#70645: Reliable HTTPS networking Christopher Baines
  2024-04-29 11:57 ` bug#70645: [PATCH 1/2] Allow specifying the socket style for open-socket-for-uri Christopher Baines
@ 2024-04-29 12:05 ` Christopher Baines
  1 sibling, 0 replies; 4+ messages in thread
From: Christopher Baines @ 2024-04-29 12:05 UTC (permalink / raw)
  To: 70645

[-- Attachment #1: Type: text/plain, Size: 1707 bytes --]

Christopher Baines <mail@cbaines.net> writes:

> For years now I've been trying to work out how to do reliable HTTPS
> networking with Guile, where reliable just means that it can't hang
> indefinitely.
>
> After a few wrong turns, I believe the way to do this is use
> non-blocking ports as that combined with suspendable ports in Guile
> allows you to provide current-read-waiter/current-write-waiter
> procedures that will timeout at some point.
>
> I think the final hurdle is to get tls-wrap in (web client) to support
> Asynchronous operation with GnuTLS [1] and I think there are only a
> couple of things missing. make-session needs passing
> connection-flag/nonblock and error/again plus error/interrupted
> exceptions need handling for the handshake using the information from
> record-get-direction about whether Guile should wait to write or read.
>
> 1: https://gnutls.org/manual/html_node/Asynchronous-operation.html
>
> I think I forgot to move things forward after guile-gnutls 4.0.0
> released with record-get-direction, so I'm opening this bug to try and
> keep track of things.

I've now sent a couple of patches.

The first is a re-send of [2], but with some docstring improvements. I
can't find any reference in the Guile docs at least to the bitwise
or'ing of options with the socket style, so while it seems to work, I'm
a bit unsure about that.

2: https://lists.gnu.org/archive/html/guile-devel/2023-07/msg00025.html

The second patch makes the changes inside of tls-wrap.

There's also this patch [3] here to make get-bytevector-all
non-blocking, and that's relevant here as it's used in
read-response-body.

3: https://lists.gnu.org/archive/html/guile-devel/2023-07/msg00023.html

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 987 bytes --]

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2024-04-29 12:05 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-04-29 10:51 bug#70645: Reliable HTTPS networking Christopher Baines
2024-04-29 11:57 ` bug#70645: [PATCH 1/2] Allow specifying the socket style for open-socket-for-uri Christopher Baines
2024-04-29 11:57   ` bug#70645: [PATCH 2/2] web: Handle non-blocking ports in tls-wrap Christopher Baines
2024-04-29 12:05 ` bug#70645: Reliable HTTPS networking Christopher Baines

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).