Newer
Older
libkc / modules / src / kc_socket.c
Nomura Kei on 29 May 21 KB update
/**
 * @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;
}