1 /*
2  *  Copyright (C) 2004-2010 Christos Tsantilas
3  *
4  *  This program is free software; you can redistribute it and/or
5  *  modify it under the terms of the GNU Lesser General Public
6  *  License as published by the Free Software Foundation; either
7  *  version 2.1 of the License, or (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  *  Lesser General Public License for more details.
13  *
14  *  You should have received a copy of the GNU Lesser General Public
15  *  License along with this library; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17  *  MA  02110-1301  USA.
18  */
19 
20 #include "common.h"
21 #include "c-icap.h"
22 #include "net_io.h"
23 #include "mem.h"
24 #include "lookup_table.h"
25 #include "cfg_param.h"
26 #include "filetype.h"
27 #include "debug.h"
28 #if defined(USE_REGEX)
29 #include "ci_regex.h"
30 #endif
31 
32 /*string operators */
stringdup(const char * str,ci_mem_allocator_t * allocator)33 void *stringdup(const char *str, ci_mem_allocator_t *allocator)
34 {
35     char *new_s = allocator->alloc(allocator,strlen(str)+1);
36     if (new_s)
37         strcpy(new_s, str);
38     return new_s;
39 }
40 
stringcmp(const void * key1,const void * key2)41 int stringcmp(const void *key1,const void *key2)
42 {
43     if (!key2)
44         return -1;
45     return strcmp((const char *)key1,(const char *)key2);
46 }
47 
stringequal(const void * key1,const void * key2)48 int stringequal(const void *key1,const void *key2)
49 {
50     if (!key2)
51         return 0;
52     return strcmp((const char *)key1,(const char *)key2) == 0;
53 }
54 
stringlen(const void * key)55 size_t stringlen(const void *key)
56 {
57     return strlen((const char *)key)+1;
58 }
59 
stringfree(void * key,ci_mem_allocator_t * allocator)60 void stringfree(void *key, ci_mem_allocator_t *allocator)
61 {
62     allocator->free(allocator, key);
63 }
64 
65 const ci_type_ops_t  ci_str_ops = {
66     stringdup,
67     stringfree,
68     stringcmp,
69     stringlen,
70     stringequal,
71 };
72 
string_ext_cmp(const void * key1,const void * key2)73 int string_ext_cmp(const void *key1,const void *key2)
74 {
75     if (!key2)
76         return -1;
77 
78     if (strcmp((const char *)key1, "*") == 0)
79         return 0;
80 
81     return strcmp((const char *)key1,(const char *)key2);
82 }
83 
string_ext_equal(const void * key1,const void * key2)84 int string_ext_equal(const void *key1,const void *key2)
85 {
86     if (!key2)
87         return 0;
88 
89     if (strcmp((const char *)key1, "*") == 0)
90         return 1;
91 
92     return strcmp((const char *)key1,(const char *)key2) == 0;
93 }
94 
95 
96 const ci_type_ops_t ci_str_ext_ops = {
97     stringdup,
98     stringfree,
99     string_ext_cmp,
100     stringlen,
101     string_ext_equal,
102 };
103 
104 
105 /*int32 operators*/
106 
int32_dup(const char * str,ci_mem_allocator_t * allocator)107 void *int32_dup(const char *str, ci_mem_allocator_t *allocator)
108 {
109     int32_t *i;
110     char *e = NULL;
111     i = allocator->alloc(allocator, sizeof(int32_t));
112     if (i) {
113         *i = strtol(str, &e, 10);
114         if (*e == 'K' || *e == 'k')
115             *i = *i * 1000;
116         else if (*e == 'M' || *e == 'm')
117             *i = *i * 1000000;
118         else if (*e == 'G' || *e == 'g')
119             *i = *i * 1000000000;
120     }
121     return (void *)i;
122 
123 }
124 
int32_cmp(const void * key1,const void * key2)125 int int32_cmp(const void *key1,const void *key2)
126 {
127     int32_t k1, k2;
128     k1 = *(int32_t *)key1;
129     k2 = *(int32_t *)key2;
130     if (k1 < k2)
131         return -1;
132     if (k1 > k2)
133         return 1;
134 
135     return 0;
136 }
137 
int32_equal(const void * key1,const void * key2)138 int int32_equal(const void *key1,const void *key2)
139 {
140     int32_t k1,k2;
141     k1 = *(int32_t *)key1;
142     k2 = *(int32_t *)key2;
143     return k1 == k2;
144 }
145 
int32_len(const void * key)146 size_t int32_len(const void *key)
147 {
148     return (size_t)4;
149 }
150 
int32_free(void * key,ci_mem_allocator_t * allocator)151 void int32_free(void *key, ci_mem_allocator_t *allocator)
152 {
153     /*nothing*/
154     allocator->free(allocator, key);
155 }
156 
157 const ci_type_ops_t  ci_int32_ops = {
158     int32_dup,
159     int32_free,
160     int32_cmp,
161     int32_len,
162     int32_equal
163 };
164 
165 /*uint64 operators*/
uint64_dup(const char * str,ci_mem_allocator_t * allocator)166 void *uint64_dup(const char *str, ci_mem_allocator_t *allocator)
167 {
168     uint64_t *i;
169     char *e = NULL;
170     i = allocator->alloc(allocator, sizeof(uint64_t));
171     if (i) {
172         *i = strtoll(str, &e, 10);
173         if (*e == 'K' || *e == 'k')
174             *i = *i * 1000;
175         else if (*e == 'M' || *e == 'm')
176             *i = *i * 1000000;
177         else if (*e == 'G' || *e == 'g')
178             *i = *i * 1000000000;
179     }
180     return (void *)i;
181 }
182 
uint64_cmp(const void * key1,const void * key2)183 int uint64_cmp(const void *key1,const void *key2)
184 {
185     uint64_t k1,k2;
186     k1 = *(uint64_t *)key1;
187     k2 = *(uint64_t *)key2;
188     if (k1 < k2)
189         return -1;
190     if (k1 > k2)
191         return 1;
192 
193     return 0;
194 }
195 
uint64_equal(const void * key1,const void * key2)196 int uint64_equal(const void *key1,const void *key2)
197 {
198     uint64_t k1,k2;
199     k1 = *(uint64_t *)key1;
200     k2 = *(uint64_t *)key2;
201     return k1 == k2;
202 }
203 
uint64_free(void * key,ci_mem_allocator_t * allocator)204 void uint64_free(void *key, ci_mem_allocator_t *allocator)
205 {
206     allocator->free(allocator, key);
207 }
208 
uint64_len(const void * key)209 size_t uint64_len(const void *key)
210 {
211     return (size_t)sizeof(uint64_t);
212 }
213 
214 const ci_type_ops_t  ci_uint64_ops = {
215     uint64_dup,
216     uint64_free,
217     uint64_cmp,
218     uint64_len,
219     uint64_equal
220 };
221 
222 
223 /*regular expresion operator definition  */
224 #if defined(USE_REGEX)
225 /*We only need the preg field which holds the compiled regular expression
226   but keep the uncompiled string too just for debuging reasons */
227 struct ci_acl_regex {
228     char *str;
229     int flags;
230     ci_regex_t preg;
231 };
232 
233 /*Parse the a regular expression in the form: /regexpression/flags
234   where flags nothing or 'i'. Examples:
235         /^\{[a-z| ]*\}/i
236     /^some test.*t/
237 */
regex_dup(const char * str,ci_mem_allocator_t * allocator)238 void *regex_dup(const char *str, ci_mem_allocator_t *allocator)
239 {
240     struct ci_acl_regex *reg;
241     char *newstr;
242     int flags, recursive;
243 
244     newstr = ci_regex_parse(str, &flags, &recursive);
245 
246     if (!newstr) {
247         ci_debug_printf(1,"Parse error, while parsing regex: '%s')!\n", str);
248         return NULL;
249     }
250 
251     reg = allocator->alloc(allocator,sizeof(struct ci_acl_regex));
252     if (!reg) {
253         ci_debug_printf(1,"Error allocating memory for regex_dup (1)!\n");
254         free(newstr);
255         return NULL;
256     }
257 
258     if ((reg->preg = ci_regex_build(newstr, flags)) == NULL) {
259         ci_debug_printf(1, "Error compiling regular expression :%s (%s)\n", str, newstr);
260         allocator->free(allocator, reg);
261         free(newstr);
262         return NULL;
263     }
264 
265     reg->str = newstr;
266     reg->flags = flags;
267 
268     return reg;
269 }
270 
regex_cmp(const void * key1,const void * key2)271 int regex_cmp(const void *key1, const void *key2)
272 {
273     struct ci_acl_regex *reg = (struct ci_acl_regex *)key1;
274     if (!key2)
275         return -1;
276     return (ci_regex_apply(reg->preg, (const char *)key2, strlen(key2), 0, NULL, NULL) == 0 ? 1 : 0);
277 }
278 
regex_equal(const void * key1,const void * key2)279 int regex_equal(const void *key1, const void *key2)
280 {
281     struct ci_acl_regex *reg = (struct ci_acl_regex *)key1;
282     if (!key2)
283         return 0;
284     return ci_regex_apply(reg->preg, (const char *)key2, strlen(key2), 0, NULL, NULL) != 0;
285 }
286 
regex_len(const void * key)287 size_t regex_len(const void *key)
288 {
289     return strlen(((const struct ci_acl_regex *)key)->str);
290 }
291 
regex_free(void * key,ci_mem_allocator_t * allocator)292 void regex_free(void *key, ci_mem_allocator_t *allocator)
293 {
294     struct ci_acl_regex *reg = (struct ci_acl_regex *)key;
295     ci_regex_free(reg->preg);
296     allocator->free(allocator, reg->str);
297     allocator->free(allocator, reg);
298 }
299 
300 
301 const ci_type_ops_t  ci_regex_ops = {
302     regex_dup,
303     regex_free,
304     regex_cmp,
305     regex_len,
306     regex_equal
307 };
308 #endif
309 
310 /*filetype operators*/
datatype_dup(const char * str,ci_mem_allocator_t * allocator)311 void *datatype_dup(const char *str, ci_mem_allocator_t *allocator)
312 {
313     int type;
314     unsigned int  *val = allocator->alloc(allocator,sizeof(unsigned int));
315     if ((type = ci_magic_type_id(str)) >= 0) {
316         *val = type;
317     } else if ( (type = ci_magic_group_id(str)) >= 0) {
318         *val = type;
319         *val = *val << 16;
320     } else {
321         allocator->free(allocator, val);
322         val = NULL;
323     }
324 
325     return (void *)val;
326 }
327 
datatype_cmp(const void * key1,const void * key2)328 int datatype_cmp(const void *key1, const void *key2)
329 {
330     unsigned int type = *(unsigned int *)key1;
331 
332     if (!key2)
333         return -1;
334 
335     if ( (0xFFFF0000 & type) == 0)
336         return (*(unsigned int *)key1 - *(unsigned int *)key2);
337     else { /*type is group check if key2 belongs to group*/
338         type = type >> 16;
339         if (ci_magic_group_check(*(unsigned int *)key2, type))
340             return 0;
341         else
342             return 1;
343     }
344 }
345 
datatype_equal(const void * key1,const void * key2)346 int datatype_equal(const void *key1, const void *key2)
347 {
348     unsigned int type = *(unsigned int *)key1;
349 
350     if (!key2)
351         return 0;
352 
353     if ( (0xFFFF0000 & type) == 0)
354         return *(unsigned int *)key1 == *(unsigned int *)key2;
355     else { /*type is group check if key2 belongs to group*/
356         type = type >> 16;
357         if (ci_magic_group_check(*(unsigned int *)key2, (int)type))
358             return 1;
359         else
360             return 0;
361     }
362 }
363 
datatype_len(const void * key)364 size_t datatype_len(const void *key)
365 {
366     return sizeof(unsigned int);
367 }
368 
datatype_free(void * key,ci_mem_allocator_t * allocator)369 void datatype_free(void *key, ci_mem_allocator_t *allocator)
370 {
371     /*nothing*/
372     allocator->free(allocator, key);
373 }
374 
375 const ci_type_ops_t  ci_datatype_ops = {
376     datatype_dup,
377     datatype_free,
378     datatype_cmp,
379     datatype_len,
380     datatype_equal
381 };
382 
383 /*IP operators*/
384 #ifdef HAVE_IPV6
385 
386 void ci_list_ipv4_to_ipv6();
387 
388 #define ci_ipv4_inaddr_is_zero(addr) ((addr).ipv4_addr.s_addr==0)
389 #define ci_ipv4_inaddr_are_equal(addr1,addr2) ((addr1).ipv4_addr.s_addr == (addr2).ipv4_addr.s_addr)
390 #define ci_ipv4_inaddr_zero(addr) ((addr).ipv4_addr.s_addr=0)
391 
392 #define ci_ipv6_inaddr_is_zero(addr) ( ci_in6_addr_u32(addr)[0] == 0 && \
393                        ci_in6_addr_u32(addr)[1] == 0 &&  \
394                        ci_in6_addr_u32(addr)[2] == 0 &&  \
395                        ci_in6_addr_u32(addr)[3] == 0)
396 
397 #define ci_ipv6_inaddr_are_equal(addr1,addr2) ( ci_in6_addr_u32(addr1)[0] == ci_in6_addr_u32(addr2)[0] && \
398                         ci_in6_addr_u32(addr1)[1] == ci_in6_addr_u32(addr2)[1] && \
399                         ci_in6_addr_u32(addr1)[2] == ci_in6_addr_u32(addr2)[2] && \
400                         ci_in6_addr_u32(addr1)[3] == ci_in6_addr_u32(addr2)[3])
401 
402 
403 #define ci_ipv6_inaddr_is_v4mapped(addr) (ci_in6_addr_u32(addr)[0] == 0 &&\
404                       ci_in6_addr_u32(addr)[1] == 0 && \
405                       ci_in6_addr_u32(addr)[2] == htonl(0xFFFF))
406 
407 
408 #define ci_ipv4_inaddr_check_net(addr1,addr2,mask) (((addr1).ipv4_addr.s_addr & (mask).ipv4_addr.s_addr) == ((addr2).ipv4_addr.s_addr & (mask).ipv4_addr.s_addr))
409 #define ci_ipv6_inaddr_check_net(addr1,addr2,mask) ((ci_in6_addr_u32(addr1)[0] & ci_in6_addr_u32(mask)[0]) == (ci_in6_addr_u32(addr2)[0] & ci_in6_addr_u32(mask)[0]) &&\
410                             (ci_in6_addr_u32(addr1)[1] & ci_in6_addr_u32(mask)[1]) == (ci_in6_addr_u32(addr2)[1] & ci_in6_addr_u32(mask)[1]) && \
411                             (ci_in6_addr_u32(addr1)[2] & ci_in6_addr_u32(mask)[2]) == (ci_in6_addr_u32(addr2)[2] & ci_in6_addr_u32(mask)[2]) && \
412                             (ci_in6_addr_u32(addr1)[3] & ci_in6_addr_u32(mask)[3]) == (ci_in6_addr_u32(addr2)[3] & ci_in6_addr_u32(mask)[3]))
413 #define ci_ipv4_in_ipv6_check_net(addr1, addr2, mask) (ci_in6_addr_u32(addr2)[0] == 0 && \
414                                ci_in6_addr_u32(addr2)[1] == 0 && \
415                                ci_in6_addr_u32(addr2)[2] == htonl(0xFFFF) && \
416                                ((addr1).ipv4_addr.s_addr & (mask).ipv4_addr.s_addr) == (ci_in6_addr_u32(addr2)[3] & (mask).ipv4_addr.s_addr))
417 #define ci_ipv6_in_ipv4_check_net(addr1, addr2, mask) (ci_in6_addr_u32(addr1)[0] == 0 && \
418                                ci_in6_addr_u32(addr1)[1] == 0 && \
419                                ci_in6_addr_u32(addr1)[2] == htonl(0xFFFF) && \
420                                (ci_in6_addr_u32(addr1)[3] & (mask).ipv4_addr.s_addr) == ((addr2).ipv4_addr.s_addr & (mask).ipv4_addr.s_addr))
421 
422 
423 /*We can do this because ipv4_addr in practice exists in s6_addr[0]*/
424 #define ci_inaddr_ipv4_to_ipv6(addr)( ci_in6_addr_u32(addr)[3] = (addr).ipv4_addr.s_addr,\
425                       ci_in6_addr_u32(addr)[0] = 0,   \
426                       ci_in6_addr_u32(addr)[1] = 0,   \
427                       ci_in6_addr_u32(addr)[2] = htonl(0xFFFF))
428 #define ci_netmask_ipv4_to_ipv6(addr)(ci_in6_addr_u32(addr)[3] = (addr).ipv4_addr.s_addr, \
429                       ci_in6_addr_u32(addr)[0] = htonl(0xFFFFFFFF), \
430                       ci_in6_addr_u32(addr)[1] = htonl(0xFFFFFFFF), \
431                       ci_in6_addr_u32(addr)[2] = htonl(0xFFFFFFFF))
432 #else                           /*if no HAVE_IPV6 */
433 
434 #define ci_ipv4_inaddr_is_zero(addr) ((addr).s_addr==0)
435 #define ci_ipv4_inaddr_are_equal(addr1,addr2) ((addr1).s_addr == (addr2).s_addr)
436 #define ci_ipv4_inaddr_check_net(addr1,addr2,mask) (((addr1).s_addr & (mask).s_addr) == ((addr2).s_addr & (mask).s_addr))
437 
438 #define ci_ipv4_inaddr_zero(addr) ((addr).s_addr=0)
439 
440 #endif                          /*ifdef HAVE_IPV6 */
441 
442 
443 
444 
ip_dup(const char * value,ci_mem_allocator_t * allocator)445 void *ip_dup(const char *value,  ci_mem_allocator_t *allocator)
446 {
447     int socket_family, len;
448     ci_ip_t *ip;
449     char str_addr[CI_IPLEN+1], str_netmask[CI_IPLEN+1];
450     char *pstr;
451     ci_in_addr_t address, netmask;
452 
453     ci_inaddr_zero(address);
454     ci_inaddr_zero(netmask);
455 
456 #ifdef HAVE_IPV6
457     if (strchr(value,':'))
458         socket_family = AF_INET6;
459     else
460 #endif
461         socket_family = AF_INET;
462 
463     if ((pstr=strchr(value,'/'))) {
464         len=(pstr-value);
465         if (len >= CI_IPLEN) {
466             ci_debug_printf(1,"Invalid ip address (len>%d): %s\n", CI_IPLEN, value);
467             return NULL;
468         }
469         strncpy(str_addr,value,len);
470         str_addr[len] = '\0';
471 
472         if (!ci_inet_aton(socket_family, str_addr, &address)) {
473             ci_debug_printf(1,"Invalid ip address in network %s definition\n", value);
474             return NULL;
475         }
476 
477         strncpy(str_netmask, pstr+1, CI_IPLEN);
478         str_netmask[CI_IPLEN] = '\0';
479 
480         if (!ci_inet_aton(socket_family, str_netmask, &netmask)) {
481             ci_debug_printf(1,"Invalid netmask in network %s definition\n", value);
482             return NULL;
483         }
484     } else { /*No netmask defined is a host ip*/
485         if (!ci_inet_aton(socket_family, value, &address)) {
486             ci_debug_printf(1,"Invalid ip address: %s\n", value);
487             return NULL;
488         }
489 #ifdef HAVE_IPV6
490         if (socket_family==AF_INET)
491             ci_ipv4_inaddr_hostnetmask(netmask);
492         else
493             ci_ipv6_inaddr_hostnetmask(netmask);
494 #else
495         ci_ipv4_inaddr_hostnetmask(netmask);
496 #endif
497     }
498 
499     ip= allocator->alloc(allocator, sizeof(ci_ip_t));
500     ip->family = socket_family;
501 
502     ci_inaddr_copy(ip->address, address);
503     ci_inaddr_copy(ip->netmask, netmask);
504 
505     return ip;
506 }
507 
ip_free(void * data,ci_mem_allocator_t * allocator)508 void ip_free(void *data, ci_mem_allocator_t *allocator)
509 {
510     allocator->free(allocator, data);
511 }
512 
ip_len(const void * key)513 size_t ip_len(const void *key)
514 {
515     return sizeof(ci_ip_t);
516 }
517 
ip_cmp(const void * ref_key,const void * key_check)518 int ip_cmp(const void *ref_key, const void *key_check)
519 {
520     /*Not implemented*/
521     return 0;
522 }
523 
ip_equal(const void * ref_key,const void * key_check)524 int ip_equal(const void *ref_key, const void *key_check)
525 {
526     const ci_ip_t *ip_ref = (const ci_ip_t *)ref_key;
527     const ci_ip_t *ip_check = (const ci_ip_t *)key_check;
528     char buf[128],buf1[128],buf2[128];
529 
530     if (!ip_check)
531         return 0;
532 
533     ci_debug_printf(9,"going to check addresses  ip address: %s %s/%s\n",
534                     ci_inet_ntoa(ip_check->family,&ip_check->address, buf, 128),
535                     ci_inet_ntoa(ip_ref->family,&ip_ref->address, buf1, 128),
536                     ci_inet_ntoa(ip_ref->family,&ip_ref->netmask, buf2, 128)
537                    );
538 #ifdef HAVE_IPV6
539     if (ip_check->family == AF_INET) {
540         if (ip_ref->family == AF_INET)
541             return ci_ipv4_inaddr_check_net(ip_ref->address, ip_check->address, ip_ref->netmask);
542         //else add->family == AF_INET6
543         return ci_ipv6_in_ipv4_check_net(ip_ref->address, ip_check->address, ip_ref->netmask);
544     }
545     //else assuming  ip_check->family == AF_INET6
546     if (ip_ref->family == AF_INET6)
547         return ci_ipv6_inaddr_check_net(ip_ref->address, ip_check->address, ip_ref->netmask);
548     //else ip->family == AF_INET
549     return ci_ipv4_in_ipv6_check_net(ip_ref->address, ip_check->address, ip_ref->netmask);
550 #else
551     return ci_ipv4_inaddr_check_net(ip_ref->address, ip_check->address, ip_ref->netmask);
552 #endif
553 
554 }
555 
ip_sockaddr_cmp(const void * ref_key,const void * key_check)556 int ip_sockaddr_cmp(const void *ref_key, const void *key_check)
557 {
558     /*Not implemented*/
559     return 1;
560 }
561 
ip_sockaddr_equal(const void * ref_key,const void * key_check)562 int ip_sockaddr_equal(const void *ref_key, const void *key_check)
563 {
564     const ci_ip_t *ip_ref = (const ci_ip_t *)ref_key;
565     const ci_sockaddr_t *ip_check = (const ci_sockaddr_t *)key_check;
566     char buf[128],buf1[128],buf2[128];
567 
568     if (!ip_check)
569         return 0;
570 
571     ci_debug_printf(9,"going to check addresses  ip address: %s %s/%s\n",
572                     ci_inet_ntoa(ip_check->ci_sin_family,ip_check->ci_sin_addr, buf, 128),
573                     ci_inet_ntoa(ip_ref->family,&ip_ref->address, buf1, 128),
574                     ci_inet_ntoa(ip_ref->family,&ip_ref->netmask, buf2, 128)
575                    );
576 #ifdef HAVE_IPV6
577     if (ip_check->ci_sin_family == AF_INET) {
578         if (ip_ref->family == AF_INET)
579             return ci_ipv4_inaddr_check_net(ip_ref->address, *(ci_in_addr_t *)ip_check->ci_sin_addr, ip_ref->netmask);
580         //else add->family == AF_INET6
581         return ci_ipv6_in_ipv4_check_net(ip_ref->address, *(ci_in_addr_t *)ip_check->ci_sin_addr, ip_ref->netmask);
582     }
583     //else assuming  ip_check->ci_sin_family == AF_INET6
584     if (ip_ref->family == AF_INET6)
585         return ci_ipv6_inaddr_check_net(ip_ref->address, *(ci_in_addr_t *)ip_check->ci_sin_addr, ip_ref->netmask);
586     //else ip->family == AF_INET
587     return ci_ipv4_in_ipv6_check_net(ip_ref->address, *(ci_in_addr_t *)ip_check->ci_sin_addr, ip_ref->netmask);
588 #else
589     return ci_ipv4_inaddr_check_net(ip_ref->address, *(ci_in_addr_t *)ip_check->ci_sin_addr, ip_ref->netmask);
590 #endif
591 
592 }
593 
594 
595 
596 const ci_type_ops_t  ci_ip_ops = {
597     ip_dup,
598     ip_free,
599     ip_cmp,
600     ip_len,
601     ip_equal
602 };
603 
604 
605 
606 const ci_type_ops_t ci_ip_sockaddr_ops = {
607     ip_dup,
608     ip_free,
609     ip_sockaddr_cmp,
610     ip_len,
611     ip_sockaddr_equal
612 };
613