结合linux内核源码理解SYN_RECV状态

这几日在看TCP协议栈代码的时候,总是有个疑问,本来是关于tcp_child_process的,看了这篇文章之后,顿悟。
  在tcp_v4_do_rcv中,有下面一段代码,是关于TCP连接建立时候的代码:

if (sk->sk_state == TCP_LISTEN) {
  struct sock *nsk = tcp_v4_hnd_req(sk, skb);
  if (!nsk)
  goto discard;
  if (nsk != sk) {
      if (tcp_child_process(sk, nsk, skb))
          goto reset;
      return 0;
    }
}

  tcp_v4_hnd_req的返回值,不同情况下不同。
  NULL 出现错误
  nsk==sk 接受到SYN
  nsk!=sk 接受到ACK
  接受到ACK包时,tcp_v4_hnd_req函数会新建一个sock结构,并设置其初始状态为SYN_RECV,并返回新建的sock结构。
  接着调用tcp_child_process函数,改变新建的sock的状态为ESTABLISHED。
  ==========================================================
  (以下基于linux内核2.4.0)
  SYN_RECV状态,顾名思义,是收到SYN包后应该置的状态。关于SYN_RECV状态,受某些教科书的误导,我以前一直理解为服务器收到SYN包后应该置此状态。也没细想到底是置那个socket的状态,最近在看三次握手协议在linux内核中的实现时,才仔细思考这个问题应该是置连接套接字的状态而非监听套接字的状态。
  通常,SYN包只用于TCP三次握手协议中。常见的tcp三次握手协议过程(当然还有同时连接、
  半连接等其它一些情况)如下:
  1、client SYN包---> server
  2、client <---SYN包/ACK包 server
  3、client ACK包---> server
  根据tcp状态图,对应下述4个状态的变化
  a、client发送完毕,状态变成SYN_SEND;
  b、server收到SYN报并发送ack确认包和SYN包,状态变为SYN_RECV
  c、client发送ack包完毕,状态变成ESTABLISHED
  d、server发送ack包完毕,状态变成ESTABLISHED
  在linux内核中,上述几个状态对应为TCP_SYN_SEND、TCP_SYN_RECV、TCP_ESTABLISHED.
  RFC793中关于SYN_RECV状态的描述如下:
  SYN-RECEIVED - represents waiting for a confirming connection
  request acknowledgment after having both received and sent a
  connection request.
  从上面可以看出,这个状态是在本端接收到对端连接请求,并发送连接对端请求后,等待对端应答时所置的状态。所以,本质上连接的过程是双方请求应答的来回, 应该称四次握手,只是常见的应用以c/s模式为主,而linux、包括绝大部分操作系统都把服务器端的应答和请求封装在一个包里面。
  但在linux内核中,却是在监听套接字收到了客户端的ACK包后,才创建连接套接字并初始化为TCP_SYN_RECV状态,如下函数调用关系:
  tcp_v4_rcv-->tcp_v4_do_rcv-->tcp_v4_hnd_req-->tcp_check_req-->
  tcp_v4_syn_recv_sock-->tcp_create_openreq_child...

  struct sock *tcp_create_openreq_child(struct sock *sk, struct open_request *req, struct sk_buff *skb)
  {
  struct sock *newsk = sk_alloc(PF_INET, GFP_ATOMIC, 0); /*创建连接sock结构*/
  if(newsk != NULL) {
  struct tcp_opt *newtp;
  ...
  memcpy(newsk, sk, sizeof(*newsk));
  newsk->state = TCP_SYN_RECV; /*置初始状态为SYN_RECV*/
  //以下为一些初始化newsk结构的操作
  ...
  }

  这里似乎都正常了,但还有一点,服务器收到ACK包后,状态应该改为连接状态,而此时连接套接字的状态还是TCP_SYN_RECV
  原因在于现在对ack包还没处理完,^_^,如下:

    int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
  {
  ...
  if (sk->state == TCP_LISTEN) { //此处是监听套接字的状态
  struct sock *nsk = tcp_v4_hnd_req(sk, skb); //获得了上面讲的连接套接字
  if (!nsk)
  goto discard;
  if (nsk != sk) { //显然监听与连接套接字不等
  if (tcp_child_process(sk, nsk, skb)) //此处调用tcp_rcv_state_process置套接字为连接建立状态
  goto reset;
  return 0;
  }
  }
  ...
  }

  可见,在linux内核中,SYN_RECV状态的保持时间是非常短暂的(也很难创建条件让此状态保持),这也是我们实际应用中通过netstat基本看不到这个状态的原因。

virt-manager配置

virtualbox 迁移

  • 删除virtualbox guest tool
  • 关闭virtualbox 虚拟机
  • 转换虚拟磁盘格式
vboxmanage clonehd --format raw 1-disk001.vdi win7.img

SPICE Tool 下载

https://www.spice-space.org/download/binaries/spice-guest-tools/

远程复制粘贴

  • 安装spice guest tool
http://www.linux-kvm.com/content/rhev-spice-guest-drivers-released-windows
  • virsh edit XXX (XXX为虚拟机的名字)
  • 在devices标签中增加了两个channel
<channel type='pty'>
    <target type='virtio' name='arbitrary.virtio.serial.port.name'/>
    <address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>
<channel type='spicevmc'>
    <target type='virtio' name='com.redhat.spice.0'/>
    <address type='virtio-serial' controller='0' bus='0' port='2'/>
</channel>

Typecho被挂马清理

被挂马原理详解[ https://mp.weixin.qq.com/s/IE9g6OqfzAZVjtag-M_W6Q ]

(1)迁移主目录

 $cp -R blog blog_back

(2)下载新版typecho

 $git clone https://github.com/typecho/typecho.git

(3)重命名主目录

 $mv typecho blog

(4)迁移配置

 $cp blog_back/config.inc.php blog

(5)迁移plugins themes uploads

 $cp -R blog_back/usr/plugins blog/usr/plugins
 $cp -R blog_back/usr/themes blog/usr/themes
 $cp -R blog_back/usr/uploads blog/usr/uploads

(6)清理原目录

 $rm -rf blog_back

RSA签名及验签原理解析

一、名词解释

公钥

  RSA 密钥体系中对外公开的部分,通常用于数据加密、验证数字签名。

私钥

  RSA 密钥体系中非公开的部分,通常用于数据解密、数据签名。

数字签名

  就是只有信息的发送者才能产生的,别人无法伪造的一段数字串,它同时也是对发送者发送的信息的真实性的一个证明。

二、为什么要使用签名

  在系统间报文交互为保证上送报文可靠性[防止中间人攻击],通常做法是在报文中加上校验字[数字签名],并约定好相关算法。

三、实现

  为方便系统间交互,本文直接讲述原理,对于RSA_sign 及 RSA_verify的使用下期进行介绍。

签名

  1. 对需要上送的报文[en_data]计算特征值[sign_block],相关算法包括[md5、sha1、sha256、sha512等等]。
  2. 使用私钥[pri_key]对sign_block加密获得数字签名[sign]。
  3. 将sign与en_data打包发送给对方。

验签

  1. 解析收到的报文,拆分为sign 及 en_data。
  2. 对en_data计算特征值[sign_block],相关算法包括[md5、sha1、sha256、sha512等等双方协商]。
  3. 使用公钥[pub_key]对sign解密,获得sign_block1。
  4. 比较sign_block 和 sign_block1,若匹配则验证成功,报文未被篡改。

生成公私钥对

int rsa_gen_der_key(int KEY_LENGTH, unsigned char * prik, unsigned char * pubk) {
    int ret = 1;
    RSA * keypair = RSA_generate_key(KEY_LENGTH, RSA_F4, NULL, NULL);

    if (!RSA_check_key(keypair)) {
        ret = 0;
    }
    /* public key */
    int size = i2d_RSAPublicKey(keypair, NULL); //提取公钥大小
    if (!size) {
        ret = 0;
    }
    unsigned char* pubbuf = (unsigned char*) malloc(size + 1); //生成公钥存储空间
    unsigned char *p = pubbuf;

    if (pubbuf == NULL) {
        ret = 0;
    }
    size = i2d_RSAPublicKey(keypair, &p); //必须用p,不能直接&pubbuf,因为参数会改变
    if (!size) {
        ret = 0;
    }
    binToStr(pubbuf, pubk, size);
    free(pubbuf);

    /* private key*/
    size = i2d_RSAPrivateKey(keypair, NULL); //提取公钥大小
    if (!size) {
        ret = 0;
    }
    unsigned char* pribuf = (unsigned char*) malloc(size + 1); //生成公钥存储空间
    p = pribuf;

    if (pribuf == NULL) {
        ret = 0;
    }
    size = i2d_RSAPrivateKey(keypair, &p);
    if (!size) {
        ret = 0;
    }
    binToStr(pribuf, prik, size);
    free(pribuf);

    RSA_free(keypair);
    return 0;
}

私钥加密[签名]代码

int prik_encrypt(unsigned char * pri_key, unsigned char * ming,unsigned char * mi) {
    int iRet = 0;
    int pubk_len = strlen(pri_key) / 2;
    unsigned char * prik;
    prik = (unsigned char*) malloc(pubk_len + 1);
    strToBin(pri_key, prik, pubk_len);

    RSA* prikey;
    unsigned char *p = prik;
    prikey = d2i_RSAPrivateKey(NULL, (const unsigned char**) &p, pubk_len);

    int len_patch = 116;

    int in_len = strlen(ming);
    int in_len_x = in_len % len_patch;
    int in_len_y = (int)(in_len / len_patch);
    if(in_len_x != 0) in_len_y += 1;
    unsigned char rsa_in[1024],rsa_out[1024];

    int rsa_i;
    int rsa_j=0;
    int start_index=0;
    for(rsa_i=0;rsa_i<in_len_y;rsa_i++){
        memset(rsa_in,  0x00, sizeof(rsa_in));
        memset(rsa_out, 0x00, sizeof(rsa_out));

        int deal_len = in_len >= len_patch ? len_patch : in_len;

        memcpy(rsa_in, ming + start_index, deal_len);
        start_index += deal_len;
        in_len -= deal_len;

        iRet = RSA_private_encrypt(deal_len, (unsigned char*) rsa_in,
                (unsigned char*) rsa_out, prikey, RSA_PKCS1_PADDING);
        if(iRet < 0){
            iRet = -1;
            break;
        }else{
            binToStr(rsa_out,mi + rsa_j,iRet);
            rsa_j += iRet * 2;
        }
    }
    if (prik)
        free(prik);
    if (prikey)
        RSA_free(prikey);

    return iRet;
}

公钥解密[验签]代码

int pubk_decrypt(unsigned char * pub_key, unsigned char * mi,unsigned char * ming) {
    int iRet = 0;
    int prik_len = strlen(pub_key) / 2;
    unsigned char * pubk;
    pubk = (unsigned char*) malloc(prik_len + 1);
    strToBin(pub_key, pubk, prik_len);

    RSA* pubkey;
    unsigned char *p = pubk;
    pubkey = d2i_RSAPublicKey(NULL, (const unsigned char**) &p, prik_len);

    int in_len = strlen(mi) / 2;

    int len_patch = 128;

    int in_len_x = in_len % len_patch;
    int in_len_y = (int)(in_len / len_patch);
    if(in_len_x != 0) in_len_y += 1;
    unsigned char rsa_128_in[130];
    unsigned char rsa_out[1024];


    int rsa_i;
    int rsa_j=0;
    int start_index = 0;
    for(rsa_i=0;rsa_i<in_len_y;rsa_i++){
        memset(rsa_128_in,0x00,sizeof(rsa_128_in));
        memset(rsa_out,0x00,sizeof(rsa_out));

        int deal_len = in_len >= len_patch ? len_patch : in_len;
        strToBin(mi + start_index, rsa_128_in, deal_len);
        start_index += deal_len * 2;
        in_len -= deal_len;

        iRet = RSA_public_decrypt(deal_len, (unsigned char*) rsa_128_in,
                (unsigned char*) rsa_out, pubkey, RSA_PKCS1_PADDING);
        if(iRet < 0){
            iRet = -1;
            break;
        }else{
            memcpy(ming + rsa_j,rsa_out,iRet);
            rsa_j += iRet;
        }
    }
    if (pubk)
        free(pubk);
    if (pubkey)
        RSA_free(pubkey);
    return iRet;
}

误区

公钥[pub_key]、私钥[pri_key]在密钥体系中是平等的;并不是公钥只能加密,私钥只能解密;私钥也可以用来加密,然后使用公钥解密,只不过我们对这一操作区了个别名叫[签名]和[验签]。