1717package backend
1818
1919import (
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+
3643func 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
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 )
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+
153396func 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
200445var 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