/** * @file kc_socket.c * @brief ソケットモジュール * @copyright 2003 - 2024 Nomura Kei */ #include <stdio.h> #include <string.h> #include <errno.h> #include <kc.h> #include <kc_lock_guard.h> #include <kc_memory.h> #include <kc_socket.h> /** * KcSocket 管理情報 */ typedef struct { socket_t sock_fd; //!< ソケットディスクリプタ int sock_type; //!< ソケットタイプ int sock_family; //!< ソケットファミリ struct sockaddr_storage remote_addr; //!< リモートアドレス struct sockaddr_storage local_addr; //!< ローカルアドレス socklen_t remote_addrlen; //!< リモートアドレス長 socklen_t local_addrlen; //!< ローカルアドレス長 char tmp_remote_host[NI_MAXHOST]; //!< 一時的なリモートホスト名格納用 char tmp_local_host[NI_MAXHOST]; //!< 一時的なローカルホスト名格納用 const char *mcast_v4_ifname; //!< マルチキャスト ifname (IPv4) const char *mcast_v6_ifname; //!< マルチキャスト ifname (IPv6) } KcSocketInfo; /** * アドレス情報 */ typedef struct { const char *addr; //!< アドレス const char *service; //!< サービス } KcSocketAddress; // ============================================================================= // プロトタイプ宣言 // ============================================================================= static void Socket_setup(void); static void Socket_cleanup(void); static const char *KcSocket_get_remote_addr(KcSocket *sock); static int KcSocket_get_remote_port(KcSocket *sock); static const char *KcSocket_get_local_addr(KcSocket *sock); static int KcSocket_get_local_port(KcSocket *sock); static socket_t KcSocket_get_socket(KcSocket *sock); static int KcSocket_get_type(KcSocket *sock); static int KcSocket_get_family(KcSocket *sock); static bool KcSocket_get_addrinfo(KcSocket *sock, struct addrinfo **result, const char *addr, const char *service, bool is_passive); static bool KcSocket_bind(KcSocket *sock, const char *addr, const char *service); static bool KcSocket_listen(KcSocket *sock, int backlog); static KcSocket *KcSocket_accept(KcSocket *sock); static bool KcSocket_connect(KcSocket *sock, const char *addr, const char *service, const char *local_addr, const char *local_service); static bool KcSocket_close(KcSocket *sock); static ssize_t KcSocket_send(KcSocket *sock, const char *buff, size_t size, int flags); static ssize_t KcSocket_recv(KcSocket *sock, char *buff, size_t size, int flags); static ssize_t KcSocket_sendto(KcSocket *sock, const char *buff, size_t size, int flags, const char *addr, const char *service); static ssize_t KcSocket_recvfrom(KcSocket *sock, char *buff, size_t size, int flags, char *src_addr, size_t src_addrlen, char *src_service, size_t src_servicelen); static bool KcSocket_set_ttl(KcSocket *sock, int val); static bool KcSocket_join(KcSocket *sock, const char *addr, const char *ifname); static bool KcSocket_leave(KcSocket *sock, const char *addr, const char *ifname); static void KcSocket_set_ifname(KcSocket *sock, const char *v4_ifname, const char *v6_ifname); static void KcSocket_print_info(KcSocket *sock, char *buff, size_t size); // 内部関数 static bool KcSocket_addrinfo_bind_and_connect( KcSocket *sock, struct addrinfo *bind_addrinfo, struct addrinfo *conn_addrinfo); static bool KcSocket_addrinfo_connect(KcSocket *sock, struct addrinfo *conn_addrinfo, socket_t sockfd); static int KcSocket_join_ipv4(KcSocket *sock, const char *addr, const char *ifname); static int KcSocket_join_ipv6(KcSocket *sock, const char *addr, const char *ifname); static int KcSocket_leave_ipv4(KcSocket *sock, const char *addr, const char *ifname); static int KcSocket_leave_ipv6(KcSocket *sock, const char *addr, const char *ifname); static bool KcSocket_set_mcastif(KcSocket *sock); static int KcSocket_set_mcastif_ipv4(KcSocket *sock, const char *ifname); static int KcSocket_set_mcastif_ipv6(KcSocket *sock, const char *ifname); static int KcSocket_print_addr(char *buff, size_t size, const struct sockaddr *addr, size_t addrlen); /** * ソケットのセットアップをします。 */ static void Socket_setup(void) { static bool is_init = false; if (!is_init) { #if (KC_IS_WINDOWS) WSADATA wsa_data; WSAStartup(MAKEWORD(2, 0), &wsa_data); #endif atexit(Socket_cleanup); is_init = true; } } /** * ソケットライブラリのクリーンアップをします。 */ static void Socket_cleanup(void) { #if (KC_IS_WINDOWS) WSACleanup(); #endif } // ============================================================================= // new // ============================================================================= /** * 指定されたタイプ、ファミリのSocket オブジェクトを構築します。 * * @param type タイプ(SOCK_STREAM/SOCK_DGRAM/SOCK_RAW) * @param family ファミリ(AF_UNSPEC/AF_INET/AF_INET6) */ KcSocket *KcSocket_new(int type, int family) { // KcSocket の管理構造 // +----------------+ // | KcSocket | // | ... | // | _info -----------+ // +----------------+ | // | <KcSocketInfo> |<--+ // +----------------+ Socket_setup(); KcSocket *sock = (KcSocket *)malloc(sizeof(KcSocket) + sizeof(KcSocketInfo)); if (sock != NULL) { sock->get_remote_addr = KcSocket_get_remote_addr; sock->get_remote_port = KcSocket_get_remote_port; sock->get_local_addr = KcSocket_get_local_addr; sock->get_local_port = KcSocket_get_local_port; sock->get_socket = KcSocket_get_socket; sock->get_type = KcSocket_get_type; sock->get_family = KcSocket_get_family; sock->get_addrinfo = KcSocket_get_addrinfo; // for local sock->bind = KcSocket_bind; sock->listen = KcSocket_listen; sock->accept = KcSocket_accept; sock->connect = KcSocket_connect; sock->close = KcSocket_close; sock->send = KcSocket_send; sock->recv = KcSocket_recv; sock->sendto = KcSocket_sendto; sock->recvfrom = KcSocket_recvfrom; sock->set_ttl = KcSocket_set_ttl; sock->join = KcSocket_join; sock->leave = KcSocket_leave; sock->set_ifname = KcSocket_set_ifname; sock->print_info = KcSocket_print_info; sock->_info = (sock + 1); KcSocketInfo *info = (KcSocketInfo *)sock->_info; info->sock_fd = INVALID_SOCKET; info->sock_type = type; info->sock_family = family; info->remote_addrlen = 0; info->local_addrlen = 0; info->tmp_local_host[0] = '\0'; info->tmp_remote_host[0] = '\0'; info->mcast_v4_ifname = NULL; info->mcast_v6_ifname = NULL; } return sock; } // ============================================================================= // delete // ============================================================================= /** * Socket を破棄します。 */ void KcSocket_delete(KcSocket *socket) { if (socket) { if (((KcSocketInfo *)socket->_info)->sock_fd != INVALID_SOCKET) { socket->close(socket); } free(socket); } } // ============================================================================= // get_remote_addr // ============================================================================= /** * ソケットの接続先アドレスを返します。 * アドレスが取得できない場合(未接続状態など)、NULL を返します。 * * @param sock 対象ソケット * @return ソケットの接続先アドレス */ static const char *KcSocket_get_remote_addr(KcSocket *sock) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; if (info->remote_addrlen) { int ret = getnameinfo( (const struct sockaddr *)&info->remote_addr, info->remote_addrlen, info->tmp_remote_host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); if (ret == 0) { return info->tmp_remote_host; } } return NULL; } /** * ソケットの接続先ポート番号を返します。 * 接続先ポート番号が取得できなかった場合、-1 を返します。 * * @param sock 対象ソケット * @return ポート番号 */ static int KcSocket_get_remote_port(KcSocket *sock) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; if (info->remote_addrlen) { char tmp_port[NI_MAXSERV]; int ret = getnameinfo( (const struct sockaddr *)&info->remote_addr, info->remote_addrlen, NULL, 0, tmp_port, sizeof(tmp_port), NI_NUMERICSERV); if (ret == 0) { return atoi(tmp_port); } } return -1; } /** * ソケットのローカルアドレスを返します。 * アドレスが取得できない場合、NULL を返します。 * * @param sock 対象ソケット * @return ソケットのローカルアドレス */ static const char *KcSocket_get_local_addr(KcSocket *sock) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; if (info->local_addrlen) { int ret = getnameinfo( (const struct sockaddr *)&info->local_addr, info->local_addrlen, info->tmp_local_host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); if (ret == 0) { return info->tmp_local_host; } } return NULL; } /** * ソケットのローカルポート番号を返します。 * ローカルポート番号が取得できなかった場合、-1 を返します。 * * @param sock 対象ソケット * @return ポート番号 */ static int KcSocket_get_local_port(KcSocket *sock) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; if (info->local_addrlen) { char tmp_port[NI_MAXSERV]; int ret = getnameinfo( (const struct sockaddr *)&info->local_addr, info->local_addrlen, NULL, 0, tmp_port, sizeof(tmp_port), NI_NUMERICSERV); if (ret == 0) { return atoi(tmp_port); } } return -1; } /** * ソケットディスクリプタを返します。 * 通常は、ソケットディスクリプタを直接操作しないでください。 * * @param sock 対象ソケット * @return ソケットディスクリプタ */ static socket_t KcSocket_get_socket(KcSocket *sock) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; return info->sock_fd; } /** * ソケットタイプを返します。 * * @param sock 対象ソケット * @return ソケットタイプ */ static int KcSocket_get_type(KcSocket *sock) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; return info->sock_type; } /** * ソケットファミリを返します。 * ソケット接続、バインド時には、実際にバインド、 * あるいは接続されているソケットファミリを返します。 * * @param sock 対象ソケット * @return ソケットファミリ */ static int KcSocket_get_family(KcSocket *sock) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; int result_family = info->sock_family; if (info->sock_fd != INVALID_SOCKET) { struct sockaddr_storage ss; socklen_t len = sizeof(struct sockaddr_storage); if (getsockname(info->sock_fd, (struct sockaddr *)&ss, &len) == 0) { result_family = ss.ss_family; } } return result_family; } /** * 指定されたアドレス、サービスのアドレス情報を取得します。 * * @param sock 対象ソケット * @param result アドレス情報格納用ポインタ * @param addr アドレス * @param service サービス * @param is_passive サーバソケットの場合、true を指定ください。 */ static bool KcSocket_get_addrinfo( KcSocket *sock, struct addrinfo **result, const char *addr, const char *service, bool is_passive) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; struct addrinfo hints; memset(&hints, 0x00, sizeof(struct addrinfo)); hints.ai_socktype = info->sock_type; hints.ai_family = info->sock_family; if (is_passive) { hints.ai_flags = AI_PASSIVE; } int ret = getaddrinfo(addr, service, &hints, result); return (ret == 0); } /** * 指定されたアドレス、サービスにバインドします。 * 指定されたアドレスとサービスにより、複数候補がある場合は、 * 最初に見つかったアドレスとポートにバインドします。 * * @param sock 対象ソケット * @param addr アドレス * @param service サービス (例: "80", "http", "ssh" など) * @return true/false (bind 成功/失敗) */ static bool KcSocket_bind(KcSocket *sock, const char *addr, const char *service) { struct addrinfo *bind_addr; bool is_success = sock->get_addrinfo(sock, &bind_addr, addr, service, true); if (is_success) { is_success = KcSocket_addrinfo_bind_and_connect(sock, bind_addr, NULL); freeaddrinfo(bind_addr); } return is_success; } /** * ソケットを接続待ちソケットとしてマークをつけます。 * 保留中の接続のキュー最大長を指定します。 * * @param sock 対象ソケット * @param backlog バックログ * @return true/false (成功/失敗) */ static bool KcSocket_listen(KcSocket *sock, int backlog) { bool result = false; KcSocketInfo *info = (KcSocketInfo *)sock->_info; if (info->sock_fd != INVALID_SOCKET) { int ret = listen(info->sock_fd, backlog); result = (ret != SOCKET_ERROR); } return result; } /** * ソケットへの接続を受け付けます。 * * @param sock 対象ソケット * @return 受け付けたソケット(失敗時NULL) */ static KcSocket *KcSocket_accept(KcSocket *sock) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; KcSocket *client = NULL; if (info->sock_fd != INVALID_SOCKET) { client = KcSocket_new(info->sock_type, info->sock_family); if (client != NULL) { KcSocketInfo *cinfo = (KcSocketInfo *)client->_info; cinfo->remote_addrlen = sizeof(struct sockaddr_storage); cinfo->sock_fd = accept(info->sock_fd, (struct sockaddr *)&cinfo->remote_addr, &cinfo->remote_addrlen); if (cinfo->sock_fd != INVALID_SOCKET) { memcpy(&cinfo->local_addr, &info->local_addr, info->local_addrlen); cinfo->local_addrlen = info->local_addrlen; cinfo->sock_type = SOCK_STREAM; cinfo->sock_family = cinfo->remote_addr.ss_family; } else { KcSocket_delete(client); client = NULL; } } } return client; } /** * 指定されたアドレス、サービス接続します。 * local_addr に NULL を指定した場合、自動的にローカルのアドレス、サービスが設定されます。 * * @param sock 対象ソケット * @param addr アドレス * @param service サービス * @param local_addr ローカルアドレス * @param local_service ローカルサービス * @return true/false (成功/失敗) */ static bool KcSocket_connect( KcSocket *sock, const char *addr, const char *service, const char *local_addr, const char *local_service) { // 接続先アドレス情報取得 struct addrinfo *conn_addr = NULL; if (!sock->get_addrinfo(sock, &conn_addr, addr, service, false)) { return false; } // バインドアドレス情報取得 bool is_success = false; struct addrinfo *bind_addr = NULL; if (local_addr != NULL) { // bind が必要 is_success = sock->get_addrinfo(sock, &bind_addr, local_addr, local_service, true); if (is_success) { is_success = KcSocket_addrinfo_bind_and_connect(sock, bind_addr, conn_addr); freeaddrinfo(bind_addr); } } else { is_success = KcSocket_addrinfo_connect(sock, conn_addr, INVALID_SOCKET); } freeaddrinfo(conn_addr); return is_success; } /** * ソケットをクローズします。 * * @param sock 対象ソケット * @return true/false (成功/失敗) */ static bool KcSocket_close(KcSocket *sock) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; if (info->sock_type == SOCK_STREAM) { // TCP の場合は、出力を閉じて、届いているデータを読み取ってから close する。 int ret = shutdown(info->sock_fd, SHUT_WR); if (ret == 0) { int read_size; char buff[1024]; do { // 届いているデータを全て読み取る read_size = recv(info->sock_fd, buff, sizeof(buff), 0); } while (read_size > 0); } } sockclose(info->sock_fd); return true; } /** * メッセージを送信します。 * * @param sock 対象ソケット * @param buff メッセージ * @param size サイズ * @param flags フラグ (MSG_CONFIRM/MSG_DONTROUTE/MSG_DONTWAIT/MSG_EOR/MSG_MORE/MSG_NOSIGNAL/MSG_OOB) * @return 送信されたデータサイズ */ static ssize_t KcSocket_send(KcSocket *sock, const char *buff, size_t size, int flags) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; ssize_t write_size = 0; do { errno = 0; write_size = send(info->sock_fd, buff, size, flags); } while ((write_size < 0) && (errno == EINTR)); return write_size; } /** * メッセージを受信します。 * 受信したメッセージは指定されたバッファに格納されます。 * * @param sock 対象ソケット * @param buff メッセージ受信用バッファ * @param size バッファサイズ * @param flags フラグ (MSG_CONFIRM/MSG_DONTROUTE/MSG_DONTWAIT/MSG_EOR/MSG_MORE/MSG_NOSIGNAL/MSG_OOB) * @return 読み取ったデータサイズ */ static ssize_t KcSocket_recv(KcSocket *sock, char *buff, size_t size, int flags) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; ssize_t read_size; do { errno = 0; read_size = recv(info->sock_fd, buff, size, flags); } while ((read_size < 0) && (errno == EINTR)); return read_size; } /** * 指定されたアドレス、サービスにメッセージを送信します。 * * @param sock 対象ソケット * @param buff メッセージ * @param size サイズ * @param flags フラグ (MSG_CONFIRM/MSG_DONTROUTE/MSG_DONTWAIT/MSG_EOR/MSG_MORE/MSG_NOSIGNAL/MSG_OOB) * @param addr アドレス * @param service サービス * @return 送信メッセージサイズ、エラー発生時は -1 */ static ssize_t KcSocket_sendto( KcSocket *sock, const char *buff, size_t size, int flags, const char *addr, const char *service) { // 接続先アドレス情報取得 struct addrinfo *conn_addr = NULL; if (!sock->get_addrinfo(sock, &conn_addr, addr, service, false)) { // 接続先情報取得NG return SOCKET_ERROR; } KcSocketInfo *info = (KcSocketInfo *)sock->_info; socket_t tmp_sock = info->sock_fd; ssize_t send_size = SOCKET_ERROR; for (struct addrinfo *rp = conn_addr; rp != NULL; rp = rp->ai_next) { if (info->sock_fd == INVALID_SOCKET) { // sockfd が無効の場合、ソケットを生成する。 tmp_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (tmp_sock == INVALID_SOCKET) { continue; } } // マルチキャスト用 ifname が指定されている場合は設定する。 KcSocket_set_mcastif(sock); send_size = sendto(tmp_sock, buff, size, flags, rp->ai_addr, rp->ai_addrlen); if (send_size != SOCKET_ERROR) { // 送信成功 info->sock_fd = tmp_sock; info->sock_family = rp->ai_family; info->remote_addrlen = rp->ai_addrlen; memcpy(&info->remote_addr, rp->ai_addr, rp->ai_addrlen); break; } else { // 送信失敗のため、一時的に生成したソケットをクローズする。 sockclose(tmp_sock); } } freeaddrinfo(conn_addr); return send_size; } /** * メッセージを受信します。 * src_addr, src_service がいずれも NULL 出ない場合、 * 送信元のアドレスとサービスが格納されます。 * * @param sock 対象ソケット * @param buff メッセージ受信用バッファ * @param size バッファサイズ * @param flags フラグ (MSG_CONFIRM/MSG_DONTROUTE/MSG_DONTWAIT/MSG_EOR/MSG_MORE/MSG_NOSIGNAL/MSG_OOB) * @param src_addr 送信元アドレス格納用バッファ * @param src_addrlen 送信元アドレス格納用バッファサイズ * @param src_service 送信元サービス格納用バッファ * @param src_servicelen 送信元サービス格納用バッファサイズ * @return 受信メッセージサイズ、エラー発生時は -1 */ static ssize_t KcSocket_recvfrom( KcSocket *sock, char *buff, size_t size, int flags, char *src_addr, size_t src_addrlen, char *src_service, size_t src_servicelen) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; ssize_t recv_size = SOCKET_ERROR; if (info->sock_fd != INVALID_SOCKET) { struct sockaddr_storage src_sockaddr; socklen_t src_sockaddrlen = sizeof(struct sockaddr_storage); recv_size = recvfrom( info->sock_fd, buff, size, flags, (struct sockaddr *)&src_sockaddr, &src_sockaddrlen); if ((src_addr != NULL) && (src_service != NULL)) { int ret = getnameinfo( (struct sockaddr *)&src_sockaddr, src_addrlen, src_addr, src_addrlen, src_service, src_servicelen, (NI_NUMERICHOST | NI_NUMERICSERV)); if (ret != 0) { src_addr[0] = '\0'; src_service[0] = '\0'; } } } return recv_size; } /** * TTL を設定します。 * * @param sock 対象ソケット * @param val 設定する TTL * @return true/false (成功/失敗) */ static bool KcSocket_set_ttl(KcSocket *sock, int val) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; int ret = SOCKET_ERROR; switch (info->sock_family) { case AF_INET: unsigned char ttl; ttl = (unsigned char)val; ret = setsockopt(info->sock_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); break; case AF_INET6: int hop; hop = val; ret = setsockopt(info->sock_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hop, sizeof(hop)); break; default: // unspoorted socket family break; } return (ret == 0); } /** * マルチキャストグループに参加するために JOIN します。 * IPv4 の場合、ifname に、IPアドレスを指定ください。 * IPv6 の場合、ifname にインタフェース名 (例: eth0 など)を指定ください。 * ifname に NULL が指定された場合、任意のインタフェースとなります。 * * @param sock 対象ソケット * @param addr マルチキャストアドレス * @param ifname インタフェース名 * @return true/false (成功/失敗) */ static bool KcSocket_join(KcSocket *sock, const char *addr, const char *ifname) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; int ret = SOCKET_ERROR; switch (info->sock_family) { case AF_INET: ret = KcSocket_join_ipv4(sock, addr, ifname); break; case AF_INET6: ret = KcSocket_join_ipv6(sock, addr, ifname); break; default: // NOP break; } return (ret == 0); } /** * マルチキャストグループから離脱します。 * * @param sock 対象ソケット * @param addr マルチキャストアドレス * @param ifname インタフェース名 * @return true/false (成功/失敗) */ static bool KcSocket_leave(KcSocket *sock, const char *addr, const char *ifname) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; int ret = SOCKET_ERROR; switch (info->sock_family) { case AF_INET: ret = KcSocket_leave_ipv4(sock, addr, ifname); break; case AF_INET6: ret = KcSocket_leave_ipv6(sock, addr, ifname); break; default: // NOP break; } return (ret == 0); } /** * マルチキャストを送信するインタフェースを指定します。 * * @param sock 対象ソケット * @param v4_ifname インタフェース名(IPアドレス) * @param v6_ifname インタフェース名(eth0 など) * @return true/false (成功/失敗) */ static void KcSocket_set_ifname(KcSocket *sock, const char *v4_ifname, const char *v6_ifname) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; info->mcast_v4_ifname = v4_ifname; info->mcast_v6_ifname = v6_ifname; } /** * 指定されたソケットの情報を指定されたバッファに出力します。 * * @param sock 対象ソケット * @param buff バッファ * @param size バッファのサイズ */ static void KcSocket_print_info(KcSocket *sock, char *buff, size_t size) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; char *write_ptr = buff; char *end_ptr = &buff[size]; write_ptr += snprintf(write_ptr, (end_ptr - write_ptr), "local : "); write_ptr += KcSocket_print_addr(write_ptr, (end_ptr - write_ptr), (struct sockaddr *)&info->local_addr, info->local_addrlen); write_ptr += snprintf(write_ptr, (end_ptr - write_ptr), "\n"); write_ptr += snprintf(write_ptr, (end_ptr - write_ptr), "remote: "); write_ptr += KcSocket_print_addr(write_ptr, (end_ptr - write_ptr), (struct sockaddr *)&info->remote_addr, info->remote_addrlen); write_ptr += snprintf(write_ptr, (end_ptr - write_ptr), "\n"); UNUSED_VARIABLE(write_ptr); } //////////////////////////////////////////////////////////////////////////////// // // [内部関数] // /** * 指定された接続アドレス情報をもとにソケットの生成、接続を試みます。 * 接続済みソケット sockfd が有効な場合、sockfd を用いて接続します。 * * @param sock 対象ソケット * @param conn_addrinfo 接続アドレス情報 * @param sockfd 接続済みソケット * @return true/false (接続成功/失敗) */ static bool KcSocket_addrinfo_connect(KcSocket *sock, struct addrinfo *conn_addrinfo, socket_t sockfd) { bool is_success = false; socket_t tmp_sock = sockfd; for (struct addrinfo *rp = conn_addrinfo; rp != NULL; rp = rp->ai_next) { if (sockfd == INVALID_SOCKET) { // sockfd が無効の場合、ソケットを生成する。 tmp_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (tmp_sock == INVALID_SOCKET) { continue; } } int ret = connect(tmp_sock, rp->ai_addr, rp->ai_addrlen); is_success = (ret != SOCKET_ERROR); if (is_success) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; info->sock_fd = tmp_sock; info->sock_family = rp->ai_family; info->remote_addrlen = rp->ai_addrlen; memcpy(&info->remote_addr, rp->ai_addr, rp->ai_addrlen); break; } if (sockfd == INVALID_SOCKET) { // sockfd が無効の場合、一時的に生成したソケットをクローズする。 sockclose(tmp_sock); } } return is_success; } /** * 指定された接続アドレス情報をもとにソケットの生成、バインドを試みます。 * conn_addrinfo が NULL でない場合、バインド後に接続を試みます。 * * @param sock 対象ソケット * @param bind_addrinfo バインドアドレス情報 * @param conn_addrinfo 接続アドレス情報 * @return true/false (接続成功/失敗) */ static bool KcSocket_addrinfo_bind_and_connect( KcSocket *sock, struct addrinfo *bind_addrinfo, struct addrinfo *conn_addrinfo) { int ret; bool is_success = false; for (struct addrinfo *rp = bind_addrinfo; rp != NULL; rp = rp->ai_next) { // sokcet : ソケット生成 socket_t tmp_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (tmp_sock == INVALID_SOCKET) { continue; } // bind const int on = 1; setsockopt(tmp_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on)); ret = bind(tmp_sock, rp->ai_addr, rp->ai_addrlen); is_success = (ret == 0); if (!is_success) { // bind 失敗したので次へ sockclose(tmp_sock); continue; } // connect if (conn_addrinfo) { is_success = KcSocket_addrinfo_connect(sock, conn_addrinfo, tmp_sock); if (!is_success) { // connect 失敗したので次へ sockclose(tmp_sock); continue; } } // bind または、bind と connect 成功 KcSocketInfo *info = (KcSocketInfo *)sock->_info; info->sock_fd = tmp_sock; info->sock_family = rp->ai_family; info->local_addrlen = rp->ai_addrlen; memcpy(&info->local_addr, rp->ai_addr, rp->ai_addrlen); break; } return is_success; } /** * マルチキャストグループに参加するために JOIN (IPv4)します。 * * @param sock 対象ソケット * @param addr マルチキャストアドレス * @param ifname インタフェース名(IP) * @return 0/-1(成功/失敗) */ static int KcSocket_join_ipv4(KcSocket *sock, const char *addr, const char *ifname) { struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr(addr); mreq.imr_interface.s_addr = (ifname != NULL) ? inet_addr(ifname) : htonl(INADDR_ANY); KcSocketInfo *info = (KcSocketInfo *)sock->_info; int ret = setsockopt(info->sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); return ret; } /** * マルチキャストグループに参加するために JOIN (IPv6)します。 * * @param sock 対象ソケット * @param addr マルチキャストアドレス * @param ifname インタフェース名 * @return 0/-1 (成功/失敗) */ static int KcSocket_join_ipv6(KcSocket *sock, const char *addr, const char *ifname) { struct ipv6_mreq mreq6; mreq6.ipv6mr_interface = (ifname != NULL) ? if_nametoindex(ifname) : 0; int ret = inet_pton(AF_INET6, addr, &mreq6.ipv6mr_multiaddr); if (ret != SOCKET_ERROR) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; ret = setsockopt(info->sock_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6)); } return ret; } /** * マルチキャストグループから離脱します(IPv4)。 * * @param sock 対象ソケット * @param addr マルチキャストアドレス * @param ifname インタフェース名(IP) * @return 0/-1 (成功/失敗) */ static int KcSocket_leave_ipv4(KcSocket *sock, const char *addr, const char *ifname) { struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr(addr); mreq.imr_interface.s_addr = (ifname != NULL) ? inet_addr(ifname) : htonl(INADDR_ANY); KcSocketInfo *info = (KcSocketInfo *)sock->_info; int ret = setsockopt(info->sock_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); return ret; } /** * マルチキャストグループに離脱します(IPv6)。 * * @param sock 対象ソケット * @param addr マルチキャストアドレス * @param ifname インタフェース名 * @return 0/-1 (成功/失敗) */ static int KcSocket_leave_ipv6(KcSocket *sock, const char *addr, const char *ifname) { struct ipv6_mreq mreq6; mreq6.ipv6mr_interface = (ifname != NULL) ? if_nametoindex(ifname) : 0; int ret = inet_pton(AF_INET6, addr, &mreq6.ipv6mr_multiaddr); if (ret != SOCKET_ERROR) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; ret = setsockopt(info->sock_fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq6, sizeof(mreq6)); } return ret; } /** * マルチキャストを送信するためのインタフェースを設定します。 * * @param sock 対象ソケット * @return true/false (成功/失敗) */ static bool KcSocket_set_mcastif(KcSocket *sock) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; int ret = SOCKET_ERROR; switch (info->sock_family) { case AF_INET: ret = (info->mcast_v4_ifname) ? KcSocket_set_mcastif_ipv4(sock, info->mcast_v4_ifname) : 0; break; case AF_INET6: ret = (info->mcast_v6_ifname) ? KcSocket_set_mcastif_ipv6(sock, info->mcast_v6_ifname) : 0; break; default: // NOP break; } return (ret == 0); } /** * マルチキャストを送信するためのインタフェースを設定します(IPv4)。 * * @param sock 対象ソケット * @param ifname インタフェース名 * @return 0/-1 (成功/失敗) */ static int KcSocket_set_mcastif_ipv4(KcSocket *sock, const char *ifname) { struct in_addr ifaddr; ifaddr.s_addr = (ifname != NULL) ? inet_addr(ifname) : htonl(INADDR_ANY); KcSocketInfo *info = (KcSocketInfo *)sock->_info; int ret = setsockopt(info->sock_fd, IPPROTO_IP, IP_MULTICAST_IF, &ifaddr, sizeof(ifaddr)); return ret; } /** * マルチキャストを送信するためのインタフェースを設定します(IPv6)。 * * @param sock 対象ソケット * @param ifname インタフェース名 * @return 0/-1 (成功/失敗) */ static int KcSocket_set_mcastif_ipv6(KcSocket *sock, const char *ifname) { unsigned int ifindex = (ifname != NULL) ? if_nametoindex(ifname) : 0; KcSocketInfo *info = (KcSocketInfo *)sock->_info; int ret = setsockopt(info->sock_fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifindex, sizeof(ifindex)); return ret; } /** * 指定された addr の情報を出力します。 * * @param buff バッファ * @param size サイズ * @param addr アドレス * @param addrlen アドレス長 * @return バッファに書き出したバイト数 */ static int KcSocket_print_addr(char *buff, size_t size, const struct sockaddr *addr, size_t addrlen) { char tmp_addr[NI_MAXHOST]; char tmp_service[NI_MAXSERV]; int ret = getnameinfo( addr, addrlen, tmp_addr, sizeof(tmp_addr), tmp_service, sizeof(tmp_service), NI_NUMERICHOST | NI_NUMERICSERV); int write_size = 0; if (ret == 0) { write_size = snprintf(buff, size, "%s:%s", tmp_addr, tmp_service); } else { write_size = snprintf(buff, size, "-:-"); } return write_size; }