diff --git a/src/conf.c b/src/conf.c index 0d55f64..eb2ed0b 100644 --- a/src/conf.c +++ b/src/conf.c @@ -72,11 +72,13 @@ void config_address_destroy(void) int config_address(char *value) { struct dns_address *address = NULL; + struct dns_address *oldaddress; char ip[MAX_IP_LEN]; char domain_key[DNS_MAX_CONF_CNAME_LEN]; char *begin = NULL; char *end = NULL; int len = 0; + int port; struct sockaddr_storage addr; socklen_t addr_len = sizeof(addr); char type = '4'; @@ -101,9 +103,12 @@ int config_address(char *value) len = end - begin; memcpy(address->domain, begin, len); address->domain[len] = 0; - strncpy(ip, end + 1, MAX_IP_LEN); reverse_string(domain_key + 1, address->domain, len); + if (parse_ip(end + 1, ip, &port) != 0) { + goto errout; + } + if (getaddr_by_host(ip, (struct sockaddr *)&addr, &addr_len) != 0) { goto errout; } @@ -135,7 +140,10 @@ int config_address(char *value) domain_key[0] = type; len++; - art_insert(&dns_conf_address, (unsigned char *)domain_key, len, address); + oldaddress = art_insert(&dns_conf_address, (unsigned char *)domain_key, len, address); + if (oldaddress) { + free(oldaddress); + } return 0; errout: @@ -277,4 +285,4 @@ errout: fclose(fp); } return -1; -} \ No newline at end of file +} diff --git a/src/dns.c b/src/dns.c index c8cf929..2df3966 100644 --- a/src/dns.c +++ b/src/dns.c @@ -412,6 +412,95 @@ int dns_get_RAW(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, void * return 0; } +int dns_add_OPT(struct dns_packet *packet, dns_rr_type type, unsigned short opt_code, unsigned short opt_len, struct dns_opt *opt) +{ + // TODO + + int maxlen = 0; + int len = 0; + struct dns_data_context data_context; + int total_len = sizeof(*opt) + opt->length; + int ttl = 0; + + /* + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + 0: | OPTION-CODE | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + 2: | OPTION-LENGTH | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + 4: | | + / OPTION-DATA / + / / + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + */ + unsigned char *data = _dns_add_rrs_start(packet, &maxlen); + if (data == NULL) { + return -1; + } + + if (total_len > maxlen) { + return -1; + } + + data_context.data = data; + data_context.ptr = data; + data_context.maxsize = maxlen; + + ttl = (opt_code << 16) | opt_len; + + /* add rr head */ + len = _dns_add_rr_head(&data_context, "", type, DNS_C_IN, ttl, total_len); + if (len < 0) { + return -1; + } + + /* add rr data */ + memcpy(data_context.ptr, opt, total_len); + data_context.ptr += total_len; + len = data_context.ptr - data_context.data; + + return dns_rr_add_end(packet, type, DNS_T_OPT, len); +} + +int dns_get_OPT(struct dns_rrs *rrs, unsigned short *opt_code, unsigned short *opt_len, struct dns_opt *opt, int *opt_maxlen) +{ + // TODO + + int qtype = 0; + int qclass = 0; + int rr_len = 0; + int ret = 0; + struct dns_data_context data_context; + char domain[DNS_MAX_CNAME_LEN]; + int maxsize = DNS_MAX_CNAME_LEN; + int ttl = 0; + unsigned char *data = rrs->data; + + data_context.data = data; + data_context.ptr = data; + data_context.maxsize = rrs->len; + + /* get rr head */ + ret = _dns_get_rr_head(&data_context, domain, maxsize, &qtype, &qclass, &ttl, &rr_len); + if (ret < 0) { + return -1; + } + + if (qtype != rrs->type || rr_len > *opt_len) { + return -1; + } + + /* get rr data */ + *opt_code = ttl >> 16; + *opt_len = ttl & 0xFFFF; + memcpy(opt, data_context.ptr, rr_len); + data_context.ptr += rr_len; + *opt_maxlen = rr_len; + + return 0; +} + + int dns_add_CNAME(struct dns_packet *packet, dns_rr_type type, char *domain, int ttl, char *cname) { int rr_len = strnlen(cname, DNS_MAX_CNAME_LEN) + 1; @@ -546,6 +635,47 @@ int dns_get_SOA(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, struct return 0; } +int dns_add_OPT_ECS(struct dns_packet *packet, dns_rr_type type, struct dns_opt_ecs *ecs) +{ + // TODO + + unsigned char opt_data[DNS_MAX_OPT_LEN]; + struct dns_opt *opt = (struct dns_opt *)opt_data; + int len = 0; + + opt->code = DNS_OPT_T_ECS; + opt->length = sizeof(*ecs); + memcpy(opt->data, ecs, sizeof(*ecs)); + + /* ecs size 4 + bit of address*/ + len = sizeof(*opt) + 4; + len += (ecs->source_prefix / 8); + len += (ecs->source_prefix % 8 > 0) ? 1 : 0; + + return dns_add_OPT(packet, type, DNS_OPT_T_ECS, len, opt); +} + +int dns_get_OPT_ECS(struct dns_rrs *rrs, unsigned short *opt_code, unsigned short *opt_len, struct dns_opt_ecs *ecs) +{ + // TODO + + unsigned char opt_data[DNS_MAX_OPT_LEN]; + struct dns_opt *opt = (struct dns_opt *)opt_data; + int len = sizeof(opt_data); + + if (dns_get_OPT(rrs, opt_code, opt_len, opt, &len) != 0) { + return -1; + } + + if (opt->code != DNS_OPT_T_ECS) { + return -1; + } + + memcpy(ecs, opt->data, opt->length); + + return 0; +} + /* * Format: * |DNS_NAME\0(string)|qtype(short)|qclass(short)| @@ -1074,6 +1204,108 @@ int _dns_encode_SOA(struct dns_context *context, struct dns_rrs *rrs) return 0; } + +static int _dns_decode_opt_ecs(struct dns_context *context, struct dns_opt_ecs *ecs) +{ + // TODO + + int len = 0; + if (_dns_left_len(context) < 4) { + return -1; + } + + ecs->family = dns_read_short(&context->ptr); + ecs->source_prefix = dns_read_char(&context->ptr); + ecs->scope_prefix = dns_read_char(&context->ptr); + len = (ecs->source_prefix / 8); + len += (ecs->source_prefix % 8 > 0) ? 1 : 0; + + if (_dns_left_len(context) < len) { + return -1; + } + + memcpy(ecs->addr, context->ptr, len); + context->ptr += len; + + return 0; +} + +int _dns_encode_OPT(struct dns_context *context, struct dns_rrs *rrs) +{ + // TODO + + return 0; +} + +static int _dns_decode_opt(struct dns_context *context, dns_rr_type type, unsigned int ttl, int rr_len) +{ + unsigned short opt_code; + unsigned short opt_len; + unsigned short ercode = (ttl >> 16) & 0xFFFF; + unsigned short ever = (ttl) & 0xFFFF; + unsigned char *start = context->ptr; + struct dns_packet *packet = context->packet; + int ret = 0; + /* + Field Name Field Type Description + ------------------------------------------------------ + NAME domain name empty (root domain) + TYPE u_int16_t OPT + CLASS u_int16_t sender's UDP payload size + TTL u_int32_t extended RCODE and flags + RDLEN u_int16_t describes RDATA + RDATA octet stream {attribute,value} pairs + + +0 (MSB) +1 (LSB) + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + 0: | OPTION-CODE | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + 2: | OPTION-LENGTH | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + 4: | | + / OPTION-DATA / + / / + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + TTL + +0 (MSB) +1 (LSB) + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + 0: | EXTENDED-RCODE | VERSION | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + 2: | Z | + +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + */ + + if (ercode != 0) { + tlog(TLOG_ERROR, "extend rcode invalid."); + return -1; + } + ever = ever; + + while (context->ptr - start < rr_len) { + opt_code = dns_read_short(&context->ptr); + opt_len = dns_read_short(&context->ptr); + switch (opt_code) { + case DNS_OPT_T_ECS: { + struct dns_opt_ecs ecs; + ret = _dns_decode_opt_ecs(context, &ecs); + if (ret != 0 ) { + tlog(TLOG_ERROR, "decode ecs failed."); + return -1; + } + + ret = dns_add_OPT_ECS(packet, type, &ecs); + } break; + default: + context->ptr += opt_len; + tlog(TLOG_DEBUG, "DNS opt type = %d not supported", opt_code); + break; + } + } + + return 0; +} + static int _dns_decode_qd(struct dns_context *context) { struct dns_packet *packet = context->packet; @@ -1200,6 +1432,19 @@ static int _dns_decode_an(struct dns_context *context, dns_rr_type type) return -1; } } break; + case DNS_T_OPT: { + unsigned char *opt_start = context->ptr; + ret = _dns_decode_opt(context, type, ttl, rr_len); + if (ret < 0) { + tlog(TLOG_ERROR, "decode opt failed, %s", domain); + return -1; + } + + if (context->ptr - opt_start != rr_len) { + tlog(TLOG_ERROR, "opt length mitchmatch, %s\n", domain); + return -1; + } + } break; default: context->ptr += rr_len; tlog(TLOG_DEBUG, "DNS type = %d not supported", qtype); @@ -1263,6 +1508,12 @@ static int _dns_encode_an(struct dns_context *context, struct dns_rrs *rrs) return -1; } break; + case DNS_T_OPT: + ret = _dns_encode_OPT(context, rrs); + if (ret < 0) { + return -1; + } + break; default: break; } diff --git a/src/dns.h b/src/dns.h index 6166e51..266f94a 100644 --- a/src/dns.h +++ b/src/dns.h @@ -9,6 +9,7 @@ #define DNS_RR_A_LEN 4 #define DNS_RR_AAAA_LEN 16 #define DNS_MAX_CNAME_LEN 256 +#define DNS_MAX_OPT_LEN 256 #define DNS_IN_PACKSIZE (512 * 4) #define DNS_PACKSIZE (512 * 8) @@ -44,6 +45,11 @@ typedef enum dns_type { DNS_T_ALL = 255 } dns_type_t; +typedef enum dns_opt_code { + DNS_OPT_T_ECS = 8, + DNS_OPT_T_ALL = 255 +} dns_opt_code_t; + typedef enum dns_opcode { DNS_OP_QUERY = 0, DNS_OP_IQUERY = 1, @@ -128,7 +134,24 @@ struct dns_soa { unsigned int expire; unsigned int minimum; } __attribute__((packed)); -; + +#define DNS_OPT_ECS_FAMILY_IPV4 1 +#define DNS_OPT_ECS_FAMILY_IPV6 2 + +/* OPT ECS */ +struct dns_opt_ecs { + unsigned short family; + unsigned char source_prefix; + unsigned char scope_prefix; + unsigned char addr[DNS_RR_AAAA_LEN]; +}; + +/* OPT */ +struct dns_opt { + unsigned short code; + unsigned short length; + unsigned char data[0]; +} __attribute__((packed)); struct dns_rrs *dns_get_rrs_next(struct dns_packet *packet, struct dns_rrs *rrs); struct dns_rrs *dns_get_rrs_start(struct dns_packet *packet, dns_rr_type type, int *count); @@ -156,6 +179,12 @@ int dns_get_AAAA(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, unsig int dns_add_SOA(struct dns_packet *packet, dns_rr_type type, char *domain, int ttl, struct dns_soa *soa); int dns_get_SOA(struct dns_rrs *rrs, char *domain, int maxsize, int *ttl, struct dns_soa *soa); + +int dns_add_OPT(struct dns_packet *packet, dns_rr_type type, unsigned short opt_code, unsigned short opt_len, struct dns_opt *opt); +int dns_get_OPT(struct dns_rrs *rrs, unsigned short *opt_code, unsigned short *opt_len, struct dns_opt *opt, int *opt_maxlen); + +int dns_add_OPT_ECS(struct dns_packet *packet, dns_rr_type type, struct dns_opt_ecs *ecs); +int dns_get_OPT_ECS(struct dns_rrs *rrs, unsigned short *opt_code, unsigned short *opt_len, struct dns_opt_ecs *ecs); /* * Packet operation */ diff --git a/src/dns_server.c b/src/dns_server.c index 6080e70..49b29b2 100644 --- a/src/dns_server.c +++ b/src/dns_server.c @@ -49,6 +49,7 @@ #include #define DNS_MAX_EVENTS 256 +#define DNS_SERVER_TMOUT_TTL (3 * 60) /* dns server data */ struct dns_server { @@ -268,11 +269,25 @@ int _dns_server_request_complete(struct dns_request *request) if (request->qtype == DNS_T_A) { tlog(TLOG_INFO, "result: %s, rcode: %d, %d.%d.%d.%d\n", request->domain, request->rcode, request->ipv4_addr[0], request->ipv4_addr[1], request->ipv4_addr[2], request->ipv4_addr[3]); + + if (request->has_ipv4) { + if (request->has_ping_result == 0 && request->ttl_v4 > DNS_SERVER_TMOUT_TTL) { + request->ttl_v4 = DNS_SERVER_TMOUT_TTL; + } + } + dns_cache_insert(request->domain, request->ttl_v4, DNS_T_A, request->ipv4_addr, DNS_RR_A_LEN); } else if (request->qtype == DNS_T_AAAA) { tlog(TLOG_INFO, "result :%s, rcode: %d, %.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", request->domain, request->rcode, request->ipv6_addr[0], request->ipv6_addr[1], request->ipv6_addr[2], request->ipv6_addr[3], request->ipv6_addr[4], request->ipv6_addr[5], request->ipv6_addr[6], request->ipv6_addr[7], request->ipv6_addr[8], request->ipv6_addr[9], request->ipv6_addr[10], request->ipv6_addr[11], request->ipv6_addr[12], request->ipv6_addr[13], request->ipv6_addr[14], request->ipv6_addr[15]); + + if (request->has_ipv6) { + if (request->has_ping_result == 0 && request->ttl_v6 > DNS_SERVER_TMOUT_TTL) { + request->ttl_v6 = DNS_SERVER_TMOUT_TTL; + } + dns_cache_insert(request->domain, request->ttl_v6, DNS_T_AAAA, request->ipv6_addr, DNS_RR_AAAA_LEN); + } } _dns_reply(request); @@ -333,7 +348,6 @@ void _dns_server_ping_result(struct ping_host_struct *ping_host, const char *hos { struct dns_request *request = userptr; int may_complete = 0; - int addr_type = 0; if (request == NULL) { return; @@ -355,7 +369,6 @@ void _dns_server_ping_result(struct ping_host_struct *ping_host, const char *hos request->ping_ttl_v4 = rtt; request->has_ipv4 = 1; memcpy(request->ipv4_addr, &addr_in->sin_addr.s_addr, 4); - addr_type = 4; } } break; case AF_INET6: { @@ -366,14 +379,12 @@ void _dns_server_ping_result(struct ping_host_struct *ping_host, const char *hos request->ping_ttl_v4 = rtt; request->has_ipv4 = 1; memcpy(request->ipv4_addr, addr_in6->sin6_addr.s6_addr + 12, 4); - addr_type = 4; } } else { if (request->ping_ttl_v6 > rtt) { request->ping_ttl_v6 = rtt; request->has_ipv6 = 1; memcpy(request->ipv6_addr, addr_in6->sin6_addr.s6_addr, 16); - addr_type = 6; } } } break; @@ -396,11 +407,6 @@ void _dns_server_ping_result(struct ping_host_struct *ping_host, const char *hos if (may_complete) { _dns_server_request_complete(request); _dns_server_request_remove(request); - if (addr_type == 4) { - dns_cache_insert(request->domain, request->ttl_v4, DNS_T_A, request->ipv4_addr, DNS_RR_A_LEN); - } else if (addr_type == 6) { - dns_cache_insert(request->domain, request->ttl_v6, DNS_T_AAAA, request->ipv6_addr, DNS_RR_AAAA_LEN); - } } } @@ -508,6 +514,10 @@ static int _dns_server_process_answer(struct dns_request *request, char *domain, memcpy(request->ipv4_addr, addr, DNS_RR_A_LEN); request->ttl_v4 = ttl; request->has_ipv4 = 1; + } else { + if (ttl < request->ttl_v4) { + request->ttl_v4 = ttl; + } } if (_dns_ip_address_check_add(request, addr, DNS_T_A) != 0) { _dns_server_request_release(request); @@ -539,6 +549,10 @@ static int _dns_server_process_answer(struct dns_request *request, char *domain, memcpy(request->ipv6_addr, addr, DNS_RR_AAAA_LEN); request->ttl_v6 = ttl; request->has_ipv6 = 1; + } else { + if (ttl < request->ttl_v6) { + request->ttl_v6 = ttl; + } } if (_dns_ip_address_check_add(request, addr, DNS_T_AAAA) != 0) { @@ -546,7 +560,7 @@ static int _dns_server_process_answer(struct dns_request *request, char *domain, break; } - sprintf(name, "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], + sprintf(ip, "%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x:%.2x%.2x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7], addr[8], addr[9], addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]); if (_dns_server_ping(request, ip) != 0) { @@ -702,7 +716,7 @@ static struct dns_address *_dns_server_get_address_by_domain(char *domain, int q domain_key[0] = type; domain_len++; - return art_substring(&dns_conf_address, (unsigned char *)domain_key, domain_len);; + return art_substring(&dns_conf_address, (unsigned char *)domain_key, domain_len); } static int _dns_server_process_address(struct dns_request *request, struct dns_packet *packet) @@ -745,7 +759,7 @@ errout: static int _dns_server_process_cache(struct dns_request *request, struct dns_packet *packet) { struct dns_cache *dns_cache = NULL; - + dns_cache = dns_cache_get(request->domain, request->qtype); if (dns_cache == NULL) { goto errout; diff --git a/src/lib/art.c b/src/lib/art.c index 7ac67e2..88994bb 100644 --- a/src/lib/art.c +++ b/src/lib/art.c @@ -170,9 +170,9 @@ static art_node** find_child(art_node *n, unsigned char c) { case NODE4: p.p1 = (art_node4*)n; for (i=0 ; i < n->num_children; i++) { - /* this cast works around a bug in gcc 5.1 when unrolling loops - * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124 - */ + /* this cast works around a bug in gcc 5.1 when unrolling loops + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59124 + */ if (((unsigned char*)p.p1->keys)[i] == c) return &p.p1->children[i]; } @@ -421,8 +421,8 @@ static void add_child48(art_node48 *n, art_node **ref, unsigned char c, void *ch n->n.num_children++; } else { art_node256 *new_node = (art_node256*)alloc_node(NODE256); - int i; - for (i=0;i<256;i++) { + int i; + for (i=0;i<256;i++) { if (n->keys[i]) { new_node->children[i] = n->children[n->keys[i] - 1]; } @@ -461,8 +461,8 @@ static void add_child16(art_node16 *n, art_node **ref, unsigned char c, void *ch #else // Compare the key to all 16 stored keys unsigned bitfield = 0; - int i; - for (i = 0; i < 16; ++i) { + int i; + for (i = 0; i < 16; ++i) { if (c < n->keys[i]) bitfield |= (1 << i); } @@ -489,9 +489,9 @@ static void add_child16(art_node16 *n, art_node **ref, unsigned char c, void *ch } else { art_node48 *new_node = (art_node48*)alloc_node(NODE48); - int i; + int i; - // Copy the child pointers and populate the key map + // Copy the child pointers and populate the key map memcpy(new_node->children, n->children, sizeof(void*)*n->n.num_children); for (i=0;in.num_children;i++) { @@ -688,8 +688,8 @@ static void remove_child256(art_node256 *n, art_node **ref, unsigned char c) { copy_header((art_node*)new_node, (art_node*)n); int pos = 0; - int i; - for (i=0;i<256;i++) { + int i; + for (i=0;i<256;i++) { if (n->children[i]) { new_node->children[pos] = n->children[i]; new_node->keys[i] = pos + 1; @@ -712,8 +712,8 @@ static void remove_child48(art_node48 *n, art_node **ref, unsigned char c) { copy_header((art_node*)new_node, (art_node*)n); int child = 0; - int i; - for (i=0;i<256;i++) { + int i; + for (i=0;i<256;i++) { pos = n->keys[i]; if (pos) { new_node->keys[child] = i; @@ -858,8 +858,8 @@ static int recursive_iter(art_node *n, art_callback cb, void *data) { } int idx, res; - int i; - switch (n->type) { + int i; + switch (n->type) { case NODE4: for (i=0; i < n->num_children; i++) { res = recursive_iter(((art_node4*)n)->children[i], cb, data); @@ -941,12 +941,6 @@ int art_iter_prefix(art_tree *t, const unsigned char *key, int key_len, art_call art_node *n = t->root; int prefix_len, depth = 0; while (n) { - - if (IS_LEAF(n)) { - n = (art_node*)LEAF_RAW(n); - art_leaf *l = (art_leaf*)n; - printf("LEAF: %s\n", l->key); - } // Might be a leaf if (IS_LEAF(n)) { n = (art_node*)LEAF_RAW(n); @@ -998,7 +992,7 @@ int art_iter_prefix(art_tree *t, const unsigned char *key, int key_len, art_call static int str_prefix_matches(const art_leaf *n, const unsigned char *str, int str_len) { // Fail if the key length is too short - if (n->key_len > (uint32_t)str_len) return 1; + if (n->key_len > (uint32_t)str_len) return 1; // Compare the keys return memcmp(str, n->key, n->key_len); @@ -1008,33 +1002,40 @@ void *art_substring(const art_tree *t, const unsigned char *str, int str_len) { art_node **child; art_node *n = t->root; - art_leaf *found = NULL; + art_node *m; + art_leaf *found = NULL; + int prefix_len, depth = 0; - int prefix_len, depth = 0; while (n) { // Might be a leaf if (IS_LEAF(n)) { n = (art_node*)LEAF_RAW(n); // Check if the expanded path matches if (!str_prefix_matches((art_leaf*)n, str, str_len)) { - found = (art_leaf*)n; + found = (art_leaf*)n; } - break; - } + break; + } + + // Check if current is leaf + child = find_child(n, 0); + m = (child) ? *child : NULL; + if (m && IS_LEAF(m)) { + m = (art_node*)LEAF_RAW(m); + // Check if the expanded path matches + if (!str_prefix_matches((art_leaf*)m, str, str_len)) { + found = (art_leaf*)m; + } + } // Bail if the prefix does not match if (n->partial_len) { prefix_len = check_prefix(n, str, str_len, depth); if (prefix_len != min(MAX_PREFIX_LEN, n->partial_len)) - break; - depth = depth + n->partial_len; + break; + depth = depth + n->partial_len; } - art_leaf *l = maximum(n); - if (!str_prefix_matches(l, str, str_len)) { - found = l; - } - // Recursively search child = find_child(n, str[depth]); n = (child) ? *child : NULL; @@ -1042,8 +1043,8 @@ void *art_substring(const art_tree *t, const unsigned char *str, int str_len) } if (found == NULL) { - return NULL; - } + return NULL; + } return found->value; -} \ No newline at end of file +}