相关文章推荐
精彩文章免费看

Hyperledger fabric grpc 源码分析--- error:connection failed

(目前有点乱,先贴上来,等以后有时间在整理吧。这个问题一直想拿出来分享,还有两个博客,都是相关的,一点点发出来)

最近要在fabric网络和外部添加一层load balance,然后使用node的grpcs调用nginx,再转发到peer或者orderer。但是一直显示 code 14 connect failed 。log信息少的可怜。所以索性就过一遍代码。找找区别,顺便打打log。

fabric-client和grpc的版本

$ npm ls | grep fabric
+-- fabric-ca-client@1.0.8
+-- fabric-client@1.0.8
$ npm ls | grep grpc
`-- grpc@1.10.1

fabric版本:

这里面要注意,fabric-node-sdk这个版本强制要求grpc的版本要高于1.10.1。

fabric sdk node的grpc

然后我们就开始跟踪fabric进行一个request的全过程。
我们跟踪的方法时client.installChaincode()。

  • 构建proposal
    其内部针对request和chaincode的相关数据进行了封装,然后用userContext的secure进行签名。最后调用clientUtils.sendPeersProposal(peers, signed_proposal, timeout)
  • 遍历peers的list,然后调用peer.sendProposal(proposal, timeout)
  • peer的构建和发起请求
  • SDK中Peer是继承了Remote类。

    Remote类主要就是两件事情:

  • 构造器针对grpc的各个参数进行配置,主要包括 ssl-target-name-override:如果server是tls开启的状态,而且hostname的名字和tls证书的CN域名不同,那么就可以在这里指定CN的那个hostname。而且,这个选项更改了grpc的两个属性:
  • grpc.ssl_target_name_override
  • grpc.default_authority这个参数就是针对server的证书进行验证。如果hostname和证书的签名是一致的,则这个参数并不需要。 pemserver的tls证书内容。
  • 还有一些其他的grpc设置
  • grpc.max_receive_message_length
  • grpc.max_send_message_length
  • request-timeout配置一个grpc的request超时时间
  • 构建内置类-Endpoint
  • 这个类非常重要,它是构建grpc对象的核心。主要就是针对url判断protocol,如果是grpcs则会使用this.creds = grpc.credentials.createSsl(pembuf)构建一个ssl的通道;如果是grpc则使用grpc.credentials.createInsecure()构建通道。
  • Peer发起的请求sendProposal,直接调用grpc的方法等待response:self._endorserClient.processProposal(proposal, function(err, proposalResponse){}
    这里要注意的是,其调用的grpc的simple RPC方法,发送一个请求,并且等待请求的response。不是array也不是stream的形式。
    查看了下grpc的service的定义,果然,直接发送object:

    service Endorser {
        rpc ProcessProposal(SignedProposal) returns (ProposalResponse) {}
    
  • node Grpc调用
  • src中核心组件就是client.jscredentials.js。前者负责请求调用,后者负责channel的创建接口(前面提到的两个创建通道的方法定义于此)。我们重点看client.js。我们逐个函数(精力有限,先分析用到的)的分析:
    (有一些需要后续补充的,EventEmitter-event调用,stream-流)
    * createStatusError:如果grpc返回的数据有error,则通过该函数解析,返回error
    * ClientUnaryCall绑定一个event-EventEmitter。
    * _readsDone当server发送消息完成之后,client会调用方法,并确认状态。默认为ok
    * _receiveStatus当从server收到任何status信息时候,调用。
    * Client(address, credentials, options)构造函数,创建一个channel。在创建一个grpc的client的时候就调用了。
    * makeClientConstructor在后面有一个导出函数,来具体根据需求创建不同类型的Client,其中request的类型
    * Client.prototype.makeUnaryRequest普通Grpc调用,创建请求。可以给出序列化和反序列化的方法,以及一些参数和回调函数。
    * getCall(channel, method, options):这里面有些参数设置,然后将一个call返回-new grpc.Call(channel, method, deadline, host,parent, propagate_flags);
    * hostname:server ip
    * deadline:这个connection的timeout
    * credential:如果这个client时grpcs(也就是含有cred),就会在call中将其进行设置-call.setCredentials(credentials)
    同时,它有三个参数:
    * channel:就是实例化一个client的时候创建的channel。
    * method:grpc方法。这里是/protos.Endorser/ProcessProposal
    * options:一些参数:包括hostname、deadline和credential等。
    找一下这个options的来源,其来源于makeUnaryRequest

    首先,其会调用makeUnaryRequest,check各种参数。
    然后,调用`var call = getCall(this.$channel, method, options);`获取需要的rpc方法。然后创建一个emitter-`new ClientUnaryCall(call)`。
    之后,组装一个client_batch,call并将其发送给server,等待response。同时收到response后使用emitter将response的消息填充进metadata。
    

    打了一圈log,在getCall中发现:

    Hostname undefined
    deadline Infinity
    parent undefined
    credentials undefined
    Init unary Call
    ClientUnaryCall {
      domain: null,
      _events: {},
      _eventsCount: 0,
      _maxListeners: undefined,
      call: Call { channel_: Channel {} } }
    

    为毛线都是undefined,貌似发现了问题。
    追踪之后发现其来源于makeUnaryRequest的参数,回头check一下校验的代码逻辑。
    首先,打印了这几个参数发现:

  • options:undefined,不应该。
  • metadata :在调用service的时候callback的function。
  • callback:undefined
  • argument:proposal的data
    这边是校验的逻辑:
  • if (options instanceof Function) {
        callback = options;
        if (metadata instanceof Metadata) {
          options = {};
        } else {
          options = metadata;
          metadata = new Metadata();
      } else if (metadata instanceof Function) {
        callback = metadata;
        metadata = new Metadata();
        options = {};
      if (!metadata) {
        metadata = new Metadata();
      if (!options) {
        options = {};
      if (!((metadata instanceof Metadata) &&
        (options instanceof Object) &&
        (callback instanceof Function))) {
        throw new Error("Argument mismatch in makeUnaryRequest");
    

    按照获取的数据来看,其走到了第二个分支:如果metadata是一个function,则callback赋值,options为空对象。

    经过查看发现,Channel中应该包含了addr和credential的相关信息。所以在options的时候就取消了。这里可以继续digging。

    Tips:一直忘记开启grpc的详细日志,在运行node的程序中使用该环境变量---GRPC_TRACE=allGRPC_VERBOSITY=DEBUG(因为这个是给C++内核用的,所以应该用export)
    打开之后,发现有个问题:

    Cannot check peer: missing selected ALPN property
    

    貌似是有关ALPN的错误。server和client并不同时支持ALPN。

    这里提一点,就是orderer的sendDeliver是用的stream,而不是普通GRPC。

    openssl 1.0.2以上的版本支持了ALPN。
    这个问题是client发起的ssl握手,然后服务端并没有将其APLN或者是NPN的版本发给客户端。
    然后,这里提到,我们用GO的sdk(或者是peer的cli)进行调用,就能够连接,并且功能执行正常。

    windows & linux

    Grpc的ssl版本在windows和linux中使用的并不一样。
    windows使用的BoringSSL, Linux使用的是OpenSSL。BoringSSL有可能不能处理证书中domin为ip的情况(还未测试)。
    如果发生了一些SSL的错误,可以直接使用openssl或者bssl的命令行进行连接测试:

    bssl s_client -connect 127.0.0.1:9110
    openssl s_client -connect 127.0.0.1:9110 -showcerts
    

    Tips:

  • BoringSSL已经将所有的ECC算法移除,除了P-256和P-384。同时其还有一些bug。如果是在找不到原因可以去github上看看issue。

  • Grpc-node上build的时候有一个配置,如果该主机不支持ALPN就会rebuild项目排除ALPN的支持。

  • 'variables': { 'runtime%': 'node', # Some Node installations use the system installation of OpenSSL, and on # some systems, the system OpenSSL still does not have ALPN support. This # will let users recompile gRPC to work without ALPN. 'grpc_alpn%': 'true', # Indicates that the library should be built with gcov. 'grpc_gcov%': 'false', # Indicates that the library should be built with compatibility for musl # libc, so that it can run on Alpine Linux. This is only necessary if not # building on Alpine Linux 'grpc_alpine%': 'false'
  • node 程序运行时可以添加环境变量:process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0",强制取消对server证书的授权验证。
  • Grpc编译(无ALPN的版本)

    发现npm支持从源码进行安装的方法-grpc npm。所以我们选择先通过源码安装grpc然后在安装其他的组件。

  • 从github中获取源码。
  • git clone https://github.com/grpc/grpc.git
    

    时间会很久。

  • 更改grpc的源码
    参考之前提到的那个配置项。这里将其改为false
    'grpc_alpn%': 'false',
    

    3 npm 编译

    npm install grpc --build-from-source 
    

    但是在windows可能会出现问题:node-grpc build on windows。如果在一开始(步骤比较靠前的地方)出现该错误:

    Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch. WINDOWS_BUILD_WARNING "..\IMPORTANT: Due to https:\github.com\nodejs\node\issues\4932, to build this library on Windows, you must first remove C:\Users\jenkins\.node-gyp\4.4.0\include\node\openssl"

    解决方法,就是把node-gyp的openssl删掉(如果存在着会发现有冲突),具体地址为:C:\Users\<username>\.node-gyp\<node_version>\include\node\openssl
    详细的解决方案可以看另外一个博客。

    fabric go server端grpc

    (待补充)

    http2和http1

    HTTP/2(超文本传输协议第2版,最初命名为 HTTP 2.0),是HTTP协议的的第二个主要版本,使用于万维网。HTTP/2 是 HTTP 协议自 1999 年 HTTP 1.1 发布后的首个更新,主要基于 SPDY 协议。HTTP/2 标准于2015年5月以 RFC 7540 正式发表,HTTP/2协议规范 rfc

    为了实现 HTTP 工作组设定的性能目标,HTTP/2 引入了一个新的二进制分帧层,该层无法与之前的 HTTP/1.x 服务器和客户端向后兼容,因此协议的主版本提升到 HTTP/2。

    http/2的优点
  • 采用二进制格式传输数据,而非文本格式。二进制格式在协议的解析和优化扩展上带来更多的优势和可能。

  • 对消息头进行压缩传输,能够节省消息头占用的网络的流量,而 HTTP 1.1 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源,头压缩能够很好的解决该问题。

  • 多路复用,就是多个请求都是通过一个 TCP 连接并发完成, HTTP 1.1 虽然通过 pipeline 也能并发请求,但是多个请求之间的响应会被阻塞的,所以 pipeline 至今也没有被普及应用,而 HTTP/2 做到了真正的并发请求,同时流还支持优先级和流量控制。

  • 服务器推送,服务端能够更快的把资源推送给客户端,例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求,当客户端需要的时候,它已经在客户端了。

  • h2c 和h2

    h2c:HTTP/2协议,类型为clear text
    h2:HTTP/2协议(加密),例如构建在SSL之上

    其是谷歌开发的一种RPC协议。主要用于建立在跨语言调用、数据压缩的C/S链接上。gRPC是建立在HTTP/2之上进行连接的,不管是cleartext(h2c,未加密的数据)还是TLS-encrypted(h2)的数据。
    一个gRPC的call,其实是实现了一个HTTP的POST请求,对body的数据进行了高效的编码(当然,肯定离不开谷歌的protobuf)。同样gRPC的response也使用了同样的编码以及HTTP的数据规则(比如说status code等)。
    gRPC协议并不直接在HTTP/1.X之上传输。gRPC使用HTTP/2是为了能够支持多工(multiplexing)以及流式传输的特性(HTTP/2)。

    ALPN和NPN

    NPN: Next Protocol Negotiation
    ALPN:Application Layer Protocol Negotiation,ALPN wiki

    这两个都是TLS的扩展组件。因为 https, SPDY and HTTP/2协议都直接连通了443端口,所以ALPN和NPN让应用层协议能够让应用层协议(plain http/1.1, SPDY or HTTP/2)转化,连通构建在SSL/TLS加密链接上的client和server。

    SPDY使用NPN进行转化,HTTP2使用ALPN进行转化。其是建立在SSL/TLS的握手协议流程之上的。
    NPN和ALPN都是在SSL/TLS建立链接中进行干预。ALPN会将client支持的应用层协议放在hello message中让server选择一个协议来建立安全链接。NPN则是server列举,client进行选择。

    我们可以通过该网站HTTP/2 Test查看浏览器针对各个协议的支持情况。
    我们也可以通过该命令行来查看是否支持APLN。

    echo | openssl s_client -alpn h2 -connect yourdomain.com:443 | grep ALPN
    //check the openssl verison
    openssl verison
    

    Tips: openssl版本一定要在1.0.2及其以上。

    Nginx支持HTTP/2 (ALPN)

    在Nginx上开启 HTTP/2 需要 Nginx 1.9.5 (或者是Nginx Plus R7)以上版本,并且需要 OpenSSL 版本在 1.0.2 以上。
    因为 HTTP/2 不仅需要Web服务器还需要一个扩展支持,目前可以用的有 ALPN 和 NPN 两种(Chrome 已经移除了对 NPN 的支持)。只有 OpenSSL 1.0.2 以上版本才开始支持 ALPN 。
    如果系统版本不支持或者openssl过低,则需要下载openssl的高版本source code,然后使用--with-openssl显示的指定openssl library的源码位置,然后rebuild整个Nginx项目。
    各个操作系统版本针对openssl以及ALPN的支持情况:

    image.png

    指的注意的是,nginx的一个端口不能绑定多个协议类型,比如说HTTP/1(文本)和ClearText类型的HTTP/2(二进制)绑定在同一个端口。建议如果针对clearText类型的数据,针对不同的协议版本绑定不同的监听端口。因为nginx需要实现设置该端口支持哪一个版本的协议。
    针对gRPC,它主要使用HTTP/2当做传输层来使用。
    所以当使用nginx来处理普通数据时,一定要小心,其可能有很多种情况。
    有三种方法可以让一个HTTP server知道这个请求是http/2:

  • 使用HTTP(原始文本)进行HTTP/2的升级
  • 使用HTTPS(加密数据),然后利用ALPN或者NPN转化为TLS建立安全连接。然后进行HTTP的消息传输。
  • 使用HTTP的原始文本,但是构建HTTP/2的链接(双方直接商议链接方案,事先约定好),直接使用HTTP/2。
  • nginx第一种不支持,并没有一种能使用HTTP/2链接来进行HTTP/1.1普通文本的数据传输(自动转化),除非事先声明,直接建立HTTP/2的链接。不能主动探测(自动识别,并使用HTTP/2进行连接)。当然第二种方案,例如GRPC的实现,也是可以的。GRPC会清楚的知道这个连接是否需要使用TLS并构建彼此的链接,也就是是否使用HTTP/2的协议。
    但是GRPC并不是真正的HTTP。他只是使用了HTTP/2的the binary framing layer,构建一个流控制的、多通道的链接,来进行gRPC的消息的传输。它和Websocket实现HTTP TCP的链接来传输消息是一样的,但是其并不是HTTP,只是用了HTTP的语法定义(规则或者说协议规则)。让他们看起来像是HTTP协议的数据。

    Tips:Nginx利用HTTP server监听gRPC的请求,同时使用grpc_pass来进行分发代理。

  • GRPC node
  • nginx with grpc
  • grpc-node for self-signed
  • grpcs ssl failed
  • node client to go server
  • node client to go server
  • windows and linux, BoringSSL in Grpc
  • fabric sdk guide for tls enabled
  • nginx grpc module
  • grpc status unstable
  • java grpc error 1
  • java grpc error 2
  • Java Grpc ALPN error
  • Support https with nginx
  • openssl howto
  • Libssl wiki
  • nodejs TLS create
  • http1.1, SPDY, http2
  • NPN and ALPN
  • TLS/SSL握手协议
  • Golang net http
  • OpenSSL到BoringSSL的移植
  •