From 9fc42c5153af44d6464d5c74fc81880b1208018d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 17 Oct 2024 08:55:00 +0800 Subject: [PATCH 1/6] Update quic-go to v0.48.0 --- go.mod | 14 +++++++------- go.sum | 30 +++++++++++++++--------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index 5d10b44d15..3b39e14eb6 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.4 github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f - github.com/sagernet/quic-go v0.47.0-beta.2 + github.com/sagernet/quic-go v0.48.0-beta.1 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/sing v0.5.0-rc.2 github.com/sagernet/sing-dns v0.3.0-rc.2 @@ -44,10 +44,10 @@ require ( github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.28.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 - golang.org/x/net v0.27.0 - golang.org/x/sys v0.25.0 + golang.org/x/net v0.30.0 + golang.org/x/sys v0.26.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 google.golang.org/grpc v1.63.2 google.golang.org/protobuf v1.33.0 @@ -89,11 +89,11 @@ require ( github.com/vishvananda/netns v0.0.4 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/mod v0.19.0 // indirect + golang.org/x/mod v0.20.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 7afac346c4..321ad968b8 100644 --- a/go.sum +++ b/go.sum @@ -110,8 +110,8 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/quic-go v0.47.0-beta.2 h1:1tCGWFOSaXIeuQaHrwOMJIYvlupjTcaVInGQw5ArULU= -github.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYigzQ5lQu3Nh4wNh8Q= +github.com/sagernet/quic-go v0.48.0-beta.1 h1:86hQZrmuoARI3BpDRkQaP0iAVpywA4YsRrzJPYuPKWg= +github.com/sagernet/quic-go v0.48.0-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= @@ -170,16 +170,16 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -190,19 +190,19 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= From 2bc5ddd28416d30a9c4158cf466972697f8c024e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 17 Oct 2024 08:55:23 +0800 Subject: [PATCH 2/6] Update dependencies --- go.mod | 6 +++--- go.sum | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 3b39e14eb6..ab71a4cb56 100644 --- a/go.mod +++ b/go.mod @@ -7,14 +7,14 @@ require ( github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 - github.com/go-chi/chi/v5 v5.0.12 + github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/render v1.0.3 github.com/gofrs/uuid/v5 v5.3.0 github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/libdns/alidns v1.0.3 github.com/libdns/cloudflare v0.1.1 github.com/logrusorgru/aurora v2.0.3+incompatible - github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d + github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa github.com/mholt/acmez v1.2.0 github.com/miekg/dns v1.1.62 github.com/ooni/go-libtor v1.1.8 @@ -40,7 +40,7 @@ require ( github.com/sagernet/utls v1.6.7 github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 - github.com/spf13/cobra v1.8.0 + github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba diff --git a/go.sum b/go.sum index 321ad968b8..c989fe47ca 100644 --- a/go.sum +++ b/go.sum @@ -8,7 +8,7 @@ github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4 github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= @@ -17,8 +17,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= -github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= +github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= @@ -70,8 +70,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNvXW824QQeN7Y26uPL5249kzWKbzO9U= -github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= +github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4= +github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= @@ -141,8 +141,8 @@ github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYASco github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From ea70360b80e0266ec67daa70231fe6e25f33bcca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Thu, 17 Oct 2024 09:00:54 +0800 Subject: [PATCH 3/6] documentation: Bump version --- docs/changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index e70ec08b96..27431089e6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,11 @@ icon: material/alert-decagram --- +#### 1.11.0-alpha.1 + +* Update quic-go to v0.48.0 +* Fixes and improvements + ### 1.10.1 * Fixes and improvements From 93dfc30af0e0b9bded54300ce42c9ffe07457044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Fri, 18 Oct 2024 09:58:03 +0800 Subject: [PATCH 4/6] Add deprecated warnings --- cmd/sing-box/cmd.go | 3 + experimental/deprecated/constants.go | 79 +++++++++++++++++++++++ experimental/deprecated/env.go | 30 +++++++++ experimental/deprecated/manager.go | 19 ++++++ experimental/libbox/config.go | 4 ++ experimental/libbox/deprecated.go | 22 +++++++ experimental/libbox/platform.go | 1 + experimental/libbox/platform/interface.go | 2 + experimental/libbox/service.go | 5 ++ route/router_geo_resources.go | 3 + 10 files changed, 168 insertions(+) create mode 100644 experimental/deprecated/constants.go create mode 100644 experimental/deprecated/env.go create mode 100644 experimental/deprecated/manager.go create mode 100644 experimental/libbox/deprecated.go diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go index f6c37ff73e..9ae3a26835 100644 --- a/cmd/sing-box/cmd.go +++ b/cmd/sing-box/cmd.go @@ -7,8 +7,10 @@ import ( "strconv" "time" + "github.com/sagernet/sing-box/experimental/deprecated" _ "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" "github.com/spf13/cobra" @@ -65,4 +67,5 @@ func preRun(cmd *cobra.Command, args []string) { if len(configPaths) == 0 && len(configDirectories) == 0 { configPaths = append(configPaths, "config.json") } + globalCtx = service.ContextWith(globalCtx, deprecated.NewEnvManager(log.StdLogger())) } diff --git a/experimental/deprecated/constants.go b/experimental/deprecated/constants.go new file mode 100644 index 0000000000..a93da8667e --- /dev/null +++ b/experimental/deprecated/constants.go @@ -0,0 +1,79 @@ +package deprecated + +import ( + C "github.com/sagernet/sing-box/constant" + F "github.com/sagernet/sing/common/format" + + "golang.org/x/mod/semver" +) + +type Note struct { + Name string + Description string + DeprecatedVersion string + ScheduledVersion string + EnvName string + MigrationLink string +} + +func (n Note) Impending() bool { + if n.ScheduledVersion == "" { + return false + } + if !semver.IsValid("v" + C.Version) { + return false + } + versionMinor := semver.Compare(semver.MajorMinor("v"+C.Version), "v"+n.ScheduledVersion) + if versionMinor < 0 { + panic("invalid deprecated note: " + n.Name) + } + return versionMinor <= 1 +} + +func (n Note) String() string { + return F.ToString( + n.Description, " is deprecated in sing-box ", n.DeprecatedVersion, + " and will be removed in sing-box ", n.ScheduledVersion, ", checkout documentation for migration: ", n.MigrationLink, + ) +} + +var OptionBadMatchSource = Note{ + Name: "bad-match-source", + Description: "legacy match source rule item", + DeprecatedVersion: "1.10.0", + ScheduledVersion: "1.11.0", + MigrationLink: "https://sing-box.sagernet.org/deprecated/#match-source-rule-items-are-renamed", +} + +var OptionGEOIP = Note{ + Name: "geoip", + Description: "geoip database", + DeprecatedVersion: "1.8.0", + ScheduledVersion: "1.12.0", + EnvName: "GEOIP", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-geoip-to-rule-sets", +} + +var OptionGEOSITE = Note{ + Name: "geosite", + Description: "geosite database", + DeprecatedVersion: "1.8.0", + ScheduledVersion: "1.12.0", + EnvName: "GEOSITE", + MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-geosite-to-rule-sets", +} + +var OptionTUNAddressX = Note{ + Name: "tun-address-x", + Description: "legacy tun address fields", + DeprecatedVersion: "1.10.0", + ScheduledVersion: "1.12.0", + MigrationLink: "https://sing-box.sagernet.org/migration/#tun-address-fields-are-merged", +} + +var Options = []Note{ + OptionBadMatchSource, + OptionGEOIP, + OptionGEOSITE, + OptionTUNAddressX, +} diff --git a/experimental/deprecated/env.go b/experimental/deprecated/env.go new file mode 100644 index 0000000000..a724fb33fe --- /dev/null +++ b/experimental/deprecated/env.go @@ -0,0 +1,30 @@ +package deprecated + +import ( + "os" + "strconv" + + "github.com/sagernet/sing/common/logger" +) + +type envManager struct { + logger logger.Logger +} + +func NewEnvManager(logger logger.Logger) Manager { + return &envManager{logger: logger} +} + +func (f *envManager) ReportDeprecated(feature Note) { + if !feature.Impending() { + f.logger.Warn(feature.String()) + return + } + enable, enableErr := strconv.ParseBool(os.Getenv("ENABLE_DEPRECATED_" + feature.EnvName)) + if enableErr == nil && enable { + f.logger.Warn(feature.String()) + return + } + f.logger.Error(feature.String()) + f.logger.Fatal("to continuing using this feature, set ENABLE_DEPRECATED_" + feature.EnvName + "=true") +} diff --git a/experimental/deprecated/manager.go b/experimental/deprecated/manager.go new file mode 100644 index 0000000000..03e7d38617 --- /dev/null +++ b/experimental/deprecated/manager.go @@ -0,0 +1,19 @@ +package deprecated + +import ( + "context" + + "github.com/sagernet/sing/service" +) + +type Manager interface { + ReportDeprecated(note Note) +} + +func Report(ctx context.Context, note Note) { + manager := service.FromContext[Manager](ctx) + if manager == nil { + return + } + manager.ReportDeprecated(note) +} diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index b7731143cb..df8b6ee34e 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" + "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" @@ -97,6 +98,9 @@ func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network str return nil, os.ErrInvalid } +func (s *platformInterfaceStub) ReportDeprecated(note deprecated.Note) { +} + type interfaceMonitorStub struct{} func (s *interfaceMonitorStub) Start() error { diff --git a/experimental/libbox/deprecated.go b/experimental/libbox/deprecated.go new file mode 100644 index 0000000000..15b1526f79 --- /dev/null +++ b/experimental/libbox/deprecated.go @@ -0,0 +1,22 @@ +package libbox + +import "github.com/sagernet/sing-box/experimental/deprecated" + +var _ = deprecated.Note(DeprecatedNote{}) + +type DeprecatedNote struct { + Name string + Description string + DeprecatedVersion string + ScheduledVersion string + EnvName string + MigrationLink string +} + +func (n DeprecatedNote) Impending() bool { + return deprecated.Note(n).Impending() +} + +func (n DeprecatedNote) String() string { + return deprecated.Note(n).String() +} diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 4078140f8c..8306012ac2 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -22,6 +22,7 @@ type PlatformInterface interface { IncludeAllNetworks() bool ReadWIFIState() *WIFIState ClearDNSCache() + ReportDeprecated(feature DeprecatedNote) } type TunInterface interface { diff --git a/experimental/libbox/platform/interface.go b/experimental/libbox/platform/interface.go index 3bec13fa5b..e6be79dc0a 100644 --- a/experimental/libbox/platform/interface.go +++ b/experimental/libbox/platform/interface.go @@ -5,6 +5,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" + "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" @@ -25,4 +26,5 @@ type Interface interface { ClearDNSCache() ReadWIFIState() adapter.WIFIState process.Searcher + deprecated.Manager } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index c65090103c..cdfae04ca8 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -14,6 +14,7 @@ import ( "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/experimental/libbox/internal/procfs" "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/log" @@ -236,3 +237,7 @@ func (w *platformInterfaceWrapper) DisableColors() bool { func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) { w.iif.WriteLog(message) } + +func (w *platformInterfaceWrapper) ReportDeprecated(note deprecated.Note) { + w.iif.ReportDeprecated(DeprecatedNote(note)) +} diff --git a/route/router_geo_resources.go b/route/router_geo_resources.go index 14364d210d..42de84d00a 100644 --- a/route/router_geo_resources.go +++ b/route/router_geo_resources.go @@ -13,6 +13,7 @@ import ( "github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geosite" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/experimental/deprecated" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/rw" @@ -41,6 +42,7 @@ func (r *Router) LoadGeosite(code string) (adapter.Rule, error) { } func (r *Router) prepareGeoIPDatabase() error { + deprecated.Report(r.ctx, deprecated.OptionGEOIP) var geoPath string if r.geoIPOptions.Path != "" { geoPath = r.geoIPOptions.Path @@ -87,6 +89,7 @@ func (r *Router) prepareGeoIPDatabase() error { } func (r *Router) prepareGeositeDatabase() error { + deprecated.Report(r.ctx, deprecated.OptionGEOIP) var geoPath string if r.geositeOptions.Path != "" { geoPath = r.geositeOptions.Path From 0b42f26af8ec82a72bfb3929102810837a16bafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sat, 19 Oct 2024 21:07:43 +0800 Subject: [PATCH 5/6] Fix rule-set format --- option/rule_set.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/option/rule_set.go b/option/rule_set.go index b6ec113e35..d4368de38a 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -48,17 +48,6 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { if r.Tag == "" { return E.New("missing tag") } - if r.Type != C.RuleSetTypeInline { - switch r.Format { - case "": - return E.New("missing format") - case C.RuleSetFormatSource, C.RuleSetFormatBinary: - default: - return E.New("unknown rule-set format: " + r.Format) - } - } else { - r.Format = "" - } var v any switch r.Type { case "", C.RuleSetTypeInline: @@ -71,6 +60,17 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule-set type: " + r.Type) } + if r.Type != C.RuleSetTypeInline { + switch r.Format { + case "": + return E.New("missing format") + case C.RuleSetFormatSource, C.RuleSetFormatBinary: + default: + return E.New("unknown rule-set format: " + r.Format) + } + } else { + r.Format = "" + } err = UnmarshallExcluded(bytes, (*_RuleSet)(r), v) if err != nil { return err From 99840ba0f40d68ed4109ee1484567039eafb6360 Mon Sep 17 00:00:00 2001 From: beryll1um Date: Mon, 14 Oct 2024 02:15:29 +0200 Subject: [PATCH 6/6] Add high-level boundary management interface I rewrote the router's boundary management part to implement dynamic management from a high-level box interface. This also includes a number of changes I made in the process of rewriting some messy parts, such as the Outbound tree bottom-top starter. --- adapter/router.go | 18 +- box.go | 225 ++++----- box_outbound.go | 85 ---- common/process/searcher.go | 6 +- common/process/searcher_linux.go | 4 +- common/process/searcher_linux_shared.go | 2 +- experimental/libbox/config.go | 2 +- experimental/libbox/internal/procfs/procfs.go | 2 +- experimental/libbox/platform.go | 2 +- experimental/libbox/service.go | 6 +- inbound/default.go | 6 + route/router.go | 437 ++++++++++++------ route/router_outbound_starter.go | 60 +++ 13 files changed, 503 insertions(+), 352 deletions(-) delete mode 100644 box_outbound.go create mode 100644 route/router_outbound_starter.go diff --git a/adapter/router.go b/adapter/router.go index 619c1110cb..4abe38d8cb 100644 --- a/adapter/router.go +++ b/adapter/router.go @@ -18,14 +18,28 @@ import ( ) type Router interface { - Service + AddOutbound(outbound Outbound) error + AddInbound(inbound Inbound) error + + RemoveOutbound(tag string) error + RemoveInbound(tag string) error + PreStarter + + StartOutbounds() error + + Service + + StartInbounds() error + PostStarter + Cleanup() error + DefaultOutbound(network string) (Outbound, error) Outbounds() []Outbound Outbound(tag string) (Outbound, bool) - DefaultOutbound(network string) (Outbound, error) + Inbound(tag string) (Inbound, bool) FakeIPStore() FakeIPStore diff --git a/box.go b/box.go index 716b1b093c..46c5fec2fe 100644 --- a/box.go +++ b/box.go @@ -29,16 +29,16 @@ import ( var _ adapter.Service = (*Box)(nil) type Box struct { - createdAt time.Time - router adapter.Router - inbounds []adapter.Inbound - outbounds []adapter.Outbound - logFactory log.Factory - logger log.ContextLogger - preServices1 map[string]adapter.Service - preServices2 map[string]adapter.Service - postServices map[string]adapter.Service - done chan struct{} + createdAt time.Time + router adapter.Router + logFactory log.Factory + logger log.ContextLogger + preServices1 map[string]adapter.Service + preServices2 map[string]adapter.Service + postServices map[string]adapter.Service + platformInterface platform.Interface + ctx context.Context + done chan struct{} } type Options struct { @@ -97,57 +97,6 @@ func New(options Options) (*Box, error) { if err != nil { return nil, E.Cause(err, "parse route options") } - inbounds := make([]adapter.Inbound, 0, len(options.Inbounds)) - outbounds := make([]adapter.Outbound, 0, len(options.Outbounds)) - for i, inboundOptions := range options.Inbounds { - var in adapter.Inbound - var tag string - if inboundOptions.Tag != "" { - tag = inboundOptions.Tag - } else { - tag = F.ToString(i) - } - in, err = inbound.New( - ctx, - router, - logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), - tag, - inboundOptions, - options.PlatformInterface, - ) - if err != nil { - return nil, E.Cause(err, "parse inbound[", i, "]") - } - inbounds = append(inbounds, in) - } - for i, outboundOptions := range options.Outbounds { - var out adapter.Outbound - var tag string - if outboundOptions.Tag != "" { - tag = outboundOptions.Tag - } else { - tag = F.ToString(i) - } - out, err = outbound.New( - ctx, - router, - logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), - tag, - outboundOptions) - if err != nil { - return nil, E.Cause(err, "parse outbound[", i, "]") - } - outbounds = append(outbounds, out) - } - err = router.Initialize(inbounds, outbounds, func() adapter.Outbound { - out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"}) - common.Must(oErr) - outbounds = append(outbounds, out) - return out - }) - if err != nil { - return nil, err - } if options.PlatformInterface != nil { err = options.PlatformInterface.Initialize(ctx, router) if err != nil { @@ -183,18 +132,35 @@ func New(options Options) (*Box, error) { router.SetV2RayServer(v2rayServer) preServices2["v2ray api"] = v2rayServer } - return &Box{ - router: router, - inbounds: inbounds, - outbounds: outbounds, - createdAt: createdAt, - logFactory: logFactory, - logger: logFactory.Logger(), - preServices1: preServices1, - preServices2: preServices2, - postServices: postServices, - done: make(chan struct{}), - }, nil + box := &Box{ + router: router, + createdAt: createdAt, + logFactory: logFactory, + logger: logFactory.Logger(), + preServices1: preServices1, + preServices2: preServices2, + postServices: postServices, + platformInterface: options.PlatformInterface, + ctx: ctx, + done: make(chan struct{}), + } + for i, outOpts := range options.Outbounds { + if outOpts.Tag == "" { + outOpts.Tag = F.ToString(i) + } + if err := box.AddOutbound(outOpts); err != nil { + return nil, E.Cause(err, "create outbound") + } + } + for i, inOpts := range options.Inbounds { + if inOpts.Tag == "" { + inOpts.Tag = F.ToString(i) + } + if err := box.AddInbound(inOpts); err != nil { + return nil, E.Cause(err, "create inbound") + } + } + return box, nil } func (s *Box) PreStart() error { @@ -263,12 +229,10 @@ func (s *Box) preStart() error { } } } - err = s.router.PreStart() - if err != nil { + if err := s.router.PreStart(); err != nil { return E.Cause(err, "pre-start router") } - err = s.startOutbounds() - if err != nil { + if err := s.router.StartOutbounds(); err != nil { return err } return s.router.Start() @@ -291,20 +255,10 @@ func (s *Box) start() error { return E.Cause(err, "start ", serviceName) } } - for i, in := range s.inbounds { - var tag string - if in.Tag() == "" { - tag = F.ToString(i) - } else { - tag = in.Tag() - } - err = in.Start() - if err != nil { - return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]") - } + if err := s.router.StartInbounds(); err != nil { + return E.Cause(err, "start inbounds") } - err = s.postStart() - if err != nil { + if err = s.postStart(); err != nil { return err } return s.router.Cleanup() @@ -317,26 +271,8 @@ func (s *Box) postStart() error { return E.Cause(err, "start ", serviceName) } } - // TODO: reorganize ALL start order - for _, out := range s.outbounds { - if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound { - err := lateOutbound.PostStart() - if err != nil { - return E.Cause(err, "post-start outbound/", out.Tag()) - } - } - } - err := s.router.PostStart() - if err != nil { - return err - } - for _, in := range s.inbounds { - if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound { - err = lateInbound.PostStart() - if err != nil { - return E.Cause(err, "post-start inbound/", in.Tag()) - } - } + if err := s.router.PostStart(); err != nil { + return E.Cause(err, "post-start") } return nil } @@ -357,20 +293,6 @@ func (s *Box) Close() error { }) monitor.Finish() } - for i, in := range s.inbounds { - monitor.Start("close inbound/", in.Type(), "[", i, "]") - errors = E.Append(errors, in.Close(), func(err error) error { - return E.Cause(err, "close inbound/", in.Type(), "[", i, "]") - }) - monitor.Finish() - } - for i, out := range s.outbounds { - monitor.Start("close outbound/", out.Type(), "[", i, "]") - errors = E.Append(errors, common.Close(out), func(err error) error { - return E.Cause(err, "close outbound/", out.Type(), "[", i, "]") - }) - monitor.Finish() - } monitor.Start("close router") if err := common.Close(s.router); err != nil { errors = E.Append(errors, err, func(err error) error { @@ -403,3 +325,58 @@ func (s *Box) Close() error { func (s *Box) Router() adapter.Router { return s.router } + +func (s *Box) AddOutbound(option option.Outbound) error { + if option.Tag == "" { + return E.New("empty tag") + } + out, err := outbound.New( + s.ctx, + s.router, + s.logFactory.NewLogger(F.ToString("outbound/", option.Type, "[", option.Tag, "]")), + option.Tag, + option, + ) + if err != nil { + return E.Cause(err, "parse addited outbound") + } + if err := s.router.AddOutbound(out); err != nil { + return E.Cause(err, "outbound/", option.Type, "[", option.Tag, "]") + } + return nil +} + +func (s *Box) AddInbound(option option.Inbound) error { + if option.Tag == "" { + return E.New("empty tag") + } + in, err := inbound.New( + s.ctx, + s.router, + s.logFactory.NewLogger(F.ToString("inbound/", option.Type, "[", option.Tag, "]")), + option.Tag, + option, + s.platformInterface, + ) + if err != nil { + return E.Cause(err, "parse addited inbound") + } + if err := s.router.AddInbound(in); err != nil { + return E.Cause(err, "inbound/", option.Type, "[", option.Tag, "]") + } + return nil +} + +func (s *Box) RemoveOutbound(tag string) error { + if err := s.router.RemoveOutbound(tag); err != nil { + return E.Cause(err, "outbound[", tag, "]") + } + return nil +} + +func (s *Box) RemoveInbound(tag string) error { + if err := s.router.RemoveInbound(tag); err != nil { + return E.Cause(err, "inbound[", tag, "]") + } + return nil +} diff --git a/box_outbound.go b/box_outbound.go deleted file mode 100644 index f03f3b7d41..0000000000 --- a/box_outbound.go +++ /dev/null @@ -1,85 +0,0 @@ -package box - -import ( - "strings" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/taskmonitor" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" -) - -func (s *Box) startOutbounds() error { - monitor := taskmonitor.New(s.logger, C.StartTimeout) - outboundTags := make(map[adapter.Outbound]string) - outbounds := make(map[string]adapter.Outbound) - for i, outboundToStart := range s.outbounds { - var outboundTag string - if outboundToStart.Tag() == "" { - outboundTag = F.ToString(i) - } else { - outboundTag = outboundToStart.Tag() - } - if _, exists := outbounds[outboundTag]; exists { - return E.New("outbound tag ", outboundTag, " duplicated") - } - outboundTags[outboundToStart] = outboundTag - outbounds[outboundTag] = outboundToStart - } - started := make(map[string]bool) - for { - canContinue := false - startOne: - for _, outboundToStart := range s.outbounds { - outboundTag := outboundTags[outboundToStart] - if started[outboundTag] { - continue - } - dependencies := outboundToStart.Dependencies() - for _, dependency := range dependencies { - if !started[dependency] { - continue startOne - } - } - started[outboundTag] = true - canContinue = true - if starter, isStarter := outboundToStart.(interface { - Start() error - }); isStarter { - monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") - err := starter.Start() - monitor.Finish() - if err != nil { - return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") - } - } - } - if len(started) == len(s.outbounds) { - break - } - if canContinue { - continue - } - currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool { - return !started[outboundTags[it]] - }) - var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error - lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error { - problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool { - return !started[it] - }) - if common.Contains(oTree, problemOutboundTag) { - return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag) - } - problemOutbound := outbounds[problemOutboundTag] - if problemOutbound == nil { - return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]") - } - return lintOutbound(append(oTree, problemOutboundTag), problemOutbound) - } - return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound) - } - return nil -} diff --git a/common/process/searcher.go b/common/process/searcher.go index cee81068ca..97df88c940 100644 --- a/common/process/searcher.go +++ b/common/process/searcher.go @@ -12,7 +12,7 @@ import ( ) type Searcher interface { - FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) + FindProcessInfo(ctx context.Context, network string, source netip.AddrPort) (*Info, error) } var ErrNotFound = E.New("process not found") @@ -29,8 +29,8 @@ type Info struct { UserId int32 } -func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { - info, err := searcher.FindProcessInfo(ctx, network, source, destination) +func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort) (*Info, error) { + info, err := searcher.FindProcessInfo(ctx, network, source) if err != nil { return nil, err } diff --git a/common/process/searcher_linux.go b/common/process/searcher_linux.go index 39470205a4..037e377c08 100644 --- a/common/process/searcher_linux.go +++ b/common/process/searcher_linux.go @@ -19,8 +19,8 @@ func NewSearcher(config Config) (Searcher, error) { return &linuxSearcher{config.Logger}, nil } -func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { - inode, uid, err := resolveSocketByNetlink(network, source, destination) +func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort) (*Info, error) { + inode, uid, err := resolveSocketByNetlink(network, source) if err != nil { return nil, err } diff --git a/common/process/searcher_linux_shared.go b/common/process/searcher_linux_shared.go index e75b0b4f9d..220a58615f 100644 --- a/common/process/searcher_linux_shared.go +++ b/common/process/searcher_linux_shared.go @@ -36,7 +36,7 @@ const ( pathProc = "/proc" ) -func resolveSocketByNetlink(network string, source netip.AddrPort, destination netip.AddrPort) (inode, uid uint32, err error) { +func resolveSocketByNetlink(network string, source netip.AddrPort) (inode, uid uint32, err error) { var family uint8 var protocol uint8 diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index df8b6ee34e..c3d33f0899 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -94,7 +94,7 @@ func (s *platformInterfaceStub) ReadWIFIState() adapter.WIFIState { return adapter.WIFIState{} } -func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) { +func (s *platformInterfaceStub) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort) (*process.Info, error) { return nil, os.ErrInvalid } diff --git a/experimental/libbox/internal/procfs/procfs.go b/experimental/libbox/internal/procfs/procfs.go index 8c918a799f..2d8e11e2aa 100644 --- a/experimental/libbox/internal/procfs/procfs.go +++ b/experimental/libbox/internal/procfs/procfs.go @@ -30,7 +30,7 @@ func init() { } } -func ResolveSocketByProcSearch(network string, source, _ netip.AddrPort) int32 { +func ResolveSocketByProcSearch(network string, source netip.AddrPort) int32 { if netIndexOfLocal < 0 || netIndexOfUid < 0 { return -1 } diff --git a/experimental/libbox/platform.go b/experimental/libbox/platform.go index 8306012ac2..8cf150ea77 100644 --- a/experimental/libbox/platform.go +++ b/experimental/libbox/platform.go @@ -10,7 +10,7 @@ type PlatformInterface interface { OpenTun(options TunOptions) (int32, error) WriteLog(message string) UseProcFS() bool - FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error) + FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32) (int32, error) PackageNameByUid(uid int32) (string, error) UIDByPackageName(packageName string) (int32, error) UsePlatformDefaultInterfaceMonitor() bool diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index cdfae04ca8..1f2fe6cbd0 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -203,10 +203,10 @@ func (w *platformInterfaceWrapper) ReadWIFIState() adapter.WIFIState { return (adapter.WIFIState)(*wifiState) } -func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) { +func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort) (*process.Info, error) { var uid int32 if w.useProcFS { - uid = procfs.ResolveSocketByProcSearch(network, source, destination) + uid = procfs.ResolveSocketByProcSearch(network, source) if uid == -1 { return nil, E.New("procfs: not found") } @@ -221,7 +221,7 @@ func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network return nil, E.New("unknown network: ", network) } var err error - uid, err = w.iif.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port()), destination.Addr().String(), int32(destination.Port())) + uid, err = w.iif.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port())) if err != nil { return nil, err } diff --git a/inbound/default.go b/inbound/default.go index 44c580deb9..f8490dcad5 100644 --- a/inbound/default.go +++ b/inbound/default.go @@ -115,6 +115,12 @@ func (a *myInboundAdapter) Start() error { func (a *myInboundAdapter) Close() error { a.inShutdown.Store(true) + if a.tcpListener != nil { + a.logger.Info("tcp server closed at ", a.tcpListener.Addr()) + } + if a.udpConn != nil { + a.logger.Info("udp server closed at ", a.udpConn.LocalAddr()) + } var err error if a.systemProxy != nil && a.systemProxy.IsEnabled() { err = a.systemProxy.Disable() diff --git a/route/router.go b/route/router.go index c8fe94be5b..de6a012d37 100644 --- a/route/router.go +++ b/route/router.go @@ -10,6 +10,7 @@ import ( "os/user" "runtime" "strings" + "sync" "time" "github.com/sagernet/sing-box/adapter" @@ -50,11 +51,15 @@ import ( var _ adapter.Router = (*Router)(nil) type Router struct { - ctx context.Context - logger log.ContextLogger - dnsLogger log.ContextLogger + ctx context.Context + logger log.ContextLogger + dnsLogger log.ContextLogger + // Currently this is responsible for protecting inbound and outbound dynamic + // control. I'm not sure if it can be separated because I haven't delved + // into the logic yet to make sure they don't interfere with each other. + // To research, may improve performance on some high-load setups. + boundary sync.RWMutex inboundByTag map[string]adapter.Inbound - outbounds []adapter.Outbound outboundByTag map[string]adapter.Outbound rules []adapter.Rule defaultDetour string @@ -113,6 +118,7 @@ func NewRouter( ctx: ctx, logger: logFactory.NewLogger("router"), dnsLogger: logFactory.NewLogger("dns"), + inboundByTag: make(map[string]adapter.Inbound), outboundByTag: make(map[string]adapter.Outbound), rules: make([]adapter.Rule, 0, len(options.Rules)), dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)), @@ -373,76 +379,6 @@ func NewRouter( return router, nil } -func (r *Router) Initialize(inbounds []adapter.Inbound, outbounds []adapter.Outbound, defaultOutbound func() adapter.Outbound) error { - inboundByTag := make(map[string]adapter.Inbound) - for _, inbound := range inbounds { - inboundByTag[inbound.Tag()] = inbound - } - outboundByTag := make(map[string]adapter.Outbound) - for _, detour := range outbounds { - outboundByTag[detour.Tag()] = detour - } - var defaultOutboundForConnection adapter.Outbound - var defaultOutboundForPacketConnection adapter.Outbound - if r.defaultDetour != "" { - detour, loaded := outboundByTag[r.defaultDetour] - if !loaded { - return E.New("default detour not found: ", r.defaultDetour) - } - if common.Contains(detour.Network(), N.NetworkTCP) { - defaultOutboundForConnection = detour - } - if common.Contains(detour.Network(), N.NetworkUDP) { - defaultOutboundForPacketConnection = detour - } - } - if defaultOutboundForConnection == nil { - for _, detour := range outbounds { - if common.Contains(detour.Network(), N.NetworkTCP) { - defaultOutboundForConnection = detour - break - } - } - } - if defaultOutboundForPacketConnection == nil { - for _, detour := range outbounds { - if common.Contains(detour.Network(), N.NetworkUDP) { - defaultOutboundForPacketConnection = detour - break - } - } - } - if defaultOutboundForConnection == nil || defaultOutboundForPacketConnection == nil { - detour := defaultOutbound() - if defaultOutboundForConnection == nil { - defaultOutboundForConnection = detour - } - if defaultOutboundForPacketConnection == nil { - defaultOutboundForPacketConnection = detour - } - outbounds = append(outbounds, detour) - outboundByTag[detour.Tag()] = detour - } - r.inboundByTag = inboundByTag - r.outbounds = outbounds - r.defaultOutboundForConnection = defaultOutboundForConnection - r.defaultOutboundForPacketConnection = defaultOutboundForPacketConnection - r.outboundByTag = outboundByTag - for i, rule := range r.rules { - if _, loaded := outboundByTag[rule.Outbound()]; !loaded { - return E.New("outbound not found for rule[", i, "]: ", rule.Outbound()) - } - } - return nil -} - -func (r *Router) Outbounds() []adapter.Outbound { - if !r.started { - return nil - } - return r.outbounds -} - func (r *Router) PreStart() error { monitor := taskmonitor.New(r.logger, C.StartTimeout) if r.interfaceMonitor != nil { @@ -581,9 +517,191 @@ func (r *Router) Start() error { return nil } +func (r *Router) Cleanup() error { + for _, ruleSet := range r.ruleSetMap { + ruleSet.Cleanup() + } + runtime.GC() + return nil +} + +func (r *Router) AddOutbound(out adapter.Outbound) error { + r.boundary.Lock() + defer r.boundary.Unlock() + + if _, ok := r.outboundByTag[out.Tag()]; ok { + return E.New("duplication of tag") + } + + if r.defaultDetour == "" || r.defaultDetour == out.Tag() { + if r.defaultOutboundForConnection == nil { + if common.Contains(out.Network(), N.NetworkTCP) { + r.defaultOutboundForConnection = out + } + } + if r.defaultOutboundForPacketConnection == nil { + if common.Contains(out.Network(), N.NetworkUDP) { + r.defaultOutboundForPacketConnection = out + } + } + } + + if r.started { + monitor := taskmonitor.New(r.logger, C.StartTimeout) + monitor.Start("initialize outbound/", out.Type(), "[", out.Tag(), "]") + defer monitor.Finish() + + if startable, isStartable := out.(interface{ Start() error }); isStartable { + if err := startable.Start(); err != nil { + return E.Cause(err, "start") + } + } + + if err := postStartOutbound(out); err != nil { + return E.Cause(err, "post start") + } + } + + r.outboundByTag[out.Tag()] = out + return nil +} + +func (r *Router) AddInbound(in adapter.Inbound) error { + r.boundary.Lock() + defer r.boundary.Unlock() + + if _, ok := r.inboundByTag[in.Tag()]; ok { + return E.New("duplication of tag") + } + + if r.started { + monitor := taskmonitor.New(r.logger, C.StartTimeout) + monitor.Start("initialize inbound/", in.Type(), "[", in.Tag(), "]") + defer monitor.Finish() + + if err := in.Start(); err != nil { + return E.Cause(err, "start") + } + + if err := postStartInbound(in); err != nil { + return E.Cause(err, "post-start") + } + } + + r.inboundByTag[in.Tag()] = in + return nil +} + +func (r *Router) RemoveOutbound(tag string) error { + r.boundary.Lock() + defer r.boundary.Unlock() + + out, ok := r.outboundByTag[tag] + if !ok { + return E.New("unknown tag") + } + delete(r.outboundByTag, tag) + + if out == r.defaultOutboundForConnection { + r.defaultOutboundForConnection = nil + } + if out == r.defaultOutboundForPacketConnection { + r.defaultOutboundForPacketConnection = nil + } + if r.defaultDetour == "" { + for _, out := range r.outboundByTag { + if r.defaultOutboundForConnection == nil { + if common.Contains(out.Network(), N.NetworkTCP) { + r.defaultOutboundForConnection = out + } + if common.Contains(out.Network(), N.NetworkUDP) { + r.defaultOutboundForPacketConnection = out + } + if r.defaultOutboundForConnection != nil && r.defaultOutboundForPacketConnection != nil { + break + } + } + } + } + + if r.started { + if err := common.Close(out); err != nil { + return E.Cause(err, "close") + } + } + + return nil +} + +func (r *Router) RemoveInbound(tag string) error { + r.boundary.Lock() + defer r.boundary.Unlock() + + in, ok := r.inboundByTag[tag] + if !ok { + return E.New("unknown tag") + } + delete(r.inboundByTag, tag) + + if r.started { + if err := in.Close(); err != nil { + return E.Cause(err, "close") + } + } + + return nil +} + +func (r *Router) StartOutbounds() error { + monitor := taskmonitor.New(r.logger, C.StartTimeout) + startedTags := make(map[string]struct{}) + + for tag, out := range r.outboundByTag { + if err := (&OutboundStarter{ + outboundByTag: r.outboundByTag, + startedTags: startedTags, + monitor: monitor, + }).Start(tag, make(map[string]struct{})); err != nil { + return E.Cause(err, "start outbound/", out.Type(), "[", tag, "]") + } + } + + return nil +} + +func (r *Router) StartInbounds() error { + for tag, in := range r.inboundByTag { + if err := in.Start(); err != nil { + return E.Cause(err, "start inbound/", in.Type(), "[", tag, "]") + } + } + return nil +} + +func (r *Router) closeBounds(monitor *taskmonitor.Monitor) error { + r.boundary.Lock() + defer r.boundary.Unlock() + var err error + for tag, in := range r.inboundByTag { + monitor.Start("close inbound/", in.Type(), "[", tag, "]") + err = E.Append(err, in.Close(), func(err error) error { + return E.Cause(err, "close inbound/", in.Type(), "[", tag, "]") + }) + monitor.Finish() + } + for tag, out := range r.outboundByTag { + monitor.Start("close outbound/", out.Type(), "[", tag, "]") + err = E.Append(err, common.Close(out), func(err error) error { + return E.Cause(err, "close outbound/", out.Type(), "[", tag, "]") + }) + monitor.Finish() + } + return err +} + func (r *Router) Close() error { monitor := taskmonitor.New(r.logger, C.StopTimeout) - var err error + err := r.closeBounds(monitor) for i, rule := range r.rules { monitor.Start("close rule[", i, "]") err = E.Append(err, rule.Close(), func(err error) error { @@ -654,10 +772,35 @@ func (r *Router) Close() error { }) monitor.Finish() } + r.started = false return err } +func postStartOutbound(out adapter.Outbound) error { + if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound { + if err := lateOutbound.PostStart(); err != nil { + return E.Cause(err, "outbound/", out.Type(), "[", out.Tag(), "]") + } + } + return nil +} + +func postStartInbound(in adapter.Inbound) error { + if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound { + if err := lateInbound.PostStart(); err != nil { + return E.Cause(err, "inbound/", in.Type(), "[", in.Tag(), "]") + } + } + return nil +} + func (r *Router) PostStart() error { + // TODO: reorganize ALL start order + for _, out := range r.outboundByTag { + if err := postStartOutbound(out); err != nil { + return err + } + } monitor := taskmonitor.New(r.logger, C.StopTimeout) if len(r.ruleSets) > 0 { monitor.Start("initialize rule-set") @@ -749,35 +892,58 @@ func (r *Router) PostStart() error { return E.Cause(err, "post start rule_set[", ruleSet.Name(), "]") } } - r.started = true - return nil -} - -func (r *Router) Cleanup() error { - for _, ruleSet := range r.ruleSetMap { - ruleSet.Cleanup() + for _, in := range r.inboundByTag { + if err := postStartInbound(in); err != nil { + return err + } } - runtime.GC() + r.started = true return nil } -func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { - outbound, loaded := r.outboundByTag[tag] - return outbound, loaded -} - func (r *Router) DefaultOutbound(network string) (adapter.Outbound, error) { - if network == N.NetworkTCP { + r.boundary.RLock() + defer r.boundary.RUnlock() + switch network { + case N.NetworkTCP: if r.defaultOutboundForConnection == nil { return nil, E.New("missing default outbound for TCP connections") } return r.defaultOutboundForConnection, nil - } else { + case N.NetworkUDP: if r.defaultOutboundForPacketConnection == nil { return nil, E.New("missing default outbound for UDP connections") } return r.defaultOutboundForPacketConnection, nil } + return nil, E.New("wrong network type provided") +} + +func (r *Router) Outbounds() []adapter.Outbound { + if !r.started { + return nil + } + r.boundary.RLock() + defer r.boundary.RUnlock() + res := make([]adapter.Outbound, 0, len(r.outboundByTag)) + for _, out := range r.outboundByTag { + res = append(res, out) + } + return res +} + +func (r *Router) Outbound(tag string) (adapter.Outbound, bool) { + r.boundary.RLock() + defer r.boundary.RUnlock() + outbound, loaded := r.outboundByTag[tag] + return outbound, loaded +} + +func (r *Router) Inbound(tag string) (adapter.Inbound, bool) { + r.boundary.RLock() + defer r.boundary.RUnlock() + inbound, loaded := r.inboundByTag[tag] + return inbound, loaded } func (r *Router) FakeIPStore() adapter.FakeIPStore { @@ -802,8 +968,8 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad if metadata.LastInbound == metadata.InboundDetour { return E.New("routing loop on detour: ", metadata.InboundDetour) } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { + detour, ok := r.Inbound(metadata.InboundDetour) + if !ok { return E.New("inbound detour not found: ", metadata.InboundDetour) } injectable, isInjectable := detour.(adapter.InjectableInbound) @@ -908,15 +1074,27 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad } else if metadata.Destination.IsIPv6() { metadata.IPVersion = 6 } - ctx, matchedRule, detour, err := r.match(ctx, &metadata, r.defaultOutboundForConnection) - if err != nil { - return err + + rule, detour := r.ruleByMetadata(ctx, &metadata) + if rule == nil { + var err error + detour, err = r.DefaultOutbound(N.NetworkTCP) + if err != nil { + return E.New("missing supported outbound, closing packet connection") + } + } + if tag, loaded := outbound.TagFromContext(ctx); loaded { + if tag == detour.Tag() { + return E.New("connection loopback in outbound/", detour.Type(), "[", detour.Tag(), "]") + } } if !common.Contains(detour.Network(), N.NetworkTCP) { - return E.New("missing supported outbound, closing connection") + return E.New("missing support of network type by outbound, closing packet connection") } + ctx = outbound.ContextWithTag(ctx, detour.Tag()) + if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, matchedRule) + trackerConn, tracker := r.clashServer.RoutedConnection(ctx, conn, metadata, rule) defer tracker.Leave() conn = trackerConn } @@ -936,8 +1114,8 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m if metadata.LastInbound == metadata.InboundDetour { return E.New("routing loop on detour: ", metadata.InboundDetour) } - detour := r.inboundByTag[metadata.InboundDetour] - if detour == nil { + detour, ok := r.Inbound(metadata.InboundDetour) + if !ok { return E.New("inbound detour not found: ", metadata.InboundDetour) } injectable, isInjectable := detour.(adapter.InjectableInbound) @@ -1082,15 +1260,27 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m } else if metadata.Destination.IsIPv6() { metadata.IPVersion = 6 } - ctx, matchedRule, detour, err := r.match(ctx, &metadata, r.defaultOutboundForPacketConnection) - if err != nil { - return err + + rule, detour := r.ruleByMetadata(ctx, &metadata) + if rule == nil { + var err error + detour, err = r.DefaultOutbound(N.NetworkUDP) + if err != nil { + return E.New("missing supported outbound, closing packet connection") + } + } + if tag, loaded := outbound.TagFromContext(ctx); loaded { + if tag == detour.Tag() { + return E.New("connection loopback in outbound/", detour.Type(), "[", detour.Tag(), "]") + } } if !common.Contains(detour.Network(), N.NetworkUDP) { - return E.New("missing supported outbound, closing packet connection") + return E.New("missing support of network type by outbound, closing packet connection") } + ctx = outbound.ContextWithTag(ctx, detour.Tag()) + if r.clashServer != nil { - trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, matchedRule) + trackerConn, tracker := r.clashServer.RoutedPacketConnection(ctx, conn, metadata, rule) defer tracker.Leave() conn = trackerConn } @@ -1105,26 +1295,9 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m return detour.NewPacketConnection(ctx, conn, metadata) } -func (r *Router) match(ctx context.Context, metadata *adapter.InboundContext, defaultOutbound adapter.Outbound) (context.Context, adapter.Rule, adapter.Outbound, error) { - matchRule, matchOutbound := r.match0(ctx, metadata, defaultOutbound) - if contextOutbound, loaded := outbound.TagFromContext(ctx); loaded { - if contextOutbound == matchOutbound.Tag() { - return nil, nil, nil, E.New("connection loopback in outbound/", matchOutbound.Type(), "[", matchOutbound.Tag(), "]") - } - } - ctx = outbound.ContextWithTag(ctx, matchOutbound.Tag()) - return ctx, matchRule, matchOutbound, nil -} - -func (r *Router) match0(ctx context.Context, metadata *adapter.InboundContext, defaultOutbound adapter.Outbound) (adapter.Rule, adapter.Outbound) { +func (r *Router) processInfoByMetadata(ctx context.Context, metadata *adapter.InboundContext) *process.Info { if r.processSearcher != nil { - var originDestination netip.AddrPort - if metadata.OriginDestination.IsValid() { - originDestination = metadata.OriginDestination.AddrPort() - } else if metadata.Destination.IsIP() { - originDestination = metadata.Destination.AddrPort() - } - processInfo, err := process.FindProcessInfo(r.processSearcher, ctx, metadata.Network, metadata.Source.AddrPort(), originDestination) + processInfo, err := process.FindProcessInfo(r.processSearcher, ctx, metadata.Network, metadata.Source.AddrPort()) if err != nil { r.logger.InfoContext(ctx, "failed to search process: ", err) } else { @@ -1145,21 +1318,26 @@ func (r *Router) match0(ctx context.Context, metadata *adapter.InboundContext, d r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId) } } - metadata.ProcessInfo = processInfo + return processInfo } } + return nil +} + +func (r *Router) ruleByMetadata(ctx context.Context, metadata *adapter.InboundContext) (adapter.Rule, adapter.Outbound) { + metadata.ProcessInfo = r.processInfoByMetadata(ctx, metadata) for i, rule := range r.rules { metadata.ResetRuleCache() if rule.Match(metadata) { detour := rule.Outbound() - r.logger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour) + r.logger.DebugContext(ctx, "rule[", i, "] ", rule.String(), " => ", detour) if outbound, loaded := r.Outbound(detour); loaded { return rule, outbound } - r.logger.ErrorContext(ctx, "outbound not found: ", detour) + r.logger.ErrorContext(ctx, "not found outbound[", detour, "]") } } - return nil, defaultOutbound + return nil, nil } func (r *Router) InterfaceFinder() control.InterfaceFinder { @@ -1306,8 +1484,8 @@ func (r *Router) notifyNetworkUpdate(event int) { func (r *Router) ResetNetwork() error { conntrack.Close() - for _, outbound := range r.outbounds { - listener, isListener := outbound.(adapter.InterfaceUpdateListener) + for _, out := range r.Outbounds() { + listener, isListener := out.(adapter.InterfaceUpdateListener) if isListener { listener.InterfaceUpdated() } @@ -1316,6 +1494,7 @@ func (r *Router) ResetNetwork() error { for _, transport := range r.transports { transport.Reset() } + return nil } diff --git a/route/router_outbound_starter.go b/route/router_outbound_starter.go new file mode 100644 index 0000000000..72cd242fb7 --- /dev/null +++ b/route/router_outbound_starter.go @@ -0,0 +1,60 @@ +package route + +import ( + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/taskmonitor" + E "github.com/sagernet/sing/common/exceptions" +) + +type OutboundStarter struct { + outboundByTag map[string]adapter.Outbound + startedTags map[string]struct{} + monitor *taskmonitor.Monitor +} + +func (s *OutboundStarter) Start(tag string, pathIncludesTags map[string]struct{}) error { + adapter := s.outboundByTag[tag] + if adapter == nil { + return E.New("dependency[", tag, "] is not found") + } + + // The outbound may have been started by another subtree in the previous, + // we don't need to start it again. + if _, ok := s.startedTags[tag]; ok { + return nil + } + + // If we detected the repetition of the tags in scope of tree evaluation, + // the circular dependency is found, as it grows from bottom to top. + if _, ok := pathIncludesTags[tag]; ok { + return E.New("circular dependency related with outbound/", adapter.Type(), "[", tag, "]") + } + + // This required to be done only if that outbound isn't already started, + // because some dependencies may come to the same root, + // but they aren't circular. + pathIncludesTags[tag] = struct{}{} + + // Next, we are recursively starting all dependencies of the current + // outbound and repeating the cycle. + for _, dependencyTag := range adapter.Dependencies() { + if err := s.Start(dependencyTag, pathIncludesTags); err != nil { + return err + } + } + + // Anyway, it will be finished soon, nothing will happen if I'll include + // Startable interface typecasting too. + s.monitor.Start("initialize outbound/", adapter.Type(), "[", tag, "]") + defer s.monitor.Finish() + + // After the evaluation of entire tree let's begin to start all + // the outbounds! + if startable, isStartable := adapter.(interface{ Start() error }); isStartable { + if err := startable.Start(); err != nil { + return E.Cause(err, "initialize outbound/", adapter.Type(), "[", tag, "]") + } + } + + return nil +}