Skip to content

Commit 704621c

Browse files
akxmichaelboulton
andcommitted
Add ALPN support
Co-authored-by: Michael Boulton <michaelboulton@gmail.com> Signed-off-by: Aarni Koskela <akx@iki.fi>
1 parent 5ae418c commit 704621c

5 files changed

Lines changed: 80 additions & 1 deletion

File tree

src/paho/mqtt/client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,7 @@ def tls_set(
866866
tls_version: int | None = None,
867867
ciphers: str | None = None,
868868
keyfile_password: str | None = None,
869+
alpn_protocols: list[str] | None = None,
869870
) -> None:
870871
"""Configure network encryption and authentication options. Enables SSL/TLS support.
871872
@@ -945,6 +946,11 @@ def tls_set(
945946
else:
946947
context.load_default_certs()
947948

949+
if alpn_protocols is not None:
950+
if not getattr(ssl, "HAS_ALPN", None):
951+
raise ValueError("SSL library has no support for ALPN")
952+
context.set_alpn_protocols(alpn_protocols)
953+
948954
self.tls_set_context(context)
949955

950956
if cert_reqs != ssl.CERT_NONE:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import os
2+
3+
import paho.mqtt.client as mqtt
4+
5+
from tests.paho_test import get_test_server_port, loop_until_keyboard_interrupt
6+
7+
8+
def on_connect(mqttc, obj, flags, rc):
9+
assert rc == 0, f"Connect failed ({rc})"
10+
mqttc.disconnect()
11+
12+
13+
mqttc = mqtt.Client("08-ssl-connect-alpn", clean_session=True)
14+
mqttc.tls_set(
15+
os.path.join(os.environ["PAHO_SSL_PATH"], "all-ca.crt"),
16+
os.path.join(os.environ["PAHO_SSL_PATH"], "client.crt"),
17+
os.path.join(os.environ["PAHO_SSL_PATH"], "client.key"),
18+
alpn_protocols=["paho-test-protocol"],
19+
)
20+
mqttc.on_connect = on_connect
21+
22+
mqttc.connect("localhost", get_test_server_port())
23+
loop_until_keyboard_interrupt(mqttc)

tests/lib/conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ def ssl_server_socket(monkeypatch):
3232
yield from _yield_server(monkeypatch, create_server_socket_ssl())
3333

3434

35+
@pytest.fixture()
36+
def alpn_ssl_server_socket(monkeypatch):
37+
if ssl is None:
38+
pytest.skip("no ssl module")
39+
if not getattr(ssl, "HAS_ALPN", False):
40+
pytest.skip("ALPN not supported in this version of Python")
41+
yield from _yield_server(monkeypatch, create_server_socket_ssl(alpn_protocols=["paho-test-protocol"]))
42+
43+
3544
def stop_process(proc: subprocess.Popen) -> None:
3645
if sys.platform == "win32":
3746
proc.send_signal(signal.CTRL_C_EVENT)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Test whether a client produces a correct connect and subsequent disconnect when using SSL.
2+
# Client must provide a certificate.
3+
#
4+
# The client should connect with keepalive=60, clean session set,
5+
# and client id 08-ssl-connect-alpn
6+
# It should use the CA certificate ssl/all-ca.crt for verifying the server.
7+
# The test will send a CONNACK message to the client with rc=0. Upon receiving
8+
# the CONNACK and verifying that rc=0, the client should send a DISCONNECT
9+
# message. If rc!=0, the client should exit with an error.
10+
#
11+
# Additionally, the secure socket must have been negotiated with the "paho-test-protocol"
12+
13+
14+
from tests import paho_test
15+
from tests.paho_test import ssl
16+
17+
18+
def test_08_ssl_connect_alpn(alpn_ssl_server_socket, start_client):
19+
connect_packet = paho_test.gen_connect("08-ssl-connect-alpn", keepalive=60)
20+
connack_packet = paho_test.gen_connack(rc=0)
21+
disconnect_packet = paho_test.gen_disconnect()
22+
23+
start_client("08-ssl-connect-alpn.py")
24+
25+
(conn, address) = alpn_ssl_server_socket.accept()
26+
conn.settimeout(10)
27+
28+
paho_test.expect_packet(conn, "connect", connect_packet)
29+
conn.send(connack_packet)
30+
31+
paho_test.expect_packet(conn, "disconnect", disconnect_packet)
32+
33+
if ssl.HAS_ALPN:
34+
negotiated_protocol = conn.selected_alpn_protocol()
35+
if negotiated_protocol != "paho-test-protocol":
36+
raise Exception(f"Unexpected protocol '{negotiated_protocol}'")
37+
38+
conn.close()

tests/paho_test.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def create_server_socket():
3232
return (sock, port)
3333

3434

35-
def create_server_socket_ssl(*, verify_mode=None):
35+
def create_server_socket_ssl(*, verify_mode=None, alpn_protocols=None):
3636
assert ssl, "SSL not available"
3737

3838
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -46,6 +46,9 @@ def create_server_socket_ssl(*, verify_mode=None):
4646
if verify_mode:
4747
context.verify_mode = verify_mode
4848

49+
if alpn_protocols is not None:
50+
context.set_alpn_protocols(alpn_protocols)
51+
4952
ssock = context.wrap_socket(sock, server_side=True)
5053
ssock.settimeout(10)
5154
port = bind_to_any_free_port(ssock)

0 commit comments

Comments
 (0)