From c29eb6760bce82e4013e0698751ae519c1e7fdbf Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 23 Jun 2026 11:22:06 +0000 Subject: [PATCH] Fix DTLS server example consuming the ClientHello before handshake (F-3481) The DTLS branch called bind_socket.recvfrom(1) before creating the context. On UDP that removes the entire first datagram (the client's ClientHello) from the queue and discards everything past the first byte, so wolfSSL_accept() had nothing to consume and the handshake only recovered after the client's retransmit timer. The captured from_addr was also reused for every iteration of the -i loop. Replace it with a peek_peer_address() helper that uses MSG_PEEK to read the source address without consuming the datagram, and move the peek into the accept loop so the address is refreshed per connection. --- examples/server.py | 19 +++++++- tests/test_dtls_server_example.py | 73 +++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/test_dtls_server_example.py diff --git a/examples/server.py b/examples/server.py index 2f060af..1df90dd 100755 --- a/examples/server.py +++ b/examples/server.py @@ -115,6 +115,21 @@ def get_DTLSmethod(index): )[index] +# Large enough to peek a DTLS ClientHello source address. +PEEK_BUFSIZE = 1500 + + +def peek_peer_address(sock): + """ + Return the source address of the next pending datagram without removing + it from the socket queue. MSG_PEEK leaves the datagram (the DTLS + ClientHello) intact so wolfSSL_accept() can consume it during the + handshake. + """ + _, from_addr = sock.recvfrom(PEEK_BUFSIZE, socket.MSG_PEEK) + return from_addr + + def main(): args = build_arg_parser().parse_args() # DTLS connection over UDP @@ -124,7 +139,6 @@ def main(): args.v = 1 bind_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) bind_socket.bind(("" if args.b else "localhost", args.p)) - data, from_addr = bind_socket.recvfrom(1) context = wolfssl.SSLContext(get_DTLSmethod(args.v), server_side=True) # SSL/TLS connection over TCP else: @@ -156,6 +170,9 @@ def main(): try: secure_socket = None if args.u: + # Peek the client's address for this connection without + # consuming the ClientHello datagram needed by the handshake. + from_addr = peek_peer_address(bind_socket) secure_socket = context.wrap_socket(bind_socket) else: new_socket, from_addr = bind_socket.accept() diff --git a/tests/test_dtls_server_example.py b/tests/test_dtls_server_example.py new file mode 100644 index 0000000..5838c05 --- /dev/null +++ b/tests/test_dtls_server_example.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# +# test_dtls_server_example.py +# +# Copyright (C) 2006-2020 wolfSSL Inc. +# +# This file is part of wolfSSL. (formerly known as CyaSSL) +# +# wolfSSL is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# wolfSSL is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +# pylint: disable=missing-docstring, invalid-name, import-error + +import os +import sys +import socket + +import pytest + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "examples")) +import server as server_example # noqa: E402 + + +@pytest.fixture +def udp_pair(): + srv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + srv.bind(("localhost", 0)) + cli = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + yield srv, cli, srv.getsockname() + finally: + srv.close() + cli.close() + + +def test_peek_peer_address_returns_source(udp_pair): + srv, cli, srv_addr = udp_pair + cli.bind(("localhost", 0)) + cli.sendto(b"clienthello-payload", srv_addr) + + addr = server_example.peek_peer_address(srv) + + assert addr == cli.getsockname() + + +def test_peek_peer_address_does_not_consume_datagram(udp_pair): + """ + Regression test for F-3481: peeking the client's address before the + DTLS handshake must leave the ClientHello datagram intact. The previous + example used recvfrom(1), which consumed the datagram and discarded + everything past the first byte, breaking the handshake. + """ + srv, cli, srv_addr = udp_pair + payload = b"X" * 256 # stand-in for a DTLS ClientHello record + cli.sendto(payload, srv_addr) + + server_example.peek_peer_address(srv) + + # The datagram must still be fully available for wolfSSL_accept(). + srv.settimeout(2) + data, _ = srv.recvfrom(4096) + assert data == payload