IP地址結構信息與字符串相互轉化:inet_pton和inet_ntop, etc.


1. 問題描述

1)假如有一個IPv4地址字符串(點分十進制),如何轉化為socket編程所需的sockaddr地址結構信息?
2)假如通過getaddrinfo等方式,已經取得了sockaddr地址結構信息,如何轉化為字符串形式?(點分十進制 for IPv4, 冒號十六進制 for IPv6)

什么是IP地址文本形式?
點分十進制數串: 形如"192.168.0.1"
冒號十六進制數串:形如"fe80::20c:29ff:fe99:de11"


2. IPv4場景解決方案

inet_aton, inet_addr, inet_ntoa在點分十進制數串與長度為32bits的網絡字節序二進制值間轉換IPv4地址。 不適用於IPv6地址。

2.1 inet_aton函數

點分十進制數串 -> IPv4地址結構(in_addr)
將主機地址cp(點分十進制數串)轉換成一個32bits的網絡字節序二進制值,保存到inp指向的緩存。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);
  • 參數
    cp 點分十進制數串,由調用者提供, 通常cp采用形如a.b.c.d形式, 其他支持的形式參見man inet_aton手冊
    inp 輸出參數, 指向in_addr結構。由調用者維護緩存, 函數填寫內容

  • 返回值
    若cp指向的地址有效,返回非0;若無效,返回0

特別地,

如果inp為NULL,那么該函數仍然將輸入的字符串執行有效性檢查,但是不存儲任何結果

2.2 inet_addr函數

點分十進制數串 -> IPv4地址結構(in_addr)
inet_addr同inet_aton的區別是, inet_addr返回32bits的網絡字節序二進制值

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

in_addr_t inet_addr(const char *cp);

該函數有個特殊情況,

所有232個可能值都是有效IP地址(0.0.0.0 到 255.255.255.255),但是出錯時,函數返回INADDR_NONE(通常-1,對應32個全1的值),也就是說點分十進制數串255.255.255.255(IPv4有限廣播地址)不能由該函數處理。

因此,應當避免使用inet_addr

2.3 inet_ntoa函數

IPv4地址結構(in_addr) -> 點分十進制數串
函數將32bits主機地址in轉化成點分十進制數串

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char *inet_ntoa(struct in_addr in);
  • 參數
    in 調用者傳入,IPv4地址結構對象。注意到與inet_aton的參數inp的區別,in並非指針

  • 返回值
    返回static分配的緩存, 后續的調用會覆寫之前的內容。因此,如果不想被覆蓋,需要自行分配存儲進行復制、保存。

2.4 in_addr和sockaddr_in地址結構

可以從下面看到,in_addr是sockaddr_in的一個成員,而in_addr實際上是一個32bit的無符號整型,不過被包裝成的結構體。
這也就是說, 如果是想直接將點分十進制數串和ip地址信息相互轉換時, 應該是與sockaddr_in.sin_addr進行轉化,而不能直接與sockaddr_in對象或s_addr進行轉換。

sockaddr_in結構:

/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
  __SOCKADDR_COMMON (sin_);
  in_port_t sin_port;			/* Port number.  */
  struct in_addr sin_addr;		/* Internet address.  */

  /* Pad to size of `struct sockaddr'.  */
  unsigned char sin_zero[sizeof (struct sockaddr) -
			   __SOCKADDR_COMMON_SIZE -
			   sizeof (in_port_t) -
			   sizeof (struct in_addr)];
};

#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family
#define __SOCKADDR_COMMON_SIZE	(sizeof (unsigned short int))

in_addr結構:

typedef uint32_t in_addr_t;

struct in_addr {
   in_addr_t s_addr;
};

2.4 示例

通過inet_aton,inet_addr,將指定點分十進制ip文本串轉化為二進制數;然后,通過inet_ntoa將指定ip地址二進制數轉化為文本串,觀察是否能還原文本串。

int test()
{
	char *ipstr = "192.168.0.1";
//	char *ipstr = "255.255.255.255";
	struct sockaddr_in sockaddr;
	int ret;
	char *str;

	/* convert ip from text to binary for IPv4  */
	ret = inet_aton(ipstr, &sockaddr.sin_addr);
	if (ret == 0) {
		fprintf(stderr, "inet_aton error: invalid ip string: %s\n", ipstr);
		return -1;
	}
	printf("inet_aton convert ip : from %s to %d\n", ipstr, sockaddr.sin_addr.s_addr);

	/* convert ip from text to binary for IPv4 except 255.255.255.255 */
	sockaddr.sin_addr.s_addr = inet_addr(ipstr); /* avoid to use inet_addr */
	if (sockaddr.sin_addr.s_addr == INADDR_NONE) {
		fprintf(stderr, "inet_addr error: invalid ip string: %s\n", ipstr);
				return -1;
	}
	printf("inet_addr convert ip : from %s to %d\n", ipstr, sockaddr.sin_addr.s_addr);

	/* convert ip from binary to text for IPv4 */
	str = inet_ntoa(sockaddr.sin_addr);
	if (str == NULL) {
		fprintf(stderr, "inet_ntoa error: invalid ip structure: %d\n", sockaddr.sin_addr.s_addr);
		return -1;
	}
	printf("inet_ntoa convert ip: from %d to %s\n", sockaddr.sin_addr.s_addr, str);

	return 0;
}

運行結果:

inet_aton convert ip : from 192.168.0.1 to 16820416
inet_addr convert ip : from 192.168.0.1 to 16820416
inet_ntoa convert ip: from 16820416 to 192.168.0.1

3. IPv4,IPv6場景共同解決方案

3.1 inet_pton函數

IPv4和IPv6地址: 文本 -> 二進制
將地址文本串 轉換成 網絡字節序二進制數形式

#include <arpa/inet.h>

int inet_pton(int af, const char *src, void *dst);
  • 參數
    af 地址家族,支持的取值(且只能是這2個):
    1)AF_INET 適用於IPv4地址,只支持"ddd.ddd.ddd.ddd"這種點分十進制數串
    2)AF_INET6 適用於IPv6地址
    注意:這里不同於socket()的af(地址family/domain),沒有AF_UNIX

src 指向IP地址的文本形式的字符串(點分十進制數串 for IPv4,冒號十六進制數串 for IPv6)

dst 指向網絡地址結構(struct in_addr for IPv4, struct struct in_addr6 for IPv6)

  • 返回值
    成功,返回1(網絡地址成功轉換);如果在指定地址家族中,src不是一個有效網絡地址文本串,則返回0;如果af不包含一個有效地址家族(非規定2個取值),則返回-1, 且errno設置為EAFNOSUPPORT。

3.2 inet_ntop函數

IPv4和IPv6地址: 二進制 -> 文本
將地址網絡字節序二進制數 轉換成 文本串形式

#include <arpa/inet.h>

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
  • 參數
    af 地址家族,支持的取值(且只能是這2個):
    1)AF_INET 適用於IPv4地址,只支持"ddd.ddd.ddd.ddd"這種點分十進制數串
    2)AF_INET6 適用於IPv6地址
    注意:這里不同於socket()的af(地址family/domain),沒有AF_UNIX

src 指向二進制地址結構,對於ipv4, src指向struct in_addr;對於ipv6,src指向struct in_addr6

dst 指向存放地址文本串的緩存,由調用者維護緩存, 函數填充內容

size 指示dst指向緩存大小

  • 返回值
    成功,返回指向dst的非NULL指針;失敗, 返回NULL,errno被設置

3.3 示例

對於ipv4,方式同inet_aton, inet_addr, inet_ntoa,先將文本串轉化為二進制數,打印;然后再轉化為文本串,再打印。觀察中間輸出,以及最終是否能還原。
對於ipv6,因為ipv6地址二進制數128bit, 這里沒有直接打印二進制數,而是先將已知的ipv6文本串(冒號十六進制表示法)先通過inet_pton轉化為二進制串,再通過inet_ntop轉換回文本串。觀察是否能還原。

int test() 
{
        /* ipv4 solution */
        struct sockaddr_in sockaddr;
        char *ipstr = "192.168.0.1";
        char ip4text[INET_ADDRSTRLEN];
        char *str;
        int ret;

	printf("===ipv4===\n");

	/* convert ip from text to binary for IPv4  */
	bzero(&sockaddr, sizeof (sockaddr));
	ret = inet_pton(AF_INET, ipstr, &sockaddr.sin_addr);
	if (ret != 1) {
		if (ret == 0) {
			fprintf(stderr, "inet_pton error: %s is not a valid ip text\n", ipstr);
		}
		else if (ret == -1) {
			perror("inet_pton error");
		}

		fprintf(stderr, "inet_pton error: unkown error");
		return -1;
	}
	printf("inet_pton convert ip from %s to %d\n", ipstr, sockaddr.sin_addr.s_addr);

	/* convert ip from binary to text  for IPv4  */
	str = inet_ntop(AF_INET, &sockaddr.sin_addr, ip4text, sizeof(ip4text));
	if (str == NULL) {
		perror("inet_ntop error");
		return -1;
	}
	printf("inet_ntop convert ip from %d to %s\n", sockaddr.sin_addr.s_addr, str);

        /* ipv6 solution */
	char *ip6str = "fe80::20c:29ff:fe99:de11"; /* inet6 from bash "$ ifconfig -a" */
	char ip6text[INET6_ADDRSTRLEN];
	struct sockaddr_in6 sockaddr6;

	printf("===ipv6===\n");
	/* convert ip from text to binary for IPv6  */
	bzero(&sockaddr6, sizeof (sockaddr6));
	ret = inet_pton(AF_INET6, ip6str, &sockaddr6.sin6_addr);
	if (ret != 1) {
		if (ret == 0) {
			fprintf(stderr, "inet_pton error: %s is not a valid ipv6 text\n", ip6str);
		}
		else if (ret == -1) {
			perror("inet_pton error");
		}

		fprintf(stderr, "inet_pton error: unkown error");
		return -1;
	}

	/* convert ip from binary to text  for IPv6  */
	str = inet_ntop(AF_INET6, &sockaddr6.sin6_addr, ip6text, sizeof(ip6text));
	if (str == NULL) {
		perror("inet_ntop error");
		return -1;
	}
	printf("%s\n", str);
}

運行結果:

===ipv4===
inet_pton convert ip from 192.168.0.1 to 16820416
inet_ntop convert ip from 16820416 to 192.168.0.1
===ipv6===
fe80::20c:29ff:fe99:de11

3.4 sock_ntop自定義函數

inet_ntop存在一個問題:要求調用者傳遞一個指向某個二進制地址的指針src,而該指針通常包含在一個套接字地址結構中,也就是說,調用者必須直到這個結構的格式,地址家族,以及應該具體為哪個IP格式提供各自的地址結構(struct sockaddr_in for IPv4, struct sockaddr_in6 for IPv6)

i.e. 對IPv4,應該編寫這樣的代碼:

struct sockaddr_in sockaddr;
inet_ntop(AF_INET, &sockaddr.sin_addr, str, sizeof(str));

對IPv6,應該編寫這樣的代碼:

struct sockaddr_in6 sockaddr6;
inet_ntop(AF_INET6, &sockaddr6.sin_addr, str, sizeof(str)); 

這樣,就使得代碼的編寫與協議相關。為解決該問題,編寫sock_ntop,以指向某個套接字地址結構的指針為參數,查看該結構的內部,然后根據具體情況返回地址的文本串格式

char *sock_ntop(const struct sockaddr *sa, socklen_t addrlen);

函數實現

char *sock_ntop(const struct sockaddr *sa, socklen_t addrlen)
{
	char portstr[8];
	static char str[128]; /* struct sockaddr_storage最多128byte */

	switch (sa->sa_family) {
	case AF_INET: {
		struct sockaddr_in *sin = (struct sockaddr_in *)sa;
		if (inet_ntop(AF_INET, &sin->sin_addr, str, sizeof(str)) == NULL)
			return NULL; /* 轉換存在錯誤 */
		if (ntohs(sin->sin_port) != 0) {
			snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin->sin_port));
			strcat(str, portstr);
		}
		return str;
	}

	case AF_INET6: {
		struct sockaddr_in *sin6 = (struct sockaddr_in *)sa;
		if (inet_ntop(AF_INET6, &sin6->sin_addr, str, sizeof(str)) == NULL)
			return NULL; /* 轉換存在錯誤 */
		if (ntohs(sin6->sin_port) != 0) {
			snprintf(portstr, sizeof(portstr), ":%d", ntohs(sin6->sin_port));
			strcat(str, portstr);
		}
		return str;
	}

	default:
		errno = EAFNOSUPPORT; /* 不支持的地址family */
	}
	return NULL;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM