Skip to content

Commit

Permalink
Feature: add dhcp type dns client (#1509)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kr328 authored Sep 6, 2021
1 parent a2d59d6 commit a5b950a
Show file tree
Hide file tree
Showing 26 changed files with 759 additions and 288 deletions.
2 changes: 1 addition & 1 deletion adapter/outbound/direct.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn,

// DialUDP implements C.ProxyAdapter
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "")
pc, err := dialer.ListenPacket(context.Background(), "udp", "")
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion adapter/outbound/shadowsocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_

// DialUDP implements C.ProxyAdapter
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "")
pc, err := dialer.ListenPacket(context.Background(), "udp", "")
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion adapter/outbound/shadowsocksr.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata)

// DialUDP implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
pc, err := dialer.ListenPacket("udp", "")
pc, err := dialer.ListenPacket(context.Background(), "udp", "")
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion adapter/outbound/socks5.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, err error) {
return
}

pc, err := dialer.ListenPacket("udp", "")
pc, err := dialer.ListenPacket(context.Background(), "udp", "")
if err != nil {
return
}
Expand Down
5 changes: 4 additions & 1 deletion adapter/provider/vehicle.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package provider
import (
"context"
"io/ioutil"
"net"
"net/http"
"net/url"
"time"
Expand Down Expand Up @@ -71,7 +72,9 @@ func (h *HTTPVehicle) Read() ([]byte, error) {
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
DialContext: dialer.DialContext,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
return dialer.DialContext(ctx, network, address)
},
}

client := http.Client{Transport: transport}
Expand Down
18 changes: 18 additions & 0 deletions component/dhcp/conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dhcp

import (
"context"
"net"
"runtime"

"github.com/Dreamacro/clash/component/dialer"
)

func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, error) {
listenAddr := "0.0.0.0:68"
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
listenAddr = "255.255.255.255:68"
}

return dialer.ListenPacket(ctx, "udp4", listenAddr, dialer.WithInterface(ifaceName), dialer.WithAddrReuse(true))
}
94 changes: 94 additions & 0 deletions component/dhcp/dhcp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package dhcp

import (
"context"
"errors"
"math/rand"
"net"

"github.com/insomniacslk/dhcp/dhcpv4"
)

var (
ErrNotResponding = errors.New("DHCP not responding")
ErrNotFound = errors.New("DNS option not found")
)

func ResolveDNSFromDHCP(context context.Context, ifaceName string) ([]net.IP, error) {
conn, err := ListenDHCPClient(context, ifaceName)
if err != nil {
return nil, err
}
defer conn.Close()

result := make(chan []net.IP, 1)

discovery, err := dhcpv4.NewDiscovery(randomHardware(), dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer))
if err != nil {
return nil, err
}

go receiveOffer(conn, discovery.TransactionID, result)

_, err = conn.WriteTo(discovery.ToBytes(), &net.UDPAddr{IP: net.IPv4bcast, Port: 67})
if err != nil {
return nil, err
}

select {
case r, ok := <-result:
if !ok {
return nil, ErrNotFound
}
return r, nil
case <-context.Done():
return nil, ErrNotResponding
}
}

func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- []net.IP) {
defer close(result)

buf := make([]byte, dhcpv4.MaxMessageSize)

for {
n, _, err := conn.ReadFrom(buf)
if err != nil {
return
}

pkt, err := dhcpv4.FromBytes(buf[:n])
if err != nil {
continue
}

if pkt.MessageType() != dhcpv4.MessageTypeOffer {
continue
}

if pkt.TransactionID != id {
continue
}

dns := pkt.DNS()
if len(dns) == 0 {
return
}

result <- dns

return
}
}

func randomHardware() net.HardwareAddr {
addr := make(net.HardwareAddr, 6)

addr[0] = 0xff

for i := 1; i < len(addr); i++ {
addr[i] = byte(rand.Intn(254) + 1)
}

return addr
}
118 changes: 0 additions & 118 deletions component/dialer/bind.go

This file was deleted.

40 changes: 23 additions & 17 deletions component/dialer/bind_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,57 @@ package dialer
import (
"net"
"syscall"

"golang.org/x/sys/unix"

"github.com/Dreamacro/clash/component/iface"
)

type controlFn = func(network, address string, c syscall.RawConn) error

func bindControl(ifaceIdx int) controlFn {
return func(network, address string, c syscall.RawConn) error {
func bindControl(ifaceIdx int, chain controlFn) controlFn {
return func(network, address string, c syscall.RawConn) (err error) {
defer func() {
if err == nil && chain != nil {
err = chain(network, address, c)
}
}()

ipStr, _, err := net.SplitHostPort(address)
if err == nil {
ip := net.ParseIP(ipStr)
if ip != nil && !ip.IsGlobalUnicast() {
return nil
return
}
}

return c.Control(func(fd uintptr) {
switch network {
case "tcp4", "udp4":
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_BOUND_IF, ifaceIdx)
unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, ifaceIdx)
case "tcp6", "udp6":
syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_BOUND_IF, ifaceIdx)
unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, ifaceIdx)
}
})
}
}

func bindIfaceToDialer(dialer *net.Dialer, ifaceName string) error {
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
return net.InterfaceByName(ifaceName)
})
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ net.IP) error {
ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil {
return err
}

dialer.Control = bindControl(iface.(*net.Interface).Index)
dialer.Control = bindControl(ifaceObj.Index, dialer.Control)
return nil
}

func bindIfaceToListenConfig(lc *net.ListenConfig, ifaceName string) error {
iface, err, _ := ifaceSingle.Do(func() (interface{}, error) {
return net.InterfaceByName(ifaceName)
})
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil {
return err
return "", err
}

lc.Control = bindControl(iface.(*net.Interface).Index)
return nil
lc.Control = bindControl(ifaceObj.Index, lc.Control)
return address, nil
}
Loading

0 comments on commit a5b950a

Please sign in to comment.