ExtOsdep.cpp 15 KB

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