diff --git a/adapter/inbound/addition.go b/adapter/inbound/addition.go index a9896c8c..c38c1aa1 100644 --- a/adapter/inbound/addition.go +++ b/adapter/inbound/addition.go @@ -63,3 +63,9 @@ func WithInAddr(addr net.Addr) Addition { } } } + +func WithDSCP(dscp uint8) Addition { + return func(metadata *C.Metadata) { + metadata.DSCP = dscp + } +} diff --git a/constant/metadata.go b/constant/metadata.go index 3c712909..6df6ff43 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -147,6 +147,7 @@ type Metadata struct { SpecialProxy string `json:"specialProxy"` SpecialRules string `json:"specialRules"` RemoteDst string `json:"remoteDestination"` + DSCP uint8 `json:"dscp"` RawSrcAddr net.Addr `json:"-"` RawDstAddr net.Addr `json:"-"` diff --git a/constant/rule.go b/constant/rule.go index 906f3cef..66fc18bb 100644 --- a/constant/rule.go +++ b/constant/rule.go @@ -14,6 +14,7 @@ const ( SrcPort DstPort InPort + DSCP InUser InName InType @@ -73,6 +74,8 @@ func (rt RuleType) String() string { return "RuleSet" case Network: return "Network" + case DSCP: + return "DSCP" case Uid: return "Uid" case SubRules: diff --git a/listener/tproxy/setsockopt_linux.go b/listener/tproxy/setsockopt_linux.go index 06f3e1c3..b83b28a4 100644 --- a/listener/tproxy/setsockopt_linux.go +++ b/listener/tproxy/setsockopt_linux.go @@ -34,6 +34,14 @@ func setsockopt(rc syscall.RawConn, addr string) error { if err == nil && isIPv6 { err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, IPV6_RECVORIGDSTADDR, 1) } + + if err == nil { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVTOS, 1) + } + + if err == nil { + err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, syscall.IPV6_RECVTCLASS, 1) + } }) return err diff --git a/listener/tproxy/udp.go b/listener/tproxy/udp.go index aa0fee19..f738ef0d 100644 --- a/listener/tproxy/udp.go +++ b/listener/tproxy/udp.go @@ -79,6 +79,9 @@ func NewUDP(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*UDPLi continue } + dscp, _ := getDSCP(oob[:oobn]) + additions = append(additions, inbound.WithDSCP(dscp)) + if rAddr.Addr().Is4() { // try to unmap 4in6 address lAddr = netip.AddrPortFrom(lAddr.Addr().Unmap(), lAddr.Port()) diff --git a/listener/tproxy/udp_linux.go b/listener/tproxy/udp_linux.go index 472a23d3..02b51379 100644 --- a/listener/tproxy/udp_linux.go +++ b/listener/tproxy/udp_linux.go @@ -104,7 +104,7 @@ func getOrigDst(oob []byte) (netip.AddrPort, error) { } // retrieve the destination address from the SCM. - sa, err := unix.ParseOrigDstAddr(&scms[0]) + sa, err := unix.ParseOrigDstAddr(&scms[1]) if err != nil { return netip.AddrPort{}, fmt.Errorf("retrieve destination: %w", err) } @@ -122,3 +122,30 @@ func getOrigDst(oob []byte) (netip.AddrPort, error) { return rAddr, nil } + +func getDSCP (oob []byte) (uint8, error) { + scms, err := unix.ParseSocketControlMessage(oob) + if err != nil { + return 0, fmt.Errorf("parse control message: %w", err) + } + dscp, err := parseDSCP(&scms[0]) + if err != nil { + return 0, fmt.Errorf("retrieve DSCP: %w", err) + } + return dscp, nil +} + +func parseDSCP(m *unix.SocketControlMessage) (uint8, error) { + switch { + case m.Header.Level == unix.SOL_IP && m.Header.Type == unix.IP_TOS: + dscp := uint8(m.Data[0] >> 2) + return dscp, nil + + case m.Header.Level == unix.SOL_IPV6 && m.Header.Type == unix.IPV6_TCLASS: + dscp := uint8(m.Data[0] >> 2) + return dscp, nil + + default: + return 0, nil + } +} diff --git a/listener/tproxy/udp_other.go b/listener/tproxy/udp_other.go index b35b07dd..2e0e0ae7 100644 --- a/listener/tproxy/udp_other.go +++ b/listener/tproxy/udp_other.go @@ -12,6 +12,10 @@ func getOrigDst(oob []byte) (netip.AddrPort, error) { return netip.AddrPort{}, errors.New("UDP redir not supported on current platform") } +func getDSCP(oob []byte) (uint8, error) { + return 0, errors.New("UDP redir not supported on current platform") +} + func dialUDP(network string, lAddr, rAddr netip.AddrPort) (*net.UDPConn, error) { return nil, errors.New("UDP redir not supported on current platform") } diff --git a/rules/common/dscp.go b/rules/common/dscp.go new file mode 100644 index 00000000..baedb28d --- /dev/null +++ b/rules/common/dscp.go @@ -0,0 +1,47 @@ +package common + +import ( + "fmt" + "strconv" + + C "github.com/metacubex/mihomo/constant" +) + +type DSCP struct { + *Base + dscp uint8 + payload string + adapter string +} + +func (d *DSCP) RuleType() C.RuleType { + return C.DSCP +} + +func (d *DSCP) Match(metadata *C.Metadata) (bool, string) { + return metadata.DSCP == d.dscp, d.adapter +} + +func (d *DSCP) Adapter() string { + return d.adapter +} + +func (d *DSCP) Payload() string { + return d.payload +} + +func NewDSCP(dscp string, adapter string) (*DSCP, error) { + dscpi, err := strconv.Atoi(dscp) + if err != nil { + return nil, fmt.Errorf("parse DSCP rule fail: %w", err) + } + if dscpi < 0 || dscpi > 63 { + return nil, fmt.Errorf("DSCP couldn't be negative or exceed 63") + } + return &DSCP{ + Base: &Base{}, + payload: dscp, + dscp: uint8(dscpi), + adapter: adapter, + }, nil +} diff --git a/rules/parser.go b/rules/parser.go index e2157cef..7a79b18b 100644 --- a/rules/parser.go +++ b/rules/parser.go @@ -38,6 +38,8 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string] parsed, parseErr = RC.NewPort(payload, target, C.DstPort) case "IN-PORT": parsed, parseErr = RC.NewPort(payload, target, C.InPort) + case "DSCP": + parsed, parseErr = RC.NewDSCP(payload, target) case "PROCESS-NAME": parsed, parseErr = RC.NewProcess(payload, target, true) case "PROCESS-PATH":