1# Test cases for dscp policy
2# Copyright (c) 2021, Jouni Malinen <j@w1.fi>
3# Copyright (c) 2021, The Linux Foundation
4#
5# This software may be distributed under the terms of the BSD license.
6# See README for more details.
7
8import struct
9import time
10import sys
11import socket
12
13import hostapd
14from wpasupplicant import WpaSupplicant
15from utils import *
16
17def register_dscp_req(hapd):
18    type = 0x00d0
19    match = "7e506f9a1a"
20    if "OK" not in hapd.request("REGISTER_FRAME %04x %s" % (type, match)):
21        raise Exception("Could not register frame reception for Vendor specific protected type")
22
23def send_dscp_req(hapd, da, oui_subtype, dialog_token, req_control, qos_ie,
24                  truncate=False):
25    type = 0
26    subtype = 13
27    category = 126
28    oui_type = 0x506f9a1a
29    if truncate:
30        req = struct.pack('>BLBB', category, oui_type, oui_subtype,
31                          dialog_token)
32    else:
33        req = struct.pack('>BLBBB', category, oui_type, oui_subtype,
34                          dialog_token, req_control)
35        if qos_ie:
36            req += qos_ie
37
38    msg = {}
39    msg['fc'] = 0x00d0
40    msg['sa'] = hapd.own_addr()
41    msg['da'] = da
42    msg['bssid'] = hapd.own_addr()
43    msg['type'] = type
44    msg['subtype'] = subtype
45    msg['payload'] = req
46
47    hapd.mgmt_tx(msg)
48    ev = hapd.wait_event(["MGMT-TX-STATUS"], timeout=5)
49    if ev is None or "stype=13 ok=1" not in ev:
50        raise Exception("No DSCP Policy Request sent")
51
52def prepare_qos_ie(policy_id, req_type, dscp, start_port=0, end_port=0,
53                   frame_classifier=None, frame_class_len=0, domain_name=None):
54    qos_elem_oui_type = 0x229a6f50
55    qos_elem_id = 221
56
57    if policy_id:
58        qos_attr = struct.pack('BBBBB', 2, 3, policy_id, req_type, dscp)
59        qos_attr_len = 5
60    else:
61        qos_attr = 0
62        qos_attr_len = 0
63
64    if start_port and end_port:
65        port_range_attr = struct.pack('>BBHH', 1, 4, start_port, end_port)
66        if qos_attr:
67            qos_attr += port_range_attr
68        else:
69            qos_attr = port_range_attr
70        qos_attr_len += 6
71
72    if frame_classifier and frame_class_len:
73        tclas_attr = struct.pack('>BB%ds' % (len(frame_classifier),), 3,
74                                 len(frame_classifier), frame_classifier)
75        if qos_attr:
76            qos_attr += tclas_attr
77        else:
78            qos_attr = tclas_attr
79        qos_attr_len += 2 + len(frame_classifier)
80
81    if domain_name:
82        s = bytes(domain_name, 'utf-8')
83        domain_name_attr = struct.pack('>BB%ds' % (len(s),), 4, len(s), s)
84        if qos_attr:
85            qos_attr += domain_name_attr
86        else:
87            qos_attr = domain_name_attr
88        qos_attr_len += 2 + len(s)
89
90    qos_attr_len += 4
91    qos_ie = struct.pack('<BBL', qos_elem_id, qos_attr_len,
92                         qos_elem_oui_type) + qos_attr
93
94    return qos_ie
95
96def validate_dscp_req_event(dev, event):
97    ev = dev.wait_event(["CTRL-EVENT-DSCP-POLICY"], timeout=2)
98    if ev is None:
99        raise Exception("No DSCP request reported")
100    if ev != event:
101        raise Exception("Invalid DSCP event received (%s; expected: %s)" % (ev, event))
102
103def handle_dscp_query(hapd, query):
104    msg = hapd.mgmt_rx()
105    if msg['payload'] != query:
106        raise Exception("Invalid DSCP Query received at AP")
107
108def handle_dscp_response(hapd, response):
109    msg = hapd.mgmt_rx()
110    if msg['payload'] != response:
111        raise Exception("Invalid DSCP Response received at AP")
112
113def ap_sta_connectivity(dev, apdev, params):
114    p = hostapd.wpa2_params(passphrase="12345678")
115    p["wpa_key_mgmt"] = "WPA-PSK"
116    p["ieee80211w"] = "1"
117    p.update(params)
118    hapd = hostapd.add_ap(apdev[0], p)
119    register_dscp_req(hapd)
120
121    dev[0].request("SET enable_dscp_policy_capa 1")
122    dev[0].connect("dscp", psk="12345678", ieee80211w="1",
123                   key_mgmt="WPA-PSK WPA-PSK-SHA256", scan_freq="2412")
124    hapd.wait_sta()
125
126    hapd.dump_monitor()
127    hapd.set("ext_mgmt_frame_handling", "1")
128    return hapd
129
130def test_dscp_query(dev, apdev):
131    """DSCP Policy Query"""
132
133    # Positive tests
134    #AP with DSCP Capabilities
135    params = {"ssid": "dscp",
136              "ext_capa": 6*"00" + "40",
137              "assocresp_elements": "dd06506f9a230101",
138              "vendor_elements": "dd06506f9a230101"}
139
140    hapd = ap_sta_connectivity(dev, apdev, params)
141    da = dev[0].own_addr()
142
143    # Query 1
144    cmd = "DSCP_QUERY wildcard"
145    if "OK" not in dev[0].request(cmd):
146        raise Exception("Sending DSCP Query failed")
147    query = b'\x7e\x50\x6f\x9a\x1a\x00\x01'
148    handle_dscp_query(hapd, query)
149
150    # Query 2
151    cmd = "DSCP_QUERY domain_name=example.com"
152    if "OK" not in dev[0].request(cmd):
153        raise Exception("Sending DSCP Query failed")
154    query = b'\x7e\x50\x6f\x9a\x1a\x00\x02\xdd\x11\x50\x6f\x9a\x22\x04\x0b\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d'
155    handle_dscp_query(hapd, query)
156
157    # Negative tests
158
159    cmd = "DSCP_QUERY domain_name=" + 250*'a' + ".example.com"
160    if "FAIL" not in dev[0].request(cmd):
161        raise Exception("Invalid DSCP_QUERY accepted")
162
163    dev[0].disconnect_and_stop_scan()
164    # AP without DSCP Capabilities
165    params = {"ssid": "dscp",
166              "ext_capa": 6*"00" + "40"}
167    hapd = ap_sta_connectivity(dev, apdev, params)
168
169    # Query 3
170    cmd = "DSCP_QUERY wildcard"
171    if "FAIL" not in dev[0].request(cmd):
172        raise Exception("Able to send invalid DSCP Query")
173
174def test_dscp_request(dev, apdev):
175    """DSCP Policy Request"""
176
177    # Positive tests
178
179    #AP with DSCP Capabilities
180    params = {"ssid": "dscp",
181              "ext_capa": 6*"00" + "40",
182              "assocresp_elements": "dd06506f9a230101",
183              "vendor_elements": "dd06506f9a230101"}
184
185    hapd = ap_sta_connectivity(dev, apdev, params)
186    da = dev[0].own_addr()
187
188    # Request 1
189    dialog_token = 5
190    send_dscp_req(hapd, da, 1, dialog_token, 2, 0)
191    event = "<3>CTRL-EVENT-DSCP-POLICY request_start clear_all"
192    validate_dscp_req_event(dev[0], event)
193    event = "<3>CTRL-EVENT-DSCP-POLICY request_end"
194    validate_dscp_req_event(dev[0], event)
195
196    # DSCP Request with multiple QoS IEs
197    # QoS IE 1
198    dialog_token = 1
199    domain_name = "example.com"
200    ipv4_src_addr = socket.inet_pton(socket.AF_INET, "192.168.0.1")
201    ipv4_dest_addr = socket.inet_pton(socket.AF_INET, "192.168.0.2")
202    frame_classifier_start = [4, 91, 4]
203    frame_classifier_end = [12, 34, 12, 34, 0, 17, 0]
204    frame_classifier = bytes(frame_classifier_start) + ipv4_src_addr + ipv4_dest_addr + bytes(frame_classifier_end)
205    frame_len = len(frame_classifier)
206    qos_ie = prepare_qos_ie(1, 0, 22, 0, 0, frame_classifier, frame_len, domain_name)
207
208    # QoS IE 2
209    ipv6_src_addr = socket.inet_pton(socket.AF_INET6, "aaaa:bbbb:cccc::1")
210    ipv6_dest_addr = socket.inet_pton(socket.AF_INET6, "aaaa:bbbb:cccc::2")
211    frame_classifier_start = [4, 79, 6]
212    frame_classifier_end = [0, 12, 34, 0, 0, 17, 0, 0, 0]
213    frame_classifier = bytes(frame_classifier_start) + ipv6_src_addr + ipv6_dest_addr + bytes(frame_classifier_end)
214    frame_len = len(frame_classifier)
215    ie = prepare_qos_ie(5, 0, 48, 12345, 23456, frame_classifier, frame_len,
216                        None)
217    qos_ie += ie
218
219    # QoS IE 3
220    ie = prepare_qos_ie(4, 0, 32, 12345, 23456, 0, 0, domain_name)
221    qos_ie += ie
222    send_dscp_req(hapd, da, 1, dialog_token, 0, qos_ie)
223
224    event = "<3>CTRL-EVENT-DSCP-POLICY request_start"
225    validate_dscp_req_event(dev[0], event)
226    event = "<3>CTRL-EVENT-DSCP-POLICY add policy_id=1 dscp=22 ip_version=4 src_ip=192.168.0.1 src_port=3106 dst_port=3106 protocol=17 domain_name=example.com"
227    validate_dscp_req_event(dev[0], event)
228    event = "<3>CTRL-EVENT-DSCP-POLICY add policy_id=5 dscp=48 ip_version=6 src_ip=aaaa:bbbb:cccc::1 dst_ip=aaaa:bbbb:cccc::2 src_port=12 protocol=17 start_port=12345 end_port=23456"
229    validate_dscp_req_event(dev[0], event)
230    event = "<3>CTRL-EVENT-DSCP-POLICY add policy_id=4 dscp=32 ip_version=0 start_port=12345 end_port=23456 domain_name=example.com"
231    validate_dscp_req_event(dev[0], event)
232    event = "<3>CTRL-EVENT-DSCP-POLICY request_end"
233    validate_dscp_req_event(dev[0], event)
234
235    # Negative Tests
236
237    # No DSCP policy attribute
238    dialog_token = 4
239    domain_name = "example.com"
240    qos_ie = prepare_qos_ie(0, 0, 0, 12345, 23456, frame_classifier, frame_len,
241                            domain_name)
242    send_dscp_req(hapd, da, 1, dialog_token, 0, qos_ie)
243    event = "<3>CTRL-EVENT-DSCP-POLICY request_start"
244    validate_dscp_req_event(dev[0], event)
245    event = "<3>CTRL-EVENT-DSCP-POLICY request_end"
246    validate_dscp_req_event(dev[0], event)
247
248    # No DSCP stream classifier params
249    dialog_token = 6
250    qos_ie = prepare_qos_ie(1, 0, 32, 0, 0, 0, 0, None)
251    send_dscp_req(hapd, da, 1, dialog_token, 0, qos_ie)
252    event = "<3>CTRL-EVENT-DSCP-POLICY request_start"
253    validate_dscp_req_event(dev[0], event)
254    event = "<3>CTRL-EVENT-DSCP-POLICY reject policy_id=1"
255    validate_dscp_req_event(dev[0], event)
256    event = "<3>CTRL-EVENT-DSCP-POLICY request_end"
257    validate_dscp_req_event(dev[0], event)
258
259    # DSCP request with both destination and domain name
260    dialog_token = 7
261    domain_name = "example.com"
262    ipv4_src_addr = socket.inet_pton(socket.AF_INET, "192.168.0.1")
263    ipv4_dest_addr = socket.inet_pton(socket.AF_INET, "192.168.0.2")
264    frame_classifier_start = [4, 69, 4]
265    frame_classifier_end = [0, 0, 0, 0, 0, 17, 0]
266    frame_classifier = bytes(frame_classifier_start) + ipv4_src_addr + ipv4_dest_addr + bytes(frame_classifier_end)
267    frame_len = len(frame_classifier)
268    qos_ie = prepare_qos_ie(1, 0, 36, 0, 0, frame_classifier, frame_len,
269                            domain_name)
270    send_dscp_req(hapd, da, 1, dialog_token, 0, qos_ie)
271    event  = "<3>CTRL-EVENT-DSCP-POLICY request_start"
272    validate_dscp_req_event(dev[0], event)
273    event = "<3>CTRL-EVENT-DSCP-POLICY reject policy_id=1"
274    validate_dscp_req_event(dev[0], event)
275    event = "<3>CTRL-EVENT-DSCP-POLICY request_end"
276    validate_dscp_req_event(dev[0], event)
277
278    # DSCP request with both port range and destination port
279    frame_classifier_start = [4, 81, 4]
280    frame_classifier_end = [0, 0, 23, 45, 0, 17, 0]
281    frame_classifier = bytes(frame_classifier_start) + ipv4_src_addr + ipv4_dest_addr + bytes(frame_classifier_end)
282    frame_len = len(frame_classifier)
283    qos_ie = prepare_qos_ie(1, 0, 36, 12345, 23456, frame_classifier, frame_len,
284                            None)
285    send_dscp_req(hapd, da, 1, dialog_token, 0, qos_ie)
286    event = "<3>CTRL-EVENT-DSCP-POLICY request_start"
287    validate_dscp_req_event(dev[0], event)
288    event = "<3>CTRL-EVENT-DSCP-POLICY reject policy_id=1"
289    validate_dscp_req_event(dev[0], event)
290    event = "<3>CTRL-EVENT-DSCP-POLICY request_end"
291    validate_dscp_req_event(dev[0], event)
292
293    # Too short DSCP Policy Request frame
294    dialog_token += 1
295    send_dscp_req(hapd, da, 1, dialog_token, 0, None, truncate=True)
296
297    # Request Type: Remove
298    dialog_token += 1
299    qos_ie = prepare_qos_ie(1, 1, 36)
300    send_dscp_req(hapd, da, 1, dialog_token, 0, qos_ie)
301    validate_dscp_req_event(dev[0], "<3>CTRL-EVENT-DSCP-POLICY request_start")
302    validate_dscp_req_event(dev[0],
303                            "<3>CTRL-EVENT-DSCP-POLICY remove policy_id=1")
304    validate_dscp_req_event(dev[0], "<3>CTRL-EVENT-DSCP-POLICY request_end")
305
306    # Request Type: Reserved
307    dialog_token += 1
308    qos_ie = prepare_qos_ie(1, 2, 36)
309    send_dscp_req(hapd, da, 1, dialog_token, 0, qos_ie)
310    validate_dscp_req_event(dev[0], "<3>CTRL-EVENT-DSCP-POLICY request_start")
311    validate_dscp_req_event(dev[0],
312                            "<3>CTRL-EVENT-DSCP-POLICY reject policy_id=1")
313    validate_dscp_req_event(dev[0], "<3>CTRL-EVENT-DSCP-POLICY request_end")
314
315def test_dscp_response(dev, apdev):
316    """DSCP Policy Response"""
317
318    # Positive tests
319
320    # AP with DSCP Capabilities
321    params = {"ssid": "dscp",
322              "ext_capa": 6*"00" + "40",
323              "assocresp_elements": "dd06506f9a230101",
324              "vendor_elements": "dd06506f9a230101"}
325    hapd = ap_sta_connectivity(dev, apdev, params)
326    da = dev[0].own_addr()
327
328    # Sending solicited DSCP response after receiving DSCP request
329    dialog_token = 1
330    domain_name = "example.com"
331    ipv4_src_addr = socket.inet_pton(socket.AF_INET, "192.168.0.1")
332    ipv4_dest_addr = socket.inet_pton(socket.AF_INET, "192.168.0.2")
333    frame_classifier_start = [4,91,4]
334    frame_classifier_end = [12,34,12,34,0,17,0]
335    frame_classifier = bytes(frame_classifier_start) + ipv4_src_addr + ipv4_dest_addr + bytes(frame_classifier_end)
336    frame_len = len(frame_classifier)
337    qos_ie = prepare_qos_ie(1, 0, 22, 0, 0, frame_classifier, frame_len,
338                            domain_name)
339    ie = prepare_qos_ie(4, 0, 32, 12345, 23456, 0, 0, domain_name)
340    qos_ie += ie
341    send_dscp_req(hapd, da, 1, dialog_token, 0, qos_ie)
342
343    ev = dev[0].wait_event(["CTRL-EVENT-DSCP-POLICY"], timeout=5)
344    if ev is None:
345        raise Exception("DSCP event not reported")
346    if "request_start" not in ev:
347        raise Exception("Unexpected DSCP event: " + ev)
348    cmd = "DSCP_RESP solicited policy_id=1 status=0 policy_id=4 status=0"
349    if "OK" not in dev[0].request(cmd):
350        raise Exception("Sending DSCP Response failed")
351    response = b'\x7e\x50\x6f\x9a\x1a\x02\x01\x00\x02\x01\x00\x04\x00'
352    handle_dscp_response(hapd, response)
353
354    # Unsolicited DSCP Response without status duples
355    cmd = "DSCP_RESP reset more"
356    if "OK" not in dev[0].request(cmd):
357        raise Exception("Sending DSCP Response failed")
358    response = b'\x7e\x50\x6f\x9a\x1a\x02\x00\x03\x00'
359    handle_dscp_response(hapd, response)
360
361    # Unsolicited DSCP Response with one status duple
362    cmd = "DSCP_RESP policy_id=2 status=0"
363    if "OK" not in dev[0].request(cmd):
364        raise Exception("Sending DSCP Response failed")
365    response = b'\x7e\x50\x6f\x9a\x1a\x02\x00\x00\x01\x02\x00'
366    handle_dscp_response(hapd, response)
367
368    # Negative tests
369
370    # Send solicited DSCP Response without prior DSCP request
371    cmd = "DSCP_RESP solicited policy_id=1 status=0 policy_id=5 status=0"
372    if "FAIL" not in dev[0].request(cmd):
373        raise Exception("Able to send invalid DSCP response")
374
375def test_dscp_unsolicited_req_at_assoc(dev, apdev):
376    """DSCP Policy and unsolicited request at association"""
377    params = {"ssid": "dscp",
378              "ext_capa": 6*"00" + "40",
379              "assocresp_elements": "dd06506f9a230103",
380              "vendor_elements": "dd06506f9a230103"}
381    hapd = ap_sta_connectivity(dev, apdev, params)
382    da = dev[0].own_addr()
383
384    dialog_token = 1
385    qos_ie = prepare_qos_ie(1, 0, 36, 12345, 23456)
386    send_dscp_req(hapd, da, 1, dialog_token, 0, qos_ie)
387    validate_dscp_req_event(dev[0], "<3>CTRL-EVENT-DSCP-POLICY request_start")
388    validate_dscp_req_event(dev[0], "<3>CTRL-EVENT-DSCP-POLICY add policy_id=1 dscp=36 ip_version=0 start_port=12345 end_port=23456")
389    validate_dscp_req_event(dev[0], "<3>CTRL-EVENT-DSCP-POLICY request_end")
390
391    cmd = "DSCP_QUERY wildcard"
392    if "OK" not in dev[0].request(cmd):
393        raise Exception("Sending DSCP Query failed")
394
395def test_dscp_missing_unsolicited_req_at_assoc(dev, apdev):
396    """DSCP Policy and missing unsolicited request at association"""
397    params = {"ssid": "dscp",
398              "ext_capa": 6*"00" + "40",
399              "assocresp_elements": "dd06506f9a230103",
400              "vendor_elements": "dd06506f9a230103"}
401    hapd = ap_sta_connectivity(dev, apdev, params)
402    da = dev[0].own_addr()
403
404    cmd = "DSCP_QUERY wildcard"
405    if "FAIL" not in dev[0].request(cmd):
406        raise Exception("DSCP_QUERY accepted during wait for unsolicited requesdt")
407    time.sleep(5)
408    validate_dscp_req_event(dev[0], "<3>CTRL-EVENT-DSCP-POLICY request_wait end")
409
410    cmd = "DSCP_QUERY wildcard"
411    if "OK" not in dev[0].request(cmd):
412        raise Exception("Sending DSCP Query failed")
413