diff --git a/cmd/client.go b/cmd/client.go
index 63e9396..d0fba20 100644
--- a/cmd/client.go
+++ b/cmd/client.go
@@ -97,6 +97,18 @@ func client(config *clientConfig) {
 	if len(config.Obfs) > 0 {
 		obfuscator = obfs.NewXPlusObfuscator([]byte(config.Obfs))
 	}
+	// Resolve preference
+	if len(config.ResolvePreference) > 0 {
+		pref, excl, err := transport.ResolvePreferenceFromString(config.ResolvePreference)
+		if err != nil {
+			logrus.WithFields(logrus.Fields{
+				"error": err,
+			}).Fatal("Failed to parse the resolve preference")
+		}
+		transport.DefaultClientTransport.PrefEnabled = true
+		transport.DefaultClientTransport.PrefIPv6 = pref
+		transport.DefaultClientTransport.PrefExclusive = excl
+	}
 	// ACL
 	var aclEngine *acl.Engine
 	if len(config.ACL) > 0 {
diff --git a/cmd/config.go b/cmd/config.go
index 9a804e8..7e14c3c 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -50,8 +50,8 @@ type serverConfig struct {
 	ReceiveWindowClient uint64 `json:"recv_window_client"`
 	MaxConnClient       int    `json:"max_conn_client"`
 	DisableMTUDiscovery bool   `json:"disable_mtu_discovery"`
-	IPv6Only            bool   `json:"ipv6_only"`
 	Resolver            string `json:"resolver"`
+	ResolvePreference   string `json:"resolve_preference"`
 	SOCKS5Outbound      struct {
 		Server   string `json:"server"`
 		User     string `json:"user"`
@@ -159,6 +159,7 @@ type clientConfig struct {
 	ReceiveWindow       uint64 `json:"recv_window"`
 	DisableMTUDiscovery bool   `json:"disable_mtu_discovery"`
 	Resolver            string `json:"resolver"`
+	ResolvePreference   string `json:"resolve_preference"`
 }
 
 func (c *clientConfig) Check() error {
diff --git a/cmd/server.go b/cmd/server.go
index cd6e096..6803ece 100644
--- a/cmd/server.go
+++ b/cmd/server.go
@@ -140,9 +140,17 @@ func server(config *serverConfig) {
 	if len(config.Obfs) > 0 {
 		obfuscator = obfs.NewXPlusObfuscator([]byte(config.Obfs))
 	}
-	// IPv6 only mode
-	if config.IPv6Only {
-		transport.DefaultServerTransport.IPv6Only = true
+	// Resolve preference
+	if len(config.ResolvePreference) > 0 {
+		pref, excl, err := transport.ResolvePreferenceFromString(config.ResolvePreference)
+		if err != nil {
+			logrus.WithFields(logrus.Fields{
+				"error": err,
+			}).Fatal("Failed to parse the resolve preference")
+		}
+		transport.DefaultServerTransport.PrefEnabled = true
+		transport.DefaultServerTransport.PrefIPv6 = pref
+		transport.DefaultServerTransport.PrefExclusive = excl
 	}
 	// SOCKS5 outbound
 	if config.SOCKS5Outbound.Server != "" {
diff --git a/pkg/transport/client.go b/pkg/transport/client.go
index 0191415..54c4838 100644
--- a/pkg/transport/client.go
+++ b/pkg/transport/client.go
@@ -13,13 +13,17 @@ import (
 )
 
 type ClientTransport struct {
-	Dialer *net.Dialer
+	Dialer        *net.Dialer
+	PrefEnabled   bool
+	PrefIPv6      bool
+	PrefExclusive bool
 }
 
 var DefaultClientTransport = &ClientTransport{
 	Dialer: &net.Dialer{
 		Timeout: 8 * time.Second,
 	},
+	PrefEnabled: false,
 }
 
 func (ct *ClientTransport) quicPacketConn(proto string, server string, obfs obfs.Obfuscator) (net.PacketConn, error) {
@@ -80,7 +84,11 @@ func (ct *ClientTransport) QUICDial(proto string, server string, tlsConfig *tls.
 }
 
 func (ct *ClientTransport) ResolveIPAddr(address string) (*net.IPAddr, error) {
-	return net.ResolveIPAddr("ip", address)
+	if ct.PrefEnabled {
+		return resolveIPAddrWithPreference(address, ct.PrefIPv6, ct.PrefExclusive)
+	} else {
+		return net.ResolveIPAddr("ip", address)
+	}
 }
 
 func (ct *ClientTransport) DialTCP(raddr *net.TCPAddr) (*net.TCPConn, error) {
diff --git a/pkg/transport/resolve.go b/pkg/transport/resolve.go
new file mode 100644
index 0000000..2bed90e
--- /dev/null
+++ b/pkg/transport/resolve.go
@@ -0,0 +1,58 @@
+package transport
+
+import (
+	"errors"
+	"fmt"
+	"net"
+)
+
+var (
+	errNoIPv4Addr = errors.New("no IPv4 address")
+	errNoIPv6Addr = errors.New("no IPv6 address")
+)
+
+func resolveIPAddrWithPreference(address string, preferIPv6 bool, exclusive bool) (*net.IPAddr, error) {
+	ips, err := net.LookupIP(address)
+	if err != nil {
+		return nil, err
+	}
+	if preferIPv6 {
+		for _, ip := range ips {
+			if ip.To4() == nil {
+				return &net.IPAddr{IP: ip}, nil
+			}
+		}
+		if exclusive {
+			return nil, errNoIPv6Addr
+		} else {
+			return &net.IPAddr{IP: ips[0]}, nil
+		}
+	} else {
+		// prefer IPv4
+		for _, ip := range ips {
+			if ip.To4() != nil {
+				return &net.IPAddr{IP: ip}, nil
+			}
+		}
+		if exclusive {
+			return nil, errNoIPv4Addr
+		} else {
+			return &net.IPAddr{IP: ips[0]}, nil
+		}
+	}
+}
+
+func ResolvePreferenceFromString(preference string) (bool, bool, error) {
+	switch preference {
+	case "4":
+		return false, true, nil
+	case "6":
+		return true, true, nil
+	case "46":
+		return false, false, nil
+	case "64":
+		return true, false, nil
+	default:
+		return false, false, fmt.Errorf("%s is not a valid preference", preference)
+	}
+}
diff --git a/pkg/transport/server.go b/pkg/transport/server.go
index 56bc451..bf03e0f 100644
--- a/pkg/transport/server.go
+++ b/pkg/transport/server.go
@@ -13,9 +13,11 @@ import (
 )
 
 type ServerTransport struct {
-	Dialer       *net.Dialer
-	IPv6Only     bool
-	SOCKS5Client *SOCKS5Client
+	Dialer        *net.Dialer
+	SOCKS5Client  *SOCKS5Client
+	PrefEnabled   bool
+	PrefIPv6      bool
+	PrefExclusive bool
 }
 
 type PUDPConn interface {
@@ -28,7 +30,7 @@ var DefaultServerTransport = &ServerTransport{
 	Dialer: &net.Dialer{
 		Timeout: 8 * time.Second,
 	},
-	IPv6Only: false,
+	PrefEnabled: false,
 }
 
 func (st *ServerTransport) quicPacketConn(proto string, laddr string, obfs obfs.Obfuscator) (net.PacketConn, error) {
@@ -92,8 +94,8 @@ func (ct *ServerTransport) QUICListen(proto string, listen string, tlsConfig *tl
 }
 
 func (ct *ServerTransport) ResolveIPAddr(address string) (*net.IPAddr, error) {
-	if ct.IPv6Only {
-		return net.ResolveIPAddr("ip6", address)
+	if ct.PrefEnabled {
+		return resolveIPAddrWithPreference(address, ct.PrefIPv6, ct.PrefExclusive)
 	} else {
 		return net.ResolveIPAddr("ip", address)
 	}