2 * Domain search option for DHCP (RFC 3397)
4 * Copyright (c) 2012 Klaus Stengel
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 #include "qemu/osdep.h"
28 static const uint8_t RFC3397_OPT_DOMAIN_SEARCH
= 119;
29 static const uint8_t MAX_OPT_LEN
= 255;
30 static const uint8_t OPT_HEADER_LEN
= 2;
31 static const uint8_t REFERENCE_LEN
= 2;
33 struct compact_domain
;
35 typedef struct compact_domain
{
36 struct compact_domain
*self
;
37 struct compact_domain
*refdom
;
44 domain_suffix_diffoff(const CompactDomain
*a
, const CompactDomain
*b
)
46 size_t la
= a
->len
, lb
= b
->len
;
47 uint8_t *da
= a
->labels
+ la
, *db
= b
->labels
+ lb
;
48 size_t i
, lm
= (la
< lb
) ? la
: lb
;
50 for (i
= 0; i
< lm
; i
++) {
59 static int domain_suffix_ord(const void *cva
, const void *cvb
)
61 const CompactDomain
*a
= cva
, *b
= cvb
;
62 size_t la
= a
->len
, lb
= b
->len
;
63 size_t doff
= domain_suffix_diffoff(a
, b
);
64 uint8_t ca
= a
->labels
[la
- doff
];
65 uint8_t cb
= b
->labels
[lb
- doff
];
82 static size_t domain_common_label(CompactDomain
*a
, CompactDomain
*b
)
84 size_t res
, doff
= domain_suffix_diffoff(a
, b
);
85 uint8_t *first_eq_pos
= a
->labels
+ (a
->len
- doff
);
86 uint8_t *label
= a
->labels
;
88 while (*label
&& label
< first_eq_pos
) {
91 res
= a
->len
- (label
- a
->labels
);
92 /* only report if it can help to reduce the packet size */
93 return (res
> REFERENCE_LEN
) ? res
: 0;
96 static void domain_fixup_order(CompactDomain
*cd
, size_t n
)
100 for (i
= 0; i
< n
; i
++) {
101 CompactDomain
*cur
= cd
+ i
, *next
= cd
[i
].self
;
103 while (!cur
->common_octets
) {
104 CompactDomain
*tmp
= next
->self
; /* backup target value */
107 cur
->common_octets
++;
115 static void domain_mklabels(CompactDomain
*cd
, const char *input
)
117 uint8_t *len_marker
= cd
->labels
;
118 uint8_t *output
= len_marker
; /* pre-incremented */
119 const char *in
= input
;
130 if (cur_chr
== '.' || cur_chr
== '\0') {
131 len
= output
- len_marker
;
132 if ((len
== 0 && cur_chr
== '.') || len
>= 64) {
143 } while (cur_chr
!= '\0');
145 /* ensure proper zero-termination */
153 g_warning("failed to parse domain name '%s'\n", input
);
158 domain_mkxrefs(CompactDomain
*doms
, CompactDomain
*last
, size_t depth
)
160 CompactDomain
*i
= doms
, *target
= doms
;
163 if (i
->labels
< target
->labels
) {
166 } while (i
++ != last
);
168 for (i
= doms
; i
!= last
; i
++) {
169 CompactDomain
*group_last
;
172 if (i
->common_octets
== depth
) {
177 for (group_last
= i
; group_last
!= last
; group_last
++) {
178 size_t co
= group_last
->common_octets
;
182 if (co
< next_depth
) {
186 domain_mkxrefs(i
, group_last
, next_depth
);
200 if (i
!= target
&& i
->refdom
== NULL
) {
202 i
->common_octets
= depth
;
204 } while (i
++ != last
);
207 static size_t domain_compactify(CompactDomain
*domains
, size_t n
)
209 uint8_t *start
= domains
->self
->labels
, *outptr
= start
;
212 for (i
= 0; i
< n
; i
++) {
213 CompactDomain
*cd
= domains
[i
].self
;
214 CompactDomain
*rd
= cd
->refdom
;
217 size_t moff
= (rd
->labels
- start
)
218 + (rd
->len
- cd
->common_octets
);
219 if (moff
< 0x3FFFu
) {
220 cd
->len
-= cd
->common_octets
- 2;
221 cd
->labels
[cd
->len
- 1] = moff
& 0xFFu
;
222 cd
->labels
[cd
->len
- 2] = 0xC0u
| (moff
>> 8);
226 if (cd
->labels
!= outptr
) {
227 memmove(outptr
, cd
->labels
, cd
->len
);
232 return outptr
- start
;
235 int translate_dnssearch(Slirp
*s
, const char **names
)
237 size_t blocks
, bsrc_start
, bsrc_end
, bdst_start
;
238 size_t i
, num_domains
, memreq
= 0;
239 uint8_t *result
= NULL
, *outptr
;
240 CompactDomain
*domains
= NULL
;
241 const char **nameptr
= names
;
243 while (*nameptr
!= NULL
) {
247 num_domains
= nameptr
- names
;
248 if (num_domains
== 0) {
252 domains
= g_malloc(num_domains
* sizeof(*domains
));
254 for (i
= 0; i
< num_domains
; i
++) {
255 size_t nlen
= strlen(names
[i
]);
256 memreq
+= nlen
+ 2; /* 1 zero octet + 1 label length octet */
257 domains
[i
].self
= domains
+ i
;
258 domains
[i
].len
= nlen
;
259 domains
[i
].common_octets
= 0;
260 domains
[i
].refdom
= NULL
;
263 /* reserve extra 2 header bytes for each 255 bytes of output */
264 memreq
+= DIV_ROUND_UP(memreq
, MAX_OPT_LEN
) * OPT_HEADER_LEN
;
265 result
= g_malloc(memreq
* sizeof(*result
));
268 for (i
= 0; i
< num_domains
; i
++) {
269 domains
[i
].labels
= outptr
;
270 domain_mklabels(domains
+ i
, names
[i
]);
271 outptr
+= domains
[i
].len
;
274 if (outptr
== result
) {
280 qsort(domains
, num_domains
, sizeof(*domains
), domain_suffix_ord
);
281 domain_fixup_order(domains
, num_domains
);
283 for (i
= 1; i
< num_domains
; i
++) {
284 size_t cl
= domain_common_label(domains
+ i
- 1, domains
+ i
);
285 domains
[i
- 1].common_octets
= cl
;
288 domain_mkxrefs(domains
, domains
+ num_domains
- 1, 0);
289 memreq
= domain_compactify(domains
, num_domains
);
291 blocks
= DIV_ROUND_UP(memreq
, MAX_OPT_LEN
);
293 bsrc_start
= (blocks
- 1) * MAX_OPT_LEN
;
294 bdst_start
= bsrc_start
+ blocks
* OPT_HEADER_LEN
;
295 memreq
+= blocks
* OPT_HEADER_LEN
;
298 size_t len
= bsrc_end
- bsrc_start
;
299 memmove(result
+ bdst_start
, result
+ bsrc_start
, len
);
300 result
[bdst_start
- 2] = RFC3397_OPT_DOMAIN_SEARCH
;
301 result
[bdst_start
- 1] = len
;
302 bsrc_end
= bsrc_start
;
303 bsrc_start
-= MAX_OPT_LEN
;
304 bdst_start
-= MAX_OPT_LEN
+ OPT_HEADER_LEN
;
308 s
->vdnssearch
= result
;
309 s
->vdnssearch_len
= memreq
;