From 24785751c42f00e2e1a6a7fb7268302a7dbafc96 Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Thu, 8 Aug 2019 21:05:10 +0800 Subject: [PATCH 01/55] update summary docs in Chinese, fix #3. --- docs/zh_cn/SUMMARY.md | 60 +++++++++---------- .../{mod_write.md => mod_rewrite.md} | 0 2 files changed, 30 insertions(+), 30 deletions(-) rename docs/zh_cn/configuration/mod_rewrite/{mod_write.md => mod_rewrite.md} (100%) diff --git a/docs/zh_cn/SUMMARY.md b/docs/zh_cn/SUMMARY.md index dc9964c65..f4d8c0948 100644 --- a/docs/zh_cn/SUMMARY.md +++ b/docs/zh_cn/SUMMARY.md @@ -14,46 +14,46 @@ * 开发参考 * [代码结构说明](development/source_code_layout.md) * 附录A: 配置说明 - * [核心配置](bfe_config/bfe.conf.md) + * [核心配置](configuration/bfe.conf.md) * 协议 - * [SSL/TLS](bfe_config/tls_conf/tls_rule_conf.data.md) - * [证书](bfe_config/tls_conf/server_cert_conf.data.md) - * [Session ticket key](bfe_config/tls_conf/session_ticket_key.data.md) + * [SSL/TLS](configuration/tls_conf/tls_rule_conf.data.md) + * [证书](configuration/tls_conf/server_cert_conf.data.md) + * [Session ticket key](configuration/tls_conf/session_ticket_key.data.md) * 路由 - * [域名规则](bfe_config/server_data_conf/host_rule.data.md) - * [VIP规则](bfe_config/server_data_conf/vip_rule.data.md) - * [路由规则](bfe_config/server_data_conf/route_rule.data.md) + * [域名规则](configuration/server_data_conf/host_rule.data.md) + * [VIP规则](configuration/server_data_conf/vip_rule.data.md) + * [路由规则](configuration/server_data_conf/route_rule.data.md) * 负载均衡 - * [子集群负载均衡](bfe_config/cluster_conf/gslb.data.md) - * [实例负载均衡](bfe_config/cluster_conf/cluster_table.data.md) + * [子集群负载均衡](configuration/cluster_conf/gslb.data.md) + * [实例负载均衡](configuration/cluster_conf/cluster_table.data.md) * 名字服务 - * [名字规则](bfe_config/server_data_conf/name_conf.data.md) + * [名字规则](configuration/server_data_conf/name_conf.data.md) * 扩展模块 - * [mod_block](bfe_config/mod_block/mod_block.md) - * [mod_header](bfe_config/mod_header/mod_header.md) - * [mod_redirect](bfe_config/mod_redirect/mod_redirect.md) - * [mod_rewrite](bfe_config/mod_rewrite/mod_rewrite.md) - * [mod_trust_clientip](bfe_config/mod_trust_clientip/mod_trust_clientip.md) + * [mod_block](configuration/mod_block/mod_block.md) + * [mod_header](configuration/mod_header/mod_header.md) + * [mod_redirect](configuration/mod_redirect/mod_redirect.md) + * [mod_rewrite](configuration/mod_rewrite/mod_rewrite.md) + * [mod_trust_clientip](configuration/mod_trust_clientip/mod_trust_clientip.md) * [附录B: 监控](monitor.md) * 协议 - * [SSL/TLS](bfe_monitor/tls_state.md) - * [HTTP](bfe_monitor/http_state.md) - * [HTTP2](bfe_monitor/http2_state.md) - * [SPDY](bfe_monitor/spdy_state.md) - * [WebSocket](bfe_monitor/websocket_state.md) - * [Stream](bfe_monitor/stream_state.md) + * [SSL/TLS](monitor/tls_state.md) + * [HTTP](monitor/http_state.md) + * [HTTP2](monitor/http2_state.md) + * [SPDY](monitor/spdy_state.md) + * [WebSocket](monitor/websocket_state.md) + * [Stream](monitor/stream_state.md) * 路由 - * [域名表](bfe_monitor/host_table_status.md) + * [域名表](monitor/host_table_status.md) * 负载均衡 - * [均衡详情](bfe_monitor/bal_table_status.md) - * [均衡错误](bfe_monitor/bal_state.md) + * [均衡详情](monitor/bal_table_status.md) + * [均衡错误](monitor/bal_state.md) * 反向代理 - * [转发状态](bfe_monitor/proxy_state.md) + * [转发状态](monitor/proxy_state.md) * 扩展模块 - * [module_status](bfe_monitor/module_status.md) - * [mod_block](bfe_monitor/mod_block.md) - * [mod_logid](bfe_monitor/mod_logid.md) - * [mod_trust_clientip](bfe_monitor/mod_trust_clientip.md) + * [module_status](monitor/module_status.md) + * [mod_block](monitor/mod_block.md) + * [mod_logid](monitor/mod_logid.md) + * [mod_trust_clientip](monitor/mod_trust_clientip.md) * 延迟 - * [延迟分布](bfe_monitor/proxy_XXX_delay.md) + * [延迟分布](monitor/proxy_XXX_delay.md) * 附录C: 条件原语 diff --git a/docs/zh_cn/configuration/mod_rewrite/mod_write.md b/docs/zh_cn/configuration/mod_rewrite/mod_rewrite.md similarity index 100% rename from docs/zh_cn/configuration/mod_rewrite/mod_write.md rename to docs/zh_cn/configuration/mod_rewrite/mod_rewrite.md From f1fceac51cd0ab04e4d2dbfc6c34514148fd9df6 Mon Sep 17 00:00:00 2001 From: yangsijie Date: Wed, 21 Aug 2019 17:30:26 +0800 Subject: [PATCH 02/55] add CONTRIBUTORS.md --- CONTRIBUTORS.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 CONTRIBUTORS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 000000000..2ce7cbeb9 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,14 @@ +* This is the list of people who have contributed code/doc to the BFE repository. +* Please keep the list sorted by **name**. + +| Name | Github Account | +| ---- | -------------- | +| Kaiyu Zheng | kaiyuzheng | +| Lu Guo | guolu60 | +| Miao Zhang | | +| Min Dai | daimin | +| Qingxin Yang | yangqingxin1993 | +| Sijie Yang | iyangsj | +| Wensi Yang | tianxinheihei | +| Yang Liu | dut-yangliu | + From 7a9feb54301679270c85b978cb1ea2536ebd0c22 Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Fri, 23 Aug 2019 10:59:59 +0800 Subject: [PATCH 03/55] update bfe.conf.md docs --- docs/zh_cn/configuration/bfe.conf.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/zh_cn/configuration/bfe.conf.md b/docs/zh_cn/configuration/bfe.conf.md index 3c0187c4c..37331a5c8 100644 --- a/docs/zh_cn/configuration/bfe.conf.md +++ b/docs/zh_cn/configuration/bfe.conf.md @@ -18,14 +18,14 @@ bfe.conf是BFE的核心配置。 | ClientWriteTimeout | Int | 写客户端超时时间,单位为秒 | | GracefulShutdownTimeout | Int | 优雅退出超时时间,单位为秒,最大300秒 | | KeepAliveEnabled | Bool | 与用户端连接是否启用HTTP KeepAlive | -| MaxHeaderBytes | Int | 请求头部的最大程度,单位为Bytes | +| MaxHeaderBytes | Int | 请求头部的最大长度,单位为Bytes | | MaxHeaderUriBytes | Int | 请求头部URI的最大长度,单位为Byte | | HostRuleConf | String | 租户域名表配置文件 | | VipRuleConf | String | 租户VIP表配置文件 | | RouteRuleConf | String | 转发规则配置文件 | | ClusterConf | String | 后端集群相关配置文件 | -| GslbConf | String | 集群级别负载均衡配置(GSLB) | -| ClusterTableConf | String | 子集群级别负载均衡配置文件 | +| GslbConf | String | 子集群级别负载均衡配置文件(GSLB) | +| ClusterTableConf | String | 实例级别负载均衡配置文件 | | NameConf | String | 名字与实例映射表配置文件 | | Modules | String | 启用的模块列表; 多个模块增加多个Modules即可 | | MonitorInterval | Int | monitor统计周期 | @@ -40,10 +40,10 @@ bfe.conf是BFE的核心配置。 | ---------------------- | ------ | ------------------------------------------------------------ | | ServerCertConf | String | 证书与密钥的配置文件 | | TlsRuleConf | String | TLS协议参数配置文件 | -| CipherSuites | String | 启用的加密套件列表。多个套件增加多个cipherSuites即可 | -| CurvePreferences | String | 启用的ECC椭圆曲线。 | +| CipherSuites | String | 启用的加密套件列表; 多个套件增加多个cipherSuites即可 | +| CurvePreferences | String | 启用的ECC椭圆曲线 | | EnableSslv2ClientHello | Bool | 针对SSLv3协议,启用对SSLv2格式ClientHello的兼容 | -| ClientCABaseDir | String | 客户端根CA证书目录。
注意:证书文件后缀约定必须是 ".crt" | +| ClientCABaseDir | String | 客户端根CA证书目录
注意:证书文件后缀约定必须是 ".crt" | ## TLS Session Cache相关配置 From 73f28205214a17ff005e7e7a638e175dafaa36ef Mon Sep 17 00:00:00 2001 From: yangsijie Date: Wed, 21 Aug 2019 17:30:26 +0800 Subject: [PATCH 04/55] add CONTRIBUTORS.md --- CONTRIBUTORS.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 CONTRIBUTORS.md diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 000000000..2ce7cbeb9 --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,14 @@ +* This is the list of people who have contributed code/doc to the BFE repository. +* Please keep the list sorted by **name**. + +| Name | Github Account | +| ---- | -------------- | +| Kaiyu Zheng | kaiyuzheng | +| Lu Guo | guolu60 | +| Miao Zhang | | +| Min Dai | daimin | +| Qingxin Yang | yangqingxin1993 | +| Sijie Yang | iyangsj | +| Wensi Yang | tianxinheihei | +| Yang Liu | dut-yangliu | + From 2ddf30e55a917a4e2ce4a3479b395cb6b748388f Mon Sep 17 00:00:00 2001 From: xiaofei0800 Date: Mon, 26 Aug 2019 10:43:07 +0000 Subject: [PATCH 05/55] fix ineffassign of handshake_client.go --- bfe_tls/handshake_client.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bfe_tls/handshake_client.go b/bfe_tls/handshake_client.go index b56881877..124ccd8a8 100644 --- a/bfe_tls/handshake_client.go +++ b/bfe_tls/handshake_client.go @@ -28,6 +28,7 @@ import ( "errors" "fmt" "io" + "math/big" "net" "strconv" ) @@ -431,8 +432,9 @@ func (hs *clientHandshakeState) doFullHandshake() error { switch key := c.config.Certificates[0].PrivateKey.(type) { case *ecdsa.PrivateKey: + var r, s *big.Int digest, _, hashId := hs.finishedHash.hashForClientCertificate(signatureECDSA) - r, s, err := ecdsa.Sign(c.config.rand(), key, digest) + r, s, err = ecdsa.Sign(c.config.rand(), key, digest) if err == nil { signed, err = asn1.Marshal(ecdsaSignature{r, s}) } From 93ed06f9dbcef04637cb12029a9a55c44f7edb1b Mon Sep 17 00:00:00 2001 From: xiaofei0800 Date: Wed, 28 Aug 2019 09:02:53 +0000 Subject: [PATCH 06/55] fix ineffassign of bfe_spdy/frame_test.go --- bfe_spdy/frame_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bfe_spdy/frame_test.go b/bfe_spdy/frame_test.go index a94c3ab11..1817fe355 100644 --- a/bfe_spdy/frame_test.go +++ b/bfe_spdy/frame_test.go @@ -358,6 +358,9 @@ func TestCreateParseHeadersFrameCompressionEnable(t *testing.T) { headersFrame.Headers = HeadersFixture framer, err := NewFramer(buffer, buffer) + if err != nil { + t.Fatal("NewFramer error:", err) + } if err := framer.WriteFrame(&headersFrame); err != nil { t.Fatal("WriteFrame with compression:", err) } From 8251f1092e50aee5da39634dcc16f1cad7645ea1 Mon Sep 17 00:00:00 2001 From: xiaofei0800 Date: Wed, 28 Aug 2019 09:18:12 +0000 Subject: [PATCH 07/55] fix ineffassign of bfe_balance/bal_gslb/bal_gslb.go --- bfe_balance/bal_gslb/bal_gslb.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bfe_balance/bal_gslb/bal_gslb.go b/bfe_balance/bal_gslb/bal_gslb.go index 2c250fe6d..1a17005fe 100644 --- a/bfe_balance/bal_gslb/bal_gslb.go +++ b/bfe_balance/bal_gslb/bal_gslb.go @@ -304,7 +304,7 @@ func (bal *BalanceGslb) Balance(req *bfe_basic.Request) (*bal_backend.BfeBackend var backend *bal_backend.BfeBackend var current *SubCluster var err error - balAlgor := bal_slb.WrrSmooth + var balAlgor int bal.lock.Lock() defer bal.lock.Unlock() From 864fa6988afb05226383a03c6568cc3cc3f97540 Mon Sep 17 00:00:00 2001 From: xiaofei0800 Date: Wed, 28 Aug 2019 09:31:30 +0000 Subject: [PATCH 08/55] fix ineffassign of bfe_util/hash_set/node_pool_test.go --- bfe_util/hash_set/node_pool_test.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/bfe_util/hash_set/node_pool_test.go b/bfe_util/hash_set/node_pool_test.go index 46eadd60b..a52930eb0 100644 --- a/bfe_util/hash_set/node_pool_test.go +++ b/bfe_util/hash_set/node_pool_test.go @@ -59,12 +59,11 @@ func TestAddItem(t *testing.T) { func TestDelItem(t *testing.T) { np := newNodePool(32, 32*32, false) - Item := []byte("keyForTest") - Item1 := []byte("keyForTest1") - node, err := np.add(-1, Item) - node, err = np.add(0, Item) - node, err = np.add(1, Item1) - _, _ = node, err + Item1 := []byte("ItemForTest1") + Item2 := []byte("ItemForTest2") + np.add(-1, Item1) + np.add(0, Item1) + np.add(1, Item2) if np.array[1].next != 0 { t.Error("1 should link to 0") } @@ -73,12 +72,12 @@ func TestDelItem(t *testing.T) { } // del at list - if np.del(1, Item) != 0 { + if np.del(1, Item1) != 0 { t.Error("del should return newhead") } // del head - if np.del(0, Item1) != 0 { + if np.del(0, Item2) != 0 { t.Error("del should return newhead") } } From 758ed9a2415d20056ee935c71355098278dc15fd Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Fri, 30 Aug 2019 15:49:58 +0800 Subject: [PATCH 09/55] update tls_rule_conf.data.md docs --- .../configuration/tls_conf/tls_rule_conf.data.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/zh_cn/configuration/tls_conf/tls_rule_conf.data.md b/docs/zh_cn/configuration/tls_conf/tls_rule_conf.data.md index 6d0c8859d..2b46c52f4 100644 --- a/docs/zh_cn/configuration/tls_conf/tls_rule_conf.data.md +++ b/docs/zh_cn/configuration/tls_conf/tls_rule_conf.data.md @@ -8,19 +8,19 @@ tls_rule_conf.data配置TLS协议参数。 | ----------------- | ------------ | ------------------------------------------------------------- | | Version | String | 配置文件版本 | | DefaultNextProtos | String Array | 默认的应用层协议(h2, spdy/3.1, http/1.1) | -| Config | Struct | TLS配置,该配置为map数据,key是产品线名称,value是tls配置详情 | +| Config | Struct | TLS配置,包含多个Name/Value对,Name是标签,Value是tls配置详情 | TLS配置详情如下: | 配置项 | 类型 | 描述 | | ------------ | ------------ | ------------------------------------------------------------------------ | | CertName | String | 服务端证书名称(注:在server_cert_conf.data文件中定义) | -| NextProtos | String Array | 启用的应用层协议(h2, spdy/3.1, http/1.1),要求满足以下规则:
- 配置为空时:默认使用"http/1.1"
- 协议列表要使用"spdy/3.1","h2","http/1.1"中的一个或多个作为配置选项,并且至少要包含"http/1.1" | -| Grade | String | 安全等级 | -| ClientAuth | Bool | 是否启用双向认证 | +| NextProtos | String Array | TLS应用层协议(h2, spdy/3.1, http/1.1); 缺省为"http/1.1" | +| Grade | String | TLS安全等级 | +| ClientAuth | Bool | 是否启用TLS双向认证 | | ClientCAName | String | 客户端证书签发CA名称 | -| VipConf | String Array | 产品线VIP列表(注:优先依据VIP来确定TLS配置) | -| SniConf | String Array | 产品线域名列表,可选(注:无法依据VIP确定TLS配置时,使用SNI确定TLS配置) | +| VipConf | String Array | VIP列表(注:优先依据VIP来确定TLS配置) | +| SniConf | String Array | 域名列表,可选(注:无法依据VIP确定TLS配置时,使用SNI确定TLS配置) | # 示例 From e5c85a87ccf9ce4b5e6d4e1b85771d505aa31e95 Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Fri, 30 Aug 2019 15:50:46 +0800 Subject: [PATCH 10/55] update mod_trust_clientip.md docs --- .../configuration/mod_trust_clientip/mod_trust_clientip.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zh_cn/configuration/mod_trust_clientip/mod_trust_clientip.md b/docs/zh_cn/configuration/mod_trust_clientip/mod_trust_clientip.md index 0c7d6f9df..ecef28b8e 100644 --- a/docs/zh_cn/configuration/mod_trust_clientip/mod_trust_clientip.md +++ b/docs/zh_cn/configuration/mod_trust_clientip/mod_trust_clientip.md @@ -18,9 +18,9 @@ conf/mod_trust_clientip/trust_client_ip.data | 配置项 | 类型 | 描述 | - | ------- | ------ | -------------------------------------------- -------------------------------- | + | ------- | ------ | ----------------------------------------------------------------------------- | | Version | String | 配置文件版本 | - | Config | Struct | 记录信任的IP列表。该配置项是一个map类型数据,key是标签,value是一个或多个IP段 | + | Config | Struct | 记录信任的IP列表。包含多个Name/Value对,Name是标签,Value是IP段列表 | ``` { From 735faf66f46b403a2e6b5df34d336d4491dd39b8 Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Fri, 30 Aug 2019 15:51:12 +0800 Subject: [PATCH 11/55] update mod_block.md docs --- docs/zh_cn/configuration/mod_block/mod_block.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh_cn/configuration/mod_block/mod_block.md b/docs/zh_cn/configuration/mod_block/mod_block.md index 5f3df0a8e..1d21f6db0 100644 --- a/docs/zh_cn/configuration/mod_block/mod_block.md +++ b/docs/zh_cn/configuration/mod_block/mod_block.md @@ -35,7 +35,7 @@ | Config Item | 类型 | | | ----------- | ------ | ------------------------------------------------------------ | | Version | String | 配置文件版本 | - | Config | Struct | 基于产品线的封禁规则,每个规则包括:
- Cond: 描述匹配请求或连接的条件
- Action: 匹配成功后的动作
- Name: 规则名称 | + | Config | Struct | 各产品线的封禁规则列表,每个规则包括:
- Cond: 描述匹配请求或连接的条件
- Action: 匹配成功后的动作
- Name: 规则名称 | | 动作 | 描述 | | ----- | -------- | From 1eee2300561bce952a2261c7b5073582501a9ff4 Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Fri, 30 Aug 2019 15:51:40 +0800 Subject: [PATCH 12/55] update cluster_table.data.md docs --- docs/zh_cn/configuration/cluster_conf/cluster_table.data.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zh_cn/configuration/cluster_conf/cluster_table.data.md b/docs/zh_cn/configuration/cluster_conf/cluster_table.data.md index 7b68d543e..75ba799f4 100644 --- a/docs/zh_cn/configuration/cluster_conf/cluster_table.data.md +++ b/docs/zh_cn/configuration/cluster_conf/cluster_table.data.md @@ -1,8 +1,8 @@ # 简介 -cluster_table.data配置文件记录各后端集群包含的: +cluster_table.data配置文件记录各后端集群包含的子集群,具体包括: - 子集群名称 -- 子集群内的实例信息(访问地址、权重) +- 子集群内的实例信息(实例地址、实例权重) # 配置 From 2858758a135bdd60c99692ca2be4d191a04ce517 Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Fri, 30 Aug 2019 15:51:55 +0800 Subject: [PATCH 13/55] update mod_header.md docs --- docs/zh_cn/configuration/mod_header/mod_header.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh_cn/configuration/mod_header/mod_header.md b/docs/zh_cn/configuration/mod_header/mod_header.md index 7ab9f18cb..8274bd6c9 100644 --- a/docs/zh_cn/configuration/mod_header/mod_header.md +++ b/docs/zh_cn/configuration/mod_header/mod_header.md @@ -20,7 +20,7 @@ | 配置项 | 类型 | 描述 | | ------- | ------ | ------------------------------------------------------------ | | Version | String | 配置文件版本 | - | Config | Struct | 基于产品线的规则配置,每条规则包括:
- Cond: 描述匹配请求的条件
- Actions: 匹配成功后的动作
- Last: 当该项为true时,命中某条规则后,不再向后匹配 | + | Config | Struct | 基于产品线的规则配置,每条规则包括:
- Cond: 描述匹配请求的条件
- Actions: 匹配成功后的动作
- Last: 当为true时,命中规则后,不再向后匹配 | | 动作 | 描述 | | -------------- | ---------- | From d60f777754df8f60aecad43a0b80554c7de71078 Mon Sep 17 00:00:00 2001 From: yangwensi Date: Tue, 27 Aug 2019 19:02:09 +0800 Subject: [PATCH 14/55] solve some ineffassign issues --- bfe_http/request_test.go | 2 +- bfe_route/trie/trie_test.go | 6 +++--- bfe_util/copy_util.go | 8 ++++++-- go.mod | 1 - go.sum | 5 ----- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/bfe_http/request_test.go b/bfe_http/request_test.go index 5426ff85e..3a440babb 100644 --- a/bfe_http/request_test.go +++ b/bfe_http/request_test.go @@ -134,7 +134,7 @@ func TestMultipartReader(t *testing.T) { } req.Header = Header{"Content-Type": {"text/plain"}} - multipart, err = req.MultipartReader() + multipart, _ = req.MultipartReader() if multipart != nil { t.Errorf("unexpected multipart for text/plain") } diff --git a/bfe_route/trie/trie_test.go b/bfe_route/trie/trie_test.go index 91ba32190..d6b21fd2c 100644 --- a/bfe_route/trie/trie_test.go +++ b/bfe_route/trie/trie_test.go @@ -30,17 +30,17 @@ func TestDNSMatch(t *testing.T) { trie.Set(strings.Split("com.baidu.*", "."), "2") trie.Set(strings.Split("co.baidu.weidu", "."), "2") - match, ok := trie.Get(strings.Split("com.baidu.www", ".")) + _, ok := trie.Get(strings.Split("com.baidu.www", ".")) if !ok { t.Error() } - match, ok = trie.Get(strings.Split("com.baidu.100", ".")) + _, ok = trie.Get(strings.Split("com.baidu.100", ".")) if !ok { t.Error() } - match, ok = trie.Get(strings.Split("com.1baidu.100", ".")) + match, _ := trie.Get(strings.Split("com.1baidu.100", ".")) if match != nil { t.Error() } diff --git a/bfe_util/copy_util.go b/bfe_util/copy_util.go index bfca7a734..c3fab697c 100644 --- a/bfe_util/copy_util.go +++ b/bfe_util/copy_util.go @@ -43,8 +43,8 @@ func CopyWithoutBuffer(wf bfe_http.WriteFlusher, src io.Reader) (written int64, nr, er := src.Read(buf) if nr > 0 { nw, ew := wf.Write(buf[0:nr]) - // flush immediately after Write. - ew = wf.Flush() + // flush immediately regardless of write result. + ef := wf.Flush() if nw > 0 { written += int64(nw) } @@ -52,6 +52,10 @@ func CopyWithoutBuffer(wf bfe_http.WriteFlusher, src io.Reader) (written int64, err = ew break } + if ef != nil { + err = ef + break + } if nr != nw { err = io.ErrShortWrite break diff --git a/go.mod b/go.mod index bac11492b..00ca1a8d9 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 golang.org/x/net v0.0.0-20190620200207-3b0461eec859 golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed - golang.org/x/tools v0.0.0-20190731214159-1e85ed8060aa // indirect gopkg.in/gcfg.v1 v1.2.3 gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index fd44ec3db..070eff3e1 100644 --- a/go.sum +++ b/go.sum @@ -18,16 +18,11 @@ golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed h1:uPxWBzB3+mlnjy9W58qY1j/cjyFjutgw/Vhan2zLy/A= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20190730215328-ed3277de2799 h1:rvNf5qrBjmtxebJHK+blZSkGIv+Yg6UlDnl2ApkB6m4= -golang.org/x/tools v0.0.0-20190730215328-ed3277de2799/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -golang.org/x/tools v0.0.0-20190731214159-1e85ed8060aa h1:kwa/4M1dbmhZqOIqYiTtbA6JrvPwo1+jqlub2qDXX90= -golang.org/x/tools v0.0.0-20190731214159-1e85ed8060aa/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= From 673d49e14edebaf00f9ff2ef5c8690d568fd4c8b Mon Sep 17 00:00:00 2001 From: yangsijie Date: Fri, 30 Aug 2019 17:43:11 +0800 Subject: [PATCH 15/55] Support the proxy protocol, close #11 --- bfe_basic/request.go | 4 +- bfe_config/bfe_conf/conf_basic.go | 28 +- bfe_modules/mod_header/action_header_var.go | 58 +++- bfe_proxy/addr_proto.go | 116 +++++++ bfe_proxy/addr_proto_test.go | 142 +++++++++ bfe_proxy/common.go | 50 +++ bfe_proxy/conn.go | 212 +++++++++++++ bfe_proxy/conn_test.go | 257 +++++++++++++++ bfe_proxy/header.go | 176 +++++++++++ bfe_proxy/header_test.go | 71 +++++ bfe_proxy/v1.go | 159 ++++++++++ bfe_proxy/v1_test.go | 151 +++++++++ bfe_proxy/v2.go | 253 +++++++++++++++ bfe_proxy/v2_test.go | 330 ++++++++++++++++++++ bfe_proxy/version_cmd.go | 67 ++++ bfe_proxy/version_cmd_test.go | 71 +++++ bfe_server/bfe_listener.go | 99 ++++++ bfe_server/bfe_server.go | 7 - bfe_server/bfe_server_init.go | 4 +- bfe_server/find_location.go | 2 +- bfe_server/http_conn.go | 8 +- bfe_server/server_status.go | 17 + bfe_server/web_server.go | 4 + bfe_util/get_l4lb_info.go | 277 +++++++++++++--- bfe_util/get_net_info.go | 68 ++++ 25 files changed, 2566 insertions(+), 65 deletions(-) create mode 100644 bfe_proxy/addr_proto.go create mode 100644 bfe_proxy/addr_proto_test.go create mode 100644 bfe_proxy/common.go create mode 100644 bfe_proxy/conn.go create mode 100644 bfe_proxy/conn_test.go create mode 100644 bfe_proxy/header.go create mode 100644 bfe_proxy/header_test.go create mode 100644 bfe_proxy/v1.go create mode 100644 bfe_proxy/v1_test.go create mode 100644 bfe_proxy/v2.go create mode 100644 bfe_proxy/v2_test.go create mode 100644 bfe_proxy/version_cmd.go create mode 100644 bfe_proxy/version_cmd_test.go create mode 100644 bfe_server/bfe_listener.go diff --git a/bfe_basic/request.go b/bfe_basic/request.go index 51f031feb..91a3a1639 100644 --- a/bfe_basic/request.go +++ b/bfe_basic/request.go @@ -113,8 +113,8 @@ func NewRequest(request *bfe_http.Request, conn net.Conn, stat *RequestStat, fReq.Context = make(map[interface{}]interface{}) fReq.Tags.TagTable = make(map[string][]string) - if conn != nil { - fReq.RemoteAddr = conn.RemoteAddr().(*net.TCPAddr) + if session != nil { + fReq.RemoteAddr = session.RemoteAddr } fReq.SvrDataConf = svrDataConf diff --git a/bfe_config/bfe_conf/conf_basic.go b/bfe_config/bfe_conf/conf_basic.go index c7efba44f..23de4fa02 100644 --- a/bfe_config/bfe_conf/conf_basic.go +++ b/bfe_config/bfe_conf/conf_basic.go @@ -27,6 +27,12 @@ import ( "github.com/baidu/bfe/bfe_util" ) +const ( + BALANCER_BGW = "BGW" // layer4 balancer in baidu + BALANCER_PROXY = "PROXY" // layer4 balancer working in PROXY mode (eg. F5, Ctrix, ELB etc) + BALANCER_NONE = "NONE" // layer4 balancer not used +) + type ConfigBasic struct { HttpPort int // listen port for http HttpsPort int // listen port for https @@ -43,6 +49,7 @@ type ConfigBasic struct { GracefulShutdownTimeout int // graceful shutdown timeout, in seconds MaxHeaderBytes int // max header length in bytes in request MaxHeaderUriBytes int // max URI(in header) length in bytes in request + MaxProxyHeaderBytes int // max header lenght in bytes in Proxy protocol KeepAliveEnabled bool // if false, client connection is shutdown disregard of http headers Modules []string // modules to load @@ -117,8 +124,8 @@ func basicConfCheck(cfg *ConfigBasic) error { } // check Layer4LoadBalancer - if cfg.Layer4LoadBalancer != "BGW" && cfg.Layer4LoadBalancer != "" { - return fmt.Errorf("Layer4LoadBalancer[%s] not support", cfg.Layer4LoadBalancer) + if err := checkLayer4LoadBalancer(cfg); err != nil { + return err } // check TlsHandshakeTimeout @@ -175,6 +182,23 @@ func basicConfCheck(cfg *ConfigBasic) error { return nil } +func checkLayer4LoadBalancer(cfg *ConfigBasic) error { + if len(cfg.Layer4LoadBalancer) == 0 { + cfg.Layer4LoadBalancer = BALANCER_BGW // default BGW + } + + switch cfg.Layer4LoadBalancer { + case BALANCER_BGW: + return nil + case BALANCER_PROXY: + return nil + case BALANCER_NONE: + return nil + default: + return fmt.Errorf("Layer4LoadBalancer[%s] should be BGW/PROXY/NONE", cfg.Layer4LoadBalancer) + } +} + func dataFileConfCheck(cfg *ConfigBasic, confRoot string) error { // check HostRuleConf if cfg.HostRuleConf == "" { diff --git a/bfe_modules/mod_header/action_header_var.go b/bfe_modules/mod_header/action_header_var.go index b1b98386a..e0d1734be 100644 --- a/bfe_modules/mod_header/action_header_var.go +++ b/bfe_modules/mod_header/action_header_var.go @@ -19,6 +19,7 @@ import ( "encoding/asn1" "encoding/hex" "fmt" + "net" "os" "strconv" ) @@ -26,17 +27,27 @@ import ( import ( "github.com/baidu/bfe/bfe_basic" "github.com/baidu/bfe/bfe_tls" + "github.com/baidu/bfe/bfe_util" ) type HeaderValueHandler func(req *bfe_basic.Request) string +const ( + UNKNOWN = "unknown" +) + var VariableHandlers = map[string]HeaderValueHandler{ // for client "bfe_client_ip": getClientIp, "bfe_client_port": getClientPort, "bfe_request_host": getRequestHost, - "bfe_session_id": getSessionId, - "bfe_vip": getBfeVip, + + // for conn info + "bfe_session_id": getSessionId, + "bfe_cip": getClientIp, // client ip (alias for bfe_clientip) + "bfe_vip": getBfeVip, // virtual ip + "bfe_bip": getBfeBip, // balancer ip + "bfe_rip": getBfeRip, // bfe ip // for bfe "bfe_server_name": getBfeServerName, @@ -240,7 +251,46 @@ func getBfeVip(req *bfe_basic.Request) string { return req.Session.Vip.String() } - return "unknown" + return UNKNOWN +} + +func getAddressFetcher(conn net.Conn) bfe_util.AddressFetcher { + if c, ok := conn.(*bfe_tls.Conn); ok { + conn = c.GetNetConn() + } + if f, ok := conn.(bfe_util.AddressFetcher); ok { + return f + } + return nil +} + +func getBfeBip(req *bfe_basic.Request) string { + f := getAddressFetcher(req.Session.Connection) + if f == nil { + return UNKNOWN + } + + baddr := f.BalancerAddr() + if baddr == nil { + return UNKNOWN + } + bip, _, err := net.SplitHostPort(baddr.String()) + if err != nil { /* never come here */ + return UNKNOWN + } + + return bip +} + +func getBfeRip(req *bfe_basic.Request) string { + conn := req.Session.Connection + raddr := conn.LocalAddr() + rip, _, err := net.SplitHostPort(raddr.String()) + if err != nil { /* never come here */ + return UNKNOWN + } + + return rip } func getBfeBackendInfo(req *bfe_basic.Request) string { @@ -252,7 +302,7 @@ func getBfeBackendInfo(req *bfe_basic.Request) string { func getBfeServerName(req *bfe_basic.Request) string { hostname, err := os.Hostname() if err != nil { - return "unknown" + return UNKNOWN } return hostname diff --git a/bfe_proxy/addr_proto.go b/bfe_proxy/addr_proto.go new file mode 100644 index 000000000..6f60d5c25 --- /dev/null +++ b/bfe_proxy/addr_proto.go @@ -0,0 +1,116 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// Copyright (c) pires. +// +// 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 bfe_proxy + +// AddressFamilyAndProtocol represents address family and transport protocol. +type AddressFamilyAndProtocol byte + +const ( + UNSPEC = '\x00' + TCPv4 = '\x11' + UDPv4 = '\x12' + TCPv6 = '\x21' + UDPv6 = '\x22' + UnixStream = '\x31' + UnixDatagram = '\x32' +) + +var supportedTransportProtocol = map[AddressFamilyAndProtocol]bool{ + TCPv4: true, + UDPv4: true, + TCPv6: true, + UDPv6: true, + UnixStream: true, + UnixDatagram: true, +} + +// IsIPv4 returns true if the address family is IPv4 (AF_INET4), false otherwise. +func (ap AddressFamilyAndProtocol) IsIPv4() bool { + return 0x10 == ap&0xF0 +} + +// IsIPv6 returns true if the address family is IPv6 (AF_INET6), false otherwise. +func (ap AddressFamilyAndProtocol) IsIPv6() bool { + return 0x20 == ap&0xF0 +} + +// IsUnix returns true if the address family is UNIX (AF_UNIX), false otherwise. +func (ap AddressFamilyAndProtocol) IsUnix() bool { + return 0x30 == ap&0xF0 +} + +// IsStream returns true if the transport protocol is TCP or STREAM (SOCK_STREAM), false otherwise. +func (ap AddressFamilyAndProtocol) IsStream() bool { + return 0x01 == ap&0x0F +} + +// IsDatagram returns true if the transport protocol is UDP or DGRAM (SOCK_DGRAM), false otherwise. +func (ap AddressFamilyAndProtocol) IsDatagram() bool { + return 0x02 == ap&0x0F +} + +// IsUnspec returns true if the transport protocol or address family is unspecified, false otherwise. +func (ap AddressFamilyAndProtocol) IsUnspec() bool { + return (0x00 == ap&0xF0) || (0x00 == ap&0x0F) +} + +func (ap AddressFamilyAndProtocol) toByte() byte { + if ap.IsIPv4() && ap.IsStream() { + return TCPv4 + } else if ap.IsIPv4() && ap.IsDatagram() { + return UDPv4 + } else if ap.IsIPv6() && ap.IsStream() { + return TCPv6 + } else if ap.IsIPv6() && ap.IsDatagram() { + return UDPv6 + } else if ap.IsUnix() && ap.IsStream() { + return UnixStream + } else if ap.IsUnix() && ap.IsDatagram() { + return UnixDatagram + } + + return UNSPEC +} + +func (ap AddressFamilyAndProtocol) String() string { + if ap.IsIPv4() && ap.IsStream() { + return "tcp4" + } else if ap.IsIPv4() && ap.IsDatagram() { + return "udp4" + } else if ap.IsIPv6() && ap.IsStream() { + return "tcp6" + } else if ap.IsIPv6() && ap.IsDatagram() { + return "udp6" + } else if ap.IsUnix() && ap.IsStream() { + return "unix" + } else if ap.IsUnix() && ap.IsDatagram() { + return "unixgram" + } + return "unspec" +} diff --git a/bfe_proxy/addr_proto_test.go b/bfe_proxy/addr_proto_test.go new file mode 100644 index 000000000..e6d7e90f7 --- /dev/null +++ b/bfe_proxy/addr_proto_test.go @@ -0,0 +1,142 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// Copyright (c) pires. +// +// 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 bfe_proxy + +import ( + "testing" +) + +func TestTCPoverIPv4(t *testing.T) { + b := byte(TCPv4) + if !AddressFamilyAndProtocol(b).IsIPv4() { + t.Fail() + } + if !AddressFamilyAndProtocol(b).IsStream() { + t.Fail() + } + if AddressFamilyAndProtocol(b).toByte() != b { + t.Fail() + } + if AddressFamilyAndProtocol(b).String() != "tcp4" { + t.Fail() + } +} + +func TestTCPoverIPv6(t *testing.T) { + b := byte(TCPv6) + if !AddressFamilyAndProtocol(b).IsIPv6() { + t.Fail() + } + if !AddressFamilyAndProtocol(b).IsStream() { + t.Fail() + } + if AddressFamilyAndProtocol(b).toByte() != b { + t.Fail() + } + if AddressFamilyAndProtocol(b).String() != "tcp6" { + t.Fail() + } +} + +func TestUDPoverIPv4(t *testing.T) { + b := byte(UDPv4) + if !AddressFamilyAndProtocol(b).IsIPv4() { + t.Fail() + } + if !AddressFamilyAndProtocol(b).IsDatagram() { + t.Fail() + } + if AddressFamilyAndProtocol(b).toByte() != b { + t.Fail() + } + if AddressFamilyAndProtocol(b).String() != "udp4" { + t.Fail() + } +} + +func TestUDPoverIPv6(t *testing.T) { + b := byte(UDPv6) + if !AddressFamilyAndProtocol(b).IsIPv6() { + t.Fail() + } + if !AddressFamilyAndProtocol(b).IsDatagram() { + t.Fail() + } + if AddressFamilyAndProtocol(b).toByte() != b { + t.Fail() + } + if AddressFamilyAndProtocol(b).String() != "udp6" { + t.Fail() + } +} + +func TestUnixStream(t *testing.T) { + b := byte(UnixStream) + if !AddressFamilyAndProtocol(b).IsUnix() { + t.Fail() + } + if !AddressFamilyAndProtocol(b).IsStream() { + t.Fail() + } + if AddressFamilyAndProtocol(b).toByte() != b { + t.Fail() + } + if AddressFamilyAndProtocol(b).String() != "unix" { + t.Fail() + } +} + +func TestUnixDatagram(t *testing.T) { + b := byte(UnixDatagram) + if !AddressFamilyAndProtocol(b).IsUnix() { + t.Fail() + } + if !AddressFamilyAndProtocol(b).IsDatagram() { + t.Fail() + } + if AddressFamilyAndProtocol(b).toByte() != b { + t.Fail() + } + if AddressFamilyAndProtocol(b).String() != "unixgram" { + t.Fail() + } +} + +func TestInvalidAddressFamilyAndProtocol(t *testing.T) { + b := byte(UNSPEC) + if !AddressFamilyAndProtocol(b).IsUnspec() { + t.Fail() + } + if AddressFamilyAndProtocol(b).toByte() != b { + t.Fail() + } + if AddressFamilyAndProtocol(b).String() != "unspec" { + t.Fail() + } +} diff --git a/bfe_proxy/common.go b/bfe_proxy/common.go new file mode 100644 index 000000000..b570fb45f --- /dev/null +++ b/bfe_proxy/common.go @@ -0,0 +1,50 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// Copyright (c) pires. +// +// 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 bfe_proxy + +import ( + "github.com/baidu/go-lib/web-monitor/metrics" +) + +// State for Proxy +type ProxyState struct { + ProxyErrReadHeader *metrics.Counter // connection with io err while read header + ProxyErrNoProxyProtocol *metrics.Counter // connection with signature unmatched + ProxyMatchedV1Signature *metrics.Counter // connection with signature v1 matched + ProxyMatchedV2Signature *metrics.Counter // connection with signature v1 matched + ProxyErrInvalidHeader *metrics.Counter // connection with invalid header + ProxyNormalV1Header *metrics.Counter // connection with normal v1 header + ProxyNormalV2Header *metrics.Counter // connection with normal v2 header +} + +var state ProxyState + +func GetProxyState() *ProxyState { + return &state +} diff --git a/bfe_proxy/conn.go b/bfe_proxy/conn.go new file mode 100644 index 000000000..668b9b715 --- /dev/null +++ b/bfe_proxy/conn.go @@ -0,0 +1,212 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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 bfe_proxy + +import ( + "fmt" + "io" + "net" + "sync" + "time" +) + +import ( + "github.com/baidu/go-lib/log" +) + +import ( + bufio "github.com/baidu/bfe/bfe_bufio" +) + +const ( + // defaultProxyHeaderTimeout is the read timeout of PROXY header. + // This can be overridden by setting ProxyHeaderTimeout. + defaultProxyHeaderTimeout = 30 * time.Second + + // defaultProxyHeaderBytes is the maximum permitted size of the PROXY headers + // + // Maximum length of header in PROXY v1 + // See: Proxy Protocol 2.1. Human-readable header format (Version 1) + // "So a 108-byte buffer is always enough to store all the line and a trailing zero." + // + // Maximum length of header in PROXY V2 + // See: Proxy Protocol 2.2. Binary header format (version 2) + // "The sender must ensure that all the protocol header is sent at once. This block + // is always smaller than an MSS, so there is no reason for it to be segmented at + // the beginning of the connection." + defualtMaxProxyHeaderBytes int64 = 2048 + + // noLimit is an effective infinite upper bound for io.LimitedReader + noLimit int64 = (1 << 63) - 1 +) + +// Conn is used to wrap an underlying connection which +// may be speaking the Proxy Protocol. If it is, the RemoteAddr() will +// return the address of the client instead of the proxy address. +type Conn struct { + conn net.Conn // underlying TCP connection + lmtReader *io.LimitedReader + bufReader *bufio.Reader + headerTimeout time.Duration // timeout for reading proxy header + headerLimit int64 // maximum bytes of proxy header accepted + headerErr error // error when parsing proxy header + dstAddr *net.TCPAddr // real dst address (i.e. virtual address) + srcAddr *net.TCPAddr // real src address (i.e. real client address) + once sync.Once +} + +// NewConn is used to wrap a net.Conn that may be speaking +// the proxy protocol +func NewConn(conn net.Conn, headerTimeout time.Duration, maxProxyHeaderBytes int64) *Conn { + if headerTimeout <= 0 { + headerTimeout = defaultProxyHeaderTimeout + } + if maxProxyHeaderBytes <= 0 { + maxProxyHeaderBytes = defualtMaxProxyHeaderBytes + } + + pConn := new(Conn) + pConn.headerTimeout = headerTimeout + pConn.headerLimit = maxProxyHeaderBytes + pConn.conn = conn + pConn.lmtReader = io.LimitReader(conn, pConn.headerLimit).(*io.LimitedReader) + pConn.bufReader = bufio.NewReader(pConn.lmtReader) + return pConn +} + +// Read reads data from the connection. +// It check for the proxy protocol header when doing +// the initial read. If there is an error parsing the header, +// it is returned and the socket is closed. +func (p *Conn) Read(b []byte) (int, error) { + p.checkProxyHeaderOnce() + if p.headerErr != nil { + return 0, p.headerErr + } + return p.bufReader.Read(b) +} + +// Write writes data to the connection. +func (p *Conn) Write(b []byte) (int, error) { + return p.conn.Write(b) +} + +// Close closes the connection. +func (p *Conn) Close() error { + return p.conn.Close() +} + +// LocalAddr returns the local network address. +func (p *Conn) LocalAddr() net.Addr { + return p.conn.LocalAddr() +} + +// RemoteAddr returns the address of the client if the proxy +// protocol is being used, otherwise just returns the address of +// the socket peer. If there is an error parsing the header, the +// address of the client is not returned, and the socket is closed. +func (p *Conn) RemoteAddr() net.Addr { + p.checkProxyHeaderOnce() + if p.srcAddr != nil { + return p.srcAddr + } + return p.conn.RemoteAddr() +} + +// VirtualAddr returns the virtual address +func (p *Conn) VirtualAddr() net.Addr { + p.checkProxyHeaderOnce() + if p.dstAddr != nil { + return p.dstAddr + } + return nil +} + +// BalancerAddr returns the address of balancer +func (p *Conn) BalancerAddr() net.Addr { + p.checkProxyHeaderOnce() + if p.dstAddr != nil { + return p.conn.RemoteAddr() + } + return nil +} + +// GetNetConn returns the underlying connection +func (p *Conn) GetNetConn() net.Conn { + return p.conn +} + +// SetDeadline implements the Conn.SetDeadline method +func (p *Conn) SetDeadline(t time.Time) error { + return p.conn.SetDeadline(t) +} + +// SetReadDeadline implements the Conn.SetReadDeadline method +func (p *Conn) SetReadDeadline(t time.Time) error { + return p.conn.SetReadDeadline(t) +} + +// SetWriteDeadline implements the Conn.SetWriteDeadline method +func (p *Conn) SetWriteDeadline(t time.Time) error { + return p.conn.SetWriteDeadline(t) +} + +func (p *Conn) checkProxyHeaderOnce() { + p.once.Do(func() { + if err := p.checkProxyHeader(); err != nil { + log.Logger.Error("bfe_proxy: Failed to read proxy header: %v", err) + p.Close() + } + }) +} + +func (p *Conn) checkProxyHeader() error { + // set read timeout for proxy header + p.conn.SetReadDeadline(time.Now().Add(p.headerTimeout)) + + // reset timeout and read limit for conn + defer func() { + p.conn.SetReadDeadline(time.Time{}) + p.lmtReader.N = noLimit + }() + + // read and parse proxy header + hdr, err := Read(p.bufReader) + if err == ErrNoProxyProtocol { // ignore ErrNoProxyProtocol + return nil + } + if err != nil { + p.conn.Close() + p.headerErr = err + return err + } + + // initial real src/dst address + srcAddr := net.JoinHostPort(hdr.SourceAddress.String(), fmt.Sprintf("%d", hdr.SourcePort)) + p.srcAddr, err = net.ResolveTCPAddr(hdr.TransportProtocol.String(), srcAddr) + if err != nil { /* never go here */ + p.conn.Close() + return err + } + + dstAddr := net.JoinHostPort(hdr.DestinationAddress.String(), fmt.Sprintf("%d", hdr.DestinationPort)) + p.dstAddr, err = net.ResolveTCPAddr(hdr.TransportProtocol.String(), dstAddr) + if err != nil { /* never go here */ + p.conn.Close() + return err + } + + return nil +} diff --git a/bfe_proxy/conn_test.go b/bfe_proxy/conn_test.go new file mode 100644 index 000000000..feadd84fb --- /dev/null +++ b/bfe_proxy/conn_test.go @@ -0,0 +1,257 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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 bfe_proxy + +import ( + "bytes" + "crypto/rand" + "io" + "net" + "strings" + "testing" + "time" +) + +import ( + bufio "github.com/baidu/bfe/bfe_bufio" + "github.com/baidu/bfe/bfe_util" +) + +func TestProxyHeaderNormal(t *testing.T) { + br := newBufioReader([]byte("PROXY TCP4 " + TCP4AddressesAndPorts + CRLF + "hello")) + testProxyConnRead(t, br, 0, 0, "") +} + +func TestProxyHeaderNoProxyProtocol(t *testing.T) { + br := newBufioReader([]byte("hello")) + testProxyConnRead(t, br, 0, 0, "") +} + +func TestProxyHeaderExceedLimit(t *testing.T) { + hdr := "PROXY TCP4 " + TCP4AddressesAndPorts + CRLF + br := newBufioReader([]byte(hdr + "hello")) + testProxyConnRead(t, br, 0, int64(len(hdr)-1), "EOF") +} + +func TestProxyHeaderResetLimit(t *testing.T) { + hdr := "PROXY TCP4 " + TCP4AddressesAndPorts + CRLF + br := newBufioReader([]byte(hdr + "hello")) + testProxyConnRead(t, br, 0, int64(len(hdr)), "") +} + +func TestProxyHeaderTimeout(t *testing.T) { + br := bufio.NewReader(new(timeoutReader)) + testProxyConnRead(t, br, 50*time.Millisecond, 0, "timeout") +} + +func TestProxyConnAddrIPv4(t *testing.T) { + br := newBufioReader([]byte("PROXY TCP4 1.1.1.1 2.2.2.2 12345 80" + CRLF)) + caddr := parseTCPAddr("tcp", "1.1.1.1:12345") + vaddr := parseTCPAddr("tcp", "2.2.2.2:80") + testProxyConnAddr(t, br, caddr, vaddr, true) +} + +func TestProxyConnAddrIPv6(t *testing.T) { + br := newBufioReader([]byte("PROXY TCP6 2001::68 2002::68 12345 80" + CRLF)) + caddr := parseTCPAddr("tcp6", "[2001::68]:12345") + vaddr := parseTCPAddr("tcp6", "[2002::68]:80") + testProxyConnAddr(t, br, caddr, vaddr, true) +} + +func TestProxyConnAddrNoProtocol(t *testing.T) { + br := newBufioReader([]byte("GET / HTTP1.1" + CRLF)) + testProxyConnAddr(t, br, nil, nil, false) +} + +func TestProxyConnAddrInvalid(t *testing.T) { + br := newBufioReader([]byte("PROXY TCP 2001::68 2002::68 12345 80" + CRLF)) + testProxyConnAddr(t, br, nil, nil, false) +} + +func TestProxyConnReadNormal(t *testing.T) { + hdr := []byte("PROXY TCP4 " + TCP4AddressesAndPorts + CRLF) + msg := readRandBytes(16 * 1024) + br := newBufioReader(append(hdr, msg...)) + + testProxyConnOperation(t, br, func(c *Conn) { + buf := make([]byte, 16*1024) + _, err := io.ReadFull(c, buf) + checkError(t, "", err) + if bytes.Compare(msg, buf) != 0 { + t.Errorf("Read want %v: \ngot %v", msg, buf) + } + }) +} + +func TestProxyConnReadAfterClose(t *testing.T) { + hdr := []byte("PROXY TCP4 " + TCP4AddressesAndPorts + CRLF) + br := newBufioReader(append(hdr, readRandBytes(16*1024)...)) + + testProxyConnOperation(t, br, func(c *Conn) { + c.RemoteAddr() + c.Close() + _, err := io.ReadFull(c, make([]byte, 16*1024)) + checkError(t, "closed", err) + }) +} + +func TestProxyConnSetWriteDeadline(t *testing.T) { + br := newBufioReader([]byte("PROXY TCP4 " + TCP4AddressesAndPorts + CRLF + "hello")) + testProxyConnOperation(t, br, func(c *Conn) { + c.SetWriteDeadline(time.Now().Add(10 * time.Millisecond)) + time.Sleep(50 * time.Millisecond) + _, err := c.Write([]byte("test")) + checkError(t, "timeout", err) + }) +} + +func TestProxyConnSetReadDeadline(t *testing.T) { + hdr := []byte("PROXY TCP4 " + TCP4AddressesAndPorts + CRLF) + br := newBufioReader(append(hdr, readRandBytes(16*1024)...)) + + testProxyConnOperation(t, br, func(c *Conn) { + c.RemoteAddr() + c.SetReadDeadline(time.Now().Add(10 * time.Millisecond)) + time.Sleep(50 * time.Millisecond) + _, err := io.ReadFull(c, make([]byte, 16*1024)) + checkError(t, "timeout", err) + }) +} + +type CheckHandler func() + +func testMockServer(t *testing.T, hs bfe_util.MockHandler, hc bfe_util.MockHandler, check CheckHandler) { + // init mock server + ms := bfe_util.NewUnstartedServer(hs) + ms.StartTCP() + defer ms.Close() + + // init mock client + cconn, err := net.Dial("tcp", ms.Listener.Addr().String()) + if err != nil { + t.Fatal(err) + } + defer cconn.Close() + + // test and check + go hc(cconn) + check() +} + +func testProxyConnRead(t *testing.T, br *bufio.Reader, timeout time.Duration, limit int64, e string) { + done := make(chan error, 0) + + testMockServer(t, func(c net.Conn) { + // create proxy conn + pconn := NewConn(c, timeout, limit) + defer pconn.Close() + + // read from proxy conn + _, err := pconn.Read(make([]byte, 1)) + + // send error for check + done <- err + }, func(c net.Conn) { + // send data to server + io.Copy(c, br) + }, func() { + // check read result in server side + err := <-done + checkError(t, e, err) + }) +} + +func testProxyConnAddr(t *testing.T, br *bufio.Reader, caddr, vaddr net.Addr, success bool) { + cliDone := make(chan net.Conn, 0) + srvDone := make(chan net.Conn, 0) + + testMockServer(t, func(c net.Conn) { + pconn := NewConn(c, 0, 0) + srvDone <- pconn + }, func(c net.Conn) { + io.Copy(c, br) + cliDone <- c + }, func() { + srvConn := <-srvDone // conn for bfe + cliConn := <-cliDone // conn for proxy + sc := srvConn.(*Conn) + + // check address + addrGot := []net.Addr{sc.RemoteAddr(), sc.VirtualAddr(), sc.BalancerAddr(), sc.LocalAddr()} + addrWant := []net.Addr{caddr, vaddr, cliConn.LocalAddr(), cliConn.RemoteAddr()} + if !success { + addrWant = []net.Addr{cliConn.LocalAddr(), nil, nil, cliConn.RemoteAddr()} + } + + for i, addr := range addrGot { + if !checkAddrEqual(addr, addrWant[i]) { + t.Fatalf("addr [%d] want %v, got %v", i, addrWant[i], addr) + } + } + }) +} + +func testProxyConnOperation(t *testing.T, br *bufio.Reader, f func(*Conn)) { + done := make(chan bool, 1) + + testMockServer(t, func(c net.Conn) { + pconn := NewConn(c, 0, 0) + defer pconn.Close() + f(pconn) + done <- true + }, func(c net.Conn) { + io.Copy(c, br) + }, func() { + <-done + }) +} + +func parseTCPAddr(network string, address string) net.Addr { + addr, _ := net.ResolveTCPAddr(network, address) + return addr +} + +func readRandBytes(n int) []byte { + b := make([]byte, n) + rand.Read(b) + return b +} + +func checkAddrEqual(a, b net.Addr) bool { + if a == nil && b == nil { + return true + } + if a != nil && b != nil && a.String() == b.String() { + return true + } + return false +} + +func checkError(t *testing.T, errWant string, errGot error) { + if len(errWant) == 0 && errGot != nil { + t.Errorf("Unexpect error: %v", errGot) + return + } + if len(errWant) > 0 && errGot == nil { + t.Errorf("Expect error: %s:", errWant) + return + } + if len(errWant) > 0 && errGot != nil { + if !strings.Contains(errGot.Error(), errWant) { + t.Errorf("Expecting error got %v ; want %v", errGot, errWant) + return + } + } +} diff --git a/bfe_proxy/header.go b/bfe_proxy/header.go new file mode 100644 index 000000000..31b75ae60 --- /dev/null +++ b/bfe_proxy/header.go @@ -0,0 +1,176 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// Copyright (c) pires. +// +// 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 bfe_proxy implements Proxy Protocol (v1 and v2) parser and writer, as per specification: +// http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt +package bfe_proxy + +import ( + "bytes" + "errors" + "io" + "net" + "time" +) + +import ( + bufio "github.com/baidu/bfe/bfe_bufio" +) + +// Protocol signature +var ( + SIGV1 = []byte{'\x50', '\x52', '\x4F', '\x58', '\x59'} + SIGV2 = []byte{'\x0D', '\x0A', '\x0D', '\x0A', '\x00', '\x0D', '\x0A', '\x51', '\x55', '\x49', '\x54', '\x0A'} +) + +var ( + ErrCantReadProtocolVersionAndCommand = errors.New("Can't read proxy protocol version and command") + ErrCantReadAddressFamilyAndProtocol = errors.New("Can't read address family or protocol") + ErrCantReadLength = errors.New("Can't read length") + ErrCantResolveSourceUnixAddress = errors.New("Can't resolve source Unix address") + ErrCantResolveDestinationUnixAddress = errors.New("Can't resolve destination Unix address") + ErrNoProxyProtocol = errors.New("Proxy protocol signature not present") + ErrUnknownProxyProtocolVersion = errors.New("Unknown proxy protocol version") + ErrUnsupportedProtocolVersionAndCommand = errors.New("Unsupported proxy protocol version and command") + ErrUnsupportedAddressFamilyAndProtocol = errors.New("Unsupported address family and protocol") + ErrInvalidLength = errors.New("Invalid length") + ErrLengthExceeded = errors.New("Length Exceeded") + ErrInvalidAddress = errors.New("Invalid address") + ErrInvalidPortNumber = errors.New("Invalid port number") +) + +// Header is the placeholder for proxy protocol header. +type Header struct { + Version byte + Command ProtocolVersionAndCommand + TransportProtocol AddressFamilyAndProtocol + SourceAddress net.IP + DestinationAddress net.IP + SourcePort uint16 + DestinationPort uint16 +} + +// EqualTo returns true if headers are equivalent, false otherwise. +func (header *Header) EqualTo(q *Header) bool { + if header == nil || q == nil { + return false + } + if header.Command.IsLocal() { + return true + } + return header.TransportProtocol == q.TransportProtocol && + header.SourceAddress.String() == q.SourceAddress.String() && + header.DestinationAddress.String() == q.DestinationAddress.String() && + header.SourcePort == q.SourcePort && + header.DestinationPort == q.DestinationPort +} + +// WriteTo renders a proxy protocol header in a format to write over the wire. +func (header *Header) WriteTo(w io.Writer) (int64, error) { + switch header.Version { + case 1: + return header.writeVersion1(w) + case 2: + return header.writeVersion2(w) + default: + return 0, ErrUnknownProxyProtocolVersion + } +} + +// Read identifies the proxy protocol version and reads the remaining of +// the header, accordingly. +// +// If proxy protocol header signature is not present, the reader buffer remains untouched +// and is safe for reading outside of this code. +// +// If proxy protocol header signature is present but an error is raised while processing +// the remaining header, assume the reader buffer to be in a corrupt state. +// Also, this operation will block until enough bytes are available for peeking. +func Read(reader *bufio.Reader) (*Header, error) { + // In order to improve speed for small non-PROXYed packets, + // take a peek at the first byte alone. + b1, err := reader.Peek(1) + if err != nil { + state.ProxyErrReadHeader.Inc(1) + return nil, err + } + if !bytes.Equal(b1[:1], SIGV1[:1]) && !bytes.Equal(b1[:1], SIGV2[:1]) { + state.ProxyErrNoProxyProtocol.Inc(1) + return nil, ErrNoProxyProtocol + } + + // Peek and check signature of PROXY v1 + signature, err := reader.Peek(5) + if err != nil { + state.ProxyErrReadHeader.Inc(1) + return nil, err + } + if bytes.Equal(signature[:5], SIGV1) { + state.ProxyMatchedV1Signature.Inc(1) + return parseVersion1(reader) + } + + // Peek and check signature of PROXY v2 + signature, err = reader.Peek(12) + if err != nil { + state.ProxyErrReadHeader.Inc(1) + return nil, err + } + if bytes.Equal(signature[:12], SIGV2) { + state.ProxyMatchedV2Signature.Inc(1) + return parseVersion2(reader) + } + + state.ProxyErrNoProxyProtocol.Inc(1) + return nil, ErrNoProxyProtocol +} + +// ReadTimeout acts as Read but takes a timeout. If that timeout is reached, it's assumed +// there's no proxy protocol header. +func ReadTimeout(reader *bufio.Reader, timeout time.Duration) (*Header, error) { + type header struct { + h *Header + e error + } + read := make(chan *header, 1) + + go func() { + h := &header{} + h.h, h.e = Read(reader) + read <- h + }() + + timer := time.NewTimer(timeout) + select { + case result := <-read: + timer.Stop() + return result.h, result.e + case <-timer.C: + return nil, ErrNoProxyProtocol + } +} diff --git a/bfe_proxy/header_test.go b/bfe_proxy/header_test.go new file mode 100644 index 000000000..e1ca8132a --- /dev/null +++ b/bfe_proxy/header_test.go @@ -0,0 +1,71 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// Copyright (c) pires. +// +// 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 bfe_proxy + +import ( + "net" + "testing" + "time" +) + +import ( + bufio "github.com/baidu/bfe/bfe_bufio" +) + +// Stuff to be used in both versions tests. + +const ( + NO_PROTOCOL = "There is no spoon" + IP4_ADDR = "127.0.0.1" + IP6_ADDR = "::1" + PORT = 65533 +) + +var ( + v4addr = net.ParseIP(IP4_ADDR).To4() + v6addr = net.ParseIP(IP6_ADDR).To16() +) + +type timeoutReader []byte + +func (t *timeoutReader) Read([]byte) (int, error) { + time.Sleep(1000 * time.Millisecond) + return 0, nil +} + +func TestReadTimeoutV1Invalid(t *testing.T) { + var b timeoutReader + reader := bufio.NewReader(&b) + _, err := ReadTimeout(reader, 50*time.Millisecond) + if err == nil { + t.Fatalf("TestReadTimeoutV1Invalid: expected error %s", ErrNoProxyProtocol) + } else if err != ErrNoProxyProtocol { + t.Fatalf("TestReadTimeoutV1Invalid: expected %s, actual %s", ErrNoProxyProtocol, err) + } +} diff --git a/bfe_proxy/v1.go b/bfe_proxy/v1.go new file mode 100644 index 000000000..9d85e2a8d --- /dev/null +++ b/bfe_proxy/v1.go @@ -0,0 +1,159 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// Copyright (c) pires. +// +// 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 bfe_proxy + +import ( + "bytes" + "io" + "net" + "strconv" + "strings" +) + +import ( + bufio "github.com/baidu/bfe/bfe_bufio" +) + +const ( + CRLF = "\r\n" + SEPARATOR = " " +) + +func initVersion1() *Header { + header := new(Header) + header.Version = 1 + // Command doesn't exist in v1 + header.Command = PROXY + return header +} + +func parseVersion1(reader *bufio.Reader) (*Header, error) { + // Make sure we have a v1 header + line, err := reader.ReadString('\n') + if err != nil { + state.ProxyErrReadHeader.Inc(1) + return nil, err + } + if !strings.HasSuffix(line, CRLF) { + state.ProxyErrInvalidHeader.Inc(1) + return nil, ErrCantReadProtocolVersionAndCommand + } + tokens := strings.Split(line[:len(line)-2], SEPARATOR) + if len(tokens) < 6 { + state.ProxyErrInvalidHeader.Inc(1) + return nil, ErrCantReadProtocolVersionAndCommand + } + + header := initVersion1() + + // Read address family and protocol + switch tokens[1] { + case "TCP4": + header.TransportProtocol = TCPv4 + case "TCP6": + header.TransportProtocol = TCPv6 + default: + header.TransportProtocol = UNSPEC + } + + // Read addresses and ports + header.SourceAddress, err = parseV1IPAddress(header.TransportProtocol, tokens[2]) + if err != nil { + state.ProxyErrInvalidHeader.Inc(1) + return nil, err + } + header.DestinationAddress, err = parseV1IPAddress(header.TransportProtocol, tokens[3]) + if err != nil { + state.ProxyErrInvalidHeader.Inc(1) + return nil, err + } + header.SourcePort, err = parseV1PortNumber(tokens[4]) + if err != nil { + state.ProxyErrInvalidHeader.Inc(1) + return nil, err + } + header.DestinationPort, err = parseV1PortNumber(tokens[5]) + if err != nil { + state.ProxyErrInvalidHeader.Inc(1) + return nil, err + } + + state.ProxyNormalV1Header.Inc(1) + return header, nil +} + +func (header *Header) writeVersion1(w io.Writer) (int64, error) { + // As of version 1, only "TCP4" ( \x54 \x43 \x50 \x34 ) for TCP over IPv4, + // and "TCP6" ( \x54 \x43 \x50 \x36 ) for TCP over IPv6 are allowed. + proto := "UNKNOWN" + if header.TransportProtocol == TCPv4 { + proto = "TCP4" + } else if header.TransportProtocol == TCPv6 { + proto = "TCP6" + } + + var buf bytes.Buffer + buf.Write(SIGV1) + buf.WriteString(SEPARATOR) + buf.WriteString(proto) + buf.WriteString(SEPARATOR) + buf.WriteString(header.SourceAddress.String()) + buf.WriteString(SEPARATOR) + buf.WriteString(header.DestinationAddress.String()) + buf.WriteString(SEPARATOR) + buf.WriteString(strconv.Itoa(int(header.SourcePort))) + buf.WriteString(SEPARATOR) + buf.WriteString(strconv.Itoa(int(header.DestinationPort))) + buf.WriteString(CRLF) + + return buf.WriteTo(w) +} + +func parseV1PortNumber(portStr string) (uint16, error) { + var port uint16 + + _port, err := strconv.Atoi(portStr) + if err == nil { + if port < 0 || port > 65535 { + err = ErrInvalidPortNumber + } + port = uint16(_port) + } + + return port, err +} + +func parseV1IPAddress(protocol AddressFamilyAndProtocol, addrStr string) (addr net.IP, err error) { + addr = net.ParseIP(addrStr) + tryV4 := addr.To4() + if (protocol == TCPv4 && tryV4 == nil) || (protocol == TCPv6 && tryV4 != nil) { + err = ErrInvalidAddress + } + return +} diff --git a/bfe_proxy/v1_test.go b/bfe_proxy/v1_test.go new file mode 100644 index 000000000..53b7c5775 --- /dev/null +++ b/bfe_proxy/v1_test.go @@ -0,0 +1,151 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// Copyright (c) pires. +// +// 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 bfe_proxy + +import ( + "bytes" + "io" + "strconv" + "strings" + "testing" +) + +import ( + bufio "github.com/baidu/bfe/bfe_bufio" +) + +var ( + TCP4AddressesAndPorts = strings.Join([]string{IP4_ADDR, IP4_ADDR, strconv.Itoa(PORT), strconv.Itoa(PORT)}, SEPARATOR) + TCP6AddressesAndPorts = strings.Join([]string{IP6_ADDR, IP6_ADDR, strconv.Itoa(PORT), strconv.Itoa(PORT)}, SEPARATOR) + + fixtureTCP4V1 = "PROXY TCP4 " + TCP4AddressesAndPorts + CRLF + "GET /" + fixtureTCP6V1 = "PROXY TCP6 " + TCP6AddressesAndPorts + CRLF + "GET /" +) + +var invalidParseV1Tests = []struct { + reader *bufio.Reader + expectedError error +}{ + { + newBufioReader([]byte("PROX")), + io.EOF, + }, + { + newBufioReader([]byte(NO_PROTOCOL)), + ErrNoProxyProtocol, + }, + { + newBufioReader([]byte("PROXY \r\n")), + ErrCantReadProtocolVersionAndCommand, + }, + { + newBufioReader([]byte("PROXY TCP4 " + TCP4AddressesAndPorts)), + io.EOF, + }, + { + newBufioReader([]byte("PROXY TCP6 " + TCP4AddressesAndPorts + CRLF)), + ErrInvalidAddress, + }, + { + newBufioReader([]byte("PROXY TCP4 " + TCP6AddressesAndPorts + CRLF)), + ErrInvalidAddress, + }, +} + +func TestReadV1Invalid(t *testing.T) { + for i, tt := range invalidParseV1Tests { + if _, err := Read(tt.reader); err != tt.expectedError { + t.Fatalf("TestReadV1Invalid: case %d: expected %s, actual %s", i, tt.expectedError, err) + } + } +} + +var validParseAndWriteV1Tests = []struct { + reader *bufio.Reader + expectedHeader *Header +}{ + { + bufio.NewReader(strings.NewReader(fixtureTCP4V1)), + &Header{ + Version: 1, + Command: PROXY, + TransportProtocol: TCPv4, + SourceAddress: v4addr, + DestinationAddress: v4addr, + SourcePort: PORT, + DestinationPort: PORT, + }, + }, + { + bufio.NewReader(strings.NewReader(fixtureTCP6V1)), + &Header{ + Version: 1, + Command: PROXY, + TransportProtocol: TCPv6, + SourceAddress: v6addr, + DestinationAddress: v6addr, + SourcePort: PORT, + DestinationPort: PORT, + }, + }, +} + +func TestParseV1Valid(t *testing.T) { + for _, tt := range validParseAndWriteV1Tests { + header, err := Read(tt.reader) + if err != nil { + t.Fatal("TestParseV1Valid: unexpected error", err.Error()) + } + if !header.EqualTo(tt.expectedHeader) { + t.Fatalf("TestParseV1Valid: expected %#v, actual %#v", tt.expectedHeader, header) + } + } +} + +func TestWriteV1Valid(t *testing.T) { + for _, tt := range validParseAndWriteV1Tests { + var b bytes.Buffer + w := bufio.NewWriter(&b) + if _, err := tt.expectedHeader.WriteTo(w); err != nil { + t.Fatal("TestWriteV1Valid: Unexpected error ", err) + } + w.Flush() + + // Read written bytes to validate written header + r := bufio.NewReader(&b) + newHeader, err := Read(r) + if err != nil { + t.Fatal("TestWriteV1Valid: Unexpected error ", err) + } + + if !newHeader.EqualTo(tt.expectedHeader) { + t.Fatalf("TestWriteV1Valid: expected %#v, actual %#v", tt.expectedHeader, newHeader) + } + } +} diff --git a/bfe_proxy/v2.go b/bfe_proxy/v2.go new file mode 100644 index 000000000..11e2b484f --- /dev/null +++ b/bfe_proxy/v2.go @@ -0,0 +1,253 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// Copyright (c) pires. +// +// 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 bfe_proxy + +import ( + "bytes" + "encoding/binary" + "io" +) + +import ( + bufio "github.com/baidu/bfe/bfe_bufio" +) + +var ( + lengthV4 = uint16(12) + lengthV6 = uint16(36) + lengthUnix = uint16(218) + + lengthV4Bytes = func() []byte { + a := make([]byte, 2) + binary.BigEndian.PutUint16(a, lengthV4) + return a + }() + lengthV6Bytes = func() []byte { + a := make([]byte, 2) + binary.BigEndian.PutUint16(a, lengthV6) + return a + }() + lengthUnixBytes = func() []byte { + a := make([]byte, 2) + binary.BigEndian.PutUint16(a, lengthUnix) + return a + }() +) + +type _ports struct { + SrcPort uint16 + DstPort uint16 +} + +type _addr4 struct { + Src [4]byte + Dst [4]byte + SrcPort uint16 + DstPort uint16 +} + +type _addr6 struct { + Src [16]byte + Dst [16]byte + _ports +} + +type _addrUnix struct { + Src [108]byte + Dst [108]byte +} + +// parseVersion2 parses protocol header +// +// Note: binary header format: +// - Signature [1~12] +// - Version and Commannd [13] +// - Protocol and Address Family [14] +// - Address Length [15] +// - Additional TLVs [optional] +func parseVersion2(reader *bufio.Reader) (header *Header, err error) { + // Skip first 12 bytes (signature) + for i := 0; i < 12; i++ { + if _, err = reader.ReadByte(); err != nil { + state.ProxyErrReadHeader.Inc(1) + return nil, ErrCantReadProtocolVersionAndCommand + } + } + + header = new(Header) + header.Version = 2 + + // Read the 13th byte, protocol version and command + b13, err := reader.ReadByte() + if err != nil { + state.ProxyErrReadHeader.Inc(1) + return nil, ErrCantReadProtocolVersionAndCommand + } + header.Command = ProtocolVersionAndCommand(b13) + if _, ok := supportedCommand[header.Command]; !ok { + state.ProxyErrInvalidHeader.Inc(1) + return nil, ErrUnsupportedProtocolVersionAndCommand + } + // If command is LOCAL, header ends here + if header.Command.IsLocal() { + return header, nil + } + + // Read the 14th byte, address family and protocol + b14, err := reader.ReadByte() + if err != nil { + state.ProxyErrReadHeader.Inc(1) + return nil, ErrCantReadAddressFamilyAndProtocol + } + header.TransportProtocol = AddressFamilyAndProtocol(b14) + if _, ok := supportedTransportProtocol[header.TransportProtocol]; !ok { + state.ProxyErrInvalidHeader.Inc(1) + return nil, ErrUnsupportedAddressFamilyAndProtocol + } + + // Make sure there are bytes available as specified in length + var length uint16 + if err := binary.Read(io.LimitReader(reader, 2), binary.BigEndian, &length); err != nil { + state.ProxyErrReadHeader.Inc(1) + return nil, ErrCantReadLength + } + + if !header.validateLength(length) { + state.ProxyErrInvalidHeader.Inc(1) + return nil, ErrInvalidLength + } + + if _, err := reader.Peek(int(length)); err != nil { + state.ProxyErrReadHeader.Inc(1) + return nil, ErrInvalidLength + } + + // Length-limited reader for payload section + payloadReader := io.LimitReader(reader, int64(length)) + + // Read addresses and ports + if header.TransportProtocol.IsIPv4() { + var addr _addr4 + if err := binary.Read(payloadReader, binary.BigEndian, &addr); err != nil { + state.ProxyErrReadHeader.Inc(1) + return nil, ErrInvalidAddress + } + header.SourceAddress = addr.Src[:] + header.DestinationAddress = addr.Dst[:] + header.SourcePort = addr.SrcPort + header.DestinationPort = addr.DstPort + } else if header.TransportProtocol.IsIPv6() { + var addr _addr6 + if err := binary.Read(payloadReader, binary.BigEndian, &addr); err != nil { + state.ProxyErrReadHeader.Inc(1) + return nil, ErrInvalidAddress + } + header.SourceAddress = addr.Src[:] + header.DestinationAddress = addr.Dst[:] + header.SourcePort = addr.SrcPort + header.DestinationPort = addr.DstPort + } + // TODO fully support Unix addresses + // else if header.TransportProtocol.IsUnix() { + // var addr _addrUnix + // if err := binary.Read(payloadReader, binary.BigEndian, &addr); err != nil { + // return nil, ErrInvalidAddress + // } + // + //if header.SourceAddress, err = net.ResolveUnixAddr("unix", string(addr.Src[:])); err != nil { + // return nil, ErrCantResolveSourceUnixAddress + //} + //if header.DestinationAddress, err = net.ResolveUnixAddr("unix", string(addr.Dst[:])); err != nil { + // return nil, ErrCantResolveDestinationUnixAddress + //} + //} + + // TODO add encapsulated TLV support + + // Drain the remaining padding + payloadReader.Read(make([]byte, length)) + + state.ProxyNormalV2Header.Inc(1) + return header, nil +} + +func (header *Header) writeVersion2(w io.Writer) (int64, error) { + var buf bytes.Buffer + buf.Write(SIGV2) + buf.WriteByte(header.Command.toByte()) + if !header.Command.IsLocal() { + buf.WriteByte(header.TransportProtocol.toByte()) + // TODO add encapsulated TLV length + var addrSrc, addrDst []byte + if header.TransportProtocol.IsIPv4() { + buf.Write(lengthV4Bytes) + addrSrc = header.SourceAddress.To4() + addrDst = header.DestinationAddress.To4() + } else if header.TransportProtocol.IsIPv6() { + buf.Write(lengthV6Bytes) + addrSrc = header.SourceAddress.To16() + addrDst = header.DestinationAddress.To16() + } else if header.TransportProtocol.IsUnix() { + buf.Write(lengthUnixBytes) + // TODO is below right? + addrSrc = []byte(header.SourceAddress.String()) + addrDst = []byte(header.DestinationAddress.String()) + } + buf.Write(addrSrc) + buf.Write(addrDst) + + portSrcBytes := func() []byte { + a := make([]byte, 2) + binary.BigEndian.PutUint16(a, header.SourcePort) + return a + }() + buf.Write(portSrcBytes) + + portDstBytes := func() []byte { + a := make([]byte, 2) + binary.BigEndian.PutUint16(a, header.DestinationPort) + return a + }() + buf.Write(portDstBytes) + + } + + return buf.WriteTo(w) +} + +func (header *Header) validateLength(length uint16) bool { + if header.TransportProtocol.IsIPv4() { + return length >= lengthV4 + } else if header.TransportProtocol.IsIPv6() { + return length >= lengthV6 + } else if header.TransportProtocol.IsUnix() { + return length >= lengthUnix + } + return false +} diff --git a/bfe_proxy/v2_test.go b/bfe_proxy/v2_test.go new file mode 100644 index 000000000..9306023ae --- /dev/null +++ b/bfe_proxy/v2_test.go @@ -0,0 +1,330 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// Copyright (c) pires. +// +// 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 bfe_proxy + +import ( + "bytes" + "encoding/binary" + "io" + "reflect" + "testing" +) + +import ( + bufio "github.com/baidu/bfe/bfe_bufio" +) + +var ( + invalidRune = byte('\x99') + + // Lengths to use in tests + lengthPadded = uint16(84) + + lengthEmptyBytes = func() []byte { + a := make([]byte, 2) + binary.BigEndian.PutUint16(a, 0) + return a + }() + lengthPaddedBytes = func() []byte { + a := make([]byte, 2) + binary.BigEndian.PutUint16(a, lengthPadded) + return a + }() + + // If life gives you lemons, make mojitos + portBytes = func() []byte { + a := make([]byte, 2) + binary.BigEndian.PutUint16(a, PORT) + return a + }() + + // Tests don't care if source and destination addresses and ports are the same + addressesIPv4 = append(v4addr.To4(), v4addr.To4()...) + addressesIPv6 = append(v6addr.To16(), v6addr.To16()...) + ports = append(portBytes, portBytes...) + + // Fixtures to use in tests + fixtureIPv4Address = append(addressesIPv4, ports...) + fixtureIPv4V2 = append(lengthV4Bytes, fixtureIPv4Address...) + fixtureIPv4V2Padded = append(append(lengthPaddedBytes, fixtureIPv4Address...), make([]byte, lengthPadded-lengthV4)...) + fixtureIPv6Address = append(addressesIPv6, ports...) + fixtureIPv6V2 = append(lengthV6Bytes, fixtureIPv6Address...) + fixtureIPv6V2Padded = append(append(lengthPaddedBytes, fixtureIPv6Address...), make([]byte, lengthPadded-lengthV6)...) + + // Arbitrary bytes following proxy bytes + arbitraryTailBytes = []byte{'\x99', '\x97', '\x98'} +) + +var invalidParseV2Tests = []struct { + reader *bufio.Reader + expectedError error +}{ + { + newBufioReader(SIGV2[2:]), + io.EOF, + }, + { + newBufioReader([]byte(NO_PROTOCOL)), + ErrNoProxyProtocol, + }, + { + newBufioReader(SIGV2), + ErrCantReadProtocolVersionAndCommand, + }, + { + newBufioReader(append(SIGV2, invalidRune)), + ErrUnsupportedProtocolVersionAndCommand, + }, + { + newBufioReader(append(SIGV2, PROXY)), + ErrCantReadAddressFamilyAndProtocol, + }, + { + newBufioReader(append(SIGV2, PROXY, invalidRune)), + ErrUnsupportedAddressFamilyAndProtocol, + }, + { + newBufioReader(append(SIGV2, PROXY, TCPv4)), + ErrCantReadLength, + }, + { + newBufioReader(append(SIGV2, PROXY, TCPv4, invalidRune)), + ErrCantReadLength, + }, + { + newBufioReader(append(append(SIGV2, PROXY, TCPv4), lengthV4Bytes...)), + ErrInvalidLength, + }, + { + newBufioReader(append(append(SIGV2, PROXY, TCPv6), lengthV6Bytes...)), + ErrInvalidLength, + }, + { + newBufioReader(append(append(append(SIGV2, PROXY, TCPv4), lengthEmptyBytes...), fixtureIPv6Address...)), + ErrInvalidLength, + }, + { + newBufioReader(append(append(append(SIGV2, PROXY, TCPv6), lengthV6Bytes...), fixtureIPv4Address...)), + ErrInvalidLength, + }, +} + +func TestParseV2Invalid(t *testing.T) { + for i, tt := range invalidParseV2Tests { + if _, err := Read(tt.reader); err != tt.expectedError { + t.Fatalf("TestParseV2Invalid: case %d: expected %v, actual %v", i, tt.expectedError, err) + } + } +} + +var validParseAndWriteV2Tests = []struct { + reader *bufio.Reader + expectedHeader *Header +}{ + // LOCAL + { + newBufioReader(append(SIGV2, LOCAL)), + &Header{ + Version: 2, + Command: LOCAL, + }, + }, + // PROXY TCP IPv4 + { + newBufioReader(append(append(SIGV2, PROXY, TCPv4), fixtureIPv4V2...)), + &Header{ + Version: 2, + Command: PROXY, + TransportProtocol: TCPv4, + SourceAddress: v4addr, + DestinationAddress: v4addr, + SourcePort: PORT, + DestinationPort: PORT, + }, + }, + // PROXY TCP IPv6 + { + newBufioReader(append(append(SIGV2, PROXY, TCPv6), fixtureIPv6V2...)), + &Header{ + Version: 2, + Command: PROXY, + TransportProtocol: TCPv6, + SourceAddress: v6addr, + DestinationAddress: v6addr, + SourcePort: PORT, + DestinationPort: PORT, + }, + }, + // PROXY UDP IPv4 + { + newBufioReader(append(append(SIGV2, PROXY, UDPv4), fixtureIPv4V2...)), + &Header{ + Version: 2, + Command: PROXY, + TransportProtocol: UDPv4, + SourceAddress: v4addr, + DestinationAddress: v4addr, + SourcePort: PORT, + DestinationPort: PORT, + }, + }, + // PROXY UDP IPv6 + { + newBufioReader(append(append(SIGV2, PROXY, UDPv6), fixtureIPv6V2...)), + &Header{ + Version: 2, + Command: PROXY, + TransportProtocol: UDPv6, + SourceAddress: v6addr, + DestinationAddress: v6addr, + SourcePort: PORT, + DestinationPort: PORT, + }, + }, + // TODO add tests for Unix stream and datagram +} + +func TestParseV2Valid(t *testing.T) { + for _, tt := range validParseAndWriteV2Tests { + header, err := Read(tt.reader) + if err != nil { + t.Fatal("TestParseV2Valid: unexpected error", err.Error()) + } + if !header.EqualTo(tt.expectedHeader) { + t.Fatalf("TestParseV2Valid: expected %#v, actual %#v", tt.expectedHeader, header) + } + } +} + +func TestWriteV2Valid(t *testing.T) { + for _, tt := range validParseAndWriteV2Tests { + var b bytes.Buffer + w := bufio.NewWriter(&b) + if _, err := tt.expectedHeader.WriteTo(w); err != nil { + t.Fatal("TestWriteVersion2: Unexpected error ", err) + } + w.Flush() + + // Read written bytes to validate written header + r := bufio.NewReader(&b) + newHeader, err := Read(r) + if err != nil { + t.Fatal("TestWriteVersion2: Unexpected error ", err) + } + + if !newHeader.EqualTo(tt.expectedHeader) { + t.Fatalf("TestWriteVersion2: expected %#v, actual %#v", tt.expectedHeader, newHeader) + } + } +} + +var validParseV2PaddedTests = []struct { + value []byte + expectedHeader *Header +}{ + // PROXY TCP IPv4 + { + append(append(SIGV2, PROXY, TCPv4), fixtureIPv4V2Padded...), + &Header{ + Version: 2, + Command: PROXY, + TransportProtocol: TCPv4, + SourceAddress: v4addr, + DestinationAddress: v4addr, + SourcePort: PORT, + DestinationPort: PORT, + }, + }, + // PROXY TCP IPv6 + { + append(append(SIGV2, PROXY, TCPv6), fixtureIPv6V2Padded...), + &Header{ + Version: 2, + Command: PROXY, + TransportProtocol: TCPv6, + SourceAddress: v6addr, + DestinationAddress: v6addr, + SourcePort: PORT, + DestinationPort: PORT, + }, + }, + // PROXY UDP IPv4 + { + append(append(SIGV2, PROXY, UDPv4), fixtureIPv4V2Padded...), + &Header{ + Version: 2, + Command: PROXY, + TransportProtocol: UDPv4, + SourceAddress: v4addr, + DestinationAddress: v4addr, + SourcePort: PORT, + DestinationPort: PORT, + }, + }, + // PROXY UDP IPv6 + { + append(append(SIGV2, PROXY, UDPv6), fixtureIPv6V2Padded...), + &Header{ + Version: 2, + Command: PROXY, + TransportProtocol: UDPv6, + SourceAddress: v6addr, + DestinationAddress: v6addr, + SourcePort: PORT, + DestinationPort: PORT, + }, + }, +} + +func TestParseV2Padded(t *testing.T) { + for _, tt := range validParseV2PaddedTests { + reader := newBufioReader(append(tt.value, arbitraryTailBytes...)) + + newHeader, err := Read(reader) + if err != nil { + t.Fatal("TestParseV2Padded: Unexpected error ", err) + } + if !newHeader.EqualTo(tt.expectedHeader) { + t.Fatalf("TestParseV2Padded: expected %#v, actual %#v", tt.expectedHeader, newHeader) + } + + // Check that remaining padding bytes have been flushed + nextBytes, err := reader.Peek(len(arbitraryTailBytes)) + if err != nil { + t.Fatal("TestParseV2Padded: Unexpected error ", err) + } + if !reflect.DeepEqual(nextBytes, arbitraryTailBytes) { + t.Fatalf("TestParseV2Padded: expected %#v, actual %#v", arbitraryTailBytes, nextBytes) + } + } +} + +func newBufioReader(b []byte) *bufio.Reader { + return bufio.NewReader(bytes.NewReader(b)) +} diff --git a/bfe_proxy/version_cmd.go b/bfe_proxy/version_cmd.go new file mode 100644 index 000000000..62dd2e544 --- /dev/null +++ b/bfe_proxy/version_cmd.go @@ -0,0 +1,67 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// Copyright (c) pires. +// +// 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 bfe_proxy + +// ProtocolVersionAndCommand represents proxy protocol version and command. +type ProtocolVersionAndCommand byte + +const ( + LOCAL = '\x20' + PROXY = '\x21' +) + +var supportedCommand = map[ProtocolVersionAndCommand]bool{ + LOCAL: true, + PROXY: true, +} + +// IsLocal returns true if the protocol version is \x2 and command is LOCAL, false otherwise. +func (pvc ProtocolVersionAndCommand) IsLocal() bool { + return 0x20 == pvc&0xF0 && 0x00 == pvc&0x0F +} + +// IsProxy returns true if the protocol version is \x2 and command is PROXY, false otherwise. +func (pvc ProtocolVersionAndCommand) IsProxy() bool { + return 0x20 == pvc&0xF0 && 0x01 == pvc&0x0F +} + +// IsUnspec returns true if the protocol version or command is unspecified, false otherwise. +func (pvc ProtocolVersionAndCommand) IsUnspec() bool { + return !(pvc.IsLocal() || pvc.IsProxy()) +} + +func (pvc ProtocolVersionAndCommand) toByte() byte { + if pvc.IsLocal() { + return LOCAL + } else if pvc.IsProxy() { + return PROXY + } + + return LOCAL +} diff --git a/bfe_proxy/version_cmd_test.go b/bfe_proxy/version_cmd_test.go new file mode 100644 index 000000000..f0e98ba1a --- /dev/null +++ b/bfe_proxy/version_cmd_test.go @@ -0,0 +1,71 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// Copyright (c) pires. +// +// 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 bfe_proxy + +import ( + "testing" +) + +func TestLocal(t *testing.T) { + b := byte(LOCAL) + if ProtocolVersionAndCommand(b).IsUnspec() { + t.Fail() + } + if !ProtocolVersionAndCommand(b).IsLocal() { + t.Fail() + } + if ProtocolVersionAndCommand(b).IsProxy() { + t.Fail() + } + if ProtocolVersionAndCommand(b).toByte() != b { + t.Fail() + } +} + +func TestProxy(t *testing.T) { + b := byte(PROXY) + if ProtocolVersionAndCommand(b).IsUnspec() { + t.Fail() + } + if ProtocolVersionAndCommand(b).IsLocal() { + t.Fail() + } + if !ProtocolVersionAndCommand(b).IsProxy() { + t.Fail() + } + if ProtocolVersionAndCommand(b).toByte() != b { + t.Fail() + } +} + +func TestInvalidProtocolVersion(t *testing.T) { + if !ProtocolVersionAndCommand(0x00).IsUnspec() { + t.Fail() + } +} diff --git a/bfe_server/bfe_listener.go b/bfe_server/bfe_listener.go new file mode 100644 index 000000000..3877576fe --- /dev/null +++ b/bfe_server/bfe_listener.go @@ -0,0 +1,99 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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. + +// BfeListener is a wapper of TCP listener which accept connections behind +// a load balancer (BGW/PROXY/NONE) +// +// Note: The TLS listener is wired together like: +// 1. TCP listener +// 2. BFE listener (BGW/PROXY) +// 3. TLS listener + +package bfe_server + +import ( + "net" + "time" +) + +import ( + "github.com/baidu/bfe/bfe_config/bfe_conf" + "github.com/baidu/bfe/bfe_proxy" + "github.com/baidu/bfe/bfe_util" +) + +import ( + "github.com/baidu/go-lib/log" +) + +// BfeListener is used to wrap an underlying TCP listener, which accept connections +// behind a layer4 load balancer (BGW/PROXY) +type BfeListener struct { + // Listener is the underlying tcp listener + Listener net.Listener + + // BalancerType is the type of Layer4 load balancer + BalancerType string + + // ProxyHeaderTimeout Optionally specifies the timeout value to + // receive the Proxy Protocol Header. Zero means no timeout. + ProxyHeaderTimeout time.Duration + + // ProxyHeaderLimit Optionally specifies the maximum bytes to + // receive the Proxy Protocol Header. Zero means default value. + ProxyHeaderLimit int64 +} + +// NewBfeListener return bfe listener according to config +func NewBfeListener(listener net.Listener, config bfe_conf.BfeConfig) *BfeListener { + l := new(BfeListener) + l.Listener = listener + l.BalancerType = config.Server.Layer4LoadBalancer + l.ProxyHeaderTimeout = time.Duration(config.Server.ClientReadTimeout) * time.Second + l.ProxyHeaderLimit = int64(config.Server.MaxProxyHeaderBytes) + + return l +} + +// Accept implements the Accept method in the Listener interface; +// it waits for the next call and returns a generic net.Conn. +func (l *BfeListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + log.Logger.Debug("BfeListener: accept error %s", err) + return nil, err + } + + switch l.BalancerType { + case bfe_conf.BALANCER_BGW: + conn = bfe_util.NewBgwConn(conn.(*net.TCPConn)) + log.Logger.Debug("BfeListener: accept connection via BGW") + + case bfe_conf.BALANCER_PROXY: + conn = bfe_proxy.NewConn(conn, l.ProxyHeaderTimeout, l.ProxyHeaderLimit) + log.Logger.Debug("BfeListener: accept connection via PROXY") + } + + return conn, nil +} + +// Close closes the underlying listener. +func (l *BfeListener) Close() error { + return l.Listener.Close() +} + +// Addr returns the underlying listener's network address. +func (l *BfeListener) Addr() net.Addr { + return l.Listener.Addr() +} diff --git a/bfe_server/bfe_server.go b/bfe_server/bfe_server.go index 98fba89e2..1ef279288 100644 --- a/bfe_server/bfe_server.go +++ b/bfe_server/bfe_server.go @@ -43,7 +43,6 @@ import ( "github.com/baidu/bfe/bfe_spdy" "github.com/baidu/bfe/bfe_stream" "github.com/baidu/bfe/bfe_tls" - "github.com/baidu/bfe/bfe_util" "github.com/baidu/bfe/bfe_util/signal_table" "github.com/baidu/bfe/bfe_websocket" ) @@ -312,12 +311,6 @@ func (srv *BfeServer) initTLSNextProtoHandler() { bfe_http2.EnableLargeConnRecvWindow() } -func (srv *BfeServer) InitLayer4InfoFetcher() { - if srv.Config.Server.Layer4LoadBalancer == "BGW" { - bfe_util.InitLayer4InfoFetcher(new(bfe_util.BGWInfoFetcher)) - } -} - func (srv *BfeServer) InitModules(confRoot string) error { return srv.Modules.Init(srv.CallBacks, srv.Monitor.WebHandlers, confRoot) } diff --git a/bfe_server/bfe_server_init.go b/bfe_server/bfe_server_init.go index e979c2e6d..63a1c48a8 100644 --- a/bfe_server/bfe_server_init.go +++ b/bfe_server/bfe_server_init.go @@ -43,7 +43,6 @@ func StartUp(cfg bfe_conf.BfeConfig, version string, confRoot string) error { // create bfe server bfeServer := NewBfeServer(cfg, lnMap, version) - bfeServer.InitLayer4InfoFetcher() // initial http err = bfeServer.InitHttp() @@ -129,6 +128,9 @@ func createListeners(config bfe_conf.BfeConfig) (map[string]net.Listener, error) if err != nil { return nil, err } + + // wrap underlying listener according to balancer type + listener = NewBfeListener(listener, config) lnMap[proto] = listener log.Logger.Info("createListeners(): begin to listen port[:%d]", port) } diff --git a/bfe_server/find_location.go b/bfe_server/find_location.go index 45e642965..b7d9a1c84 100644 --- a/bfe_server/find_location.go +++ b/bfe_server/find_location.go @@ -113,7 +113,7 @@ func (srv *BfeServer) Balance(e interface{}) (*backend.BfeBackend, error) { // create pesudo request session := bfe_basic.NewSession(conn) - vip, vport, err := bfe_util.GetVipAndPort(conn) + vip, vport, err := bfe_util.GetVipPort(conn) if err == nil { session.Vip = vip session.Vport = vport diff --git a/bfe_server/http_conn.go b/bfe_server/http_conn.go index 8ed7961ae..06be0aea8 100644 --- a/bfe_server/http_conn.go +++ b/bfe_server/http_conn.go @@ -147,10 +147,10 @@ func newConn(rwc net.Conn, srv *BfeServer) (c *conn, err error) { br := srv.BufioCache.newBufioReader(c.lr) bw := srv.BufioCache.newBufioWriterSize(c.rwc, 4<<10) c.buf = bfe_bufio.NewReadWriter(br, bw) + c.reqSN = 0 c.session = bfe_basic.NewSession(rwc) - c.reqSN = 0 - vip, vport, err := bfe_util.GetVipAndPort(rwc) + vip, vport, err := bfe_util.GetVipPort(rwc) if err == nil { c.session.Vip = vip c.session.Vport = vport @@ -244,8 +244,8 @@ const rstAvoidanceDelay = 500 * time.Millisecond // See http://golang.org/issue/3595 func (c *conn) closeWriteAndWait() { c.finalFlush() - if tcp, ok := c.rwc.(*net.TCPConn); ok { - tcp.CloseWrite() + if cw, ok := c.rwc.(bfe_util.CloseWriter); ok { + cw.CloseWrite() } time.Sleep(rstAvoidanceDelay) } diff --git a/bfe_server/server_status.go b/bfe_server/server_status.go index e8f68b76e..84de3e963 100644 --- a/bfe_server/server_status.go +++ b/bfe_server/server_status.go @@ -26,6 +26,7 @@ import ( "github.com/baidu/bfe/bfe_http" "github.com/baidu/bfe/bfe_http2" "github.com/baidu/bfe/bfe_module" + "github.com/baidu/bfe/bfe_proxy" "github.com/baidu/bfe/bfe_spdy" "github.com/baidu/bfe/bfe_stream" "github.com/baidu/bfe/bfe_tls" @@ -51,6 +52,10 @@ const ( ) type ServerStatus struct { + // for proxy protocol + ProxyProtocolState *bfe_proxy.ProxyState + ProxyProtocolMetrics metrics.Metrics + // for tls protocol TlsState *bfe_tls.TlsState TlsMetrics metrics.Metrics @@ -99,6 +104,7 @@ func NewServerStatus() *ServerStatus { m := new(ServerStatus) // initialize counter state + m.ProxyProtocolState = bfe_proxy.GetProxyState() m.TlsState = bfe_tls.GetTlsState() m.SpdyState = bfe_spdy.GetSpdyState() m.Http2State = bfe_http2.GetHttp2State() @@ -109,6 +115,7 @@ func NewServerStatus() *ServerStatus { m.BalState = bal.GetBalErrState() // initialize metrics + m.ProxyProtocolMetrics.Init(m.ProxyProtocolState, KP_PROXY_STATE, 0) m.TlsMetrics.Init(m.TlsState, KP_PROXY_STATE, 0) m.SpdyMetrics.Init(m.SpdyState, KP_PROXY_STATE, 0) m.Http2Metrics.Init(m.Http2State, KP_PROXY_STATE, 0) @@ -140,6 +147,16 @@ func NewServerStatus() *ServerStatus { return m } +func (srv *BfeServer) proxyProtocolStateGetAll(params map[string][]string) ([]byte, error) { + s := srv.serverStatus.ProxyProtocolMetrics.GetAll() + return s.Format(params) +} + +func (srv *BfeServer) proxyProtocolStateGetDiff(params map[string][]string) ([]byte, error) { + s := srv.serverStatus.ProxyProtocolMetrics.GetDiff() + return s.Format(params) +} + func (srv *BfeServer) tlsStateGetAll(params map[string][]string) ([]byte, error) { s := srv.serverStatus.TlsMetrics.GetAll() return s.Format(params) diff --git a/bfe_server/web_server.go b/bfe_server/web_server.go index 6605b3fc2..0e26e87a3 100644 --- a/bfe_server/web_server.go +++ b/bfe_server/web_server.go @@ -66,6 +66,10 @@ func (m *BfeMonitor) monitorHandlers() map[string]interface{} { "bal_state": m.srv.balStateGetAll, "bal_state_diff": m.srv.balStateGetDiff, + // for proxy protocol + "proxy_protocol_state": m.srv.proxyProtocolStateGetAll, + "proxy_protocol_state_diff": m.srv.proxyProtocolStateGetDiff, + // for tls "tls_state": m.srv.tlsStateGetAll, "tls_state_diff": m.srv.tlsStateGetDiff, diff --git a/bfe_util/get_l4lb_info.go b/bfe_util/get_l4lb_info.go index 3e6663dd1..b34f55bf4 100644 --- a/bfe_util/get_l4lb_info.go +++ b/bfe_util/get_l4lb_info.go @@ -19,89 +19,278 @@ import ( "errors" "fmt" "net" + "sync" + "syscall" + "time" ) import ( - sys "golang.org/x/sys/unix" + "github.com/baidu/go-lib/log" +) + +import ( + "github.com/baidu/bfe/bfe_tls" ) const ( - OptionVIP = 254 // ipv4 vip from tcp option - OptionVIP6 = 240 // ipv6 vip from tcp option + TCP_OPT_CIP_ANY = 230 // get cip from tcp option + TCP_OPT_VIP_ANY = 229 // get vip from tcp option +) + +var ( + ErrAddressFormat = errors.New("address format error") ) -type Layer4InfoFetcher interface { - // GetVirtualAddr returns virtual ip and port of given conn - GetVirtualAddr(conn net.Conn) (net.IP, int, error) +// GetVipPort return vip and port for given conn +func GetVipPort(conn net.Conn) (net.IP, int, error) { + // get underlying bfe conn, the given net.Conn may be wired like: + // - TLS Connection (optional) + // - BFE Connection (BGW/PROXY, optional) + // - TCP Connection + if tc, ok := conn.(*bfe_tls.Conn); ok { + conn = tc.GetNetConn() + } + + // get virtual vip + if af, ok := conn.(AddressFetcher); ok { + vaddr := af.VirtualAddr() + if vaddr == nil { + return nil, 0, fmt.Errorf("vip unknown") + } + return ParseIpAndPort(vaddr.String()) + } + + return getVipPortViaBGW(conn) } -type BGWInfoFetcher struct{} +// GetVip return vip for given conn +func GetVip(conn net.Conn) net.IP { + vip, _, err := GetVipPort(conn) + if err != nil { + return nil + } + return vip +} -func (f *BGWInfoFetcher) GetVirtualAddr(conn net.Conn) (net.IP, int, error) { - // get underlying tcp conn - tcpConn, err := GetTCPConn(conn) +// getVipPortViaBGW gets vip/port from tcp conn via BGW +func getVipPortViaBGW(conn net.Conn) (net.IP, int, error) { + // get conn fd + f, err := GetConnFile(conn) if err != nil { return nil, 0, err } + defer f.Close() + fd := int(f.Fd()) - // get fd - file, err := tcpConn.File() // copy of the underlying os.File - defer file.Close() + // get vip/port + rawAddr, err := GetsockoptMutiByte(fd, syscall.IPPROTO_TCP, TCP_OPT_VIP_ANY) if err != nil { + log.Logger.Debug("GetsockoptMutiByte() fail: TCP_OPT_VIP_ANY: %v", err) return nil, 0, err } - fd := int(file.Fd()) + log.Logger.Debug("getVipPortViaBGW(): VIP raw : %v", rawAddr) + + // parse vip/port + return parseSockAddr(rawAddr) +} - // get options - var opt int - remoteAddr := conn.RemoteAddr().(*net.TCPAddr) - if len(remoteAddr.IP.To4()) == 4 { - opt = OptionVIP - } else { - opt = OptionVIP6 +// getCipPortViaBGW gets cip/port from tcp conn via BGW +func getCipPortViaBGW(conn net.Conn) (net.IP, int, error) { + // get conn fd + f, err := GetConnFile(conn) + if err != nil { + return nil, 0, err } + defer f.Close() + fd := int(f.Fd()) - // get vip and port raw info - rawInfo, err := GetsockoptMutiByte(fd, sys.IPPROTO_TCP, opt) + // get cip/port + rawAddr, err := GetsockoptMutiByte(fd, syscall.IPPROTO_TCP, TCP_OPT_CIP_ANY) if err != nil { + log.Logger.Debug("GetsockoptMutiByte fail: TCP_OPT_CIP_ANY: %v", err) return nil, 0, err } + log.Logger.Debug("getCipPortViaBGW(): CIP raw : %v", rawAddr) + + // parse cip/port + return parseSockAddr(rawAddr) +} - // convert raw info to net.IP +// parseSockAddr parses addr from data with format sockaddr_in/sockaddr_in6 +// +// Note: Address format of sockaddr_in: +// struct sockaddr_in { +// sa_family_t sin_family; /* address family: AF_INET */ +// in_port_t sin_port; /* port in network byte order */ +// struct in_addr sin_addr; /* internet address */ +// }; +// struct in_addr { +// uint32_t s_addr; /* address in network byte order */ +// }; +// +// Note: Address format of sockaddr_in6: +// struct sockaddr_in6 { +// sa_family_t sin6_family; /* AF_INET6 */ +// in_port_t sin6_port; /* port number */ +// uint32_t sin6_flowinfo; /* IPv6 flow information */ +// struct in6_addr sin6_addr; /* IPv6 address */ +// uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */ +// }; +// struct in6_addr { +// unsigned char s6_addr[16]; /* IPv6 address */ +// }; +// +func parseSockAddr(rawAddr []byte) (net.IP, int, error) { + family := NativeUint16(rawAddr[0:2]) + + // parse ip var ip net.IP - if opt == OptionVIP { - ip = net.IPv4(rawInfo[4], rawInfo[5], rawInfo[6], rawInfo[7]).To4() - } else { - ip = net.IP(rawInfo[8:24]).To16() + switch family { + case syscall.AF_INET: + ip = net.IPv4(rawAddr[4], rawAddr[5], rawAddr[6], rawAddr[7]).To4() + case syscall.AF_INET6: + ip = net.IP(rawAddr[8:24]).To16() + default: + return nil, 0, ErrAddressFormat } if ip == nil { - return nil, 0, errors.New("ip format error") + return nil, 0, ErrAddressFormat } - // get port - port := binary.BigEndian.Uint16(rawInfo[2:4]) + // parse port + port := binary.BigEndian.Uint16(rawAddr[2:4]) + return ip, int(port), nil } -var layer4InfoFetcher Layer4InfoFetcher +var _ AddressFetcher = new(BgwConn) + +// BgwConn is used to wrap an underlying tcp connection which +// may be speaking the bgw Protocol. If it is, the RemoteAddr() will +// return the address of the client. +type BgwConn struct { + conn *net.TCPConn -func InitLayer4InfoFetcher(fetcher Layer4InfoFetcher) { - layer4InfoFetcher = fetcher + // srcAddr is address of real client + // Note: srcAddr is different from conn.RemoteAddr() under BGW64 + srcAddr *net.TCPAddr + + // dstAddr is address of virtual server + dstAddr *net.TCPAddr + once sync.Once } -// GetVipAndPort returns vip and port of given conn -func GetVipAndPort(conn net.Conn) (net.IP, int, error) { - if layer4InfoFetcher != nil { - return layer4InfoFetcher.GetVirtualAddr(conn) +// NewBgwConn is used to wrap a net.TCPConn via BGW +func NewBgwConn(conn *net.TCPConn) *BgwConn { + bConn := &BgwConn{ + conn: conn, } - return nil, 0, fmt.Errorf("No layer4 load balancer configed") + return bConn } -// GetVip returns vip of given conn -func GetVip(conn net.Conn) net.IP { - vip, _, err := GetVipAndPort(conn) +// Read reads data from the connection. +func (c *BgwConn) Read(b []byte) (int, error) { + return c.conn.Read(b) +} + +// Write writes data to the connection. +func (c *BgwConn) Write(b []byte) (int, error) { + return c.conn.Write(b) +} + +// Close closes the connection. +func (c *BgwConn) Close() error { + return c.conn.Close() +} + +func (c *BgwConn) CloseWrite() error { + return c.conn.CloseWrite() +} + +// LocalAddr returns the local network address. +func (c *BgwConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +// RemoteAddr returns the address of the client if the bgw +// protocol is being used, otherwise just returns the address of +// the socket peer. +func (c *BgwConn) RemoteAddr() net.Addr { + c.checkTtmInfoOnce() + if c.srcAddr != nil { + return c.srcAddr + } + return c.conn.RemoteAddr() +} + +// VirtualAddr returns the visited address by client +func (c *BgwConn) VirtualAddr() net.Addr { + c.checkTtmInfoOnce() + if c.dstAddr != nil { + return c.dstAddr + } + return nil +} + +// BalancerAddr returns the address of balancer +func (c *BgwConn) BalancerAddr() net.Addr { + // Note: Not implement, just ignore + return nil +} + +// GetNetConn returns the underlying connection +func (c *BgwConn) GetNetConn() net.Conn { + return c.conn +} + +// SetDeadline implements the Conn.SetDeadline method +func (c *BgwConn) SetDeadline(t time.Time) error { + return c.conn.SetDeadline(t) +} + +// SetReadDeadline implements the Conn.SetReadDeadline method +func (c *BgwConn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} + +// SetWriteDeadline implements the Conn.SetWriteDeadline method +func (c *BgwConn) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) +} + +func (c *BgwConn) checkTtmInfoOnce() { + c.once.Do(func() { + c.checkTtmInfo() + }) +} + +func (c *BgwConn) checkTtmInfo() { + c.initSrcAddr() + c.initDstAddr() +} + +func (c *BgwConn) initSrcAddr() { + cip, cport, err := getCipPortViaBGW(c) if err != nil { - return nil + log.Logger.Debug("BgwConn getCipPortViaBGW failed, err:%s", err.Error()) + return + } + + c.srcAddr = &net.TCPAddr{ + IP: cip, + Port: cport, + } +} + +func (c *BgwConn) initDstAddr() { + vip, vport, err := getVipPortViaBGW(c) + if err != nil { + log.Logger.Debug("BgwConn getVipPortViaBGW failed, err:%s", err.Error()) + return + } + + c.dstAddr = &net.TCPAddr{ + IP: vip, + Port: vport, } - return vip } diff --git a/bfe_util/get_net_info.go b/bfe_util/get_net_info.go index 24cbe5704..52f6cd0c1 100644 --- a/bfe_util/get_net_info.go +++ b/bfe_util/get_net_info.go @@ -15,8 +15,10 @@ package bfe_util import ( + "encoding/binary" "fmt" "net" + "os" "reflect" ) @@ -28,6 +30,32 @@ import ( sys "golang.org/x/sys/unix" ) +// CloseWriter is the interface that wraps the basic CloseWrite method. +type CloseWriter interface { + CloseWrite() error +} + +// AddressFetcher is the interface that group the address related method. +type AddressFetcher interface { + // RemoteAddr returns the remote network address. + RemoteAddr() net.Addr + + // LocalAddr returns the local network address. + LocalAddr() net.Addr + + // VirtualAddr returns the virtual network address. + VirtualAddr() net.Addr + + // BalancerAddr return the balancer network address. May be nil. + BalancerAddr() net.Addr +} + +// ConnFetcher is the interface that wrap the GetNetConn +type ConnFetcher interface { + // GetNetConn returns the underlying net.Conn + GetNetConn() net.Conn +} + // GetTCPConn returns underlying TCPConn of given conn. func GetTCPConn(conn net.Conn) (*net.TCPConn, error) { switch conn.(type) { @@ -41,9 +69,49 @@ func GetTCPConn(conn net.Conn) (*net.TCPConn, error) { } } +// GetConnFile get a copy of underlying os.File of tcp conn +func GetConnFile(conn net.Conn) (*os.File, error) { + // get underlying net.Conn + if c, ok := conn.(ConnFetcher); ok { + conn = c.GetNetConn() + return GetConnFile(conn) + } + + // the fd is tcpConn.fd.sysfd + if c, ok := conn.(*net.TCPConn); ok { + return c.File() + } + + return nil, fmt.Errorf("GetConnFd(): conn type not support %s", reflect.TypeOf(conn)) +} + // GetsockoptMutiByte returns the value of the socket option opt for the // socket associated with fd at the given socket level. func GetsockoptMutiByte(fd, level, opt int) ([]byte, error) { val, err := sys.GetsockoptString(fd, level, opt) return []byte(val), err } + +// ParseIpAndPort return parsed ip address +func ParseIpAndPort(addr string) (net.IP, int, error) { + taddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return nil, 0, err + } + return taddr.IP, taddr.Port, nil +} + +func NativeUint16(data []byte) uint16 { + if IsBigEndian() { + return binary.BigEndian.Uint16(data) + } else { + return binary.LittleEndian.Uint16(data) + } +} + +// IsBigEndian check machine is big endian or not +func IsBigEndian() bool { + var i int32 = 0x12345678 + var b byte = byte(i) + return b == 0x12 +} From a9a8b06a979aca53ad09d71fa92c568352e62e58 Mon Sep 17 00:00:00 2001 From: Sijie Yang Date: Fri, 30 Aug 2019 22:31:13 +0800 Subject: [PATCH 16/55] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 23 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..08406e6df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,23 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..bbcbbe7d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 5ae5a08b8a67f70e3c641ab93354aaa2c6a7e86c Mon Sep 17 00:00:00 2001 From: Sijie Yang Date: Fri, 30 Aug 2019 22:45:24 +0800 Subject: [PATCH 17/55] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..12d0c8587 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at bfe-osc@baidu.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq From dfafa1e28ebcfac20588b00608c2619f18469e63 Mon Sep 17 00:00:00 2001 From: Sijie Yang Date: Sat, 31 Aug 2019 16:30:54 +0800 Subject: [PATCH 18/55] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f27418a30..a90620ad9 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,9 @@ BFE is an open-source layer 7 load balancer derived from proprietary Baidu Front ## Discussion - Issue: https://github.com/baidu/bfe/issues +## Contact +- Email:bfe-osc@baidu.com + ## License BFE is under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details. From 84f6cc52a9b02e4e20c4204150700f16639b8ebf Mon Sep 17 00:00:00 2001 From: xiaofei0800 Date: Mon, 2 Sep 2019 02:54:57 +0000 Subject: [PATCH 19/55] Update comments. --- bfe_bufio/bufio_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bfe_bufio/bufio_test.go b/bfe_bufio/bufio_test.go index 9e19838aa..e2da4a888 100644 --- a/bfe_bufio/bufio_test.go +++ b/bfe_bufio/bufio_test.go @@ -813,8 +813,8 @@ type errorWriterToTest struct { expected error } -func (r errorWriterToTest) Read(p []byte) (int, error) { - return len(p) * r.rn, r.rerr +func (w errorWriterToTest) Read(p []byte) (int, error) { + return len(p) * w.rn, w.rerr } func (w errorWriterToTest) Write(p []byte) (int, error) { @@ -879,8 +879,8 @@ func (r errorReaderFromTest) Read(p []byte) (int, error) { return len(p) * r.rn, r.rerr } -func (w errorReaderFromTest) Write(p []byte) (int, error) { - return len(p) * w.wn, w.werr +func (r errorReaderFromTest) Write(p []byte) (int, error) { + return len(p) * r.wn, r.werr } var errorReaderFromTests = []errorReaderFromTest{ From ce25ffdb1b5ffa439530647dc7c4f4c7970521d2 Mon Sep 17 00:00:00 2001 From: yangsijie Date: Tue, 3 Sep 2019 09:03:33 +0800 Subject: [PATCH 20/55] Add release regulation --- docs/en_us/development/release-regulation.md | 51 ++++++++++++++++++++ docs/zh_cn/development/release-regulation.md | 38 +++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 docs/en_us/development/release-regulation.md create mode 100644 docs/zh_cn/development/release-regulation.md diff --git a/docs/en_us/development/release-regulation.md b/docs/en_us/development/release-regulation.md new file mode 100644 index 000000000..93d6aa2fe --- /dev/null +++ b/docs/en_us/development/release-regulation.md @@ -0,0 +1,51 @@ +# BFE Release Regulation +BFE development follows git-flow branching model and [Semantic Versioning](http://semver.org/). + +## Branch Regulation + +BFE development follows [git-flow](http://nvie.com/posts/a-successful-git-branching-model/), but makes some minor differences for github. + +* For the official bfe repository, developers should follow [git-flow](http://nvie.com/posts/a-successful-git-branching-model/). + + * 'master' branch is the stable branch. Latest commit of the 'master' branch is unit-tested and regression-tested. + + * 'develop' branch is the development branch. Every commit of the 'develop' branch is unit-tested, but not regression-tested. + + * 'release/vX.Y.Z' branch is the temporary branch created for release. The code on this branch is undergoing regression testing. + +* For the forked bfe repository, developers don't need to strictly abide the git-flow(http://nvie.com/posts/a-successful-git-branching-model/). Each branch of the forked repository is equivalent to feature branch. Specific Suggestions are as follows: + + * Developers synchronize 'develop' branches of the forked repository with that of the official repository. + + * Developers create 'feature' branch from 'develop' branch of the forked repository. + + * After completion of 'feature' branch development, developers submit 'Pull Request' to the official repository for code review. + + * During the review process, developers may continue to modify and submit code in their feature branches. + + * In addition, the 'bugfix' branch is also maintained in the developer's forked repository. Different from the feature branch, developers should submit 'Pull Request' from the 'bugfix' branch to 'master', 'develop' and possibly 'release/vX.Y.Z' branches of the official repository respectively. + + +## Release Regulation + +Follow the following procedures to release a new version: + +1. Create a new branch from the 'develop' branch with the name 'release/xX.Y.Z'. For example, `release/v0.10.0` + +1. Tag the version of the new branch with 'X.Y.Z-rc.N' (N is patch number). The first tag is'0.10.0-rc.1', the second tag is '0.10.0-rc.2', and so on. + +1. For the submission of this version, do the following: + + * Modify version information in 'Makefile'. + + * Test the functional correctness of the version. If it fails, fixing all the bugs in the 'release/vX.Y.Z' branch, and return to the second step with patch number added by 1. + +1. After the third step, merge the 'release/vX.Y.Z' branch into the master branch, and delete the 'release/vX.Y.Z' branch. Merge 'master' branches into the 'develop' branch. + +1. Tag the latest commit of the master branch with 'vX.Y.Z' and complete the writing of Release Note + +Note: + +* Once a release branch has been created, it is generally not allowed to merge 'release/vX.Y.Z' from the 'develop' branch. This ensures that the 'release/vX.Y.Z' branch is frozen, making it easy for QA to test. + +* When the 'release/vX.Y.Z' branch exists, merge the 'bugfix' branch into the 'master', 'develop' and 'release/vX.Y.Z' branches at the same time, if there are bugfix behaviors. diff --git a/docs/zh_cn/development/release-regulation.md b/docs/zh_cn/development/release-regulation.md new file mode 100644 index 000000000..9559c2190 --- /dev/null +++ b/docs/zh_cn/development/release-regulation.md @@ -0,0 +1,38 @@ +# BFE发行规范 + +BFE使用git-flow branching model做分支管理,使用[Semantic Versioning](http://semver.org/)标准表示BFE版本号。 + + +## 分支规范说明 + +BFE开发过程使用[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范,并适应github的特性做了一些区别。 + +* BFE的主版本库遵循[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范。其中: + * `master`分支为稳定(stable branch)版本分支。每一个`master`分支的版本都是经过单元测试和回归测试的版本。 + * `develop`分支为开发(develop branch)版本分支。每一个`develop`分支的版本都经过单元测试,但并没有经过回归测试。 + * `release/vX.Y.Z`分支为每一次Release时建立的临时分支。在这个阶段的代码正在经历回归测试。 + +* 开发者的fork版本库并不需要严格遵守[git-flow](http://nvie.com/posts/a-successful-git-branching-model/)分支规范,所有fork的版本库的所有分支都相当于特性分支。具体建议如下: + * 开发者fork的版本库使用`develop`分支同步主版本库的`develop`分支。 + * 开发者fork的版本库中,再基于`develop`版本fork出自己的功能分支。 + * 当功能分支开发完毕后,向BFE的主版本库提交`Pull Reuqest`,进而进行代码评审。 + * 在评审过程中,开发者修改自己的代码,可以继续在自己的功能分支提交代码。 + * 另外,`bugfix`分支也是在开发者自己的fork版本库维护,与功能分支不同的是,`bugfix`分支需要分别给主版本库的`master`、`develop`与可能有的`release/vX.Y.Z`分支,同时提起`Pull Request`。 + + +## 版本发布流程 + +BFE每次发新的版本,遵循以下流程: + +1. 从`develop`分支派生出新的分支,分支名为`release/vX.Y.Z`。例如,`release/v0.10.0` +1. 将新分支的版本打上tag,tag为`vX.Y.Z-rc.N` (N代表Patch号)。第一个tag为`v0.10.0-rc.1`,第二个为`v0.10.0-rc.2`,依次类推。 +1. 对这个版本的提交,做如下几个操作: + * 修改`Makefile`中的版本信息。 + * 测试版本的功能正确性。如果失败,在这个`release/vX.Y.Z`分支中修复所有bug,返回第二步并将Patch号加一。 +1. 第三步完成后,将`release/vX.Y.Z`分支合入master分支,并删除`release/vX.Y.Z`分支。同时再将`master`分支合入`develop`分支。 +1. 将master分支的合入commit打上tag,tag为`vX.Y.Z`,并完成Release Note的书写。 + +需要注意的是: + +* `release/vX.Y.Z`分支一旦建立,一般不允许再从`develop`分支合入`release/vX.Y.Z`。这样保证`release/vX.Y.Z`分支功能的封闭,方便测试人员测试BFE的行为。 +* 在`release/vX.Y.Z`分支存在的时候,如果有bugfix的行为,需要将bugfix的分支同时merge到`master`, `develop`和`release/vX.Y.Z`这三个分支。 From eb0008c595993886ba8de81744bc9f1cd0c1b816 Mon Sep 17 00:00:00 2001 From: yangsijie Date: Tue, 3 Sep 2019 13:35:09 +0800 Subject: [PATCH 21/55] Update docs for layer-4 load balancer --- bfe_config/bfe_conf/conf_basic.go | 2 +- conf/bfe.conf | 1 + docs/en_us/configuration/bfe.conf.md | 9 ++++++++- docs/zh_cn/configuration/bfe.conf.md | 10 ++++++++-- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/bfe_config/bfe_conf/conf_basic.go b/bfe_config/bfe_conf/conf_basic.go index 23de4fa02..231a48708 100644 --- a/bfe_config/bfe_conf/conf_basic.go +++ b/bfe_config/bfe_conf/conf_basic.go @@ -49,7 +49,7 @@ type ConfigBasic struct { GracefulShutdownTimeout int // graceful shutdown timeout, in seconds MaxHeaderBytes int // max header length in bytes in request MaxHeaderUriBytes int // max URI(in header) length in bytes in request - MaxProxyHeaderBytes int // max header lenght in bytes in Proxy protocol + MaxProxyHeaderBytes int // max header length in bytes in Proxy protocol KeepAliveEnabled bool // if false, client connection is shutdown disregard of http headers Modules []string // modules to load diff --git a/conf/bfe.conf b/conf/bfe.conf index da19d209d..6f06707de 100644 --- a/conf/bfe.conf +++ b/conf/bfe.conf @@ -9,6 +9,7 @@ monitorPort = 8299 # max number of CPUs to use (0 to use all CPUs) maxCpus = 0 +# type of layer-4 load balancer (PROXY/BGW/NONE) Layer4LoadBalancer = "" # tls handshake timeout, in seconds diff --git a/docs/en_us/configuration/bfe.conf.md b/docs/en_us/configuration/bfe.conf.md index fb4fe4dd0..149aef94e 100644 --- a/docs/en_us/configuration/bfe.conf.md +++ b/docs/en_us/configuration/bfe.conf.md @@ -12,7 +12,7 @@ bfe.conf is the core config file of BFE. | HttpsPort | Int | Listen port for HTTPS | | MonitorPort | Int | Listen port for monitor | | MaxCpus | Int | Max number of CPUs to use (0 to use all CPUs) | -| Layer4LoadBalancer | String | Type of layer-4 load balancer | +| Layer4LoadBalancer | String | Type of layer-4 load balancer (PROXY/BGW/NONE) | | TlsHandshakeTimeout | Int | TLS handshake timeout, in seconds | | ClientReadTimeout | Int | Read timeout of communicating with http client, in seconds | | ClientWriteTimeout | Int | Write timeout of communicating with http client, in seconds | @@ -80,6 +80,13 @@ monitorPort = 8299 # max number of CPUs to use (0 to use all CPUs) maxCpus = 0 +# type of layer-4 load balancer (PROXY/BGW/NONE) +# +# Note: +# - PROXY: layer-4 balancer talking the proxy protocol +# eg. F5 BigIP/Citrix ADC +# - BGW: Baidu GateWay +# - NONE: layer-4 balancer disabled layer4LoadBalancer = "" # tls handshake timeout, in seconds diff --git a/docs/zh_cn/configuration/bfe.conf.md b/docs/zh_cn/configuration/bfe.conf.md index 37331a5c8..25b83fd64 100644 --- a/docs/zh_cn/configuration/bfe.conf.md +++ b/docs/zh_cn/configuration/bfe.conf.md @@ -12,7 +12,7 @@ bfe.conf是BFE的核心配置。 | HttpsPort | Int | HTTPS流量监听端口 | | MonitorPort | Int | 监控流量监听端口 | | MaxCpus | Int | 最大使用CPU核数; 0代表使用所有CPU核 | -| Layer4LoadBalancer | String | 四层负载均衡器类型 | +| Layer4LoadBalancer | String | 四层负载均衡器类型 (PROXY/BGW/NONE | | TlsHandshakeTimeout | Int | TLS握手超时时间,单位为秒 | | ClientReadTimeout | Int | 读客户端超时时间,单位为秒 | | ClientWriteTimeout | Int | 写客户端超时时间,单位为秒 | @@ -79,7 +79,13 @@ monitorPort = 8299 # max number of CPUs to use (0 to use all CPUs) maxCpus = 0 -# type of layer-4 load balancer +# type of layer-4 load balancer (PROXY/BGW/NONE) +# +# Note: +# - PROXY: layer-4 balancer talking the proxy protocol +# eg. F5 BigIP/Citrix ADC +# - BGW: Baidu GateWay +# - NONE: layer-4 balancer disabled layer4LoadBalancer = "" # tls handshake timeout, in seconds From c1323c3226c3b5cdefe75caf6a7a21b7500dc4cf Mon Sep 17 00:00:00 2001 From: yangsijie Date: Fri, 6 Sep 2019 09:17:01 +0800 Subject: [PATCH 22/55] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 2ce7cbeb9..cfacc5434 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -10,5 +10,6 @@ | Qingxin Yang | yangqingxin1993 | | Sijie Yang | iyangsj | | Wensi Yang | tianxinheihei | +| Xiaofei Yu | xiaofei0800 | | Yang Liu | dut-yangliu | From 8e13ee063b88c21a450bb8827d752065a1fd75b8 Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Fri, 6 Sep 2019 09:47:18 +0800 Subject: [PATCH 23/55] update condition about request docs --- docs/en_us/condition/request/cookie.md | 50 +++++++ docs/en_us/condition/request/header.md | 55 ++++++++ docs/en_us/condition/request/ip.md | 42 ++++++ docs/en_us/condition/request/method.md | 14 ++ docs/en_us/condition/request/protocol.md | 5 + docs/en_us/condition/request/tag.md | 13 ++ docs/en_us/condition/request/uri.md | 130 ++++++++++++++++++ docs/zh_cn/condition/request/cookie.md | 54 +++++--- docs/zh_cn/condition/request/header.md | 53 ++++---- docs/zh_cn/condition/request/ip.md | 46 ++++--- docs/zh_cn/condition/request/method.md | 9 +- docs/zh_cn/condition/request/tag.md | 10 +- docs/zh_cn/condition/request/uri.md | 159 +++++++++++++---------- 13 files changed, 512 insertions(+), 128 deletions(-) create mode 100644 docs/en_us/condition/request/cookie.md create mode 100644 docs/en_us/condition/request/header.md create mode 100644 docs/en_us/condition/request/ip.md create mode 100644 docs/en_us/condition/request/method.md create mode 100644 docs/en_us/condition/request/protocol.md create mode 100644 docs/en_us/condition/request/tag.md create mode 100644 docs/en_us/condition/request/uri.md diff --git a/docs/en_us/condition/request/cookie.md b/docs/en_us/condition/request/cookie.md new file mode 100644 index 000000000..f3acbf454 --- /dev/null +++ b/docs/en_us/condition/request/cookie.md @@ -0,0 +1,50 @@ +# Cookie + +## Common Condition Primitive Parameter + +- key: String, the key in cookie +- patterns: String, representing multiple patterns, format is as "pattern1|pattern2" +- case_insensitive: Bool, if ignore the case-sensitive of value + +## Condition Primitive About Cookie + +- **req_cookie_key_in(patterns)** + - Judge if cookie key matches configured patterns + + ``` + # if cookie key is UID + req_cookie_key_in(“UID”) + ``` +- **req_cookie_value_in(key, patterns, case_insensitive)** + - Judge if value of key in cookie matches configured patterns + + ``` + # if the value of UID(case-insensitive) in cookie is XXX + req_cookie_value_in(“UID”, "XXX", true) + ``` +- **req_cookie_value_prefix_in(key, patterns, case_insensitive)** + + - Judge if value prefix of key in cookie matches configured patterns + + ``` + # if the value prefix of UID(case-insensitive) in cookie is XXX + req_cookie_value_prefix_in(“UID”, "XXX", true) + ``` +- **req_cookie_value_suffix_in(key, patterns, case_insensitive)** + + - Judge if value suffix of key in cookie matches configured patterns + + ``` + # if the value suffix of UID(case-insensitive) in cookie is XXX + req_cookie_value_suffix_in(“UID”, "XXX", true) + ``` +- **req_cookie_value_hash_in(key, patterns, case_insensitive)** + - Judge if value of key after hash in cookie matches configured patterns (value after hash is 0~9999) + + ``` + # if the value after hash of UID(case-insensitive) in cookie is 100 + req_cookie_value_hash_in(“UID”, “100”, true) + ``` + + + diff --git a/docs/en_us/condition/request/header.md b/docs/en_us/condition/request/header.md new file mode 100644 index 000000000..ebe7ce121 --- /dev/null +++ b/docs/en_us/condition/request/header.md @@ -0,0 +1,55 @@ +# Header + +## Common Condition Primitive Parameter + +- header_name: String, the key in header +- patterns: String, representing multiple patterns, format is as "pattern1|pattern2" +- case_insensitive: Bool, if ignore the case-sensitive of value + +## Condition Primitive About Header + +- **req_header_key_in(patterns)** + + - Judge if header key in matches configured patterns + + - **Note: each word in header key need to be capitalized** + + ``` +right:req_header_key_in(“Header-Test”) + wrong:req_header_key_in(“Header-test”), req_header_key_in(“header-test”), req_header_key_in(“header-Test”) + ``` + +- **req_header_value_in(header_name, patterns, case_insensitive)** + + - Judge if value of key in header matches configured patterns + + ``` + # if the value of Host in header is XXX.com + req_header_value_in("Host", "XXX.com", true) + ``` +- **req_header_value_prefix_in(header_name, patterns, case_insensitive)** + - Judge if value prefix of key in header matches configured patterns + + ``` + # if the value prefix of Host in header is XXX + req_header_prefix_value_in("Host", "XXX", true) + ``` +- **req_header_value_suffix_in(header_name, patterns, case_insensitive)** + - Judge if value suffix of key in header matches configured patterns + + ``` + # if the value suffix of Host in header is XXX + req_header_suffix_value_in("Host", "XXX", true) + ``` +- **req_header_value_hash_in(header_name, patterns, case_insensitive)** + + - Judge if value of key after hash in header matches configured patterns (value after hash is 0~9999) + + ``` + # if the value after hash of Host in header is 100~200 or 400 + req_header_value_hash_in("Host", "100-200|400", true) + ``` + + + + \ No newline at end of file diff --git a/docs/en_us/condition/request/ip.md b/docs/en_us/condition/request/ip.md new file mode 100644 index 000000000..d9998c823 --- /dev/null +++ b/docs/en_us/condition/request/ip.md @@ -0,0 +1,42 @@ +# IP + +## Condition Primitive About ClientIP + +- **req_cip_range("startIP", "endIP")** + - Judge if client IP is in [startIP, endIP] + + ``` + # if client IP is in 10.0.0.1~10.0.0.10 + req_cip_range("10.0.0.1", "10.0.0.10") + ``` +- **req_cip_trusted()** + + - Judge if client IP is trust IP +- **req_cip_hash_in(patterns)** + - Judge if client IP after hash matches configured patterns (value after hash is 0~9999) + + - Patterns represent multiple patterns. The type of it is string, format is as "pattern1|pattern2" (e.g. “0-100|1000-1100|9999”) + + ``` + # if the value after hash of client IP is 100~200 + req_cip_hash_in("100-200") + ``` + +## Condition Primitive About VIP + +- **req_vip_in("vip1|vip2|vip3")** + - Judge if VIP is in configured VIP list + + ``` + # if vip is 10.0.0.1 or 10.0.0.2 + req_vip_in("10.0.0.1|10.0.0.2") + ``` +- **req_vip_range("startVIP", "endVIP")** + - Judge if VIP is in [startIP, endIP] + + ``` + # if vip is in 10.0.0.1~10.0.0.10 + req_vip_range("10.0.0.1", "10.0.0.10") + ``` + + diff --git a/docs/en_us/condition/request/method.md b/docs/en_us/condition/request/method.md new file mode 100644 index 000000000..23f343c17 --- /dev/null +++ b/docs/en_us/condition/request/method.md @@ -0,0 +1,14 @@ +# Method + +- **req_method_in(patterns)** + - Judge if request method matches configured patterns + + - Patterns represent multiple patterns. The type of it is string, format is as "pattern1|pattern2". The value of pattern only can be GET/POST/PUT/DELETE + + ``` + # if the method of request is GET or POST + req_method_in("GET|POST") + ``` + + + diff --git a/docs/en_us/condition/request/protocol.md b/docs/en_us/condition/request/protocol.md new file mode 100644 index 000000000..cc35bf861 --- /dev/null +++ b/docs/en_us/condition/request/protocol.md @@ -0,0 +1,5 @@ +# Protocol + +- **req_proto_secure()** + - Judge if request is based on secure protocol, secure protocol includes https/spdy/http2 + - If protocol is http, return false; if protocol is https/spdy/http2, return true \ No newline at end of file diff --git a/docs/en_us/condition/request/tag.md b/docs/en_us/condition/request/tag.md new file mode 100644 index 000000000..27da230b2 --- /dev/null +++ b/docs/en_us/condition/request/tag.md @@ -0,0 +1,13 @@ +# Tag + +- **req_tag_match(tagName, tagValue)** + - In process of request, tag can be added sometimes + + - e.g. After matching dictionary, set value of clientIP(tag) to news_blackIPList + + ``` + # if the value of clientIP tag is news_blackIPList + req_tag_match("clientIP", "news_blackIPList") + ``` + + diff --git a/docs/en_us/condition/request/uri.md b/docs/en_us/condition/request/uri.md new file mode 100644 index 000000000..b91199438 --- /dev/null +++ b/docs/en_us/condition/request/uri.md @@ -0,0 +1,130 @@ +# URI + +In general, URI format is as follows: + +- http://host/path/?query + +## Common Condition Primitive Parameter + +- patterns: String, representing multiple patterns, format is as "pattern1|pattern2" +- case_insensitive: Bool, if ignore the case-sensitive of value + +## Host + +- **req_host_in(patterns)** + - Judge if host matches configured patterns + + - Case insensitive + + ``` + // match www.bfe-networks.com or bfe-networks.com, case insensitive + req_host_in(“www.bfe-networks.com|bfe-networks.com”) + ``` + + - **Note: the both sides of | can not be space** + + ``` + right:req_host_in(“www.bfe-networks.com|bfe-networks.com”) + wrong:req_host_in(“www.bfe-networks.com | bfe-networks.com”) + ``` + +## Path + +- **req_path_in(patterns, case_insensitive)** + + - Judge if request path matches configured patterns + + ``` + // if path is /abc,case insensitive + req_path_in(“/abc”, true) + ``` +- **req_path_prefix_in(patterns, case_insensitive)** + - Judge if request path prefix matches configured patterns + + ``` + // if path prefix is /abc,case insensitive + req_path_prefix_in(“/x/y”, false) + ``` + + +- **req_path_suffix_in(patterns, case_insensitive)** + - Judge if request path suffix matches configured patterns + + ``` + // if path suffix is /abc,case insensitive + req_path_suffix_in(“/x/y”, false) + ``` + +**Note:** + +**The patterns of req_path_in and req_path_prefix_in need to be included "/"** + +## Query + +- **req_query_key_in(patterns)** + - Judge if query key matches configured patterns + + ``` + # if key in query is abc + req_query_key_exist(“abc”) + ``` +- **req_query_key_prefix_in(patterns)** + + - Judge if query key prefix matches configured patterns + + ``` + # if key prefix in query is abc + req_query_key_prefix_in(“abc”) + ``` +- **req_query_value_in(key, patterns, case_insensitive)** + + - Judge if value of query key matches configured patterns + + ``` + # if the value of abc in query is XXX, case insensitive + req_query_value_in(“abc”, "XXX", true) + ``` +- **req_query_value_prefix_in(key, patterns, case_insensitive)** + - Judge if value prefix of query key matches configured patterns + + ``` + # if the value prefix of abc in query is XXX, case insensitive + req_query_value_prefix_in(“abc”, "XXX", true) + ``` +- **req_query_value_suffix_in(key, patterns, case_insensitive)** + - Judge if value suffix of query key matches configured patterns + + ``` + # if the value suffix of abc in query is XXX, case insensitive + req_query_value_suffix_in(“abc”, "XXX", true) + ``` +- **req_query_value_hash_in(key, patterns, case_insensitive)** + - Judge if value of key after hash in request query matches configured patterns (value after hash is 0~9999) + + ``` + # if the value after hash of abc in query is 100, case insensitive + req_query_value_hash_in(“abc”, "100", true) + ``` + +## Port + +- **req_port_in(patterns)** + + - Judge if port matches configured patterns + + ``` + # check if port is 80 or 8080 + req_port_in(“80|8080”) + ``` + +## URL + +- **req_url_regmatch(patterns)** + - patterns is regular expression to match yrl + + - ` is recommended using + + ``` + # check if url is "/s?word=123" + req_url_regmatch(`/s\?word=123`) + ``` diff --git a/docs/zh_cn/condition/request/cookie.md b/docs/zh_cn/condition/request/cookie.md index 3c86da1ab..b730024b8 100644 --- a/docs/zh_cn/condition/request/cookie.md +++ b/docs/zh_cn/condition/request/cookie.md @@ -1,32 +1,48 @@ -## Cookie相关 +# Cookie相关 + +## 通用原语参数 + +- key:字符串,cookie中的key +- patterns: 字符串,表示多个可匹配的pattern,用‘|’连接 +- case_insensitive:bool类型,是否忽略key的值大小写 ## 通用Cookie原语 - **req_cookie_key_in(patterns)** - 判断Cookie key是否为patterns之一 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 + + ``` + # cookie key为UID的请求 + req_cookie_key_in(“UID”) + ``` - **req_cookie_value_in(key, patterns, case_insensitive)** + - 判断cookie中key对应的值是否为patterns之一 - - key,字符串 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 + + ``` + # UID(忽略大小写)的cookie值是否是XXX + req_cookie_value_in(“UID”, "XXX", true) + ``` - **req_cookie_value_prefix_in(key, patterns, case_insensitive)** + - 判断cookie中key的值是否前缀匹配patterns之一 - - key,字符串 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 + + ``` + # UID(忽略大小写)的cookie值是否以XXX开头 + req_cookie_value_prefix_in(“UID”, "XXX", true) + ``` - **req_cookie_value_suffix_in(key, patterns, case_insensitive)** - 判断cookie中key的值是否后缀匹配patterns之一 - - key,字符串 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 + + ``` + # UID(忽略大小写)的cookie值是否以XXX结尾 + req_cookie_value_suffix_in(“UID”, "XXX", true) + ``` - **req_cookie_value_hash_in(key, patterns, case_insensitive)** + - 对cookie中key的值哈希取模,判断是否匹配patterns之一(模值0~9999) - - key,字符串 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接(如 “0-100|1000-1100|9999”) - - case_insensitive,bool类型,是否忽略key的值大小写 - -举例: - -``` -#UID的cookie值是否以XXX结尾 -req_cookie_value_suffix_in(“UID”, “XXX”) -``` - + + ``` + #UID(忽略大小写)的cookie值取模后是否为100 + req_cookie_value_hash_in(“UID”, “100”, true) + ``` diff --git a/docs/zh_cn/condition/request/header.md b/docs/zh_cn/condition/request/header.md index ac0b4f461..b8bc9733c 100644 --- a/docs/zh_cn/condition/request/header.md +++ b/docs/zh_cn/condition/request/header.md @@ -1,42 +1,51 @@ # Header相关 +## 通用原语参数 + +- header_name:字符串,header中的key +- patterns:字符串,表示多个可匹配的pattern,用‘|’连接 +- case_insensitive:bool类型,是否忽略key的值大小写 + ## 通用Header原语 - **req_header_key_in(patterns)** - 判断请求头部中key是否为patterns之一 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 - - **注意:** + - **注意:Header首字母要大写** ``` - Header首字母要大写 - 正确:req_header_key_in(“Header-Test”) + 正确:req_header_key_in(“Header-Test”) 错误:req_header_key_in(“Header-test”), req_header_key_in(“header-test”), req_header_key_in(“header-Test”) ``` - + - **req_header_value_in(header_name, patterns, case_insensitive)** + - 判断http消息头部字段是否为patterns之一 - - header_name,字符串,头部字段名称 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 - - case_insensitive,bool类型,是否忽略大小写 + + ``` + # header中Host为XXX.com的请求 + req_header_value_in("Host", "XXX.com", true) + ``` - **req_header_value_prefix_in(header_name, patterns, case_insensitive)** - 判断http消息头部字段是否前缀匹配patterns之一 - - header_name,字符串,头部字段名称 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 - - case_insensitive,bool类型,是否忽略大小写 + + ``` + # header中Host值前缀为XXX的请求 + req_header_prefix_value_in("Host", "XXX", true) + ``` - **req_header_value_suffix_in(header_name, patterns, case_insensitive)** - 判断http消息头部字段是否后缀匹配patterns之一 - - header_name,字符串,头部字段名称 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 - - case_insensitive,bool类型,是否忽略大小写 + + ``` + # header中Host值后缀为XXX的请求 + req_header_suffix_value_in("Host", "XXX", true) + ``` - **req_header_value_hash_in(header_name, patterns, case_insensitive)** - 对http消息头部字段值哈希取模,判断是否匹配patterns之一(模值0~9999) - - header_name,字符串,头部字段名称 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接(如 “0-100|1000-1100|9999”) - - 单值形式:100 - - 多值形式:100|101|102 - - 范围形式:100-200 - - 混合形式:100-200|400-500 - - case_insensitive,bool类型,是否忽略头部字段值大小写 \ No newline at end of file + + ``` + # header中Host值hash取模后,值为100-200或400的请求 + req_header_value_hash_in("Host", "100-200|400", true) + ``` + diff --git a/docs/zh_cn/condition/request/ip.md b/docs/zh_cn/condition/request/ip.md index 36574e5a8..6713a4a6a 100644 --- a/docs/zh_cn/condition/request/ip.md +++ b/docs/zh_cn/condition/request/ip.md @@ -1,29 +1,45 @@ # IP地址相关 -请求clientip相关的条件原语 +## 请求clientip相关的条件原语 - **req_cip_range("startIP", "endIP")** + - 判断请求的clientip是否在 [startIP, endIP] 的区间内 - - 在区间内,返回true,不在区间内,返回false - - startIP格式: "202.196.64.1" - - 暂只支持IPv4 格式地址 -- **req_cip_dictmatch(dictfile)** - - 判断clientip是否在词表中 - - dictfile,词表的文件名 - - 注:目前暂不支持 + + ``` + # clientip在10.0.0.1~10.0.0.10的请求 + req_cip_range("10.0.0.1", "10.0.0.10") + ``` - **req_cip_trusted()** + - 判断clientip是否为trust ip - **req_cip_hash_in(patterns)** + - 对cip哈希取模,判断是否匹配patterns之一(模值0~9999) - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接(如 “0-100|1000-1100|9999”) + + - patterns:字符串,表示多个可匹配的pattern,用‘|’连接 + + ``` + # 对clientip哈希取模后,值为100~200的请求 + req_cip_hash_in("100-200") + ``` -请求vip相关的条件原语 +## 请求vip相关的条件原语 - **req_vip_in("vip1|vip2|vip3")** - - 判断访问VIP是否在指定VIP列表中 - - 在列表内,返回true,不在列表内,返回false - - 同时支持IPv4 、IPV6格式地址 + + - 判断访问VIP是否在指定VIP列表中 + + ``` + # vip 为10.0.0.1或10.0.0.2的请求 + req_vip_in("10.0.0.1|10.0.0.2") + ``` - **req_vip_range("startVIP", "endVIP")** - 判断访问VIP是否在指定 [startIP, endIP] 的区间内 - - 在区间内,返回true,不在区间内,返回false - - 同时支持IPv4 、IPV6格式地址 + + ``` + # vip在10.0.0.1~10.0.0.10的请求 + req_vip_range("10.0.0.1", "10.0.0.10") + ``` + + diff --git a/docs/zh_cn/condition/request/method.md b/docs/zh_cn/condition/request/method.md index 1b651c0c1..dcc2fe473 100644 --- a/docs/zh_cn/condition/request/method.md +++ b/docs/zh_cn/condition/request/method.md @@ -1,5 +1,12 @@ # method相关 - **req_method_in(patterns)** + - 请求方法是否匹配patterns之一 - - Patterns,字符串,表示多个可匹配的pattern,用‘|’连接,pattern取值只能是GET/POST/PUT/DELETE \ No newline at end of file + + - Patterns,字符串,表示多个可匹配的pattern,用‘|’连接,pattern取值只能是GET/POST/PUT/DELETE + + ``` + # 使用GET或POST方法的请求 + req_method_in("GET|POST") + ``` \ No newline at end of file diff --git a/docs/zh_cn/condition/request/tag.md b/docs/zh_cn/condition/request/tag.md index a20c1e34b..0f1f14707 100644 --- a/docs/zh_cn/condition/request/tag.md +++ b/docs/zh_cn/condition/request/tag.md @@ -1,7 +1,15 @@ # Tag相关 - **req_tag_match(tagName, tagValue)** + - 在请求处理过程中,在某些环节可能会打上一些标签 + - 如:在经过词典匹配后,设置clientIP这个tag的值为news_blackIPList - - 本原语用于对tag的名字和值进行匹配 + + ``` + # clientIP tag的值为news_blackIPList的请求 + req_tag_match("clientIP", "news_blackIPList") + ``` + + diff --git a/docs/zh_cn/condition/request/uri.md b/docs/zh_cn/condition/request/uri.md index 1f20e864e..91388666a 100644 --- a/docs/zh_cn/condition/request/uri.md +++ b/docs/zh_cn/condition/request/uri.md @@ -4,119 +4,138 @@ URI的一般形式: - http://host[:port\]/path/?query](http://host/path/?query) -## 1. Host相关 +## 通用原语参数 + +- patterns:字符串,表示多个可匹配的pattern,用‘|’连接 + +- case_insensitive:bool类型,是否忽略key的值大小写 + +## Host相关 以下列举了判断http request中host相关的条件原语,由于host本身是忽略大小写的,所以所有host相关的原语不提供是否大小写忽略选项 - **req_host_in(patterns)** - 判断http的host是否为patterns之一 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 + - 忽略大小写精确匹配 - -举例: - -``` -// 匹配www.bfe-networks.com或bfe-networks.com,忽略大小写 -req_host_in(“www.bfe-networks.com|bfe-networks.com”) -``` - -**注意**: - -``` -正确:req_host_in(“www.bfe-networks.com|bfe-networks.com”) -错误:req_host_in(“www.bfe-networks.com | bfe-networks.com”) -``` - -## 2. Path相关 + + ``` + // 匹配www.bfe-networks.com或bfe-networks.com,忽略大小写 + req_host_in(“www.bfe-networks.com|bfe-networks.com”) + ``` + + - **注意: 多个pattern中间的|两侧不可以有空格** + + ``` + 正确:req_host_in(“www.bfe-networks.com|bfe-networks.com”) + 错误:req_host_in(“www.bfe-networks.com | bfe-networks.com”) + ``` + +## Path相关 判断url中path部分的条件原语 - **req_path_in(patterns, case_insensitive)** - 判断http的path是否为patterns之一 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 - - case_insensitive,bool类型,是否忽略大小写,值为true或false + + ``` + // path是否为/abc,忽略大小写 + req_path_in(“/abc”, true) + ``` - **req_path_prefix_in(patterns, case_insensitive)** - 判断http的path是否前缀匹配patterns之一 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 - - case_insensitive,bool类型,是否忽略大小写,值为true或false + + ``` + // path的前缀是否为/x/y,大小写敏感 + req_path_prefix_in(“/x/y”, false) + ``` - **req_path_suffix_in(patterns, case_insensitive)** - 判断http的path是否后缀匹配patterns之一 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 - - case_insensitive,bool类型,是否忽略大小写,值为true或false - -举例 - -``` -// 匹配/abc,忽略大小写 -req_path_in(“/abc”, true) - -// 匹配/x/y,大小写敏感 -req_path_in(“/x/y”, false) -``` + + ``` + // path的后缀是否为/x/y,大小写敏感 + req_path_suffix_in(“/x/y”, false) + ``` **注意:** -**req_path_in和req_path_prefix_in的patterns需要包含/** +**req_path_in和req_path_prefix_in的patterns需要包含开头的/** -## 3. Query相关 +## Query相关 查询参数相关的条件原语 - **req_query_key_in(patterns)** + - 判断query key是否为patterns之一 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 + + ``` + # 查询参数中是否有名字为abc的key + req_query_key_exist(“abc”) + ``` - **req_query_key_prefix_in(patterns)** + - 判断query key是否为前缀匹配patterns之一 + + ``` + # 查询参数中是否有名字前缀为abc的key + req_query_key_prefix_in(“abc”) + ``` - **req_query_value_in(key, patterns, case_insensitive)** + - 判断query中key的值是否精确匹配patterns之一 - - key,字符串 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 + + ``` + # 查询参数中key为abc的参数值为XXX的请求,大小写忽略 + req_query_value_in(“abc”, "XXX", true) + ``` - **req_query_value_prefix_in(key, patterns, case_insensitive)** + - 判断query中key的值是否前缀匹配patterns之一 - - key,字符串 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 + + ``` + # 查询参数中key为abc的参数值前缀为XXX的请求,大小写忽略 + req_query_value_prefix_in(“abc”, "XXX", true) + ``` - **req_query_value_suffix_in(key, patterns, case_insensitive)** - 判断query中key的值是否后缀匹配patterns之一 - - key,字符串 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 + + ``` + # 查询参数中key为abc的参数值前缀为XXX的请求,大小写忽略 + req_query_value_suffix_in(“abc”, "XXX", true) + ``` - **req_query_value_hash_in(key, patterns, case_insensitive)** + - 对query中key的值哈希取模,判断是否匹配patterns之一(模值0~9999) - - key,字符串 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接(如 “0-100|1000-1100|9999”) - - case_insensitive,bool类型,是否忽略头部字段值大小写 - -举例: - -``` -# 查询参数中是否有名字为abc的key -req_query_key_exist(“abc”) -``` + + ``` + # 查询参数中key为abc的参数值哈希取模后值为100的请求,大小写忽略 + req_query_value_hash_in(“abc”, "100", true) + ``` -## 4. Port相关 +## Port相关 查询参数相关的条件原语 - **req_port_in(patterns)** - - patterns,字符串,表示多个可匹配的端口,用‘|’连接 + + - 判断请求端口是否为patterns之一 + + ``` + # 查询端口是否为80或8080 + req_port_in(“80|8080”) + ``` -举例: - -``` -# 查询端口是否为80或8080 -req_port_in(“80|8080”) -``` - -## 5. 完整URL相关 +## 完整URL相关 查询参数相关的条件原语 - **req_url_regmatch(patterns)** - patterns,正则表达式,用来匹配完整url的正则表达式 + - 推荐使用反引号,不需要额外进行转义 - -举例: - -``` -# 查询url是否是/s?word=123 -req_url_regmatch(`/s\?word=123`) -``` \ No newline at end of file + + ``` + # 查询url是否是/s?word=123 + req_url_regmatch(`/s\?word=123`) + ``` From 2ca54eb5ddb33b51d7123a2c3bf34351e4c778cf Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Fri, 6 Sep 2019 09:47:52 +0800 Subject: [PATCH 24/55] update condition about response docs --- docs/en_us/condition/response.md | 38 ++++++++++++++++++++++++++++++++ docs/zh_cn/condition/response.md | 30 ++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 docs/en_us/condition/response.md diff --git a/docs/en_us/condition/response.md b/docs/en_us/condition/response.md new file mode 100644 index 000000000..8ef2f1277 --- /dev/null +++ b/docs/en_us/condition/response.md @@ -0,0 +1,38 @@ +# Response + +## Response Status Code + +- **res_code_in(codes)** + - Judge response HTTP status code is in configured codes + + - Codes represent multiple HTTP status code, it is string, format is as "200|400|403" + + ``` + # if response status code is 200 or 500 + res_code_in(200|500) + ``` + +## Header + +- **res_header_key_in(patterns)** + - Judge if key in Header of response matches configured patterns + + - Patterns represent multiple patterns. The type of it is string, format is as "pattern1|pattern2" + + ``` + # if the key in header of response is Header-Test + res_header_key_in(“Header-Test”) + ``` +- **res_header_value_in(key, patterns, case_insensitive)** + - Judge if value of key in response header matches configured patterns + + - The type of key is string + + - Patterns represent multiple patterns. The type of it is string, format is as "pattern1|pattern2" + + ``` + # if the value of Header-Test in header is XXX + res_header_value_in("Header-Test", "XXX", true) + ``` + + \ No newline at end of file diff --git a/docs/zh_cn/condition/response.md b/docs/zh_cn/condition/response.md index cd63dc88c..7387775a9 100644 --- a/docs/zh_cn/condition/response.md +++ b/docs/zh_cn/condition/response.md @@ -1,17 +1,41 @@ # Response相关的条件原语 -## 1. status code相关 +## status code相关 - **res_code_in(codes)** - 判断响应状态码是否为指定的任意状态码 + - codes代表多个状态码,是一个字符串,格式示例“200|400|403” + + ``` + # 响应返回状态码为200或500 + res_code_in(200|500) + ``` -## 2. header相关 +## header相关 - **res_header_key_in(patterns)** - 判断响应头部中key是否满足patterns之一 + - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 + + ``` + # 头部中key为Header-Test的响应 + res_header_key_in(“Header-Test”) + ``` - **res_header_value_in(key, patterns, case_insensitive)** + - 判断header中key对应的值是否满足patterns之一 + - key,字符串 - - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 \ No newline at end of file + + - patterns,字符串,表示多个可匹配的pattern,用‘|’连接 + + - case_insensitive,bool类型,是否忽略key的值大小写 + + ``` + # 头部中,key为Header-Test的值为XXX的响应 + res_header_value_in("Header-Test", "XXX", true) + ``` + + \ No newline at end of file From 76275b6d2ab0f08cd567b46c6a723d929f7491f7 Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Fri, 6 Sep 2019 09:49:16 +0800 Subject: [PATCH 25/55] update condition about system docs --- docs/en_us/condition/system/time.md | 47 +++++++++++++++++++++++ docs/zh_cn/condition/{ => system}/time.md | 19 +++++---- 2 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 docs/en_us/condition/system/time.md rename docs/zh_cn/condition/{ => system}/time.md (94%) diff --git a/docs/en_us/condition/system/time.md b/docs/en_us/condition/system/time.md new file mode 100644 index 000000000..3896aa85d --- /dev/null +++ b/docs/en_us/condition/system/time.md @@ -0,0 +1,47 @@ +# Time + +## Condition Primitive + +- **bfe_time_range(start_time, end_time)** + - Judge if current time is in [start_time, end_time] + + - Time format:yyyymmddhhmmssZ,Z is time zone,detail information is shown in the table of Section3 + + ``` + # time is in 2019-02-04 20:30~20:45 of Hotel Time Zone + bfe_time_range("20190204203000H", "20190204204500H") + ``` + +## Condition Primitive Test + +- In order to test time condition primitive, **X-Bfe-Debug-Time** can be added in header of request to mock system time + +## Time Zone Detail + +| **Time zone name** | **Letter** | **Offset** | **Description** | +| :----------------- | :--------- | :----------------------------------------------------- | :------------------ | +| Alfa Time Zone | A | [+1](https://en.wikipedia.org/wiki/UTC%2B01:00) | | +| Bravo Time Zone | B | [+2](https://en.wikipedia.org/wiki/UTC%2B02:00) | | +| Charlie Time Zone | C | [+3](https://en.wikipedia.org/wiki/UTC%2B03:00) | | +| Delta Time Zone | D | [+4](https://en.wikipedia.org/wiki/UTC%2B04:00) | | +| Echo Time Zone | E | [+5](https://en.wikipedia.org/wiki/UTC%2B05:00) | | +| Foxtrot Time Zone | F | [+6](https://en.wikipedia.org/wiki/UTC%2B06:00) | | +| Golf Time Zone | G | [+7](https://en.wikipedia.org/wiki/UTC%2B07:00) | | +| Hotel Time Zone | **H** | [+8](https://en.wikipedia.org/wiki/UTC%2B08:00) | **Beijing Time** | +| India Time Zone | I | [+9](https://en.wikipedia.org/wiki/UTC%2B09:00) | | +| Kilo Time Zone | K | [+10](https://en.wikipedia.org/wiki/UTC%2B10:00) | | +| Lima Time Zone | L | [+11](https://en.wikipedia.org/wiki/UTC%2B11:00) | | +| Mike Time Zone | M | [+12](https://en.wikipedia.org/wiki/UTC%2B12:00) | | +| November Time Zone | N | [−1](https://en.wikipedia.org/wiki/UTC−01:00) | | +| Oscar Time Zone | O | [−2](https://en.wikipedia.org/wiki/UTC−02:00) | | +| Papa Time Zone | P | [−3](https://en.wikipedia.org/wiki/UTC−03:00) | | +| Quebec Time Zone | Q | [−4](https://en.wikipedia.org/wiki/UTC−04:00) | | +| Romeo Time Zone | R | [−5](https://en.wikipedia.org/wiki/UTC−05:00) | | +| Sierra Time Zone | S | [−6](https://en.wikipedia.org/wiki/UTC−06:00) | | +| Tango Time Zone | T | [−7](https://en.wikipedia.org/wiki/UTC−07:00) | | +| Uniform Time Zone | U | [−8](https://en.wikipedia.org/wiki/UTC−08:00) | | +| Victor Time Zone | V | [−9](https://en.wikipedia.org/wiki/UTC−09:00) | | +| Whiskey Time Zone | W | [−10](https://en.wikipedia.org/wiki/UTC−10:00) | | +| X-ray Time Zone | X | [−11](https://en.wikipedia.org/wiki/UTC−11:00) | | +| Yankee Time Zone | Y | [−12](https://en.wikipedia.org/wiki/UTC−12:00) | | +| Zulu Time Zone | Z | [0](https://en.wikipedia.org/wiki/Greenwich_Mean_Time) | Greenwich Mean Time | \ No newline at end of file diff --git a/docs/zh_cn/condition/time.md b/docs/zh_cn/condition/system/time.md similarity index 94% rename from docs/zh_cn/condition/time.md rename to docs/zh_cn/condition/system/time.md index 1b321508b..832a7f15c 100644 --- a/docs/zh_cn/condition/time.md +++ b/docs/zh_cn/condition/system/time.md @@ -1,23 +1,22 @@ # 时间相关 -## 1. 原语 +## 原语 - **bfe_time_range(start_time, end_time)** - 判断当前时间是否属于[start_time, end_time] + - 时间格式:yyyymmddhhmmssZ,其中Z代表时区,详见注2表说明 + + ``` + #时间在北京时间2019-02-04 20:30~20:45内 + bfe_time_range("20190204203000H", "20190204204500H") + ``` -举例: - -``` -#时间在北京时间2019-02-04 20:30~20:45内 -bfe_time_range("20190204203000H", "20190204204500H") -``` - -## 2. 时间原语测试 +## 时间原语测试 - 为便于测试条件时间原语,可以在请求中增加 **X-Bfe-Debug-Time** 头部携带时间,来mock系统时间 -## 3. 时区字符编码 +## 时区字符编码 | **Time zone name** | **Letter** | **Offset** | **说明** | | :----------------- | :--------- | :----------------------------------------------------- | :--------------- | From bc27038a6653d475245f3fc7f976ddf5f64b9ad1 Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Fri, 6 Sep 2019 09:50:01 +0800 Subject: [PATCH 26/55] update condition rule grammar docs --- docs/en_us/condition/rule_grammar.md | 97 ++++++++++++++++++++++++++++ docs/zh_cn/condition/rule_grammar.md | 35 ++++------ 2 files changed, 111 insertions(+), 21 deletions(-) create mode 100644 docs/en_us/condition/rule_grammar.md diff --git a/docs/en_us/condition/rule_grammar.md b/docs/en_us/condition/rule_grammar.md new file mode 100644 index 000000000..705c6e29f --- /dev/null +++ b/docs/en_us/condition/rule_grammar.md @@ -0,0 +1,97 @@ +# Rule Grammar + +## 1. Basic Concepts + +- **Condition Primitive**: + + - Basic conditional judgment unit, which defines the primitive of comparison; + + - e.g. + + ``` + req_host_in(“www.bfe-networks.com|bfe-networks.com”) # host is one of the configured domains + ``` + +- **Condition Expression** + + - Expression using "and/or/not" to connect condition primitive ; + + - e.g. + + ``` + req_host_in(“bfe-networks.com”) && req_method_in(“GET”) # domain is bfe-networks.com and HTTP method is "GET" + ``` + +- **Condition Variable** + + - Variable that is defined by **Condition Expression**; + + - e.g. + + ``` + bfe_host = req_host_in(“bfe-networks.com”) # variable bfe_host is defined by condition expression + ``` + +- **Advanced Condition Expression** + + - Expression using "and/or/not" to connect condition primitive and condition variable + + - In advanced condition expression, condition variable is identified by **"$" prefix**; + + - e.g. + + ``` + $news_host && req_method_in("GET") # match condition variable and HTTP method is "GET" + ``` + + +## 2. Condition Primitive Grammar + +- Basic conditional judgment unit, format is shown as fol: + +​ **FuncName( params )** + +- The number of parameters can be one or more + +- Condition Primitive Grammar + - The type of returning value is bool; + - Condition primitive like function definition, multiple parameters are supported in it; + +## 3. Condition Expression Grammar + +Condition Expression grammar is defined as follows: + +- Priority and combination rule of "&&/||/!" is same as them in C language; + +- Expression description + + ``` + Condition Expression(CE) -> + CE && CE + | CE || CE + | ( CE ) + | ! CE + | Condition Primitive + ``` + + + +## 4. Advanced Condition Expression Grammar + +Advanced Condition Expression grammar is defined as follows:: + +- Priority and combination rule of "&&/||/!" is same as them in C language; + +- Expression description + + ``` + Advanced Condition Expression(ACE) -> + ACE && ACE + | ACE || ACE + | ( ACE) + | ! ACE + | Condition Primitive + | Condition Variable + ``` + + \ No newline at end of file diff --git a/docs/zh_cn/condition/rule_grammar.md b/docs/zh_cn/condition/rule_grammar.md index 71691bf1d..f126a2a1f 100644 --- a/docs/zh_cn/condition/rule_grammar.md +++ b/docs/zh_cn/condition/rule_grammar.md @@ -1,47 +1,40 @@ # 规则语法 -## 1. 原语、表达式和变量 +## 原语、表达式和变量 - **条件原语**(Condition Primitive): - 最基本的条件判断单元,定义了比较的原语; - - 如: - ``` - req_host_in(“www.bfe-networks.com|bfe-networks.com”) # host是两个域名之一 + req_host_in(“www.bfe-networks.com|bfe-networks.com”) # host是两个域名之一 ``` - + - **条件表达式**(Condition Expression):基于条件原语的“与 / 或 / 非组合”; - - 举例: - - ``` - req_host_in(“bfe-networks.com”) && req_method_in(“GET”) # 域名是bfe-networks.com、且方法是"GET" - ``` - + ``` +req_host_in(“bfe-networks.com”) && req_method_in(“GET”) # 域名是bfe-networks.com、且方法是"GET" + ``` + - **条件变量**(Condition Variable) - 可以将**条件表达式**赋值给一个变量,这个变量被定义为条件变量 - - 例如: - ``` - bfe_host = req_host_in(“bfe-networks.com”) # 将条件表达式赋值给变量bfe_host + bfe_host = req_host_in(“bfe-networks.com”) # 将条件表达式赋值给变量bfe_host ``` - + - **高级条件表达式**(Advanced Condition Expression):基于条件原语和条件变量的“与 / 或 / 非组合” - 在高级条件表达式中,条件变量以**“$”前缀**作为标示 - - 例如: - ``` - $news_host && req_method_in("GET") # 符合变量news_host、且方法是"GET" + $news_host && req_method_in("GET") # 符合变量news_host、且方法是"GET" ``` + -## 2. 条件原语的语法 +## 条件原语的语法 - **条件原语**是最基本的条件判断单元,形式为: @@ -54,7 +47,7 @@ - 条件原语的返回值都是bool类型; - 条件原语类似于函数定义,可以有多个参数; -## 3**.** 条件表达式的语法 +## 条件表达式的语法 条件表达式的语法定义如下: @@ -73,7 +66,7 @@ -## 4**.** 高级条件表达式的语法 +## 高级条件表达式的语法 高级条件表达式的语法定义如下: From 8f7cf1b4d9b6c7cdf6a58b01ab64a8662c294b32 Mon Sep 17 00:00:00 2001 From: zhengkaiyu Date: Fri, 6 Sep 2019 09:50:45 +0800 Subject: [PATCH 27/55] update condition primitive standard docs --- .../condition/condition_primitive_standard.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docs/en_us/condition/condition_primitive_standard.md diff --git a/docs/en_us/condition/condition_primitive_standard.md b/docs/en_us/condition/condition_primitive_standard.md new file mode 100644 index 000000000..4ce3da7c6 --- /dev/null +++ b/docs/en_us/condition/condition_primitive_standard.md @@ -0,0 +1,28 @@ +# Condition Primitive Standard + +- **Request** or **Response** + - Condition primitive about request is used ”**req_**“ prefix + - e.g. **req_host_in()** + - Condition primitive about response is used ”**res_**“ prefix + - e.g. **res_code_in()** + - Condition primitive about session is used ”**ses_**“ prefix + - e.g. **ses_vip_in()** + - Condition primitive about system is used ”**bfe_**“ prefix + - e.g. **bfe_time_range**() +- Compare actions include as follows: + - **match**:accurate matching + - In this situation, the only one parameter is given + - **in**:if the value is in the configured set + - **prefix_in**:if the value prefix is in the configured set + - **suffix_in**:if the value suffix is in the configured set + - **key_exist**:if the key exists + - In general, this action is used in the compare of query/cookie/header + - **value_in**:for the configured key, judge if the value is in the configured value set + - **value_prefix_in**:for the configured key, judge if the value prefix is in the configured set + - **value_suffix_in**:for the configured key, judge if the value suffix is in the configured set + - **range**: scope match + - In general, this action is used in the compare of ip/time + - **regmatch**:regular match + - This action is not encouraged to use + - **dictmatch**:if matching the content of dictionary + - **contain**: if include the configured string \ No newline at end of file From dc2f43ea7760e1970ec9b08726d03539d553fc07 Mon Sep 17 00:00:00 2001 From: yangsijie Date: Fri, 6 Sep 2019 11:52:50 +0800 Subject: [PATCH 28/55] Add mod_key_file --- bfe_modules/mod_key_log/conf_mod_key_log.go | 75 ++++++++++++ bfe_modules/mod_key_log/mod_key_log.go | 102 +++++++++++++++++ bfe_util/access_log/access_log.go | 120 ++++++++++++++++++++ 3 files changed, 297 insertions(+) create mode 100644 bfe_modules/mod_key_log/conf_mod_key_log.go create mode 100644 bfe_modules/mod_key_log/mod_key_log.go create mode 100644 bfe_util/access_log/access_log.go diff --git a/bfe_modules/mod_key_log/conf_mod_key_log.go b/bfe_modules/mod_key_log/conf_mod_key_log.go new file mode 100644 index 000000000..f6dbfff4c --- /dev/null +++ b/bfe_modules/mod_key_log/conf_mod_key_log.go @@ -0,0 +1,75 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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 mod_key_log + +import ( + "fmt" +) + +import ( + "github.com/baidu/go-lib/log/log4go" + gcfg "gopkg.in/gcfg.v1" +) + +// ConfModKeyLog represents the basic config for mod_key_log. +type ConfModKeyLog struct { + Log struct { + LogPrefix string // log file prefix + LogDir string // log file dir + RotateWhen string // rotate time + BackupCount int // log file backup number + } +} + +// Check validates module config +func (cfg *ConfModKeyLog) Check() error { + if cfg.Log.LogPrefix == "" { + return fmt.Errorf("LogPrefix is empty") + } + + if cfg.Log.LogDir == "" { + return fmt.Errorf("LogDir is empty") + } + + if !log4go.WhenIsValid(cfg.Log.RotateWhen) { + return fmt.Errorf("RotateWhen invalid: %s", cfg.Log.RotateWhen) + } + + if cfg.Log.BackupCount <= 0 { + return fmt.Errorf("BackupCount should > 0: %d", cfg.Log.BackupCount) + } + + return nil +} + +// ConfLoad loads config from file +func ConfLoad(filePath string) (*ConfModKeyLog, error) { + var cfg ConfModKeyLog + var err error + + // read config from file + err = gcfg.ReadFileInto(&cfg, filePath) + if err != nil { + return nil, err + } + + // check config + err = cfg.Check() + if err != nil { + return nil, err + } + + return &cfg, nil +} diff --git a/bfe_modules/mod_key_log/mod_key_log.go b/bfe_modules/mod_key_log/mod_key_log.go new file mode 100644 index 000000000..2a462a65d --- /dev/null +++ b/bfe_modules/mod_key_log/mod_key_log.go @@ -0,0 +1,102 @@ +// Copyright (c) 2019 Baidu, Inc. +// +// 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 mod_key_log + +import ( + "encoding/hex" + "fmt" +) + +import ( + "github.com/baidu/go-lib/log/log4go" + "github.com/baidu/go-lib/web-monitor/web_monitor" +) + +import ( + "github.com/baidu/bfe/bfe_basic" + "github.com/baidu/bfe/bfe_module" + "github.com/baidu/bfe/bfe_util/access_log" +) + +// ModuleKeyLog writes key logs in NSS key log format so that external +// programs(eg. wireshark) can decrypt TLS connections for trouble shooting. +// +// For more information about NSS key log format, see: +// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format +type ModuleKeyLog struct { + name string // module name + conf *ConfModKeyLog // module config + logger log4go.Logger // key logger +} + +func NewModuleKeyLog() *ModuleKeyLog { + m := new(ModuleKeyLog) + m.name = "mod_key_log" + return m +} + +func (m *ModuleKeyLog) Name() string { + return m.name +} + +func (m *ModuleKeyLog) loadConf(confPath string) error { + conf, err := ConfLoad(confPath) + if err != nil { + return fmt.Errorf("%s: conf load err %s", m.name, err.Error()) + } + + m.conf = conf + return nil +} + +func (m *ModuleKeyLog) logTlsKey(session *bfe_basic.Session) int { + tlsState := session.TlsState + if tlsState == nil { + return bfe_module.BFE_HANDLER_GOON + } + + // key log format: