TCP/IP实现(四) IP编址

一.IP地址

        一个IP地址是被指派给一个系统中的网络接口的而不是系统本身,就如在《TCP/IP实现(二) 接口层数据结构》中描述的那样,每一个用于存储IP协议地址信息的in_ifaddr结构都和一个描述接口信息的ifnet(或者是他的专用化)相关联。

        IPv4地址分为5类:          

                                                                     

二.地址指派

三.ioctl系统调用

        ioctl函数可以用于获取接口信息,访问路由表,访问ARP高速缓存等,一般将那些不适合归于其它类别的特性接口放入该函数中。在本节中我们只讨论与接口信息相关的使用与实现。

1.ioctl函数的使用

1)获取所有接口的所有地址信息和接口名SIOCGIFCONF

        通过给ioctl函数传递参数SIOCGIFCONF可以获取接口的名称和地址信息,此外还需要一个套接字描述符和一个ifconf结构体,该结构体的定义如下:

// 该结构体其实是对一个缓冲区的封装,该缓冲区用于存放ifreq结构
struct ifconf {
    int  ifc_len; // 初始化时设置为缓冲区buf的长度,当调用完ioctl函数后为缓冲区中的数据长度
    union {
        char* buf;                // 在开辟空间时使用
        struct ifreq *ifcu_req;   // 在遍历缓冲区时使用
    }ifc_ifcu;
    #define ifc_buf	ifc_ifcu.ifcu_buf
    #define ifc_req	ifc_ifcu.ifcu_req
};

struct ifreq {
    #define IFHWADDRLEN	6
    #define IFNAMSIZ	IF_NAMESIZE
    union {
	    char ifrn_name[IFNAMSIZ];	//接口名称,比如:le0
      } ifr_ifrn;

    union {                        // 注意以下都是共用体,因此一个ifreq只能保存以下信息中的一个
	    struct sockaddr ifru_addr;    //接口地址信息
	    struct sockaddr ifru_dstaddr;
	    struct sockaddr ifru_broadaddr;
	    struct sockaddr ifru_netmask;
	    struct sockaddr ifru_hwaddr;
	    short int ifru_flags;
	    int ifru_ivalue;
	    int ifru_mtu;
	    struct ifmap ifru_map;
	    char ifru_slave[IFNAMSIZ];	/* Just fits the size */
	    char ifru_newname[IFNAMSIZ];
	    __caddr_t ifru_data;
      } ifr_ifru;
  };

    使用方法如下:

void get_ifi_info(){
    int sockfd;
    int lastlen = 0, len;
    char *buf;
    ifconf ifc;
    ifreq* ifrp;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    len = 100 * sizeof(struct ifreq);
    // 获取接口列表
    // - 由于起初并不知道要为接收接口信息的缓存ifconf 开辟多大的空间,因此只能采用探测的方法
    // - 先调用ioctl,记录下缓冲区中的数据长度,再次用更大的缓冲区去调用ioctl,只有两次数据长度相等才说明缓冲区够大
    while(1) {
        buf = new char[len];
        ifc.ifc_len = len;
        ifc.ifc_buf = buf;
        if(ioctl(sockfd, SIOCGIFCONF, &ifc) < 0) { // 调用ioctl将接口信息存入ifconf指示的缓存
            if(errno != EINVAL || lastlen != 0) {
                exit(errno);
            }
        }
        else {
            if(ifc.ifc_len == lastlen)
                break;
            lastlen = ifc.ifc_len;
        }
        len += 10 * sizeof(struct ifreq);
        delete buf;
    }
    // 遍历存有接口信息的缓冲区
    int addrlen;
    sockaddr_in *addr4;
    sockaddr_in6 *addr6;
    char addrBuf[30];
    for(ifrp = ifc.ifc_req; (char*)ifrp < buf + ifc.ifc_len; ifrp++){
        switch(ifrp->ifr_addr.sa_family ) { // 判断地址簇类型
        case AF_INET6:
            addr6 = reinterpret_cast<sockaddr_in6 *>(&ifrp->ifr_addr);
            inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf,30);
            addrlen = sizeof(struct sockaddr_in6);
            break;
        case AF_INET:
             addr4 = reinterpret_cast<sockaddr_in *>(&ifrp->ifr_addr);
             inet_ntop(AF_INET, &addr4->sin_addr, addrBuf,30);
        default:
            addrlen = sizeof(struct sockaddr_in);
            break;
        }
        std::cout<<ifrp->ifr_name << "  " << addrBuf <<std::endl; // 打印接口名和接口地址
    }
}

2)获取及设置接口标志(SIOCGIFFLAGS,SIOCSIFFLAGS)

        接口标志ifnet中的if_flag用于表明接口的操作状态和属性,一个进程可以查看所有标志,但下图中标为内核专用的不可改变

                                         

        查看接口标志的命令是:SIOCGIFFLAGS

        设置接口标志的命令是:SIOCSIFFLAGS

        对于这两个命令需要通过ifreq参数传入接口名来指定接口,从而获取指定接口的标志,若接口不存在则将errno设置为ENXIO

       使用方式如下:

void get_if_flags()
{
    struct ifreq ifq;
    strcpy(ifq.ifr_name, "ens33");  // 设置要查询的接口名,如“lo”
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(ioctl(sockfd, SIOCGIFFLAGS, &ifq) < 0) { // 将设置了接口名的ifreq 作为参数传入ioctl
        exit(errno);
    }
    int flags = ifq.ifr_flags;
    if( (flags & IFF_UP) == 0) { // 该接口是否打开
        std::cout<<"IFF DOWN";
        return;
    }

    if(flags & IFF_BROADCAST) { // 该接口是否用于广播网
        std::cout<<"ifnet be used to broadcast"<<std::endl;;
    }

    if(flags & IFF_LOOPBACK) { // 该接口是否用于环回网络
        std::cout<<"ifnet be used to LOOPBACK"<<std::endl;;
    }
}

3)获取和设置接口的IP地址,网络掩码等(SIOCGIFADDR,SIOCSIFADDR,...)

        意该命令所指定的socket必须支持传入地址的地址簇类,即你不能通过一个UDP套接字配置一个OSI地址。

设置接口IP(SIOCSIFADDR):

        若该接口还未设置IP地址,则为其分配一个新的IP地址,若已有IP地址则将其第一个IP地址改变为用户设置的IP。若该过程中IP设置失败,则将接口回滚回原先的IP。

获取接口IP(SIOCGIFADDR):

         该命令将IP地址存入用户指定的ifreq中,以便返回给用户。

这里只对设置接口IP进行举例说明:

void set_ifi_addr()
{
    struct ifreq ifq;
    strcpy(ifq.ifr_name,"ens33");        // 通过接口名指定接口
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    std::string ip = "10.1.16.9";
    sockaddr_in *addr = (sockaddr_in*)&(ifq.ifr_addr);
    addr->sin_family = AF_INET;
    ::inet_pton(AF_INET, ip.c_str(), &addr->sin_addr);
    if(ioctl(sockfd, SIOCSIFADDR, &ifq) < 0) { // 设置指定接口的IP
        std::cout<<"errno"<<errno<<std::endl;
    }
}

        

2.ioctl系统调用的实现

1)SIOCGIFCONF(获取各个接口的名称和全部地址)命令的实现

       ioctl函数转而调用ifconf函数来获取接口名和为其配置的地址信息。该函数首先遍历接口链表ifnet,将接口的名称复制到一个局部的ifreq中,接着遍历该接口ifnet的地址链表,将地址信息也保存到局部ifreq中,若无地址信息则将ifreq的ifru_addr成员清0,最后将该局部ifreq复制到用户提供的缓存中。其简要伪代码如下:

ifreq ifr; // 局部ifreq
// 遍历接口链表,并判断用户提供的剩余空间是否足够大
for(ifnet *ifp = ifnet; ifp && space > sizeof(ifreq); ifp = ifp->if_next) {
    拷贝接口名到ifr ->ifr_name
    if(ifp->if_addrlist == 0) {// 若接口的地址链表为空
        bzero(ifr->ifru_adr);//清空局部ifreq的地址信息
        将ifr拷贝至用户缓存;
    }
    else {    
        for(遍历接口的地址链表) {
            拷贝地址信息到局部ifreq;
            将ifr拷贝至用户缓存;
        }
    }
}

          参照图:

           

 

2)SIOCGIFFLAGS(获取接口标志)和SIOCSIFFLAGS(设置接口标志)命令的实现

        对于获取接口标志的命令SIOCGIFFLAGS,ioctl函数根据提供的接口名查找到相应接口,并将接口中的标志信息ifnet的if_flags复制到用户提供的ifreq中。

       而对于设置接口标志的命令SIOCSIFFLAGS,ioctl函数还会调用suser函数检查进程的权限,只有进程拥有超级用户权限才可以修改接口标志,之后将用户传入的flags和~IFF_CANTCHANCE进行与操作,其意图在于去除那些不能被进程改变的标志,最后调用相应函数设置接口标志。

3) SIOCSIFADDR(设置接口IP)等命令的实现

       在调用ioctl函数后回进行如下伪代码操作:

for(in_ifaddr* ia = in_ifaddr; ia; ia = ia->ia->next){ //遍历整个IP地址链表(in_ifaddr结构)
    // 若该IP地址关联的接口是要找的接口
    if(ia->ia_ifp == ifp) // ia_ifp 为协议地址结构对接口信息结构ifnet的回指
        break;
}

switch(cmd) // 命令分类
{
    case SIOCSIFADDR:
    case SIOCSIFNETMASK:
    case SIOCSIFDSTADDR:
        if( (so->so_state & SS_PRIV) == 0) // 若不是超级用户进程创建的套接字
            return EPERM;                  // 进程能够创建一个套接字,并放弃它的超级用户权限,仍可发送有特权的ioctl命令。
        if(ia == (struct in_ifaddr *)0) {
            分配一个新的in_ifaddr结构,并进行初始化,但不填入地址信息
        }
};

switch(cmd)
{
    case SIOCSIFADDR:
        调用in_ifinit函数将地址信息填入找到的第一个IP地址结构或是上面新创建的地址结构中
        若失败则回滚到旧值
        break;
    case SIOCSIFNETMASK:
        ...
        break;
    ...
}

   注意设置IP地址时,最后会执行ifp->if_flags |= IFF_UP。即将接口设为运行状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值