1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """EDNS Options"""
19
20 from __future__ import absolute_import
21
22 import math
23 import struct
24
25 import dns.inet
26
27
28 NSID = 3
29
30 DAU = 5
31
32 DHU = 6
33
34 N3U = 7
35
36 ECS = 8
37
38 EXPIRE = 9
39
40 COOKIE = 10
41
42 KEEPALIVE = 11
43
44 PADDING = 12
45
46 CHAIN = 13
49
50 """Base class for all EDNS option types."""
51
53 """Initialize an option.
54
55 *otype*, an ``int``, is the option type.
56 """
57 self.otype = otype
58
60 """Convert an option to wire format.
61 """
62 raise NotImplementedError
63
64 @classmethod
65 - def from_wire(cls, otype, wire, current, olen):
66 """Build an EDNS option object from wire format.
67
68 *otype*, an ``int``, is the option type.
69
70 *wire*, a ``binary``, is the wire-format message.
71
72 *current*, an ``int``, is the offset in *wire* of the beginning
73 of the rdata.
74
75 *olen*, an ``int``, is the length of the wire-format option data
76
77 Returns a ``dns.edns.Option``.
78 """
79
80 raise NotImplementedError
81
82 - def _cmp(self, other):
83 """Compare an EDNS option with another option of the same type.
84
85 Returns < 0 if < *other*, 0 if == *other*, and > 0 if > *other*.
86 """
87 raise NotImplementedError
88
90 if not isinstance(other, Option):
91 return False
92 if self.otype != other.otype:
93 return False
94 return self._cmp(other) == 0
95
97 if not isinstance(other, Option):
98 return False
99 if self.otype != other.otype:
100 return False
101 return self._cmp(other) != 0
102
104 if not isinstance(other, Option) or \
105 self.otype != other.otype:
106 return NotImplemented
107 return self._cmp(other) < 0
108
110 if not isinstance(other, Option) or \
111 self.otype != other.otype:
112 return NotImplemented
113 return self._cmp(other) <= 0
114
116 if not isinstance(other, Option) or \
117 self.otype != other.otype:
118 return NotImplemented
119 return self._cmp(other) >= 0
120
122 if not isinstance(other, Option) or \
123 self.otype != other.otype:
124 return NotImplemented
125 return self._cmp(other) > 0
126
129
130 """Generic Option Class
131
132 This class is used for EDNS option types for which we have no better
133 implementation.
134 """
135
139
141 file.write(self.data)
142
144 return "Generic %d" % self.otype
145
146 @classmethod
147 - def from_wire(cls, otype, wire, current, olen):
148 return cls(otype, wire[current: current + olen])
149
150 - def _cmp(self, other):
151 if self.data == other.data:
152 return 0
153 if self.data > other.data:
154 return 1
155 return -1
156
159 """EDNS Client Subnet (ECS, RFC7871)"""
160
161 - def __init__(self, address, srclen=None, scopelen=0):
162 """*address*, a ``text``, is the client address information.
163
164 *srclen*, an ``int``, the source prefix length, which is the
165 leftmost number of bits of the address to be used for the
166 lookup. The default is 24 for IPv4 and 56 for IPv6.
167
168 *scopelen*, an ``int``, the scope prefix length. This value
169 must be 0 in queries, and should be set in responses.
170 """
171
172 super(ECSOption, self).__init__(ECS)
173 af = dns.inet.af_for_address(address)
174
175 if af == dns.inet.AF_INET6:
176 self.family = 2
177 if srclen is None:
178 srclen = 56
179 elif af == dns.inet.AF_INET:
180 self.family = 1
181 if srclen is None:
182 srclen = 24
183 else:
184 raise ValueError('Bad ip family')
185
186 self.address = address
187 self.srclen = srclen
188 self.scopelen = scopelen
189
190 addrdata = dns.inet.inet_pton(af, address)
191 nbytes = int(math.ceil(srclen/8.0))
192
193
194
195 self.addrdata = addrdata[:nbytes]
196 nbits = srclen % 8
197 if nbits != 0:
198 last = struct.pack('B', ord(self.addrdata[-1:]) & (0xff << nbits))
199 self.addrdata = self.addrdata[:-1] + last
200
202 return "ECS {}/{} scope/{}".format(self.address, self.srclen,
203 self.scopelen)
204
206 file.write(struct.pack('!H', self.family))
207 file.write(struct.pack('!BB', self.srclen, self.scopelen))
208 file.write(self.addrdata)
209
210 @classmethod
212 family, src, scope = struct.unpack('!HBB', wire[cur:cur+4])
213 cur += 4
214
215 addrlen = int(math.ceil(src/8.0))
216
217 if family == 1:
218 af = dns.inet.AF_INET
219 pad = 4 - addrlen
220 elif family == 2:
221 af = dns.inet.AF_INET6
222 pad = 16 - addrlen
223 else:
224 raise ValueError('unsupported family')
225
226 addr = dns.inet.inet_ntop(af, wire[cur:cur+addrlen] + b'\x00' * pad)
227 return cls(addr, src, scope)
228
229 - def _cmp(self, other):
230 if self.addrdata == other.addrdata:
231 return 0
232 if self.addrdata > other.addrdata:
233 return 1
234 return -1
235
236 _type_to_class = {
237 ECS: ECSOption
238 }
241 """Return the class for the specified option type.
242
243 The GenericOption class is used if a more specific class is not
244 known.
245 """
246
247 cls = _type_to_class.get(otype)
248 if cls is None:
249 cls = GenericOption
250 return cls
251
254 """Build an EDNS option object from wire format.
255
256 *otype*, an ``int``, is the option type.
257
258 *wire*, a ``binary``, is the wire-format message.
259
260 *current*, an ``int``, is the offset in *wire* of the beginning
261 of the rdata.
262
263 *olen*, an ``int``, is the length of the wire-format option data
264
265 Returns an instance of a subclass of ``dns.edns.Option``.
266 """
267
268 cls = get_option_class(otype)
269 return cls.from_wire(otype, wire, current, olen)
270