class Tablomat::IPTables
The IPTables
interface
Attributes
active[R]
builtin_chains[R]
iptables_bin[RW]
tmp_chain[R]
Public Class Methods
new()
click to toggle source
# File lib/tablomat/iptables.rb, line 16 def initialize # iptables must be in PATH @iptables_bin = 'iptables' @iptables_bin = "sudo #{@iptables_bin}" if Etc.getlogin != 'root' # get random value for rule creation hack, iptables limits name to 28 characters @tmp_chain_prefix = 'tablomat' @builtin_chains = { filter: %w[INPUT FORWARD OUTPUT], nat: %w[PREROUTING INPUT OUTPUT POSTROUTING], mangle: %w[PREROUTING INPUT FORWARD OUTPUT POSTROUTING] } @tables = {} @active = true synchronize end
Public Instance Methods
activate()
click to toggle source
# File lib/tablomat/iptables.rb, line 146 def activate @active = true @tables.each do |_name, table| table.activate end end
append(table_name, chain_name, data)
click to toggle source
# File lib/tablomat/iptables.rb, line 136 def append(table_name, chain_name, data) data = normalize data, table_name table(table_name).append(chain_name, data) end
deactivate()
click to toggle source
# File lib/tablomat/iptables.rb, line 153 def deactivate @active = false tables.each do |_name, table| table.deactivate end end
delete(table_name, chain_name, data)
click to toggle source
# File lib/tablomat/iptables.rb, line 141 def delete(table_name, chain_name, data) data = normalize data, table_name table(table_name).delete(chain_name, data) end
exec(cmd)
click to toggle source
# File lib/tablomat/iptables.rb, line 31 def exec(cmd) Exec.exec(cmd) end
exists(table_name, chain_name, data = nil)
click to toggle source
rubocop:disable Metrics/AbcSize
# File lib/tablomat/iptables.rb, line 121 def exists(table_name, chain_name, data = nil) if data.nil? table(table_name).chain_exists(chain_name) && table(table_name).chain(chain_name).active else data = normalize data, table_name table(table_name).chain(chain_name).rules.count { |_k, v| normalize(v.description, table_name) == data && v.active } >= 1 end end
get_active_rules(table = 'nat', chain = 'PREROUTING')
click to toggle source
rubocop:disable Metrics/AbcSize
# File lib/tablomat/iptables.rb, line 161 def get_active_rules(table = 'nat', chain = 'PREROUTING') rrgx = /(?<key>--?[[:alpha:]-]+) (?<value>[[:alnum:]:\.]+)/.freeze switch_map = { '-s' => :source, '--source' => :source, '-d' => :destination, '--destination' => :destination, '--dport' => :dport, '--to-destination' => :to_dest, '-p' => :protocol, '--protocol' => :protocol, '-j' => :jump, '--jump' => :jump, '--match-set' => :match } table(table).chain(chain).rules.filter { |_, r| r.active }.map do |_, rule| rule.description.to_enum(:scan, rrgx) .map { Regexp.last_match } .map { |m| [switch_map[m[:key]], m[:value]] } .filter { |m| m[0] }.to_h end end
insert(table_name, chain_name, data, pos)
click to toggle source
rubocop:enable Metrics/AbcSize
# File lib/tablomat/iptables.rb, line 131 def insert(table_name, chain_name, data, pos) data = normalize data, table_name table(table_name).insert(chain_name, data, pos) end
normalize(data, table = 'filter')
click to toggle source
converts any given rule to the iptables internal description format by using a temporary table rubocop:disable Metrics/AbcSize
# File lib/tablomat/iptables.rb, line 103 def normalize(data, table = 'filter') synchronize tmp_chain = "#{@tmp_chain_prefix}#{Digest::SHA256.hexdigest Random.rand(1024).to_s}"[0, 28] self.table(table).chain(tmp_chain).activate self.table(table).append(tmp_chain, data) synchronize normalized = data self.table(table).chain(tmp_chain).rules.select { |_k, r| r.owned == false }.each do |_k, r| normalized = r.description end self.table(table).delete(tmp_chain, data) self.table(table).chain(tmp_chain).deactivate self.table(table).chain(tmp_chain).apply_delete normalized end
parse_chain(chain, rules)
click to toggle source
# File lib/tablomat/iptables.rb, line 86 def parse_chain(chain, rules) rules.each do |rule| r = chain.rule(rule, false) r.activate true end end
parse_data(data)
click to toggle source
rubocop:enable Metrics/AbcSize
# File lib/tablomat/iptables.rb, line 70 def parse_data(data) data.each do |table, chains| t = self.table(table, false) t.activate true parse_table(t, chains) end end
parse_output(stdout)
click to toggle source
rubocop:disable Metrics/AbcSize
# File lib/tablomat/iptables.rb, line 49 def parse_output(stdout) stdout = stdout.split("\n").reject { |s| s[0] == '#' }.join("\n") data = {} stdout.split(/^\*/).reject { |s| s == '' }.each do |ruleset| table, rule = ruleset.match(/(.+?)\n(.*)/m).to_a[1..-1] data[table] = {} raise "Empty ruleset in table #{table}" if table.nil? rule.scan(/^:(.+?)\s+/).each do |match| data[table][match[0]] = [] end rule.scan(/^-A (.+?) (.+?)\n/).each do |match| data[table][match[0]] << match[1] end end parse_data(data) end
parse_table(table, chains)
click to toggle source
# File lib/tablomat/iptables.rb, line 78 def parse_table(table, chains) chains.each do |chain, rules| c = table.chain(chain, false) c.activate true parse_chain(c, rules) end end
print()
click to toggle source
rubocop:enable Metrics/AbcSize
# File lib/tablomat/iptables.rb, line 197 def print require 'pp' pp self end
switch_sources(old_src, new_src)
click to toggle source
used to easily switch rules from one src ip to another
# File lib/tablomat/iptables.rb, line 179 def switch_sources(old_src, new_src) @tables.to_a.each do |_kt, tbl| tbl.chains.to_a.each do |_kc, chn| next if chn.name.include? 'tablomat' chn.rules.to_a.each do |_key, rule| next unless rule.description.include? "-s #{old_src}" new_data = normalize(rule.description, tbl.name).sub "-s #{old_src}", "-s #{new_src}" pos = rule.position + 1 delete(tbl.name, chn.name, rule.description) insert(tbl.name, chn.name, new_data, pos) end end end end
synchronize()
click to toggle source
# File lib/tablomat/iptables.rb, line 35 def synchronize # called regularly by normalize command = "#{@iptables_bin}-save" stdout = `#{command} 2>&1`.strip << "\n" if $CHILD_STATUS != 0 # throw error puts "Invalid return value when calling #{command}" end parse_output stdout rescue StandardError => e puts "[error] #{e}" end
table(name, owned = true, &block)
click to toggle source
# File lib/tablomat/iptables.rb, line 93 def table(name, owned = true, &block) name = name.to_s.downcase (@tables[name] || Table.new(self, name, owned)).tap do |table| @tables[name] = table block&.call(table) end end