p2p-nfc.py 17 KB

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