Skip to content

Commit

Permalink
Merge pull request #41 from opus-codium/variables-in-host-and-port-lists
Browse files Browse the repository at this point in the history
Add support for variables in host/port lists
  • Loading branch information
neomilium authored Jul 6, 2024
2 parents 0bda2c8 + 92c1f28 commit 435b50c
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 34 deletions.
2 changes: 2 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ task test: %i[spec features]

task default: :test

task feature: :gen_parser
task build: :gen_parser
task spec: :gen_parser

desc 'Generate the puffy language parser'
task gen_parser: 'lib/puffy/parser.tab.rb'
Expand Down
19 changes: 13 additions & 6 deletions lib/puffy/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ rule
variable_value: ADDRESS { result = val[0][:value] }
| STRING { result = val[0][:value] }
| VARIABLE { result = @variables.fetch(val[0][:value]) }
| port { result = val[0] }

service: SERVICE service_name block { @services[val[1]] = val[2] }

Expand Down Expand Up @@ -132,9 +133,12 @@ rule
| PORT port { result = val[1] }
|

port_list: port_list ',' port { result = val[0] + [val[2]] }
| port_list port { result = val[0] + [val[1]] }
| port { result = [val[0]] }
port_list: port_list ',' port_list_item { result = val[0] + val[2] }
| port_list port_list_item { result = val[0] + val[1] }
| port_list_item { result = val[0] }

port_list_item: port { result = [val[0]] }
| VARIABLE { result = @variables.fetch(val[0][:value]) }

port: INTEGER { result = val[0][:value] }
| IDENTIFIER { result = val[0][:value] }
Expand All @@ -143,9 +147,12 @@ rule
host: ADDRESS { result = val[0][:value] }
| STRING { result = val[0][:value] }

host_list: host_list ',' host { result = val[0] + [val[2]] }
| host_list host { result = val[0] + [val[1]] }
| host { result = [val[0]] }
host_list: host_list ',' host_list_item { result = val[0] + val[2] }
| host_list host_list_item { result = val[0] + val[1] }
| host_list_item { result = val[0] }

host_list_item: host { result = [val[0]] }
| VARIABLE { result = @variables.fetch(val[0][:value]) }

filteropts: filteropts ',' filteropt { result = val[0].merge(val[2]) }
| filteropts filteropt { result = val[0].merge(val[1]) }
Expand Down
27 changes: 19 additions & 8 deletions spec/puffy/resolver_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,32 @@
# https://github.com/jordansissel/experiments/tree/master/ruby/dns-resolving-bug
module Puffy
RSpec.describe Resolver do
subject { Puffy::Resolver.instance }
before(:each) do
dns = Resolv::DNS.new
allow(dns).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::A).and_return([Resolv::DNS::Resource::IN::A.new('203.0.113.42')])
allow(dns).to receive(:getresources).with('example.com', Resolv::DNS::Resource::IN::AAAA).and_return([Resolv::DNS::Resource::IN::AAAA.new('2001:db8:fa4e:adde::42')])
allow(dns).to receive(:getresources).with('host.invalid.', Resolv::DNS::Resource::IN::A).and_call_original
allow(dns).to receive(:getresources).with('host.invalid.', Resolv::DNS::Resource::IN::AAAA).and_call_original

allow(Resolv::DNS).to receive(:open).with(nil).and_return(dns)
end

subject { Puffy::Resolver.clone.instance }

it 'resolves IPv4 and IPv6' do
expect(subject.resolv('example.com').collect(&:to_s)).to eq(['2606:2800:220:1:248:1893:25c8:1946', '93.184.216.34'])
expect(subject.resolv('example.com').collect(&:to_s)).to eq(['2001:db8:fa4e:adde::42', '203.0.113.42'])
end

it 'resolves IPv4 only' do
expect(subject.resolv('example.com', :inet).collect(&:to_s)).to eq(['93.184.216.34'])
expect(subject.resolv(IPAddr.new('93.184.216.34'), :inet).collect(&:to_s)).to eq(['93.184.216.34'])
expect(subject.resolv(IPAddr.new('2606:2800:220:1:248:1893:25c8:1946'), :inet).collect(&:to_s)).to eq([])
expect(subject.resolv('example.com', :inet).collect(&:to_s)).to eq(['203.0.113.42'])
expect(subject.resolv(IPAddr.new('203.0.113.27'), :inet).collect(&:to_s)).to eq(['203.0.113.27'])
expect(subject.resolv(IPAddr.new('2001:db8:c0ff:ee::42'), :inet).collect(&:to_s)).to eq([])
end

it 'resolves IPv6 only' do
expect(subject.resolv('example.com', :inet6).collect(&:to_s)).to eq(['2606:2800:220:1:248:1893:25c8:1946'])
expect(subject.resolv(IPAddr.new('93.184.216.34'), :inet6).collect(&:to_s)).to eq([])
expect(subject.resolv(IPAddr.new('2606:2800:220:1:248:1893:25c8:1946'), :inet6).collect(&:to_s)).to eq(['2606:2800:220:1:248:1893:25c8:1946'])
expect(subject.resolv('example.com', :inet6).collect(&:to_s)).to eq(['2001:db8:fa4e:adde::42'])
expect(subject.resolv(IPAddr.new('203.0.113.27'), :inet6).collect(&:to_s)).to eq([])
expect(subject.resolv(IPAddr.new('2001:db8:c0ff:ee::42'), :inet6).collect(&:to_s)).to eq(['2001:db8:c0ff:ee::42'])
end

it 'raises exceptions with unknown hosts' do
Expand Down
28 changes: 15 additions & 13 deletions spec/puffy/rule_factory_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ module Puffy
end

it 'passes addresses and networks' do
result = subject.build(to: [{ host: IPAddr.new('192.0.2.1') }])
result = subject.build(to: [{ host: IPAddr.new('203.0.113.42') }])

expect(result.count).to eq(1)
expect(result[0].to[:host]).to eq(IPAddr.new('192.0.2.1'))
expect(result[0].to[:host]).to eq(IPAddr.new('203.0.113.42'))

result = subject.build(to: [{ host: IPAddr.new('192.0.2.0/24') }])

Expand All @@ -39,13 +39,13 @@ module Puffy

it 'resolves hostnames' do
expect(Rule).to receive(:new).twice.and_call_original
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.com').and_return([IPAddr.new('2001:DB8::1'), IPAddr.new('192.0.2.1')])
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.com').and_return([IPAddr.new('2001:db8:fa4e:adde::42'), IPAddr.new('203.0.113.42')])

result = subject.build(to: [{ host: 'example.com' }])

expect(result.count).to eq(2)
expect(result[0].to[:host]).to eq(IPAddr.new('2001:DB8::1'))
expect(result[1].to[:host]).to eq(IPAddr.new('192.0.2.1'))
expect(result[0].to[:host]).to eq(IPAddr.new('2001:db8:fa4e:adde::42'))
expect(result[1].to[:host]).to eq(IPAddr.new('203.0.113.42'))
end

it 'accepts service names' do
Expand Down Expand Up @@ -77,18 +77,18 @@ module Puffy
end

it 'does not mix IPv4 and IPv6' do
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.net').and_return([IPAddr.new('2001:DB8::FFFF:FFFF:FFFF'), IPAddr.new('198.51.100.1')])
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.com').and_return([IPAddr.new('2001:DB8::1'), IPAddr.new('192.0.2.1')])
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.net').and_return([IPAddr.new('2001:db8:fa4e:adde::27'), IPAddr.new('203.0.113.27')])
expect(Puffy::Resolver.instance).to receive(:resolv).with('example.com').and_return([IPAddr.new('2001:db8:fa4e:adde::42'), IPAddr.new('203.0.113.42')])

expect(Rule).to receive(:new).exactly(4).times.and_call_original

result = subject.build(from: [{ host: 'example.net' }], to: [{ host: 'example.com' }])

expect(result.count).to eq(2)
expect(result[0].from[:host]).to eq(IPAddr.new('2001:DB8::FFFF:FFFF:FFFF'))
expect(result[0].to[:host]).to eq(IPAddr.new('2001:DB8::1'))
expect(result[1].from[:host]).to eq(IPAddr.new('198.51.100.1'))
expect(result[1].to[:host]).to eq(IPAddr.new('192.0.2.1'))
expect(result[0].from[:host]).to eq(IPAddr.new('2001:db8:fa4e:adde::27'))
expect(result[0].to[:host]).to eq(IPAddr.new('2001:db8:fa4e:adde::42'))
expect(result[1].from[:host]).to eq(IPAddr.new('203.0.113.27'))
expect(result[1].to[:host]).to eq(IPAddr.new('203.0.113.42'))
end

it 'filters address family' do
Expand All @@ -100,19 +100,21 @@ module Puffy
end

it 'limits scope to IP version' do
expect(Puffy::Resolver.instance).to receive(:resolv).twice.with('example.com').and_return([IPAddr.new('2001:db8:fa4e:adde::42'), IPAddr.new('203.0.113.42')])

result = []

subject.ipv4 do
result = subject.build(to: [{ host: 'example.com' }])
end
expect(result.count).to eq(1)
expect(result[0].to[:host]).to eq(IPAddr.new('93.184.216.34'))
expect(result[0].to[:host]).to eq(IPAddr.new('203.0.113.42'))

subject.ipv6 do
result = subject.build(to: [{ host: 'example.com' }])
end
expect(result.count).to eq(1)
expect(result[0].to[:host]).to eq(IPAddr.new('2606:2800:220:1:248:1893:25c8:1946'))
expect(result[0].to[:host]).to eq(IPAddr.new('2001:db8:fa4e:adde::42'))
end
end
end
14 changes: 7 additions & 7 deletions spec/puffy/rule_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ module Puffy
it 'detects IPv4 rules' do
expect(Rule.new.ipv4?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { port: 80 }).ipv4?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('192.0.2.1'), port: 80 }).ipv4?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('2001:DB8::1'), port: 80 }).ipv4?).to be_falsy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('203.0.113.42'), port: 80 }).ipv4?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('2001:db8:fa4e:adde::42'), port: 80 }).ipv4?).to be_falsy
expect(Rule.new(action: :pass, dir: :fwd, in: 'eth0', out: 'eth1').ipv4?).to be_truthy
end

it 'detects IPv6 rules' do
expect(Rule.new.ipv6?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { port: 80 }).ipv6?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('192.0.2.1'), port: 80 }).ipv6?).to be_falsy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('2001:DB8::1'), port: 80 }).ipv6?).to be_truthy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('203.0.113.42'), port: 80 }).ipv6?).to be_falsy
expect(Rule.new(action: :block, dir: :out, proto: :tcp, to: { host: IPAddr.new('2001:db8:fa4e:adde::42'), port: 80 }).ipv6?).to be_truthy
expect(Rule.new(action: :pass, dir: :fwd, in: 'eth0', out: 'eth1').ipv6?).to be_truthy
end

Expand All @@ -45,23 +45,23 @@ module Puffy
expect(Rule.new.rdr?).to be_falsy
expect(Rule.new(action: :pass, dir: :in, proto: :tcp, to: { port: 80 }).rdr?).to be_falsy
expect(Rule.new(action: :pass, dir: :out, on: 'eth0', nat_to: IPAddr.new('198.51.100.72')).rdr?).to be_falsy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('192.0.2.1') }).rdr?).to be_truthy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('203.0.113.42') }).rdr?).to be_truthy
expect(Rule.new(action: :pass, dir: :fwd, in: 'eth0', out: 'eth1').rdr?).to be_falsy
end

it 'detects NAT rules' do
expect(Rule.new.nat?).to be_falsy
expect(Rule.new(action: :pass, dir: :out, proto: :tcp, to: { port: 80 }).nat?).to be_falsy
expect(Rule.new(action: :pass, dir: :out, on: 'eth0', nat_to: IPAddr.new('198.51.100.72')).nat?).to be_truthy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('192.0.2.1') }).nat?).to be_falsy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('203.0.113.42') }).nat?).to be_falsy
expect(Rule.new(action: :pass, dir: :fwd, in: 'eth0', out: 'eth1').nat?).to be_falsy
end

it 'detects forward rules' do
expect(Rule.new.fwd?).to be_falsy
expect(Rule.new(action: :pass, dir: :out, proto: :tcp, to: { port: 80 }).fwd?).to be_falsy
expect(Rule.new(action: :pass, dir: :out, on: 'eth0', nat_to: IPAddr.new('198.51.100.72')).fwd?).to be_falsy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('192.0.2.1') }).fwd?).to be_falsy
expect(Rule.new(action: :pass, dir: :in, on: 'eth0', rdr_to: { host: IPAddr.new('203.0.113.42') }).fwd?).to be_falsy
expect(Rule.new(action: :pass, dir: :fwd, in: 'eth0', out: 'eth1').fwd?).to be_truthy
end

Expand Down

0 comments on commit 435b50c

Please sign in to comment.