github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/experimental/libbox/command_urltest.go (about)

     1  package libbox
     2  
     3  import (
     4  	"encoding/binary"
     5  	"net"
     6  	"time"
     7  
     8  	"github.com/inazumav/sing-box/adapter"
     9  	"github.com/inazumav/sing-box/common/urltest"
    10  	"github.com/inazumav/sing-box/outbound"
    11  	"github.com/sagernet/sing/common"
    12  	"github.com/sagernet/sing/common/batch"
    13  	E "github.com/sagernet/sing/common/exceptions"
    14  	"github.com/sagernet/sing/common/rw"
    15  )
    16  
    17  func (c *CommandClient) URLTest(groupTag string) error {
    18  	conn, err := c.directConnect()
    19  	if err != nil {
    20  		return err
    21  	}
    22  	defer conn.Close()
    23  	err = binary.Write(conn, binary.BigEndian, uint8(CommandURLTest))
    24  	if err != nil {
    25  		return err
    26  	}
    27  	err = rw.WriteVString(conn, groupTag)
    28  	if err != nil {
    29  		return err
    30  	}
    31  	return readError(conn)
    32  }
    33  
    34  func (s *CommandServer) handleURLTest(conn net.Conn) error {
    35  	defer conn.Close()
    36  	groupTag, err := rw.ReadVString(conn)
    37  	if err != nil {
    38  		return err
    39  	}
    40  	service := s.service
    41  	if service == nil {
    42  		return nil
    43  	}
    44  	abstractOutboundGroup, isLoaded := service.instance.Router().Outbound(groupTag)
    45  	if !isLoaded {
    46  		return writeError(conn, E.New("outbound group not found: ", groupTag))
    47  	}
    48  	outboundGroup, isOutboundGroup := abstractOutboundGroup.(adapter.OutboundGroup)
    49  	if !isOutboundGroup {
    50  		return writeError(conn, E.New("outbound is not a group: ", groupTag))
    51  	}
    52  	urlTest, isURLTest := abstractOutboundGroup.(*outbound.URLTest)
    53  	if isURLTest {
    54  		go urlTest.CheckOutbounds()
    55  	} else {
    56  		var historyStorage *urltest.HistoryStorage
    57  		if clashServer := service.instance.Router().ClashServer(); clashServer != nil {
    58  			historyStorage = clashServer.HistoryStorage()
    59  		} else {
    60  			return writeError(conn, E.New("Clash API is required for URLTest on non-URLTest group"))
    61  		}
    62  
    63  		outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
    64  			itOutbound, _ := service.instance.Router().Outbound(it)
    65  			return itOutbound
    66  		}), func(it adapter.Outbound) bool {
    67  			if it == nil {
    68  				return false
    69  			}
    70  			_, isGroup := it.(adapter.OutboundGroup)
    71  			if isGroup {
    72  				return false
    73  			}
    74  			return true
    75  		})
    76  		b, _ := batch.New(service.ctx, batch.WithConcurrencyNum[any](10))
    77  		for _, detour := range outbounds {
    78  			outboundToTest := detour
    79  			outboundTag := outboundToTest.Tag()
    80  			b.Go(outboundTag, func() (any, error) {
    81  				t, err := urltest.URLTest(service.ctx, "", outboundToTest)
    82  				if err != nil {
    83  					historyStorage.DeleteURLTestHistory(outboundTag)
    84  				} else {
    85  					historyStorage.StoreURLTestHistory(outboundTag, &urltest.History{
    86  						Time:  time.Now(),
    87  						Delay: t,
    88  					})
    89  				}
    90  				return nil, nil
    91  			})
    92  		}
    93  	}
    94  	return writeError(conn, nil)
    95  }