Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

阅读笔记: Beej's Guide to Network Programming #301

Open
nonocast opened this issue Jun 14, 2022 · 0 comments
Open

阅读笔记: Beej's Guide to Network Programming #301

nonocast opened this issue Jun 14, 2022 · 0 comments

Comments

@nonocast
Copy link
Owner

nonocast commented Jun 14, 2022

Internet socket

  • Stream Socket (SOCK_STREAM)
  • Datagram Socket (SOCK_DGRAM)

Stream Socket借助TCP(The Transmission Control Protocol)实现重发和排序,确保数据完整性,而Datagram Socket则不检查包的达和顺序,但如果一个包到达,则这个包的内容是完整的,不会出现到半个包的情况。

TCP 會在傳輸層對將上層送來的過大訊息分割成多個分段(TCP segments),而 UDP 本身不會,UDP 是訊息導向的(message oriented),若 UDP 訊息過大時(整體封包長度超過 MTU),則會由 host 或 router 在 IP 層對封包進行分割,將一個 IP packet 分割成多個 IP fragments。IP fragmention 的缺點是,接收端的系統需要做 IP 封包的重組,將多個 fragments 重組合併為原本的 IP 封包,同時也會增加封包遺失的機率。如將一個 IP packet 分裂成多個 IP fragments,只要其中一個 IP fragment 遺失了,接收端就會無法順利重組 IP 封包,因而造成封包的遺失,若是高可靠度的應用,則上層協定需重送整個 packet 的資料。UDP, User Datagram Protocol就意味着将transimission control交给用户自行处理。

IPv4 and IPv6

  • IPv4 (32bit): 多采用Dotted Decimal Notation (点分十进制): 192.168.0.1 (0xc0a80001)
  • IPv6 (128bit): 多采用十六进制表达法, 每两个bytes间以冒号分隔: 2001:0db8:c9d2:aee5:73e3:934a:a5ae:9551
    • 可省略00, 如果2001:0db8:c9d2:0012:0000:0000:0000:0051可缩写为2001:0db8:c9d2:0012::0051
    • 2001:0db8:c9d2:0012:0000:0000:0000:0000可缩写为2001:0db8:c9d2:0012::
    • 0000:0000:0000:0000:0000:0000:0000:0001可缩写为::1

IPv4的addr对应sockaddr_in (16 bytes):

/*
 * [XSI] Structure used by kernel to store most addresses.
 */
struct sockaddr {
	__uint8_t       sa_len;         /* total length */
	sa_family_t     sa_family;      /* [XSI] address family */
	char            sa_data[14];    /* [XSI] addr value (actually larger) */
};

/*
 * Socket address, internet style.
 */
struct sockaddr_in {
	__uint8_t       sin_len;
	sa_family_t     sin_family;
	in_port_t       sin_port;
	struct  in_addr sin_addr;
	char            sin_zero[8];
};
  • XSI: System-V的prefix
  • sin_port, sin_addr都采用Network Byte Order (BE), sin_port赋值时都通过htons()转换,htons会根据arch实现cross-platorm,sin_addr也是,192.168.0.1也是按192, 168, 0, 1顺序存储

IPv6的addr对应sockaddr_in6 (28 bytes)

struct sockaddr_in6 {
	__uint8_t       sin6_len;       /* length of this struct(sa_family_t) */
	sa_family_t     sin6_family;    /* AF_INET6 (sa_family_t) */
	in_port_t       sin6_port;      /* Transport layer port # (in_port_t) */
	__uint32_t      sin6_flowinfo;  /* IP6 flow information */
	struct in6_addr sin6_addr;      /* IP6 address */
	__uint32_t      sin6_scope_id;  /* scope zone index */
};

IPv4和IPv6的前4字节的布局是一样的,如果字节不敏感,完全可以用IPv6的28字节布局来做IPv4,这样可以兼容IPv4, IPv6地址,通过不同类型指针去指向即可。

struct sockaddr_storage {
    sa_family_t ss_family; // address family
    char __ss_pad1[_SS_PAD1SIZE];
    int64_t __ss_align;
    char __ss_pad2[_SS_PAD2SIZE];
};

IP address 操作

仅 IPv4 (过弃)

  • inet_addr()
  • inet_aton()
  • inet_ntoa()
// Dotted Decimal Notation (点分十进制) => 32 bits (Network Order)
// 192.168.0.1 => 0x0100a8c0
TEST_F(SockaddrTest, inet_addr) {
  in_addr_t in = inet_addr("192.168.0.1");
  uint8_t expected[] = {192, 168, 0, 1};
  EXPECT_EQ(memcmp(&in, expected, 4), 0);
}

//  Dotted Decimal Notation => in_addr 
TEST_F(SockaddrTest, inet_aton) {
  struct in_addr in;
  inet_aton("192.168.0.1", &in);
  uint8_t expected[] = {192, 168, 0, 1};
  EXPECT_EQ(memcmp(&in, expected, 4), 0);
}

// in_addr => Dotted Decimal Notation
// 0x0100a8c0 => 192.168.0.1
TEST_F(SockaddrTest, inet_ntoa_a) {
  int32_t data = 0x0100a8c0;
  char *cp = inet_ntoa(*(struct in_addr *)&data);
  EXPECT_EQ(strcmp(cp, "192.168.0.1"), 0);
}

TEST_F(SockaddrTest, inet_ntoa_b) {
  struct in_addr in;
  in.s_addr = inet_addr("192.168.0.1");
  char *cp = inet_ntoa(in);
  EXPECT_EQ(strcmp(cp, "192.168.0.1"), 0);
}

兼容 IPv6 (推荐)

  • inet_pton()
  • inet_ntop()
// 192.168.0.1 => in_addr
// ::1 => in6_addr
TEST_F(SockaddrTest, inet_pton) {
  uint8_t expected4[] = {192, 168, 0, 1};
  uint8_t expected6[] = {[15] = 1};

  struct in_addr in;
  int rc = inet_pton(AF_INET, "192.168.0.1", &in);
  EXPECT_EQ(rc, 1);
  EXPECT_EQ(memcmp(&in, expected4, 4), 0);

  struct in6_addr in6;
  inet_pton(AF_INET6, "::1", &in6);
  EXPECT_EQ(memcmp(&in6, expected6, sizeof(in6_addr)), 0);
}

// in_addr => 192.168.0.1
// in6_addr => ::1
TEST_F(SockaddrTest, inet_ntop) {
  char ip4[INET6_ADDRSTRLEN];
  struct in_addr in;
  inet_aton("192.168.0.1", &in);
  const char *rp = inet_ntop(AF_INET, &in, ip4, INET_ADDRSTRLEN);
  EXPECT_EQ(strcmp(ip4, "192.168.0.1"), 0);
  EXPECT_EQ(strcmp(rp, "192.168.0.1"), 0);
  EXPECT_EQ(rp, ip4);
}

System Calls or Bust

getaddrinfo() - prepare to launch!

getaddrinfo帮助我们快速得到sockaddr,支持DNS解析和IPv6的支持,取代之前的gethostbyname。

如果你需要开启一个server, 那么对应的调用如下:

/*
 * 取代 gethostbyname()
 * host+service(port) => addrinfos
 * cat /etc/services
 * struct addrinfo {
 *   int ai_flags;	           / * AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST * /
 *   int ai_family;            / * PF_xxx * /
 *   int ai_socktype;          / * SOCK_xxx * /
 *   int ai_protocol;          / * 0 or IPPROTO_xxx for IPv4 and IPv6 * /
 *   socklen_t ai_addrlen;     / * length of ai_addr * /
 *   char *ai_canonname;       / * canonical name for hostname * /
 *   struct sockaddr *ai_addr; / * binary address * /
 *   struct addrinfo *ai_next; / * next structure in linked list * /
 * };
 * return when hints = NULL:
 * 1. AF_INET4 SOCK_STREAM
 * 2. AF_INET4 SOCK_DGRAM
 * 3. AF_INET6 SOCK_STREAM
 * 4. AF_INET6 SOCK_DGRAM
*/
TEST_F(SockaddrTest, getaddrinfo_as_server) {
  char ip[INET6_ADDRSTRLEN];
  struct addrinfo *servinfo;

  // first NULL means local host
  int error = getaddrinfo(NULL, "http", NULL, &servinfo);
  EXPECT_EQ(error, 0);

  int count = 0;
  for (struct addrinfo *p = servinfo; p != NULL; p = p->ai_next) {
    ++count;
    if (p->ai_family == AF_INET) {
      struct sockaddr_in *in = (struct sockaddr_in *)p->ai_addr;
      EXPECT_EQ(strcmp(inet_ntoa(in->sin_addr), "127.0.0.1"), 0);
      inet_ntop(in->sin_family, &in->sin_addr, ip, INET6_ADDRSTRLEN);
      EXPECT_EQ(strcmp(ip, "127.0.0.1"), 0);
    } else if (p->ai_family == AF_INET6) {
      struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)p->ai_addr;
      inet_ntop(in6->sin6_family, &in6->sin6_addr, ip, INET6_ADDRSTRLEN);
      EXPECT_EQ(strcmp(ip, "::1"), 0);
    }
  }

  EXPECT_EQ(count, 4);
  freeaddrinfo(servinfo);
}

// Server: hostname=NULL && AI_PASSIVE
TEST_F(SockaddrTest, getaddrinfo_as_server_with_hints) {
  char ip[INET6_ADDRSTRLEN];
  struct addrinfo *servinfo;
  struct addrinfo hints {
    .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_PASSIVE
  };

  // first NULL means local host
  int error = getaddrinfo(NULL, "http", &hints, &servinfo);
  EXPECT_EQ(error, 0);

  int count = 0;
  for (struct addrinfo *p = servinfo; p != NULL; p = p->ai_next) {
    ++count;
    if (p->ai_family == AF_INET) {
      struct sockaddr_in *in = (struct sockaddr_in *)p->ai_addr;
      EXPECT_EQ(strcmp(inet_ntoa(in->sin_addr), "0.0.0.0"), 0);
      inet_ntop(in->sin_family, &in->sin_addr, ip, INET6_ADDRSTRLEN);
      EXPECT_EQ(strcmp(ip, "0.0.0.0"), 0);
    } else if (p->ai_family == AF_INET6) {
      struct sockaddr_in *in = (struct sockaddr_in *)p->ai_addr;
      inet_ntop(in->sin_family, &in->sin_addr, ip, INET6_ADDRSTRLEN);
      EXPECT_EQ(strcmp(ip, "::"), 0);
    }
  }

  EXPECT_EQ(count, 2);
  freeaddrinfo(servinfo);
}
  • getaddrinfo会根据输入条件返回sockaddr列表
  • hints用来限制返回的列表,类似filter,比如你只需要IPv4的TCP,默认情况下,会返回IPv4, IPv6的TCP和UDP
  • 如果是server, 即需要bind,则需要设置AI_PASSIVE,同时host为NULL

如果你是client,则对应的代码如下:

TEST_F(SockaddrTest, getaddrinfo_as_client) {
  char ip[INET_ADDRSTRLEN];
  struct addrinfo *servinfo;
  struct addrinfo hints {
    .ai_family = AF_INET, .ai_socktype = SOCK_STREAM
  };

  int error = getaddrinfo("nonocast.cn", "http", &hints, &servinfo);
  EXPECT_EQ(error, 0);
  int count = 0;
  for (struct addrinfo *p = servinfo; p != NULL; p = p->ai_next) {
    ++count;
    if (p->ai_family == AF_INET) {
      struct sockaddr_in *in = (struct sockaddr_in *)p->ai_addr;
      EXPECT_EQ(strcmp(inet_ntoa(in->sin_addr), "212.64.40.9"), 0);
      inet_ntop(in->sin_family, &in->sin_addr, ip, INET_ADDRSTRLEN);
      EXPECT_EQ(strcmp(ip, "212.64.40.9"), 0);
    }
  }

  EXPECT_EQ(count, 1);
  freeaddrinfo(servinfo);
}

socket()

  • AF_* 表示 address family, 用于sockaddr
  • PF_* 表示 protocol family, 用于socket
  • 混用也没问题,值是相同的

然后,应该在getaddrinfo的基础上,call socket:

int s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
  • 返回值s为socket descriptor (中文翻译: 描述符,句柄之类), 错误时返回-1,通过全局的errno查看错误码

bind()

  • bind的作用是将user level的socket和kernel的port进行关联。
  • server: bind, 然后listen,但是client会在connect时自动bind

参考文档

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant