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..52becbc9 100644 --- a/listener/tproxy/udp.go +++ b/listener/tproxy/udp.go @@ -74,7 +74,8 @@ func NewUDP(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*UDPLi continue } - rAddr, err := getOrigDst(oob[:oobn]) + rAddr, dscp, err := getOrigDstAndDSCP(oob[:oobn]) + additions = append(additions, inbound.WithDSCP(dscp)) if err != nil { continue } diff --git a/listener/tproxy/udp_linux.go b/listener/tproxy/udp_linux.go index 472a23d3..eee09c00 100644 --- a/listener/tproxy/udp_linux.go +++ b/listener/tproxy/udp_linux.go @@ -96,17 +96,23 @@ func udpAddrFamily(net string, lAddr, rAddr netip.AddrPort) int { return syscall.AF_INET6 } -func getOrigDst(oob []byte) (netip.AddrPort, error) { +func getOrigDstAndDSCP(oob []byte) (netip.AddrPort, uint8, error) { // oob contains socket control messages which we need to parse. scms, err := unix.ParseSocketControlMessage(oob) if err != nil { - return netip.AddrPort{}, fmt.Errorf("parse control message: %w", err) + return netip.AddrPort{}, 0, fmt.Errorf("parse control message: %w", err) } // 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) + return netip.AddrPort{}, 0, fmt.Errorf("retrieve destination: %w", err) + } + + // retrieve DSCP from the SCM + dscp, err := parseDSCP(&scms[0]) + if err != nil { + return netip.AddrPort{}, 0, fmt.Errorf("retrieve DSCP: %w", err) } // encode the destination address into a cmsg. @@ -117,8 +123,23 @@ func getOrigDst(oob []byte) (netip.AddrPort, error) { case *unix.SockaddrInet6: rAddr = netip.AddrPortFrom(netip.AddrFrom16(v.Addr), uint16(v.Port)) default: - return netip.AddrPort{}, fmt.Errorf("unsupported address type: %T", v) + return netip.AddrPort{}, 0, fmt.Errorf("unsupported address type: %T", v) } - return rAddr, nil + return rAddr, 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/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":