iface 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/iface.rb +4 -0
- data/lib/iface/config.rb +58 -0
- data/lib/iface/config_file.rb +167 -0
- data/lib/iface/ip_address.rb +13 -0
- data/lib/iface/ip_helpers.rb +33 -0
- data/lib/iface/value_set.rb +45 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0dabac73d94d38d1e887f22ad10f75ae1c40a212
|
4
|
+
data.tar.gz: 5b36a303566537a64be7b4d11bcb2030a7894baf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 72c5525357faea04f9244a122dcd92cea7de287860481db15af8450f46fa4b4febacddceaee364a4ffe995e38d5762ce6d9743f44393f35f70698d0359ec512a
|
7
|
+
data.tar.gz: 05d5d2fabe7b9b831d990bf5039a5ee1aef7fd6f6c5635011f869bdef084c2f863bdc0661f8eebb6d3cb0c8676d2786c0d389502a15460448690da58cd955d69
|
data/lib/iface.rb
ADDED
data/lib/iface/config.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'config_file'
|
4
|
+
require 'ipaddr'
|
5
|
+
|
6
|
+
module Iface
|
7
|
+
# Represents a set of ConfigFiles for network interface configuration
|
8
|
+
class Config
|
9
|
+
Reserved_IP_Ranges = %w{10.0.0.0/8 192.168.0.0/16}.collect { |i| IPAddr.new(i) }
|
10
|
+
|
11
|
+
def self.discover(pattern)
|
12
|
+
new.tap do |config|
|
13
|
+
Dir.glob(pattern) do |fullname|
|
14
|
+
puts fullname
|
15
|
+
File.open(fullname) { |io| config.add(fullname, io) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@files = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def add(filename, io)
|
25
|
+
file = ConfigFile.create(filename, io)
|
26
|
+
file_type = file.class.file_type_name
|
27
|
+
if @files.key?(file_type)
|
28
|
+
@files[file_type] << file
|
29
|
+
else
|
30
|
+
@files[file_type] = [file]
|
31
|
+
end
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the PrimaryFile
|
36
|
+
#
|
37
|
+
# There should be 0 or 1 of these; else it's an error.
|
38
|
+
def primary
|
39
|
+
result = @files[:primary].find_all do |file|
|
40
|
+
if file.ip_address.nil?
|
41
|
+
false
|
42
|
+
else
|
43
|
+
ipaddr = IPAddr.new(file.ip_address)
|
44
|
+
!Reserved_IP_Ranges.any? { |range| range.include?(ipaddr) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
case result.size
|
49
|
+
when 0
|
50
|
+
nil
|
51
|
+
when 1
|
52
|
+
result.first
|
53
|
+
else
|
54
|
+
raise RuntimeError, "Expected 0 or 1 primary files; found #{result.size}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'ip_helpers'
|
4
|
+
require_relative 'value_set'
|
5
|
+
|
6
|
+
class String
|
7
|
+
def decamelize
|
8
|
+
gsub(/[A-Z]/) {|m| $` == '' ? m.downcase : "_#{m.downcase}"}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Iface
|
13
|
+
# Base class for a network interface config file
|
14
|
+
class ConfigFile
|
15
|
+
def self.create(filename, io)
|
16
|
+
fname = File.split(filename).last
|
17
|
+
device, range_num, clone_num = parse_filename(fname)
|
18
|
+
vars = ValueSet.new(io)
|
19
|
+
|
20
|
+
FILE_TYPES.each do |klass|
|
21
|
+
if klass.recognize?(device, range_num, clone_num, vars)
|
22
|
+
return klass.new(filename, device, range_num, clone_num, vars)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
raise ArgumentError, "Input not recognized from file #{fname}: #{[device, range_num, clone_num, vars].inspect}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.parse_filename(filename)
|
30
|
+
match = filename.match(/\Aifcfg-(\w+)((-range(\d+))|(:(\d+)))?\Z/)
|
31
|
+
if match
|
32
|
+
device, _skip0, _skip1, range_num, _skip2, clone_num = match.captures
|
33
|
+
[device, range_num&.to_i, clone_num&.to_i]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.recognize?(_device, _range_num, _clone_num, _vars)
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.file_type_name
|
42
|
+
if self.name =~ /File\Z/
|
43
|
+
self.name.split('::').last[0..-5].decamelize.to_sym
|
44
|
+
else
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(filename, device, _range_num, _clone_num, _vars)
|
50
|
+
@filename = filename
|
51
|
+
@device = device
|
52
|
+
end
|
53
|
+
|
54
|
+
def static?
|
55
|
+
raise NotImplementedError
|
56
|
+
end
|
57
|
+
|
58
|
+
def include?(_ip)
|
59
|
+
raise NotImplementedError
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Represents a primary config file (not loopback, range or clone file)
|
64
|
+
#
|
65
|
+
# These are files named like "ifcfg-eth0".
|
66
|
+
class PrimaryFile < ConfigFile
|
67
|
+
attr_reader :ip_address, :ipv6_address, :ipv6_secondaries
|
68
|
+
|
69
|
+
def self.recognize?(device, range_num, clone_num, _vars)
|
70
|
+
device != 'lo' && range_num.nil? && clone_num.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(filename, device, range_num, clone_num, vars)
|
74
|
+
super
|
75
|
+
if (vars['bootproto'] == 'static') || (vars['bootproto'] == 'none') # RHEL6 uses "none"
|
76
|
+
@ip_address = vars['ipaddr']
|
77
|
+
@ipv6_address = vars['ipv6addr']
|
78
|
+
@ipv6_secondaries = vars['ipv6addr_secondaries']&.split(/\s+/)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def static?
|
83
|
+
!@ip_address.nil?
|
84
|
+
end
|
85
|
+
|
86
|
+
def include?(ip)
|
87
|
+
@ip_address == ip
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Represents a clone config file (single IP address)
|
92
|
+
#
|
93
|
+
# These are files named like "ifcfg-eth0:1".
|
94
|
+
class CloneFile < ConfigFile
|
95
|
+
attr_reader :ip_address, :clone_num
|
96
|
+
|
97
|
+
def self.recognize?(_device, _range_num, clone_num, _vars)
|
98
|
+
!clone_num.nil?
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize(filename, device, _range_num, clone_num, vars)
|
102
|
+
super
|
103
|
+
@ip_address = vars['ipaddr']
|
104
|
+
@clone_num = clone_num
|
105
|
+
end
|
106
|
+
|
107
|
+
def static?
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
def include?(ip)
|
112
|
+
@ip_address == ip
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Represents a range config file (a range of IP addresses)
|
117
|
+
#
|
118
|
+
# These are files named like "ifcfg-eth0-range0".
|
119
|
+
class RangeFile < ConfigFile
|
120
|
+
attr_reader :start_clone_num
|
121
|
+
|
122
|
+
def self.recognize?(_device, range_num, _clone_num, _vars)
|
123
|
+
!range_num.nil?
|
124
|
+
end
|
125
|
+
|
126
|
+
def initialize(filename, device, range_num, clone_num, vars)
|
127
|
+
super
|
128
|
+
@start_ip_num = string_to_ip_num(vars['ipaddr_start'])
|
129
|
+
@end_ip_num = string_to_ip_num(vars['ipaddr_end'])
|
130
|
+
@start_clone_num = vars['clonenum_start']&.to_i
|
131
|
+
end
|
132
|
+
|
133
|
+
def static?
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
137
|
+
def include?(ip)
|
138
|
+
ip_num = string_to_ip_num(ip)
|
139
|
+
@start_ip_num <= ip_num && ip_num <= @end_ip_num
|
140
|
+
end
|
141
|
+
|
142
|
+
def start_ip_num
|
143
|
+
@start_ip_num.to_ip
|
144
|
+
end
|
145
|
+
|
146
|
+
def end_ip_num
|
147
|
+
@end_ip_num.to_ip
|
148
|
+
end
|
149
|
+
|
150
|
+
def string_to_ip_num(str)
|
151
|
+
str.split('.').collect { |x| x.to_i.to_s(16).rjust(2, '0') }.join.hex
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class LoopbackFile < ConfigFile
|
156
|
+
def self.recognize?(device, _range_num, _clone_num, _vars)
|
157
|
+
device == 'lo'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
FILE_TYPES = [
|
162
|
+
PrimaryFile,
|
163
|
+
CloneFile,
|
164
|
+
RangeFile,
|
165
|
+
LoopbackFile
|
166
|
+
].freeze
|
167
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ipaddr'
|
4
|
+
|
5
|
+
module Iface
|
6
|
+
class IpAddress
|
7
|
+
def initialize(*args)
|
8
|
+
@ipaddr = IPAddr.new(*args)
|
9
|
+
@_bitmask = @ipaddr.instance_eval { @mask_addr.to_s(2) }
|
10
|
+
@inverse_mask_addr = @_bitmask.tr('01', '10').to_i(2)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Adds IP-related methods to all Integers
|
4
|
+
class Integer
|
5
|
+
Mask32_ = 0xffffffffffffffff
|
6
|
+
|
7
|
+
def to_ip(ipver = 4)
|
8
|
+
to_ipaddr(ipver).to_s
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_ipaddr4
|
12
|
+
IPAddr.new_ntoh([self].pack('N'))
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_ipaddr6
|
16
|
+
IPAddr.new_ntoh([(self >> 96), (self >> 64) & Mask32_, (self >> 32) & Mask32_, self & Mask32_].pack('N*'))
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_ipaddr(ipver = 4)
|
20
|
+
case ipver
|
21
|
+
when 4
|
22
|
+
to_ipaddr4
|
23
|
+
when 6
|
24
|
+
to_ipaddr6
|
25
|
+
else
|
26
|
+
raise ArgumentError, "Expecting argument 1 to be either 4 or 6; got #{ipver.inspect}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def max_mask_bits
|
31
|
+
to_s(2)[/(0*)$/, 1].size
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Iface
|
4
|
+
# Represents a set of NAME=value pairs
|
5
|
+
class ValueSet
|
6
|
+
# Represents a NAME=value pair
|
7
|
+
class Pair
|
8
|
+
attr_reader :name, :value
|
9
|
+
|
10
|
+
def initialize(line)
|
11
|
+
match = line.match(/(^[A-Z0-9_]+)="?(.*?)"?$/)
|
12
|
+
raise ArgumentError, "Expected pattern NAME=value; got #{line.inspect}" unless match
|
13
|
+
@name = match[1]
|
14
|
+
@value = match[2]&.sub(/^"/, '')&.sub(/"$/, '')
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"#{@name}=\"#{@value}\""
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(io)
|
23
|
+
@vars = {}
|
24
|
+
io.each_line do |line|
|
25
|
+
edited_line = line.sub(/#.*$/, '').strip
|
26
|
+
next if edited_line.empty?
|
27
|
+
pair = Pair.new(edited_line)
|
28
|
+
@vars[pair.name] = pair
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](name)
|
33
|
+
@vars[name.upcase]&.value
|
34
|
+
end
|
35
|
+
|
36
|
+
def key?(name)
|
37
|
+
@vars.key?(name.upcase)
|
38
|
+
end
|
39
|
+
alias has_key? key?
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
@vars.values.map(&:to_s).join("\n")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iface
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jim Cain
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-02 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email: camelotjim@jcain.net
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/iface.rb
|
20
|
+
- lib/iface/config.rb
|
21
|
+
- lib/iface/config_file.rb
|
22
|
+
- lib/iface/ip_address.rb
|
23
|
+
- lib/iface/ip_helpers.rb
|
24
|
+
- lib/iface/value_set.rb
|
25
|
+
homepage: http://rubygems.org/gems/hola
|
26
|
+
licenses:
|
27
|
+
- BSD-3-Clause
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 2.6.11
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: Configures network interfaces on Red Hat systems
|
49
|
+
test_files: []
|