Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add anchor, a protocol to share proxy in Lan #191

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/java/io/nekohasekai/sagernet/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ object Key {

const val MIXED_PORT = "mixedPort"
const val ALLOW_ACCESS = "allowAccess"
const val DISCOVERY_IN_LAN = "discoveryInLan"
const val SPEED_INTERVAL = "speedInterval"
const val SHOW_DIRECT_SPEED = "showDirectSpeed"
const val LOCAL_DNS_PORT = "portLocalDns"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,8 @@ class NativeInterface : PlatformInterface {
return SDK_INT >= Build.VERSION_CODES.R // SDK 30 (Android 11)
}

override fun allowDiscoveryByLan(): Boolean {
return DataStore.discoveryInLan
}
xchacha20-poly1305 marked this conversation as resolved.
Show resolved Hide resolved

}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
var inboundPassword by configurationStore.string(Key.INBOUND_PASSWORD) { "" }

var allowAccess by configurationStore.boolean(Key.ALLOW_ACCESS)
var discoveryInLan by configurationStore.boolean(Key.DISCOVERY_IN_LAN)
var speedInterval by configurationStore.stringToInt(Key.SPEED_INTERVAL)
var showGroupInNotification by configurationStore.boolean("showGroupInNotification")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
lateinit var bypassLan: SwitchPreference
lateinit var bypassLanInCore: SwitchPreference

lateinit var allowAccess: SwitchPreference
lateinit var discoveryInLan: SwitchPreference

lateinit var logLevel: LongClickListPreference
lateinit var alwaysShowAddress: SwitchPreference
lateinit var blurredAddress: SwitchPreference
Expand Down Expand Up @@ -177,6 +180,8 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
Key.INBOUND_SETTINGS -> preferenceCategory.forEach { preference ->
when (preference.key) {
Key.MIXED_PORT, Key.LOCAL_DNS_PORT -> (preference as EditTextPreference).setPortEdit()
Key.ALLOW_ACCESS -> allowAccess = preference as SwitchPreference
Key.DISCOVERY_IN_LAN -> discoveryInLan = preference as SwitchPreference
else -> preference.onPreferenceChangeListener = reloadListener
}
}
Expand Down Expand Up @@ -275,6 +280,14 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
true
}

discoveryInLan.isEnabled = bypassLan.isChecked
discoveryInLan.onPreferenceChangeListener = reloadListener
allowAccess.setOnPreferenceChangeListener { _, newValue ->
discoveryInLan.isEnabled = newValue as Boolean
needReload()
true
}
xchacha20-poly1305 marked this conversation as resolved.
Show resolved Hide resolved

blurredAddress.isEnabled = alwaysShowAddress.isChecked
alwaysShowAddress.setOnPreferenceChangeListener { _, newValue ->
blurredAddress.isEnabled = newValue as Boolean
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -588,4 +588,5 @@
<string name="override_port">覆写端口</string>
<string name="allow_apps_bypass_vpn">允许应用绕过 VPN</string>
<string name="clash_mode">Clash 模式</string>
<string name="enable_local_network_discovery">允许局域网发现</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -655,4 +655,5 @@
<string name="override_port">Override Port</string>
<string name="allow_apps_bypass_vpn">Allow Apps to bypass VPN</string>
<string name="clash_mode">Clash Mode</string>
<string name="enable_local_network_discovery">Enable Local Network Discovery</string>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/xml/global_preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@
app:key="allowAccess"
app:summary="@string/allow_access_sum"
app:title="@string/allow_access" />
<SwitchPreference
app:icon="@drawable/ic_baseline_location_on_24"
app:key="discoveryInLan"
app:title="@string/enable_local_network_discovery" />
xchacha20-poly1305 marked this conversation as resolved.
Show resolved Hide resolved
<EditTextPreference
app:icon="@drawable/ic_baseline_person_24"
app:key="inboundUsername"
Expand Down
12 changes: 12 additions & 0 deletions libcore/anchor/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package anchor

import (
"github.com/sagernet/sing/common/auth"
)

type Options struct {
SocksPort uint16
User auth.User
DnsPort uint16
DeviceName string
}
120 changes: 120 additions & 0 deletions libcore/anchor/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Package anchor provides anchor API server.
package anchor

import (
"context"
"net"
"os"

"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"

"github.com/xchacha20-poly1305/anchor"
)

var (
_ adapter.Service = (*Anchor)(nil)
_ E.Handler = (*Anchor)(nil)
)

type Anchor struct {
ctx context.Context
logger logger.ContextLogger
packetConn net.PacketConn
response []byte
done chan struct{}
}

func New(ctx context.Context, ctxLogger logger.ContextLogger, options Options) (*Anchor, error) {
packetConn, err := net.ListenUDP(N.NetworkUDP+"4", &net.UDPAddr{
IP: net.IPv4zero, // Not listen IPv6
Port: anchor.Port,
})
if err != nil {
return nil, E.Cause(err, "listen anchor API")
}
if ctxLogger == nil {
ctxLogger = logger.NOP()
}
response, _ := (&anchor.Response{
Version: anchor.Version,
DnsPort: options.DnsPort,
DeviceName: options.DeviceName,
SocksPort: options.SocksPort,
User: options.User,
}).MarshalBinary()
xchacha20-poly1305 marked this conversation as resolved.
Show resolved Hide resolved
return &Anchor{
ctx: ctx,
logger: ctxLogger,
response: response,
packetConn: packetConn,
done: make(chan struct{}),
}, nil
}

func (a *Anchor) Start() error {
go a.loop()
return nil
}

func (a *Anchor) loop() {
a.logger.InfoContext(a.ctx, "Anchor: started loop")
stop := context.AfterFunc(a.ctx, func() {
_ = a.Close()
})
for {
select {
case <-a.ctx.Done():
return
case <-a.done:
return
default:
}
buffer := buf.NewSize(anchor.MaxQuerySize)
_, source, err := buffer.ReadPacketFrom(a.packetConn)
if err != nil {
stop()
buffer.Release()
a.NewError(a.ctx, err)
return
}
go a.handle(source, buffer)
xchacha20-poly1305 marked this conversation as resolved.
Show resolved Hide resolved
}
}

func (a *Anchor) handle(source net.Addr, buffer *buf.Buffer) {
defer buffer.Release()
a.logger.TraceContext(a.ctx, "Anchor: new query from: ", source)
query, err := anchor.ParseQuery(buffer.Bytes())
if err != nil {
a.NewError(a.ctx, err)
return
}
a.logger.TraceContext(a.ctx, "Anchor: device name: ", query.DeviceName)
_, err = a.packetConn.WriteTo(a.response, source)
if err != nil {
a.NewError(a.ctx, err)
return
}
}
xchacha20-poly1305 marked this conversation as resolved.
Show resolved Hide resolved

func (a *Anchor) NewError(ctx context.Context, err error) {
if E.IsClosedOrCanceled(err) {
return
}
a.logger.InfoContext(ctx, "Anchor: ", err)
}

func (a *Anchor) Close() error {
select {
case <-a.done:
return os.ErrClosed
default:
close(a.done)
}
return common.Close(a.packetConn)
}
31 changes: 31 additions & 0 deletions libcore/box.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package libcore

import (
"context"
"net/netip"
"time"

box "github.com/sagernet/sing-box"
Expand All @@ -19,6 +20,7 @@ import (
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"

"libcore/anchor"
"libcore/protect"
"libcore/v2rayapilite"
)
Expand Down Expand Up @@ -110,6 +112,35 @@ func NewBoxInstance(config string, platformInterface PlatformInterface) (b *BoxI
}
}

if platformInterface.AllowDiscoveryByLan() {
var anchorOptions anchor.Options
Find:
for _, inbound := range options.Inbounds {
switch inbound.Type {
case C.TypeMixed:
listen := (*netip.Addr)(inbound.MixedOptions.Listen)
if !listen.IsUnspecified() {
xchacha20-poly1305 marked this conversation as resolved.
Show resolved Hide resolved
break Find
}
anchorOptions.SocksPort = inbound.MixedOptions.ListenPort
if len(inbound.MixedOptions.Users) > 0 {
anchorOptions.User = inbound.MixedOptions.Users[0]
}
case C.TypeDirect:
if inbound.Tag != "dns-in" {
continue
}
anchorOptions.DnsPort = inbound.DirectOptions.OverridePort
}
}
anchorServer, err := anchor.New(ctx, log.StdLogger(), anchorOptions)
if err != nil {
log.WarnContext(ctx, "create anchor service: ", err)
} else {
b.services = append(b.services, anchorServer)
}
xchacha20-poly1305 marked this conversation as resolved.
Show resolved Hide resolved
}
xchacha20-poly1305 marked this conversation as resolved.
Show resolved Hide resolved

// Protect
protectServer, err := protect.New(ctx, log.StdLogger(), ProtectPath, func(fd int) error {
return platformInterface.AutoDetectInterfaceControl(int32(fd))
Expand Down
1 change: 1 addition & 0 deletions libcore/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/sagernet/sing-dns v0.3.0-beta.14
github.com/sagernet/sing-tun v0.4.0-beta.16
github.com/xchacha20-poly1305/TLS-scribe v0.6.1
github.com/xchacha20-poly1305/anchor v0.2.0-beta.0
github.com/xchacha20-poly1305/cazilla v0.3.3
github.com/xchacha20-poly1305/libping v0.7.1
golang.org/x/crypto v0.27.0
Expand Down
2 changes: 2 additions & 0 deletions libcore/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1Y
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/xchacha20-poly1305/TLS-scribe v0.6.1 h1:eMe562VUOSjoumbom/XS1O5mw0A3/QOKPyLgwAjVDgo=
github.com/xchacha20-poly1305/TLS-scribe v0.6.1/go.mod h1:2suBWLi3ESUbkp97tMz1okYK6Dh9XpjRzyO7eMA5s6Q=
github.com/xchacha20-poly1305/anchor v0.2.0-beta.0 h1:qic4oHg2TNeOaRSyDIPkeEDqAXzgITjiR01FIFhqm18=
github.com/xchacha20-poly1305/anchor v0.2.0-beta.0/go.mod h1:/QUPDmgmJ0q7m5vuPte9Bve9vXLS/SNophSntKuCrX4=
github.com/xchacha20-poly1305/cazilla v0.3.3 h1:9qKZHKexlzGHmXeAHVOwj0dnc9/WEftGSwwWC3703wM=
github.com/xchacha20-poly1305/cazilla v0.3.3/go.mod h1:CRNqqSLuWw8gStu7qDwjPYmsLE91OCVWazN9AH4LyWc=
github.com/xchacha20-poly1305/libping v0.7.1 h1:MQkq6gxn67SZlBgG2Wp7ek0QYQsxKnKSbg0h235S6+o=
Expand Down
1 change: 1 addition & 0 deletions libcore/platform_java.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type PlatformInterface interface {
GetInterfaces() (NetworkInterfaceIterator, error)
UsePlatformInterfaceGetter() bool

AllowDiscoveryByLan() bool
xchacha20-poly1305 marked this conversation as resolved.
Show resolved Hide resolved
SelectorCallback(tag string)
ClashModeCallback(mode string)
}
Expand Down