Feature: support multiport condition for rule SRC-PORT and DST-PORT
This commit is contained in:
parent
e2c7b19000
commit
56dff65149
6 changed files with 118 additions and 28 deletions
20
README.md
20
README.md
|
@ -31,7 +31,7 @@
|
||||||
## Getting Started
|
## Getting Started
|
||||||
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
|
||||||
|
|
||||||
## Advanced usage for this fork repository
|
## Advanced usage for this fork branch
|
||||||
### TUN configuration
|
### TUN configuration
|
||||||
Support macOS Linux and Windows.
|
Support macOS Linux and Windows.
|
||||||
|
|
||||||
|
@ -46,25 +46,33 @@ tun:
|
||||||
```
|
```
|
||||||
### Rules configuration
|
### Rules configuration
|
||||||
- Support rule `GEOSITE`
|
- Support rule `GEOSITE`
|
||||||
- Support rule `GEOIP` not match condition
|
- Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`
|
||||||
|
- Support not match condition for rule `GEOIP`
|
||||||
- Support `network` condition for all rules
|
- Support `network` condition for all rules
|
||||||
|
|
||||||
The `GEOSITE` and `GEOIP` databases via https://github.com/Loyalsoldier/v2ray-rules-dat
|
The `GEOSITE` and `GEOIP` databases via https://github.com/Loyalsoldier/v2ray-rules-dat
|
||||||
```yaml
|
```yaml
|
||||||
rules:
|
rules:
|
||||||
# network condition for rules
|
# network condition for rules
|
||||||
- DOMAIN-SUFFIX,tabao.com,DIRECT,tcp
|
- DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp
|
||||||
- DST-PORT,123,DIRECT,udp
|
- DOMAIN-SUFFIX,bilibili.com,REJECT,udp
|
||||||
|
|
||||||
|
# multiport condition for rule SRC-PORT and DST-PORT
|
||||||
|
- DST-PORT,123/136/137-139,DIRECT,udp
|
||||||
|
|
||||||
# rule GEOSITE
|
# rule GEOSITE
|
||||||
- GEOSITE,category-ads-all,REJECT
|
- GEOSITE,category-ads-all,REJECT
|
||||||
- GEOSITE,icloud@cn,DIRECT
|
- GEOSITE,icloud@cn,DIRECT
|
||||||
- GEOSITE,apple@cn,DIRECT
|
- GEOSITE,apple@cn,DIRECT
|
||||||
- GEOSITE,microsoft@cn,DIRECT
|
- GEOSITE,microsoft@cn,DIRECT
|
||||||
|
- GEOSITE,facebook,PROXY
|
||||||
- GEOSITE,youtube,PROXY
|
- GEOSITE,youtube,PROXY
|
||||||
- GEOSITE,geolocation-cn,DIRECT
|
- GEOSITE,geolocation-cn,DIRECT
|
||||||
|
- GEOSITE,gfw,PROXY
|
||||||
|
- GEOSITE,greatfire,PROXY
|
||||||
#- GEOSITE,geolocation-!cn,PROXY
|
#- GEOSITE,geolocation-!cn,PROXY
|
||||||
|
|
||||||
|
- GEOIP,telegram,PROXY,no-resolve
|
||||||
- GEOIP,private,DIRECT,no-resolve
|
- GEOIP,private,DIRECT,no-resolve
|
||||||
- GEOIP,cn,DIRECT
|
- GEOIP,cn,DIRECT
|
||||||
|
|
||||||
|
@ -76,7 +84,7 @@ rules:
|
||||||
### IPTABLES auto-configuration
|
### IPTABLES auto-configuration
|
||||||
Only work on Linux OS who support `iptables`, Clash will auto-configuration iptables for tproxy listener when `tproxy-port` value isn't zero.
|
Only work on Linux OS who support `iptables`, Clash will auto-configuration iptables for tproxy listener when `tproxy-port` value isn't zero.
|
||||||
|
|
||||||
When `TPROXY` is enabled, the `TUN` must be disabled.
|
If `TPROXY` is enabled, the `TUN` must be disabled.
|
||||||
```yaml
|
```yaml
|
||||||
# Enable the TPROXY listener
|
# Enable the TPROXY listener
|
||||||
tproxy-port: 9898
|
tproxy-port: 9898
|
||||||
|
@ -84,7 +92,7 @@ tproxy-port: 9898
|
||||||
tun:
|
tun:
|
||||||
enable: false
|
enable: false
|
||||||
```
|
```
|
||||||
Create user give name `clash`, run `$ sudo useradd -M clash` in command line.
|
Create user given name `clash`
|
||||||
|
|
||||||
Run Clash by user `clash` as a daemon.
|
Run Clash by user `clash` as a daemon.
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter"
|
"github.com/Dreamacro/clash/adapter"
|
||||||
|
@ -433,6 +434,8 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, error) {
|
||||||
rules = append(rules, parsed)
|
rules = append(rules, parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runtime.GC()
|
||||||
|
|
||||||
return rules, nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
_ "github.com/Dreamacro/clash/rule/geodata/standard"
|
_ "github.com/Dreamacro/clash/rule/geodata/standard"
|
||||||
)
|
)
|
||||||
|
|
||||||
var geoIPMatcher *router.GeoIPMatcher
|
var multiGeoIPMatcher *router.MultiGeoIPMatcher
|
||||||
|
|
||||||
type fallbackIPFilter interface {
|
type fallbackIPFilter interface {
|
||||||
Match(net.IP) bool
|
Match(net.IP) bool
|
||||||
|
@ -19,35 +19,49 @@ type fallbackIPFilter interface {
|
||||||
type geoipFilter struct{}
|
type geoipFilter struct{}
|
||||||
|
|
||||||
func (gf *geoipFilter) Match(ip net.IP) bool {
|
func (gf *geoipFilter) Match(ip net.IP) bool {
|
||||||
if geoIPMatcher == nil {
|
if multiGeoIPMatcher == nil {
|
||||||
countryCode := "cn"
|
countryCodeCN := "cn"
|
||||||
|
countryCodePrivate := "private"
|
||||||
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("[GeoIPFilter] GetGeoDataLoader error: %s", err.Error())
|
log.Errorln("[GeoIPFilter] GetGeoDataLoader error: %s", err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
records, err := geoLoader.LoadGeoIP(countryCode)
|
recordsCN, err := geoLoader.LoadGeoIP(countryCodeCN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("[GeoIPFilter] LoadGeoIP error: %s", err.Error())
|
log.Errorln("[GeoIPFilter] LoadGeoIP error: %s", err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
geoIP := &router.GeoIP{
|
recordsPrivate, err := geoLoader.LoadGeoIP(countryCodePrivate)
|
||||||
CountryCode: countryCode,
|
if err != nil {
|
||||||
Cidr: records,
|
log.Errorln("[GeoIPFilter] LoadGeoIP error: %s", err.Error())
|
||||||
ReverseMatch: false,
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
geoIPMatcher, err = router.NewGeoIPMatcher(geoIP)
|
geoips := []*router.GeoIP{
|
||||||
|
{
|
||||||
|
CountryCode: countryCodeCN,
|
||||||
|
Cidr: recordsCN,
|
||||||
|
ReverseMatch: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CountryCode: countryCodePrivate,
|
||||||
|
Cidr: recordsPrivate,
|
||||||
|
ReverseMatch: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
multiGeoIPMatcher, err = router.NewMultiGeoIPMatcher(geoips)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("[GeoIPFilter] NewGeoIPMatcher error: %s", err.Error())
|
log.Errorln("[GeoIPFilter] NewMultiGeoIPMatcher error: %s", err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !geoIPMatcher.Match(ip)
|
return !multiGeoIPMatcher.ApplyIp(ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ipnetFilter struct {
|
type ipnetFilter struct {
|
||||||
|
@ -61,6 +75,7 @@ func (inf *ipnetFilter) Match(ip net.IP) bool {
|
||||||
type fallbackDomainFilter interface {
|
type fallbackDomainFilter interface {
|
||||||
Match(domain string) bool
|
Match(domain string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type domainFilter struct {
|
type domainFilter struct {
|
||||||
tree *trie.DomainTrie
|
tree *trie.DomainTrie
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ type TunDevice interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetLinuxAutoRoute() {
|
func SetLinuxAutoRoute() {
|
||||||
log.Infoln("Tun adapter auto setting MacOS route")
|
log.Infoln("Tun adapter auto setting global route")
|
||||||
addLinuxSystemRoute("1")
|
addLinuxSystemRoute("1")
|
||||||
addLinuxSystemRoute("2/7")
|
addLinuxSystemRoute("2/7")
|
||||||
addLinuxSystemRoute("4/6")
|
addLinuxSystemRoute("4/6")
|
||||||
|
@ -32,7 +32,7 @@ func SetLinuxAutoRoute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveLinuxAutoRoute() {
|
func RemoveLinuxAutoRoute() {
|
||||||
log.Infoln("Tun adapter removing MacOS route")
|
log.Infoln("Tun adapter removing global route")
|
||||||
delLinuxSystemRoute("1")
|
delLinuxSystemRoute("1")
|
||||||
delLinuxSystemRoute("2/7")
|
delLinuxSystemRoute("2/7")
|
||||||
delLinuxSystemRoute("4/6")
|
delLinuxSystemRoute("4/6")
|
||||||
|
@ -50,7 +50,7 @@ func addLinuxSystemRoute(net string) {
|
||||||
}
|
}
|
||||||
cmd := exec.Command("route", "add", "-net", net, "198.18.0.1")
|
cmd := exec.Command("route", "add", "-net", net, "198.18.0.1")
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
log.Errorln("[MacOS auto route] Failed to add system route: %s, cmd: %s", err.Error(), cmd.String())
|
log.Errorln("[auto route] Failed to add system route: %s, cmd: %s", err.Error(), cmd.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,6 @@ func delLinuxSystemRoute(net string) {
|
||||||
cmd := exec.Command("route", "delete", "-net", net, "198.18.0.1")
|
cmd := exec.Command("route", "delete", "-net", net, "198.18.0.1")
|
||||||
_ = cmd.Run()
|
_ = cmd.Run()
|
||||||
//if err := cmd.Run(); err != nil {
|
//if err := cmd.Run(); err != nil {
|
||||||
// log.Errorln("[MacOS auto route]Failed to delete system route: %s, cmd: %s", err.Error(), cmd.String())
|
// log.Errorln("[auto route]Failed to delete system route: %s, cmd: %s", err.Error(), cmd.String())
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
73
rule/port.go
73
rule/port.go
|
@ -1,15 +1,23 @@
|
||||||
package rules
|
package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type portReal struct {
|
||||||
|
portStart int
|
||||||
|
portEnd int
|
||||||
|
}
|
||||||
|
|
||||||
type Port struct {
|
type Port struct {
|
||||||
adapter string
|
adapter string
|
||||||
port string
|
port string
|
||||||
isSource bool
|
isSource bool
|
||||||
|
portList []portReal
|
||||||
network C.NetWork
|
network C.NetWork
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,9 +30,9 @@ func (p *Port) RuleType() C.RuleType {
|
||||||
|
|
||||||
func (p *Port) Match(metadata *C.Metadata) bool {
|
func (p *Port) Match(metadata *C.Metadata) bool {
|
||||||
if p.isSource {
|
if p.isSource {
|
||||||
return metadata.SrcPort == p.port
|
return p.matchPortReal(metadata.SrcPort)
|
||||||
}
|
}
|
||||||
return metadata.DstPort == p.port
|
return p.matchPortReal(metadata.DstPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Port) Adapter() string {
|
func (p *Port) Adapter() string {
|
||||||
|
@ -43,15 +51,72 @@ func (p *Port) NetWork() C.NetWork {
|
||||||
return p.network
|
return p.network
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Port) matchPortReal(portRef string) bool {
|
||||||
|
port, _ := strconv.Atoi(portRef)
|
||||||
|
var rs bool
|
||||||
|
for _, pr := range p.portList {
|
||||||
|
if pr.portEnd == -1 {
|
||||||
|
rs = port == pr.portStart
|
||||||
|
} else {
|
||||||
|
rs = port >= pr.portStart && port <= pr.portEnd
|
||||||
|
}
|
||||||
|
if rs {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func NewPort(port string, adapter string, isSource bool, network C.NetWork) (*Port, error) {
|
func NewPort(port string, adapter string, isSource bool, network C.NetWork) (*Port, error) {
|
||||||
_, err := strconv.Atoi(port)
|
ports := strings.Split(port, "/")
|
||||||
if err != nil {
|
if len(ports) > 28 {
|
||||||
|
return nil, fmt.Errorf("%s, too many ports to use, maximum support 28 ports", errPayload.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var portList []portReal
|
||||||
|
for _, p := range ports {
|
||||||
|
if p == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
subPort := strings.Split(strings.Trim(p, "[ ]"), "-")
|
||||||
|
subPortLen := len(subPort)
|
||||||
|
if subPortLen > 2 {
|
||||||
return nil, errPayload
|
return nil, errPayload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
portStart, err := strconv.Atoi(subPort[0])
|
||||||
|
if err != nil || portStart < 0 || portStart > 65535 {
|
||||||
|
return nil, errPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
if subPortLen == 1 {
|
||||||
|
portList = append(portList, portReal{portStart, -1})
|
||||||
|
|
||||||
|
} else if subPortLen == 2 {
|
||||||
|
portEnd, err1 := strconv.Atoi(subPort[1])
|
||||||
|
if err1 != nil || portEnd < 0 || portEnd > 65535 {
|
||||||
|
return nil, errPayload
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldReverse := portStart > portEnd
|
||||||
|
if shouldReverse {
|
||||||
|
portList = append(portList, portReal{portEnd, portStart})
|
||||||
|
} else {
|
||||||
|
portList = append(portList, portReal{portStart, portEnd})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(portList) == 0 {
|
||||||
|
return nil, errPayload
|
||||||
|
}
|
||||||
|
|
||||||
return &Port{
|
return &Port{
|
||||||
adapter: adapter,
|
adapter: adapter,
|
||||||
port: port,
|
port: port,
|
||||||
isSource: isSource,
|
isSource: isSource,
|
||||||
|
portList: portList,
|
||||||
network: network,
|
network: network,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,7 @@ var (
|
||||||
|
|
||||||
preProcessCacheFinder, _ = R.NewProcess("", "", C.ALLNet)
|
preProcessCacheFinder, _ = R.NewProcess("", "", C.ALLNet)
|
||||||
|
|
||||||
fakeIpMask = net.IPv4Mask(0, 0, 0xff, 0xff)
|
tunBroadcastAddr = net.IPv4(198, 18, 255, 255)
|
||||||
fakeIpMaxIp = net.IPv4(0, 0, 255, 255)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -144,7 +143,7 @@ func preHandleMetadata(metadata *C.Metadata) error {
|
||||||
// redir-host should lookup the hosts
|
// redir-host should lookup the hosts
|
||||||
metadata.DstIP = node.Data.(net.IP)
|
metadata.DstIP = node.Data.(net.IP)
|
||||||
}
|
}
|
||||||
} else if resolver.IsFakeIP(metadata.DstIP) && !fakeIpMaxIp.Equal(metadata.DstIP.Mask(fakeIpMask)) {
|
} else if resolver.IsFakeIP(metadata.DstIP) && !tunBroadcastAddr.Equal(metadata.DstIP) {
|
||||||
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
|
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue