catflap 0.0.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +7 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +49 -0
- data/LICENSE +20 -0
- data/README.md +134 -0
- data/Rakefile +6 -0
- data/bin/catflap +71 -64
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/catflap.gemspec +32 -0
- data/etc/config.yaml +30 -0
- data/etc/init.d/catflap +89 -0
- data/etc/passfile.yaml +7 -0
- data/lib/catflap.rb +108 -64
- data/lib/catflap/command.rb +102 -0
- data/lib/catflap/firewall.rb +56 -0
- data/lib/catflap/http.rb +288 -0
- data/lib/catflap/netfilter/writer.rb +127 -0
- data/lib/catflap/plugins/firewall/iptables.rb +104 -0
- data/lib/catflap/plugins/firewall/netfilter.rb +114 -0
- data/lib/catflap/plugins/firewall/plugin.rb +67 -0
- data/lib/catflap/version.rb +5 -0
- data/lib/netfilter/writer.rb +125 -0
- data/ui/css/catflap.css +44 -0
- data/ui/images/catflap.png +0 -0
- data/ui/index.rhtml +23 -0
- data/ui/js/catflap.js +85 -0
- data/ui/js/sha256.js +166 -0
- metadata +109 -11
- data/lib/catflap-http.rb +0 -111
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'resolv'
|
3
|
+
|
4
|
+
# Mixin module to add functions to firewall drivers that need them.
|
5
|
+
#
|
6
|
+
# It is methods in this module that must run with root privileges and at some
|
7
|
+
# point it may form the basis for a micro-service that runs with system
|
8
|
+
# privileges, so that the webserver can be run as a non-privileged user.
|
9
|
+
#
|
10
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
11
|
+
module Firewall
|
12
|
+
# http://blog.markhatton.co.uk/2011/03/15/regular-expressions-for-ip-addresses-cidr-ranges-and-hostnames/
|
13
|
+
CIDR_PATTERN = %r{
|
14
|
+
^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}
|
15
|
+
([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])
|
16
|
+
(\/([0-9]|[1-2][0-9]|3[0-2]))?$
|
17
|
+
}x
|
18
|
+
|
19
|
+
# Execute firewall commands in a forked process.
|
20
|
+
# @param [String] output firewall command string to be forked and executed.
|
21
|
+
# @return [String] any output returned by the forked process is returned.
|
22
|
+
# @raise StandardError when the forked process returns an error.
|
23
|
+
|
24
|
+
def execute(output)
|
25
|
+
puts output if @verbose
|
26
|
+
return if @noop
|
27
|
+
|
28
|
+
out, err, = Open3.capture3 output << ' 2>/dev/null'
|
29
|
+
raise err if err != ''
|
30
|
+
out
|
31
|
+
end
|
32
|
+
|
33
|
+
# Execute firewall command in forked process to see if it emits an error.
|
34
|
+
#
|
35
|
+
# This is used by the 'check' command to see if an IP rule is already in the
|
36
|
+
# firewall allow chain or not. It is to get around the quirkiness of the
|
37
|
+
# netfilter -C check rule.
|
38
|
+
# @param [String] output firewall command string to be forked and executed.
|
39
|
+
# @return [Boolean] true if no error was returned from the forked process.
|
40
|
+
|
41
|
+
def execute_true?(output)
|
42
|
+
_, err, = Open3.capture3 output << ' 2>/dev/null'
|
43
|
+
(err == '')
|
44
|
+
end
|
45
|
+
|
46
|
+
# Data validation method to ensure that user-submitted IP addresses can be
|
47
|
+
# resolved to a valid IP address. This prevents any attempt at a shell/command
|
48
|
+
# injection attack.
|
49
|
+
# @param [String] suspect the user-submitted address to be validated.
|
50
|
+
# @raise Resolve::ResolvError if string doesn't resolve to an IP address.
|
51
|
+
|
52
|
+
def assert_valid_ipaddr(suspect)
|
53
|
+
return suspect if suspect =~ CIDR_PATTERN
|
54
|
+
Resolv.getaddress(suspect) # raises Resolv::ResolvError on bad IP.
|
55
|
+
end
|
56
|
+
end
|
data/lib/catflap/http.rb
ADDED
@@ -0,0 +1,288 @@
|
|
1
|
+
require 'catflap'
|
2
|
+
require 'webrick'
|
3
|
+
require 'json'
|
4
|
+
include WEBrick
|
5
|
+
|
6
|
+
##
|
7
|
+
# Module to implement a WEBrick web server.
|
8
|
+
#
|
9
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
10
|
+
module CfWebserver
|
11
|
+
# Add a mime type for *.rhtml files
|
12
|
+
HTTPUtils::DefaultMimeTypes.store('rhtml', 'text/html')
|
13
|
+
|
14
|
+
# Factory method to generate a new WEBrick server.
|
15
|
+
# @param [Catflap] cf a fully instantiated Catflap object.
|
16
|
+
# @param [Boolean] https set to true to generate an HTTPS server.
|
17
|
+
# @return void
|
18
|
+
def generate_server(cf, https = false)
|
19
|
+
port = (https == true) ? cf.https['port'] : cf.port
|
20
|
+
|
21
|
+
# Expand relative paths - particularly important for daemonizing.
|
22
|
+
docroot = File.expand_path cf.docroot
|
23
|
+
|
24
|
+
config = {
|
25
|
+
BindAddress: cf.listen_addr,
|
26
|
+
Port: port,
|
27
|
+
DocumentRoot: docroot,
|
28
|
+
StartCallback: lambda do
|
29
|
+
# Write the pid to file when the server starts.
|
30
|
+
if File.writable? cf.pid_path
|
31
|
+
File.open(get_pidfile(cf, https), 'w') do |file|
|
32
|
+
file.puts Process.pid.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end,
|
36
|
+
StopCallback: lambda do
|
37
|
+
# Delete the pid file when the server shuts down.
|
38
|
+
pidfile = get_pidfile(cf, https)
|
39
|
+
File.delete pidfile if File.exist? pidfile
|
40
|
+
end
|
41
|
+
}
|
42
|
+
|
43
|
+
config[:ServerType] = Daemon if cf.daemonize
|
44
|
+
|
45
|
+
if https
|
46
|
+
require 'webrick/https'
|
47
|
+
require 'openssl'
|
48
|
+
|
49
|
+
if File.readable? cf.https['certificate']
|
50
|
+
cert = OpenSSL::X509::Certificate.new File.read cf.https['certificate']
|
51
|
+
end
|
52
|
+
|
53
|
+
if File.readable? cf.https['private_key']
|
54
|
+
pkey = OpenSSL::PKey::RSA.new File.read cf.https['private_key']
|
55
|
+
end
|
56
|
+
|
57
|
+
config[:SSLEnable] = true
|
58
|
+
|
59
|
+
if cert && pkey
|
60
|
+
config[:SSLCertificate] = cert
|
61
|
+
config[:SSLPrivateKey] = pkey
|
62
|
+
else
|
63
|
+
# We don't have a certificate so generate a new self-signed certificate.
|
64
|
+
config[:SSLCertName] = [%w(CN localhost)]
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
server = HTTPServer.new(config)
|
70
|
+
yield server if block_given?
|
71
|
+
|
72
|
+
%w(INT TERM).each do |signal|
|
73
|
+
trap(signal) { server.shutdown }
|
74
|
+
end
|
75
|
+
|
76
|
+
server.start
|
77
|
+
end
|
78
|
+
|
79
|
+
# Method to start the WEBrick web server.
|
80
|
+
# @param [Catflap] cf a fully instantiated Catflap object.
|
81
|
+
# @param [Boolean] https set to true to start an HTTPS server.
|
82
|
+
# @return void
|
83
|
+
def server_start(cf, https = false)
|
84
|
+
generate_server(cf, https) do |server|
|
85
|
+
server.mount cf.endpoint, CfApiServlet, cf
|
86
|
+
|
87
|
+
# Redirect HTTP to HTTPS if force option is set.
|
88
|
+
if !https && cf.https['force']
|
89
|
+
server.mount_proc '/' do |req, res|
|
90
|
+
res.set_redirect WEBrick::HTTPStatus::TemporaryRedirect,
|
91
|
+
'https://' + req.server_name
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Method to stop the WEBrick web server.
|
98
|
+
# @param [Catflap] cf a fully instantiated Catflap object.
|
99
|
+
# @param [Boolean] https set to true to stop an HTTPS server.
|
100
|
+
# @return [Boolean] true if process termination was successful.
|
101
|
+
def server_stop(cf, https = false)
|
102
|
+
pid = get_pid(cf, https)
|
103
|
+
Process.kill('INT', pid) if pid > 0
|
104
|
+
end
|
105
|
+
|
106
|
+
# Method to get the status of the WEBrick web server and process id.
|
107
|
+
# @param [Catflap] cf a fully instantiated Catflap object.
|
108
|
+
# @param [Boolean] https set to true to get status of an HTTPS server.
|
109
|
+
# @return [Hash<Sym, Object>] the process id or 0 if there is no pid.
|
110
|
+
def server_status(cf, https = false)
|
111
|
+
{
|
112
|
+
pid: get_pid(cf, https),
|
113
|
+
address: cf.listen_addr,
|
114
|
+
port: https ? cf.https['port'] : cf.port
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
# Method to get the process id of the running process.
|
119
|
+
# @param [Catflap] cf a fully instantiated Catflap object.
|
120
|
+
# @param [Boolean] https set to true to get process id of an HTTPS server.
|
121
|
+
# @return [Integer] the process id or 0 if there is no pid.
|
122
|
+
def get_pid(cf, https = false)
|
123
|
+
pidfile = get_pidfile(cf, https)
|
124
|
+
pid = nil
|
125
|
+
if File.readable? pidfile
|
126
|
+
File.open(pidfile, 'r') do |file|
|
127
|
+
pid = file.readline
|
128
|
+
end
|
129
|
+
end
|
130
|
+
pid.to_i
|
131
|
+
end
|
132
|
+
|
133
|
+
# Method to get the pid file path
|
134
|
+
# @param [Catflap] cf a fully instantiated Catflap object.
|
135
|
+
# @param [Boolean] https set to true to get pidfile of an HTTPS server.
|
136
|
+
# @return [String] the file path to the pid file.
|
137
|
+
def get_pidfile(cf, https = false)
|
138
|
+
filename = https ? 'catflap-https.pid' : 'catflap-http.pid'
|
139
|
+
cf.pid_path + File::SEPARATOR + filename
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# A WEBrick servlet class to handle API requrests
|
144
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
145
|
+
class CfApiServlet < HTTPServlet::AbstractServlet
|
146
|
+
# Initializer to construct a new CfApiServlet object.
|
147
|
+
# @param [HTTPServer] server a WEBrick HTTP server object.
|
148
|
+
# @param [Catflap] cf a fully instantiated Catflap object.
|
149
|
+
# @return void
|
150
|
+
def initialize(server, cf)
|
151
|
+
super server
|
152
|
+
@cf = cf
|
153
|
+
end
|
154
|
+
|
155
|
+
# Implementation of HTTPServlet::AbstractServlet method to handle GET
|
156
|
+
# method requests.
|
157
|
+
# @param [HTTPRequest] req a WEBrick::HTTPRequest object.
|
158
|
+
# @param [HTTPResponse] resp a WEBrick::HTTPResponse object.
|
159
|
+
# @return void
|
160
|
+
# rubocop:disable Style/MethodName
|
161
|
+
def do_POST(req, resp)
|
162
|
+
# Split the path into piece
|
163
|
+
path = req.path[1..-1].split('/')
|
164
|
+
|
165
|
+
# We don't want to cache catflap login page so set response headers.
|
166
|
+
# Chrome and FF respect the no-store, while IE respects no-cache.
|
167
|
+
resp['Cache-Control'] = 'no-cache, no-store'
|
168
|
+
resp['Pragma'] = 'no-cache' # Legacy
|
169
|
+
resp['Expires'] = '-1' # Microsoft advises this for older IE browsers.
|
170
|
+
|
171
|
+
response_class = CfRestService.const_get 'CfRestService'
|
172
|
+
|
173
|
+
raise "#{response_class} not a Class" unless response_class.is_a?(Class)
|
174
|
+
|
175
|
+
raise HTTPStatus::NotFound unless path[1]
|
176
|
+
|
177
|
+
response_method = path[1].to_sym
|
178
|
+
# Make sure the method exists in the class
|
179
|
+
raise HTTPStatus::NotFound unless response_class
|
180
|
+
.respond_to? response_method
|
181
|
+
|
182
|
+
if :sync == response_method
|
183
|
+
resp.body = response_class.send response_method, req, resp, @cf
|
184
|
+
end
|
185
|
+
|
186
|
+
if :knock == response_method
|
187
|
+
resp.body = response_class.send response_method, req, resp, @cf
|
188
|
+
end
|
189
|
+
|
190
|
+
# Remaining path segments get passed in as arguments to the method
|
191
|
+
if path.length > 2
|
192
|
+
resp.body = response_class.send response_method, req, resp,
|
193
|
+
@cf, path[1..-1]
|
194
|
+
else
|
195
|
+
resp.body = response_class.send response_method, req, resp, @cf
|
196
|
+
end
|
197
|
+
raise HTTPStatus::OK
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
# rubocop:enable Style/MethodName
|
202
|
+
|
203
|
+
##
|
204
|
+
# REST service to handle REST requests from CfApiServlet.
|
205
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
206
|
+
module CfRestService
|
207
|
+
# REST service handler Class
|
208
|
+
class CfRestService
|
209
|
+
# Numeric response code indicating that the token has expired.
|
210
|
+
STATUS_TOKEN_EXPIRED = 405
|
211
|
+
# Numeric response code indicating that the handshake was ok.
|
212
|
+
STATUS_SYNC_OK = 200
|
213
|
+
# Numeric response code indicating a failed authentication attempt.
|
214
|
+
STATUS_AUTH_FAIL = 401
|
215
|
+
# Numeric response code indicating a successful authentication attempt.
|
216
|
+
STATUS_AUTH_PASS = 200
|
217
|
+
|
218
|
+
# Handler method for handling sync/timestamp requests for handshaking.
|
219
|
+
#
|
220
|
+
# This is a handshake request from the browser for a timestamp to use
|
221
|
+
# to encrypt the pass phrase. This timestamp is passed back along with
|
222
|
+
# pass phrase. If the timestamp is older than the expiry then the token
|
223
|
+
# will be rejected. If the timestamp has not expired it will be used to
|
224
|
+
# generate a matching token for authentication.
|
225
|
+
# @return [Integer] an integer representation of a unix timestamp.
|
226
|
+
|
227
|
+
def self.sync(_req, _res, _cf)
|
228
|
+
result = {
|
229
|
+
Status: 'Handshake sync OK',
|
230
|
+
StatusCode: STATUS_SYNC_OK,
|
231
|
+
Timestamp: Time.new.to_i
|
232
|
+
}
|
233
|
+
JSON.generate(result)
|
234
|
+
end
|
235
|
+
|
236
|
+
# Handler method for handling knock requests for authentication.
|
237
|
+
# @param [WEBRick::HTTPRequest] req a WEBrick request object.
|
238
|
+
# @param [WEBrick::HTTPResponse] resp a WEBrick response object.
|
239
|
+
# @param [Catflap] cf a fully instantiated Catflap object.
|
240
|
+
# @return void
|
241
|
+
|
242
|
+
def self.knock(req, _resp, cf)
|
243
|
+
ip = req.peeraddr.pop
|
244
|
+
query = req.query
|
245
|
+
passkey = query['_key']
|
246
|
+
|
247
|
+
# Calculate difference between the timestamp sent to the client and
|
248
|
+
# the current timestamp.
|
249
|
+
ts_delta = Time.new.to_i - query['ts'].to_i
|
250
|
+
|
251
|
+
if ts_delta > cf.token_ttl
|
252
|
+
result = {
|
253
|
+
Status: 'Expired Token',
|
254
|
+
StatusCode: STATUS_TOKEN_EXPIRED
|
255
|
+
}
|
256
|
+
JSON.generate(result)
|
257
|
+
end
|
258
|
+
|
259
|
+
# If we have a matching key in the passfile then create a test token.
|
260
|
+
unless cf.passphrases[query['_key']].nil?
|
261
|
+
test_token = cf.generate_token(cf.passphrases[passkey], query['ts'])
|
262
|
+
end
|
263
|
+
|
264
|
+
# by default we tell the browser to reload the page, but we can configure
|
265
|
+
# catflap in the configuration file to redirect to some other URL.
|
266
|
+
redirect_url = cf.redirect_url ? cf.redirect_url : 'reload'
|
267
|
+
|
268
|
+
if test_token && test_token == query['token']
|
269
|
+
# The tokens matched and validated so we add the address and respond
|
270
|
+
# to the browser.
|
271
|
+
cf.firewall.add_address ip unless cf.firewall.check_address(ip)
|
272
|
+
|
273
|
+
result = {
|
274
|
+
Status: 'Authenticated',
|
275
|
+
StatusCode: STATUS_AUTH_PASS,
|
276
|
+
RedirectUrl: redirect_url
|
277
|
+
}
|
278
|
+
|
279
|
+
else
|
280
|
+
result = {
|
281
|
+
Status: 'Authentication failed',
|
282
|
+
StatusCode: STATUS_AUTH_FAIL
|
283
|
+
}
|
284
|
+
end
|
285
|
+
JSON.generate(result)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'catflap/firewall'
|
2
|
+
include Firewall
|
3
|
+
|
4
|
+
# Mixin module to add rule handling functions to netfilter-based drivers.
|
5
|
+
#
|
6
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
7
|
+
module NetfilterWriter
|
8
|
+
# Class providing a DSL for defining netfilter rules
|
9
|
+
# @author Nyk Cowham <nykcowham@gmail.com>
|
10
|
+
class Rules
|
11
|
+
attr_accessor :noop, :verbose, :match
|
12
|
+
|
13
|
+
def initialize(table, dports = nil)
|
14
|
+
@table = table
|
15
|
+
@dports = dports
|
16
|
+
@match = nil
|
17
|
+
@buffer = ''
|
18
|
+
end
|
19
|
+
|
20
|
+
# Chainable setter function: change the default table for rules.
|
21
|
+
# @param [String] table the name of the table (e.g. 'nat', 'filter', etc.)
|
22
|
+
# @return self
|
23
|
+
def table(table)
|
24
|
+
@table = table
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
# Chainable setter function: change the default destination ports for rules.
|
29
|
+
# @param [String] table the name of the table (e.g. 'nat', 'filter', etc.)
|
30
|
+
# @return self
|
31
|
+
def dports(dports)
|
32
|
+
@dports = dports
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create, flush and delete chains and other iptable chain operations.
|
37
|
+
# @param [Symbol] cmd the operation to perform (:new, :delete, :flush)
|
38
|
+
# @param [String] chain name of the chain (e.g. INPUT, CATFLAP-DENY, etc.)
|
39
|
+
# @param [Hash] p parameters for specific iptables features.
|
40
|
+
# @return self
|
41
|
+
# @example
|
42
|
+
# Rules.new('nat').chain(:list, 'MY-CHAIN', numeric: true).flush
|
43
|
+
# => "iptables -t nat -n -L MY-CHAIN"
|
44
|
+
def chain(cmd, chain, p = {})
|
45
|
+
cmds = {
|
46
|
+
new: '-N', rename: '-E', delete: '-X', flush: '-F',
|
47
|
+
list_rules: '-S', list: '-L', zero: '-Z', policy: '-P'
|
48
|
+
}
|
49
|
+
|
50
|
+
@buffer << [
|
51
|
+
'iptables',
|
52
|
+
option('-t', @table), cmds[cmd], option('-n', p[:numeric]), chain,
|
53
|
+
option(false, p[:rulenum]), option(false, p[:to])
|
54
|
+
].compact.join(' ') << "\n"
|
55
|
+
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# Create, flush and delete chains
|
60
|
+
# @param [String] cmd the operation to perform (add, delete, insert, etc.)
|
61
|
+
# @param [Hash] p parameters for specific iptables features.
|
62
|
+
# @param [Block] block will evaluate a block that will return true/false.
|
63
|
+
# @return self
|
64
|
+
def rule(cmd, p, &block)
|
65
|
+
# Evaluate a block expression and return early if it evaluates to false.
|
66
|
+
# If no block is passed it is equivalent to the block: { true }.
|
67
|
+
return self if block_given? && !instance_eval(&block)
|
68
|
+
|
69
|
+
raise ArgumentError, 'chain is a required argument' unless p[:chain]
|
70
|
+
assert_valid_ipaddr(p[:src]) if p[:src]
|
71
|
+
assert_valid_ipaddr(p[:dst]) if p[:dst]
|
72
|
+
|
73
|
+
# Map of commands for rules
|
74
|
+
cmds = {
|
75
|
+
add: '-A', delete: '-D', insert: '-I', replace: '-R', check: '-C'
|
76
|
+
}
|
77
|
+
|
78
|
+
@buffer << [
|
79
|
+
'iptables', option('-t', @table), cmds[cmd], p[:chain],
|
80
|
+
option(false, p[:rulenum]), option('-f', p[:frag]),
|
81
|
+
option('-s', p[:src]), option('-d', p[:dst]),
|
82
|
+
option('-o', p[:out]), option('-i', p[:in]),
|
83
|
+
option('-p', p[:proto] || 'tcp'), option('-m', p[:match] || @match),
|
84
|
+
option('--sport', p[:sports] || @sports),
|
85
|
+
option('--dport', p[:dports] || @dports), p[:jump] || p[:goto],
|
86
|
+
option('--to-port', p[:to_port])
|
87
|
+
].compact.join(' ') << "\n"
|
88
|
+
|
89
|
+
self
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def option(flag, value)
|
94
|
+
return flag if value.is_a?(TrueClass)
|
95
|
+
return flag.insert(0, '!') if value.is_a?(FalseClass)
|
96
|
+
return value if flag.is_a?(FalseClass)
|
97
|
+
flag << ' ' << value.to_s if flag && value
|
98
|
+
end
|
99
|
+
|
100
|
+
# Add a raw text rule, (e.g.: iptables -t nat CATFLAP-ALLOW ...)
|
101
|
+
# @param [String] raw_rule custom raw iptables command.
|
102
|
+
# @return self
|
103
|
+
def raw(raw_rule)
|
104
|
+
@buffer = raw_rule
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
# Flush the rule buffer and output the resulting iptables commands.
|
109
|
+
# @return [String] rule text that can be sent iptables user-space client.
|
110
|
+
def flush
|
111
|
+
out = @buffer
|
112
|
+
@buffer = ''
|
113
|
+
out
|
114
|
+
end
|
115
|
+
|
116
|
+
# Flush the rule and execute in iptables user-space client.
|
117
|
+
# @return void
|
118
|
+
def do
|
119
|
+
execute flush
|
120
|
+
end
|
121
|
+
|
122
|
+
# Flush the rule and execute commands and return success/fail value.
|
123
|
+
# @return [Boolean] true if the execution was successful.
|
124
|
+
def do?
|
125
|
+
execute_true? flush
|
126
|
+
end
|
127
|
+
end
|