luci.upnp 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. // Copyright 2022 Jo-Philipp Wich <jo@mein.io>
  2. // Licensed to the public under the Apache License 2.0.
  3. 'use strict';
  4. import { access, open, popen } from 'fs';
  5. import { connect } from 'ubus';
  6. import { cursor } from 'uci';
  7. // Establish ubus connection persistently outside of the call handler scope to
  8. // prevent premature GC'ing. Can be moved into `get_status` callback once
  9. // https://github.com/jow-/ucode/commit/a58fe4709f661b5f28e26701ea8638efccf5aeb6
  10. // is merged.
  11. const ubus = connect();
  12. const methods = {
  13. get_status: {
  14. call: function(req) {
  15. const uci = cursor();
  16. const rules = [];
  17. const leases = [];
  18. const leasefile = open(uci.get('upnpd', 'config', 'upnp_lease_file'), 'r');
  19. if (leasefile) {
  20. for (let line = leasefile.read('line'); length(line); line = leasefile.read('line')) {
  21. const record = split(line, ':', 6);
  22. if (length(record) == 6) {
  23. push(leases, {
  24. proto: uc(record[0]),
  25. extport: +record[1],
  26. intaddr: arrtoip(iptoarr(record[2])),
  27. intport: +record[3],
  28. expiry: +record[4],
  29. description: trim(record[5])
  30. });
  31. }
  32. }
  33. leasefile.close();
  34. }
  35. const ipt = popen('iptables --line-numbers -t nat -xnvL MINIUPNPD 2>/dev/null');
  36. if (ipt) {
  37. for (let line = ipt.read('line'); length(line); line = ipt.read('line')) {
  38. let m = match(line, /^([0-9]+)\s+([a-z]+).+dpt:([0-9]+) to:(\S+):([0-9]+)/);
  39. if (m) {
  40. push(rules, {
  41. num: m[1],
  42. proto: uc(m[2]),
  43. extport: +m[3],
  44. intaddr: arrtoip(iptoarr(m[4])),
  45. intport: +m[5],
  46. descr: ''
  47. });
  48. }
  49. }
  50. ipt.close();
  51. }
  52. const nft = popen('nft --handle list chain inet fw4 upnp_prerouting 2>/dev/null');
  53. if (nft) {
  54. for (let line = nft.read('line'), num = 1; length(line); line = nft.read('line')) {
  55. let m = match(line, /^\t\tiif ".+" @nh,72,8 (0x6|0x11) th dport ([0-9]+) dnat ip to ([0-9.]+):([0-9]+)/);
  56. if (m) {
  57. push(rules, {
  58. num: `${num}`,
  59. proto: (m[1] == '0x6') ? 'TCP' : 'UDP',
  60. extport: +m[2],
  61. intaddr: arrtoip(iptoarr(m[3])),
  62. intport: +m[4],
  63. descr: ''
  64. });
  65. num++;
  66. }
  67. }
  68. nft.close();
  69. }
  70. return ubus.defer('luci-rpc', 'getHostHints', {}, function(rc, host_hints) {
  71. for (let rule in rules) {
  72. for (let lease in leases) {
  73. if (lease.proto == rule.proto &&
  74. lease.intaddr == rule.intaddr &&
  75. lease.intport == rule.intport &&
  76. lease.extport == rule.extport)
  77. {
  78. rule.descr = lease.description;
  79. break;
  80. }
  81. }
  82. for (let mac, hint in host_hints) {
  83. if (rule.intaddr in hint.ipaddrs) {
  84. rule.host_hint = hint.name;
  85. break;
  86. }
  87. }
  88. }
  89. req.reply({ rules });
  90. });
  91. }
  92. },
  93. delete_rule: {
  94. args: { token: 'token' },
  95. call: function(req) {
  96. const idx = +req.args?.token;
  97. if (idx > 0) {
  98. const uci = cursor();
  99. const leasefile = uci.get('upnpd', 'config', 'upnp_lease_file');
  100. if (access(leasefile)) {
  101. system(['sed', '-i', '-e', `${idx}d`, leasefile]);
  102. system(['/etc/init.d/miniupnpd', 'restart']);
  103. }
  104. return { result: 'OK' };
  105. }
  106. return { result: 'Bad request' };
  107. }
  108. }
  109. };
  110. return { 'luci.upnp': methods };