#define EFL_NET_IP_ADDRESS_PROTECTED 1 #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_ARPA_INET_H # include #endif #include "Ecore.h" #include "Ecore_Con.h" #include "ecore_con_private.h" typedef struct _Efl_Net_Ip_Address_Data { char string[INET6_ADDRSTRLEN + sizeof("[]:65536")]; union { struct sockaddr addr; struct sockaddr_in ipv4; struct sockaddr_in6 ipv6; }; Eina_Slice addr_slice; } Efl_Net_Ip_Address_Data; typedef struct _Efl_Net_Ip_Address_Resolve_Value { Eina_Stringshare *request_address; /**< The 'address' argument given to * Efl.Net.Ip_Address.resolve */ Eina_Stringshare *canonical_name; /**< The canonical name, if it was requested in * flags */ const Eina_Value_Array results; /**< The resolved objects. Do not modify this array but * you can keep reference to elements using efl_ref() * and efl_unref() */ } Efl_Net_Ip_Address_Resolve_Value; #define MY_CLASS EFL_NET_IP_ADDRESS_CLASS EOLIAN static Eo * _efl_net_ip_address_efl_object_finalize(Eo *o, Efl_Net_Ip_Address_Data *pd) { const uint16_t *pport; o = efl_finalize(efl_super(o, MY_CLASS)); if (!o) return NULL; EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->addr.sa_family == 0, NULL); if (!efl_net_ip_port_fmt(pd->string, sizeof(pd->string), &pd->addr)) { ERR("Could not format address!"); return NULL; } if (pd->addr.sa_family == AF_INET6) pport = &pd->ipv6.sin6_port; else pport = &pd->ipv4.sin_port; if (*pport == 0) /* port == 0, no ":0" in the string */ { char *d = strrchr(pd->string, ':'); EINA_SAFETY_ON_NULL_RETURN_VAL(d, NULL); *d = '\0'; } return o; } EOLIAN static const char * _efl_net_ip_address_string_get(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return pd->string; } EOLIAN static void _efl_net_ip_address_family_set(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd, int family) { if (pd->addr.sa_family == family) return; EINA_SAFETY_ON_TRUE_RETURN(pd->addr.sa_family != 0); EINA_SAFETY_ON_TRUE_RETURN((family != AF_INET) && (family != AF_INET6)); pd->addr.sa_family = family; if (family == AF_INET6) { pd->addr_slice.mem = &pd->ipv6.sin6_addr; pd->addr_slice.len = sizeof(pd->ipv6.sin6_addr); } else { pd->addr_slice.mem = &pd->ipv4.sin_addr; pd->addr_slice.len = sizeof(pd->ipv4.sin_addr); } } EOLIAN static int _efl_net_ip_address_family_get(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return pd->addr.sa_family; } EOLIAN static void _efl_net_ip_address_port_set(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd, uint16_t port) { uint16_t *pport, nport = eina_htons(port); EINA_SAFETY_ON_TRUE_RETURN(pd->addr.sa_family == 0); if (pd->addr.sa_family == AF_INET6) pport = &pd->ipv6.sin6_port; else pport = &pd->ipv4.sin_port; if (*pport == nport) return; if (*pport) { ERR("port already set to %hu, new %hu", eina_ntohs(*pport), port); return; } *pport = nport; } EOLIAN static uint16_t _efl_net_ip_address_port_get(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { const uint16_t *pport; EINA_SAFETY_ON_TRUE_RETURN_VAL(pd->addr.sa_family == 0, 0); if (pd->addr.sa_family == AF_INET6) pport = &pd->ipv6.sin6_port; else pport = &pd->ipv4.sin_port; return eina_ntohs(*pport); } EOLIAN static void _efl_net_ip_address_address_set(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd, Eina_Slice address) { Eina_Rw_Slice rw_slice; size_t i; EINA_SAFETY_ON_TRUE_RETURN(pd->addr.sa_family == 0); rw_slice.mem = (void *)pd->addr_slice.mem; rw_slice.len = pd->addr_slice.len; EINA_SAFETY_ON_TRUE_RETURN(rw_slice.len != address.len); if (eina_slice_compare(eina_rw_slice_slice_get(rw_slice), address) == 0) return; for (i = 0; i < rw_slice.len; i++) { if (rw_slice.bytes[i]) { char old_str[INET6_ADDRSTRLEN] = ""; char new_str[INET6_ADDRSTRLEN] = ""; if (!inet_ntop(pd->addr.sa_family, rw_slice.mem, old_str, sizeof(old_str))) { old_str[0] = '?'; old_str[1] = '\0'; } if (!inet_ntop(pd->addr.sa_family, address.mem, new_str, sizeof(new_str))) { new_str[0] = '?'; new_str[1] = '\0'; } ERR("address already set to %s, new %s", old_str, new_str); return; } } eina_rw_slice_copy(rw_slice, address); } EOLIAN static Eina_Slice _efl_net_ip_address_address_get(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return pd->addr_slice; } EOLIAN static void _efl_net_ip_address_sockaddr_set(Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd, const void *ptr) { const struct sockaddr *sockaddr = ptr; EINA_SAFETY_ON_TRUE_RETURN(pd->addr.sa_family != 0); EINA_SAFETY_ON_NULL_RETURN(sockaddr); EINA_SAFETY_ON_TRUE_RETURN((sockaddr->sa_family != AF_INET) && (sockaddr->sa_family != AF_INET6)); if (sockaddr->sa_family == AF_INET6) { memcpy(&pd->ipv6, sockaddr, sizeof(pd->ipv6)); pd->addr_slice.mem = &pd->ipv6.sin6_addr; pd->addr_slice.len = sizeof(pd->ipv6.sin6_addr); } else { memcpy(&pd->ipv4, sockaddr, sizeof(pd->ipv4)); pd->addr_slice.mem = &pd->ipv4.sin_addr; pd->addr_slice.len = sizeof(pd->ipv4.sin_addr); } } EOLIAN static const void * _efl_net_ip_address_sockaddr_get(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return &pd->addr; } #define IPV4_ADDR_GET(pd) eina_ntohl(pd->ipv4.sin_addr.s_addr) EOLIAN static Eina_Bool _efl_net_ip_address_ipv4_class_a_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return (pd->addr.sa_family == AF_INET) && IN_CLASSA(IPV4_ADDR_GET(pd)); } EOLIAN static Eina_Bool _efl_net_ip_address_ipv4_class_b_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return (pd->addr.sa_family == AF_INET) && IN_CLASSB(IPV4_ADDR_GET(pd)); } EOLIAN static Eina_Bool _efl_net_ip_address_ipv4_class_c_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return (pd->addr.sa_family == AF_INET) && IN_CLASSC(IPV4_ADDR_GET(pd)); } EOLIAN static Eina_Bool _efl_net_ip_address_ipv4_class_d_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return (pd->addr.sa_family == AF_INET) && IN_CLASSD(IPV4_ADDR_GET(pd)); } EOLIAN static Eina_Bool _efl_net_ip_address_ipv6_v4mapped_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return (pd->addr.sa_family == AF_INET6) && IN6_IS_ADDR_V4MAPPED(&pd->ipv6.sin6_addr); } EOLIAN static Eina_Bool _efl_net_ip_address_ipv6_v4compat_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return (pd->addr.sa_family == AF_INET6) && IN6_IS_ADDR_V4COMPAT(&pd->ipv6.sin6_addr); } EOLIAN static Eina_Bool _efl_net_ip_address_ipv6_local_link_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return (pd->addr.sa_family == AF_INET6) && IN6_IS_ADDR_LINKLOCAL(&pd->ipv6.sin6_addr); } EOLIAN static Eina_Bool _efl_net_ip_address_ipv6_local_site_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { return (pd->addr.sa_family == AF_INET6) && IN6_IS_ADDR_SITELOCAL(&pd->ipv6.sin6_addr); } EOLIAN static Eina_Bool _efl_net_ip_address_multicast_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { if (pd->addr.sa_family == AF_INET6) return IN6_IS_ADDR_MULTICAST(&pd->ipv6.sin6_addr); else return IN_MULTICAST(IPV4_ADDR_GET(pd)); } EOLIAN static Eina_Bool _efl_net_ip_address_loopback_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { if (pd->addr.sa_family == AF_INET6) return IN6_IS_ADDR_LOOPBACK(&pd->ipv6.sin6_addr); else return IPV4_ADDR_GET(pd) == INADDR_LOOPBACK; } EOLIAN static Eina_Bool _efl_net_ip_address_any_check(const Eo *o EINA_UNUSED, Efl_Net_Ip_Address_Data *pd) { size_t i; for (i = 0; i < pd->addr_slice.len; i++) { if (pd->addr_slice.bytes[i]) return EINA_FALSE; } return i > 0; } EOLIAN static Efl_Net_Ip_Address * _efl_net_ip_address_create(uint16_t port, const Eina_Slice address) { int family; EINA_SAFETY_ON_TRUE_RETURN_VAL(address.len != 4 && address.len != 16, NULL); if (address.len == 16) family = AF_INET6; else family = AF_INET; return efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, efl_main_loop_get(), efl_net_ip_address_family_set(efl_added, family), efl_net_ip_address_port_set(efl_added, port), efl_net_ip_address_set(efl_added, address)); } EOLIAN static Efl_Net_Ip_Address * _efl_net_ip_address_create_sockaddr(const void *ptr) { const struct sockaddr *sockaddr = ptr; EINA_SAFETY_ON_NULL_RETURN_VAL(sockaddr, NULL); EINA_SAFETY_ON_TRUE_RETURN_VAL((sockaddr->sa_family != AF_INET) && (sockaddr->sa_family != AF_INET6), NULL); return efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, efl_main_loop_get(), efl_net_ip_address_sockaddr_set(efl_added, sockaddr)); } EOLIAN static Efl_Net_Ip_Address * _efl_net_ip_address_parse(const char *numeric_address) { struct sockaddr_storage ss; Eina_Bool r; const char *address = numeric_address; char *tmp = NULL; EINA_SAFETY_ON_NULL_RETURN_VAL(numeric_address, NULL); if (numeric_address[0] != '[') { const char *p = strchr(numeric_address, ':'); if (p) { p = strchr(p + 1, ':'); if (p) { size_t len = strlen(numeric_address); /* IPv6 no braces: ::1, etc... no port, add braces */ tmp = malloc(len + sizeof("[]")); EINA_SAFETY_ON_NULL_RETURN_VAL(tmp, NULL); tmp[0] = '['; memcpy(tmp + 1, numeric_address, len); tmp[1 + len] = ']'; tmp[1 + len + 1] = '\0'; address = tmp; } } } r = efl_net_ip_port_parse(address, &ss); free(tmp); if (!r) { DBG("could not parse numeric address: %s", numeric_address); return NULL; } return efl_add_ref(EFL_NET_IP_ADDRESS_CLASS, efl_main_loop_get(), efl_net_ip_address_sockaddr_set(efl_added, &ss)); } typedef struct _Efl_Net_Ip_Address_Resolve_Context { Eina_Stringshare *request_address; Ecore_Thread *thread; Eina_Promise *promise; } Efl_Net_Ip_Address_Resolve_Context; static Eina_Value_Struct_Desc * _efl_net_ip_address_resolve_value_desc_get(void) { static Eina_Value_Struct_Member struct_members[] = { // no eina_value_type as they are not constant initializers, see below. EINA_VALUE_STRUCT_MEMBER(NULL, Efl_Net_Ip_Address_Resolve_Value, canonical_name), EINA_VALUE_STRUCT_MEMBER(NULL, Efl_Net_Ip_Address_Resolve_Value, request_address), EINA_VALUE_STRUCT_MEMBER(NULL, Efl_Net_Ip_Address_Resolve_Value, results) }; static Eina_Value_Struct_Desc struct_desc = { EINA_VALUE_STRUCT_DESC_VERSION, NULL, struct_members, EINA_C_ARRAY_LENGTH(struct_members), sizeof (Efl_Net_Ip_Address_Resolve_Value) }; struct_members[0].type = EINA_VALUE_TYPE_STRINGSHARE; struct_members[1].type = EINA_VALUE_TYPE_STRINGSHARE; struct_members[2].type = EINA_VALUE_TYPE_ARRAY; struct_desc.ops = EINA_VALUE_STRUCT_OPERATIONS_BINSEARCH; return &struct_desc; } static void _efl_net_ip_address_resolve_del(void *data, const Eina_Promise *dead_promise EINA_UNUSED) { Efl_Net_Ip_Address_Resolve_Context *ctx = data; ctx->promise = NULL; eina_stringshare_replace(&ctx->request_address, NULL); if (ctx->thread) { ecore_thread_cancel(ctx->thread); ecore_thread_wait(ctx->thread, 1); ctx->thread = NULL; } free(ctx); } static inline int _efl_net_ip_address_find(const Eina_Value *array, const struct sockaddr *addr) { const Efl_Net_Ip_Address *o; unsigned int i, len; EINA_VALUE_ARRAY_FOREACH(array, len, i, o) { const struct sockaddr *other = efl_net_ip_address_sockaddr_get(o); if (addr->sa_family == AF_INET6) { if (other->sa_family == AF_INET6) { if (memcmp(other, addr, sizeof(struct sockaddr_in6)) == 0) return (int)i; } } else { if (other->sa_family == AF_INET) { if (memcmp(other, addr, sizeof(struct sockaddr_in)) == 0) return (int)i; } } } return -1; } static void _efl_net_ip_address_resolve_done(void *data, const char *host, const char *port, const struct addrinfo *hints EINA_UNUSED, struct addrinfo *result, int gai_error) { Efl_Net_Ip_Address_Resolve_Context *ctx = data; Eina_Value_Array desc = { 0 }; Eina_Value s = EINA_VALUE_EMPTY; Eina_Value r = EINA_VALUE_EMPTY; Eina_Error err = EFL_NET_ERROR_COULDNT_RESOLVE_HOST; const struct addrinfo *a; DBG("done resolving '%s' (host='%s', port='%s'): %s", ctx->request_address, host, port, gai_error ? gai_strerror(gai_error) : "success"); ctx->thread = NULL; if (gai_error) { if (gai_error == EAI_SYSTEM) err = errno; goto on_error; } err = ENOMEM; if (!eina_value_array_setup(&r, EINA_VALUE_TYPE_OBJECT, 1)) goto on_error; if (!eina_value_struct_setup(&s, _efl_net_ip_address_resolve_value_desc_get())) goto on_error; eina_value_struct_set(&s, "request_address", ctx->request_address); for (a = result; a != NULL; a = a->ai_next) { Eina_Stringshare *canonical_name = NULL; Eo *o; eina_value_struct_get(&s, "canonical_name", &canonical_name); if (EINA_UNLIKELY((canonical_name == NULL) && (a->ai_canonname != NULL))) { canonical_name = eina_stringshare_add(a->ai_canonname); eina_value_struct_set(&s, "canonical_name", canonical_name); eina_stringshare_del(canonical_name); } /* some addresses get duplicated with different options that we * do not care, so check for duplicates. */ if (EINA_UNLIKELY(_efl_net_ip_address_find(&r, a->ai_addr) >= 0)) continue; o = efl_net_ip_address_create_sockaddr(a->ai_addr); if (!o) continue ; eina_value_array_append(&r, o); efl_unref(o); } freeaddrinfo(result); if (!eina_value_pget(&r, &desc)) goto on_error; if (!eina_value_struct_pset(&s, "results", &desc)) goto on_error; eina_value_flush(&r); eina_promise_resolve(ctx->promise, s); eina_stringshare_replace(&ctx->request_address, NULL); free(ctx); return ; on_error: eina_promise_reject(ctx->promise, err); eina_stringshare_replace(&ctx->request_address, NULL); free(ctx); } EOLIAN static Eina_Future * _efl_net_ip_address_resolve(const char *address, int family, int flags) { Efl_Net_Ip_Address_Resolve_Context *ctx; struct addrinfo hints = { }; const char *host = NULL, *port = NULL; Eina_Bool r; char *str; EINA_SAFETY_ON_NULL_RETURN_VAL(address, NULL); if (family == 0) family = AF_UNSPEC; EINA_SAFETY_ON_TRUE_RETURN_VAL((family != AF_UNSPEC) && (family != AF_INET) && (family != AF_INET6), NULL); if (flags == 0) flags = AI_ADDRCONFIG | AI_V4MAPPED | AI_CANONNAME; hints.ai_family = family; hints.ai_flags = flags; str = strdup(address); EINA_SAFETY_ON_NULL_RETURN_VAL(str, NULL); r = efl_net_ip_port_split(str, &host, &port); if ((!r) || (!host) || (host[0] == '\0')) { host = address; port = "0"; } if (!port) port = "0"; ctx = calloc(1, sizeof(Efl_Net_Ip_Address_Resolve_Context)); EINA_SAFETY_ON_NULL_GOTO(ctx, error_ctx); ctx->request_address = eina_stringshare_add(address); EINA_SAFETY_ON_NULL_GOTO(ctx->request_address, error_result_address); ctx->thread = efl_net_ip_resolve_async_new(host, port, &hints, _efl_net_ip_address_resolve_done, ctx); EINA_SAFETY_ON_NULL_GOTO(ctx->thread, error_thread); ctx->promise = eina_promise_new(efl_loop_future_scheduler_get(efl_main_loop_get()), _efl_net_ip_address_resolve_del, ctx); EINA_SAFETY_ON_NULL_GOTO(ctx->promise, error_promise); free(str); return eina_future_new(ctx->promise); error_promise: ecore_thread_cancel(ctx->thread); error_thread: eina_stringshare_del(ctx->request_address); error_result_address: free(ctx); error_ctx: free(str); return NULL; } #include "efl_net_ip_address.eo.c"