Skip to content

Commit 4a01c25

Browse files
Merge pull request #1226 from bfenetworks/release/v1.8.0
Merge release/v1.8.0 into master
2 parents 5e12c28 + 4167976 commit 4a01c25

File tree

102 files changed

+9203
-217
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+9203
-217
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ dist/*
3030
conf/wasm_plugin
3131

3232
.DS_Store
33+
.git*

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ All notable changes to this project will be documented in this file.
1010
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1111
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1212

13+
## [v1.8.0] - 2025-09-03
14+
15+
### Added
16+
17+
- Support third-party WAF integration ([Pull #1222](https://github.com/bfenetworks/bfe/pull/1222))
18+
- Support basic functions of an AI gateway ([Pull #1223](https://github.com/bfenetworks/bfe/pull/1223))
19+
- Support forwarding requests to backend RS over HTTPS
20+
21+
1322
## [v1.7.0] - 2025-01-19
1423

1524
### Added

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.7.0
1+
1.8.0

bfe_balance/backend/health_check.go

Lines changed: 258 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,36 @@
1717
package backend
1818

1919
import (
20+
"crypto/x509"
21+
"encoding/json"
2022
"fmt"
2123
"net"
2224
"net/http"
25+
"net/url"
26+
"regexp"
27+
"strconv"
2328
"strings"
2429
"time"
25-
)
2630

27-
import (
2831
"github.com/baidu/go-lib/log"
29-
)
3032

31-
import (
3233
"github.com/bfenetworks/bfe/bfe_config/bfe_cluster_conf/cluster_conf"
3334
"github.com/bfenetworks/bfe/bfe_debug"
35+
"github.com/bfenetworks/bfe/bfe_tls"
3436
)
3537

38+
type checkRtn struct {
39+
ok bool
40+
err error
41+
}
42+
3643
func UpdateStatus(backend *BfeBackend, cluster string) bool {
44+
var (
45+
checkConf *cluster_conf.BackendCheck
46+
httpsConf *cluster_conf.BackendHTTPS
47+
)
3748
// get conf of health check, which is separately stored for each cluster
38-
checkConf := getCheckConf(cluster)
49+
checkConf, httpsConf = getCheckConf(cluster)
3950
if checkConf == nil {
4051
// just ignore if not found health check conf
4152
return false
@@ -45,14 +56,15 @@ func UpdateStatus(backend *BfeBackend, cluster string) bool {
4556
// if backend's status become fail, start healthcheck.
4657
// at most start 1 check goroutine for each backend.
4758
if backend.UpdateStatus(*checkConf.FailNum) {
48-
go check(backend, cluster)
59+
go check(backend, cluster, httpsConf)
4960
return true
5061
}
5162

5263
return false
5364
}
5465

55-
func check(backend *BfeBackend, cluster string) {
66+
func check(backend *BfeBackend, cluster string, httpsConf *cluster_conf.BackendHTTPS) {
67+
5668
log.Logger.Info("start healthcheck for %s", backend.Name)
5769

5870
// backend close chan
@@ -67,7 +79,7 @@ loop:
6779
}
6880

6981
// get the latest conf to do health check
70-
checkConf := getCheckConf(cluster)
82+
checkConf, _ := getCheckConf(cluster)
7183
if checkConf == nil {
7284
// never come here
7385
time.Sleep(time.Second)
@@ -76,7 +88,7 @@ loop:
7688
checkInterval := time.Duration(*checkConf.CheckInterval) * time.Millisecond
7789

7890
// health check
79-
if ok, err := CheckConnect(backend, checkConf); !ok {
91+
if ok, err := CheckConnect(backend, checkConf, httpsConf); !ok {
8092
backend.ResetSuccNum()
8193
if bfe_debug.DebugHealthCheck {
8294
log.Logger.Debug("backend %s still not avail (check failure: %s)", backend.Name, err)
@@ -150,6 +162,237 @@ func doHTTPHealthCheck(request *http.Request, timeout time.Duration) (int, error
150162
return response.StatusCode, nil
151163
}
152164

165+
// extractIP extract ip address
166+
func extractIP(rsAddr string) string {
167+
if strings.HasPrefix(rsAddr, "[") {
168+
// IPv6
169+
endIndex := strings.LastIndex(rsAddr, "]")
170+
if endIndex == -1 {
171+
return ""
172+
}
173+
ip := rsAddr[:endIndex+1]
174+
if net.ParseIP(ip[1:endIndex]) == nil {
175+
return ""
176+
}
177+
return ip
178+
} else {
179+
// IPv4
180+
ip := strings.Split(rsAddr, ":")[0]
181+
if net.ParseIP(ip) == nil {
182+
return ""
183+
}
184+
return ip
185+
}
186+
}
187+
188+
func getHostByType(host, rsAddr, hostType *string, def string) string {
189+
if hostType == nil {
190+
ht := cluster_conf.HostType_HOST
191+
hostType = &ht
192+
}
193+
switch *hostType {
194+
case cluster_conf.HostType_Instance_IP:
195+
if rsAddr != nil {
196+
return extractIP(*rsAddr)
197+
}
198+
default:
199+
if host != nil {
200+
return *host
201+
}
202+
}
203+
return def
204+
}
205+
206+
func checkHTTPSConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck, httpsConf *cluster_conf.BackendHTTPS) (bool, error) {
207+
var (
208+
err error
209+
conn net.Conn
210+
addrInfo = getHealthCheckAddrInfo(backend, checkConf)
211+
checkTimeout = 30 * time.Second
212+
statusCode = 0
213+
host string
214+
rootCAs *x509.CertPool = nil
215+
certs []bfe_tls.Certificate = nil
216+
cert bfe_tls.Certificate
217+
insecure = false
218+
uri = "/"
219+
checkRtnCh = make(chan checkRtn, 1)
220+
rtn checkRtn
221+
)
222+
223+
var (
224+
getStatusCodeFn = func(statusLine string) (int, error) {
225+
// "HTTP/1.1 200 OK"
226+
re, err := regexp.Compile(`\s(\d{3})\s`)
227+
if err != nil {
228+
return 0, err
229+
}
230+
matches := re.FindStringSubmatch(statusLine)
231+
if len(matches) == 2 {
232+
statusCode := matches[1]
233+
log.Logger.Debug("StatusCode = %s, raw = %s", statusCode, statusLine)
234+
return strconv.Atoi(statusCode)
235+
} else {
236+
return 0, fmt.Errorf("Status code not found: %s", statusLine)
237+
}
238+
}
239+
240+
doCheckFn = func(conn net.Conn) checkRtn {
241+
// Set timeout
242+
timeout := 3 * time.Second
243+
err = conn.SetDeadline(time.Now().Add(timeout))
244+
if err != nil {
245+
return checkRtn{false, err}
246+
}
247+
248+
// TLS Check
249+
if err = conn.(*bfe_tls.Conn).Handshake(); err != nil {
250+
log.Logger.Debug("debug_https err=%s", err.Error())
251+
return checkRtn{false, err}
252+
}
253+
if *checkConf.Schem == "tls" {
254+
return checkRtn{true, nil}
255+
}
256+
257+
// HTTPS Check
258+
if checkConf.Uri != nil && *checkConf.Uri != "" {
259+
uri = *checkConf.Uri
260+
}
261+
request := fmt.Sprintf("GET %s HTTP/1.1\r\n"+
262+
"Host: %s\r\n"+
263+
"User-Agent: BFE-Health-Check\r\n"+
264+
"\r\n", uri, host)
265+
_, err = conn.Write([]byte(request))
266+
if err != nil {
267+
log.Logger.Debug("debug_https err=%s", err.Error())
268+
return checkRtn{false, err}
269+
}
270+
var (
271+
response = ""
272+
ok bool
273+
err error
274+
data = make([]byte, 0)
275+
bufSz = 128
276+
buf = make([]byte, bufSz)
277+
total = 0
278+
)
279+
280+
for {
281+
total, err = conn.Read(buf)
282+
if err != nil {
283+
break
284+
}
285+
data = append(data, buf[:total]...)
286+
if total < bufSz {
287+
break
288+
}
289+
}
290+
291+
if err != nil {
292+
log.Logger.Debug("debug_https err=%s", err.Error())
293+
return checkRtn{false, err}
294+
}
295+
response = string(data)
296+
log.Logger.Debug("<- Request:\n%s", request)
297+
log.Logger.Debug("-> Response:\n%s", response)
298+
if checkConf.StatusCode != nil { // check status code
299+
var (
300+
s string
301+
arr = strings.Split(response, "\n")
302+
)
303+
if len(arr) > 0 {
304+
s = strings.ToUpper(arr[0])
305+
statusCode, err = getStatusCodeFn(s)
306+
if err != nil {
307+
return checkRtn{false, err}
308+
}
309+
if checkConf.StatusCodeRange != nil && *checkConf.StatusCodeRange != "" {
310+
log.Logger.Debug("statusCode=%d, statusCodeRange=%s", statusCode, *checkConf.StatusCodeRange)
311+
ok, err := cluster_conf.MatchStatusCodeRange(fmt.Sprintf("%d", statusCode), *checkConf.StatusCodeRange)
312+
return checkRtn{ok, err}
313+
}
314+
}
315+
ok, err = cluster_conf.MatchStatusCode(statusCode, *checkConf.StatusCode)
316+
}
317+
return checkRtn{ok, err}
318+
}
319+
320+
toStringFn = func(o interface{}) string {
321+
b, err := json.Marshal(o)
322+
if err != nil {
323+
return err.Error()
324+
}
325+
return string(b)
326+
}
327+
)
328+
329+
if checkConf.CheckTimeout != nil {
330+
checkTimeout = time.Duration(*checkConf.CheckTimeout) * time.Millisecond
331+
}
332+
conn, err = net.DialTimeout("tcp", addrInfo, checkTimeout)
333+
334+
if err != nil {
335+
log.Logger.Debug("debug_https err=%v", err)
336+
return false, err
337+
}
338+
339+
defer func() {
340+
if r := recover(); r != nil {
341+
log.Logger.Debug("recover_panic = %v", r)
342+
}
343+
_ = conn.Close()
344+
}()
345+
346+
_, err = url.Parse(fmt.Sprintf("%s://%s%s", "https", addrInfo, *checkConf.Uri))
347+
if err != nil {
348+
log.Logger.Debug("debug_https err=%v", err)
349+
return false, err
350+
}
351+
352+
serverName := ""
353+
if httpsConf.RSHost != nil {
354+
serverName = *httpsConf.RSHost
355+
} else if checkConf.Host != nil {
356+
serverName = *checkConf.Host
357+
}
358+
host = getHostByType(checkConf.Host, &addrInfo, checkConf.HostType, serverName)
359+
360+
rootCAs, err = httpsConf.GetRSCAList()
361+
362+
if cert, err = httpsConf.GetBFECert(); err == nil {
363+
certs = []bfe_tls.Certificate{cert}
364+
}
365+
366+
if httpsConf.RSInsecureSkipVerify != nil {
367+
insecure = *httpsConf.RSInsecureSkipVerify
368+
}
369+
370+
conn = bfe_tls.Client(conn, &bfe_tls.Config{
371+
Certificates: certs,
372+
InsecureSkipVerify: true,
373+
ServerName: host,
374+
RootCAs: rootCAs,
375+
VerifyPeerCertificate: bfe_tls.NewVerifyPeerCertHooks(insecure, host, rootCAs).Ready(),
376+
})
377+
378+
log.Logger.Debug("httpsCheck conf=%s", toStringFn(checkConf))
379+
go func(conn net.Conn, rtnCh chan checkRtn) {
380+
rtnCh <- doCheckFn(conn)
381+
}(conn, checkRtnCh)
382+
383+
if checkTimeout > 0 {
384+
select {
385+
case rtn = <-checkRtnCh:
386+
return rtn.ok, rtn.err
387+
case <-time.Tick(checkTimeout):
388+
return false, fmt.Errorf("https checkTimeout %dms", checkTimeout/time.Millisecond)
389+
}
390+
} else {
391+
rtn = <-checkRtnCh
392+
}
393+
return rtn.ok, rtn.err
394+
}
395+
153396
func checkHTTPConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck) (bool, error) {
154397
// prepare health check request
155398
addrInfo := getHealthCheckAddrInfo(backend, checkConf)
@@ -182,26 +425,28 @@ func checkHTTPConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck)
182425
}
183426

184427
// CheckConnect checks whether backend server become available.
185-
func CheckConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck) (bool, error) {
428+
func CheckConnect(backend *BfeBackend, checkConf *cluster_conf.BackendCheck, httpsConf *cluster_conf.BackendHTTPS) (bool, error) {
186429
switch *checkConf.Schem {
187430
case "http":
188431
return checkHTTPConnect(backend, checkConf)
189432
case "tcp":
190433
return checkTCPConnect(backend, checkConf)
434+
case "https", "tls":
435+
return checkHTTPSConnect(backend, checkConf, httpsConf)
191436
default:
192437
// never come here
193438
return checkHTTPConnect(backend, checkConf)
194439
}
195440
}
196441

197442
// CheckConfFetcher returns current health check conf for cluster.
198-
type CheckConfFetcher func(cluster string) *cluster_conf.BackendCheck
443+
type CheckConfFetcher func(name string) (*cluster_conf.BackendCheck, *cluster_conf.BackendHTTPS)
199444

200445
var checkConfFetcher CheckConfFetcher
201446

202-
func getCheckConf(cluster string) *cluster_conf.BackendCheck {
447+
func getCheckConf(cluster string) (*cluster_conf.BackendCheck, *cluster_conf.BackendHTTPS) {
203448
if checkConfFetcher == nil {
204-
return nil
449+
return nil, nil
205450
}
206451
return checkConfFetcher(cluster)
207452
}

0 commit comments

Comments
 (0)