UNIX网络编程笔记(7)—名字与地址转换
这一章就明显没有前面的TCP和UDP重要了,看得也比较糙,试了几个函数,了解了一些DNS服务器的工作原理,比如递归啊,迭代啊,ISP DNS等等。。。
(一)概述
一般情况下,我们以数值地址表示主机,如127.0.0.1本地回路或206.6.226.33书中的例子,用数值端口号来标识服务器,例如用端口13来标识标准的daytime服务器,本章讲述地名字和数值地址之间的转换,相关的函数有:gethostbyname
、gethostbyaddr
在主机名字与IPv4地址之间的转换等等。
(二)域名系统
域名系统,也就是我们说的DNS,全名叫Domain Name System,可以用于主机名字和IP地址之间的映射。www.baidu.com这样的名字被称作全限定域名(FQDN),我们也可以使用简单的名字,例如localhost等等。
这里有一个小插曲,关于Ubuntu的域名解析系统,这里直接参考Ubuntu nameserver 127.0.1.1
2.1 DNS替代法
不使用DNS也可以直接获取名字和地址信息,常用的替代方法有:
1.静态主机文件 /etc/hosts 文件
2.NIS(Network Information System)网络信息系统。
3.轻权目录访问协议(Lightweight Directory Access Protocol,LDAP)
关于hosts再简单聊一聊关于“墙”的东西,这也是以前了解的一些东西。以前的“墙”做的比较简单,大概是这样的:
GFW会对所有经过骨干出口路由的在UDP53端口(也就是DNS开放端口)的域名进行检测,一旦发现黑名单里的域名,它就会伪装成目标域名的解析服务器给查询者返回虚假结果。由于 UDP是一种无连接不可靠的协议,查询者只能接受最先返回的结果。
以前“墙”还不那么高的时候,可以通过静态主机文件也就是hosts文件编辑主机名/域名 和对应IP这样就绕过了DNS服务器这一层了,不过GFW也是在不断发展的,这个办法已经行不通了,因为http协议是明文的,GFW还会对明文中的关键字过滤掉,而https是加密的,所以https+hosts的办法就可以翻墙了,不过因为GFW的其他一些手段,这个办法仅限教育网IPv6~ LOL
ps:hosts的请求级别比DNS服务器高,所以会优先查host
(三)gethostbyname函数和gethostbyaddr
简单的区别是前者通过主机名得到IP,而后者通过二进制IP得到主机名。
#include <netdb.h>
struct hostent * gethostbyname(const char *hostname);
//如果出错则返回NULL指针
如果调用成功就会返回一个hostent指针,所指向的结构包含查找主机的所有IPv4地址,局限时只能返回IPv4地址。
hostent结构如下:
struct hostent{
char * h_name;//正式主机名
char **h_aliases;//别名
int h_addrtypes;//AF_INET
int h_length;//4
char **h_addr_list;//IP
}
h_length的大小是4,这里的4是指4个字节,对于一个点分十进制IPv4地址,比如说:“192.169.1.1”这样一个字符串,在IPv4中以4个整数(二进制来表示),一个字节8位可以表示的范围是0~255,所以只需要4个字节就可以表示了。而对于一个字符串形式的点分十进制ipv4地址,则至少需要16个字节(参考形式xxx.xxx.xxx.xxx还要加一个字节的结束符)。
通过一个图可以了解hostent的结构:
gethostbyname的测试代码
参照UNP中的代码,把包裹函数拆开。
//testgethostbyname.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <stdio.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <signal.h>
#include <arpa/inet.h>
int main(int argc, char **argv)
{
char * ptr;
char ** pptr;
struct hostent *hptr;
//#define INET_ADDRSTRLEN 16 //xxx.xxx.xxx.xxx
char str[INET_ADDRSTRLEN];
while(--argc > 0)
{
++argv;
ptr=*argv;
if((hptr=gethostbyname(ptr))==NULL)
{
printf("gethostbyname error,NULL ptr returned\r\n");
return -1;
}
printf("official hostname: %s\r\n",hptr->h_name);
printf("h_length = %d\r\n",hptr->h_length);
for(pptr=hptr->h_aliases;*pptr != NULL;pptr ++)
{
printf("alias: %s\r\n",*pptr);
}
if(AF_INET==hptr->h_addrtype)
{
pptr=hptr->h_addr_list;
for(;*pptr!=NULL;pptr++)
{
const char * cpstr=inet_ntop(AF_INET,*pptr,str,sizeof(str));
if(NULL == cpstr)
{
printf("inet_ntop error NULL returned \r\n");
return -1;
}
printf("address: %s\r\n",cpstr);
}
}
else
{
printf("unknown address type\r\n");
return -1;
}
}//end while
return 0;
}
(四)getservbyname和getservbyport函数
这两个函数就跟端口号相关了,从TCP和UDP编程的例子我们可以知道,服务器通过bind特定的端口号来提供服务,这其中也有众所周知的服务和其对应的端口号,通过查看/etc/service
文件。
这种函数从名字就能知道其含义了。以getservbyname
为例
#include<netdb.h>
struct servent * getservbyname(const char *servname,const char *protoname);
第一个参数是服务的名字,例如“daytime”,“ftp”,这些都是可以在/etc/service文件中找到的,第二个参数是具体协议,可以是TCP,可以是UDP,也可以是NULL,如果某个服务仅仅支持单个协议例如FTP只支持TCP,那使用NULL就会默认为TCP。
getservbyname 和 gethostbyname结合使用
书中给出的示例客户端,该客户端有如下功能:我们只需要给出主机名服务名就可以获取服务器提供的服务,如果服务器正在运行的话,当然为了简单起见,这里以daytime 13/tcp
为例,在客户端程序只有简单的read,所以我们完全可以把自己之前写的TCP回射服务器程序添加到/etc/services里卖去,例如:
zxecho 1024/tcp
我们可以做如下调用:
./newdaytimetcpcli localhost zxecho
或者
./newdaytimetcpcli localhost daytime
前提是,绑定1024端口的服务器或者绑定daytime服务端口的服务器运行,并向已连接套接字发送一些数据。
4.1 代码
//newdaytimetcpcli.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <stdio.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <signal.h>
#include <arpa/inet.h>
#define MAXLINE 1024
int main(int argc,char **argv)
{
int sockfd;
int n;
struct sockaddr_in servaddr;
struct hostent *hp;
struct servent *sp;
char **pptr;
char recvline[MAXLINE];
if(argc != 3)
{
printf("usage:daytimetcpcli1 <hostname> <service>\r\n");
return -1;
}
hp=gethostbyname(argv[1]);
if(NULL==hp)
{
printf("gethostbyname error! NULL returned\r\n");
return -1;
}
else
{
pptr=hp->h_addr_list;
}
sp=getservbyname(argv[2],"tcp");
if(NULL==sp)
{
printf("gerservbyname error! NULL returned\r\n");
return -1;
}
for(;*pptr!=NULL;pptr++)
{
if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("socket error \r\n");
return -1;
}
memset(&servaddr,0x00,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=sp->s_port;//network byte order
memcpy(&servaddr.sin_addr,(struct in_addr *)*pptr,sizeof(struct in_addr));
if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))==0)
{
printf("connect success\r\n");
break;//connect success
}
else
{
printf("connect error\r\n");
close(sockfd);
}
}
if(NULL==*pptr)
{
printf("unable to connect\r\n");
return -1;
}
while((n=read(sockfd,recvline,MAXLINE))>0)
{
recvline[n]='\0';
fputs(recvline,stdout);
}
return 0;
}
(五)总结
这一章蛮僵的,以至于后面几个函数看看就算了,懒得测试了。
本章的主要收获到不在于那几个函数,如何调用又花式的获取主机名,ip地址等等,感觉了解就好。
不过因为牵涉到DNS,顺带看了看相关内容,什么DNS劫持啊,GFW,之类。