From 6233dae1d1fc193a186eff875c95a552dab68811 Mon Sep 17 00:00:00 2001
From: Timo Pohl <s6topohl@uni-bonn.de>
Date: Mon, 31 Oct 2022 15:10:23 +0100
Subject: [PATCH 1/5] Add simplecov-cobertura to Gemfile.lock

---
 Gemfile.lock | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 98be755..8c19ad5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -46,6 +46,8 @@ GEM
     simplecov (0.18.5)
       docile (~> 1.1)
       simplecov-html (~> 0.11)
+    simplecov-cobertura (1.4.2)
+      simplecov (~> 0.8)
     simplecov-html (0.12.2)
     unicode-display_width (1.7.0)
 
@@ -58,7 +60,8 @@ DEPENDENCIES
   rubocop (~> 0.88.0)
   rubocop-performance (~> 1.7.1)
   simplecov (~> 0.18.5)
+  simplecov-cobertura (~> 1.4, >= 1.4.1)
   tablomat!
 
 BUNDLED WITH
-   2.2.14
+   2.3.10
-- 
GitLab


From 8c3e4773a429678f1dc24a72bff662011d5136dd Mon Sep 17 00:00:00 2001
From: Timo Pohl <s6topohl@uni-bonn.de>
Date: Mon, 31 Oct 2022 16:03:17 +0100
Subject: [PATCH 2/5] Add support for ip6tables

---
 Gemfile.lock                   |   2 +-
 lib/tablomat/iptables.rb       |  31 +++++--
 lib/tablomat/iptables/chain.rb |   2 +-
 lib/tablomat/iptables/rule.rb  |   2 +-
 lib/tablomat/iptables/table.rb |   2 +-
 lib/tablomat/version.rb        |   2 +-
 spec/ip6tables_spec.rb         | 148 +++++++++++++++++++++++++++++++++
 spec/ipsetip6tables_spec.rb    |  75 +++++++++++++++++
 8 files changed, 252 insertions(+), 12 deletions(-)
 create mode 100644 spec/ip6tables_spec.rb
 create mode 100644 spec/ipsetip6tables_spec.rb

diff --git a/Gemfile.lock b/Gemfile.lock
index 8c19ad5..654e870 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,7 @@
 PATH
   remote: .
   specs:
-    tablomat (1.2.1)
+    tablomat (1.3.0)
 
 GEM
   remote: https://rubygems.org/
diff --git a/lib/tablomat/iptables.rb b/lib/tablomat/iptables.rb
index d45041d..7b31c23 100644
--- a/lib/tablomat/iptables.rb
+++ b/lib/tablomat/iptables.rb
@@ -7,17 +7,13 @@ require 'tempfile'
 require 'tablomat/exec'
 
 module Tablomat
-  # The IPTables interface
-  class IPTables
+  # The base class for the IPTables interface
+  class IPTablesBase
     extend Exec
     attr_accessor :iptables_bin
     attr_reader :active, :builtin_chains, :tmp_chain
 
     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'
 
@@ -25,7 +21,6 @@ module Tablomat
       @tables = {}
 
       @active = true
-      synchronize
     end
 
     def exec(cmd)
@@ -199,4 +194,26 @@ module Tablomat
       pp self
     end
   end
+
+  # The IPTables interface
+  class IPTables < IPTablesBase
+    def initialize
+      super()
+      # iptables must be in PATH
+      @iptables_bin = 'iptables'
+      @iptables_bin = "sudo #{@iptables_bin}" if Etc.getlogin != 'root'
+      synchronize
+    end
+  end
+
+  # The IP6Tables interface
+  class IP6Tables < IPTablesBase
+    def initialize
+      super()
+      # ip6tables must be in PATH
+      @iptables_bin = 'ip6tables'
+      @iptables_bin = "sudo #{@iptables_bin}" if Etc.getlogin != 'root'
+      synchronize
+    end
+  end
 end
diff --git a/lib/tablomat/iptables/chain.rb b/lib/tablomat/iptables/chain.rb
index c2fad83..e00ca32 100644
--- a/lib/tablomat/iptables/chain.rb
+++ b/lib/tablomat/iptables/chain.rb
@@ -3,7 +3,7 @@
 require 'tablomat/iptables/rule'
 
 module Tablomat
-  class IPTables
+  class IPTablesBase
     # The IPTables class is the interface to the iptables command
     class Chain
       attr_accessor :owned
diff --git a/lib/tablomat/iptables/rule.rb b/lib/tablomat/iptables/rule.rb
index eea4b25..07a7e16 100644
--- a/lib/tablomat/iptables/rule.rb
+++ b/lib/tablomat/iptables/rule.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 module Tablomat
-  class IPTables
+  class IPTablesBase
     # IPTables are made of Rules
     class Rule
       attr_accessor :owned, :active, :method, :position
diff --git a/lib/tablomat/iptables/table.rb b/lib/tablomat/iptables/table.rb
index b8cee0f..61c9f55 100644
--- a/lib/tablomat/iptables/table.rb
+++ b/lib/tablomat/iptables/table.rb
@@ -3,7 +3,7 @@
 require 'tablomat/iptables/chain'
 
 module Tablomat
-  class IPTables
+  class IPTablesBase
     # Puts the Table in IPTables
     class Table
       attr_accessor :owned
diff --git a/lib/tablomat/version.rb b/lib/tablomat/version.rb
index 450c37e..c083b0e 100644
--- a/lib/tablomat/version.rb
+++ b/lib/tablomat/version.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 module Tablomat
-  VERSION = '1.2.1'
+  VERSION = '1.3.0'
 end
diff --git a/spec/ip6tables_spec.rb b/spec/ip6tables_spec.rb
new file mode 100644
index 0000000..993bdb5
--- /dev/null
+++ b/spec/ip6tables_spec.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'etc'
+
+describe Tablomat::IP6Tables do
+  describe 'Initialize module' do
+    @iptables = Tablomat::IP6Tables.new
+  end
+
+  before(:all) do
+    @command = 'ip6tables'
+    @command = "sudo #{@command}" if Etc.getlogin != 'root'
+
+    @iptables = Tablomat::IP6Tables.new
+    @iptables.activate
+  end
+
+  after(:all) do
+    # cleanup tables
+    @iptables.table('mangle').chain('test', &:apply_delete)
+    @iptables.table('mangle').chain('custom', &:apply_delete)
+    @iptables.table('filter').chain('rspec', &:apply_delete)
+    @iptables.table('nat').chain('rspec', &:apply_delete)
+  end
+
+  it 'does raise an error when getting an invalid ruleset' do
+    output = <<~IPTABLE
+      *nat
+      *mangle
+      :PREROUTING ACCEPT [57:17363]
+      :INPUT ACCEPT [57:17363]
+      :FORWARD ACCEPT [0:0]
+      :OUTPUT ACCEPT [60:16575]
+      :POSTROUTING ACCEPT [60:16575]
+      COMMIT
+      *filter
+    IPTABLE
+    expect { @iptables.parse_output(output) }.to raise_error(StandardError)
+  end
+
+  it 'converts rules to iptables-save format via tmp chain' do
+    expect(@iptables.normalize(protocol: :tcp, sport: 123, source: '1.2.3.4')).to eq('-s 1.2.3.4/32 -p tcp -m tcp --sport 123')
+    expect(@iptables.normalize('-s 1.2.3.4/32 -p tcp -m tcp --sport 123')).to eq('-s 1.2.3.4/32 -p tcp -m tcp --sport 123')
+    expect(@iptables.normalize('-s 1.2.3.4/32 -m tcp --sport 123 -p tcp')).to eq('-s 1.2.3.4/32 -p tcp -m tcp --sport 123')
+  end
+
+  it 'gets current ruleset' do
+    # remove all existing rules in mangle FORWARD (hurts running host rules during test)
+    `#{@command}-save > /tmp/iptables.save`
+    `#{@command} -t mangle -F FORWARD`
+    `#{@command} -t mangle -A FORWARD -p tcp -j ACCEPT`
+    @iptables.synchronize
+
+    `#{@command}-restore < /tmp/iptables.save`
+    # @iptables.print
+
+    # check that filter:INPUT is not owned!
+    expect(@iptables.table('filter').chain('INPUT').owned).to be_falsey
+
+    expect(@iptables.table('mangle').chain('FORWARD').rule(@iptables.normalize(protocol: :tcp, jump: :ACCEPT)).owned).to be_falsey
+    expect(@iptables.table('mangle').chain('FORWARD').rule(@iptables.normalize(protocol: :tcp, jump: :ACCEPT)).active).to be_truthy
+  end
+
+  it 'creates new chain in table mangle' do
+    @iptables.table('mangle').chain('custom').activate
+    expect(@iptables.table('mangle').chain('custom').active).to be_truthy
+  end
+
+  it 'allows to retrieve active rules' do
+    @iptables.append('nat', 'PREROUTING', source: '127.0.0.2',
+                                          destination: '127.0.0.3',
+                                          protocol: :tcp,
+                                          dport: 8080,
+                                          jump: 'DNAT', to: '127.0.0.4:9090')
+    target_hash = { source: '127.0.0.2', destination: '127.0.0.3', dport: '8080', jump: 'DNAT', protocol: 'tcp', to_dest: '127.0.0.4:9090' }
+    rules = @iptables.get_active_rules
+    expect(rules.length).to eq(2)
+    expect(rules.pop).to eq(target_hash)
+    @iptables.delete('nat', 'PREROUTING', source: '127.0.0.2',
+                                          destination: '127.0.0.3',
+                                          protocol: :tcp,
+                                          dport: 8080,
+                                          jump: 'DNAT', to: '127.0.0.4:9090')
+  end
+
+  it 'is not possible to set policy for non builtin chains' do
+    expect do
+      @iptables.table('mangle').chain('custom') do |chain|
+        chain.policy 'RETURN'
+        chain.apply
+      end
+    end.to raise_error('Unable to assign policy to non builtin chains, TODO: implement handling')
+  end
+
+  #  it "can set policy for builtin chains" do
+  #    @iptables.table("mangle").chain("INPUT") do | chain |
+  #      chain.set_policy "ACCEPT"
+  #    end
+  #    @iptables.activate
+  #  end
+
+  it 'can remove chains' do
+    # create chain
+    @iptables.table('mangle').chain('test').activate
+    expect(@iptables.exists('mangle', 'test')).to be_truthy
+    @iptables.table('mangle').chain('test').deactivate
+    expect(@iptables.exists('mangle', 'test')).to be_falsey
+  end
+
+  it 'checks if rule already exists' do
+    # check precondition
+    expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_falsy
+
+    # check existing
+    @iptables.append('mangle', 'custom', protocol: :tcp, sport: 123)
+    expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_truthy
+    expect(@iptables.table('mangle').chain('FORWARD').rule(@iptables.normalize(protocol: :tcp, jump: :ACCEPT)).active).to be_truthy
+
+    # check missing
+    @iptables.delete('mangle', 'custom', protocol: :tcp, sport: 123)
+    expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_falsy
+  end
+
+  it 'appends new rule via system' do
+    expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_falsy
+    @iptables.append('mangle', 'custom', protocol: :tcp, sport: 123)
+    expect(@iptables.table('mangle').chain('FORWARD').rule(@iptables.normalize(protocol: :tcp, jump: :ACCEPT)).owned).to be_falsey
+    expect(@iptables.table('mangle').chain('FORWARD').rule(@iptables.normalize(protocol: :tcp, jump: :ACCEPT)).active).to be_truthy
+  end
+
+  it 'can remove rules via system' do
+    expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_truthy
+    @iptables.delete('mangle', 'custom', protocol: :tcp, sport: 123)
+    expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_falsey
+  end
+
+  it 'changes all rules for a specific source address to another source address' do
+    @iptables.append('filter', 'rspec', protocol: :tcp, sport: 123, source: '1.2.3.5')
+    @iptables.switch_sources('1.2.3.5', '5.3.2.1')
+    expect(@iptables.exists('filter', 'rspec', source: '5.3.2.1', protocol: :tcp, sport: 123)).to be_truthy
+    expect(@iptables.exists('filter', 'rspec', source: '1.2.3.5', protocol: :tcp, sport: 123)).to be_falsey
+    @iptables.append('nat', 'rspec', protocol: :tcp, sport: 123, source: '1.2.3.5', jump: :DNAT, 'to-destination': '127.0.0.1:123')
+    @iptables.switch_sources('1.2.3.5', '5.3.2.1')
+    expect(@iptables.exists('nat', 'rspec', source: '5.3.2.1', protocol: :tcp, sport: 123, jump: :DNAT, 'to-destination': '127.0.0.1:123')).to be_truthy
+    expect(@iptables.exists('nat', 'rspec', source: '1.2.3.5', protocol: :tcp, sport: 123, jump: :DNAT, 'to-destination': '127.0.0.1:123')).to be_falsey
+  end
+end
diff --git a/spec/ipsetip6tables_spec.rb b/spec/ipsetip6tables_spec.rb
new file mode 100644
index 0000000..da7a8ec
--- /dev/null
+++ b/spec/ipsetip6tables_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'etc'
+
+describe Tablomat::IPTables, Tablomat::IPSet do
+  describe 'Initialize module' do
+    @ipset = Tablomat::IPSet.new
+    @iptables = Tablomat::IPTables.new
+  end
+
+  before(:all) do
+    @command = 'ipset'
+    @command = "sudo #{@command}" if Etc.getlogin != 'root'
+    @ipset = Tablomat::IPSet.new
+    @iptables = Tablomat::IPTables.new
+    @iptables.activate
+  end
+
+  after(:all) do
+    # cleanup table and ipsets
+    @iptables.table('mangle').chain('custom', &:apply_delete)
+    @ipset.destroy_all
+  end
+
+  it 'can create and delete rules using ipsets if set exists' do
+    #  initialize ipset and create set
+    @ipset.create(set_name: 'testset1', type: 'hash:ip')
+    #  create rule
+    @iptables.append('mangle', 'custom', set: @ipset.matchset(set_name: 'testset1', flags: 'src'), protocol: :tcp, sport: 123)
+    expect(@iptables.exists('mangle', 'custom', set: @ipset.matchset(set_name: 'testset1', flags: 'src'), protocol: :tcp, sport: 123)).to be_truthy
+    @iptables.delete('mangle', 'custom', set: @ipset.matchset(set_name: 'testset1', flags: 'src'), protocol: :tcp, sport: 123)
+    expect(@iptables.exists('mangle', 'custom', set: @ipset.matchset(set_name: 'testset1', flags: 'src'), protocol: :tcp, sport: 123)).to be_falsey
+  end
+
+  it 'can create and delete rules using ipsets with Options' do
+    @iptables.append('mangle', 'custom', match: @ipset.matchset(set_name: 'testset1', flags: 'src', option: '--update-subcounters', negate_option: true, negate: true), protocol: :tcp, sport: 123)
+    expect(@iptables.exists('mangle', 'custom', match: @ipset.matchset(set_name: 'testset1', flags: 'src', option: '--update-subcounters', negate_option: true, negate: true), protocol: :tcp, sport: 123)).to be_truthy
+    @iptables.delete('mangle', 'custom', match: @ipset.matchset(set_name: 'testset1', flags: 'src', option: '--update-subcounters', negate_option: true, negate: true), protocol: :tcp, sport: 123)
+    expect(@iptables.exists('mangle', 'custom', match: @ipset.matchset(set_name: 'testset1', flags: 'src', option: '--update-subcounters', negate_option: true, negate: true), protocol: :tcp, sport: 123)).to be_falsey
+  end
+
+  it 'can not create rules using a set if the set does not exist' do
+    expect(@ipset.set('testset2').exists?).to be_falsey
+    expect { @iptables.append('mangle', 'custom', match: @ipset.matchset(set_name: 'testset', flags: 'src'), protocol: :tcp, sport: 123) }.to raise_error(RuntimeError)
+  end
+
+  it 'can not destroy a set if a rule is using it' do
+    @ipset.create(set_name: 'testset3', type: 'hash:ip')
+    @iptables.append('mangle', 'custom', match: @ipset.matchset(set_name: 'testset3', flags: 'src'), protocol: :tcp, sport: 123)
+    expect(@iptables.exists('mangle', 'custom', match: @ipset.matchset(set_name: 'testset3', flags: 'src'), protocol: :tcp, sport: 123)).to be_truthy
+    expect { @ipset.destroy(set_name: 'testset3') }.to raise_error(Tablomat::IPSetError)
+    @iptables.delete('mangle', 'custom', match: @ipset.matchset(set_name: 'testset3', flags: 'src'), protocol: :tcp, sport: 123)
+    expect(@iptables.exists('mangle', 'custom', match: @ipset.matchset(set_name: 'testset3', flags: 'src'), protocol: :tcp, sport: 123)).to be_falsey
+    @ipset.destroy(set_name: 'testset3')
+  end
+
+  it 'lists the set in active rules, if a set is given' do
+    @ipset.create(set_name: 'testset4', type: 'hash:ip')
+    @iptables.append('mangle', 'custom', match: @ipset.matchset(set_name: 'testset4', flags: 'src'), protocol: :tcp, sport: 123)
+    active_rules = @iptables.get_active_rules('mangle', 'custom')
+    expect(active_rules[-1][:match]).to eq('testset4')
+    @iptables.delete('mangle', 'custom', match: @ipset.matchset(set_name: 'testset4', flags: 'src'), protocol: :tcp, sport: 123)
+    @ipset.destroy(set_name: 'testset4')
+  end
+
+  it 'does not list a set in active rules, if no set is given' do
+    @ipset.create(set_name: 'testset5', type: 'hash:ip')
+    @iptables.append('mangle', 'custom', source: '10.0.0.1', protocol: :tcp, sport: 123)
+    active_rules = @iptables.get_active_rules('mangle', 'custom')
+    expect(active_rules[-1].key?(:match)).to be_falsey
+    @iptables.delete('mangle', 'custom', source: '10.0.0.1', protocol: :tcp, sport: 123)
+    @ipset.destroy(set_name: 'testset5')
+  end
+end
-- 
GitLab


From b9167828b0bbaebac93c9855474d01c1328e8159 Mon Sep 17 00:00:00 2001
From: Timo Pohl <s6topohl@uni-bonn.de>
Date: Thu, 3 Nov 2022 15:52:57 +0100
Subject: [PATCH 3/5] Add tests for ipv6 tables

---
 spec/ip6tables_spec.rb      | 38 ++++++++++++++++++-------------------
 spec/ipsetip6tables_spec.rb | 18 +++++++++---------
 2 files changed, 28 insertions(+), 28 deletions(-)

diff --git a/spec/ip6tables_spec.rb b/spec/ip6tables_spec.rb
index 993bdb5..ae05c23 100644
--- a/spec/ip6tables_spec.rb
+++ b/spec/ip6tables_spec.rb
@@ -40,9 +40,9 @@ describe Tablomat::IP6Tables do
   end
 
   it 'converts rules to iptables-save format via tmp chain' do
-    expect(@iptables.normalize(protocol: :tcp, sport: 123, source: '1.2.3.4')).to eq('-s 1.2.3.4/32 -p tcp -m tcp --sport 123')
-    expect(@iptables.normalize('-s 1.2.3.4/32 -p tcp -m tcp --sport 123')).to eq('-s 1.2.3.4/32 -p tcp -m tcp --sport 123')
-    expect(@iptables.normalize('-s 1.2.3.4/32 -m tcp --sport 123 -p tcp')).to eq('-s 1.2.3.4/32 -p tcp -m tcp --sport 123')
+    expect(@iptables.normalize(protocol: :tcp, sport: 123, source: 'aaaa::ffff')).to eq('-s aaaa::ffff/128 -p tcp -m tcp --sport 123')
+    expect(@iptables.normalize('-s aaaa::ffff/128 -p tcp -m tcp --sport 123')).to eq('-s aaaa::ffff/128 -p tcp -m tcp --sport 123')
+    expect(@iptables.normalize('-s aaaa::ffff/128 -m tcp --sport 123 -p tcp')).to eq('-s aaaa::ffff/128 -p tcp -m tcp --sport 123')
   end
 
   it 'gets current ruleset' do
@@ -68,20 +68,20 @@ describe Tablomat::IP6Tables do
   end
 
   it 'allows to retrieve active rules' do
-    @iptables.append('nat', 'PREROUTING', source: '127.0.0.2',
-                                          destination: '127.0.0.3',
+    @iptables.append('nat', 'PREROUTING', source: 'cccc::ffff',
+                                          destination: 'cccc::fffe',
                                           protocol: :tcp,
                                           dport: 8080,
-                                          jump: 'DNAT', to: '127.0.0.4:9090')
-    target_hash = { source: '127.0.0.2', destination: '127.0.0.3', dport: '8080', jump: 'DNAT', protocol: 'tcp', to_dest: '127.0.0.4:9090' }
+                                          jump: 'DNAT', to: 'cccc::bbbb:9090')
+    target_hash = { source: 'cccc::ffff', destination: 'cccc::fffe', dport: '8080', jump: 'DNAT', protocol: 'tcp', to_dest: 'cccc::bbbb:9090' }
     rules = @iptables.get_active_rules
-    expect(rules.length).to eq(2)
+    expect(rules.length).to eq(1)
     expect(rules.pop).to eq(target_hash)
-    @iptables.delete('nat', 'PREROUTING', source: '127.0.0.2',
-                                          destination: '127.0.0.3',
+    @iptables.delete('nat', 'PREROUTING', source: 'cccc::ffff',
+                                          destination: 'cccc::fffe',
                                           protocol: :tcp,
                                           dport: 8080,
-                                          jump: 'DNAT', to: '127.0.0.4:9090')
+                                          jump: 'DNAT', to: 'cccc::bbbb:9090')
   end
 
   it 'is not possible to set policy for non builtin chains' do
@@ -136,13 +136,13 @@ describe Tablomat::IP6Tables do
   end
 
   it 'changes all rules for a specific source address to another source address' do
-    @iptables.append('filter', 'rspec', protocol: :tcp, sport: 123, source: '1.2.3.5')
-    @iptables.switch_sources('1.2.3.5', '5.3.2.1')
-    expect(@iptables.exists('filter', 'rspec', source: '5.3.2.1', protocol: :tcp, sport: 123)).to be_truthy
-    expect(@iptables.exists('filter', 'rspec', source: '1.2.3.5', protocol: :tcp, sport: 123)).to be_falsey
-    @iptables.append('nat', 'rspec', protocol: :tcp, sport: 123, source: '1.2.3.5', jump: :DNAT, 'to-destination': '127.0.0.1:123')
-    @iptables.switch_sources('1.2.3.5', '5.3.2.1')
-    expect(@iptables.exists('nat', 'rspec', source: '5.3.2.1', protocol: :tcp, sport: 123, jump: :DNAT, 'to-destination': '127.0.0.1:123')).to be_truthy
-    expect(@iptables.exists('nat', 'rspec', source: '1.2.3.5', protocol: :tcp, sport: 123, jump: :DNAT, 'to-destination': '127.0.0.1:123')).to be_falsey
+    @iptables.append('filter', 'rspec', protocol: :tcp, sport: 123, source: 'aaaa::ffff')
+    @iptables.switch_sources('aaaa::ffff', 'aaaa::bbbb')
+    expect(@iptables.exists('filter', 'rspec', source: 'aaaa::bbbb', protocol: :tcp, sport: 123)).to be_truthy
+    expect(@iptables.exists('filter', 'rspec', source: 'aaaa::ffff', protocol: :tcp, sport: 123)).to be_falsey
+    @iptables.append('nat', 'rspec', protocol: :tcp, sport: 123, source: 'aaaa::ffff', jump: :DNAT, 'to-destination': 'cccc::ffff:123')
+    @iptables.switch_sources('aaaa::ffff', 'aaaa::bbbb')
+    expect(@iptables.exists('nat', 'rspec', source: 'aaaa::bbbb', protocol: :tcp, sport: 123, jump: :DNAT, 'to-destination': 'cccc::ffff:123')).to be_truthy
+    expect(@iptables.exists('nat', 'rspec', source: 'aaaa::ffff', protocol: :tcp, sport: 123, jump: :DNAT, 'to-destination': 'cccc::ffff:123')).to be_falsey
   end
 end
diff --git a/spec/ipsetip6tables_spec.rb b/spec/ipsetip6tables_spec.rb
index da7a8ec..59c6542 100644
--- a/spec/ipsetip6tables_spec.rb
+++ b/spec/ipsetip6tables_spec.rb
@@ -3,17 +3,17 @@
 require 'spec_helper'
 require 'etc'
 
-describe Tablomat::IPTables, Tablomat::IPSet do
+describe Tablomat::IP6Tables, Tablomat::IPSet do
   describe 'Initialize module' do
     @ipset = Tablomat::IPSet.new
-    @iptables = Tablomat::IPTables.new
+    @iptables = Tablomat::IP6Tables.new
   end
 
   before(:all) do
     @command = 'ipset'
     @command = "sudo #{@command}" if Etc.getlogin != 'root'
     @ipset = Tablomat::IPSet.new
-    @iptables = Tablomat::IPTables.new
+    @iptables = Tablomat::IP6Tables.new
     @iptables.activate
   end
 
@@ -25,7 +25,7 @@ describe Tablomat::IPTables, Tablomat::IPSet do
 
   it 'can create and delete rules using ipsets if set exists' do
     #  initialize ipset and create set
-    @ipset.create(set_name: 'testset1', type: 'hash:ip')
+    @ipset.create(set_name: 'testset1', type: 'hash:ip', create_options: 'family inet6')
     #  create rule
     @iptables.append('mangle', 'custom', set: @ipset.matchset(set_name: 'testset1', flags: 'src'), protocol: :tcp, sport: 123)
     expect(@iptables.exists('mangle', 'custom', set: @ipset.matchset(set_name: 'testset1', flags: 'src'), protocol: :tcp, sport: 123)).to be_truthy
@@ -46,7 +46,7 @@ describe Tablomat::IPTables, Tablomat::IPSet do
   end
 
   it 'can not destroy a set if a rule is using it' do
-    @ipset.create(set_name: 'testset3', type: 'hash:ip')
+    @ipset.create(set_name: 'testset3', type: 'hash:ip', create_options: 'family inet6')
     @iptables.append('mangle', 'custom', match: @ipset.matchset(set_name: 'testset3', flags: 'src'), protocol: :tcp, sport: 123)
     expect(@iptables.exists('mangle', 'custom', match: @ipset.matchset(set_name: 'testset3', flags: 'src'), protocol: :tcp, sport: 123)).to be_truthy
     expect { @ipset.destroy(set_name: 'testset3') }.to raise_error(Tablomat::IPSetError)
@@ -56,7 +56,7 @@ describe Tablomat::IPTables, Tablomat::IPSet do
   end
 
   it 'lists the set in active rules, if a set is given' do
-    @ipset.create(set_name: 'testset4', type: 'hash:ip')
+    @ipset.create(set_name: 'testset4', type: 'hash:ip', create_options: 'family inet6')
     @iptables.append('mangle', 'custom', match: @ipset.matchset(set_name: 'testset4', flags: 'src'), protocol: :tcp, sport: 123)
     active_rules = @iptables.get_active_rules('mangle', 'custom')
     expect(active_rules[-1][:match]).to eq('testset4')
@@ -65,11 +65,11 @@ describe Tablomat::IPTables, Tablomat::IPSet do
   end
 
   it 'does not list a set in active rules, if no set is given' do
-    @ipset.create(set_name: 'testset5', type: 'hash:ip')
-    @iptables.append('mangle', 'custom', source: '10.0.0.1', protocol: :tcp, sport: 123)
+    @ipset.create(set_name: 'testset5', type: 'hash:ip', create_options: 'family inet6')
+    @iptables.append('mangle', 'custom', source: 'aaaa::ffff', protocol: :tcp, sport: 123)
     active_rules = @iptables.get_active_rules('mangle', 'custom')
     expect(active_rules[-1].key?(:match)).to be_falsey
-    @iptables.delete('mangle', 'custom', source: '10.0.0.1', protocol: :tcp, sport: 123)
+    @iptables.delete('mangle', 'custom', source: 'aaaa::ffff', protocol: :tcp, sport: 123)
     @ipset.destroy(set_name: 'testset5')
   end
 end
-- 
GitLab


From 07057be5c7db97bb8dcc107a9133995393fbefe3 Mon Sep 17 00:00:00 2001
From: Timo Pohl <s6topohl@uni-bonn.de>
Date: Thu, 3 Nov 2022 15:57:22 +0100
Subject: [PATCH 4/5] Bump version and add changelog

---
 changelog.md            | 3 +++
 lib/tablomat/version.rb | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/changelog.md b/changelog.md
index 25586e8..2caa29c 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,2 +1,5 @@
+# 1.4.0
+  * Add support for IPv6 / `ip6tables`
+
 # 1.2.1
   * 🚀 public release
diff --git a/lib/tablomat/version.rb b/lib/tablomat/version.rb
index c083b0e..3fbabac 100644
--- a/lib/tablomat/version.rb
+++ b/lib/tablomat/version.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 module Tablomat
-  VERSION = '1.3.0'
+  VERSION = '1.4.0'
 end
-- 
GitLab


From abcf47bb5b0d83ac43d2a93d33be7e0a3c5e629b Mon Sep 17 00:00:00 2001
From: Timo Pohl <s6topohl@uni-bonn.de>
Date: Thu, 3 Nov 2022 16:30:24 +0100
Subject: [PATCH 5/5] Fix version bump

---
 changelog.md            | 2 +-
 lib/tablomat/version.rb | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/changelog.md b/changelog.md
index 2caa29c..8baa345 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,4 @@
-# 1.4.0
+# 1.3.0
   * Add support for IPv6 / `ip6tables`
 
 # 1.2.1
diff --git a/lib/tablomat/version.rb b/lib/tablomat/version.rb
index 3fbabac..c083b0e 100644
--- a/lib/tablomat/version.rb
+++ b/lib/tablomat/version.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 module Tablomat
-  VERSION = '1.4.0'
+  VERSION = '1.3.0'
 end
-- 
GitLab