You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@skywalking.apache.org by li...@apache.org on 2022/09/07 12:07:13 UTC

[skywalking-rover] branch main updated: Add HTTP2 protocol identify (#51)

This is an automated email from the ASF dual-hosted git repository.

liuhan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-rover.git


The following commit(s) were added to refs/heads/main by this push:
     new 404e93f  Add HTTP2 protocol identify (#51)
404e93f is described below

commit 404e93fbdcce108df205472ce2da7301cd602e72
Author: mrproliu <74...@qq.com>
AuthorDate: Wed Sep 7 20:07:08 2022 +0800

    Add HTTP2 protocol identify (#51)
---
 .github/workflows/rover.yaml                       |  2 +
 bpf/profiling/network/protocol_analyze.h           | 81 ++++++++++++++++++
 docs/en/setup/configuration/profiling.md           | 13 +--
 test/e2e/cases/profiling/network/base/nginx.conf   | 14 ++++
 .../network/{base/nginx.conf => http2/Dockerfile}  | 37 +++++----
 .../profiling/network/http2/docker-compose.yml     | 64 ++++++++++++++
 test/e2e/cases/profiling/network/http2/e2e.yaml    | 45 ++++++++++
 test/e2e/cases/profiling/network/http2/go.mod      | 33 ++++++++
 test/e2e/cases/profiling/network/http2/grpc.go     | 97 ++++++++++++++++++++++
 .../cases/profiling/network/http2/service.proto    | 36 ++++++++
 10 files changed, 399 insertions(+), 23 deletions(-)

diff --git a/.github/workflows/rover.yaml b/.github/workflows/rover.yaml
index 72f1a43..6011c4c 100644
--- a/.github/workflows/rover.yaml
+++ b/.github/workflows/rover.yaml
@@ -163,6 +163,8 @@ jobs:
             config: test/e2e/cases/profiling/network/c_plus_plus/e2e.yaml
           - name: Nodejs Profiling
             config: test/e2e/cases/profiling/network/nodejs/e2e.yaml
+          - name: HTTP2 Profiling
+            config: test/e2e/cases/profiling/network/http2/e2e.yaml
     steps:
       - uses: actions/checkout@v2
         with:
diff --git a/bpf/profiling/network/protocol_analyze.h b/bpf/profiling/network/protocol_analyze.h
index 16f122c..6f7e2c2 100644
--- a/bpf/profiling/network/protocol_analyze.h
+++ b/bpf/profiling/network/protocol_analyze.h
@@ -111,6 +111,85 @@ static __inline __u32 infer_http_message(const char* buf, size_t count) {
     return kUnknown;
 }
 
+// frame format: https://www.rfc-editor.org/rfc/rfc7540.html#section-4.1
+static __inline __u32 infer_http2_message(const char* buf_src, size_t count) {
+static const uint8_t kFrameBasicSize = 0x9; // including Length, Type, Flags, Reserved, Stream Identity
+static const uint8_t kFrameTypeHeader = 0x1; // the type of the frame: https://www.rfc-editor.org/rfc/rfc7540.html#section-6.2
+static const uint8_t kFrameLoopCount = 5;
+
+static const uint8_t kStaticTableMaxSize = 61;// https://www.rfc-editor.org/rfc/rfc7541#appendix-A
+static const uint8_t kStaticTableAuth = 1;
+static const uint8_t kStaticTableGet = 2;
+static const uint8_t kStaticTablePost = 3;
+static const uint8_t kStaticTablePath1 = 4;
+static const uint8_t kStaticTablePath2 = 5;
+
+    // the buffer size must bigger than basic frame size
+    if (count < kFrameBasicSize) {
+		return kUnknown;
+    }
+
+    // frame info
+    __u8 frame[21] = { 0 };
+    __u32 frameOffset = 0;
+    // header info
+    __u8 staticInx, headerBlockFragmentOffset;
+
+    // each all frame
+#pragma unroll
+    for (__u8 i = 0; i < kFrameLoopCount; i++) {
+        if (frameOffset >= count) {
+            break;
+        }
+
+        // read frame
+        bpf_probe_read(frame, sizeof(frame), buf_src + frameOffset);
+        frameOffset += (bpf_ntohl(*(__u32 *) frame) >> 8) + kFrameBasicSize;
+
+        // is header frame
+        if (frame[3] != kFrameTypeHeader) {
+            continue;
+        }
+
+        // validate the header(unset): not HTTP2 protocol
+        // this frame must is a send request
+        if ((frame[4] & 0xd2) || frame[5] & 0x01) {
+            return kUnknown;
+        }
+
+        // locate the header block fragment offset
+        headerBlockFragmentOffset = kFrameBasicSize;
+        if (frame[4] & 0x20) {  // PADDED flag is set
+            headerBlockFragmentOffset += 1;
+        }
+        if (frame[4] & 0x20) {  // PRIORITY flag is set
+            headerBlockFragmentOffset += 5;
+        }
+
+#pragma unroll
+        for (__u8 j = 0; j <= kStaticTablePath2; j++) {
+            if (headerBlockFragmentOffset > count) {
+                return kUnknown;
+            }
+            staticInx = frame[headerBlockFragmentOffset] & 0x7f;
+            if (staticInx <= kStaticTableMaxSize && staticInx > 0) {
+                if (staticInx == kStaticTableAuth ||
+                    staticInx == kStaticTableGet ||
+                    staticInx == kStaticTablePost ||
+                    staticInx == kStaticTablePath1 ||
+                    staticInx == kStaticTablePath2) {
+                    return kRequest;
+                } else {
+                    return kResponse;
+                }
+            }
+            headerBlockFragmentOffset++;
+        }
+    }
+
+	return kUnknown;
+}
+
 // Cassandra frame:
 //      0         8        16        24        32         40
 //      +---------+---------+---------+---------+---------+
@@ -678,6 +757,8 @@ static __inline enum message_type_t analyze_protocol(char *buf, __u32 count, str
     // PROTOCOL_LIST: Requires update on new protocols.
     if ((inferred_message.type = infer_http_message(buf, count)) != kUnknown) {
         inferred_message.protocol = kProtocolHTTP;
+    } else if ((inferred_message.type = infer_http2_message(buf, count)) != kUnknown) {
+        inferred_message.protocol = kProtocolHTTP2;
     } else if ((inferred_message.type = infer_cql_message(buf, count)) != kUnknown) {
         inferred_message.protocol = kProtocolCQL;
     } else if ((inferred_message.type = infer_mongo_message(buf, count)) != kUnknown) {
diff --git a/docs/en/setup/configuration/profiling.md b/docs/en/setup/configuration/profiling.md
index 78d6fa6..a63b92b 100644
--- a/docs/en/setup/configuration/profiling.md
+++ b/docs/en/setup/configuration/profiling.md
@@ -32,12 +32,13 @@ Off CPU Profiling task is attach the `finish_task_switch` in `krobe` to profilin
 Network Profiling task is intercept IO-related syscall and `urprobe` in process to identify the network traffic and generate the metrics.
 Also, the following protocol are supported for analyzing using OpenSSL library, BoringSSL library, GoTLS, NodeTLS or plaintext:
 
-1. HTTP
-2. MySQL
-3. CQL(The Cassandra Query Language)
-4. MongoDB
-5. Kafka
-6. DNS
+1. HTTP/1.x
+2. HTTP/2
+3. MySQL
+4. CQL(The Cassandra Query Language)
+5. MongoDB
+6. Kafka
+7. DNS
 
 #### Metrics
 
diff --git a/test/e2e/cases/profiling/network/base/nginx.conf b/test/e2e/cases/profiling/network/base/nginx.conf
index 2de1521..57acaf5 100644
--- a/test/e2e/cases/profiling/network/base/nginx.conf
+++ b/test/e2e/cases/profiling/network/base/nginx.conf
@@ -32,4 +32,18 @@ http {
 		    proxy_http_version 1.1;
 		}
 	}
+
+    server {
+        listen       9000 ssl http2;
+        server_name  proxy;
+        include       mime.types;
+        default_type  application/octet-stream;
+
+        ssl_certificate /ssl_data/proxy.crt;
+        ssl_certificate_key /ssl_data/proxy.key;
+
+        location / {
+            grpc_pass grpcs://service:9000;
+        }
+    }
 }
\ No newline at end of file
diff --git a/test/e2e/cases/profiling/network/base/nginx.conf b/test/e2e/cases/profiling/network/http2/Dockerfile
similarity index 53%
copy from test/e2e/cases/profiling/network/base/nginx.conf
copy to test/e2e/cases/profiling/network/http2/Dockerfile
index 2de1521..b72b7b3 100644
--- a/test/e2e/cases/profiling/network/base/nginx.conf
+++ b/test/e2e/cases/profiling/network/http2/Dockerfile
@@ -14,22 +14,25 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-user nginx;
-worker_processes  auto;
-events {
-	worker_connections  102400;
-}
-http {
-	server {
-		listen       443 ssl;
-		server_name proxy;
+FROM golang:1.17
 
-		ssl_certificate /ssl_data/proxy.crt;
-		ssl_certificate_key /ssl_data/proxy.key;
+COPY http2/ /service
 
-		location /provider {
-		    proxy_pass https://service:10443/provider;
-		    proxy_http_version 1.1;
-		}
-	}
-}
\ No newline at end of file
+WORKDIR /
+RUN apt update && apt install -y zip && \
+    mkdir protoc && cd protoc && \
+    curl -sL https://github.com/protocolbuffers/protobuf/releases/download/v21.5/protoc-21.5-linux-x86_64.zip -o protoc.zip && \
+    unzip protoc.zip
+
+WORKDIR /service
+
+RUN go get -u google.golang.org/protobuf/cmd/protoc-gen-go@v1.26.0 && \
+    go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0 && \
+    go get google.golang.org/grpc/internal/transport@v1.44.0 && \
+    /protoc/bin/protoc --go_out=. --go-grpc_out=. service.proto && \
+    go build .
+
+COPY base/ssl /usr/local/share/ca-certificates
+RUN update-ca-certificates
+
+CMD ["/service/test"]
diff --git a/test/e2e/cases/profiling/network/http2/docker-compose.yml b/test/e2e/cases/profiling/network/http2/docker-compose.yml
new file mode 100644
index 0000000..6b94443
--- /dev/null
+++ b/test/e2e/cases/profiling/network/http2/docker-compose.yml
@@ -0,0 +1,64 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+version: '2.1'
+
+services:
+  service:
+    build:
+      context: ../
+      dockerfile: http2/Dockerfile
+    volumes:
+      - ./../base/ssl:/ssl_data
+    networks:
+      - e2e
+    ports:
+      - 8080:8080
+    healthcheck:
+      test: [ "CMD", "bash", "-c", "cat < /dev/null > /dev/tcp/127.0.0.1/8080" ]
+      interval: 5s
+      timeout: 60s
+      retries: 120
+
+  proxy:
+    extends:
+      file: ../base/docker-compose.yml
+      service: proxy
+    networks:
+      - e2e
+    depends_on:
+      service:
+        condition: service_healthy
+
+  oap:
+    extends:
+      file: ../base/docker-compose.yml
+      service: oap
+    ports:
+      - 12800:12800
+
+  rover:
+    extends:
+      file: ../base/docker-compose.yml
+      service: rover
+    environment:
+      ROVER_PROCESS_DISCOVERY_REGEX_SCANNER_MATCH_CMD2: service/test
+      ROVER_PROCESS_DISCOVERY_REGEX_SCANNER_PROCESS_NAME2: service
+    depends_on:
+      oap:
+        condition: service_healthy
+
+networks:
+  e2e:
\ No newline at end of file
diff --git a/test/e2e/cases/profiling/network/http2/e2e.yaml b/test/e2e/cases/profiling/network/http2/e2e.yaml
new file mode 100644
index 0000000..b6e80b6
--- /dev/null
+++ b/test/e2e/cases/profiling/network/http2/e2e.yaml
@@ -0,0 +1,45 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+setup:
+  env: compose
+  file: docker-compose.yml
+  timeout: 20m
+  init-system-environment: ../../../../base/env
+  steps:
+    - name: set PATH
+      command: export PATH=/tmp/skywalking-infra-e2e/bin:$PATH
+    - name: install yq
+      command: bash test/e2e/base/scripts/prepare/setup-e2e-shell/install.sh yq
+    - name: install swctl
+      command: bash test/e2e/base/scripts/prepare/setup-e2e-shell/install.sh swctl
+
+trigger:
+  action: http
+  interval: 3s
+  times: 10
+  url: http://${service_host}:${service_8080}/singleCall
+  method: GET
+
+verify:
+  # verify with retry strategy
+  retry:
+    # max retry count
+    count: 20
+    # the interval between two retries, in millisecond.
+    interval: 10s
+  cases:
+    - includes:
+        - ../network-cases.yaml
\ No newline at end of file
diff --git a/test/e2e/cases/profiling/network/http2/go.mod b/test/e2e/cases/profiling/network/http2/go.mod
new file mode 100644
index 0000000..d49a293
--- /dev/null
+++ b/test/e2e/cases/profiling/network/http2/go.mod
@@ -0,0 +1,33 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+module test
+
+go 1.17
+
+require (
+	google.golang.org/grpc v1.44.0
+	google.golang.org/protobuf v1.25.0
+)
+
+require (
+	github.com/golang/protobuf v1.4.3 // indirect
+	golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
+	golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
+	golang.org/x/text v0.3.0 // indirect
+	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
+)
diff --git a/test/e2e/cases/profiling/network/http2/grpc.go b/test/e2e/cases/profiling/network/http2/grpc.go
new file mode 100644
index 0000000..f6e0237
--- /dev/null
+++ b/test/e2e/cases/profiling/network/http2/grpc.go
@@ -0,0 +1,97 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package main
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"net"
+	"net/http"
+	"test/service"
+
+	"google.golang.org/grpc/credentials"
+
+	"google.golang.org/grpc"
+)
+
+var (
+	httpPort = 8080
+	gRPCPort = 9000
+	gRPCConn *grpc.ClientConn
+)
+
+type Provider struct {
+	service.UnimplementedServiceServer
+}
+
+func singleCall(w http.ResponseWriter, req *http.Request) {
+	log.Printf("receive the single call request")
+	if gRPCConn == nil {
+		c, err := credentials.NewClientTLSFromFile("/ssl_data/proxy.crt", "proxy")
+		if err != nil {
+			log.Fatalf("credentials.NewClientTLSFromFile err: %v", err)
+		}
+
+		dial, err := grpc.Dial(fmt.Sprintf("proxy:%d", gRPCPort), grpc.WithTransportCredentials(c))
+		if err != nil {
+			log.Printf("init gRPC client failure: %v", err)
+			_, _ = w.Write([]byte("error"))
+			return
+		}
+		gRPCConn = dial
+	}
+
+	client := service.NewServiceClient(gRPCConn)
+	resp, err := client.SingleCall(context.Background(), &service.CallRequest{})
+	if err != nil {
+		log.Printf("send single call request failure: %v", err)
+		_, _ = w.Write([]byte("error"))
+		return
+	}
+
+	w.Header().Set("Content-Type", "text/plain")
+	_, _ = w.Write([]byte(resp.Message))
+}
+
+func (p *Provider) SingleCall(context.Context, *service.CallRequest) (*service.CallReply, error) {
+	return &service.CallReply{Message: "response success"}, nil
+}
+
+func main() {
+	c, err := credentials.NewServerTLSFromFile("/ssl_data/service.crt", "/ssl_data/service.key")
+	if err != nil {
+		log.Fatalf("credentials.NewServerTLSFromFile err: %v", err)
+	}
+	server := grpc.NewServer(grpc.Creds(c))
+	service.RegisterServiceServer(server, &Provider{})
+	listen, err := net.Listen("tcp", fmt.Sprintf(":%d", gRPCPort))
+	if err != nil {
+		log.Fatalf("listen gRPC port failure: %v", err)
+		return
+	}
+	go func() {
+		if err := server.Serve(listen); err != nil {
+			log.Fatalf("startup gRPC server failure")
+		}
+	}()
+
+	http.HandleFunc("/singleCall", singleCall)
+	err1 := http.ListenAndServe(fmt.Sprintf(":%d", httpPort), nil)
+	log.Fatal(err1)
+}
diff --git a/test/e2e/cases/profiling/network/http2/service.proto b/test/e2e/cases/profiling/network/http2/service.proto
new file mode 100644
index 0000000..88ccc64
--- /dev/null
+++ b/test/e2e/cases/profiling/network/http2/service.proto
@@ -0,0 +1,36 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+syntax = "proto3";
+
+option go_package="./service;";
+
+// The greeting service definition.
+service Service {
+  // Sends a greeting
+  rpc SingleCall(CallRequest) returns (CallReply) {}
+}
+
+// The request message containing the user's name.
+message CallRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings
+message CallReply {
+  string message = 1;
+}
\ No newline at end of file