Skip to content

Commit

Permalink
[fix] grpc and http2 frames are mixed in a response
Browse files Browse the repository at this point in the history
  • Loading branch information
funa-tk committed Feb 4, 2020
1 parent 2788de9 commit 40188ab
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 51 deletions.
9 changes: 7 additions & 2 deletions src/main/java/core/packetproxy/encode/EncodeHTTPBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import java.io.InputStream;

import packetproxy.http.Http;
import packetproxy.http2.FramesBase;
import packetproxy.http2.Grpc;
import packetproxy.http2.Http2;
import packetproxy.model.Packet;

Expand All @@ -27,7 +29,7 @@ public enum HTTPVersion {
HTTP1, HTTP2
}
private HTTPVersion httpVersion;
private Http2 http2;
private FramesBase http2;

public EncodeHTTPBase() {
super("http/1.1");
Expand All @@ -40,9 +42,12 @@ public EncodeHTTPBase(String ALPN) throws Exception {
httpVersion = HTTPVersion.HTTP1;
} else if (ALPN.equals("http/1.0") || ALPN.equals("http/1.1")) {
httpVersion = HTTPVersion.HTTP1;
} else if (ALPN.equals("h2") || ALPN.startsWith("grpc")) {
} else if (ALPN.equals("h2")) {
httpVersion = HTTPVersion.HTTP2;
http2 = new Http2();
} else if (ALPN.equals("grpc") || ALPN.equals("grpc-exp")) {
httpVersion = HTTPVersion.HTTP2;
http2 = new Grpc();
} else {
httpVersion = HTTPVersion.HTTP1;
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/core/packetproxy/http2/FramesBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import packetproxy.http2.frames.Frame;
import packetproxy.http2.frames.FrameUtils;
import packetproxy.model.Packet;

public abstract class FramesBase
{
Expand Down Expand Up @@ -139,4 +140,5 @@ public InputStream getServerFlowControlledInputStream() {
protected abstract byte[] decodeServerResponseFromFrames(byte[] frames) throws Exception;
protected abstract byte[] encodeClientRequestToFrames(byte[] data) throws Exception;
protected abstract byte[] encodeServerResponseToFrames(byte[] data) throws Exception;
public abstract void setGroupId(Packet packet) throws Exception;
}
176 changes: 176 additions & 0 deletions src/main/java/core/packetproxy/http2/Grpc.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Copyright 2019 DeNA Co., Ltd.
*
* Licensed 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 packetproxy.http2;

import java.io.ByteArrayOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http2.hpack.HpackEncoder;

import packetproxy.common.UniqueID;
import packetproxy.http.HeaderField;
import packetproxy.http.Http;
import packetproxy.http2.frames.DataFrame;
import packetproxy.http2.frames.Frame;
import packetproxy.http2.frames.FrameUtils;
import packetproxy.http2.frames.HeadersFrame;
import packetproxy.model.Packet;

public class Grpc extends FramesBase
{
private StreamManager clientStreamManager = new StreamManager();
private StreamManager serverStreamManager = new StreamManager();

private static String[] GRPC_RESPONSE_2ND_HEADERS = new String[]{"grpc-status","trace-proto-bin"};

public Grpc() throws Exception {
super();
}

@Override
public String getName() {
return "gRPC";
}

@Override
protected byte[] passFramesToDecodeClientRequest(List<Frame> frames) throws Exception { return filterFrames(clientStreamManager, frames); }
@Override
protected byte[] passFramesToDecodeServerResponse(List<Frame> frames) throws Exception { return filterFrames(serverStreamManager, frames); }

private byte[] filterFrames(StreamManager streamManager, List<Frame> frames) throws Exception {
for (Frame frame : frames) {
if (frame instanceof HeadersFrame) {
streamManager.write(frame);
} else if (frame instanceof DataFrame) {
streamManager.write(frame);
}
if ((frame.getFlags() & 0x01) > 0) {
List<Frame> stream = streamManager.read(frame.getStreamId());
return FrameUtils.toByteArray(stream);
}
}
return null;
}

@Override
protected byte[] decodeClientRequestFromFrames(byte[] frames) throws Exception { return decodeFromFrames(frames); }
@Override
protected byte[] decodeServerResponseFromFrames(byte[] frames) throws Exception { return decodeFromFrames(frames); }

private byte[] decodeFromFrames(byte[] frames) throws Exception {
ByteArrayOutputStream outHeader = new ByteArrayOutputStream();
ByteArrayOutputStream outData = new ByteArrayOutputStream();
Http httpHeaderSums = null;

for (Frame frame : FrameUtils.parseFrames(frames)) {
if (frame instanceof HeadersFrame) {
HeadersFrame headersFrame = (HeadersFrame)frame;
Http http = new Http(headersFrame.getHttp());
if(null==httpHeaderSums){
httpHeaderSums = http;
}else{
for(HeaderField field: http.getHeader().getFields()){
httpHeaderSums.updateHeader(field.getName(), field.getValue());
}
}
} else if (frame instanceof DataFrame) {
DataFrame dataFrame = (DataFrame)frame;
outData.write(dataFrame.getPayload());
}
}
outHeader.write(httpHeaderSums.toByteArray());
//順序をHeader, Dataにする。本当はStreamIDで管理してencode時に元の順番に戻せるようにしたい。
//暫定でgRPC over HTTP2のレスポンスの2つめのヘッダはGRPC_RESPONSE_2ND_HEADERSに従って元の順番に戻す。
outData.writeTo(outHeader);
Http http = new Http(outHeader.toByteArray());
int flags = Integer.valueOf(http.getFirstHeader("X-PacketProxy-HTTP2-Flags"));
if (http.getBody() == null || http.getBody().length == 0) {
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
}
return http.toByteArray();
}

@Override
protected byte[] encodeClientRequestToFrames(byte[] http) throws Exception { return encodeToFrames(http, super.getClientHpackEncoder()); }
@Override
protected byte[] encodeServerResponseToFrames(byte[] http) throws Exception { return encodeToFrames(http, super.getServerHpackEncoder()); }

private byte[] encodeToFrames(byte[] data, HpackEncoder encoder) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Http http = new Http(data);
int flags = Integer.valueOf(http.getFirstHeader("X-PacketProxy-HTTP2-Flags"));
if (http.getBody() != null && http.getBody().length > 0) {
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff & ~HeadersFrame.FLAG_END_STREAM));
HttpFields GRPC2ndHeaderHttpFields = new HttpFields();
for(String k:GRPC_RESPONSE_2ND_HEADERS){
String v = http.getFirstHeader(k);
http.removeHeader(k);
if("".equals(v)) continue;
GRPC2ndHeaderHttpFields.add(k, v);
}

HeadersFrame headersFrame = new HeadersFrame(http);
out.write(headersFrame.toByteArrayWithoutExtra(encoder));

DataFrame dataFrame = new DataFrame(http);
if(GRPC2ndHeaderHttpFields.size()>0){
dataFrame.setFlags(dataFrame.getFlags() & 0xff & ~DataFrame.FLAG_END_STREAM);
}
out.write(dataFrame.toByteArrayWithoutExtra());

if(GRPC2ndHeaderHttpFields.size()>0) {
Http althttp = http;
althttp.setBody(new byte[0]);
althttp.removeMatches("^(?!X-PacketProxy-HTTP2).*$");

for (HttpField headerField : GRPC2ndHeaderHttpFields) {
althttp.updateHeader(headerField.getName(), headerField.getValue());
}
althttp.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
HeadersFrame headers2ndFrame = new HeadersFrame(althttp);
out.write(headers2ndFrame.toByteArrayWithoutExtra(encoder));
}
} else {
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
HeadersFrame headersFrame = new HeadersFrame(http);
out.write(headersFrame.toByteArrayWithoutExtra(encoder));
}
return out.toByteArray();
}

/* key: streamId, value: groupId */
private Map<Long,Long> groupMap = new HashMap<>();

public void setGroupId(Packet packet) throws Exception {
byte[] data = (packet.getDecodedData().length > 0) ? packet.getDecodedData() : packet.getModifiedData();
Http http = new Http(data);
String streamIdStr = http.getFirstHeader("X-PacketProxy-HTTP2-Stream-Id");
if (streamIdStr != null && streamIdStr.length() > 0) {
long streamId = Long.parseLong(streamIdStr);
if (groupMap.containsKey(streamId)) {
packet.setGroup(groupMap.get(streamId));
} else {
long groupId = UniqueID.getInstance().createId();
groupMap.put(streamId, groupId);
packet.setGroup(groupId);
}
}
}
}
53 changes: 4 additions & 49 deletions src/main/java/core/packetproxy/http2/Http2.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,10 @@
import java.util.List;
import java.util.Map;

import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http2.hpack.HpackEncoder;

import packetproxy.common.UniqueID;
import packetproxy.http.HeaderField;
import packetproxy.http.Http;
import packetproxy.http.HttpHeader;
import packetproxy.http2.frames.DataFrame;
import packetproxy.http2.frames.Frame;
import packetproxy.http2.frames.FrameUtils;
Expand All @@ -39,8 +35,6 @@ public class Http2 extends FramesBase
private StreamManager clientStreamManager = new StreamManager();
private StreamManager serverStreamManager = new StreamManager();

private static String[] GRPC_RESPONSE_2ND_HEADERS = new String[]{"grpc-status","trace-proto-bin"};

public Http2() throws Exception {
super();
}
Expand Down Expand Up @@ -76,31 +70,17 @@ private byte[] filterFrames(StreamManager streamManager, List<Frame> frames) thr
protected byte[] decodeServerResponseFromFrames(byte[] frames) throws Exception { return decodeFromFrames(frames); }

private byte[] decodeFromFrames(byte[] frames) throws Exception {
ByteArrayOutputStream outHeader = new ByteArrayOutputStream();
ByteArrayOutputStream outData = new ByteArrayOutputStream();
Http httpHeaderSums = null;

ByteArrayOutputStream out = new ByteArrayOutputStream();
for (Frame frame : FrameUtils.parseFrames(frames)) {
if (frame instanceof HeadersFrame) {
HeadersFrame headersFrame = (HeadersFrame)frame;
Http http = new Http(headersFrame.getHttp());
if(null==httpHeaderSums){
httpHeaderSums = http;
}else{
for(HeaderField field: http.getHeader().getFields()){
httpHeaderSums.updateHeader(field.getName(), field.getValue());
}
}
out.write(headersFrame.getHttp());
} else if (frame instanceof DataFrame) {
DataFrame dataFrame = (DataFrame)frame;
outData.write(dataFrame.getPayload());
out.write(dataFrame.getPayload());
}
}
outHeader.write(httpHeaderSums.toByteArray());
//順序をHeader, Dataにする。本当はStreamIDで管理してencode時に元の順番に戻せるようにしたい。
//暫定でgRPC over HTTP2のレスポンスの2つめのヘッダはGRPC_RESPONSE_2ND_HEADERSに従って元の順番に戻す。
outData.writeTo(outHeader);
Http http = new Http(outHeader.toByteArray());
Http http = new Http(out.toByteArray());
int flags = Integer.valueOf(http.getFirstHeader("X-PacketProxy-HTTP2-Flags"));
if (http.getBody() == null || http.getBody().length == 0) {
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
Expand All @@ -119,35 +99,10 @@ private byte[] encodeToFrames(byte[] data, HpackEncoder encoder) throws Exceptio
int flags = Integer.valueOf(http.getFirstHeader("X-PacketProxy-HTTP2-Flags"));
if (http.getBody() != null && http.getBody().length > 0) {
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff & ~HeadersFrame.FLAG_END_STREAM));
HttpFields GRPC2ndHeaderHttpFields = new HttpFields();
for(String k:GRPC_RESPONSE_2ND_HEADERS){
String v = http.getFirstHeader(k);
http.removeHeader(k);
if("".equals(v)) continue;
GRPC2ndHeaderHttpFields.add(k, v);
}

HeadersFrame headersFrame = new HeadersFrame(http);
out.write(headersFrame.toByteArrayWithoutExtra(encoder));

DataFrame dataFrame = new DataFrame(http);
if(GRPC2ndHeaderHttpFields.size()>0){
dataFrame.setFlags(dataFrame.getFlags() & 0xff & ~DataFrame.FLAG_END_STREAM);
}
out.write(dataFrame.toByteArrayWithoutExtra());

if(GRPC2ndHeaderHttpFields.size()>0) {
Http althttp = http;
althttp.setBody(new byte[0]);
althttp.removeMatches("^(?!X-PacketProxy-HTTP2).*$");

for (HttpField headerField : GRPC2ndHeaderHttpFields) {
althttp.updateHeader(headerField.getName(), headerField.getValue());
}
althttp.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
HeadersFrame headers2ndFrame = new HeadersFrame(althttp);
out.write(headers2ndFrame.toByteArrayWithoutExtra(encoder));
}
} else {
http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff | HeadersFrame.FLAG_END_STREAM));
HeadersFrame headersFrame = new HeadersFrame(http);
Expand Down

0 comments on commit 40188ab

Please sign in to comment.