@@ -14,6 +14,7 @@ import (
1414 "net/http"
1515 "os"
1616 "path/filepath"
17+ "runtime/debug"
1718 "strings"
1819 "sync"
1920 "sync/atomic"
@@ -60,15 +61,16 @@ type fronted struct {
6061 providers map [string ]* Provider
6162 clientHelloID tls.ClientHelloID
6263
63- providersMu sync.RWMutex
64- frontsMu sync.RWMutex
65- stopCh chan interface {}
66- crawlOnce sync.Once
67- stopped atomic.Bool
68- countryCode string
69- httpClient * http.Client
70- configURL string
71- frontsCh chan Front
64+ providersMu sync.RWMutex
65+ frontsMu sync.RWMutex
66+ stopCh chan interface {}
67+ crawlOnce sync.Once
68+ stopped atomic.Bool
69+ countryCode string
70+ httpClient * http.Client
71+ configURL string
72+ frontsCh chan Front
73+ panicListener func (string )
7274}
7375
7476// Interface for sending HTTP traffic over domain fronting.
@@ -115,6 +117,7 @@ func NewFronted(options ...Option) Fronted {
115117 cacheFile : defaultCacheFilePath (),
116118 configURL : "" ,
117119 frontsCh : make (chan Front , 4000 ),
120+ panicListener : func (msg string ) { log .Errorf ("Panic in fronted: %v" , msg ) },
118121 }
119122
120123 for _ , opt := range options {
@@ -157,6 +160,12 @@ func WithConfigURL(configURL string) Option {
157160 }
158161}
159162
163+ func WithPanicListener (panicListener func (string )) Option {
164+ return func (f * fronted ) {
165+ f .panicListener = panicListener
166+ }
167+ }
168+
160169func defaultCacheFilePath () string {
161170 if dir , err := os .UserConfigDir (); err != nil {
162171 log .Errorf ("Unable to get user config dir: %v" , err )
@@ -187,6 +196,12 @@ func (f *fronted) keepCurrent() {
187196 )
188197
189198 go func () {
199+ // Recover from panics and log them
200+ defer func () {
201+ if r := recover (); r != nil {
202+ f .panicListener (fmt .Sprintf ("Panic waiting for fronts %v with stack: %v" , r , debug .Stack ()))
203+ }
204+ }()
190205 for data := range chDB {
191206 log .Debug ("Received new fronted configuration" )
192207 f .onNewFrontsConfig (data )
@@ -241,7 +256,14 @@ func (f *fronted) onNewFronts(pool *x509.CertPool, providers map[string]*Provide
241256
242257 // The goroutine for finding working fronts runs forever, so only start it once.
243258 f .crawlOnce .Do (func () {
244- go f .findWorkingFronts ()
259+ go func () {
260+ defer func () {
261+ if r := recover (); r != nil {
262+ f .panicListener (fmt .Sprintf ("Panic finding working fronts %v with stack: %v" , r , debug .Stack ()))
263+ }
264+ }()
265+ f .findWorkingFronts ()
266+ }()
245267 })
246268}
247269
@@ -607,6 +629,7 @@ func Vet(m *Masquerade, pool *x509.CertPool, testURL string) bool {
607629 certPool : atomic.Value {},
608630 maxAllowedCachedAge : defaultMaxAllowedCachedAge ,
609631 maxCacheSize : defaultMaxCacheSize ,
632+ panicListener : func (msg string ) { log .Errorf ("Panic in fronted: %v" , msg ) },
610633 }
611634 d .certPool .Store (pool )
612635 masq := & front {Masquerade : * m }
0 commit comments