mirror of
https://github.com/SagerNet/sing.git
synced 2025-04-04 12:27:37 +03:00
Refine geosite
This commit is contained in:
parent
5991cfc072
commit
12408d60a0
27 changed files with 739 additions and 263 deletions
27
README.md
27
README.md
|
@ -2,11 +2,28 @@
|
|||
|
||||
Do you hear the people sing?
|
||||
|
||||
```shell
|
||||
# geo resources
|
||||
go install -v -trimpath -ldflags "-s -w -buildid=" ./cli/get-geoip
|
||||
go install -v -trimpath -ldflags "-s -w -buildid=" ./cli/gen-geosite
|
||||
### geosite
|
||||
|
||||
# ss-local
|
||||
```shell
|
||||
go install -v -trimpath -ldflags "-s -w -buildid=" ./cli/geosite
|
||||
```
|
||||
|
||||
create from v2ray
|
||||
|
||||
`geosite add v2ray`
|
||||
|
||||
create cn only dat
|
||||
|
||||
`geosite add v2ray cn`
|
||||
|
||||
### geoip
|
||||
|
||||
```shell
|
||||
wget 'https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb'
|
||||
```
|
||||
|
||||
### ss-local
|
||||
|
||||
```shell
|
||||
go install -v -trimpath -ldflags "-s -w -buildid=" ./cli/ss-local
|
||||
```
|
|
@ -1,60 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/ulikunitz/xz"
|
||||
"github.com/v2fly/v2ray-core/v5/app/router/routercommon"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func main() {
|
||||
response, err := http.Get("https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat.xz")
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
reader, err := xz.NewReader(response.Body)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
geosite := routercommon.GeoSiteList{}
|
||||
if err = proto.Unmarshal(data, &geosite); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
output, err := os.Create("geosite.dat")
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
binary.Write(output, binary.BigEndian, byte(0)) // version
|
||||
writer, _ := zstd.NewWriter(output)
|
||||
rw.WriteUVariant(writer, uint64(len(geosite.Entry)))
|
||||
for _, site := range geosite.Entry {
|
||||
rw.WriteVString(writer, site.CountryCode)
|
||||
domains := make([]string, 0, len(site.Domain))
|
||||
for _, domain := range site.Domain {
|
||||
if domain.Type == routercommon.Domain_Full {
|
||||
domains = append(domains, domain.Value)
|
||||
} else {
|
||||
domains = append(domains, domain.Value)
|
||||
domains = append(domains, "."+domain.Value)
|
||||
}
|
||||
}
|
||||
rw.WriteUVariant(writer, uint64(len(domains)))
|
||||
for _, domain := range domains {
|
||||
rw.WriteVString(writer, domain)
|
||||
}
|
||||
}
|
||||
writer.Close()
|
||||
output.Close()
|
||||
}
|
209
cli/geosite/main.go
Normal file
209
cli/geosite/main.go
Normal file
|
@ -0,0 +1,209 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/geosite"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/ulikunitz/xz"
|
||||
"github.com/v2fly/v2ray-core/v5/app/router/routercommon"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var path string
|
||||
|
||||
func main() {
|
||||
command := &cobra.Command{
|
||||
Use: "geosite ...",
|
||||
}
|
||||
command.PersistentFlags().StringVarP(&path, "file", "f", "geosite.dat", "set resource path")
|
||||
command.AddCommand(&cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List codes",
|
||||
PreRun: load,
|
||||
Run: edit,
|
||||
})
|
||||
command.AddCommand(&cobra.Command{
|
||||
Use: "keep",
|
||||
Short: "Keep selected codes",
|
||||
PreRun: load,
|
||||
Run: keep,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
})
|
||||
command.AddCommand(&cobra.Command{
|
||||
Use: "add <v2ray | loyalsoldier | path | url> [code]...",
|
||||
Short: "Add codes form external file or url",
|
||||
PreRun: load0,
|
||||
Run: add,
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
})
|
||||
if err := command.Execute(); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var site map[string][]string
|
||||
|
||||
func load(cmd *cobra.Command, args []string) {
|
||||
geoFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
logrus.Fatal(E.Cause(err, "open geo resources"))
|
||||
}
|
||||
defer geoFile.Close()
|
||||
site, err = geosite.Read(geoFile)
|
||||
if err != nil {
|
||||
logrus.Fatal(E.Cause(err, "read geo resources"))
|
||||
}
|
||||
}
|
||||
|
||||
func load0(cmd *cobra.Command, args []string) {
|
||||
geoFile, err := os.Open(path)
|
||||
if err == nil {
|
||||
defer geoFile.Close()
|
||||
site, err = geosite.Read(geoFile)
|
||||
if err != nil {
|
||||
logrus.Fatal(E.Cause(err, "read geo resources"))
|
||||
}
|
||||
}
|
||||
site = make(map[string][]string)
|
||||
}
|
||||
|
||||
func edit(cmd *cobra.Command, args []string) {
|
||||
for code := range site {
|
||||
println(strings.ToLower(code))
|
||||
}
|
||||
}
|
||||
|
||||
func keep(cmd *cobra.Command, args []string) {
|
||||
kept := make(map[string][]string)
|
||||
for _, code := range args {
|
||||
code = strings.ToUpper(code)
|
||||
if domains, exists := site[code]; exists {
|
||||
kept[code] = domains
|
||||
} else {
|
||||
logrus.Fatal("code ", strings.ToLower(code), " do not exists!")
|
||||
}
|
||||
}
|
||||
geoFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
defer geoFile.Close()
|
||||
err = geosite.Write(geoFile, kept)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func add(cmd *cobra.Command, args []string) {
|
||||
resource := args[0]
|
||||
find:
|
||||
switch resource {
|
||||
case "v2ray":
|
||||
for _, dir := range []string{
|
||||
"/usr/share/v2ray",
|
||||
"/usr/local/share/v2ray",
|
||||
"/opt/share/v2ray",
|
||||
} {
|
||||
file := dir + "/geosite.dat"
|
||||
if common.FileExists(file) {
|
||||
resource = file
|
||||
break find
|
||||
}
|
||||
}
|
||||
|
||||
resource = "https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat.xz"
|
||||
case "loyalsoldier":
|
||||
resource = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat"
|
||||
}
|
||||
|
||||
var data []byte
|
||||
var err error
|
||||
if strings.HasPrefix(resource, "http://") || strings.HasPrefix(resource, "https://") {
|
||||
logrus.Info("download ", resource)
|
||||
data, err = N.Get(resource)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
logrus.Info("open ", resource)
|
||||
file, err := os.Open(resource)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
data, err = ioutil.ReadAll(file)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
logrus.Fatal("read ", resource, ": ", err)
|
||||
}
|
||||
}
|
||||
{
|
||||
if strings.HasSuffix(resource, ".xz") {
|
||||
decoder, err := xz.NewReader(bytes.NewReader(data))
|
||||
if err == nil {
|
||||
data, _ = ioutil.ReadAll(decoder)
|
||||
}
|
||||
}
|
||||
}
|
||||
loaded := make(map[string][]string)
|
||||
{
|
||||
geositeList := routercommon.GeoSiteList{}
|
||||
err = proto.Unmarshal(data, &geositeList)
|
||||
if err == nil {
|
||||
for _, geoSite := range geositeList.Entry {
|
||||
domains := make([]string, 0, len(geoSite.Domain))
|
||||
for _, domain := range geoSite.Domain {
|
||||
if domain.Type == routercommon.Domain_Full {
|
||||
domains = append(domains, domain.Value)
|
||||
} else {
|
||||
domains = append(domains, domain.Value)
|
||||
domains = append(domains, "."+domain.Value)
|
||||
}
|
||||
}
|
||||
loaded[strings.ToLower(geoSite.CountryCode)] = common.Uniq(domains)
|
||||
}
|
||||
goto finish
|
||||
}
|
||||
}
|
||||
{
|
||||
loaded, _ = geosite.Read(bytes.NewReader(data))
|
||||
}
|
||||
finish:
|
||||
if len(loaded) == 0 {
|
||||
logrus.Fatal("unknown resource format")
|
||||
}
|
||||
if len(args) > 1 {
|
||||
for _, code := range args[1:] {
|
||||
code = strings.ToLower(code)
|
||||
if domains, exists := loaded[code]; exists {
|
||||
site[code] = domains
|
||||
} else {
|
||||
logrus.Fatal("code ", code, " do not exists!")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for code, domains := range loaded {
|
||||
site[code] = domains
|
||||
}
|
||||
}
|
||||
for code, domains := range site {
|
||||
site[code] = common.Uniq(domains)
|
||||
}
|
||||
geoFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
defer geoFile.Close()
|
||||
err = geosite.Write(geoFile, site)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
logrus.Info("saved ", path)
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
response, err := http.Get("https://github.com/Dreamacro/maxmind-geoip/releases/latest/download/Country.mmdb")
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
output, err := os.Create("Country.mmdb")
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
defer output.Close()
|
||||
_, err = io.Copy(output, response.Body)
|
||||
if err != nil {
|
||||
os.RemoveAll("Country.mmdb")
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -258,13 +258,18 @@ func NewLocalClient(f *flags) (*LocalClient, error) {
|
|||
return nil, E.Cause(err, "geosite.dat not found")
|
||||
}
|
||||
|
||||
geositeMatcher, err := geosite.LoadGeositeMatcher(geodata, f.Bypass)
|
||||
site, err := geosite.ReadArray(geodata, f.Bypass)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
geositeMatcher, err := geosite.NewMatcher(site)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client.Matcher = geositeMatcher
|
||||
debug.FreeOSMemory()
|
||||
}
|
||||
debug.FreeOSMemory()
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
|
|
@ -22,3 +22,21 @@ func GetBytes() []byte {
|
|||
func PutBytes(buffer []byte) {
|
||||
pool.Put(buffer)
|
||||
}
|
||||
|
||||
func Make(size int) []byte {
|
||||
var buffer []byte
|
||||
if size <= 64 {
|
||||
buffer = make([]byte, 64)
|
||||
} else if size <= 1024 {
|
||||
buffer = make([]byte, 1024)
|
||||
} else if size <= 4096 {
|
||||
buffer = make([]byte, 4096)
|
||||
} else if size <= 16384 {
|
||||
buffer = make([]byte, 16384)
|
||||
} else if size <= 65535 {
|
||||
buffer = make([]byte, 65535)
|
||||
} else {
|
||||
buffer = make([]byte, size)
|
||||
}
|
||||
return buffer[:size]
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Any[T any](array []T, block func(it T) bool) bool {
|
||||
|
@ -53,6 +54,11 @@ func Find[T any](arr []T, block func(it T) bool) T {
|
|||
return defaultValue
|
||||
}
|
||||
|
||||
func Dup[T any](obj T) T {
|
||||
p := uintptr(unsafe.Pointer(&obj))
|
||||
return *(*T)(unsafe.Pointer(p))
|
||||
}
|
||||
|
||||
func Uniq[T comparable](arr []T) []T {
|
||||
result := make([]T, 0, len(arr))
|
||||
seen := make(map[T]struct{}, len(arr))
|
||||
|
|
|
@ -42,6 +42,9 @@ func New(message ...any) error {
|
|||
}
|
||||
|
||||
func Cause(cause error, message string) Exception {
|
||||
if cause == nil {
|
||||
return nil
|
||||
}
|
||||
return exception{message, cause}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
package geosite
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/klauspost/compress/zstd"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common/trieset"
|
||||
)
|
||||
|
||||
|
@ -20,58 +12,10 @@ func (m *Matcher) Match(domain string) bool {
|
|||
return m.ds.Has(domain)
|
||||
}
|
||||
|
||||
func LoadGeositeMatcher(reader io.Reader, code string) (*Matcher, error) {
|
||||
version, err := rw.ReadByte(reader)
|
||||
func NewMatcher(domains []string) (*Matcher, error) {
|
||||
ds, err := trieset.New(domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != 0 {
|
||||
return nil, E.New("bad geosite data")
|
||||
}
|
||||
decoder, err := zstd.NewReader(reader, zstd.WithDecoderLowmem(true), zstd.WithDecoderConcurrency(1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer decoder.Close()
|
||||
bufferedReader := bufio.NewReader(decoder)
|
||||
geositeLength, err := binary.ReadUvarint(bufferedReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for geositeLength > 0 {
|
||||
geositeLength--
|
||||
countryCode, err := rw.ReadVString(bufferedReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainLength, err := binary.ReadUvarint(bufferedReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.EqualFold(code, countryCode) {
|
||||
domains := make([]string, 0, domainLength)
|
||||
for domainLength > 0 {
|
||||
domainLength--
|
||||
domain, err := rw.ReadVString(bufferedReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
ds, err := trieset.New(domains)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Matcher{ds}, nil
|
||||
} else {
|
||||
for domainLength > 0 {
|
||||
domainLength--
|
||||
_, err = rw.ReadVString(bufferedReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, E.New(code, " not found in geosite")
|
||||
return &Matcher{ds}, nil
|
||||
}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
package geosite
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGeosite(t *testing.T) {
|
||||
geosite, err := os.Open("../../geosite.dat")
|
||||
if err != nil {
|
||||
t.Skip("no geosite found")
|
||||
return
|
||||
}
|
||||
matcher, err := LoadGeositeMatcher(geosite, "cn")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !matcher.Match("baidu.cn") {
|
||||
t.Fatal("match failed")
|
||||
}
|
||||
}
|
238
common/geosite/metadata.go
Normal file
238
common/geosite/metadata.go
Normal file
|
@ -0,0 +1,238 @@
|
|||
package geosite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
|
||||
func Write(writer io.Writer, geosite map[string][]string) error {
|
||||
keys := make([]string, 0, len(geosite))
|
||||
for code := range geosite {
|
||||
keys = append(keys, code)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
content := &bytes.Buffer{}
|
||||
index := make(map[string]int)
|
||||
for _, code := range keys {
|
||||
index[code] = content.Len()
|
||||
for _, domain := range geosite[code] {
|
||||
if err := rw.WriteVString(content, domain); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := rw.WriteByte(writer, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = rw.WriteUVariant(writer, uint64(len(keys)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, code := range keys {
|
||||
err = rw.WriteVString(writer, code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rw.WriteUVariant(writer, uint64(index[code]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rw.WriteUVariant(writer, uint64(len(geosite[code])))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = writer.Write(content.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Read(reader io.Reader, codes ...string) (map[string][]string, error) {
|
||||
version, err := rw.ReadByte(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != 0 {
|
||||
return nil, E.New("bad version")
|
||||
}
|
||||
length, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys := make([]string, length)
|
||||
domainIndex := make(map[string]int)
|
||||
domainLength := make(map[string]int)
|
||||
for i := 0; i < int(length); i++ {
|
||||
code, err := rw.ReadVString(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys[i] = code
|
||||
codeIndex, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
codeLength, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainIndex[code] = int(codeIndex)
|
||||
domainLength[code] = int(codeLength)
|
||||
}
|
||||
site := make(map[string][]string)
|
||||
for _, code := range keys {
|
||||
if len(codes) == 0 || common.Contains(codes, code) {
|
||||
domains := make([]string, domainLength[code])
|
||||
for i := range domains {
|
||||
domains[i], err = rw.ReadVString(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
site[code] = domains
|
||||
} else {
|
||||
dLength := domainLength[code]
|
||||
for i := 0; i < dLength; i++ {
|
||||
_, err = rw.ReadVString(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return site, nil
|
||||
}
|
||||
|
||||
func ReadSeek(reader io.ReadSeeker, codes ...string) (map[string][]string, error) {
|
||||
version, err := rw.ReadByte(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != 0 {
|
||||
return nil, E.New("bad version")
|
||||
}
|
||||
length, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys := make([]string, length)
|
||||
domainIndex := make(map[string]int)
|
||||
domainLength := make(map[string]int)
|
||||
for i := 0; i < int(length); i++ {
|
||||
code, err := rw.ReadVString(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys[i] = code
|
||||
codeIndex, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
codeLength, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainIndex[code] = int(codeIndex)
|
||||
domainLength[code] = int(codeLength)
|
||||
}
|
||||
if len(codes) == 0 {
|
||||
codes = keys
|
||||
}
|
||||
site := make(map[string][]string)
|
||||
counter := &rw.ReadCounter{Reader: reader}
|
||||
for _, code := range codes {
|
||||
domains := make([]string, domainLength[code])
|
||||
if _, exists := domainIndex[code]; !exists {
|
||||
return nil, E.New("code ", code, " not exists!")
|
||||
}
|
||||
_, err = reader.Seek(int64(domainIndex[code])-counter.Count(), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range domains {
|
||||
domains[i], err = rw.ReadVString(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
site[code] = domains
|
||||
}
|
||||
|
||||
return site, nil
|
||||
}
|
||||
|
||||
func ReadArray(reader io.ReadSeeker, codes ...string) ([]string, error) {
|
||||
version, err := rw.ReadByte(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != 0 {
|
||||
return nil, E.New("bad version")
|
||||
}
|
||||
length, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys := make([]string, length)
|
||||
domainIndex := make(map[string]int)
|
||||
domainLength := make(map[string]int)
|
||||
for i := 0; i < int(length); i++ {
|
||||
code, err := rw.ReadVString(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys[i] = code
|
||||
codeIndex, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
codeLength, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainIndex[code] = int(codeIndex)
|
||||
domainLength[code] = int(codeLength)
|
||||
}
|
||||
if len(codes) == 0 {
|
||||
codes = keys
|
||||
}
|
||||
var domainL int
|
||||
for _, code := range keys {
|
||||
if _, exists := domainIndex[code]; !exists {
|
||||
return nil, E.New("code ", code, " not exists!")
|
||||
}
|
||||
domainL += domainLength[code]
|
||||
}
|
||||
domains := make([]string, 0, domainL)
|
||||
counter := &rw.ReadCounter{Reader: reader}
|
||||
for _, code := range codes {
|
||||
_, err := reader.Seek(int64(domainIndex[code])-counter.Count(), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
codeL := domainLength[code]
|
||||
for i := 0; i < codeL; i++ {
|
||||
domain, err := rw.ReadVString(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
84
common/geosite/reader.go
Normal file
84
common/geosite/reader.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package geosite
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
reader io.ReadSeeker
|
||||
counter *rw.ReadCounter
|
||||
domainIndex map[string]int
|
||||
domainLength map[string]int
|
||||
cache map[string][]string
|
||||
}
|
||||
|
||||
func NewReader(reader io.ReadSeeker) (*Reader, error) {
|
||||
r := &Reader{
|
||||
reader: reader,
|
||||
counter: &rw.ReadCounter{Reader: reader},
|
||||
domainIndex: map[string]int{},
|
||||
domainLength: map[string]int{},
|
||||
cache: map[string][]string{},
|
||||
}
|
||||
|
||||
version, err := rw.ReadByte(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != 0 {
|
||||
return nil, E.New("bad version")
|
||||
}
|
||||
length, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < int(length); i++ {
|
||||
code, err := rw.ReadVString(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainIndex, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domainLength, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.domainIndex[code] = int(domainIndex)
|
||||
r.domainLength[code] = int(domainLength)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *Reader) Load(code string) ([]string, error) {
|
||||
code = strings.ToLower(code)
|
||||
if cache, ok := r.cache[code]; ok {
|
||||
return cache, nil
|
||||
}
|
||||
index, exists := r.domainIndex[code]
|
||||
if !exists {
|
||||
return nil, E.New("code ", code, " not exists!")
|
||||
}
|
||||
_, err := r.reader.Seek(int64(index)-r.counter.Count(), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.counter.Reset()
|
||||
dLength := r.domainLength[code]
|
||||
domains := make([]string, dLength)
|
||||
for i := 0; i < dLength; i++ {
|
||||
domains[i], err = rw.ReadVString(r.counter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
r.cache[code] = domains
|
||||
return domains, nil
|
||||
}
|
7
common/lowmem/debug.go
Normal file
7
common/lowmem/debug.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
//go:build debug
|
||||
|
||||
package lowmem
|
||||
|
||||
func init() {
|
||||
Enabled = true
|
||||
}
|
13
common/lowmem/free.go
Normal file
13
common/lowmem/free.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package lowmem
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var Enabled = false
|
||||
|
||||
func Free() {
|
||||
if Enabled {
|
||||
runtime.GC()
|
||||
}
|
||||
}
|
12
common/network/dialer.go
Normal file
12
common/network/dialer.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
type ContextDialer interface {
|
||||
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
var SystemDialer ContextDialer = &net.Dialer{}
|
|
@ -18,6 +18,15 @@ func HTTPClientWithNetwork(network string) *http.Client {
|
|||
}
|
||||
}
|
||||
|
||||
func Get(url string) ([]byte, error) {
|
||||
response, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
return ioutil.ReadAll(response.Body)
|
||||
}
|
||||
|
||||
func GetContext(ctx context.Context, client *http.Client, url string) ([]byte, error) {
|
||||
request, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
|
|
|
@ -41,7 +41,7 @@ func ReadFromVar(writerVar *io.Writer, reader io.Reader) (int64, error) {
|
|||
}
|
||||
|
||||
func CopyConn(ctx context.Context, conn net.Conn, dest net.Conn) error {
|
||||
return task.Run(context.Background(), func() error {
|
||||
err := task.Run(ctx, func() error {
|
||||
defer CloseRead(conn)
|
||||
defer CloseWrite(dest)
|
||||
return common.Error(io.Copy(dest, conn))
|
||||
|
@ -50,6 +50,9 @@ func CopyConn(ctx context.Context, conn net.Conn, dest net.Conn) error {
|
|||
defer CloseWrite(conn)
|
||||
return common.Error(io.Copy(conn, dest))
|
||||
})
|
||||
conn.Close()
|
||||
dest.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
func CopyPacketConn(ctx context.Context, conn net.PacketConn, outPacketConn net.PacketConn) error {
|
||||
|
|
27
common/rw/count.go
Normal file
27
common/rw/count.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package rw
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type ReadCounter struct {
|
||||
io.Reader
|
||||
count int64
|
||||
}
|
||||
|
||||
func (r *ReadCounter) Read(p []byte) (n int, err error) {
|
||||
n, err = r.Reader.Read(p)
|
||||
if n > 0 {
|
||||
atomic.AddInt64(&r.count, int64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *ReadCounter) Count() int64 {
|
||||
return r.count
|
||||
}
|
||||
|
||||
func (r *ReadCounter) Reset() {
|
||||
atomic.StoreInt64(&r.count, 0)
|
||||
}
|
|
@ -14,12 +14,12 @@ func CloseRead(conn io.Closer) error {
|
|||
if closer, ok := conn.(ReadCloser); ok {
|
||||
return closer.CloseRead()
|
||||
}
|
||||
return conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func CloseWrite(conn io.Closer) error {
|
||||
if closer, ok := conn.(WriteCloser); ok {
|
||||
return closer.CloseWrite()
|
||||
}
|
||||
return conn.Close()
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,14 +7,23 @@ import (
|
|||
"github.com/sagernet/sing/common"
|
||||
)
|
||||
|
||||
type InputStream interface {
|
||||
type stubByteReader struct {
|
||||
io.Reader
|
||||
io.ByteReader
|
||||
}
|
||||
|
||||
type OutputStream interface {
|
||||
io.Writer
|
||||
io.ByteWriter
|
||||
func (r stubByteReader) ReadByte() (byte, error) {
|
||||
return ReadByte(r)
|
||||
}
|
||||
|
||||
func ToByteReader(reader io.Reader) io.ByteReader {
|
||||
if byteReader, ok := reader.(io.ByteReader); ok {
|
||||
return byteReader
|
||||
}
|
||||
return &stubByteReader{reader}
|
||||
}
|
||||
|
||||
func ReadUVariant(reader io.Reader) (uint64, error) {
|
||||
return binary.ReadUvarint(ToByteReader(reader))
|
||||
}
|
||||
|
||||
func WriteUVariant(writer io.Writer, value uint64) error {
|
||||
|
@ -30,8 +39,8 @@ func WriteVString(writer io.Writer, value string) error {
|
|||
return WriteString(writer, value)
|
||||
}
|
||||
|
||||
func ReadVString(reader InputStream) (string, error) {
|
||||
length, err := binary.ReadUvarint(reader)
|
||||
func ReadVString(reader io.Reader) (string, error) {
|
||||
length, err := binary.ReadUvarint(ToByteReader(reader))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
12
debug.go
Normal file
12
debug.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
//go:build debug
|
||||
|
||||
package sing
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
)
|
||||
|
||||
func init() {
|
||||
go http.ListenAndServe(":8964", nil)
|
||||
}
|
3
go.mod
3
go.mod
|
@ -3,8 +3,6 @@ module github.com/sagernet/sing
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41
|
||||
github.com/klauspost/compress v1.15.1
|
||||
github.com/openacid/low v0.1.21
|
||||
github.com/oschwald/geoip2-golang v1.7.0
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c
|
||||
|
@ -34,7 +32,6 @@ require (
|
|||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
|
57
go.sum
57
go.sum
|
@ -6,44 +6,20 @@ 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/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165 h1:BS21ZUJ/B5X2UVUbczfmdWH7GapPWAhxcMsDnjJTU1E=
|
||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41 h1:Yg3n3AI7GoHnWt7dyjsLPU+TEuZfPAg0OdiA3MJUV6I=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A=
|
||||
github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/openacid/errors v0.8.1/go.mod h1:GUQEJJOJE3W9skHm8E8Y4phdl2LLEN8iD7c5gcGgdx0=
|
||||
|
@ -65,13 +41,10 @@ github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c h1:pqy40B3M
|
|||
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||
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 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
|
@ -79,49 +52,19 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
github.com/v2fly/v2ray-core/v5 v5.0.3 h1:2rnJ9vZbBQ7V4upWsoUVYGoqZl4grrx8SxOReKx+jjc=
|
||||
github.com/v2fly/v2ray-core/v5 v5.0.3/go.mod h1:zhDdsUJcNE8LcLRA3l7fEQ6QLuveD4/OLbQM2CceSHM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921 h1:iU7T1X1J6yxDr0rda54sWGkHgOp5XJrqm79gcNlC2VM=
|
||||
golang.org/x/crypto v0.0.0-20220408190544-5352b0902921/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3 h1:EN5+DfgmRMvRUrMGERW2gQl3Vc+Z7ZMnI/xdEpPSf0c=
|
||||
golang.org/x/net v0.0.0-20220407224826-aac1ed45d8e3/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw=
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d h1:q4JksJ2n0fmbXC0Aj0eOs6E0AcPqnKglxWXWFqGD6x0=
|
||||
|
|
|
@ -131,7 +131,7 @@ func (r *Reader) Read(b []byte) (n int, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
type AEADWriter struct {
|
||||
type Writer struct {
|
||||
upstream io.Writer
|
||||
cipher cipher.AEAD
|
||||
data []byte
|
||||
|
@ -139,8 +139,8 @@ type AEADWriter struct {
|
|||
maxPacketSize int
|
||||
}
|
||||
|
||||
func NewWriter(upstream io.Writer, cipher cipher.AEAD, maxPacketSize int) *AEADWriter {
|
||||
return &AEADWriter{
|
||||
func NewWriter(upstream io.Writer, cipher cipher.AEAD, maxPacketSize int) *Writer {
|
||||
return &Writer{
|
||||
upstream: upstream,
|
||||
cipher: cipher,
|
||||
data: make([]byte, maxPacketSize+PacketLengthBufferSize+cipher.Overhead()*2),
|
||||
|
@ -149,19 +149,19 @@ func NewWriter(upstream io.Writer, cipher cipher.AEAD, maxPacketSize int) *AEADW
|
|||
}
|
||||
}
|
||||
|
||||
func (w *AEADWriter) Upstream() io.Writer {
|
||||
func (w *Writer) Upstream() io.Writer {
|
||||
return w.upstream
|
||||
}
|
||||
|
||||
func (w *AEADWriter) Replaceable() bool {
|
||||
func (w *Writer) Replaceable() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *AEADWriter) SetWriter(writer io.Writer) {
|
||||
func (w *Writer) SetWriter(writer io.Writer) {
|
||||
w.upstream = writer
|
||||
}
|
||||
|
||||
func (w *AEADWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
for {
|
||||
offset := w.cipher.Overhead() + PacketLengthBufferSize
|
||||
readN, readErr := r.Read(w.data[offset : offset+w.maxPacketSize])
|
||||
|
@ -185,7 +185,7 @@ func (w *AEADWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (w *AEADWriter) Write(p []byte) (n int, err error) {
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -110,6 +110,32 @@ func (m *Method) KeyLength() int {
|
|||
return m.keySaltLength
|
||||
}
|
||||
|
||||
func (m *Method) ReadRequest(upstream io.Reader) (io.Reader, error) {
|
||||
saltBuffer := buf.Make(m.keySaltLength)
|
||||
salt := common.Dup(saltBuffer)
|
||||
_, err := io.ReadFull(upstream, salt)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read salt")
|
||||
}
|
||||
if m.replayFilter != nil {
|
||||
if !m.replayFilter.Check(salt) {
|
||||
return nil, E.New("salt not unique")
|
||||
}
|
||||
}
|
||||
return NewReader(upstream, m.constructor(Kdf(m.key, salt, m.keySaltLength)), MaxPacketSize), nil
|
||||
}
|
||||
|
||||
func (m *Method) WriteResponse(upstream io.Writer) (io.Writer, error) {
|
||||
saltBuffer := buf.Make(m.keySaltLength)
|
||||
salt := common.Dup(saltBuffer)
|
||||
common.Must1(io.ReadFull(m.secureRNG, salt))
|
||||
_, err := upstream.Write(salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewWriter(upstream, m.constructor(Kdf(m.key, salt, m.keySaltLength)), MaxPacketSize), nil
|
||||
}
|
||||
|
||||
func (m *Method) DialConn(conn net.Conn, destination *M.AddrPort) (net.Conn, error) {
|
||||
shadowsocksConn := &clientConn{
|
||||
Conn: conn,
|
||||
|
@ -131,19 +157,19 @@ func (m *Method) DialPacketConn(conn net.Conn) socks.PacketConn {
|
|||
return &aeadPacketConn{conn, m}
|
||||
}
|
||||
|
||||
func (m *Method) EncodePacket(key []byte, buffer *buf.Buffer) error {
|
||||
cipher := m.constructor(Kdf(key, buffer.To(m.keySaltLength), m.keySaltLength))
|
||||
cipher.Seal(buffer.From(m.keySaltLength)[:0], rw.ZeroBytes[:cipher.NonceSize()], buffer.From(m.keySaltLength), nil)
|
||||
buffer.Extend(cipher.Overhead())
|
||||
func (m *Method) EncodePacket(buffer *buf.Buffer) error {
|
||||
c := m.constructor(Kdf(m.key, buffer.To(m.keySaltLength), m.keySaltLength))
|
||||
c.Seal(buffer.From(m.keySaltLength)[:0], rw.ZeroBytes[:c.NonceSize()], buffer.From(m.keySaltLength), nil)
|
||||
buffer.Extend(c.Overhead())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Method) DecodePacket(key []byte, buffer *buf.Buffer) error {
|
||||
func (m *Method) DecodePacket(buffer *buf.Buffer) error {
|
||||
if buffer.Len() < m.keySaltLength {
|
||||
return E.New("bad packet")
|
||||
}
|
||||
aead := m.constructor(Kdf(key, buffer.To(m.keySaltLength), m.keySaltLength))
|
||||
packet, err := aead.Open(buffer.Index(m.keySaltLength), rw.ZeroBytes[:aead.NonceSize()], buffer.From(m.keySaltLength), nil)
|
||||
c := m.constructor(Kdf(m.key, buffer.To(m.keySaltLength), m.keySaltLength))
|
||||
packet, err := c.Open(buffer.Index(m.keySaltLength), rw.ZeroBytes[:c.NonceSize()], buffer.From(m.keySaltLength), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -222,7 +248,7 @@ func (c *clientConn) readResponse() error {
|
|||
}
|
||||
if c.method.replayFilter != nil {
|
||||
if !c.method.replayFilter.Check(salt) {
|
||||
return E.New("salt is not unique")
|
||||
return E.New("salt not unique")
|
||||
}
|
||||
}
|
||||
c.reader = NewReader(
|
||||
|
@ -288,7 +314,7 @@ func (c *aeadPacketConn) WritePacket(buffer *buf.Buffer, destination *M.AddrPort
|
|||
return err
|
||||
}
|
||||
buffer = buffer.WriteBufferAtFirst(header)
|
||||
err = c.method.EncodePacket(c.method.key, buffer)
|
||||
err = c.method.EncodePacket(buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -301,7 +327,7 @@ func (c *aeadPacketConn) ReadPacket(buffer *buf.Buffer) (*M.AddrPort, error) {
|
|||
return nil, err
|
||||
}
|
||||
buffer.Truncate(n)
|
||||
err = c.method.DecodePacket(c.method.key, buffer)
|
||||
err = c.method.DecodePacket(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -256,7 +256,7 @@ func (c *clientConn) readResponse() error {
|
|||
}
|
||||
|
||||
if !c.method.replayFilter.Check(salt) {
|
||||
return E.New("salt is not unique")
|
||||
return E.New("salt not unique")
|
||||
}
|
||||
|
||||
reader := shadowaead.NewReader(
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/lowmem"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/redir"
|
||||
)
|
||||
|
@ -105,6 +106,7 @@ func (l *Listener) loop() {
|
|||
if hErr != nil {
|
||||
l.handler.HandleError(&Error{Conn: tcpConn, Cause: hErr})
|
||||
}
|
||||
lowmem.Free()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue