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.
pull/70/head
Juliusz Sosinowicz 2026-06-23 11:22:06 +00:00
parent 74a340db5e
commit c29eb6760b
2 changed files with 91 additions and 1 deletions

View File

@ -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()

View File

@ -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