50a0ed0a3eec1a9f2c9045ea8fcadf5fb592547b
[vpp.git] / vnet / vnet / map / examples / mapalgs.py
1 #!/usr/bin/env python3
2
3 # The MIT License (MIT)
4 #
5 # Copyright (c) 2015 
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in all
15 # copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 # SOFTWARE.
24
25 # File included from https://github.com/ejordangottlieb/pyswmap
26 # Thanks to jordan ;)
27 # - Pierre
28 #
29
30 # There is still a great deal of work required on this module.  Please
31 # use with caution.  
32 # -Jordan 
33
34 import sys
35 from ipaddress import (
36         IPv6Address,
37         IPv6Network,
38         ip_network,
39         ip_address,
40         )
41 from math import (
42         log,
43         )
44
45 class MapCalc(object):
46
47     def __init__(self,**bmr):
48         #rulev6,rulev4):
49         self.portranges = False
50
51         # Validate and set BMR and BMR derived values 
52         self._check_bmr_values(bmr)
53
54     def _check_bmr_values(self,bmr):
55         # Assume these values have not been supplied.  Validate later.
56         self.ealen = False
57         self.ratio = False
58
59         # Validate that a proper PSID Offset has been set
60         if 'psidoffset' not in bmr:
61             # Set Default PSID Offset of 6 if it is not set 
62             self.psidoffset = 6
63         else:
64             self.psidoffset = self._psid_offset(bmr['psidoffset'])
65
66         # Validate that a proper IPv4 rule prefix is defined
67         if 'rulev4' not in bmr:
68             print("The rule IPv4 prefix has not been set")
69             sys.exit(1)
70         else:
71             self.rulev4 = self._ipv4_rule(bmr['rulev4'])
72
73         # Validate that a proper IPv6 rule prefix is defined
74         if 'rulev6' not in bmr:
75             print("The rule IPv6 prefix has not been set")
76             sys.exit(1)
77         else:
78             self.rulev6 = self._ipv6_rule(bmr['rulev6'])
79
80         # Check if EA length was passed
81         if 'ealen' not in bmr:
82             self.ealen = False
83         else:
84             self.ealen = bmr['ealen']
85             self.ratio = self._calc_ratio(bmr['ealen'])
86
87         # Check if sharing ratio was passed or calculated by _calc_ratio
88         if 'ratio' not in bmr:
89             # Skip if we have already calculated ratio
90             if not (self.ratio):
91                 self.ratio = False
92         else:
93             if (self.ealen):
94                 # Check to see if supplied EA length contradicts supplied ratio
95                 if ( bmr['ratio'] != self.ratio ):
96                     eavalue = "EA value {}".format(self.ealen)
97                     sharingratio = "sharing ratio {}".format(bmr['ratio'])
98                     print("Supplied {} and {} are contradictory".format(
99                                                                   eavalue,
100                                                                   sharingratio)
101                          )
102                     sys.exit(1)
103             else:
104                 self.ratio = bmr['ratio']
105                 self.ealen = self._calc_ea(bmr['ratio'])
106
107         # EA length or sharing ratio must be set
108         if not ( self.ealen or self.ratio):
109             print("The BMR must include an EA length or sharing ratio")
110             sys.exit(1)
111
112         # Since we have not hit an exception we can calculate the port bits
113         self.portbits = self._calc_port_bits()
114
115     def _ipv4_rule(self,rulev4):
116         try:
117             self.rulev4mask = ip_network(
118                         rulev4,
119                         strict=False
120                         ).prefixlen
121         except ValueError: 
122             print("Invalid IPv4 prefix {}".format(rulev4))
123             sys.exit(1)
124
125         self.rulev4object = ip_network(rulev4)
126
127         return rulev4
128
129     def _ipv6_rule(self,rulev6):
130         try:
131             self.rulev6mask = IPv6Network(
132                         rulev6,
133                         strict=False
134                         ).prefixlen
135         except ValueError:
136             print("Invalid IPv6 prefix {}".format(rulev6))
137             sys.exit(1)
138
139         return rulev6
140
141     def _psid_offset(self,psidoffset):
142         PSIDOFFSET_MAX = 6
143         if psidoffset in range(0,PSIDOFFSET_MAX+1):
144             return psidoffset
145         else:
146             print("Invalid PSID Offset value: {}".format(psidoffset))
147             sys.exit(1)
148
149     def _psid_range(self,x):
150         rset = []
151         for i in range(0,x+1):
152             rset.append(2**i)
153         return rset
154
155     def _calc_port_bits(self):
156         portbits = 16 - self.psidoffset - self.psidbits
157         return portbits
158
159     def _calc_ea(self,ratio):
160         if ratio not in ( self._psid_range(16) ):
161             print("Invalid ratio {}".format(ratio))
162             print("Ratio between 2 to the power of 0 thru 16")
163             sys.exit(1)
164
165         if ( 1 == ratio):
166             self.psidbits = 0
167         else:
168             self.psidbits = int(log(ratio,2))
169         ealen = self.psidbits + ( 32 - self.rulev4mask )
170         return ealen
171
172     def _calc_ratio(self,ealen):
173         maskbits = 32 - self.rulev4mask
174         if ( ealen < maskbits ):
175             print("EA of {} incompatible with rule IPv4 prefix {}".format(
176               ealen,
177               self.rulev4,
178               )
179             )
180             print("EA length must be at least {} bits".format(
181               maskbits,
182               )
183             )
184             sys.exit(1)
185
186         self.psidbits = ealen - ( 32 - self.rulev4mask )
187         if ( self.psidbits > 16):
188             print("EA length of {} is too large".format(
189               ealen,
190               )
191             )
192             print("EA should not exceed {} for rule IPv4 prefix {}".format(
193               maskbits + 16,
194               self.rulev4,
195               )
196             )
197             sys.exit(1)
198         ratio = 2**self.psidbits
199         return ratio
200
201     def gen_psid(self,portnum):
202         if ( portnum < self.start_port() ):
203             print("port value is less than allowed by PSID Offset")
204             sys.exit(1)
205         psid = (portnum & ((2**self.psidbits - 1) << self.portbits))
206         psid = psid >> self.portbits
207         return psid
208
209     def port_ranges(self):
210         return 2**self.psidoffset - 1
211
212     def start_port(self):
213         if self.psidoffset == 0: return 0  
214         return 2**(16 - self.psidoffset)
215
216     def port_list(self,psid):
217         startrange = psid * (2**self.portbits) + self.start_port()
218         increment = (2**self.psidbits) * (2**self.portbits)
219         portlist = [ ]
220         for port in range(startrange,startrange + 2**self.portbits):
221             if port >= 65536: continue
222             portlist.append(port)
223         for x in range(1,self.port_ranges()):
224             startrange += increment
225             for port in range(startrange,startrange + 2**self.portbits):
226                 portlist.append(port)
227         return portlist
228
229     def ipv4_index(self,ipv4addr):
230         if ip_address(ipv4addr) in ip_network(self.rulev4):
231             x = ip_address(ipv4addr)
232             y = ip_network(self.rulev4,strict=False).network_address
233             self.ipv4addr = x
234             return ( int(x) - int(y) )
235         else:
236             print("Error: IPv4 address {} not in Rule IPv4 subnet {}".format(
237                   ipv4add,
238                   ip_network(self.rulev4,strict=False).network_address))
239             sys.exit(1)
240
241     def _calc_ipv6bit_pos(self):
242         addroffset = 128 - (self.rulev6mask + ( self.ealen - self.psidbits))
243         psidshift = 128 - ( self.rulev6mask + self.ealen )
244         return [addroffset,psidshift]
245
246     def _append_map_eabits(self,ipv4index,addroffset,psidshift,psid):
247         rulev6base = IPv6Network(self.rulev6,strict=False).network_address
248         map_prefix = int(rulev6base) | ( ipv4index << addroffset )
249         map_fullprefix = map_prefix | ( psid << psidshift)
250         return map_fullprefix
251         
252
253     def get_mapce_addr(self,ipv4addr,psid):
254         ipv4index = self.ipv4_index(ipv4addr)
255         (addroffset,psidshift) = self._calc_ipv6bit_pos()
256         map_fullprefix = self._append_map_eabits(ipv4index,
257                                                  addroffset,
258                                                  psidshift,
259                                                  psid)
260         mapv4iid = map_fullprefix | ( int(self.ipv4addr) << 16 )
261         map_full_address = mapv4iid | psid
262         mapce_address = "{}".format(IPv6Address(map_full_address))
263         return mapce_address
264
265     def get_mapce_prefix(self,ipv4addr,psid):
266         ipv4index = self.ipv4_index(ipv4addr)
267         (addroffset,psidshift) = self._calc_ipv6bit_pos()
268         map_fullprefix = self._append_map_eabits(ipv4index,
269                                                  addroffset,
270                                                  psidshift,
271                                                  psid)
272         mapce_prefix = "{}/{}".format(
273                                    IPv6Address(map_fullprefix),
274                                    self.rulev6mask + self.ealen
275                                 )
276         return mapce_prefix
277
278     def get_map_ipv4(self,mapce_address):
279         ipv4 = (int(IPv6Address(mapce_address)) & ( 0xffffffff << 16 )) >> 16
280         return ip_address(ipv4)
281             
282
283
284 class DmrCalc(object):
285
286     def __init__(self,dmr):
287
288         # Validate and set BMR and BMR derived values 
289         self.dmrprefix = self._check_dmr_prefix(dmr)
290
291     def embed_6052addr(self,ipv4addr):
292
293         try:
294             ipv4addrint = int(ip_address(ipv4addr))
295         except ValueError:
296             print("Invalid IPv4 address {}".format(ipv4addr))
297             sys.exit(1)
298
299         if ( self.dmrprefix.prefixlen == 64 ):
300             ipv6int = ipv4addrint << 24
301             ipv6int += int(self.dmrprefix.network_address)
302             return IPv6Address(ipv6int)
303
304         if ( self.dmrprefix.prefixlen == 96 ):
305             ipv6int = ipv4addrint
306             ipv6int += int(self.dmrprefix.network_address)
307             return IPv6Address(ipv6int)
308
309     def _check_dmr_prefix(self,dmrprefix):
310         try:
311             self.dmrmask = IPv6Network(
312                         dmrprefix,
313                         strict=False
314                         ).prefixlen
315         except ValueError:
316             print("Invalid IPv6 prefix {}".format(prefix))
317             sys.exit(1)
318
319         if self.dmrmask not in (32,40,48,56,64,96):
320             print("Invalid prefix mask /{}".format(self.dmrmask))
321             sys.exit(1)
322
323         return IPv6Network(dmrprefix)
324
325 if __name__ == "__main__":
326     m = DmrCalc('fd80::/48')
327     print(m.dmrprefix)