p2p-nfc.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. #!/usr/bin/python
  2. #
  3. # Example nfcpy to wpa_supplicant wrapper for P2P NFC operations
  4. # Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi>
  5. #
  6. # This software may be distributed under the terms of the BSD license.
  7. # See README for more details.
  8. import os
  9. import sys
  10. import time
  11. import random
  12. import threading
  13. import argparse
  14. import nfc
  15. import nfc.ndef
  16. import nfc.llcp
  17. import nfc.handover
  18. import logging
  19. import wpaspy
  20. wpas_ctrl = '/var/run/wpa_supplicant'
  21. ifname = None
  22. init_on_touch = False
  23. in_raw_mode = False
  24. prev_tcgetattr = 0
  25. include_wps_req = True
  26. include_p2p_req = True
  27. no_input = False
  28. srv = None
  29. continue_loop = True
  30. terminate_now = False
  31. summary_file = None
  32. success_file = None
  33. def summary(txt):
  34. print txt
  35. if summary_file:
  36. with open(summary_file, 'a') as f:
  37. f.write(txt + "\n")
  38. def success_report(txt):
  39. summary(txt)
  40. if success_file:
  41. with open(success_file, 'a') as f:
  42. f.write(txt + "\n")
  43. def wpas_connect():
  44. ifaces = []
  45. if os.path.isdir(wpas_ctrl):
  46. try:
  47. ifaces = [os.path.join(wpas_ctrl, i) for i in os.listdir(wpas_ctrl)]
  48. except OSError, error:
  49. print "Could not find wpa_supplicant: ", error
  50. return None
  51. if len(ifaces) < 1:
  52. print "No wpa_supplicant control interface found"
  53. return None
  54. for ctrl in ifaces:
  55. if ifname:
  56. if ifname not in ctrl:
  57. continue
  58. try:
  59. print "Trying to use control interface " + ctrl
  60. wpas = wpaspy.Ctrl(ctrl)
  61. return wpas
  62. except Exception, e:
  63. pass
  64. return None
  65. def wpas_tag_read(message):
  66. wpas = wpas_connect()
  67. if (wpas == None):
  68. return False
  69. cmd = "WPS_NFC_TAG_READ " + str(message).encode("hex")
  70. global force_freq
  71. if force_freq:
  72. cmd = cmd + " freq=" + force_freq
  73. if "FAIL" in wpas.request(cmd):
  74. return False
  75. return True
  76. def wpas_get_handover_req():
  77. wpas = wpas_connect()
  78. if (wpas == None):
  79. return None
  80. res = wpas.request("NFC_GET_HANDOVER_REQ NDEF P2P-CR").rstrip()
  81. if "FAIL" in res:
  82. return None
  83. return res.decode("hex")
  84. def wpas_get_handover_req_wps():
  85. wpas = wpas_connect()
  86. if (wpas == None):
  87. return None
  88. res = wpas.request("NFC_GET_HANDOVER_REQ NDEF WPS-CR").rstrip()
  89. if "FAIL" in res:
  90. return None
  91. return res.decode("hex")
  92. def wpas_get_handover_sel(tag=False):
  93. wpas = wpas_connect()
  94. if (wpas == None):
  95. return None
  96. if tag:
  97. res = wpas.request("NFC_GET_HANDOVER_SEL NDEF P2P-CR-TAG").rstrip()
  98. else:
  99. res = wpas.request("NFC_GET_HANDOVER_SEL NDEF P2P-CR").rstrip()
  100. if "FAIL" in res:
  101. return None
  102. return res.decode("hex")
  103. def wpas_get_handover_sel_wps():
  104. wpas = wpas_connect()
  105. if (wpas == None):
  106. return None
  107. res = wpas.request("NFC_GET_HANDOVER_SEL NDEF WPS-CR");
  108. if "FAIL" in res:
  109. return None
  110. return res.rstrip().decode("hex")
  111. def wpas_report_handover(req, sel, type):
  112. wpas = wpas_connect()
  113. if (wpas == None):
  114. return None
  115. cmd = "NFC_REPORT_HANDOVER " + type + " P2P " + str(req).encode("hex") + " " + str(sel).encode("hex")
  116. global force_freq
  117. if force_freq:
  118. cmd = cmd + " freq=" + force_freq
  119. return wpas.request(cmd)
  120. def wpas_report_handover_wsc(req, sel, type):
  121. wpas = wpas_connect()
  122. if (wpas == None):
  123. return None
  124. cmd = "NFC_REPORT_HANDOVER " + type + " WPS " + str(req).encode("hex") + " " + str(sel).encode("hex")
  125. if force_freq:
  126. cmd = cmd + " freq=" + force_freq
  127. return wpas.request(cmd)
  128. def p2p_handover_client(llc):
  129. message = nfc.ndef.HandoverRequestMessage(version="1.2")
  130. message.nonce = random.randint(0, 0xffff)
  131. global include_p2p_req
  132. if include_p2p_req:
  133. data = wpas_get_handover_req()
  134. if (data == None):
  135. summary("Could not get handover request carrier record from wpa_supplicant")
  136. return
  137. print "Handover request carrier record from wpa_supplicant: " + data.encode("hex")
  138. datamsg = nfc.ndef.Message(data)
  139. message.add_carrier(datamsg[0], "active", datamsg[1:])
  140. global include_wps_req
  141. if include_wps_req:
  142. print "Handover request (pre-WPS):"
  143. try:
  144. print message.pretty()
  145. except Exception, e:
  146. print e
  147. data = wpas_get_handover_req_wps()
  148. if data:
  149. print "Add WPS request in addition to P2P"
  150. datamsg = nfc.ndef.Message(data)
  151. message.add_carrier(datamsg[0], "active", datamsg[1:])
  152. print "Handover request:"
  153. try:
  154. print message.pretty()
  155. except Exception, e:
  156. print e
  157. print str(message).encode("hex")
  158. client = nfc.handover.HandoverClient(llc)
  159. try:
  160. summary("Trying to initiate NFC connection handover")
  161. client.connect()
  162. summary("Connected for handover")
  163. except nfc.llcp.ConnectRefused:
  164. summary("Handover connection refused")
  165. client.close()
  166. return
  167. except Exception, e:
  168. summary("Other exception: " + str(e))
  169. client.close()
  170. return
  171. summary("Sending handover request")
  172. if not client.send(message):
  173. summary("Failed to send handover request")
  174. client.close()
  175. return
  176. summary("Receiving handover response")
  177. message = client._recv()
  178. if message is None:
  179. summary("No response received")
  180. client.close()
  181. return
  182. if message.type != "urn:nfc:wkt:Hs":
  183. summary("Response was not Hs - received: " + message.type)
  184. client.close()
  185. return
  186. print "Received message"
  187. try:
  188. print message.pretty()
  189. except Exception, e:
  190. print e
  191. print str(message).encode("hex")
  192. message = nfc.ndef.HandoverSelectMessage(message)
  193. summary("Handover select received")
  194. try:
  195. print message.pretty()
  196. except Exception, e:
  197. print e
  198. for carrier in message.carriers:
  199. print "Remote carrier type: " + carrier.type
  200. if carrier.type == "application/vnd.wfa.p2p":
  201. print "P2P carrier type match - send to wpa_supplicant"
  202. if "OK" in wpas_report_handover(data, carrier.record, "INIT"):
  203. success_report("P2P handover reported successfully (initiator)")
  204. else:
  205. summary("P2P handover report rejected")
  206. break
  207. print "Remove peer"
  208. client.close()
  209. print "Done with handover"
  210. global only_one
  211. if only_one:
  212. print "only_one -> stop loop"
  213. global continue_loop
  214. continue_loop = False
  215. global no_wait
  216. if no_wait:
  217. print "Trying to exit.."
  218. global terminate_now
  219. terminate_now = True
  220. class HandoverServer(nfc.handover.HandoverServer):
  221. def __init__(self, llc):
  222. super(HandoverServer, self).__init__(llc)
  223. self.sent_carrier = None
  224. self.ho_server_processing = False
  225. self.success = False
  226. # override to avoid parser error in request/response.pretty() in nfcpy
  227. # due to new WSC handover format
  228. def _process_request(self, request):
  229. summary("received handover request {}".format(request.type))
  230. response = nfc.ndef.Message("\xd1\x02\x01Hs\x12")
  231. if not request.type == 'urn:nfc:wkt:Hr':
  232. summary("not a handover request")
  233. else:
  234. try:
  235. request = nfc.ndef.HandoverRequestMessage(request)
  236. except nfc.ndef.DecodeError as e:
  237. summary("error decoding 'Hr' message: {}".format(e))
  238. else:
  239. response = self.process_request(request)
  240. summary("send handover response {}".format(response.type))
  241. return response
  242. def process_request(self, request):
  243. self.ho_server_processing = True
  244. clear_raw_mode()
  245. print "HandoverServer - request received"
  246. try:
  247. print "Parsed handover request: " + request.pretty()
  248. except Exception, e:
  249. print e
  250. sel = nfc.ndef.HandoverSelectMessage(version="1.2")
  251. found = False
  252. for carrier in request.carriers:
  253. print "Remote carrier type: " + carrier.type
  254. if carrier.type == "application/vnd.wfa.p2p":
  255. print "P2P carrier type match - add P2P carrier record"
  256. found = True
  257. self.received_carrier = carrier.record
  258. print "Carrier record:"
  259. try:
  260. print carrier.record.pretty()
  261. except Exception, e:
  262. print e
  263. data = wpas_get_handover_sel()
  264. if data is None:
  265. print "Could not get handover select carrier record from wpa_supplicant"
  266. continue
  267. print "Handover select carrier record from wpa_supplicant:"
  268. print data.encode("hex")
  269. self.sent_carrier = data
  270. if "OK" in wpas_report_handover(self.received_carrier, self.sent_carrier, "RESP"):
  271. success_report("P2P handover reported successfully (responder)")
  272. else:
  273. summary("P2P handover report rejected")
  274. break
  275. message = nfc.ndef.Message(data);
  276. sel.add_carrier(message[0], "active", message[1:])
  277. break
  278. for carrier in request.carriers:
  279. if found:
  280. break
  281. print "Remote carrier type: " + carrier.type
  282. if carrier.type == "application/vnd.wfa.wsc":
  283. print "WSC carrier type match - add WSC carrier record"
  284. found = True
  285. self.received_carrier = carrier.record
  286. print "Carrier record:"
  287. try:
  288. print carrier.record.pretty()
  289. except Exception, e:
  290. print e
  291. data = wpas_get_handover_sel_wps()
  292. if data is None:
  293. print "Could not get handover select carrier record from wpa_supplicant"
  294. continue
  295. print "Handover select carrier record from wpa_supplicant:"
  296. print data.encode("hex")
  297. self.sent_carrier = data
  298. if "OK" in wpas_report_handover_wsc(self.received_carrier, self.sent_carrier, "RESP"):
  299. success_report("WSC handover reported successfully")
  300. else:
  301. summary("WSC handover report rejected")
  302. break
  303. message = nfc.ndef.Message(data);
  304. sel.add_carrier(message[0], "active", message[1:])
  305. found = True
  306. break
  307. print "Handover select:"
  308. try:
  309. print sel.pretty()
  310. except Exception, e:
  311. print e
  312. print str(sel).encode("hex")
  313. summary("Sending handover select")
  314. self.success = True
  315. return sel
  316. def clear_raw_mode():
  317. import sys, tty, termios
  318. global prev_tcgetattr, in_raw_mode
  319. if not in_raw_mode:
  320. return
  321. fd = sys.stdin.fileno()
  322. termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr)
  323. in_raw_mode = False
  324. def getch():
  325. import sys, tty, termios, select
  326. global prev_tcgetattr, in_raw_mode
  327. fd = sys.stdin.fileno()
  328. prev_tcgetattr = termios.tcgetattr(fd)
  329. ch = None
  330. try:
  331. tty.setraw(fd)
  332. in_raw_mode = True
  333. [i, o, e] = select.select([fd], [], [], 0.05)
  334. if i:
  335. ch = sys.stdin.read(1)
  336. finally:
  337. termios.tcsetattr(fd, termios.TCSADRAIN, prev_tcgetattr)
  338. in_raw_mode = False
  339. return ch
  340. def p2p_tag_read(tag):
  341. success = False
  342. if len(tag.ndef.message):
  343. for record in tag.ndef.message:
  344. print "record type " + record.type
  345. if record.type == "application/vnd.wfa.wsc":
  346. summary("WPS tag - send to wpa_supplicant")
  347. success = wpas_tag_read(tag.ndef.message)
  348. break
  349. if record.type == "application/vnd.wfa.p2p":
  350. summary("P2P tag - send to wpa_supplicant")
  351. success = wpas_tag_read(tag.ndef.message)
  352. break
  353. else:
  354. summary("Empty tag")
  355. if success:
  356. success_report("Tag read succeeded")
  357. return success
  358. def rdwr_connected_p2p_write(tag):
  359. summary("Tag found - writing - " + str(tag))
  360. global p2p_sel_data
  361. tag.ndef.message = str(p2p_sel_data)
  362. success_report("Tag write succeeded")
  363. print "Done - remove tag"
  364. global only_one
  365. if only_one:
  366. global continue_loop
  367. continue_loop = False
  368. global p2p_sel_wait_remove
  369. return p2p_sel_wait_remove
  370. def wps_write_p2p_handover_sel(clf, wait_remove=True):
  371. print "Write P2P handover select"
  372. data = wpas_get_handover_sel(tag=True)
  373. if (data == None):
  374. summary("Could not get P2P handover select from wpa_supplicant")
  375. return
  376. global p2p_sel_wait_remove
  377. p2p_sel_wait_remove = wait_remove
  378. global p2p_sel_data
  379. p2p_sel_data = nfc.ndef.HandoverSelectMessage(version="1.2")
  380. message = nfc.ndef.Message(data);
  381. p2p_sel_data.add_carrier(message[0], "active", message[1:])
  382. print "Handover select:"
  383. try:
  384. print p2p_sel_data.pretty()
  385. except Exception, e:
  386. print e
  387. print str(p2p_sel_data).encode("hex")
  388. print "Touch an NFC tag"
  389. clf.connect(rdwr={'on-connect': rdwr_connected_p2p_write})
  390. def rdwr_connected(tag):
  391. global only_one, no_wait
  392. summary("Tag connected: " + str(tag))
  393. if tag.ndef:
  394. print "NDEF tag: " + tag.type
  395. try:
  396. print tag.ndef.message.pretty()
  397. except Exception, e:
  398. print e
  399. success = p2p_tag_read(tag)
  400. if only_one and success:
  401. global continue_loop
  402. continue_loop = False
  403. else:
  404. summary("Not an NDEF tag - remove tag")
  405. return True
  406. return not no_wait
  407. def llcp_worker(llc):
  408. global init_on_touch
  409. if init_on_touch:
  410. print "Starting handover client"
  411. p2p_handover_client(llc)
  412. return
  413. global no_input
  414. if no_input:
  415. print "Wait for handover to complete"
  416. else:
  417. print "Wait for handover to complete - press 'i' to initiate ('w' for WPS only, 'p' for P2P only)"
  418. global srv
  419. global wait_connection
  420. while not wait_connection and srv.sent_carrier is None:
  421. if srv.ho_server_processing:
  422. time.sleep(0.025)
  423. elif no_input:
  424. time.sleep(0.5)
  425. else:
  426. global include_wps_req, include_p2p_req
  427. res = getch()
  428. if res == 'i':
  429. include_wps_req = True
  430. include_p2p_req = True
  431. elif res == 'p':
  432. include_wps_req = False
  433. include_p2p_req = True
  434. elif res == 'w':
  435. include_wps_req = True
  436. include_p2p_req = False
  437. else:
  438. continue
  439. clear_raw_mode()
  440. print "Starting handover client"
  441. p2p_handover_client(llc)
  442. return
  443. clear_raw_mode()
  444. print "Exiting llcp_worker thread"
  445. def llcp_startup(clf, llc):
  446. print "Start LLCP server"
  447. global srv
  448. srv = HandoverServer(llc)
  449. return llc
  450. def llcp_connected(llc):
  451. print "P2P LLCP connected"
  452. global wait_connection
  453. wait_connection = False
  454. global init_on_touch
  455. if not init_on_touch:
  456. global srv
  457. srv.start()
  458. if init_on_touch or not no_input:
  459. threading.Thread(target=llcp_worker, args=(llc,)).start()
  460. return True
  461. def terminate_loop():
  462. global terminate_now
  463. return terminate_now
  464. def main():
  465. clf = nfc.ContactlessFrontend()
  466. parser = argparse.ArgumentParser(description='nfcpy to wpa_supplicant integration for P2P and WPS NFC operations')
  467. parser.add_argument('-d', const=logging.DEBUG, default=logging.INFO,
  468. action='store_const', dest='loglevel',
  469. help='verbose debug output')
  470. parser.add_argument('-q', const=logging.WARNING, action='store_const',
  471. dest='loglevel', help='be quiet')
  472. parser.add_argument('--only-one', '-1', action='store_true',
  473. help='run only one operation and exit')
  474. parser.add_argument('--init-on-touch', '-I', action='store_true',
  475. help='initiate handover on touch')
  476. parser.add_argument('--no-wait', action='store_true',
  477. help='do not wait for tag to be removed before exiting')
  478. parser.add_argument('--ifname', '-i',
  479. help='network interface name')
  480. parser.add_argument('--no-wps-req', '-N', action='store_true',
  481. help='do not include WPS carrier record in request')
  482. parser.add_argument('--no-input', '-a', action='store_true',
  483. help='do not use stdout input to initiate handover')
  484. parser.add_argument('--tag-read-only', '-t', action='store_true',
  485. help='tag read only (do not allow connection handover)')
  486. parser.add_argument('--handover-only', action='store_true',
  487. help='connection handover only (do not allow tag read)')
  488. parser.add_argument('--freq', '-f',
  489. help='forced frequency of operating channel in MHz')
  490. parser.add_argument('--summary',
  491. help='summary file for writing status updates')
  492. parser.add_argument('--success',
  493. help='success file for writing success update')
  494. parser.add_argument('command', choices=['write-p2p-sel'],
  495. nargs='?')
  496. args = parser.parse_args()
  497. global only_one
  498. only_one = args.only_one
  499. global no_wait
  500. no_wait = args.no_wait
  501. global force_freq
  502. force_freq = args.freq
  503. logging.basicConfig(level=args.loglevel)
  504. global init_on_touch
  505. init_on_touch = args.init_on_touch
  506. if args.ifname:
  507. global ifname
  508. ifname = args.ifname
  509. print "Selected ifname " + ifname
  510. if args.no_wps_req:
  511. global include_wps_req
  512. include_wps_req = False
  513. if args.summary:
  514. global summary_file
  515. summary_file = args.summary
  516. if args.success:
  517. global success_file
  518. success_file = args.success
  519. if args.no_input:
  520. global no_input
  521. no_input = True
  522. clf = nfc.ContactlessFrontend()
  523. global wait_connection
  524. try:
  525. if not clf.open("usb"):
  526. print "Could not open connection with an NFC device"
  527. raise SystemExit
  528. if args.command == "write-p2p-sel":
  529. wps_write_p2p_handover_sel(clf, wait_remove=not args.no_wait)
  530. raise SystemExit
  531. global continue_loop
  532. while continue_loop:
  533. print "Waiting for a tag or peer to be touched"
  534. wait_connection = True
  535. try:
  536. if args.tag_read_only:
  537. if not clf.connect(rdwr={'on-connect': rdwr_connected}):
  538. break
  539. elif args.handover_only:
  540. if not clf.connect(llcp={'on-startup': llcp_startup,
  541. 'on-connect': llcp_connected},
  542. terminate=terminate_loop):
  543. break
  544. else:
  545. if not clf.connect(rdwr={'on-connect': rdwr_connected},
  546. llcp={'on-startup': llcp_startup,
  547. 'on-connect': llcp_connected},
  548. terminate=terminate_loop):
  549. break
  550. except Exception, e:
  551. print "clf.connect failed"
  552. global srv
  553. if only_one and srv and srv.success:
  554. raise SystemExit
  555. except KeyboardInterrupt:
  556. raise SystemExit
  557. finally:
  558. clf.close()
  559. raise SystemExit
  560. if __name__ == '__main__':
  561. main()