smart_proxy_ipam 0.0.9 → 0.0.10
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.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3812263cf984e604cf0b7272bf9519cf503ea883b46a353a6f1edbcaefe7eab
|
4
|
+
data.tar.gz: 11a128c3d83dc3f6c2b4c9157846076a351c4dc0ab82c4c7b332ddcb1a23349f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 517e692d368b9febe9d8c34eba7b9e9e7e919714cf7d560d6df4483f28e0e8bb3f8ef485030cd68f08458a7c87dacb358e8a4aac1bd4882b95df0c696415587e
|
7
|
+
data.tar.gz: 5bb0c0a9bf0ce71d58d2fc234429568882d210436b21589acbc74d96a4bddcca6b6ccb4f4ae371f805368db9fa71bda6bc21c946a2ed00b52090ce7f2e5a7179
|
@@ -10,11 +10,6 @@ module Proxy::Phpipam
|
|
10
10
|
include ::Proxy::Log
|
11
11
|
helpers ::Proxy::Helpers
|
12
12
|
|
13
|
-
get '/providers' do
|
14
|
-
content_type :json
|
15
|
-
{:ipam_providers => ['phpIPAM']}.to_json
|
16
|
-
end
|
17
|
-
|
18
13
|
# Gets the next available IP address based on a given subnet
|
19
14
|
#
|
20
15
|
# Input: cidr(string): CIDR address in the format: "100.20.20.0/24"
|
@@ -29,26 +24,30 @@ module Proxy::Phpipam
|
|
29
24
|
|
30
25
|
begin
|
31
26
|
cidr = params[:cidr]
|
27
|
+
mac = params[:mac]
|
32
28
|
|
33
29
|
if not cidr
|
34
30
|
return {:error => "A 'cidr' parameter for the subnet must be provided(e.g. 100.10.10.0/24)"}.to_json
|
35
31
|
end
|
32
|
+
if not mac
|
33
|
+
return {:error => "A 'mac' address must be provided(e.g. 00:0a:95:9d:68:10)"}.to_json
|
34
|
+
end
|
36
35
|
|
37
36
|
phpipam_client = PhpipamClient.new
|
38
|
-
|
37
|
+
subnet = JSON.parse(phpipam_client.get_subnet(cidr))
|
39
38
|
|
40
|
-
if
|
39
|
+
if !subnet.kind_of?(Array) && subnet['message'] && subnet['message'].downcase == "no subnets found"
|
41
40
|
return {:error => "The specified subnet does not exist in External IPAM."}.to_json
|
42
41
|
end
|
43
|
-
|
44
|
-
subnet_id =
|
45
|
-
|
42
|
+
|
43
|
+
subnet_id = subnet[0]['id']
|
44
|
+
ip = phpipam_client.get_next_ip(subnet_id, mac, cidr)
|
46
45
|
|
47
|
-
if
|
46
|
+
if !ip.kind_of?(Array) && ip['message'] && ip['message'].downcase == "no free addresses found"
|
48
47
|
return {:error => "There are no more free addresses in subnet #{cidr}"}.to_json
|
49
48
|
end
|
50
49
|
|
51
|
-
{:cidr => cidr, :next_ip =>
|
50
|
+
{:cidr => cidr, :next_ip => ip['next_ip']}.to_json
|
52
51
|
rescue Errno::ECONNREFUSED
|
53
52
|
return {:error => "Unable to connect to External IPAM server"}.to_json
|
54
53
|
end
|
@@ -115,9 +114,9 @@ module Proxy::Phpipam
|
|
115
114
|
end
|
116
115
|
end
|
117
116
|
|
118
|
-
# Get a list of subnets for given external ipam section
|
117
|
+
# Get a list of subnets for given external ipam section/group
|
119
118
|
#
|
120
|
-
# Input:
|
119
|
+
# Input: section_name(string). The name of the external ipam section/group
|
121
120
|
# Returns: Array of subnets(as json) in "data" key on success, hash with error otherwise
|
122
121
|
# Examples:
|
123
122
|
# Response if success:
|
@@ -169,19 +168,19 @@ module Proxy::Phpipam
|
|
169
168
|
# "time":0.012
|
170
169
|
# }
|
171
170
|
# Response if :error =>
|
172
|
-
# {"error":"Unable to connect to
|
173
|
-
get '/sections/:
|
171
|
+
# {"error":"Unable to connect to External IPAM server"}
|
172
|
+
get '/sections/:section_name/subnets' do
|
174
173
|
content_type :json
|
175
174
|
|
176
175
|
begin
|
177
|
-
|
176
|
+
section_name = URI.decode(params[:section_name])
|
178
177
|
|
179
|
-
if not
|
180
|
-
return {:error => "A '
|
178
|
+
if not section_name
|
179
|
+
return {:error => "A 'section_name' must be provided"}.to_json
|
181
180
|
end
|
182
181
|
|
183
182
|
phpipam_client = PhpipamClient.new
|
184
|
-
subnets = phpipam_client.get_subnets(
|
183
|
+
subnets = phpipam_client.get_subnets(section_name)
|
185
184
|
subnets.to_json
|
186
185
|
rescue Errno::ECONNREFUSED
|
187
186
|
return {:error => "Unable to connect to External IPAM server"}.to_json
|
@@ -1,37 +1,37 @@
|
|
1
1
|
require 'yaml'
|
2
|
-
require 'logger'
|
3
2
|
require 'json'
|
4
3
|
require 'net/http'
|
4
|
+
require 'monitor'
|
5
|
+
require 'concurrent'
|
5
6
|
require 'smart_proxy_ipam/ipam'
|
6
7
|
require 'smart_proxy_ipam/ipam_main'
|
7
8
|
|
8
9
|
module Proxy::Phpipam
|
9
10
|
class PhpipamClient
|
11
|
+
include Proxy::Log
|
12
|
+
|
13
|
+
MAX_RETRIES = 5
|
14
|
+
DEFAULT_CLEANUP_INTERVAL = 120 # 2 mins
|
15
|
+
@@ip_cache = nil
|
16
|
+
|
10
17
|
def initialize
|
11
|
-
conf = Proxy::Ipam.get_config[:phpipam]
|
12
|
-
@
|
13
|
-
:url => conf[:url],
|
14
|
-
:user => conf[:user],
|
15
|
-
:password => conf[:password]
|
16
|
-
}
|
17
|
-
@api_base = "#{conf[:url]}/api/#{conf[:user]}/"
|
18
|
+
@conf = Proxy::Ipam.get_config[:phpipam]
|
19
|
+
@api_base = "#{@conf[:url]}/api/#{@conf[:user]}/"
|
18
20
|
@token = nil
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
@@ipam_reservations = {}
|
21
|
+
@m = Monitor.new
|
22
|
+
init_cache if @@ip_cache.nil?
|
23
|
+
start_cleanup_task
|
23
24
|
end
|
24
25
|
|
25
26
|
def get_subnet(cidr)
|
26
|
-
|
27
|
-
subnets = get(url)
|
27
|
+
subnets = get("subnets/cidr/#{cidr.to_s}")
|
28
28
|
response = []
|
29
29
|
|
30
30
|
if subnets
|
31
31
|
if subnets['message'] && subnets['message'].downcase == 'no subnets found'
|
32
32
|
return {:message => "no subnets found"}.to_json
|
33
33
|
else
|
34
|
-
# Only return the relevant fields
|
34
|
+
# Only return the relevant fields
|
35
35
|
subnets['data'].each do |subnet|
|
36
36
|
response.push({
|
37
37
|
:id => subnet['id'],
|
@@ -68,12 +68,16 @@ module Proxy::Phpipam
|
|
68
68
|
response
|
69
69
|
end
|
70
70
|
|
71
|
-
def get_subnets(
|
72
|
-
|
73
|
-
|
71
|
+
def get_subnets(section_name)
|
72
|
+
sections = get_sections
|
73
|
+
section = sections.select {|section| section[:name] == section_name}
|
74
|
+
return {:error => "Section '#{section_name}' not found"}.to_json if section.size == 0
|
75
|
+
section_id = section[0][:id].to_s
|
76
|
+
subnets = get("sections/#{section_id}/subnets/")
|
77
|
+
response = []
|
74
78
|
|
75
79
|
if subnets && subnets['data']
|
76
|
-
# Only return the relevant
|
80
|
+
# Only return the relevant fields
|
77
81
|
subnets['data'].each do |subnet|
|
78
82
|
response.push({
|
79
83
|
:id => subnet['id'],
|
@@ -92,7 +96,7 @@ module Proxy::Phpipam
|
|
92
96
|
usage = get_subnet_usage(subnet_id)
|
93
97
|
|
94
98
|
# We need to check subnet usage first in the case there are zero ips in the subnet. Checking
|
95
|
-
# the ip existence on an empty subnet returns a malformed response from phpIPAM, containing
|
99
|
+
# the ip existence on an empty subnet returns a malformed response from phpIPAM(v1.3), containing
|
96
100
|
# HTML in the JSON response.
|
97
101
|
if usage['data']['used'] == "0"
|
98
102
|
return {:ip => ip, :exists => false}.to_json
|
@@ -116,9 +120,120 @@ module Proxy::Phpipam
|
|
116
120
|
delete("addresses/#{ip}/#{subnet_id.to_s}/")
|
117
121
|
end
|
118
122
|
|
119
|
-
def get_next_ip(subnet_id)
|
120
|
-
|
121
|
-
|
123
|
+
def get_next_ip(subnet_id, mac, cidr)
|
124
|
+
response = get("subnets/#{subnet_id.to_s}/first_free/")
|
125
|
+
subnet_hash = @@ip_cache[cidr.to_sym]
|
126
|
+
|
127
|
+
return response if response['message']
|
128
|
+
|
129
|
+
if subnet_hash&.key?(mac.to_sym)
|
130
|
+
response['next_ip'] = @@ip_cache[cidr.to_sym][mac.to_sym]
|
131
|
+
else
|
132
|
+
new_ip = response['data']
|
133
|
+
ip_not_in_cache = subnet_hash&.key(new_ip).nil?
|
134
|
+
|
135
|
+
if ip_not_in_cache
|
136
|
+
next_ip = new_ip.to_s
|
137
|
+
add_ip_to_cache(new_ip, mac, cidr)
|
138
|
+
else
|
139
|
+
next_ip = find_new_ip(subnet_id, new_ip, mac, cidr)
|
140
|
+
end
|
141
|
+
|
142
|
+
if next_ip.nil?
|
143
|
+
response['error'] = "Unable to find another available IP address in subnet #{cidr}"
|
144
|
+
return response
|
145
|
+
end
|
146
|
+
|
147
|
+
unless usable_ip(next_ip, cidr)
|
148
|
+
response['error'] = "It is possible that there are no more free addresses in subnet #{cidr}. Available IP's may be cached, and could become available after in-memory IP cache is cleared(up to #{CLEAR_CACHE_DELAY} seconds)."
|
149
|
+
return response
|
150
|
+
end
|
151
|
+
|
152
|
+
response['next_ip'] = next_ip
|
153
|
+
end
|
154
|
+
|
155
|
+
response
|
156
|
+
end
|
157
|
+
|
158
|
+
def start_cleanup_task
|
159
|
+
logger.info("Starting allocated ip address maintenance (used by get_next_ip call).")
|
160
|
+
@timer_task = Concurrent::TimerTask.new(:execution_interval => DEFAULT_CLEANUP_INTERVAL) { init_cache }
|
161
|
+
@timer_task.execute
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
# @@ip_cache structure
|
167
|
+
# {
|
168
|
+
# "100.55.55.0/24":{
|
169
|
+
# "00:0a:95:9d:68:10": "100.55.55.1"
|
170
|
+
# },
|
171
|
+
# "123.11.33.0/24":{
|
172
|
+
# "00:0a:95:9d:68:33": "123.11.33.1",
|
173
|
+
# "00:0a:95:9d:68:34": "123.11.33.2",
|
174
|
+
# "00:0a:95:9d:68:35": "123.11.33.3"
|
175
|
+
# }
|
176
|
+
# }
|
177
|
+
def init_cache
|
178
|
+
logger.debug("Clearing ip cache.")
|
179
|
+
@m.synchronize do
|
180
|
+
@@ip_cache = {}
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def add_ip_to_cache(ip, mac, cidr)
|
185
|
+
logger.debug("Adding IP #{ip} to cache for subnet #{cidr}")
|
186
|
+
@m.synchronize do
|
187
|
+
if @@ip_cache.key?(cidr.to_sym)
|
188
|
+
@@ip_cache[cidr.to_sym][mac.to_sym] = ip.to_s
|
189
|
+
else
|
190
|
+
@@ip_cache = @@ip_cache.merge({cidr.to_sym => {mac.to_sym => ip.to_s}})
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Called when next available IP from external IPAM has been cached by another user/host, but
|
196
|
+
# not actually persisted in external IPAM. Try to increment the IP(MAX_RETRIES times), and
|
197
|
+
# see if it is available in external IPAM.
|
198
|
+
def find_new_ip(subnet_id, ip, mac, cidr)
|
199
|
+
found_ip = nil
|
200
|
+
temp_ip = ip
|
201
|
+
retry_count = 0
|
202
|
+
|
203
|
+
loop do
|
204
|
+
new_ip = increment_ip(temp_ip)
|
205
|
+
verify_ip = JSON.parse(ip_exists(new_ip, subnet_id))
|
206
|
+
|
207
|
+
# If new IP doesn't exist in IPAM and not in the cache
|
208
|
+
if verify_ip['exists'] == false && !ip_exists_in_cache(new_ip, cidr)
|
209
|
+
found_ip = new_ip.to_s
|
210
|
+
add_ip_to_cache(found_ip, mac, cidr)
|
211
|
+
break
|
212
|
+
end
|
213
|
+
|
214
|
+
temp_ip = new_ip
|
215
|
+
retry_count += 1
|
216
|
+
break if retry_count >= MAX_RETRIES
|
217
|
+
end
|
218
|
+
|
219
|
+
# Return the original IP found in external ipam if no new ones found after MAX_RETRIES
|
220
|
+
return ip if found_ip.nil?
|
221
|
+
|
222
|
+
found_ip
|
223
|
+
end
|
224
|
+
|
225
|
+
def increment_ip(ip)
|
226
|
+
IPAddr.new(ip.to_s).succ.to_s
|
227
|
+
end
|
228
|
+
|
229
|
+
def ip_exists_in_cache(ip, cidr)
|
230
|
+
@@ip_cache[cidr.to_sym] && !@@ip_cache[cidr.to_sym].key(ip).nil?
|
231
|
+
end
|
232
|
+
|
233
|
+
# Checks if given IP is within a subnet. Broadcast address is considered unusable
|
234
|
+
def usable_ip(ip, cidr)
|
235
|
+
network = IPAddr.new(cidr)
|
236
|
+
network.include?(IPAddr.new(ip)) && network.to_range.last != ip
|
122
237
|
end
|
123
238
|
|
124
239
|
def get(path, body=nil)
|
@@ -166,7 +281,7 @@ module Proxy::Phpipam
|
|
166
281
|
def authenticate
|
167
282
|
auth_uri = URI(@api_base + '/user/')
|
168
283
|
request = Net::HTTP::Post.new(auth_uri)
|
169
|
-
request.basic_auth @
|
284
|
+
request.basic_auth @conf[:user], @conf[:password]
|
170
285
|
|
171
286
|
response = Net::HTTP.start(auth_uri.hostname, auth_uri.port) {|http|
|
172
287
|
http.request(request)
|
@@ -174,6 +289,6 @@ module Proxy::Phpipam
|
|
174
289
|
|
175
290
|
response = JSON.parse(response.body)
|
176
291
|
@token = response['data']['token']
|
177
|
-
end
|
292
|
+
end
|
178
293
|
end
|
179
294
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_proxy_ipam
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christopher Smith
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Smart proxy plugin for IPAM integration with various IPAM providers
|
14
14
|
email: chrisjsmith001@gmail.com
|
@@ -49,8 +49,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
49
|
- !ruby/object:Gem::Version
|
50
50
|
version: '0'
|
51
51
|
requirements: []
|
52
|
-
|
53
|
-
rubygems_version: 2.7.7
|
52
|
+
rubygems_version: 3.0.6
|
54
53
|
signing_key:
|
55
54
|
specification_version: 4
|
56
55
|
summary: Smart proxy plugin for IPAM integration with various IPAM providers
|