前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >代码实现TCP三次握手:程序实现

代码实现TCP三次握手:程序实现

作者头像
望月从良
发布2019-08-20 16:33:10
9820
发布2019-08-20 16:33:10
举报
文章被收录于专栏:Coding迪斯尼

本节我们通过代码来实现TCP协议连接时的三次握手过程。首先我们需要再次重温一下TCP数据包的相关结构:

我们们将依照上面结构所示来构建数据包,相关代码如下:

代码语言:javascript
复制
public class TCPProtocolLayer implements IProtocol {
    private static int HEADER_LENGTH = 20;
    private int sequence_number = 2;
    private int acknowledgement_number = 0;
    private static int PSEUDO_HEADER_LENGTH = 12;
    public static byte TCP_PROTOCOL_NUMBER = 6;
    private static int POSITION_FOR_DATA_OFFSET = 12;
    private static int POSITION_FOR_CHECKSUM = 16;
    private static byte MAXIMUN_SEGMENT_SIZE_OPTION_LENGTH = 4;
    private static byte MAXIMUN_SEGMENT_OPTION_KIND = 2;
    private static byte WINDOW_SCALE_OPTION_KIND = 3;
    private static byte WINDOW_SCALE_OPTION_LENGTH = 3;
    private static byte WINDOW_SCALE_SHIFT_BYTES = 6;
    private static byte TCP_URG_BIT = (1 << 5);
    private static byte TCP_ACK_BIT = (1 << 4);
    private static byte TCP_PSH_BIT = (1 << 3);
    private static byte TCP_RST_BIT = (1 << 2);
    private static byte TCP_SYN_BIT = (1 << 1);
    private static byte TCP_FIN_BIT = (1);
    @Override
    public byte[] createHeader(HashMap<String, Object> headerInfo) {
        short data_length = 0;
        byte[] data = null;
        if (headerInfo.get("data") != null) {
            data = (byte[])headerInfo.get("data");
        }
        byte[] header_buf = new byte[HEADER_LENGTH];
        ByteBuffer byteBuffer = ByteBuffer.wrap(header_buf);
        if (headerInfo.get("src_port") == null) {
            return null;
        }
        short srcPort = (short)headerInfo.get("src_port");
        byteBuffer.putShort(srcPort);
        if (headerInfo.get("dest_port") == null) {
            return  null;
        }
        short  destPort = (short)headerInfo.get("dest_port");
        byteBuffer.putShort(destPort);

        //设置初始序列号
        if (headerInfo.get("seq_num") != null) {
            sequence_number = (int)headerInfo.get("seq_num");
        }
        if (headerInfo.get("ack_num") != null) {
            acknowledgement_number = (int)headerInfo.get("ack_num");
        }
        byteBuffer.putInt(sequence_number);
        byteBuffer.putInt(acknowledgement_number);
        short control_bits = 0;
        //设置控制位
        if (headerInfo.get("URG") != null) {
            control_bits |= (1 << 5);
        }
        if (headerInfo.get("ACK") != null) {
            control_bits |= (1 << 4);
        }
        if (headerInfo.get("PSH") != null) {
            control_bits |= (1 << 3);
        }
        if (headerInfo.get("RST") != null) {
            control_bits |= (1 << 2);
        }
        if (headerInfo.get("SYN") != null) {
            control_bits |= (1 << 1);
        }
        if (headerInfo.get("FIN") != null) {
            control_bits |= (1);
        }
        byteBuffer.putShort(control_bits);
        System.out.println(Integer.toBinaryString(control_bits));

        char window = 65535;
        byteBuffer.putChar(window);
        short check_sum = 0;
        byteBuffer.putShort(check_sum);
        short urgent_pointer = 0;
        byteBuffer.putShort(urgent_pointer);

        byte[] maximun_segment_option = new byte[MAXIMUN_SEGMENT_SIZE_OPTION_LENGTH];
        ByteBuffer maximun_segment_buffer =  ByteBuffer.wrap(maximun_segment_option);
        maximun_segment_buffer.put(MAXIMUN_SEGMENT_OPTION_KIND);
        maximun_segment_buffer.put(MAXIMUN_SEGMENT_SIZE_OPTION_LENGTH);
        short segment_size = 1460;
        maximun_segment_buffer.putShort(segment_size);

        byte[] window_scale_option = new byte[WINDOW_SCALE_OPTION_LENGTH];
        ByteBuffer window_scale_buffer = ByteBuffer.wrap(window_scale_option);
        window_scale_buffer.put(WINDOW_SCALE_OPTION_KIND);
        window_scale_buffer.put(WINDOW_SCALE_OPTION_LENGTH);
        window_scale_buffer.put(WINDOW_SCALE_SHIFT_BYTES);

        byte[] option_end = new byte[1];
        option_end[0] = 0;

        int total_length = data_length + header_buf.length + maximun_segment_option.length + window_scale_option.length + option_end.length;
        //总长度必须是4的倍数,不足的话以0补全
        if (total_length % 4 != 0) {
            total_length = (total_length / 4 + 1) * 4;
        }
        byte[] tcp_buffer = new byte[total_length];
        ByteBuffer buffer = ByteBuffer.wrap(tcp_buffer);
        buffer.put(header_buf);
        buffer.put(maximun_segment_option);
        buffer.put(window_scale_option);
        buffer.put(option_end);
        short data_offset = buffer.getShort(POSITION_FOR_DATA_OFFSET);
        data_offset |= (((total_length / 4) & 0x0F) << 12);
        System.out.println(Integer.toBinaryString(data_offset));
        buffer.putShort(POSITION_FOR_DATA_OFFSET, data_offset);
        check_sum = (short)compute_checksum(headerInfo, buffer);
        buffer.putShort(POSITION_FOR_CHECKSUM, check_sum);
        return buffer.array();
    }

    private long compute_checksum(HashMap<String, Object> headerInfo, ByteBuffer tcp_buffer) {
        byte[] pseudo_header = new byte[PSEUDO_HEADER_LENGTH];
        ByteBuffer pseudo_header_buf = ByteBuffer.wrap(pseudo_header);
        byte[] src_addr = (byte[])headerInfo.get("src_ip");
        byte[] dst_addr = (byte[])headerInfo.get("dest_ip");
        pseudo_header_buf.put(src_addr);
        pseudo_header_buf.put(dst_addr);
        byte reserved = 0;
        pseudo_header_buf.put(reserved);
        pseudo_header_buf.put(TCP_PROTOCOL_NUMBER);
        short tcp_length = (short)tcp_buffer.array().length;
        //将伪包头和tcp包头内容合在一起计算校验值
        byte[] total_buffer = new byte[PSEUDO_HEADER_LENGTH + tcp_buffer.array().length];
        ByteBuffer total_buf = ByteBuffer.wrap(total_buffer);
        total_buf.put(pseudo_header);
        total_buf.put(tcp_buffer.array());
        return Utility.checksum(total_buffer, total_buffer.length);
    }

    @Override
    public HashMap<String, Object> handlePacket(Packet packet) {
        ByteBuffer buffer= ByteBuffer.wrap(packet.header);
        HashMap<String, Object> headerInfo = new HashMap<String, Object>();
        short src_port = buffer.getShort();
        headerInfo.put("src_port", src_port);
        short dst_port = buffer.getShort();
        headerInfo.put("dest_port", dst_port);
        int seq_num = buffer.getInt();
        headerInfo.put("seq_num", seq_num);
        int ack_num = buffer.getInt();
        headerInfo.put("ack_num", ack_num);
        short control_bits = buffer.getShort();
        if ((control_bits & TCP_ACK_BIT) != 0) {
            headerInfo.put("ACK", 1);
        }
        if ((control_bits & TCP_SYN_BIT) != 0) {
            headerInfo.put("SYN", 1);
        }
        if ((control_bits & TCP_FIN_BIT) != 0) {
            headerInfo.put("FIN", 1);
        }
        short win_size = buffer.getShort();
        headerInfo.put("window", win_size);
        //越过校验值
        buffer.getShort();
        short urg_pointer = buffer.getShort();
        headerInfo.put("urg_ptr", urg_pointer);
        return headerInfo;
    }
}

上面代码实现了协议层TCP的封包与解包,在函数createHeader中,我们按照上图结构填写相关包头的字段,在函数handlePacket中,我们根据包头的字段获取相应信息。

在ProtocolManager中转层,我们实现下面代码:

代码语言:javascript
复制
private void handleTCPPacket(Packet packet,  HashMap<String, Object> infoFromUpLayer) {
        IProtocol tcpProtocol = new TCPProtocolLayer();
        HashMap<String, Object> headerInfo = tcpProtocol.handlePacket(packet);
        short dstPort = (short)headerInfo.get("dest_port");
        //根据端口获得应该接收UDP数据包的程序
        IApplication app = ApplicationManager.getInstance().getApplicationByPort(dstPort);
        if (app != null) {
            app.handleData(headerInfo);
        }
    }

一旦程序通过JPCap收到TCP包后,它会让上面实现的TCPProtocolLayer去解析数据包内的各个字段,然后检测数据包对应的端口是否在应用层有对应的接收对象,如果有的话,它就将解析信息转交给应用层的接收对象,接下来我们看应用层的相关实现:

代码语言:javascript
复制
public class TCPThreeHandShakes extends Application{
    private byte[] dest_ip;
    private short dest_port;
    private int ack_num = 0;
    private int seq_num = 0;
    public TCPThreeHandShakes(byte[] server_ip, short server_port) {
        this.dest_ip = server_ip;
        this.dest_port = server_port;
         //指定一个固定端口,以便抓包调试
        this.port = (short)11940;
    }

   public void beginThreeHandShakes() throws Exception {
       createAndSendPacket(null, "SYN");
   }

   private void createAndSendPacket(byte[] data, String flags) throws Exception {
       byte[] tcpHeader = createTCPHeader(null, flags);
       if (tcpHeader == null) {
            throw new Exception("tcp Header create fail");
        }
       byte[] ipHeader = createIP4Header(tcpHeader.length);
       byte[] packet  = new byte[tcpHeader.length + ipHeader.length];
       ByteBuffer packetBuffer = ByteBuffer.wrap(packet);
       packetBuffer.put(ipHeader);
       packetBuffer.put(tcpHeader);
       sendPacket(packet);
   }

   private void sendPacket(byte[] packet) {
       try {
            InetAddress ip = InetAddress.getByName("192.168.2.1");
            ProtocolManager.getInstance().sendData(packet, ip.getAddress());
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
   }

   private byte[] createTCPHeader(byte[] data, String flags) {
       IProtocol tcpProto = ProtocolManager.getInstance().getProtocol("tcp");
        if (tcpProto == null) {
            return null;
        }
        HashMap<String, Object> headerInfo = new HashMap<String, Object>();
        byte[] src_ip = DataLinkLayer.getInstance().deviceIPAddress();
        headerInfo.put("src_ip", src_ip);
        headerInfo.put("dest_ip", this.dest_ip);
        headerInfo.put("src_port", (short)this.port);
        headerInfo.put("dest_port", this.dest_port);
        headerInfo.put("seq_num", seq_num);
        headerInfo.put("ack_num", ack_num);
        String[] flag_units = flags.split(",");
        for(int i = 0; i < flag_units.length; i++) {
            headerInfo.put(flag_units[i], 1);
        }

        byte[] tcpHeader = tcpProto.createHeader(headerInfo);
        return tcpHeader;
   }

   protected byte[] createIP4Header(int dataLength) {
        IProtocol ip4Proto = ProtocolManager.getInstance().getProtocol("ip");
        if (ip4Proto == null || dataLength <= 0) {
            return null;
        }
        //创建IP包头默认情况下只需要发送数据长度,下层协议号,接收方ip地址
        HashMap<String, Object> headerInfo = new HashMap<String, Object>();
        headerInfo.put("data_length", dataLength);
        ByteBuffer destIP = ByteBuffer.wrap(this.dest_ip);
        headerInfo.put("destination_ip", destIP.getInt());
        byte protocol = TCPProtocolLayer.TCP_PROTOCOL_NUMBER;
        headerInfo.put("protocol", protocol);
        headerInfo.put("identification", (short)this.port);
        byte[] ipHeader = ip4Proto.createHeader(headerInfo);

        return ipHeader;
    }

   @Override
    public void handleData(HashMap<String, Object> headerInfo) {
       short src_port = (short)headerInfo.get("src_port");
       System.out.println("receive TCP packet with port:" + src_port);
       boolean ack =  false, syn = false;
       if (headerInfo.get("ACK") != null) {
           System.out.println("it is a ACK packet");
           ack = true;
       }
       if (headerInfo.get("SYN") != null) {
           System.out.println("it is a SYN packet");
           syn = true;
       }
       if (ack && syn) {
           int seq_num = (int)headerInfo.get("seq_num");
           int ack_num = (int)headerInfo.get("ack_num");
           System.out.println("tcp handshake from othersize with seq_num" + seq_num + " and ack_num: " + ack_num);
           this.seq_num = ack_num + 1;
           try {
            createAndSendPacket(null, "ACK");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       }

   }
}

应用层对象的主要目标是实现TCP连接的三次握手功能。它首先构造了一个TCP数据包,将SYN控制位打开,然后将数据包发送给目标服务器。然后等待对方回应数据包,一旦本机收到对方回发的ACK数据包后,会将数据包内的相关信息转交给当前应用对象,它解读出对方ACK包中回复的ACK数值后,将该数值加一然后再次构造一个ACK包发送给对方,上面程序运行后通过wireshark抓包可看到如下显示:

由此可见,我们成功的完成了TCP协议连接时的三次握手功能,上图显示中有一个数据包设置了RST标志位,它表示重置连接,这个数据包其实不是我们的应用对象发送,很可能是我们绕过了系统网络层发送数据包,当对方数据包回来时,操作系统的网络层发现接收对象没有在它内部不存在,于是自己构造了一个RST数据包发回给对方。

更多讲解和调试演示,请点击’阅读原文‘

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coding迪斯尼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档