##PHP_FPM

  • 关于FPM

    参考文章:Nginx+Php-fpm 运行原理详解

    • 正向代理与反向代理:

      vpn对于“我们”来说,是可以感知到的(我们连接vpn)
      vpn对于”google服务器”来说,是不可感知的(google只知道有http请求过来)。
      对于人来说可以感知到,但服务器感知不到的服务器,我们叫他正向代理服务器。

      反向代理:通过反向代理实现负载均衡

      此代理服务器,对于“我们”来说是不可感知的(我们只能感知到访问的是百度的服务器,不知道中间还有代理服务器来做负载均衡)。
      此代理服务器,对于”server1 server2 server3”是可感知的(代理服务器负载均衡路由到不同的server)
      对于人来说不可感知,但对于服务器来说是可以感知的,我们叫他反向代理服务器

    • Nginx是什么

      Nginx (“engine x”) 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。

    • cgi、fast-cgi协议
      cgi的历史

      早期的webserver只处理html等静态文件,但是随着技术的发展,出现了像php等动态语言。
      webserver处理不了了,怎么办呢?那就交给php解释器来处理吧!
      交给php解释器处理很好,但是,php解释器如何与webserver进行通信呢?
      ==为了解决不同的语言解释器(如php、python解释器)与webserver的通信,于是出现了cgi协议。==只要你按照cgi协议去编写程序,就能实现语言解释器与webwerver的通信。如php-cgi程序。

      fast-cgi的改进

      有了cgi协议,解决了php解释器与webserver通信的问题,webserver终于可以处理动态语言了。但是,webserver每收到一个请求,都会去fork一个cgi进程,请求结束再kill掉这个进程。这样有10000个请求,就需要fork、kill php-cgi进程10000次。

      有没有发现很浪费资源?

      于是,出现了cgi的改良版本,fast-cgi。fast-cgi每次处理完请求后,不会kill掉这个进程,而是保留这个进程,使这个进程可以一次处理多个请求。这样每次就不用重新fork一个进程了,大大提高了效率。

    • php-fpm是什么

      php-fpm即php-Fastcgi Process Manager.

      php-fpm是 FastCGI 的实现,并提供了进程管理的功能。
      进程包含 master 进程和 worker 进程两种进程。
      master 进程只有一个,负责监听端口,接收来自 Web Server 的请求,而 worker 进程则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个 PHP 解释器,是 PHP 代码真正执行的地方。

    www.example.com/index.php
            |
            |
          nginx
            |
            |
    加载nginx的fast-cgi模块
            |
            |
    fast-cgi对根据fast-cgi协议请求包进行封装,然后将封装好的包发给php-fpm
            |
            |
    php-fpm 据fast-cgi协议将TCP流解析成真正的数据,调用php文件
            |
            |
    php-fpm处理完请求,返回给nginx
            |
            |
    nginx将结果通过http返回给浏览器
    
  • 攻击原理

    基本原理就是模仿nginx的fast-cgi,直接与php-fpm进行通信。在通信之前,我们首先需要了解一下其通信包的构成。

    • Fastcgi协议由多个record组成,record也有header和body一说,但是和HTTP头不同,record的头固定8个字节,body是由头中的contentLength指定,其结构如下:

      typedef struct {
        /* Header */
        unsigned char version; // 版本
        unsigned char type; // 请求包的类型
        unsigned char requestIdB1; // 请求包id高8位
        unsigned char requestIdB0; // 请求包id低8位
        unsigned char contentLengthB1; // body长度高8位
        unsigned char contentLengthB0; // body长度低8位
        unsigned char paddingLength; // 结尾填充长度
        unsigned char reserved;   // 保留字节
      
        /* Body */
        unsigned char contentData[contentLength];
        unsigned char paddingData[paddingLength];
      } FCGI_Record;
      

      可以看出一个请求头为8个字节。其参数解释如下: - version:用来表示版本信息,如果是web服务器给php-fpm发送的消息,请求头中只需要将其置0就可以 - type:此字段用来说明每次所发送消息的类型,其具体值可以为如下:

      CGI

      -requestId:占俩个字节,一个唯一的标志id,以避免同时处理多个请求时的影响。 - contentLength:占2个字节,表示body的长度。语言端解析了fastcgi头以后,拿到contentLength,然后再在TCP流里读取大小等于contentLength的数据,这就是body体。 - paddingLength:填充长度的值,为了提高处理消息的能力,我们的每个消息大小都必须为8的倍数,此长度标示,我们在消息的尾部填充的长度 - reserved:保留字段

    • fastcgi客户端脚本分析

      协议的内容大致了解了,接下来就是写代码,封装一下请求包。已经有前辈做了这件事情–>https://github.com/wuyunfeng/Python-FastCGI-Client,我们来尝试分析一下代码。

      #!/usr/bin/python
      
      import socket
      import random
      

      class FastCGIClient:
      “””A Fast-CGI Client for Python”””

      # 版本号,不重要
      __FCGI_VERSION = 1

      # FastCGI服务器角色及其设置
      __FCGI_ROLE_RESPONDER = 1
      __FCGI_ROLE_AUTHORIZER = 2
      __FCGI_ROLE_FILTER = 3

      # # type 记录类型1-11
      __FCGI_TYPE_BEGIN = 1
      __FCGI_TYPE_ABORT = 2
      __FCGI_TYPE_END = 3
      __FCGI_TYPE_PARAMS = 4
      __FCGI_TYPE_STDIN = 5
      __FCGI_TYPE_STDOUT = 6
      __FCGI_TYPE_STDERR = 7
      __FCGI_TYPE_DATA = 8
      __FCGI_TYPE_GETVALUES = 9
      __FCGI_TYPE_GETVALUES_RESULT = 10
      __FCGI_TYPE_UNKOWNTYPE = 11

      # 头部长度,默认为8
      __FCGI_HEADER_SIZE = 8

      # 自定义请求状态
      FCGI_STATE_SEND = 1
      FCGI_STATE_ERROR = 2
      FCGI_STATE_SUCCESS = 3

      def init(self, host, port, timeout, keepalive):
      self.host = host
      self.port = port
      self.timeout = timeout
      if keepalive:
      self.keepalive = 1
      else:
      self.keepalive = 0
      self.sock = None
      self.requests = dict()

      def __connect(self):
      # 此函数创建了一个socket,并且去连接(self.host, self.port)
      self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      self.sock.settimeout(self.timeout)
      self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      try:
      self.sock.connect((self.host, int(self.port)))
      except socket.error as msg:
      self.sock.close()
      self.sock = None
      print(repr(msg))
      return False
      return True

      def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
      # 此函数根据fcgi_type对content进行封装
      length = len(content)
      return chr(FastCGIClient.__FCGI_VERSION)
      + chr(fcgi_type)
      + chr((requestid >> 8) & 0xFF)
      + chr(requestid & 0xFF)
      + chr((length >> 8) & 0xFF)
      + chr(length & 0xFF)
      + chr(0)
      + chr(0)
      + content

      def __encodeNameValueParams(self, name, value):
      # 此函数对body进行编码
      nLen = len(str(name))
      vLen = len(str(value))
      record = ‘’
      if nLen < 128:
      record += chr(nLen)
      else:
      record += chr((nLen >> 24) | 0x80)
      + chr((nLen >> 16) & 0xFF)
      + chr((nLen >> 8) & 0xFF)
      + chr(nLen & 0xFF)
      if vLen < 128:
      record += chr(vLen)
      else:
      record += chr((vLen >> 24) | 0x80)
      + chr((vLen >> 16) & 0xFF)
      + chr((vLen >> 8) & 0xFF)
      + chr(vLen & 0xFF)
      return record + str(name) + str(value)

      def __decodeFastCGIHeader(self, stream):
      # 此函数对header进行解码
      # 被用于__decodeFastCGIRecord函数的一部分
      header = dict()
      header[‘version’] = ord(stream[0])
      header[‘type’] = ord(stream[1])
      header[‘requestId’] = (ord(stream[2]) << 8) + ord(stream[3])
      header[‘contentLength’] = (ord(stream[4]) << 8) + ord(stream[5])
      header[‘paddingLength’] = ord(stream[6])
      header[‘reserved’] = ord(stream[7])
      return header

      def __decodeFastCGIRecord(self):
      # 此函数对record进行解码
      header = self.sock.recv(int(FastCGIClient.__FCGI_HEADER_SIZE))
      if not header:
      return False
      else:
      record = self.__decodeFastCGIHeader(header)
      record[‘content’] = ‘’
      if ‘contentLength’ in record.keys():
      contentLength = int(record[‘contentLength’])
      buffer = self.sock.recv(contentLength)
      while contentLength and buffer:
      contentLength -= len(buffer)
      record[‘content’] += buffer
      if ‘paddingLength’ in record.keys():
      skiped = self.sock.recv(int(record[‘paddingLength’]))
      return record

      def request(self, nameValuePairs={}, post=’’):
      if not self.__connect():
      print(‘connect failure! please check your fasctcgi-server !!’)
      return
      # 区分多段Record.requestId作为同一次请求的标志
      requestId = random.randint(1, (1 << 16) - 1)
      self.requests[requestId] = dict()
      request = “”
      # 构造header
      beginFCGIRecordContent = chr(0)
      + chr(FastCGIClient.__FCGI_ROLE_RESPONDER)
      + chr(self.keepalive)
      + chr(0) * 5
      request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
      beginFCGIRecordContent, requestId)

      # 构造body
      paramsRecord = ‘’
      if nameValuePairs:
      for (name, value) in nameValuePairs.iteritems():
      # paramsRecord = self.__encodeNameValueParams(name, value)
      # request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
      paramsRecord += self.__encodeNameValueParams(name, value)

      if paramsRecord:
      request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
      request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, ‘’, requestId)

      if post:
      request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, post, requestId)
      request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, ‘’, requestId)
      # 发送fast-cgi格式的包
      self.sock.send(request)
      self.requests[requestId][‘state’] = FastCGIClient.FCGI_STATE_SEND
      self.requests[requestId][‘response’] = ‘’
      # 接受返回包
      return self.__waitForResponse(requestId)

      def __waitForResponse(self, requestId):
      # 接受返回包
      while True:
      response = self.__decodeFastCGIRecord()
      if not response:
      break
      if response[‘type’] == FastCGIClient.__FCGI_TYPE_STDOUT
      or response[‘type’] == FastCGIClient.__FCGI_TYPE_STDERR:
      if response[‘type’] == FastCGIClient.__FCGI_TYPE_STDERR:
      self.requests[‘state’] = FastCGIClient.FCGI_STATE_ERROR
      if requestId == int(response[‘requestId’]):
      self.requests[requestId][‘response’] += response[‘content’]
      if response[‘type’] == FastCGIClient.FCGI_STATE_SUCCESS:
      self.requests[requestId]
      return self.requests[requestId][‘response’]

      def repr(self):
      return “fastcgi connect host:{} port:{}”.format(self.host, self.port)
      ​```