• 网上最全的套接字socket


    套接字socket使用


    1.1创建socket
      sk = socket(int family, int type, int protocol);
      family:协议簇,PF_INET、PF_INET6、PF_PACKET等等。
      type:类型,SOCK_DGRAM、SOCK_STREAM、SOCK_RAW等等。
      protocol:协议,协议簇上面的协议,这个字段不可以使用组合方式,只能使用一个(例如不能使用IPPROTO_UDP|IPPROTO_ICMP)。
      协议栈收到数据包是从family开始匹配的,之后匹配type,protocol,如果family是PF_PACKET,则继续匹配protocol(此时没有类型区分)。

    1.2.socket参数组合

      [1]创建INET TCP/UDP socket:
        socket(PF_INET,  SOCK_DGRAM,  IPPROTO_UDP);   

                    //创建UDP socket,第三个参数可以填0,填0默认是用UDP类型
        socket(PF_INET,  SOCK_STREAM,  IPPROTO_TCP);   

                    //创建TCPsocket,第三个参数可以填0,填0默认是用TCP类型
        收到的为应用层数据,发送时填充应用层数据。

      [2]创建INET RAW socket:
        socket(PF_INET,  SOCK_RAW,  IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP);
        收到的是从IP头开始的数据,发送时从IP协议之后填充,例如UDP协议时填充UDP头+应用数据,TCP则填充TCP头+应用数据。
        如果设置了IP_HDRINCL选项:setsockopt(sk, IPPROTO_IP, IP_HDRINCL, &option, sizeof(option)),则UDP协议时发送时填充IP头+UDP头+应用数据。
        socket(PF_INET,  SOCK_RAW,  IPPROTO_RAW);  //收发都是从ip头开始,等于自动设置IP_HDRINCL选项

      [3]创建链路层socket:
        socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))
        收到的是从二层头开始的完整数据包,当第三个参数是ETH_P_ALL时,可以接收到从本机发出去的数据包。
        socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))
        收到的是从二层头上面开始的数据包。
    1.3.总结
      [1]SOCK_RAW类型通常称为"原始套接字",这里的"原始"是相对来说的,例如协议簇是PF_INET时,则原始就是IP层的起始。协议簇是PF_PACKET时,则原始就是二层的起始

    套接字socket选项


    1. IP_TRANSPARENT
      [1]socket设置该选项后,可以处理发往非本机的数据包。
      [2]使用流程:配置防火墙和路由:

    iptables -t mangle -A PREROUTING ! -d 10.0.110.250 -p tcp -j TPROXY --on-port 10000 --on-ip 0.0.0.0 --tproxy-mark 0x1/0x1
    ip rule add fwmark 1 lookup 100 
    ip route add local 0.0.0.0/0 dev lo table 100

    1. //创建监听socket
    2. int option = 1;
    3. struct sockaddr_in addr;
    4. addr.sin_addr.s_addr = 0;
    5. addr.sin_port = 10000;
    6. addr.sin_family = PF_INET;
    7. bind(fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in));
    8. setsockopt(fd, SOL_IP, IP_TRANSPARENT, &option, sizeof(option));
    9. setsockopt(fd, IPPROTO_IP, IP_RECVORIGDSTADDR, &option, sizeof(option)); // udp socket需要设置
    10. //tcp获取客户端连接的真实服务器地址
    11. struct sockaddr_in client_addr, server_addr;
    12. socklen_t addrlen = sizeof(struct sockaddr_in);
    13. new_fd = accept(fd, (struct sockaddr *)&client_addr, &addrlen);
    14. getsockname(fd, (struct sockaddr *)&server_addr, &addrlen);
    15. //udp获取客户端连接的真实服务器地址
    16. int found;
    17. char buffer[1024];
    18. char cntrlbuf[64];
    19. struct iovec iov[1];
    20. struct msghdr msg;
    21. struct cmsghdr *cmsg;
    22. struct sockaddr_in client_addr, server_addr;
    23. msg.msg_name = &client_addr;
    24. msg.msg_namelen = sizeof(struct sockaddr_in);
    25. msg.msg_control = cntrlbuf;
    26. msg.msg_controllen = sizeof(cntrlbuf);
    27. iov[0].iov_base = buffer;
    28. iov[0].iov_len = sizeof(buffer);
    29. msg.msg_iov = iov;
    30. msg.msg_iovlen = 1;
    31. ret = recvmsg(fd, &msg, 0);
    32. if(ret >= 0)
    33. {
    34.     found = 0;
    35.     for(cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg))
    36. {
    37.         if(cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVORIGDSTADDR)
    38. {
    39.             memcpy(&server_addr, CMSG_DATA(cmsg), sizeof(struct sockaddr_in));
    40.             server_addr.sin_family = AF_INET;
    41.             found = 1;
    42.         }
    43.     }
    44. }

    2.SO_REUSEADDR
      [1]这个选项表示复用地址,表示多个socket可以绑定到同一个地址。
      [2]TCP协议时,设置该选项后,可以使用相同的地址去连接不同的服务器地址。但是不可以去连接相同的服务器地址,除非本地的当前连接处于TIME_WAIT状态。
      [3]TCP协议时,如果一个地址已经被bind,则另一个socket不可以再次listen。如果一个地址已经被socket执行listen,则另一个socket不可以再次bind。
      [4]UDP协议时,设置该选项后,可以多个socket绑定同一个地址去接收数据,但是只有最后一个socket可以收到数据(需要继续分析代码)。
      需要注意的是socket的地址格式是ip:port,只有ip和port都相等时才存在复用,有一个不等则不存在复用这一说。

      
    AF_PACKET


    使用socket(AF_PACKET, SOCK_RAW, ETH_P_ALL)创建的套接字到底为何于众不同,今日追踪了一下。使用Linux 3.2.5版内核
    net/socket.c


    SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
    {
    ......
        retval = sock_create(family,  type, protocol, &sock);
    ......
    }


    int sock_create(int family, int type, int protocol, struct socket **res)
    {
        return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
    }


    int __sock_create(struct net *net, int family, int type, int protocol,
    struct socket **res, int kern)
    {
    ......
        pf = rcu_dereference(net_families[family]);
    ......
        err = pf->create(net, sock, protocol, kern);
    ......
    }


    static const struct net_proto_family __rcu *net_families[NPROTO] __read_mostly;

    int sock_register(const struct net_proto_family *ops)
    {
    ......
        if (rcu_dereference_protected(net_families[ops->family],
                     lockdep_is_held(&net_family_lock)))
            err = -EEXIST;
        else {
            rcu_assign_pointer(net_families[ops->family], ops);
            err = 0;
        }
    ......
    }

    net/packet/af_packet.c

    static const struct net_proto_family packet_family_ops = {
        .family =    PF_PACKET,
        .create =    packet_create,
        .owner    =    THIS_MODULE,
    };

    static int __init packet_init(void)
    {
    ......
        sock_register(&packet_family_ops);
    ......
    }

    module_init(packet_init);


    static int packet_create(struct net *net, struct socket *sock, int protocol,
                 int kern)
    {
        struct sock *sk;
        struct packet_sock *po;
        __be16 proto = (__force __be16)protocol; /* weird, but documented */
    ......
        sk = sk_alloc(net, PF_PACKET, GFP_KERNEL, &packet_proto);
    ......
        sock->ops = &packet_ops;
    ......
        po = pkt_sk(sk);
        sk->sk_family = PF_PACKET;
        po->num = proto;
    ......
        po->prot_hook.func = packet_rcv;
    ......
        po->prot_hook.af_packet_priv = sk;
        if (proto) {
            po->prot_hook.type = proto;
            register_prot_hook(sk);
        }
    ......
    }

    AF_PACKET套接字的功能来源于prot_hook,其本身是struct packet_type类型:
    1:type成员设定为了socket()传递的参数(这里是ETH_P_ALL)
    2:过滤得到的包的处理函数保存于func成员,这里被设定为 packet_rcv()
    3:dev成员用于对net_device的过滤,可以在bind()中指定

    static int packet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
    {
    ......
        if (sll->sll_ifindex) 
        {
            err = -ENODEV;
            dev = dev_get_by_index(sock_net(sk), sll->sll_ifindex);
            if (dev == NULL)
                goto out;
        }
        err = packet_do_bind(sk, dev, sll->sll_protocol ? : pkt_sk(sk)->num);
    ......
    }

    static int packet_do_bind(struct sock *sk, struct net_device *dev, __be16 protocol)
    {
        struct packet_sock *po = pkt_sk(sk);
    ......
        po->prot_hook.dev = dev;

        po->ifindex = dev ? dev->ifindex : 0;
    ......
        if (!dev || (dev->flags & IFF_UP)) {
            register_prot_hook(sk);
    ......
    }

    可见在bind()中若指定了绑定的net_device同样会触发prot_hook的注册动作。


    static void register_prot_hook(struct sock *sk)
    {
        struct packet_sock *po = pkt_sk(sk);
        if (!po->running) {
            if (po->fanout)
                __fanout_link(sk, po);
            else
                dev_add_pack(&po->prot_hook);
            sock_hold(sk);
            po->running = 1;
        }
    }

    net/core/dev.c


    void dev_add_pack(struct packet_type *pt)
    {
        struct list_head *head = ptype_head(pt);
        spin_lock(&ptype_lock);
        list_add_rcu(&pt->list, head);
        spin_unlock(&ptype_lock);
    }


    static struct list_head ptype_all __read_mostly;    /* Taps */

    static inline struct list_head *ptype_head(const struct packet_type *pt)
    {
        if (pt->type == htons(ETH_P_ALL))
            return &ptype_all;
        else
            return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];
    }

    历尽千辛万苦,终于知道AF_PACKET套接字把自己的prot_hook挂到了ptype_all链表上或ptype_base链表上。至于prot_hook怎么发挥作用进行监听,请听下回分解!

    使用PF_PACKET和SOCK_RAW发送自定义type以太网数据包
    使用PF_PACKET和 SOCK_RAW探测网络包 可以更简单的捕捉我们自定义的数据包,使用SOCK_DGRAM。
    本篇使用PF_PACKET和SOCK_DGRAM探测网络包

    1. #include <stdio.h>
    2. #include <errno.h>  
    3. #include <unistd.h>
    4. #include <sys/socket.h>
    5. #include <sys/types.h>  
    6. #include <linux/in.h>
    7. #include <linux/if_ether.h>
    8. #include <stdlib.h>
    9. int main(int argc, char **argv) 
    10. {
    11.     int sock, n;
    12.     char buffer[2048];
    13.     unsigned char *data, *ethhead;
    14.     //SOCK_DGRAM是过滤的L2的数据头
    15.     if ( (sock=socket(PF_PACKET, SOCK_DGRAM,  htons(0x8874)))<0
    16.     {                    
    17.         perror("socket");
    18.         exit(1);
    19.     }
    20.     while (1
    21.     {
    22.         printf("----------\n");
    23.         n = recvfrom(sock,buffer,2048,0,NULL,NULL);//读取时buffer直接只剩下数据了,不需要像SOCK_RAW一样跳过L2的数据头
    24.         printf("%d bytes read\n",n);
    25.         if (n<42)
    26. {
    27.             perror("recvfrom():");
    28.             printf("Incomplete packet (errno is %d)\n",
    29.                     errno);
    30.             close(sock);
    31.             exit(0);
    32.         }
    33.         /*
    34.         ethhead = buffer;
    35.         printf("Source MAC address: "
    36.                 "%02x:%02x:%02x:%02x:%02x:%02x\n",
    37.                 ethhead[0],ethhead[1],ethhead[2],
    38.                 ethhead[3],ethhead[4],ethhead[5]);
    39.         printf("Destination MAC address: "
    40.                 "%02x:%02x:%02x:%02x:%02x:%02x\n",
    41.                 ethhead[6],ethhead[7],ethhead[8],
    42.                 ethhead[9],ethhead[10],ethhead[11]);
    43.         printf("%x%x\n", ethhead[12],ethhead[13]);
    44.         data=buffer+14;
    45.         
    46.         printf("%s\n",data);
    47.         */
    48.         printf("%s\n",buffer);
    49.     }
    50. }

    网络编程还是挺有意思!!!
     

  • 相关阅读:
    【python海洋专题三十八】海洋指数画法--折线图样式二
    python AIOT教程一1.必备多元函数微分学理论基础
    【无标题】
    基于改进灰狼算法优化核极限学习机的锂电池动力电池荷电状态估计
    【紫光同创国产FPGA教程】——【PGL22G第九章】HDMI环路实验例程
    C++函数模板
    R语言绘制分组方框图四
    麒麟系统加密/麒麟系统防泄密
    Django REST framework(十)路由集routers的使用
    015 Linux 标准输入输出、重定向、管道和后台启动进程命令
  • 原文地址:https://blog.csdn.net/qq_20853741/article/details/125548622