ExtOsdep.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
  4. *
  5. * (c) ZeroTier, Inc.
  6. * https://www.zerotier.com/
  7. */
  8. #include "ExtOsdep.hpp"
  9. #include "../node/AtomicCounter.hpp"
  10. #include <fcntl.h>
  11. #include <iostream>
  12. #include <list>
  13. #include <sys/times.h>
  14. #include <unistd.h>
  15. #define ZT_TAP_BUF_SIZE 16384
  16. #ifdef ZT_EXTOSDEP
  17. namespace ZeroTier {
  18. static int eodFd = -1;
  19. static Mutex eodMutex;
  20. static int eodMgmtFd = -1;
  21. struct EodRoute {
  22. InetAddress target;
  23. InetAddress via;
  24. InetAddress src;
  25. std::string ifname;
  26. };
  27. static std::list<EodRoute> allRoutes;
  28. template <typename T> static void __eodSend(const T& t)
  29. {
  30. write(eodFd, &t, sizeof(t));
  31. }
  32. static void strncpyx(char* dest, const char* src, size_t n)
  33. {
  34. strncpy(dest, src, n);
  35. if (n > 1) {
  36. dest[n - 1] = 0;
  37. }
  38. }
  39. static int __eodWait(unsigned char msg, unsigned char* d, unsigned l, unsigned maxl = 0, int* recvfd = nullptr)
  40. {
  41. if (! maxl) {
  42. maxl = l;
  43. }
  44. auto start = times(NULL);
  45. while (1) {
  46. msghdr mh;
  47. iovec iov;
  48. struct {
  49. size_t cmsg_len;
  50. int cmsg_level;
  51. int cmsg_type;
  52. int fd;
  53. } __attribute__((packed)) cmsg;
  54. memset(&mh, 0, sizeof(mh));
  55. mh.msg_iov = &iov;
  56. mh.msg_iovlen = 1;
  57. if (recvfd) {
  58. mh.msg_control = &cmsg;
  59. mh.msg_controllen = sizeof(cmsg);
  60. }
  61. iov.iov_base = d;
  62. iov.iov_len = maxl;
  63. int r = recvmsg(eodFd, &mh, MSG_TRUNC | MSG_CMSG_CLOEXEC);
  64. if (r > 0) {
  65. if (recvfd && mh.msg_controllen >= sizeof(cmsg) && cmsg.cmsg_len == sizeof(cmsg) && cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS) {
  66. *recvfd = cmsg.fd;
  67. fprintf(stderr, "eodWait: received fd %d\n", *recvfd);
  68. }
  69. if (d[0] != msg) {
  70. fprintf(stderr, "eodWait: wrong msg, expected %u got %u\n", msg, d[0]);
  71. return -1;
  72. }
  73. if ((unsigned)r < l || (unsigned)r > maxl) {
  74. fprintf(stderr, "eodWait: wrong len, expected %u got %d\n", l, r);
  75. return -1;
  76. }
  77. return r;
  78. }
  79. if (times(NULL) - start > 500) {
  80. fprintf(stderr, "eodWait: timeout\n");
  81. return -1;
  82. }
  83. usleep(100000);
  84. }
  85. }
  86. template <typename T> static bool __eodWait(unsigned msg, T& t)
  87. {
  88. return __eodWait(msg, (unsigned char*)&t, sizeof(T)) == (int)sizeof(T);
  89. }
  90. template <typename M, typename R> static bool __eodXchg(const M& m, unsigned rm, R& r)
  91. {
  92. __eodSend(m);
  93. return __eodWait(rm, r);
  94. }
  95. template <typename M, typename R> static bool eodXchg(const M& m, unsigned rm, R& r)
  96. {
  97. Mutex::Lock l(eodMutex);
  98. return __eodXchg(m, rm, r);
  99. }
  100. void ExtOsdep::init(int fd1, int fd2)
  101. {
  102. eodFd = fd1;
  103. eodMgmtFd = fd2;
  104. fcntl(eodMgmtFd, F_SETFL, O_NONBLOCK);
  105. }
  106. void ExtOsdep::started(int* f, void** cp)
  107. {
  108. *f = eodMgmtFd;
  109. *cp = (void*)eodMgmtFd;
  110. unsigned char msg = ZT_EOD_MSG_STARTED;
  111. Mutex::Lock l(eodMutex);
  112. __eodSend(msg);
  113. }
  114. static std::string mgmtrd;
  115. static std::string mgmtwr;
  116. bool ExtOsdep::mgmtWritable(void* cookie)
  117. {
  118. if (cookie != (void*)eodMgmtFd) {
  119. return false;
  120. }
  121. if (mgmtwr.size() == 0) {
  122. return true;
  123. }
  124. auto sz = write(eodMgmtFd, mgmtwr.data(), mgmtwr.size());
  125. if (sz <= 0) {
  126. return false;
  127. }
  128. mgmtwr.erase(mgmtwr.begin(), mgmtwr.begin() + sz);
  129. return mgmtwr.empty();
  130. }
  131. bool ExtOsdep::mgmtRecv(void* cookie, void* data, unsigned long len, std::function<unsigned(unsigned, const std::string&, const std::string&, std::string&)> cb)
  132. {
  133. if (cookie != (void*)eodMgmtFd) {
  134. return false;
  135. }
  136. mgmtrd.append((char*)data, len);
  137. while (1) {
  138. auto req = (zt_eod_mgmt_req*)mgmtrd.data();
  139. if (mgmtrd.size() < sizeof(*req)) {
  140. break;
  141. }
  142. unsigned reqsz = sizeof(*req) + req->pathlen + req->datalen;
  143. if (mgmtrd.size() < reqsz) {
  144. break;
  145. }
  146. std::string resp;
  147. char* p = (char*)req->data;
  148. zt_eod_mgmt_reply rep;
  149. rep.scode = cb(req->method, std::string(p, p + req->pathlen), std::string(p + req->pathlen, p + req->pathlen + req->datalen), resp);
  150. rep.datalen = resp.size();
  151. mgmtrd.erase(mgmtrd.begin(), mgmtrd.begin() + reqsz);
  152. mgmtwr.append((char*)&rep, sizeof(rep));
  153. mgmtwr.append(resp);
  154. auto sz = write(eodMgmtFd, mgmtwr.data(), mgmtwr.size());
  155. if (sz > 0) {
  156. mgmtwr.erase(mgmtwr.begin(), mgmtwr.begin() + sz);
  157. }
  158. }
  159. return ! mgmtwr.empty();
  160. }
  161. void ExtOsdep::routeAddDel(bool add, const InetAddress& target, const InetAddress& via, const InetAddress& src, const char* ifname)
  162. {
  163. Mutex::Lock l(eodMutex);
  164. std::string ifn;
  165. if (ifname) {
  166. ifn = ifname;
  167. }
  168. if (add) {
  169. for (auto x = allRoutes.begin(); x != allRoutes.end(); ++x) {
  170. if (x->target == target && x->via == via && x->src == src && x->ifname == ifn) {
  171. return;
  172. }
  173. }
  174. allRoutes.push_back({ target, via, src, ifn });
  175. }
  176. else {
  177. bool found = false;
  178. for (auto x = allRoutes.begin(); x != allRoutes.end(); ++x) {
  179. if (x->target == target && x->via == via && x->src == src && x->ifname == ifn) {
  180. allRoutes.erase(x);
  181. found = true;
  182. break;
  183. }
  184. }
  185. if (! found) {
  186. return;
  187. }
  188. }
  189. zt_eod_msg_route req;
  190. memset(&req, 0, sizeof(req));
  191. req.cmd = add ? ZT_EOD_MSG_ADDROUTE : ZT_EOD_MSG_DELROUTE;
  192. req.afi = target.isV4() ? 1 : 2;
  193. req.dstlen = target.netmaskBits();
  194. memcpy(req.dst, target.rawIpData(), target.isV4() ? 4 : 16);
  195. if (ifname) {
  196. strncpyx(req.dev, ifname, sizeof(req.dev));
  197. }
  198. if (via) {
  199. memcpy(req.gw, via.rawIpData(), target.isV4() ? 4 : 16);
  200. }
  201. if (src) {
  202. memcpy(req.src, src.rawIpData(), target.isV4() ? 4 : 16);
  203. }
  204. unsigned char resp;
  205. __eodXchg(req, add ? ZT_EOD_MSG_ADDROUTERESP : ZT_EOD_MSG_DELROUTERESP, resp);
  206. }
  207. bool ExtOsdep::getBindAddrs(std::map<InetAddress, std::string>& ret)
  208. {
  209. Mutex::Lock l(eodMutex);
  210. unsigned char req = ZT_EOD_MSG_GETBINDADDRS;
  211. __eodSend(req);
  212. zt_eod_msg_getbindaddrsresp* resp;
  213. unsigned char buf[ZT_EOD_MAXMSGSIZE];
  214. int r = __eodWait(ZT_EOD_MSG_GETBINDADDRSRESP, (unsigned char*)buf, sizeof(*resp), sizeof(buf));
  215. if (r < (int)sizeof(*resp)) {
  216. return false;
  217. }
  218. int c = (r - (int)sizeof(*resp)) / sizeof(resp->addrs[0]);
  219. resp = (zt_eod_msg_getbindaddrsresp*)buf;
  220. for (int i = 0; i < c; ++i) {
  221. ret[InetAddress(resp->addrs[i].data, resp->addrs[i].afi == 1 ? 4 : 16, resp->addrs[i].len)] = resp->addrs[i].ifname;
  222. }
  223. return resp->result;
  224. }
  225. ExtOsdepTap::ExtOsdepTap(
  226. const char* homePath,
  227. const MAC& mac,
  228. unsigned int mtu,
  229. unsigned int metric,
  230. uint64_t nwid,
  231. const char* friendlyName,
  232. void (*handler)(void*, void*, uint64_t, const MAC&, const MAC&, unsigned int, unsigned int, const void*, unsigned int),
  233. void* arg)
  234. : _handler(handler)
  235. , _arg(arg)
  236. , _nwid(nwid)
  237. , _mac(mac)
  238. , _homePath(homePath)
  239. , _mtu(mtu)
  240. , _fd(0)
  241. , _enabled(true)
  242. , _run(true)
  243. {
  244. zt_eod_msg_addtap req;
  245. req.cmd = ZT_EOD_MSG_ADDTAP;
  246. req.nwid = nwid;
  247. req.mtu = mtu;
  248. req.metric = metric;
  249. strncpyx(req.fname, friendlyName, sizeof(req.fname));
  250. mac.copyTo(req.mac, 6);
  251. zt_eod_msg_addtapresp resp;
  252. Mutex::Lock l(eodMutex);
  253. __eodSend(req);
  254. _fd = -1;
  255. if (__eodWait(ZT_EOD_MSG_ADDTAPRESP, (unsigned char*)&resp, sizeof(resp), sizeof(resp), &_fd) != sizeof(resp)) {
  256. throw std::runtime_error(std::string("could not create TAP"));
  257. }
  258. _dev = resp.name;
  259. if (_dev.empty() || _fd < 0) {
  260. throw std::runtime_error(std::string("could not create TAP"));
  261. }
  262. fcntl(_fd, F_SETFL, O_NONBLOCK);
  263. (void)::pipe(_shutdownSignalPipe);
  264. for (unsigned int t = 0; t < 2; ++t) {
  265. _tapReaderThread[t] = std::thread([this, t] {
  266. fd_set readfds, nullfds;
  267. int n, nfds, r;
  268. void* buf = nullptr;
  269. std::vector<void*> buffers;
  270. if (! _run) {
  271. return;
  272. }
  273. FD_ZERO(&readfds);
  274. FD_ZERO(&nullfds);
  275. nfds = (int)std::max(_shutdownSignalPipe[0], _fd) + 1;
  276. r = 0;
  277. for (;;) {
  278. FD_SET(_shutdownSignalPipe[0], &readfds);
  279. FD_SET(_fd, &readfds);
  280. select(nfds, &readfds, &nullfds, &nullfds, (struct timeval*)0);
  281. if (FD_ISSET(_shutdownSignalPipe[0], &readfds)) { // writes to shutdown pipe terminate thread
  282. break;
  283. }
  284. if (FD_ISSET(_fd, &readfds)) {
  285. for (;;) { // read until there are no more packets, then return to outer select() loop
  286. if (! buf) {
  287. // To reduce use of the mutex, we keep a local buffer vector and
  288. // swap (which is a pointer swap) with the global one when it's
  289. // empty. This retrieves a batch of buffers to use.
  290. if (buffers.empty()) {
  291. std::lock_guard<std::mutex> l(_buffers_l);
  292. buffers.swap(_buffers);
  293. }
  294. if (buffers.empty()) {
  295. buf = malloc(ZT_TAP_BUF_SIZE);
  296. if (! buf) {
  297. break;
  298. }
  299. }
  300. else {
  301. buf = buffers.back();
  302. buffers.pop_back();
  303. }
  304. }
  305. n = (int)::read(_fd, reinterpret_cast<uint8_t*>(buf) + r, ZT_TAP_BUF_SIZE - r);
  306. if (n > 0) {
  307. // Some tap drivers like to send the ethernet frame and the
  308. // payload in two chunks, so handle that by accumulating
  309. // data until we have at least a frame.
  310. r += n;
  311. if (r > 14) {
  312. if (r > ((int)_mtu + 14)) { // sanity check for weird TAP behavior on some platforms
  313. r = _mtu + 14;
  314. }
  315. if (_enabled && _tapqsize.load() < 1000) {
  316. ++_tapqsize;
  317. _tapq.post(std::pair<void*, int>(buf, r));
  318. buf = nullptr;
  319. }
  320. r = 0;
  321. }
  322. }
  323. else {
  324. r = 0;
  325. break;
  326. }
  327. }
  328. }
  329. }
  330. });
  331. }
  332. _tapProcessorThread = std::thread([this] {
  333. MAC to, from;
  334. std::pair<void*, int> qi;
  335. while (_tapq.get(qi)) {
  336. --_tapqsize;
  337. uint8_t* const b = reinterpret_cast<uint8_t*>(qi.first);
  338. if (b) {
  339. to.setTo(b, 6);
  340. from.setTo(b + 6, 6);
  341. unsigned int etherType = Utils::ntoh(((const uint16_t*)b)[6]);
  342. _handler(_arg, nullptr, _nwid, from, to, etherType, 0, (const void*)(b + 14), (unsigned int)(qi.second - 14));
  343. {
  344. std::lock_guard<std::mutex> l(_buffers_l);
  345. if (_buffers.size() < 128) {
  346. _buffers.push_back(qi.first);
  347. }
  348. else {
  349. free(qi.first);
  350. }
  351. }
  352. }
  353. else {
  354. break;
  355. }
  356. }
  357. });
  358. }
  359. ExtOsdepTap::~ExtOsdepTap()
  360. {
  361. _run = false;
  362. (void)::write(_shutdownSignalPipe[1], "\0", 1); // causes reader thread(s) to exit
  363. _tapq.post(std::pair<void*, int>(nullptr, 0)); // causes processor thread to exit
  364. _tapReaderThread[0].join();
  365. _tapReaderThread[1].join();
  366. _tapProcessorThread.join();
  367. ::close(_fd);
  368. ::close(_shutdownSignalPipe[0]);
  369. ::close(_shutdownSignalPipe[1]);
  370. for (std::vector<void*>::iterator i(_buffers.begin()); i != _buffers.end(); ++i) {
  371. free(*i);
  372. }
  373. std::vector<std::pair<void*, int> > dv(_tapq.drain());
  374. for (std::vector<std::pair<void*, int> >::iterator i(dv.begin()); i != dv.end(); ++i) {
  375. if (i->first) {
  376. free(i->first);
  377. }
  378. }
  379. zt_eod_msg_deltap req;
  380. req.cmd = ZT_EOD_MSG_DELTAP;
  381. strcpy(req.name, _dev.c_str());
  382. unsigned char resp;
  383. eodXchg(req, ZT_EOD_MSG_DELTAPRESP, resp);
  384. }
  385. void ExtOsdepTap::setEnabled(bool en)
  386. {
  387. _enabled = en;
  388. }
  389. bool ExtOsdepTap::enabled() const
  390. {
  391. return _enabled;
  392. }
  393. void ExtOsdepTap::doRemoveIp(const InetAddress& ip)
  394. {
  395. zt_eod_msg_ip req;
  396. req.cmd = ZT_EOD_MSG_DELIP;
  397. strcpy(req.name, _dev.c_str());
  398. req.afi = ip.isV4() ? 1 : 2;
  399. req.len = ip.netmaskBits();
  400. memcpy(req.data, ip.rawIpData(), ip.isV4() ? 4 : 16);
  401. unsigned char resp;
  402. __eodXchg(req, ZT_EOD_MSG_DELIPRESP, resp);
  403. }
  404. bool ExtOsdepTap::addIp(const InetAddress& ip)
  405. {
  406. Mutex::Lock l(eodMutex);
  407. for (auto i = allIps.begin(); i != allIps.end(); ++i) {
  408. if (*i == ip) {
  409. return true;
  410. }
  411. if (i->ipsEqual(ip)) {
  412. doRemoveIp(*i);
  413. }
  414. }
  415. zt_eod_msg_ip req;
  416. req.cmd = ZT_EOD_MSG_ADDIP;
  417. strcpy(req.name, _dev.c_str());
  418. req.afi = ip.isV4() ? 1 : 2;
  419. req.len = ip.netmaskBits();
  420. memcpy(req.data, ip.rawIpData(), ip.isV4() ? 4 : 16);
  421. unsigned char resp;
  422. __eodXchg(req, ZT_EOD_MSG_ADDIPRESP, resp);
  423. allIps.push_back(ip);
  424. return true;
  425. }
  426. bool ExtOsdepTap::addIps(std::vector<InetAddress> ips)
  427. {
  428. return false;
  429. }
  430. bool ExtOsdepTap::removeIp(const InetAddress& ip)
  431. {
  432. Mutex::Lock l(eodMutex);
  433. for (auto i = allIps.begin(); i != allIps.end(); ++i) {
  434. if (*i == ip) {
  435. doRemoveIp(*i);
  436. return true;
  437. }
  438. }
  439. return false;
  440. }
  441. std::vector<InetAddress> ExtOsdepTap::ips() const
  442. {
  443. std::vector<InetAddress> ret;
  444. Mutex::Lock l(eodMutex);
  445. zt_eod_msg_getips req;
  446. req.cmd = ZT_EOD_MSG_GETIPS;
  447. strcpy(req.name, _dev.c_str());
  448. __eodSend(req);
  449. zt_eod_msg_getipsresp* resp;
  450. unsigned char buf[ZT_EOD_MAXMSGSIZE];
  451. int r = __eodWait(ZT_EOD_MSG_GETIPSRESP, (unsigned char*)buf, sizeof(*resp), sizeof(buf));
  452. if (r < (int)sizeof(*resp)) {
  453. return ret;
  454. }
  455. int c = (r - (int)sizeof(*resp)) / sizeof(resp->addrs[0]);
  456. resp = (zt_eod_msg_getipsresp*)buf;
  457. for (int i = 0; i < c; ++i) {
  458. ret.push_back(InetAddress(resp->addrs[i].data, resp->addrs[i].afi == 1 ? 4 : 16, resp->addrs[i].len));
  459. }
  460. return ret;
  461. }
  462. void ExtOsdepTap::put(const MAC& from, const MAC& to, unsigned int etherType, const void* data, unsigned int len)
  463. {
  464. char putBuf[ZT_MAX_MTU + 64];
  465. if ((_fd > 0) && (len <= _mtu) && (_enabled)) {
  466. to.copyTo(putBuf, 6);
  467. from.copyTo(putBuf + 6, 6);
  468. *((uint16_t*)(putBuf + 12)) = htons((uint16_t)etherType);
  469. memcpy(putBuf + 14, data, len);
  470. len += 14;
  471. (void)::write(_fd, putBuf, len);
  472. }
  473. }
  474. std::string ExtOsdepTap::deviceName() const
  475. {
  476. return _dev;
  477. }
  478. void ExtOsdepTap::setFriendlyName(const char* friendlyName)
  479. {
  480. }
  481. void ExtOsdepTap::scanMulticastGroups(std::vector<MulticastGroup>& added, std::vector<MulticastGroup>& removed)
  482. {
  483. char *ptr, *ptr2;
  484. unsigned char mac[6];
  485. std::vector<MulticastGroup> newGroups;
  486. int fd = ::open("/proc/net/dev_mcast", O_RDONLY);
  487. if (fd > 0) {
  488. char buf[131072];
  489. int n = (int)::read(fd, buf, sizeof(buf));
  490. if ((n > 0) && (n < (int)sizeof(buf))) {
  491. buf[n] = (char)0;
  492. for (char* l = strtok_r(buf, "\r\n", &ptr); (l); l = strtok_r((char*)0, "\r\n", &ptr)) {
  493. int fno = 0;
  494. char* devname = (char*)0;
  495. char* mcastmac = (char*)0;
  496. for (char* f = strtok_r(l, " \t", &ptr2); (f); f = strtok_r((char*)0, " \t", &ptr2)) {
  497. if (fno == 1) {
  498. devname = f;
  499. }
  500. else if (fno == 4) {
  501. mcastmac = f;
  502. }
  503. ++fno;
  504. }
  505. if ((devname) && (! strcmp(devname, _dev.c_str())) && (mcastmac) && (Utils::unhex(mcastmac, mac, 6) == 6)) {
  506. newGroups.push_back(MulticastGroup(MAC(mac, 6), 0));
  507. }
  508. }
  509. }
  510. ::close(fd);
  511. }
  512. std::vector<InetAddress> allIps(ips());
  513. for (std::vector<InetAddress>::iterator ip(allIps.begin()); ip != allIps.end(); ++ip) {
  514. newGroups.push_back(MulticastGroup::deriveMulticastGroupForAddressResolution(*ip));
  515. }
  516. std::sort(newGroups.begin(), newGroups.end());
  517. newGroups.erase(std::unique(newGroups.begin(), newGroups.end()), newGroups.end());
  518. for (std::vector<MulticastGroup>::iterator m(newGroups.begin()); m != newGroups.end(); ++m) {
  519. if (! std::binary_search(_multicastGroups.begin(), _multicastGroups.end(), *m)) {
  520. added.push_back(*m);
  521. }
  522. }
  523. for (std::vector<MulticastGroup>::iterator m(_multicastGroups.begin()); m != _multicastGroups.end(); ++m) {
  524. if (! std::binary_search(newGroups.begin(), newGroups.end(), *m)) {
  525. removed.push_back(*m);
  526. }
  527. }
  528. _multicastGroups.swap(newGroups);
  529. }
  530. void ExtOsdepTap::setMtu(unsigned int mtu)
  531. {
  532. if (mtu == _mtu) {
  533. return;
  534. }
  535. _mtu = mtu;
  536. zt_eod_msg_setmtu req;
  537. req.cmd = ZT_EOD_MSG_SETMTU;
  538. strcpy(req.name, _dev.c_str());
  539. req.mtu = mtu;
  540. unsigned char resp;
  541. eodXchg(req, ZT_EOD_MSG_SETMTURESP, resp);
  542. }
  543. } // namespace ZeroTier
  544. #endif // ZT_EXTOSDEP