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
27 static const uint8_t RFC3397_OPT_DOMAIN_SEARCH
= 119;
28 static const uint8_t MAX_OPT_LEN
= 255;
29 static const uint8_t OPT_HEADER_LEN
= 2;
30 static const uint8_t REFERENCE_LEN
= 2;
32 struct compact_domain
;
34 typedef struct compact_domain
{
35 struct compact_domain
*self
;
36 struct compact_domain
*refdom
;
43 domain_suffix_diffoff(const CompactDomain
*a
, const CompactDomain
*b
)
45 size_t la
= a
->len
, lb
= b
->len
;
46 uint8_t *da
= a
->labels
+ la
, *db
= b
->labels
+ lb
;
47 size_t i
, lm
= (la
< lb
) ? la
: lb
;
49 for (i
= 0; i
< lm
; i
++) {
58 static int domain_suffix_ord(const void *cva
, const void *cvb
)
60 const CompactDomain
*a
= cva
, *b
= cvb
;
61 size_t la
= a
->len
, lb
= b
->len
;
62 size_t doff
= domain_suffix_diffoff(a
, b
);
63 uint8_t ca
= a
->labels
[la
- doff
];
64 uint8_t cb
= b
->labels
[lb
- doff
];
81 static size_t domain_common_label(CompactDomain
*a
, CompactDomain
*b
)
83 size_t res
, doff
= domain_suffix_diffoff(a
, b
);
84 uint8_t *first_eq_pos
= a
->labels
+ (a
->len
- doff
);
85 uint8_t *label
= a
->labels
;
87 while (*label
&& label
< first_eq_pos
) {
90 res
= a
->len
- (label
- a
->labels
);
91 /* only report if it can help to reduce the packet size */
92 return (res
> REFERENCE_LEN
) ? res
: 0;
95 static void domain_fixup_order(CompactDomain
*cd
, size_t n
)
99 for (i
= 0; i
< n
; i
++) {
100 CompactDomain
*cur
= cd
+ i
, *next
= cd
[i
].self
;
102 while (!cur
->common_octets
) {
103 CompactDomain
*tmp
= next
->self
; /* backup target value */
106 cur
->common_octets
++;
114 static void domain_mklabels(CompactDomain
*cd
, const char *input
)
116 uint8_t *len_marker
= cd
->labels
;
117 uint8_t *output
= len_marker
; /* pre-incremented */
118 const char *in
= input
;
129 if (cur_chr
== '.' || cur_chr
== '\0') {
130 len
= output
- len_marker
;
131 if ((len
== 0 && cur_chr
== '.') || len
>= 64) {
142 } while (cur_chr
!= '\0');
144 /* ensure proper zero-termination */
152 g_warning("failed to parse domain name '%s'\n", input
);
157 domain_mkxrefs(CompactDomain
*doms
, CompactDomain
*last
, size_t depth
)
159 CompactDomain
*i
= doms
, *target
= doms
;
162 if (i
->labels
< target
->labels
) {
165 } while (i
++ != last
);
167 for (i
= doms
; i
!= last
; i
++) {
168 CompactDomain
*group_last
;
171 if (i
->common_octets
== depth
) {
176 for (group_last
= i
; group_last
!= last
; group_last
++) {
177 size_t co
= group_last
->common_octets
;
181 if (co
< next_depth
) {
185 domain_mkxrefs(i
, group_last
, next_depth
);
199 if (i
!= target
&& i
->refdom
== NULL
) {
201 i
->common_octets
= depth
;
203 } while (i
++ != last
);
206 static size_t domain_compactify(CompactDomain
*domains
, size_t n
)
208 uint8_t *start
= domains
->self
->labels
, *outptr
= start
;
211 for (i
= 0; i
< n
; i
++) {
212 CompactDomain
*cd
= domains
[i
].self
;
213 CompactDomain
*rd
= cd
->refdom
;
216 size_t moff
= (rd
->labels
- start
)
217 + (rd
->len
- cd
->common_octets
);
218 if (moff
< 0x3FFFu
) {
219 cd
->len
-= cd
->common_octets
- 2;
220 cd
->labels
[cd
->len
- 1] = moff
& 0xFFu
;
221 cd
->labels
[cd
->len
- 2] = 0xC0u
| (moff
>> 8);
225 if (cd
->labels
!= outptr
) {
226 memmove(outptr
, cd
->labels
, cd
->len
);
231 return outptr
- start
;
234 int translate_dnssearch(Slirp
*s
, const char **names
)
236 size_t blocks
, bsrc_start
, bsrc_end
, bdst_start
;
237 size_t i
, num_domains
, memreq
= 0;
238 uint8_t *result
= NULL
, *outptr
;
239 CompactDomain
*domains
= NULL
;
240 const char **nameptr
= names
;
242 while (*nameptr
!= NULL
) {
246 num_domains
= nameptr
- names
;
247 if (num_domains
== 0) {
251 domains
= g_malloc(num_domains
* sizeof(*domains
));
253 for (i
= 0; i
< num_domains
; i
++) {
254 size_t nlen
= strlen(names
[i
]);
255 memreq
+= nlen
+ 2; /* 1 zero octet + 1 label length octet */
256 domains
[i
].self
= domains
+ i
;
257 domains
[i
].len
= nlen
;
258 domains
[i
].common_octets
= 0;
259 domains
[i
].refdom
= NULL
;
262 /* reserve extra 2 header bytes for each 255 bytes of output */
263 memreq
+= DIV_ROUND_UP(memreq
, MAX_OPT_LEN
) * OPT_HEADER_LEN
;
264 result
= g_malloc(memreq
* sizeof(*result
));
267 for (i
= 0; i
< num_domains
; i
++) {
268 domains
[i
].labels
= outptr
;
269 domain_mklabels(domains
+ i
, names
[i
]);
270 outptr
+= domains
[i
].len
;
273 if (outptr
== result
) {
279 qsort(domains
, num_domains
, sizeof(*domains
), domain_suffix_ord
);
280 domain_fixup_order(domains
, num_domains
);
282 for (i
= 1; i
< num_domains
; i
++) {
283 size_t cl
= domain_common_label(domains
+ i
- 1, domains
+ i
);
284 domains
[i
- 1].common_octets
= cl
;
287 domain_mkxrefs(domains
, domains
+ num_domains
- 1, 0);
288 memreq
= domain_compactify(domains
, num_domains
);
290 blocks
= DIV_ROUND_UP(memreq
, MAX_OPT_LEN
);
292 bsrc_start
= (blocks
- 1) * MAX_OPT_LEN
;
293 bdst_start
= bsrc_start
+ blocks
* OPT_HEADER_LEN
;
294 memreq
+= blocks
* OPT_HEADER_LEN
;
297 size_t len
= bsrc_end
- bsrc_start
;
298 memmove(result
+ bdst_start
, result
+ bsrc_start
, len
);
299 result
[bdst_start
- 2] = RFC3397_OPT_DOMAIN_SEARCH
;
300 result
[bdst_start
- 1] = len
;
301 bsrc_end
= bsrc_start
;
302 bsrc_start
-= MAX_OPT_LEN
;
303 bdst_start
-= MAX_OPT_LEN
+ OPT_HEADER_LEN
;
307 s
->vdnssearch
= result
;
308 s
->vdnssearch_len
= memreq
;