Feature: support multiport condition for rule SRC-PORT and DST-PORT

This commit is contained in:
yaling888 2021-07-06 15:07:05 +08:00
parent e2c7b19000
commit 56dff65149
6 changed files with 118 additions and 28 deletions

View file

@ -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.

View file

@ -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
} }

View file

@ -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
} }

View file

@ -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())
//} //}
} }

View file

@ -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
} }

View file

@ -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)
} }
} }