分类 默认分类 下的文章

结合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>

Debian Stretch remmina安装

由于Debian Stretch的官方源中并未包含remmina,所以我们通过如下方法安装

从其他软件源中安装

echo "deb http://apt.jurisic.org/debian/ stretch main contrib non-free" > /etc/apt/sources.list.d/jurisic.list
wget -q http://apt.jurisic.org/Release.key -O- | apt-key add -
apt-get update
apt-get install remmina

源码编译安装

Compile on Debian 9 Stretch

自建DDNS服务

环境

操作系统:Debian[jessie]
DNS服务器:powerdns
DDNS外挂:pboehm/ddns[https://github.com/pboehm/ddns.git]

配置go环境

1.下载go最新版本并解压至/opt
2.配置环境变量

vi /etc/profile
新增
export GOROOT=/opt/go
export GOPATH=/opt/go/root
export PATH=$PATH:$GOROOT/bin

3.安装ddns

$ go get github.com/pboehm/ddns
$ ls $GOPATH/bin/ddns     # the displayed path will be used later
/opt/go/root/bin/ddns

安装DNS服务器powerdns

#apt-get install redis-server pdns-server pdns-backend-pipe

配置DNS

#vim /etc/powerdns/pdns.d/pipe.conf

配置如下

launch=pipe
pipebackend-abi-version=1
pipe-command=/opt/go/root/bin/ddns --soa_fqdn=dns.example.com --domain=sub.example.com backend

soa_fqdn:自建DNS服务器地址
domain :ddns的子域名

配置ddns前端

# apt-get install nginx supervisor

新增配置

$ sudo vim /etc/supervisor/conf.d/ddns.conf
$ cat /etc/supervisor/conf.d/ddns.conf
[program:ddns]
command = /opt/go/root/bin/ddns --domain=sub.example.com --listen=127.0.0.1:10080  web
stdout_logfile=/var/log/supervisor/ddns.log
stderr_logfile=/var/log/supervisor/ddns.log
autostart = True
autorestart = True
redirect_stderr = True
startsecs=10

配置nginx

$ sudo vim /etc/nginx/sites-enabled/ddns
$ cat /etc/nginx/sites-enabled/ddns
server {
    listen   公网ip:10080;
    server_name  ddns.example.com;

    location / {
        proxy_pass http://127.0.0.1:10080;
        proxy_set_header    Host            $host;
        proxy_set_header    X-Real-IP       $remote_addr;
        proxy_set_header    X-Forwarded-for $proxy_add_x_forwarded_for;
        proxy_connect_timeout 300;
    }
}

启动服务

#service pdns start
#service supervisor start
#service nginx start

配置域名解析[以万网为例]

1.自定义DNS服务器
Unnamed1.jpg
2.选择自定义DNS服务器,并进行如下配置[此处一经定义不可修改]
Unnamed3.jpg
3.新增 DNS服务器对应的A记录 例如 dns.example.com
4.新增 NS记录,指定子域名由dns.example.com 解析,如下图
Unnamed2.jpg
其中 dns.example.com 对应IP为自己服务器ip