p2p-nfc.py revision 281681
150959Speter#!/usr/bin/python
250120Swpaul#
350120Swpaul# Example nfcpy to wpa_supplicant wrapper for P2P NFC operations
450120Swpaul# Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi>
550120Swpaul#
650120Swpaul# This software may be distributed under the terms of the BSD license.
750120Swpaul# See README for more details.
850120Swpaul
950120Swpaulimport os
1050120Swpaulimport sys
1150120Swpaulimport time
1250120Swpaulimport random
1350120Swpaulimport threading
1450120Swpaulimport argparse
1550120Swpaul
1650120Swpaulimport nfc
1750120Swpaulimport nfc.ndef
1850120Swpaulimport nfc.llcp
1950120Swpaulimport nfc.handover
2050120Swpaul
2150120Swpaulimport logging
2250120Swpaul
2350120Swpaulimport wpaspy
2450120Swpaul
2550120Swpaulwpas_ctrl = '/var/run/wpa_supplicant'
2650120Swpaulifname = None
2750120Swpaulinit_on_touch = False
2850120Swpaulin_raw_mode = False
2950120Swpaulprev_tcgetattr = 0
3050120Swpaulinclude_wps_req = True
3150120Swpaulinclude_p2p_req = True
3250120Swpaulno_input = False
3350120Swpaulsrv = None
3450120Swpaulcontinue_loop = True
3550120Swpaulterminate_now = False
3650120Swpaulsummary_file = None
3750120Swpaulsuccess_file = None
3850120Swpaul
3950120Swpauldef summary(txt):
4050120Swpaul    print txt
4150120Swpaul    if summary_file:
4250120Swpaul        with open(summary_file, 'a') as f:
4350120Swpaul            f.write(txt + "\n")
4450120Swpaul
4550120Swpauldef success_report(txt):
4650120Swpaul    summary(txt)
4750120Swpaul    if success_file:
4850120Swpaul        with open(success_file, 'a') as f:
4950120Swpaul            f.write(txt + "\n")
5050120Swpaul
5150120Swpauldef wpas_connect():
52179895Sdelphij    ifaces = []
5361907Ssemenu    if os.path.isdir(wpas_ctrl):
5450120Swpaul        try:
55190538Simp            ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)]
56179098Syongari        except OSError, error:
5750577Swpaul            print "Could not find wpa_supplicant: ", error
58178667Sjhb            return None
59135048Swpaul
6050120Swpaul    if len(ifaces) < 1:
61160637Syongari        print "No wpa_supplicant control interface found"
6250120Swpaul        return None
6350120Swpaul
6477078Swpaul    for ctrl in ifaces:
65179335Syongari        if ifname:
6650120Swpaul            if ifname not in ctrl:
6750120Swpaul                continue
6850120Swpaul        try:
6994149Swpaul            print "Trying to use control interface " + ctrl
7050120Swpaul            wpas = wpaspy.Ctrl(ctrl)
7150120Swpaul            return wpas
72179592Sbenno        except Exception, e:
7366989Simp            pass
7450120Swpaul    return None
75170364Syongari
7659475Swpaul
7775353Smjacobdef wpas_tag_read(message):
7884145Sjlemon    wpas = wpas_connect()
7950120Swpaul    if (wpas == None):
8050120Swpaul        return False
8150120Swpaul    cmd = "WPS_NFC_TAG_READ " + str(message).encode("hex")
8250120Swpaul    global force_freq
8374129Sjlemon    if force_freq:
8474129Sjlemon        cmd = cmd + " freq=" + force_freq
8574129Sjlemon    if "FAIL" in wpas.request(cmd):
8650120Swpaul        return False
8750120Swpaul    return True
8861907Ssemenu
8959475Swpaul
90165090Sscottldef wpas_get_handover_req():
9150120Swpaul    wpas = wpas_connect()
9250120Swpaul    if (wpas == None):
9350120Swpaul        return None
9450120Swpaul    res = wpas.request("NFC_GET_HANDOVER_REQ NDEF P2P-CR").rstrip()
9559475Swpaul    if "FAIL" in res:
9650120Swpaul        return None
9750120Swpaul    return res.decode("hex")
9850120Swpaul
9950120Swpauldef wpas_get_handover_req_wps():
10050120Swpaul    wpas = wpas_connect()
10150120Swpaul    if (wpas == None):
10250120Swpaul        return None
10350120Swpaul    res = wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR").rstrip()
104173130Syongari    if "FAIL" in res:
105119976Swpaul        return None
10650120Swpaul    return res.decode("hex")
10750120Swpaul
10850120Swpaul
10950120Swpauldef wpas_get_handover_sel(tag=False):
11050120Swpaul    wpas = wpas_connect()
111179895Sdelphij    if (wpas == None):
112179895Sdelphij        return None
113179895Sdelphij    if tag:
11461907Ssemenu        res = wpas.request("NFC_GET_HANDOVER_SEL NDEF P2P-CR-TAG").rstrip()
11561907Ssemenu    else:
116109147Sobrien	res = wpas.request("NFC_GET_HANDOVER_SEL NDEF P2P-CR").rstrip()
117164833Smarius    if "FAIL" in res:
11861907Ssemenu        return None
11950120Swpaul    return res.decode("hex")
12050120Swpaul
12166127Swpaul
122164833Smariusdef wpas_get_handover_sel_wps():
12350120Swpaul    wpas = wpas_connect()
124190538Simp    if (wpas == None):
125190538Simp        return None
126190538Simp    res = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR");
127179098Syongari    if "FAIL" in res:
128179098Syongari        return None
129183567Sstas    return res.rstrip().decode("hex")
130179098Syongari
13150577Swpaul
13299440Sbennodef wpas_report_handover(req, sel, type):
13399439Sbenno    wpas = wpas_connect()
13499440Sbenno    if (wpas == None):
13599440Sbenno        return None
136119917Swpaul    cmd = "NFC_REPORT_HANDOVER " + type + " P2P " + str(req).encode("hex") + " " + str(sel).encode("hex")
13759475Swpaul    global force_freq
13883029Swpaul    if force_freq:
13983029Swpaul        cmd = cmd + " freq=" + force_freq
140165090Sscottl    return wpas.request(cmd)
141161749Syongari
14292931Swpaul
143170391Sdavidchdef wpas_report_handover_wsc(req, sel, type):
144103103Sjdp    wpas = wpas_connect()
145114547Sps    if (wpas == None):
146117659Swpaul        return None
147135772Sps    cmd = "NFC_REPORT_HANDOVER " + type + " WPS " + str(req).encode("hex") + " " + str(sel).encode("hex")
148146413Sps    if force_freq:
149157041Soleg        cmd = cmd + " freq=" + force_freq
150166876Sjhb    return wpas.request(cmd)
151168601Smarius
152166031Sjkim
153170391Sdavidchdef p2p_handover_client(llc):
154176850Sdavidch    message = nfc.ndef.HandoverRequestMessage(version="1.2")
155176881Sjhb    message.nonce = random.randint(0, 0xffff)
156176850Sdavidch
157178667Sjhb    global include_p2p_req
15850577Swpaul    if include_p2p_req:
159135048Swpaul        data = wpas_get_handover_req()
160135048Swpaul        if (data == None):
161176773Sraj            summary("Could not get handover request carrier record from wpa_supplicant")
162184192Syongari            return
163135048Swpaul        print "Handover request carrier record from wpa_supplicant: " + data.encode("hex")
164135048Swpaul        datamsg = nfc.ndef.Message(data)
165178598Sraj        message.add_carrier(datamsg[0], "active", datamsg[1:])
166170364Syongari
167135048Swpaul    global include_wps_req
16850120Swpaul    if include_wps_req:
169164833Smarius        print "Handover request (pre-WPS):"
17050120Swpaul        try:
17150120Swpaul            print message.pretty()
17250120Swpaul        except Exception, e:
173170524Syongari            print e
17450120Swpaul
175170524Syongari        data = wpas_get_handover_req_wps()
176170524Syongari        if data:
17750120Swpaul            print "Add WPS request in addition to P2P"
178160637Syongari            datamsg = nfc.ndef.Message(data)
179170366Syongari            message.add_carrier(datamsg[0], "active", datamsg[1:])
180160637Syongari
181177930Syongari    print "Handover request:"
182160637Syongari    try:
18350120Swpaul        print message.pretty()
18474129Sjlemon    except Exception, e:
18576483Sjlemon        print e
18676483Sjlemon    print str(message).encode("hex")
18776483Sjlemon
18874129Sjlemon    client = nfc.handover.HandoverClient(llc)
18950120Swpaul    try:
19077078Swpaul        summary("Trying to initiate NFC connection handover")
19177078Swpaul        client.connect()
19277078Swpaul        summary("Connected for handover")
193179335Syongari    except nfc.llcp.ConnectRefused:
194179335Syongari        summary("Handover connection refused")
195179335Syongari        client.close()
196179335Syongari        return
19750120Swpaul    except Exception, e:
19850120Swpaul        summary("Other exception: " + str(e))
19950120Swpaul        client.close()
20050120Swpaul        return
20150120Swpaul
20250120Swpaul    summary("Sending handover request")
203175702Smarius
204175702Smarius    if not client.send(message):
20576479Swpaul        summary("Failed to send handover request")
20676479Swpaul        client.close()
20750120Swpaul        return
20850120Swpaul
20950120Swpaul    summary("Receiving handover response")
21050120Swpaul    message = client._recv()
21194149Swpaul    if message is None:
21294149Swpaul        summary("No response received")
213165782Sticso        client.close()
214173130Syongari        return
21594149Swpaul    if message.type != "urn:nfc:wkt:Hs":
21650120Swpaul        summary("Response was not Hs - received: " + message.type)
21750120Swpaul        client.close()
21850120Swpaul        return
21950120Swpaul
22050120Swpaul    print "Received message"
22150120Swpaul    try:
22250120Swpaul        print message.pretty()
223179592Sbenno    except Exception, e:
224179592Sbenno        print e
225179592Sbenno    print str(message).encode("hex")
22666989Simp    message = nfc.ndef.HandoverSelectMessage(message)
22766989Simp    summary("Handover select received")
22866989Simp    try:
22950120Swpaul        print message.pretty()
23050120Swpaul    except Exception, e:
23150120Swpaul        print e
23259475Swpaul
23359475Swpaul    for carrier in message.carriers:
23459475Swpaul        print "Remote carrier type: " + carrier.type
23575353Smjacob        if carrier.type == "application/vnd.wfa.p2p":
23675353Smjacob            print "P2P carrier type match - send to wpa_supplicant"
23784145Sjlemon            if "OK" in wpas_report_handover(data, carrier.record, "INIT"):
238120281Swilko                success_report("P2P handover reported successfully (initiator)")
239165096Syongari            else:
240165096Syongari                summary("P2P handover report rejected")
241165096Syongari            break
242165096Syongari
243165096Syongari    print "Remove peer"
244165096Syongari    client.close()
245165096Syongari    print "Done with handover"
246165096Syongari    global only_one
247165096Syongari    if only_one:
248182751Sraj        print "only_one -> stop loop"
249165096Syongari        global continue_loop
25084145Sjlemon        continue_loop = False
251165096Syongari
252165096Syongari    global no_wait
253165096Syongari    if no_wait:
254165096Syongari        print "Trying to exit.."
255        global terminate_now
256        terminate_now = True
257
258
259class HandoverServer(nfc.handover.HandoverServer):
260    def __init__(self, llc):
261        super(HandoverServer, self).__init__(llc)
262        self.sent_carrier = None
263        self.ho_server_processing = False
264        self.success = False
265
266    # override to avoid parser error in request/response.pretty() in nfcpy
267    # due to new WSC handover format
268    def _process_request(self, request):
269        summary("received handover request {}".format(request.type))
270        response = nfc.ndef.Message("\xd1\x02\x01Hs\x12")
271        if not request.type == 'urn:nfc:wkt:Hr':
272            summary("not a handover request")
273        else:
274            try:
275                request = nfc.ndef.HandoverRequestMessage(request)
276            except nfc.ndef.DecodeError as e:
277                summary("error decoding 'Hr' message: {}".format(e))
278            else:
279                response = self.process_request(request)
280        summary("send handover response {}".format(response.type))
281        return response
282
283    def process_request(self, request):
284        self.ho_server_processing = True
285        clear_raw_mode()
286        print "HandoverServer - request received"
287        try:
288            print "Parsed handover request: " + request.pretty()
289        except Exception, e:
290            print e
291
292        sel = nfc.ndef.HandoverSelectMessage(version="1.2")
293
294        found = False
295
296        for carrier in request.carriers:
297            print "Remote carrier type: " + carrier.type
298            if carrier.type == "application/vnd.wfa.p2p":
299                print "P2P carrier type match - add P2P carrier record"
300                found = True
301                self.received_carrier = carrier.record
302                print "Carrier record:"
303                try:
304                    print carrier.record.pretty()
305                except Exception, e:
306                    print e
307                data = wpas_get_handover_sel()
308                if data is None:
309                    print "Could not get handover select carrier record from wpa_supplicant"
310                    continue
311                print "Handover select carrier record from wpa_supplicant:"
312                print data.encode("hex")
313                self.sent_carrier = data
314                if "OK" in wpas_report_handover(self.received_carrier, self.sent_carrier, "RESP"):
315                    success_report("P2P handover reported successfully (responder)")
316                else:
317                    summary("P2P handover report rejected")
318                    break
319
320                message = nfc.ndef.Message(data);
321                sel.add_carrier(message[0], "active", message[1:])
322                break
323
324        for carrier in request.carriers:
325            if found:
326                break
327            print "Remote carrier type: " + carrier.type
328            if carrier.type == "application/vnd.wfa.wsc":
329                print "WSC carrier type match - add WSC carrier record"
330                found = True
331                self.received_carrier = carrier.record
332                print "Carrier record:"
333                try:
334                    print carrier.record.pretty()
335                except Exception, e:
336                    print e
337                data = wpas_get_handover_sel_wps()
338                if data is None:
339                    print "Could not get handover select carrier record from wpa_supplicant"
340                    continue
341                print "Handover select carrier record from wpa_supplicant:"
342                print data.encode("hex")
343                self.sent_carrier = data
344                if "OK" in wpas_report_handover_wsc(self.received_carrier, self.sent_carrier, "RESP"):
345                    success_report("WSC handover reported successfully")
346                else:
347                    summary("WSC handover report rejected")
348                    break
349
350                message = nfc.ndef.Message(data);
351                sel.add_carrier(message[0], "active", message[1:])
352                found = True
353                break
354
355        print "Handover select:"
356        try:
357            print sel.pretty()
358        except Exception, e:
359            print e
360        print str(sel).encode("hex")
361
362        summary("Sending handover select")
363        self.success = True
364        return sel
365
366
367def clear_raw_mode():
368    import sys, tty, termios
369    global prev_tcgetattr, in_raw_mode
370    if not in_raw_mode:
371        return
372    fd = sys.stdin.fileno()
373    termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr)
374    in_raw_mode = False
375
376
377def getch():
378    import sys, tty, termios, select
379    global prev_tcgetattr, in_raw_mode
380    fd = sys.stdin.fileno()
381    prev_tcgetattr = termios.tcgetattr(fd)
382    ch = None
383    try:
384        tty.setraw(fd)
385        in_raw_mode = True
386        [i, o, e] = select.select([fd], [], [], 0.05)
387        if i:
388            ch = sys.stdin.read(1)
389    finally:
390        termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr)
391        in_raw_mode = False
392    return ch
393
394
395def p2p_tag_read(tag):
396    success = False
397    if len(tag.ndef.message):
398        for record in tag.ndef.message:
399            print "record type " + record.type
400            if record.type == "application/vnd.wfa.wsc":
401                summary("WPS tag - send to wpa_supplicant")
402                success = wpas_tag_read(tag.ndef.message)
403                break
404            if record.type == "application/vnd.wfa.p2p":
405                summary("P2P tag - send to wpa_supplicant")
406                success = wpas_tag_read(tag.ndef.message)
407                break
408    else:
409        summary("Empty tag")
410
411    if success:
412        success_report("Tag read succeeded")
413
414    return success
415
416
417def rdwr_connected_p2p_write(tag):
418    summary("Tag found - writing - " + str(tag))
419    global p2p_sel_data
420    tag.ndef.message = str(p2p_sel_data)
421    success_report("Tag write succeeded")
422    print "Done - remove tag"
423    global only_one
424    if only_one:
425        global continue_loop
426        continue_loop = False
427    global p2p_sel_wait_remove
428    return p2p_sel_wait_remove
429
430def wps_write_p2p_handover_sel(clf, wait_remove=True):
431    print "Write P2P handover select"
432    data = wpas_get_handover_sel(tag=True)
433    if (data == None):
434        summary("Could not get P2P handover select from wpa_supplicant")
435        return
436
437    global p2p_sel_wait_remove
438    p2p_sel_wait_remove = wait_remove
439    global p2p_sel_data
440    p2p_sel_data = nfc.ndef.HandoverSelectMessage(version="1.2")
441    message = nfc.ndef.Message(data);
442    p2p_sel_data.add_carrier(message[0], "active", message[1:])
443    print "Handover select:"
444    try:
445        print p2p_sel_data.pretty()
446    except Exception, e:
447        print e
448    print str(p2p_sel_data).encode("hex")
449
450    print "Touch an NFC tag"
451    clf.connect(rdwr={'on-connect': rdwr_connected_p2p_write})
452
453
454def rdwr_connected(tag):
455    global only_one, no_wait
456    summary("Tag connected: " + str(tag))
457
458    if tag.ndef:
459        print "NDEF tag: " + tag.type
460        try:
461            print tag.ndef.message.pretty()
462        except Exception, e:
463            print e
464        success = p2p_tag_read(tag)
465        if only_one and success:
466            global continue_loop
467            continue_loop = False
468    else:
469        summary("Not an NDEF tag - remove tag")
470        return True
471
472    return not no_wait
473
474
475def llcp_worker(llc):
476    global init_on_touch
477    if init_on_touch:
478            print "Starting handover client"
479            p2p_handover_client(llc)
480            return
481
482    global no_input
483    if no_input:
484        print "Wait for handover to complete"
485    else:
486        print "Wait for handover to complete - press 'i' to initiate ('w' for WPS only, 'p' for P2P only)"
487    global srv
488    global wait_connection
489    while not wait_connection and srv.sent_carrier is None:
490        if srv.ho_server_processing:
491            time.sleep(0.025)
492        elif no_input:
493            time.sleep(0.5)
494        else:
495            global include_wps_req, include_p2p_req
496            res = getch()
497            if res == 'i':
498                include_wps_req = True
499                include_p2p_req = True
500            elif res == 'p':
501                include_wps_req = False
502                include_p2p_req = True
503            elif res == 'w':
504                include_wps_req = True
505                include_p2p_req = False
506            else:
507                continue
508            clear_raw_mode()
509            print "Starting handover client"
510            p2p_handover_client(llc)
511            return
512
513    clear_raw_mode()
514    print "Exiting llcp_worker thread"
515
516def llcp_startup(clf, llc):
517    print "Start LLCP server"
518    global srv
519    srv = HandoverServer(llc)
520    return llc
521
522def llcp_connected(llc):
523    print "P2P LLCP connected"
524    global wait_connection
525    wait_connection = False
526    global init_on_touch
527    if not init_on_touch:
528        global srv
529        srv.start()
530    if init_on_touch or not no_input:
531        threading.Thread(target=llcp_worker, args=(llc,)).start()
532    return True
533
534def terminate_loop():
535    global terminate_now
536    return terminate_now
537
538def main():
539    clf = nfc.ContactlessFrontend()
540
541    parser = argparse.ArgumentParser(description='nfcpy to wpa_supplicant integration for P2P and WPS NFC operations')
542    parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO,
543                        action='store_const', dest='loglevel',
544                        help='verbose debug output')
545    parser.add_argument('-q', const=logging.WARNING, action='store_const',
546                        dest='loglevel', help='be quiet')
547    parser.add_argument('--only-one', '-1', action='store_true',
548                        help='run only one operation and exit')
549    parser.add_argument('--init-on-touch', '-I', action='store_true',
550                        help='initiate handover on touch')
551    parser.add_argument('--no-wait', action='store_true',
552                        help='do not wait for tag to be removed before exiting')
553    parser.add_argument('--ifname', '-i',
554                        help='network interface name')
555    parser.add_argument('--no-wps-req', '-N', action='store_true',
556                        help='do not include WPS carrier record in request')
557    parser.add_argument('--no-input', '-a', action='store_true',
558                        help='do not use stdout input to initiate handover')
559    parser.add_argument('--tag-read-only', '-t', action='store_true',
560                        help='tag read only (do not allow connection handover)')
561    parser.add_argument('--handover-only', action='store_true',
562                        help='connection handover only (do not allow tag read)')
563    parser.add_argument('--freq', '-f',
564                        help='forced frequency of operating channel in MHz')
565    parser.add_argument('--summary',
566                        help='summary file for writing status updates')
567    parser.add_argument('--success',
568                        help='success file for writing success update')
569    parser.add_argument('command', choices=['write-p2p-sel'],
570                        nargs='?')
571    args = parser.parse_args()
572
573    global only_one
574    only_one = args.only_one
575
576    global no_wait
577    no_wait = args.no_wait
578
579    global force_freq
580    force_freq = args.freq
581
582    logging.basicConfig(level=args.loglevel)
583
584    global init_on_touch
585    init_on_touch = args.init_on_touch
586
587    if args.ifname:
588        global ifname
589        ifname = args.ifname
590        print "Selected ifname " + ifname
591
592    if args.no_wps_req:
593        global include_wps_req
594        include_wps_req = False
595
596    if args.summary:
597        global summary_file
598        summary_file = args.summary
599
600    if args.success:
601        global success_file
602        success_file = args.success
603
604    if args.no_input:
605        global no_input
606        no_input = True
607
608    clf = nfc.ContactlessFrontend()
609    global wait_connection
610
611    try:
612        if not clf.open("usb"):
613            print "Could not open connection with an NFC device"
614            raise SystemExit
615
616        if args.command == "write-p2p-sel":
617            wps_write_p2p_handover_sel(clf, wait_remove=not args.no_wait)
618            raise SystemExit
619
620        global continue_loop
621        while continue_loop:
622            print "Waiting for a tag or peer to be touched"
623            wait_connection = True
624            try:
625                if args.tag_read_only:
626                    if not clf.connect(rdwr={'on-connect': rdwr_connected}):
627                        break
628                elif args.handover_only:
629                    if not clf.connect(llcp={'on-startup': llcp_startup,
630                                             'on-connect': llcp_connected},
631                                       terminate=terminate_loop):
632                        break
633                else:
634                    if not clf.connect(rdwr={'on-connect': rdwr_connected},
635                                       llcp={'on-startup': llcp_startup,
636                                             'on-connect': llcp_connected},
637                                       terminate=terminate_loop):
638                        break
639            except Exception, e:
640                print "clf.connect failed"
641
642            global srv
643            if only_one and srv and srv.success:
644                raise SystemExit
645
646    except KeyboardInterrupt:
647        raise SystemExit
648    finally:
649        clf.close()
650
651    raise SystemExit
652
653if __name__ == '__main__':
654    main()
655