Annotation of embedaddon/dnsmasq/src/domain-match.c, revision 1.1.1.1
1.1 misho 1: /* dnsmasq is Copyright (c) 2000-2022 Simon Kelley
2:
3: This program is free software; you can redistribute it and/or modify
4: it under the terms of the GNU General Public License as published by
5: the Free Software Foundation; version 2 dated June, 1991, or
6: (at your option) version 3 dated 29 June, 2007.
7:
8: This program is distributed in the hope that it will be useful,
9: but WITHOUT ANY WARRANTY; without even the implied warranty of
10: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11: GNU General Public License for more details.
12:
13: You should have received a copy of the GNU General Public License
14: along with this program. If not, see <http://www.gnu.org/licenses/>.
15: */
16:
17: #include "dnsmasq.h"
18:
19: static int order(char *qdomain, size_t qlen, struct server *serv);
20: static int order_qsort(const void *a, const void *b);
21: static int order_servers(struct server *s, struct server *s2);
22:
23: /* If the server is USE_RESOLV or LITERAL_ADDRES, it lives on the local_domains chain. */
24: #define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS)
25:
26: void build_server_array(void)
27: {
28: struct server *serv;
29: int count = 0;
30:
31: for (serv = daemon->servers; serv; serv = serv->next)
32: #ifdef HAVE_LOOP
33: if (!(serv->flags & SERV_LOOP))
34: #endif
35: {
36: count++;
37: if (serv->flags & SERV_WILDCARD)
38: daemon->server_has_wildcard = 1;
39: }
40:
41: for (serv = daemon->local_domains; serv; serv = serv->next)
42: {
43: count++;
44: if (serv->flags & SERV_WILDCARD)
45: daemon->server_has_wildcard = 1;
46: }
47:
48: daemon->serverarraysz = count;
49:
50: if (count > daemon->serverarrayhwm)
51: {
52: struct server **new;
53:
54: count += 10; /* A few extra without re-allocating. */
55:
56: if ((new = whine_malloc(count * sizeof(struct server *))))
57: {
58: if (daemon->serverarray)
59: free(daemon->serverarray);
60:
61: daemon->serverarray = new;
62: daemon->serverarrayhwm = count;
63: }
64: }
65:
66: count = 0;
67:
68: for (serv = daemon->servers; serv; serv = serv->next)
69: #ifdef HAVE_LOOP
70: if (!(serv->flags & SERV_LOOP))
71: #endif
72: {
73: daemon->serverarray[count] = serv;
74: serv->serial = count;
75: serv->last_server = -1;
76: count++;
77: }
78:
79: for (serv = daemon->local_domains; serv; serv = serv->next, count++)
80: daemon->serverarray[count] = serv;
81:
82: qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
83:
84: /* servers need the location in the array to find all the whole
85: set of equivalent servers from a pointer to a single one. */
86: for (count = 0; count < daemon->serverarraysz; count++)
87: if (!(daemon->serverarray[count]->flags & SERV_IS_LOCAL))
88: daemon->serverarray[count]->arrayposn = count;
89: }
90:
91: /* we're looking for the server whose domain is the longest exact match
92: to the RH end of qdomain, or a local address if the flags match.
93: Add '.' to the LHS of the query string so
94: server=/.example.com/ works.
95:
96: A flag of F_SERVER returns an upstream server only.
97: A flag of F_DNSSECOK returns a DNSSEC capable server only and
98: also disables NODOTS servers from consideration.
99: A flag of F_DOMAINSRV returns a domain-specific server only.
100: A flag of F_CONFIG returns anything that generates a local
101: reply of IPv4 or IPV6.
102: return 0 if nothing found, 1 otherwise.
103: */
104: int lookup_domain(char *domain, int flags, int *lowout, int *highout)
105: {
106: int rc, crop_query, nodots;
107: ssize_t qlen;
108: int try, high, low = 0;
109: int nlow = 0, nhigh = 0;
110: char *cp, *qdomain = domain;
111:
112: /* may be no configured servers. */
113: if (daemon->serverarraysz == 0)
114: return 0;
115:
116: /* find query length and presence of '.' */
117: for (cp = qdomain, nodots = 1, qlen = 0; *cp; qlen++, cp++)
118: if (*cp == '.')
119: nodots = 0;
120:
121: /* Handle empty name, and searches for DNSSEC queries without
122: diverting to NODOTS servers. */
123: if (qlen == 0 || flags & F_DNSSECOK)
124: nodots = 0;
125:
126: /* Search shorter and shorter RHS substrings for a match */
127: while (qlen >= 0)
128: {
129: /* Note that when we chop off a label, all the possible matches
130: MUST be at a larger index than the nearest failing match with one more
131: character, since the array is sorted longest to smallest. Hence
132: we don't reset low to zero here, we can go further below and crop the
133: search string to the size of the largest remaining server
134: when this match fails. */
135: high = daemon->serverarraysz;
136: crop_query = 1;
137:
138: /* binary search */
139: while (1)
140: {
141: try = (low + high)/2;
142:
143: if ((rc = order(qdomain, qlen, daemon->serverarray[try])) == 0)
144: break;
145:
146: if (rc < 0)
147: {
148: if (high == try)
149: {
150: /* qdomain is longer or same length as longest domain, and try == 0
151: crop the query to the longest domain. */
152: crop_query = qlen - daemon->serverarray[try]->domain_len;
153: break;
154: }
155: high = try;
156: }
157: else
158: {
159: if (low == try)
160: {
161: /* try now points to the last domain that sorts before the query, so
162: we know that a substring of the query shorter than it is required to match, so
163: find the largest domain that's shorter than try. Note that just going to
164: try+1 is not optimal, consider searching bbb in (aaa,ccc,bb). try will point
165: to aaa, since ccc sorts after bbb, but the first domain that has a chance to
166: match is bb. So find the length of the first domain later than try which is
167: is shorter than it.
168: There's a nasty edge case when qdomain sorts before _any_ of the
169: server domains, where try _doesn't point_ to the last domain that sorts
170: before the query, since no such domain exists. In that case, the loop
171: exits via the rc < 0 && high == try path above and this code is
172: not executed. */
173: ssize_t len, old = daemon->serverarray[try]->domain_len;
174: while (++try != daemon->serverarraysz)
175: {
176: if (old != (len = daemon->serverarray[try]->domain_len))
177: {
178: crop_query = qlen - len;
179: break;
180: }
181: }
182: break;
183: }
184: low = try;
185: }
186: };
187:
188: if (rc == 0)
189: {
190: int found = 1;
191:
192: if (daemon->server_has_wildcard)
193: {
194: /* if we have example.com and *example.com we need to check against *example.com,
195: but the binary search may have found either. Use the fact that example.com is sorted before *example.com
196: We favour example.com in the case that both match (ie www.example.com) */
197: while (try != 0 && order(qdomain, qlen, daemon->serverarray[try-1]) == 0)
198: try--;
199:
200: if (!(qdomain == domain || *qdomain == 0 || *(qdomain-1) == '.'))
201: {
202: while (try < daemon->serverarraysz-1 && order(qdomain, qlen, daemon->serverarray[try+1]) == 0)
203: try++;
204:
205: if (!(daemon->serverarray[try]->flags & SERV_WILDCARD))
206: found = 0;
207: }
208: }
209:
210: if (found && filter_servers(try, flags, &nlow, &nhigh))
211: /* We have a match, but it may only be (say) an IPv6 address, and
212: if the query wasn't for an AAAA record, it's no good, and we need
213: to continue generalising */
214: {
215: /* We've matched a setting which says to use servers without a domain.
216: Continue the search with empty query. We set the F_SERVER flag
217: so that --address=/#/... doesn't match. */
218: if (daemon->serverarray[nlow]->flags & SERV_USE_RESOLV)
219: {
220: crop_query = qlen;
221: flags |= F_SERVER;
222: }
223: else
224: break;
225: }
226: }
227:
228: /* crop_query must be at least one always. */
229: if (crop_query == 0)
230: crop_query = 1;
231:
232: /* strip chars off the query based on the largest possible remaining match,
233: then continue to the start of the next label unless we have a wildcard
234: domain somewhere, in which case we have to go one at a time. */
235: qlen -= crop_query;
236: qdomain += crop_query;
237: if (!daemon->server_has_wildcard)
238: while (qlen > 0 && (*(qdomain-1) != '.'))
239: qlen--, qdomain++;
240: }
241:
242: /* domain has no dots, and we have at least one server configured to handle such,
243: These servers always sort to the very end of the array.
244: A configured server eg server=/lan/ will take precdence. */
245: if (nodots &&
246: (daemon->serverarray[daemon->serverarraysz-1]->flags & SERV_FOR_NODOTS) &&
247: (nlow == nhigh || daemon->serverarray[nlow]->domain_len == 0))
248: filter_servers(daemon->serverarraysz-1, flags, &nlow, &nhigh);
249:
250: if (lowout)
251: *lowout = nlow;
252:
253: if (highout)
254: *highout = nhigh;
255:
256: /* qlen == -1 when we failed to match even an empty query, if there are no default servers. */
257: if (nlow == nhigh || qlen == -1)
258: return 0;
259:
260: return 1;
261: }
262:
263: /* Return first server in group of equivalent servers; this is the "master" record. */
264: int server_samegroup(struct server *a, struct server *b)
265: {
266: return order_servers(a, b) == 0;
267: }
268:
269: int filter_servers(int seed, int flags, int *lowout, int *highout)
270: {
271: int nlow = seed, nhigh = seed;
272: int i;
273:
274: /* expand nlow and nhigh to cover all the records with the same domain
275: nlow is the first, nhigh - 1 is the last. nlow=nhigh means no servers,
276: which can happen below. */
277: while (nlow > 0 && order_servers(daemon->serverarray[nlow-1], daemon->serverarray[nlow]) == 0)
278: nlow--;
279:
280: while (nhigh < daemon->serverarraysz-1 && order_servers(daemon->serverarray[nhigh], daemon->serverarray[nhigh+1]) == 0)
281: nhigh++;
282:
283: nhigh++;
284:
285: #define SERV_LOCAL_ADDRESS (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS)
286:
287: if (flags & F_CONFIG)
288: {
289: /* We're just lookin for any matches that return an RR. */
290: for (i = nlow; i < nhigh; i++)
291: if (daemon->serverarray[i]->flags & SERV_LOCAL_ADDRESS)
292: break;
293:
294: /* failed, return failure. */
295: if (i == nhigh)
296: nhigh = nlow;
297: }
298: else
299: {
300: /* Now the servers are on order between low and high, in the order
301: IPv6 addr, IPv4 addr, return zero for both, resolvconf servers, send upstream, no-data return.
302:
303: See which of those match our query in that priority order and narrow (low, high) */
304:
305: for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++);
306:
307: if (!(flags & F_SERVER) && i != nlow && (flags & F_IPV6))
308: nhigh = i;
309: else
310: {
311: nlow = i;
312:
313: for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_4ADDR); i++);
314:
315: if (!(flags & F_SERVER) && i != nlow && (flags & F_IPV4))
316: nhigh = i;
317: else
318: {
319: nlow = i;
320:
321: for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_ALL_ZEROS); i++);
322:
323: if (!(flags & F_SERVER) && i != nlow && (flags & (F_IPV4 | F_IPV6)))
324: nhigh = i;
325: else
326: {
327: nlow = i;
328:
329: /* Short to resolv.conf servers */
330: for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_USE_RESOLV); i++);
331:
332: if (i != nlow)
333: nhigh = i;
334: else
335: {
336: /* now look for a server */
337: for (i = nlow; i < nhigh && !(daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
338:
339: if (i != nlow)
340: {
341: /* If we want a server that can do DNSSEC, and this one can't,
342: return nothing, similarly if were looking only for a server
343: for a particular domain. */
344: if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC))
345: nlow = nhigh;
346: else if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
347: nlow = nhigh;
348: else
349: nhigh = i;
350: }
351: else
352: {
353: /* --local=/domain/, only return if we don't need a server. */
354: if (flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER))
355: nhigh = i;
356: }
357: }
358: }
359: }
360: }
361: }
362:
363: *lowout = nlow;
364: *highout = nhigh;
365:
366: return (nlow != nhigh);
367: }
368:
369: int is_local_answer(time_t now, int first, char *name)
370: {
371: int flags = 0;
372: int rc = 0;
373:
374: if ((flags = daemon->serverarray[first]->flags) & SERV_LITERAL_ADDRESS)
375: {
376: if (flags & SERV_4ADDR)
377: rc = F_IPV4;
378: else if (flags & SERV_6ADDR)
379: rc = F_IPV6;
380: else if (flags & SERV_ALL_ZEROS)
381: rc = F_IPV4 | F_IPV6;
382: else
383: {
384: /* argument first is the first struct server which matches the query type;
385: now roll back to the server which is just the same domain, to check if that
386: provides an answer of a different type. */
387:
388: for (;first > 0 && order_servers(daemon->serverarray[first-1], daemon->serverarray[first]) == 0; first--);
389:
390: if ((daemon->serverarray[first]->flags & SERV_LOCAL_ADDRESS) ||
391: check_for_local_domain(name, now))
392: rc = F_NOERR;
393: else
394: rc = F_NXDOMAIN;
395: }
396: }
397:
398: return rc;
399: }
400:
401: size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last, int ede)
402: {
403: int trunc = 0, anscount = 0;
404: unsigned char *p;
405: int start;
406: union all_addr addr;
407:
408: if (flags & (F_NXDOMAIN | F_NOERR))
409: log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL, 0);
410:
411: setup_reply(header, flags, ede);
412:
413: if (!(p = skip_questions(header, size)))
414: return 0;
415:
416: if (flags & gotname & F_IPV4)
417: for (start = first; start != last; start++)
418: {
419: struct serv_addr4 *srv = (struct serv_addr4 *)daemon->serverarray[start];
420:
421: if (srv->flags & SERV_ALL_ZEROS)
422: memset(&addr, 0, sizeof(addr));
423: else
424: addr.addr4 = srv->addr;
425:
426: if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr))
427: anscount++;
428: log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL, 0);
429: }
430:
431: if (flags & gotname & F_IPV6)
432: for (start = first; start != last; start++)
433: {
434: struct serv_addr6 *srv = (struct serv_addr6 *)daemon->serverarray[start];
435:
436: if (srv->flags & SERV_ALL_ZEROS)
437: memset(&addr, 0, sizeof(addr));
438: else
439: addr.addr6 = srv->addr;
440:
441: if (add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr))
442: anscount++;
443: log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL, 0);
444: }
445:
446: if (trunc)
447: header->hb3 |= HB3_TC;
448: header->ancount = htons(anscount);
449:
450: return p - (unsigned char *)header;
451: }
452:
453: #ifdef HAVE_DNSSEC
454: int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp)
455: {
456: int first, last, index;
457:
458: /* Find server to send DNSSEC query to. This will normally be the
459: same as for the original query, but may be another if
460: servers for domains are involved. */
461: if (!lookup_domain(keyname, F_DNSSECOK, &first, &last))
462: return -1;
463:
464: for (index = first; index != last; index++)
465: if (daemon->serverarray[index] == server)
466: break;
467:
468: /* No match to server used for original query.
469: Use newly looked up set. */
470: if (index == last)
471: index = daemon->serverarray[first]->last_server == -1 ?
472: first : daemon->serverarray[first]->last_server;
473:
474: if (firstp)
475: *firstp = first;
476:
477: if (lastp)
478: *lastp = last;
479:
480: return index;
481: }
482: #endif
483:
484: /* order by size, then by dictionary order */
485: static int order(char *qdomain, size_t qlen, struct server *serv)
486: {
487: size_t dlen = 0;
488:
489: /* servers for dotless names always sort last
490: searched for name is never dotless. */
491: if (serv->flags & SERV_FOR_NODOTS)
492: return -1;
493:
494: dlen = serv->domain_len;
495:
496: if (qlen < dlen)
497: return 1;
498:
499: if (qlen > dlen)
500: return -1;
501:
502: return hostname_order(qdomain, serv->domain);
503: }
504:
505: static int order_servers(struct server *s1, struct server *s2)
506: {
507: int rc;
508:
509: /* need full comparison of dotless servers in
510: order_qsort() and filter_servers() */
511:
512: if (s1->flags & SERV_FOR_NODOTS)
513: return (s2->flags & SERV_FOR_NODOTS) ? 0 : 1;
514:
515: if ((rc = order(s1->domain, s1->domain_len, s2)) != 0)
516: return rc;
517:
518: /* For identical domains, sort wildcard ones first */
519: if (s1->flags & SERV_WILDCARD)
520: return (s2->flags & SERV_WILDCARD) ? 0 : 1;
521:
522: return (s2->flags & SERV_WILDCARD) ? -1 : 0;
523: }
524:
525: static int order_qsort(const void *a, const void *b)
526: {
527: int rc;
528:
529: struct server *s1 = *((struct server **)a);
530: struct server *s2 = *((struct server **)b);
531:
532: rc = order_servers(s1, s2);
533:
534: /* Sort all literal NODATA and local IPV4 or IPV6 responses together,
535: in a very specific order. We flip the SERV_LITERAL_ADDRESS bit
536: so the order is IPv6 literal, IPv4 literal, all-zero literal,
537: unqualified servers, upstream server, NXDOMAIN literal. */
538: if (rc == 0)
539: rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_USE_RESOLV | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS) -
540: ((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_USE_RESOLV | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS);
541:
542: /* Finally, order by appearance in /etc/resolv.conf etc, for --strict-order */
543: if (rc == 0)
544: if (!(s1->flags & SERV_LITERAL_ADDRESS))
545: rc = s1->serial - s2->serial;
546:
547: return rc;
548: }
549:
550:
551: /* When loading large numbers of server=.... lines during startup,
552: there's no possibility that there will be server records that can be reused, but
553: searching a long list for each server added grows as O(n^2) and slows things down.
554: This flag is set only if is known there may be free server records that can be reused.
555: There's a call to mark_servers(0) in read_opts() to reset the flag before
556: main config read. */
557:
558: static int maybe_free_servers = 0;
559:
560: /* Must be called before add_update_server() to set daemon->servers_tail */
561: void mark_servers(int flag)
562: {
563: struct server *serv, *next, **up;
564:
565: maybe_free_servers = !!flag;
566:
567: daemon->servers_tail = NULL;
568:
569: /* mark everything with argument flag */
570: for (serv = daemon->servers; serv; serv = serv->next)
571: {
572: if (serv->flags & flag)
573: serv->flags |= SERV_MARK;
574: else
575: serv->flags &= ~SERV_MARK;
576:
577: daemon->servers_tail = serv;
578: }
579:
580: /* --address etc is different: since they are expected to be
581: 1) numerous and 2) not reloaded often. We just delete
582: and recreate. */
583: if (flag)
584: for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = next)
585: {
586: next = serv->next;
587:
588: if (serv->flags & flag)
589: {
590: *up = next;
591: free(serv->domain);
592: free(serv);
593: }
594: else
595: up = &serv->next;
596: }
597: }
598:
599: void cleanup_servers(void)
600: {
601: struct server *serv, *tmp, **up;
602:
603: /* unlink and free anything still marked. */
604: for (serv = daemon->servers, up = &daemon->servers, daemon->servers_tail = NULL; serv; serv = tmp)
605: {
606: tmp = serv->next;
607: if (serv->flags & SERV_MARK)
608: {
609: server_gone(serv);
610: *up = serv->next;
611: free(serv->domain);
612: free(serv);
613: }
614: else
615: {
616: up = &serv->next;
617: daemon->servers_tail = serv;
618: }
619: }
620: }
621:
622: int add_update_server(int flags,
623: union mysockaddr *addr,
624: union mysockaddr *source_addr,
625: const char *interface,
626: const char *domain,
627: union all_addr *local_addr)
628: {
629: struct server *serv = NULL;
630: char *alloc_domain;
631:
632: if (!domain)
633: domain = "";
634:
635: /* .domain == domain, for historical reasons. */
636: if (*domain == '.')
637: while (*domain == '.') domain++;
638: else if (*domain == '*')
639: {
640: domain++;
641: if (*domain != 0)
642: flags |= SERV_WILDCARD;
643: }
644:
645: if (*domain == 0)
646: alloc_domain = whine_malloc(1);
647: else
648: alloc_domain = canonicalise((char *)domain, NULL);
649:
650: if (!alloc_domain)
651: return 0;
652:
653: if (flags & SERV_IS_LOCAL)
654: {
655: size_t size;
656:
657: if (flags & SERV_6ADDR)
658: size = sizeof(struct serv_addr6);
659: else if (flags & SERV_4ADDR)
660: size = sizeof(struct serv_addr4);
661: else
662: size = sizeof(struct serv_local);
663:
664: if (!(serv = whine_malloc(size)))
665: {
666: free(alloc_domain);
667: return 0;
668: }
669:
670: serv->next = daemon->local_domains;
671: daemon->local_domains = serv;
672:
673: if (flags & SERV_4ADDR)
674: ((struct serv_addr4*)serv)->addr = local_addr->addr4;
675:
676: if (flags & SERV_6ADDR)
677: ((struct serv_addr6*)serv)->addr = local_addr->addr6;
678: }
679: else
680: {
681: /* Upstream servers. See if there is a suitable candidate, if so unmark
682: and move to the end of the list, for order. The entry found may already
683: be at the end. */
684: struct server **up, *tmp;
685:
686: serv = NULL;
687:
688: if (maybe_free_servers)
689: for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp)
690: {
691: tmp = serv->next;
692: if ((serv->flags & SERV_MARK) &&
693: hostname_isequal(alloc_domain, serv->domain))
694: {
695: /* Need to move down? */
696: if (serv->next)
697: {
698: *up = serv->next;
699: daemon->servers_tail->next = serv;
700: daemon->servers_tail = serv;
701: serv->next = NULL;
702: }
703: break;
704: }
705: else
706: up = &serv->next;
707: }
708:
709: if (serv)
710: {
711: free(alloc_domain);
712: alloc_domain = serv->domain;
713: }
714: else
715: {
716: if (!(serv = whine_malloc(sizeof(struct server))))
717: {
718: free(alloc_domain);
719: return 0;
720: }
721:
722: memset(serv, 0, sizeof(struct server));
723:
724: /* Add to the end of the chain, for order */
725: if (daemon->servers_tail)
726: daemon->servers_tail->next = serv;
727: else
728: daemon->servers = serv;
729: daemon->servers_tail = serv;
730: }
731:
732: #ifdef HAVE_LOOP
733: serv->uid = rand32();
734: #endif
735:
736: if (interface)
737: safe_strncpy(serv->interface, interface, sizeof(serv->interface));
738: if (addr)
739: serv->addr = *addr;
740: if (source_addr)
741: serv->source_addr = *source_addr;
742: }
743:
744: serv->flags = flags;
745: serv->domain = alloc_domain;
746: serv->domain_len = strlen(alloc_domain);
747:
748: return 1;
749: }
750:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>