PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/os_captive_portals/linux_captive_portal.rb

https://github.com/idemarinis/OpenWISP-Captive-Portals-Manager
Ruby | 633 lines | 425 code | 116 blank | 92 comment | 39 complexity | ce0bed0f6bcd85163c18a59f2f5e9ce7 MD5 | raw file
  1. # This file is part of the OpenWISP Captive Portal Manager
  2. #
  3. # Copyright (C) 2012 OpenWISP.org
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. module OsUtils
  18. IP = "/sbin/ip"
  19. TC = "/sbin/tc"
  20. public
  21. # Test to see if a string contains a valid linux interface name
  22. def is_interface_name?(interface_name)
  23. (interface_name =~ /\A[a-z_][a-z0-9_\.\-]*\Z/i) != nil
  24. end
  25. # Returns the client mac address associated with the passed ipv4/v6 address
  26. def get_host_mac_address(address)
  27. mac = nil
  28. if IPAddr.new(address).ipv4? or IPAddr.new(address).ipv6?
  29. res = /lladdr\s+(([0-9a-fA-F]{1,2}:){5}[0-9a-fA-F]{1,2})\s+/.match(%x[#{IP} neighbor show #{address}])
  30. mac = res[1] unless res.nil?
  31. end
  32. mac
  33. end
  34. # Returns the interface that will be used to reach the passed ip address
  35. def get_interface(address)
  36. interface = nil
  37. if IPAddr.new(address).ipv4? or IPAddr.new(address).ipv6?
  38. res = /dev\s+([a-zA-Z_][a-zA-Z0-9_\.\-]*)\s+src/.match(%x[#{IP} route get #{address}])
  39. interface = res[1] unless res.nil?
  40. end
  41. interface
  42. end
  43. # Returns the first ipv4 address assigned to the passed interface name
  44. def get_interface_ipv4_address(interface)
  45. ipv4_address = nil
  46. if is_interface_name?(interface)
  47. res = /\s+inet\s+([0-9a-fA-F:\.]+)\/\d+\s+/.match(%x[#{IP} -f inet addr show #{interface} | grep "scope global" | head -1])
  48. ipv4_address = res[1] unless res.nil?
  49. end
  50. ipv4_address
  51. end
  52. # Returns the first non-link-local ipv6 address assigned to the passed interface name
  53. def get_interface_ipv6_address(interface)
  54. ipv6_address = nil
  55. if is_interface_name?(interface)
  56. res = /inet6\s+([0-9a-fA-F:]+)\/\d+\s+scope/.match(%x[#{IP} -f inet6 addr show #{interface} | grep "scope global | head -1"])
  57. ipv6_address = res[1] unless res.nil?
  58. end
  59. ipv6_address
  60. end
  61. end
  62. module IpTablesUtils
  63. IPTABLES = "/sbin/iptables"
  64. def execute_actions(actions, options = {})
  65. options[:blind] ||= false
  66. actions.each do |action|
  67. action << " >/dev/null 2>&1" if options[:blind]
  68. unless system(action)
  69. raise "#{caller[2]} - problem executing action: '#{action}'" unless options[:blind]
  70. end
  71. end
  72. end
  73. end
  74. class OsCaptivePortal
  75. include OsUtils
  76. include IpTablesUtils
  77. public
  78. MARK = 0x10000
  79. MARK_MASK = 0x10000
  80. MARK_MAX = 0xFFFC
  81. TC_CLASS_MAX = 0xFFFE
  82. @@client_marks = Array.new(MARK_MAX)
  83. @@tc_classes = Array.new(TC_CLASS_MAX)
  84. def self.create_mark_for_client(mac)
  85. # This function calculates a number used for both classid and iptables mark target/match
  86. # 0, 1, 2 are reserved:
  87. # MARK (0x10000) is used to mark the unclassified traffic
  88. # x:0 is the root tc handle
  89. # x:1 is the first htb class id
  90. # x:2 is default htb class id
  91. if idx = @@client_marks.index(mac)
  92. return idx + 3
  93. end
  94. if idx = @@client_marks.index(nil)
  95. @@client_marks[idx] = mac
  96. return idx + 3
  97. else
  98. return nil
  99. end
  100. end
  101. def self.remove_mark_for_client(mac)
  102. if idx = @@client_marks.index(mac)
  103. @@client_marks[idx] = nil
  104. return idx + 3
  105. else
  106. return nil
  107. end
  108. end
  109. def self.create_tc_class_for_cp(cp_interface)
  110. if idx = @@tc_classes.index(cp_interface)
  111. return idx + 1
  112. end
  113. if idx = @@tc_classes.index(nil)
  114. @@tc_classes[idx] = cp_interface
  115. return idx + 1
  116. else
  117. return nil
  118. end
  119. end
  120. def self.get_tc_class_for_cp(cp_interface)
  121. if idx = @@tc_classes.index(cp_interface)
  122. return idx + 1
  123. else
  124. return nil
  125. end
  126. end
  127. def self.remove_tc_class_for_cp(cp_interface)
  128. idx = self.get_tc_class_for_cp(cp_interface)
  129. unless idx.nil?
  130. @@tc_classes[idx - 1] = nil
  131. return idx
  132. else
  133. return nil
  134. end
  135. end
  136. # Adds a new captive portal
  137. def start
  138. #TO DO: ip6tables rules!
  139. cp_ip = get_interface_ipv4_address(@cp_interface)
  140. firewall_create_actions = [
  141. # creating_cp_chains
  142. "#{IPTABLES} -t nat -N '_REDIR_#{@cp_interface}'",
  143. "#{IPTABLES} -t nat -N '_DNAT_#{@cp_interface}'",
  144. "#{IPTABLES} -t filter -N '_FINP_#{@cp_interface}'",
  145. "#{IPTABLES} -t filter -N '_FOUT_#{@cp_interface}'",
  146. "#{IPTABLES} -t mangle -N '_XCPT_IN_#{@cp_interface}'",
  147. "#{IPTABLES} -t mangle -N '_XCPT_OUT_#{@cp_interface}'",
  148. "#{IPTABLES} -t mangle -N '_AUTH_IN_#{@cp_interface}'",
  149. "#{IPTABLES} -t mangle -N '_AUTH_OUT_#{@cp_interface}'",
  150. # creating_http_redirections
  151. "#{IPTABLES} -t nat -A '_REDIR_#{@cp_interface}' -p tcp --dport 80 -j DNAT --to-destination '#{cp_ip}:#{@local_http_port}'",
  152. # "#{IPTABLES} -t nat -A '_REDIR_#{@cp_interface}' -p tcp --dport 443 -j DNAT --to-destination '#{cp_ip}:#{@local_https_port}'",
  153. # creating_dns_redirections
  154. "#{IPTABLES} -t nat -A '_DNAT_#{@cp_interface}' -p udp --dport 53 -j DNAT --to-destination '#{cp_ip}:#{DNS_PORT}'",
  155. "#{IPTABLES} -t nat -A '_DNAT_#{@cp_interface}' -p tcp --dport 53 -j DNAT --to-destination '#{cp_ip}:#{DNS_PORT}'",
  156. # creating_redirection_rules
  157. "#{IPTABLES} -t nat -A _PRER_NAT -i '#{@cp_interface}' -j '_DNAT_#{@cp_interface}'",
  158. # creating_auth_users_rules
  159. "#{IPTABLES} -t mangle -A _PRER_MAN -i '#{@cp_interface}' -j '_AUTH_IN_#{@cp_interface}'",
  160. "#{IPTABLES} -t mangle -A _POSR_MAN -o '#{@cp_interface}' -j '_AUTH_OUT_#{@cp_interface}'",
  161. # creating_exceptions_rules
  162. "#{IPTABLES} -t mangle -A _PRER_MAN -i '#{@cp_interface}' -j '_XCPT_IN_#{@cp_interface}'",
  163. "#{IPTABLES} -t mangle -A _POSR_MAN -o '#{@cp_interface}' -j '_XCPT_OUT_#{@cp_interface}'",
  164. # creating_main_filtering
  165. "#{IPTABLES} -t filter -A _FORW_FIL -i '#{@cp_interface}' -o '#{@wan_interface}' -m mark --mark '#{MARK}/#{MARK_MASK}' -j RETURN",
  166. "#{IPTABLES} -t filter -A _FORW_FIL -i '#{@cp_interface}' -j DROP",
  167. # creating_redirection_skip_rules
  168. "#{IPTABLES} -t nat -A _PRER_NAT -i '#{@cp_interface}' -m mark --mark '#{MARK}/#{MARK_MASK}' -j RETURN",
  169. "#{IPTABLES} -t nat -A _PRER_NAT -i '#{@cp_interface}' -j '_REDIR_#{@cp_interface}'",
  170. # creating_filtering
  171. "#{IPTABLES} -t filter -A _INPU_FIL -i '#{@cp_interface}' -j '_FINP_#{@cp_interface}'",
  172. "#{IPTABLES} -t filter -A _OUTP_FIL -o '#{@cp_interface}' -j '_FOUT_#{@cp_interface}'",
  173. # basic_service_filtering_rules
  174. "#{IPTABLES} -t filter -A '_FINP_#{@cp_interface}' -p tcp --dport #{@local_http_port} -m state --state NEW,ESTABLISHED -j ACCEPT",
  175. "#{IPTABLES} -t filter -A '_FINP_#{@cp_interface}' -p tcp --dport #{@local_https_port} -m state --state NEW,ESTABLISHED -j ACCEPT",
  176. "#{IPTABLES} -t filter -A '_FINP_#{@cp_interface}' -p tcp --dport #{DNS_PORT} -m state --state NEW,ESTABLISHED -j ACCEPT",
  177. "#{IPTABLES} -t filter -A '_FINP_#{@cp_interface}' -p udp --dport #{DNS_PORT} -m state --state NEW,ESTABLISHED -j ACCEPT",
  178. "#{IPTABLES} -t filter -A '_FINP_#{@cp_interface}' -p udp --sport #{DHCP_SRC_PORT} --dport #{DHCP_DST_PORT} -j ACCEPT",
  179. "#{IPTABLES} -t filter -A '_FOUT_#{@cp_interface}' -p udp --dport #{DHCP_SRC_PORT} --sport #{DHCP_DST_PORT} -j ACCEPT",
  180. "#{IPTABLES} -t filter -A '_FOUT_#{@cp_interface}' -m state --state ESTABLISHED -j ACCEPT",
  181. "#{IPTABLES} -t filter -A '_FOUT_#{@cp_interface}' -j DROP",
  182. # user_defined_nat_rules
  183. "#{IPTABLES} -t nat -A _POSR_NAT -i '#{@cp_interface}' -j RETURN"
  184. ]
  185. execute_actions(firewall_create_actions)
  186. unless @total_upload_bandwidth.blank?
  187. shaping_down_create_actions = [
  188. # root handle and class for clients upload
  189. "#{TC} qdisc add dev '#{@cp_interface}' root handle 1: htb",
  190. "#{TC} class add dev '#{@cp_interface}' parent 1 classid 1:1 htb rate #{@total_download_bandwidth}kbit ceil #{@total_download_bandwidth}kbit",
  191. ]
  192. execute_actions(shaping_down_create_actions)
  193. end
  194. unless @total_download_bandwidth.blank?
  195. shaping_up_create_root_action = [
  196. "#{TC} qdisc add dev '#{@wan_interface}' root handle 1: htb",
  197. ]
  198. tc_class = OsCaptivePortal::create_tc_class_for_cp(@cp_interface) ||
  199. raise("FATAL: cannot add captive portal for '#{@cp_interface}'. Limit reached?")
  200. shaping_up_create_actions = [
  201. # root handle and class for clients download
  202. "#{TC} class add dev '#{@wan_interface}' parent 1 classid #{tc_class}:1 htb rate #{@total_upload_bandwidth}kbit ceil #{@total_upload_bandwidth}kbit",
  203. ]
  204. execute_actions(shaping_up_create_root_action, :blind => true)
  205. execute_actions(shaping_up_create_actions)
  206. end
  207. end
  208. # Removes a captive portal
  209. # cp_interface is the name of the interface directly connected the clients
  210. def stop
  211. #TO DO: ip6tables rules!
  212. firewall_destroy_actions = [
  213. # flushing_chains
  214. "#{IPTABLES} -t mangle -F '_XCPT_IN_#{@cp_interface}'",
  215. "#{IPTABLES} -t mangle -F '_XCPT_OUT_#{@cp_interface}'",
  216. "#{IPTABLES} -t mangle -F '_AUTH_IN_#{@cp_interface}'",
  217. "#{IPTABLES} -t mangle -F '_AUTH_OUT_#{@cp_interface}'",
  218. "#{IPTABLES} -t nat -F '_REDIR_#{@cp_interface}'",
  219. "#{IPTABLES} -t nat -F '_DNAT_#{@cp_interface}'",
  220. "#{IPTABLES} -t filter -F '_FINP_#{@cp_interface}'",
  221. "#{IPTABLES} -t filter -F '_FOUT_#{@cp_interface}'",
  222. # deleting_rules
  223. "#{IPTABLES} -t nat -D _PRER_NAT -i '#{@cp_interface}' -j '_DNAT_#{@cp_interface}'",
  224. "#{IPTABLES} -t nat -D _PRER_NAT -i '#{@cp_interface}' -m mark --mark '#{MARK}/#{MARK_MASK}' -j RETURN",
  225. "#{IPTABLES} -t nat -D _PRER_NAT -i '#{@cp_interface}' -j '_REDIR_#{@cp_interface}'",
  226. "#{IPTABLES} -t nat -D _POSR_NAT -i '#{@cp_interface}' -j RETURN",
  227. "#{IPTABLES} -t filter -D _INPU_FIL -i '#{@cp_interface}' -j '_FINP_#{@cp_interface}'",
  228. "#{IPTABLES} -t filter -D _OUTP_FIL -o '#{@cp_interface}' -j '_FOUT_#{@cp_interface}'",
  229. "#{IPTABLES} -t filter -D _FORW_FIL -i '#{@cp_interface}' -o '#{@wan_interface}' -m mark --mark '#{MARK}/#{MARK_MASK}' -j RETURN",
  230. "#{IPTABLES} -t filter -D _FORW_FIL -i '#{@cp_interface}' -j DROP",
  231. "#{IPTABLES} -t mangle -D _PRER_MAN -i '#{@cp_interface}' -j '_AUTH_IN_#{@cp_interface}'",
  232. "#{IPTABLES} -t mangle -D _POSR_MAN -o '#{@cp_interface}' -j '_AUTH_OUT_#{@cp_interface}'",
  233. "#{IPTABLES} -t mangle -D _PRER_MAN -i '#{@cp_interface}' -j '_XCPT_IN_#{@cp_interface}'",
  234. "#{IPTABLES} -t mangle -D _POSR_MAN -o '#{@cp_interface}' -j '_XCPT_OUT_#{@cp_interface}'",
  235. # destroying_chains
  236. "#{IPTABLES} -t mangle -X '_XCPT_IN_#{@cp_interface}'",
  237. "#{IPTABLES} -t mangle -X '_XCPT_OUT_#{@cp_interface}'",
  238. "#{IPTABLES} -t mangle -X '_AUTH_IN_#{@cp_interface}'",
  239. "#{IPTABLES} -t mangle -X '_AUTH_OUT_#{@cp_interface}'",
  240. "#{IPTABLES} -t nat -X '_REDIR_#{@cp_interface}'",
  241. "#{IPTABLES} -t nat -X '_DNAT_#{@cp_interface}'",
  242. "#{IPTABLES} -t filter -X '_FINP_#{@cp_interface}'",
  243. "#{IPTABLES} -t filter -X '_FOUT_#{@cp_interface}'"
  244. ]
  245. execute_actions(firewall_destroy_actions)
  246. unless @total_upload_bandwidth.blank?
  247. tc_class = OsCaptivePortal::remove_tc_class_for_cp(@cp_interface)
  248. shaping_upload_destroy_actions = [
  249. # root handle and class for clients upload
  250. "#{TC} class del dev '#{@wan_interface}' parent 1 classid #{tc_class}:1 htb rate #{@total_upload_bandwidth}kbit ceil #{@total_upload_bandwidth}kbit",
  251. # "#{TC} qdisc del dev '#{@wan_interface}' root handle 1: htb",
  252. ]
  253. execute_actions(shaping_upload_destroy_actions)
  254. end
  255. unless @total_download_bandwidth.blank?
  256. shaping_down_destroy_actions = [
  257. # root handle and class for clients download
  258. "#{TC} qdisc del dev '#{@cp_interface}' root handle 1: htb",
  259. ]
  260. execute_actions(shaping_down_destroy_actions)
  261. end
  262. end
  263. # Creates/Removes an iptables rule parameters for allowed traffic
  264. def add_remove_allowed_traffic(action = :add, options = {})
  265. # Determine source host type (if any)
  266. if options[:source_host].blank?
  267. source_host_type = nil
  268. else
  269. begin
  270. source_host_type = IPAddr.new(options[:source_host]).is_ipv4? ? :ipv4 : :ipv6
  271. rescue
  272. # If the previous fails with an exception, assume source_host is an hostname
  273. source_host_type = :hostname
  274. end
  275. end
  276. # Determine destination host type (if any)
  277. if options[:destination_host].blank?
  278. destination_host_type = nil
  279. else
  280. begin
  281. destination_host_type = IPAddr.new(options[:destination_host]).is_ipv4? ? :ipv4 : :ipv6
  282. rescue
  283. # If the previous fails with an exception, assume destination_host is an hostname
  284. destination_host_type = :hostname
  285. end
  286. end
  287. if !source_host_type.nil? and !destination_host_type.nil? and
  288. source_host_type != :hostname and destination_host_type != :hostname and
  289. source_host_type != destination_host_type
  290. raise("BUG: source and destination host must belong to the same family (#{options[:source_host]} is " +
  291. "'#{source_host_type}' and #{options[:destination_host]} is '#{destination_host_type}')")
  292. end
  293. if !(source_host_type == :ipv6 or destination_host_type == :ipv6)
  294. # IPv4 rule
  295. ipv4_exception_rule = "#{IPTABLES} -t mangle " + (action == :add ? "-A" : "-D") + " '_XCPT_IN_#{@cp_interface}' -i '#{@cp_interface}'"
  296. ipv4_exception_rule += " -m mac --mac-source '#{options[:source_mac]}'" unless options[:source_mac].blank?
  297. ipv4_exception_rule += " -s #{options[:source_host]}" unless options[:source_host].blank?
  298. ipv4_exception_rule += " -d #{options[:destination_host]}" unless options[:destination_host].blank?
  299. ipv4_exception_rule += " -p #{options[:protocol]}" unless options[:protocol].blank?
  300. ipv4_exception_rule += " --sport #{options[:source_port]}" unless options[:source_port].blank? or options[:protocol].blank?
  301. ipv4_exception_rule += " --dport #{options[:destination_port]}" unless options[:destination_port].blank? or options[:protocol].blank?
  302. ipv4_exception_rule += " -j MARK --set-mark '#{MARK}'"
  303. execute_actions([ipv4_exception_rule])
  304. end
  305. if !(source_host_type == :ipv4 and destination_host_type == :ipv4)
  306. # TO DO: IPv6 rule
  307. not_implemented
  308. end
  309. end
  310. # Add an iptables rule for allowed traffic
  311. def add_allowed_traffic(options = {})
  312. add_remove_allowed_traffic(:add, options)
  313. end
  314. # Removes an iptables rule for allowed traffic
  315. def remove_allowed_traffic(options = {})
  316. add_remove_allowed_traffic(:remove, options)
  317. end
  318. # Allows a client through the captive portal
  319. def add_user(client_address, client_mac_address, options = {})
  320. raise("BUG: Invalid mac address '#{client_mac_address}'") unless is_mac_address?(client_mac_address)
  321. upload_bandwidth = options[:max_upload_bandwidth] || @default_upload_bandwidth
  322. download_bandwidth = options[:max_download_bandwidth] || @default_download_bandwidth
  323. firewall_paranoid_remove_user_actions = []
  324. firewall_add_user_actions = []
  325. mark = OsCaptivePortal::create_mark_for_client(client_mac_address) || raise("FATAL: cannot add user with mac '#{client_mac_address}'. Users limit reached?")
  326. if is_ipv4_address?(client_address)
  327. firewall_paranoid_remove_user_actions = [
  328. # paranoid_rules
  329. "#{IPTABLES} -t mangle -D '_AUTH_IN_#{@cp_interface}' -s '#{client_address}' -m mac --mac-source '#{client_mac_address}' -j MARK --set-mark '#{mark + MARK}'",
  330. "#{IPTABLES} -t mangle -D '_AUTH_OUT_#{@cp_interface}' -d '#{client_address}' -j MARK --set-mark '#{mark + MARK}'",
  331. ]
  332. firewall_add_user_actions = [
  333. # adding_user_marking_rule
  334. "#{IPTABLES} -t mangle -A '_AUTH_IN_#{@cp_interface}' -s '#{client_address}' -m mac --mac-source '#{client_mac_address}' -j MARK --set-mark '#{mark + MARK}'",
  335. "#{IPTABLES} -t mangle -A '_AUTH_OUT_#{@cp_interface}' -d '#{client_address}' -j MARK --set-mark '#{mark + MARK}'",
  336. ]
  337. elsif is_ipv6_address?(client_address)
  338. #TO DO: ip6tables rules!
  339. not_implemented
  340. else
  341. raise("BUG: unexpected address type '#{client_address}'")
  342. end
  343. execute_actions(firewall_paranoid_remove_user_actions, :blind => true)
  344. execute_actions(firewall_add_user_actions)
  345. unless @total_upload_bandwidth.blank? or upload_bandwidth.blank?
  346. tc_class = OsCaptivePortal::get_tc_class_for_cp(@cp_interface) || raise("BUG: tc class not found for cp '#{@cp_interface}'")
  347. shaping_up_paranoid_remove_user_actions = [
  348. # upload class, qdisc and filter paranoid remotion
  349. "#{TC} filter del dev '#{@wan_interface}' parent 1: protocol ip pref 1 handle #{mark + MARK} fw classid #{tc_class}:#{mark}",
  350. "#{TC} qdisc del dev '#{@wan_interface}' parent #{tc_class}:#{mark} handle #{mark}: sfq perturb 10",
  351. "#{TC} class del dev '#{@wan_interface}' parent #{tc_class}:1 classid #{tc_class}:#{mark} htb rate #{upload_bandwidth}kbit ceil #{upload_bandwidth}kbit",
  352. ]
  353. shaping_up_add_user_actions = [
  354. # upload class, qdisc and filter
  355. "#{TC} class add dev '#{@wan_interface}' parent #{tc_class}:1 classid #{tc_class}:#{mark} htb rate #{upload_bandwidth}kbit ceil #{upload_bandwidth}kbit",
  356. "#{TC} qdisc add dev '#{@wan_interface}' parent #{tc_class}:#{mark} handle #{mark}: sfq perturb 10",
  357. "#{TC} filter add dev '#{@wan_interface}' parent 1: protocol ip pref 1 handle #{mark + MARK} fw classid #{tc_class}:#{mark}",
  358. ]
  359. execute_actions(shaping_up_paranoid_remove_user_actions, :blind => true)
  360. execute_actions(shaping_up_add_user_actions)
  361. end
  362. unless @total_download_bandwidth.blank? or download_bandwidth.blank?
  363. shaping_down_paranoid_remove_user_actions = [
  364. # download class, qdisc and filter
  365. "#{TC} filter del dev '#{@cp_interface}' parent 1: protocol ip pref 1 handle #{mark + MARK} fw classid 1:#{mark}",
  366. "#{TC} qdisc del dev '#{@cp_interface}' parent 1:#{mark} handle #{mark}: sfq perturb 10",
  367. "#{TC} class del dev '#{@cp_interface}' parent 1:1 classid 1:#{mark} htb rate #{download_bandwidth}kbit ceil #{download_bandwidth}kbit",
  368. ]
  369. shaping_down_add_user_actions = [
  370. # download class, qdisc and filter
  371. "#{TC} class add dev '#{@cp_interface}' parent 1:1 classid 1:#{mark} htb rate #{download_bandwidth}kbit ceil #{download_bandwidth}kbit",
  372. "#{TC} qdisc add dev '#{@cp_interface}' parent 1:#{mark} handle #{mark}: sfq perturb 10",
  373. "#{TC} filter add dev '#{@cp_interface}' parent 1: protocol ip pref 1 handle #{mark + MARK} fw classid 1:#{mark}",
  374. ]
  375. execute_actions(shaping_down_paranoid_remove_user_actions, :blind => true)
  376. execute_actions(shaping_down_add_user_actions)
  377. end
  378. end
  379. # Removes a client
  380. def remove_user(client_address, client_mac_address, options = {})
  381. raise("BUG: Invalid mac address '#{client_mac_address}'") unless is_mac_address?(client_mac_address)
  382. upload_bandwidth = options[:max_upload_bandwidth] || @default_upload_bandwidth
  383. download_bandwidth = options[:max_download_bandwidth] || @default_download_bandwidth
  384. firewall_remove_user_actions = []
  385. mark = OsCaptivePortal::remove_mark_for_client(client_mac_address) || raise("BUG: mac address not found '#{client_mac_address}'")
  386. if is_ipv4_address?(client_address)
  387. firewall_remove_user_actions = [
  388. # removing_user_marking_rule
  389. "#{IPTABLES} -t mangle -D '_AUTH_IN_#{@cp_interface}' -s '#{client_address}' -m mac --mac-source '#{client_mac_address}' -j MARK --set-mark '#{mark + MARK}'",
  390. "#{IPTABLES} -t mangle -D '_AUTH_OUT_#{@cp_interface}' -d '#{client_address}' -j MARK --set-mark '#{mark + MARK}'",
  391. ]
  392. elsif is_ipv6_address?(client_address)
  393. #TO DO: ip6tables rules!
  394. not_implemented
  395. else
  396. raise("BUG: unexpected address type '#{client_address}'")
  397. end
  398. execute_actions(firewall_remove_user_actions)
  399. unless @total_upload_bandwidth.blank? or upload_bandwidth.blank?
  400. tc_class = OsCaptivePortal::get_tc_class_for_cp(@cp_interface) || raise("BUG: tc class not found for cp '#{@cp_interface}'")
  401. shaping_up_remove_user_actions = [
  402. # upload class, qdisc and filter
  403. "#{TC} filter del dev '#{@wan_interface}' parent 1: protocol ip pref 1 handle #{mark + MARK} fw classid #{tc_class}:#{mark}",
  404. "#{TC} qdisc del dev '#{@wan_interface}' parent #{tc_class}:#{mark} handle #{mark}: sfq perturb 10",
  405. "#{TC} class del dev '#{@wan_interface}' parent #{tc_class}:1 classid #{tc_class}:#{mark} htb rate #{upload_bandwidth}kbit ceil #{upload_bandwidth}kbit",
  406. ]
  407. execute_actions(shaping_up_remove_user_actions)
  408. end
  409. unless @total_download_bandwidth.blank? or download_bandwidth.blank?
  410. shaping_down_remove_user_actions = [
  411. # download class, qdisc and filter
  412. "#{TC} filter del dev '#{@cp_interface}' parent 1: protocol ip pref 1 handle #{mark + MARK} fw classid 1:#{mark}",
  413. "#{TC} qdisc del dev '#{@cp_interface}' parent 1:#{mark} handle #{mark}: sfq perturb 10",
  414. "#{TC} class del dev '#{@cp_interface}' parent 1:1 classid 1:#{mark} htb rate #{download_bandwidth}kbit ceil #{download_bandwidth}kbit",
  415. ]
  416. execute_actions(shaping_down_remove_user_actions)
  417. end
  418. end
  419. # Returns uploaded and downloaded bytes (respectively) for a given client
  420. def get_user_bytes_counters(client_address)
  421. ret = [0, 0]
  422. if is_ipv4_address?(client_address)
  423. up_match = /\A\s*(\d+)\s+(\d+)\s+/.match(%x[#{IPTABLES} -t mangle -vnx -L '_AUTH_IN_#{@cp_interface}' | grep '#{client_address}'])
  424. dn_match = /\A\s*(\d+)\s+(\d+)\s+/.match(%x[#{IPTABLES} -t mangle -vnx -L '_AUTH_OUT_#{@cp_interface}' | grep '#{client_address}'])
  425. ret = [up_match[2].to_i, dn_match[2].to_i]
  426. elsif is_ipv6_address?(client_address)
  427. #TO DO: ip6tables rules!
  428. not_implemented
  429. else
  430. raise("BUG: unexpected address type '#{client_address}'")
  431. end
  432. ret
  433. end
  434. # Returns uploaded and downloaded packets (respectively) for a given client
  435. def get_user_packets_counters(client_address)
  436. ret = [0, 0]
  437. if is_ipv4_address?(client_address)
  438. up_match = /\A\s*(\d+)\s+(\d+)\s+/.match(%x[#{IPTABLES} -t mangle -vnx -L '_AUTH_IN_#{@cp_interface}' | grep '#{client_address}'])
  439. dn_match = /\A\s*(\d+)\s+(\d+)\s+/.match(%x[#{IPTABLES} -t mangle -vnx -L '_AUTH_OUT_#{@cp_interface}' | grep '#{client_address}'])
  440. ret = [up_match[1].to_i, dn_match[1].to_i]
  441. elsif is_ipv6_address?(client_address)
  442. #TO DO: ip6tables rules!
  443. not_implemented
  444. else
  445. raise("BUG: unexpected address type '#{client_address}'")
  446. end
  447. ret
  448. end
  449. end
  450. # This class implements a singleton object that control main Linux OS commands for
  451. # the captive portals
  452. class OsControl
  453. include OsUtils
  454. include IpTablesUtils
  455. public
  456. # Initializes captive portal firewalling infrastructure
  457. def start
  458. #TO DO: ip6tables rules!
  459. start_actions = [
  460. # creating_main_chains
  461. "#{IPTABLES} -t nat -N _PRER_NAT",
  462. "#{IPTABLES} -t nat -N _POSR_NAT",
  463. "#{IPTABLES} -t filter -N _FORW_FIL",
  464. "#{IPTABLES} -t filter -N _INPU_FIL",
  465. "#{IPTABLES} -t filter -N _OUTP_FIL",
  466. "#{IPTABLES} -t mangle -N _PRER_MAN",
  467. "#{IPTABLES} -t mangle -N _POSR_MAN",
  468. # creating_filtering
  469. "#{IPTABLES} -t filter -I FORWARD 1 -j _FORW_FIL",
  470. "#{IPTABLES} -t filter -I INPUT 1 -j _INPU_FIL",
  471. "#{IPTABLES} -t filter -I OUTPUT 1 -j _OUTP_FIL",
  472. # creating_nat
  473. "#{IPTABLES} -t nat -I PREROUTING 1 -j _PRER_NAT",
  474. "#{IPTABLES} -t nat -I POSTROUTING 1 -j _POSR_NAT",
  475. # creating_mangle
  476. "#{IPTABLES} -t mangle -I PREROUTING 1 -j _PRER_MAN",
  477. "#{IPTABLES} -t mangle -I POSTROUTING 1 -j _POSR_MAN"
  478. ]
  479. execute_actions(start_actions)
  480. end
  481. # Finalize captive portal firewalling infrastructure
  482. def stop
  483. @captive_portals.each_value do |cp|
  484. cp.stop
  485. end
  486. @captive_portals = Hash.new
  487. #TO DO: ip6tables rules!
  488. stop_actions = [
  489. # destroying_redirections
  490. "#{IPTABLES} -t nat -D PREROUTING -j _PRER_NAT",
  491. "#{IPTABLES} -t nat -F _PRER_NAT",
  492. "#{IPTABLES} -t nat -X _PRER_NAT",
  493. # destroying_filtering
  494. "#{IPTABLES} -t filter -D FORWARD -j _FORW_FIL",
  495. "#{IPTABLES} -t filter -F _FORW_FIL",
  496. "#{IPTABLES} -t filter -X _FORW_FIL",
  497. "#{IPTABLES} -t filter -D INPUT -j _INPU_FIL",
  498. "#{IPTABLES} -t filter -F _INPU_FIL",
  499. "#{IPTABLES} -t filter -X _INPU_FIL",
  500. "#{IPTABLES} -t filter -D OUTPUT -j _OUTP_FIL",
  501. "#{IPTABLES} -t filter -F _OUTP_FIL",
  502. "#{IPTABLES} -t filter -X _OUTP_FIL",
  503. # destroying_nat
  504. "#{IPTABLES} -t nat -D POSTROUTING -j _POSR_NAT",
  505. "#{IPTABLES} -t nat -F _POSR_NAT",
  506. "#{IPTABLES} -t nat -X _POSR_NAT",
  507. # destroying_mangle
  508. "#{IPTABLES} -t mangle -D PREROUTING -j _PRER_MAN",
  509. "#{IPTABLES} -t mangle -F _PRER_MAN",
  510. "#{IPTABLES} -t mangle -X _PRER_MAN",
  511. "#{IPTABLES} -t mangle -D POSTROUTING -j _POSR_MAN",
  512. "#{IPTABLES} -t mangle -F _POSR_MAN",
  513. "#{IPTABLES} -t mangle -X _POSR_MAN"
  514. ]
  515. execute_actions(stop_actions)
  516. end
  517. end