Newer
Older
libkc / modules / src / kc_socket.c
/**
 * @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>

#if (KC_IS_WINDOWS)
// Windows の setsockopt に指定する値は、様々な型のデータを渡すにもかかわらず、
// const char* 型と定義されているため、明示的にキャストする。
#define setsockopt(sockfd, level, optname, optval, optlen) \
	setsockopt(sockfd, level, optname, (const char *)optval, optlen)
#endif // KC_IS_WINDOWS

/**
 * 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(socket_t tmp_sock, int family, const char* mcast_v4_ifname, const char* mcast_v6_ifname);
static int KcSocket_set_mcastif_ipv4(socket_t tmp_sock, const char *ifname);
static int KcSocket_set_mcastif_ipv6(socket_t tmp_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(tmp_sock, rp->ai_family, info->mcast_v4_ifname, info->mcast_v6_ifname);

		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;
	printf("v4 %s\n", info->mcast_v4_ifname);
	printf("v6 %s\n", info->mcast_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(
	socket_t tmp_sock, int family,
	const char* mcast_v4_ifname, const char* mcast_v6_ifname)
{
	int ret = SOCKET_ERROR;
	switch (family)
	{
	case AF_INET:
		ret = (mcast_v4_ifname)
				  ? KcSocket_set_mcastif_ipv4(tmp_sock, mcast_v4_ifname)
				  : 0;
		break;
	case AF_INET6:
		ret = (mcast_v6_ifname)
				  ? KcSocket_set_mcastif_ipv6(tmp_sock, 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(socket_t tmp_sock, const char *ifname)
{
	struct in_addr ifaddr;
	ifaddr.s_addr = (ifname != NULL) ? inet_addr(ifname) : htonl(INADDR_ANY);
	int ret = setsockopt(tmp_sock, IPPROTO_IP, IP_MULTICAST_IF, &ifaddr, sizeof(ifaddr));
	return ret;
}

/**
 * マルチキャストを送信するためのインタフェースを設定します(IPv6)。
 *
 * @param sock 対象一時ソケット
 * @param ifname インタフェース名
 * @return 0/-1 (成功/失敗)
 */
static int KcSocket_set_mcastif_ipv6(socket_t tmp_sock, const char *ifname)
{
	unsigned int ifindex = (ifname != NULL) ? if_nametoindex(ifname) : 0;
	int ret = setsockopt(tmp_sock, 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;
}