/** * @file kc_socket.c * @brief ソケットモジュール * @copyright 2003 - 2024 Nomura Kei */ #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; //!< ローカルアドレス帳 } 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, char *addr, size_t addrlen); static int KcSocket_get_remote_port(KcSocket *sock); static const char *KcSocket_get_local_addr(KcSocket *sock, char *addr, size_t addrlen); 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, unsigned int ifindex); static bool KcSocket_leave(KcSocket *sock, const char *addr); 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 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->_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; } 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 // ============================================================================= /** * ソケットの接続先アドレスを指定されたバッファ addr に格納します。 * addr のバッファサイズは、KC_NI_MAXHOST とすべきです。 * * @param sock 対象ソケット * @param addr 接続先アドレス格納用バッファ * @param addrlen addr のバッファサイズ * @return addr へのポインタ */ static const char *KcSocket_get_remote_addr(KcSocket *sock, char *addr, size_t addrlen) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; if (info->remote_addrlen) { int ret = getnameinfo( (const struct sockaddr *)&info->remote_addr, info->remote_addrlen, addr, addrlen, NULL, 0, NI_NUMERICHOST); if (ret == 0) { return addr; } } 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; } /** * ソケットのローカルアドレスを指定されたバッファ addr に格納します。 * addr のバッファサイズは、KC_NI_MAXHOST とすべきです。 * * @param sock 対象ソケット * @param addr ローカルアドレス格納用バッファ * @param addrlen addr のバッファサイズ * @return addr へのポインタ */ static const char *KcSocket_get_local_addr(KcSocket *sock, char *addr, size_t addrlen) { KcSocketInfo *info = (KcSocketInfo *)sock->_info; if (info->local_addrlen) { int ret = getnameinfo( (const struct sockaddr *)&info->local_addr, info->remote_addrlen, addr, addrlen, NULL, 0, NI_NUMERICHOST); if (ret == 0) { return addr; } } 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->remote_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_family == 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) { (void)sock; (void)buff; (void)size; (void)flags; (void)addr; (void)service; return 0; } /** * メッセージを受信します。 * 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, 接続が正しく shutdown した場合は 0 */ 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) { (void)sock; (void)buff; (void)size; (void)flags; (void)src_addr; (void)src_addrlen; (void)src_service; (void)src_servicelen; return 0; } /** * TTL を設定します。 * * @param sock 対象ソケット * @param val 設定する TTL * @return true/false (成功/失敗) */ static bool KcSocket_set_ttl(KcSocket *sock, int val) { (void)sock; (void)val; return true; } /** * マルチキャストグループに参加するために JOIN します。 * * @param sock 対象ソケット * @param addr マルチキャストアドレス * @param ifname インタフェース名 * @param ifindex インタフェースインデックス * @return true/false (成功/失敗) */ static bool KcSocket_join( KcSocket *sock, const char *addr, const char *ifname, unsigned int ifindex) { (void)sock; (void)addr; (void)ifname; (void)ifindex; return true; } /** * マルチキャストグループから離脱します。 * * @param sock 対象ソケット * @param addr マルチキャストアドレス * @return true/false (成功/失敗) */ static bool KcSocket_leave( KcSocket *sock, const char *addr) { (void)sock; (void)addr; return true; } //////////////////////////////////////////////////////////////////////////////// // // [内部関数] // /** * 指定された接続アドレス情報をもとにソケットの生成、接続を試みます。 * 接続済みソケット 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->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 != SOCKET_ERROR); 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->local_addrlen = rp->ai_addrlen; memcpy(&info->local_addr, rp->ai_addr, rp->ai_addrlen); break; } return is_success; }