net_iptables.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. /**
  2. * @file net_iptables.c
  3. * @author Ambroz Bizjak <ambrop7@gmail.com>
  4. *
  5. * @section LICENSE
  6. *
  7. * This file is part of BadVPN.
  8. *
  9. * BadVPN is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License version 2
  11. * as published by the Free Software Foundation.
  12. *
  13. * BadVPN is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License along
  19. * with this program; if not, write to the Free Software Foundation, Inc.,
  20. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  21. *
  22. * @section DESCRIPTION
  23. *
  24. * iptables module.
  25. *
  26. * Note that all iptables commands (in general) must be issued synchronously, or
  27. * the kernel may randomly report errors if there is another iptables command in progress.
  28. * To solve this, the NCD process contains a single "iptables lock". All iptables commands
  29. * exposed here go through that lock.
  30. * In case you wish to call iptables directly, the lock is exposed via net.iptables.lock().
  31. *
  32. * Synopsis:
  33. * net.iptables.append(string table, string chain, string arg1 ...)
  34. * Description:
  35. * init: iptables -t table -A chain arg1 ...
  36. * deinit: iptables -t table -D chain arg1 ...
  37. *
  38. * Synopsis:
  39. * net.iptables.policy(string table, string chain, string target, string revert_target)
  40. * Description:
  41. * init: iptables -t table -P chain target
  42. * deinit: iptables -t table -P chain revert_target
  43. *
  44. * Synopsis:
  45. * net.iptables.newchain(string chain)
  46. * Description:
  47. * init: iptables -N chain
  48. * deinit: iptables -X chain
  49. *
  50. * Synopsis:
  51. * net.iptables.lock()
  52. * Description:
  53. * Use at the beginning of a block of custom iptables commands to make sure
  54. * they do not interfere with other iptables commands.
  55. * WARNING: improper usage of the lock can lead to deadlock. In particular:
  56. * - Do not call any of the iptables wrappers above from a lock section; those
  57. * will attempt to aquire the lock themselves.
  58. * - Do not enter another lock section from a lock section.
  59. * - Do not perform any potentially long wait from a lock section.
  60. *
  61. * Synopsis:
  62. * net.iptables.lock::unlock()
  63. * Description:
  64. * Use at the end of a block of custom iptables commands to make sure
  65. * they do not interfere with other iptables commands.
  66. */
  67. #include <stdlib.h>
  68. #include <string.h>
  69. #include <unistd.h>
  70. #include <misc/debug.h>
  71. #include <ncd/BEventLock.h>
  72. #include <ncd/modules/command_template.h>
  73. #include <generated/blog_channel_ncd_net_iptables.h>
  74. #define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
  75. #define IPTABLES_PATH "/sbin/iptables"
  76. #define IPTABLES_PATH2 "/usr/sbin/iptables"
  77. static void template_free_func (void *vo, int is_error);
  78. BEventLock iptables_lock;
  79. struct instance {
  80. NCDModuleInst *i;
  81. command_template_instance cti;
  82. };
  83. struct unlock_instance;
  84. #define LOCK_STATE_LOCKING 1
  85. #define LOCK_STATE_LOCKED 2
  86. #define LOCK_STATE_UNLOCKED 3
  87. #define LOCK_STATE_RELOCKING 4
  88. struct lock_instance {
  89. NCDModuleInst *i;
  90. BEventLockJob lock_job;
  91. struct unlock_instance *unlock;
  92. int state;
  93. };
  94. struct unlock_instance {
  95. NCDModuleInst *i;
  96. struct lock_instance *lock;
  97. };
  98. static void unlock_free (struct unlock_instance *o);
  99. static const char *find_iptables (NCDModuleInst *i)
  100. {
  101. if (access(IPTABLES_PATH, X_OK) == 0) {
  102. return IPTABLES_PATH;
  103. }
  104. if (access(IPTABLES_PATH2, X_OK) == 0) {
  105. return IPTABLES_PATH2;
  106. }
  107. ModuleLog(i, BLOG_ERROR, "failed to find iptables (tried "IPTABLES_PATH" and "IPTABLES_PATH2")");
  108. return NULL;
  109. }
  110. static int build_append_cmdline (NCDModuleInst *i, int remove, char **exec, CmdLine *cl)
  111. {
  112. // read arguments
  113. NCDValue *table_arg;
  114. NCDValue *chain_arg;
  115. if (!NCDValue_ListReadHead(i->args, 2, &table_arg, &chain_arg)) {
  116. ModuleLog(i, BLOG_ERROR, "wrong arity");
  117. goto fail0;
  118. }
  119. if (NCDValue_Type(table_arg) != NCDVALUE_STRING || NCDValue_Type(chain_arg) != NCDVALUE_STRING) {
  120. ModuleLog(i, BLOG_ERROR, "wrong type");
  121. goto fail0;
  122. }
  123. char *table = NCDValue_StringValue(table_arg);
  124. char *chain = NCDValue_StringValue(chain_arg);
  125. // find iptables
  126. const char *iptables_path = find_iptables(i);
  127. if (!iptables_path) {
  128. goto fail0;
  129. }
  130. // alloc exec
  131. if (!(*exec = strdup(iptables_path))) {
  132. ModuleLog(i, BLOG_ERROR, "strdup failed");
  133. goto fail0;
  134. }
  135. // start cmdline
  136. if (!CmdLine_Init(cl)) {
  137. ModuleLog(i, BLOG_ERROR, "CmdLine_Init failed");
  138. goto fail1;
  139. }
  140. // add header
  141. if (!CmdLine_Append(cl, iptables_path) || !CmdLine_Append(cl, "-t") || !CmdLine_Append(cl, table) || !CmdLine_Append(cl, (remove ? "-D" : "-A")) || !CmdLine_Append(cl, chain)) {
  142. ModuleLog(i, BLOG_ERROR, "CmdLine_Append failed");
  143. goto fail2;
  144. }
  145. // add additional arguments
  146. NCDValue *arg = NCDValue_ListNext(i->args, chain_arg);
  147. while (arg) {
  148. if (NCDValue_Type(arg) != NCDVALUE_STRING) {
  149. ModuleLog(i, BLOG_ERROR, "wrong type");
  150. goto fail2;
  151. }
  152. if (!CmdLine_Append(cl, NCDValue_StringValue(arg))) {
  153. ModuleLog(i, BLOG_ERROR, "CmdLine_Append failed");
  154. goto fail2;
  155. }
  156. arg = NCDValue_ListNext(i->args, arg);
  157. }
  158. // finish
  159. if (!CmdLine_Finish(cl)) {
  160. ModuleLog(i, BLOG_ERROR, "CmdLine_Finish failed");
  161. goto fail2;
  162. }
  163. return 1;
  164. fail2:
  165. CmdLine_Free(cl);
  166. fail1:
  167. free(*exec);
  168. fail0:
  169. return 0;
  170. }
  171. static int build_policy_cmdline (NCDModuleInst *i, int remove, char **exec, CmdLine *cl)
  172. {
  173. // read arguments
  174. NCDValue *table_arg;
  175. NCDValue *chain_arg;
  176. NCDValue *target_arg;
  177. NCDValue *revert_target_arg;
  178. if (!NCDValue_ListRead(i->args, 4, &table_arg, &chain_arg, &target_arg, &revert_target_arg)) {
  179. ModuleLog(i, BLOG_ERROR, "wrong arity");
  180. goto fail0;
  181. }
  182. if (NCDValue_Type(table_arg) != NCDVALUE_STRING || NCDValue_Type(chain_arg) != NCDVALUE_STRING ||
  183. NCDValue_Type(target_arg) != NCDVALUE_STRING || NCDValue_Type(revert_target_arg) != NCDVALUE_STRING
  184. ) {
  185. ModuleLog(i, BLOG_ERROR, "wrong type");
  186. goto fail0;
  187. }
  188. char *table = NCDValue_StringValue(table_arg);
  189. char *chain = NCDValue_StringValue(chain_arg);
  190. char *target = NCDValue_StringValue(target_arg);
  191. char *revert_target = NCDValue_StringValue(revert_target_arg);
  192. // find iptables
  193. const char *iptables_path = find_iptables(i);
  194. if (!iptables_path) {
  195. goto fail0;
  196. }
  197. // alloc exec
  198. if (!(*exec = strdup(iptables_path))) {
  199. ModuleLog(i, BLOG_ERROR, "strdup failed");
  200. goto fail0;
  201. }
  202. // start cmdline
  203. if (!CmdLine_Init(cl)) {
  204. ModuleLog(i, BLOG_ERROR, "CmdLine_Init failed");
  205. goto fail1;
  206. }
  207. // add arguments
  208. if (!CmdLine_Append(cl, iptables_path) || !CmdLine_Append(cl, "-t") || !CmdLine_Append(cl, table) ||
  209. !CmdLine_Append(cl, "-P") || !CmdLine_Append(cl, chain) || !CmdLine_Append(cl, (remove ? revert_target : target))) {
  210. ModuleLog(i, BLOG_ERROR, "CmdLine_Append failed");
  211. goto fail2;
  212. }
  213. // finish
  214. if (!CmdLine_Finish(cl)) {
  215. ModuleLog(i, BLOG_ERROR, "CmdLine_Finish failed");
  216. goto fail2;
  217. }
  218. return 1;
  219. fail2:
  220. CmdLine_Free(cl);
  221. fail1:
  222. free(*exec);
  223. fail0:
  224. return 0;
  225. }
  226. static int build_newchain_cmdline (NCDModuleInst *i, int remove, char **exec, CmdLine *cl)
  227. {
  228. // read arguments
  229. NCDValue *chain_arg;
  230. if (!NCDValue_ListRead(i->args, 1, &chain_arg)) {
  231. ModuleLog(i, BLOG_ERROR, "wrong arity");
  232. goto fail0;
  233. }
  234. if (NCDValue_Type(chain_arg) != NCDVALUE_STRING) {
  235. ModuleLog(i, BLOG_ERROR, "wrong type");
  236. goto fail0;
  237. }
  238. char *chain = NCDValue_StringValue(chain_arg);
  239. // find iptables
  240. const char *iptables_path = find_iptables(i);
  241. if (!iptables_path) {
  242. goto fail0;
  243. }
  244. // alloc exec
  245. if (!(*exec = strdup(iptables_path))) {
  246. ModuleLog(i, BLOG_ERROR, "strdup failed");
  247. goto fail0;
  248. }
  249. // start cmdline
  250. if (!CmdLine_Init(cl)) {
  251. ModuleLog(i, BLOG_ERROR, "CmdLine_Init failed");
  252. goto fail1;
  253. }
  254. // add arguments
  255. if (!CmdLine_AppendMulti(cl, 3, iptables_path, (remove ? "-X" : "-N"), chain)) {
  256. ModuleLog(i, BLOG_ERROR, "CmdLine_AppendMulti failed");
  257. goto fail2;
  258. }
  259. // finish
  260. if (!CmdLine_Finish(cl)) {
  261. ModuleLog(i, BLOG_ERROR, "CmdLine_Finish failed");
  262. goto fail2;
  263. }
  264. return 1;
  265. fail2:
  266. CmdLine_Free(cl);
  267. fail1:
  268. free(*exec);
  269. fail0:
  270. return 0;
  271. }
  272. static void lock_job_handler (struct lock_instance *o)
  273. {
  274. ASSERT(o->state == LOCK_STATE_LOCKING || o->state == LOCK_STATE_RELOCKING)
  275. if (o->state == LOCK_STATE_LOCKING) {
  276. ASSERT(!o->unlock)
  277. // up
  278. NCDModuleInst_Backend_Up(o->i);
  279. // set state locked
  280. o->state = LOCK_STATE_LOCKED;
  281. }
  282. else if (o->state == LOCK_STATE_RELOCKING) {
  283. ASSERT(o->unlock)
  284. ASSERT(o->unlock->lock == o)
  285. // die unlock
  286. unlock_free(o->unlock);
  287. o->unlock = NULL;
  288. // set state locked
  289. o->state = LOCK_STATE_LOCKED;
  290. }
  291. }
  292. static int func_globalinit (struct NCDModuleInitParams params)
  293. {
  294. // init iptables lock
  295. BEventLock_Init(&iptables_lock, BReactor_PendingGroup(params.reactor));
  296. return 1;
  297. }
  298. static void func_globalfree (void)
  299. {
  300. // free iptables lock
  301. BEventLock_Free(&iptables_lock);
  302. }
  303. static void func_new (NCDModuleInst *i, command_template_build_cmdline build_cmdline)
  304. {
  305. // allocate instance
  306. struct instance *o = malloc(sizeof(*o));
  307. if (!o) {
  308. BLog(BLOG_ERROR, "malloc failed");
  309. goto fail0;
  310. }
  311. NCDModuleInst_Backend_SetUser(i, o);
  312. // init arguments
  313. o->i = i;
  314. command_template_new(&o->cti, i, build_cmdline, template_free_func, o, BLOG_CURRENT_CHANNEL, &iptables_lock);
  315. return;
  316. fail0:
  317. NCDModuleInst_Backend_SetError(i);
  318. NCDModuleInst_Backend_Dead(i);
  319. }
  320. void template_free_func (void *vo, int is_error)
  321. {
  322. struct instance *o = vo;
  323. NCDModuleInst *i = o->i;
  324. // free instance
  325. free(o);
  326. if (is_error) {
  327. NCDModuleInst_Backend_SetError(i);
  328. }
  329. NCDModuleInst_Backend_Dead(i);
  330. }
  331. static void append_func_new (NCDModuleInst *i)
  332. {
  333. func_new(i, build_append_cmdline);
  334. }
  335. static void policy_func_new (NCDModuleInst *i)
  336. {
  337. func_new(i, build_policy_cmdline);
  338. }
  339. static void newchain_func_new (NCDModuleInst *i)
  340. {
  341. func_new(i, build_newchain_cmdline);
  342. }
  343. static void func_die (void *vo)
  344. {
  345. struct instance *o = vo;
  346. command_template_die(&o->cti);
  347. }
  348. static void lock_func_new (NCDModuleInst *i)
  349. {
  350. // allocate instance
  351. struct lock_instance *o = malloc(sizeof(*o));
  352. if (!o) {
  353. BLog(BLOG_ERROR, "malloc failed");
  354. goto fail0;
  355. }
  356. NCDModuleInst_Backend_SetUser(i, o);
  357. // init arguments
  358. o->i = i;
  359. // init lock job
  360. BEventLockJob_Init(&o->lock_job, &iptables_lock, (BEventLock_handler)lock_job_handler, o);
  361. BEventLockJob_Wait(&o->lock_job);
  362. // set no unlock
  363. o->unlock = NULL;
  364. // set state locking
  365. o->state = LOCK_STATE_LOCKING;
  366. return;
  367. fail0:
  368. NCDModuleInst_Backend_SetError(i);
  369. NCDModuleInst_Backend_Dead(i);
  370. }
  371. static void lock_func_die (void *vo)
  372. {
  373. struct lock_instance *o = vo;
  374. NCDModuleInst *i = o->i;
  375. if (o->state == LOCK_STATE_UNLOCKED) {
  376. ASSERT(o->unlock)
  377. ASSERT(o->unlock->lock == o)
  378. o->unlock->lock = NULL;
  379. }
  380. else if (o->state == LOCK_STATE_RELOCKING) {
  381. ASSERT(o->unlock)
  382. ASSERT(o->unlock->lock == o)
  383. unlock_free(o->unlock);
  384. }
  385. else {
  386. ASSERT(!o->unlock)
  387. }
  388. // free lock job
  389. BEventLockJob_Free(&o->lock_job);
  390. // free instance
  391. free(o);
  392. // dead
  393. NCDModuleInst_Backend_Dead(i);
  394. }
  395. static void unlock_func_new (NCDModuleInst *i)
  396. {
  397. // allocate instance
  398. struct unlock_instance *o = malloc(sizeof(*o));
  399. if (!o) {
  400. BLog(BLOG_ERROR, "malloc failed");
  401. goto fail0;
  402. }
  403. NCDModuleInst_Backend_SetUser(i, o);
  404. // init arguments
  405. o->i = i;
  406. // get lock lock
  407. struct lock_instance *lock = i->method_object->inst_user;
  408. // make sure lock doesn't already have an unlock
  409. if (lock->unlock) {
  410. BLog(BLOG_ERROR, "lock already has an unlock");
  411. goto fail1;
  412. }
  413. ASSERT(lock->state == LOCK_STATE_LOCKED)
  414. // set lock
  415. o->lock = lock;
  416. // set unlock in lock
  417. lock->unlock = o;
  418. // up
  419. NCDModuleInst_Backend_Up(o->i);
  420. // release lock
  421. BEventLockJob_Release(&lock->lock_job);
  422. // set lock state unlocked
  423. lock->state = LOCK_STATE_UNLOCKED;
  424. return;
  425. fail1:
  426. free(o);
  427. fail0:
  428. NCDModuleInst_Backend_SetError(i);
  429. NCDModuleInst_Backend_Dead(i);
  430. }
  431. static void unlock_func_die (void *vo)
  432. {
  433. struct unlock_instance *o = vo;
  434. NCDModuleInst *i = o->i;
  435. // if lock is gone, die right away
  436. if (!o->lock) {
  437. unlock_free(o);
  438. return;
  439. }
  440. ASSERT(o->lock->unlock == o)
  441. ASSERT(o->lock->state == LOCK_STATE_UNLOCKED)
  442. // wait lock
  443. BEventLockJob_Wait(&o->lock->lock_job);
  444. // set lock state relocking
  445. o->lock->state = LOCK_STATE_RELOCKING;
  446. }
  447. static void unlock_free (struct unlock_instance *o)
  448. {
  449. NCDModuleInst *i = o->i;
  450. // free instance
  451. free(o);
  452. NCDModuleInst_Backend_Dead(i);
  453. }
  454. static const struct NCDModule modules[] = {
  455. {
  456. .type = "net.iptables.append",
  457. .func_new = append_func_new,
  458. .func_die = func_die
  459. }, {
  460. .type = "net.iptables.policy",
  461. .func_new = policy_func_new,
  462. .func_die = func_die
  463. }, {
  464. .type = "net.iptables.newchain",
  465. .func_new = newchain_func_new,
  466. .func_die = func_die
  467. }, {
  468. .type = "net.iptables.lock",
  469. .func_new = lock_func_new,
  470. .func_die = lock_func_die
  471. }, {
  472. .type = "net.iptables.lock::unlock",
  473. .func_new = unlock_func_new,
  474. .func_die = unlock_func_die
  475. }, {
  476. .type = NULL
  477. }
  478. };
  479. const struct NCDModuleGroup ncdmodule_net_iptables = {
  480. .modules = modules,
  481. .func_globalinit = func_globalinit,
  482. .func_globalfree = func_globalfree
  483. };