diff --git a/AGHTechDoc.md b/AGHTechDoc.md
index 4f0c5c8d..5e9fa0d5 100644
--- a/AGHTechDoc.md
+++ b/AGHTechDoc.md
@@ -33,6 +33,7 @@ Contents:
 	* Static IP check/set
 	* API: Add a static lease
 	* API: Reset DHCP configuration
+	* RA+SLAAC
 * DNS general settings
 	* API: Get DNS general settings
 	* API: Set DNS general settings
@@ -725,6 +726,53 @@ Response:
 	200 OK
 
 
+### RA+SLAAC
+
+There are 3 options for a client to get IPv6 address:
+
+1. via DHCPv6.
+	Client doesn't receive any `ICMPv6.RouterAdvertisement` packets, so it tries to use DHCPv6.
+2. via SLAAC.
+	Client receives a `ICMPv6.RouterAdvertisement` packet with `Managed=false` flag and IPv6 prefix.
+	Client then assigns to itself an IPv6 address using this prefix and its MAC address.
+	DHCPv6 server won't be started in this case.
+3. via DHCPv6 or SLAAC.
+	Client receives a `ICMPv6.RouterAdvertisement` packet with `Managed=true` flag and IPv6 prefix.
+	Client may choose to use SLAAC or DHCPv6 to obtain an IPv6 address.
+
+Configuration:
+
+	dhcp:
+		...
+		dhcpv6:
+			...
+			ra_slaac_only: false
+			ra_allow_slaac: false
+
+* `ra_slaac_only:false; ra_allow_slaac:false`: use option #1.
+	Don't send any `ICMPv6.RouterAdvertisement` packets.
+* `ra_slaac_only:true; ra_allow_slaac:false`: use option #2.
+	Periodically send `ICMPv6.RouterAdvertisement(Flags=(Managed=false,Other=false))` packets.
+* `ra_slaac_only:false; ra_allow_slaac:true`: use option #3.
+	Periodically send `ICMPv6.RouterAdvertisement(Flags=(Managed=true,Other=true))` packets.
+
+ICMPv6.RouterAdvertisement packet description:
+
+	ICMPv6:
+	Type=RouterAdvertisement(134)
+	Flags
+		Managed=<BOOL>
+		Other=<BOOL>
+	Option=Prefix information(3)
+		<IPv6 address prefix (/64) of the network interface>
+	Option=MTU(5)
+		<...>
+	Option=Source link-layer address(1)
+		<MAC address>
+	Option=Recursive DNS Server(25)
+		<IPv6 address of DNS server>
+
+
 ## TLS
 
 
diff --git a/dhcpd/README.md b/dhcpd/README.md
index 83e48183..fb2bdc8d 100644
--- a/dhcpd/README.md
+++ b/dhcpd/README.md
@@ -30,6 +30,10 @@ To set up a test environment for DHCP server you need:
 
 3. Start your VM, install an OS.  Configure your network interface to use DHCP and the OS should ask for a IP address from our DHCP server.
 
+4. To see the current IP address on client OS you can use `ip a` command on Linux or `ipconfig` on Windows.
+
+5. To force the client OS to request an IP from DHCP server again, you can use `dhclient` on Linux or `ipconfig /release` on Windows.
+
 ### Configure server
 
 1. Edit server configuration file 'AdGuardHome.yaml', for example:
@@ -37,12 +41,19 @@ To set up a test environment for DHCP server you need:
         dhcp:
           enabled: true
           interface_name: vboxnet0
-          gateway_ip: 192.168.56.1
-          subnet_mask: 255.255.255.0
-          range_start: 192.168.56.2
-          range_end: 192.168.56.2
-          lease_duration: 86400
-          icmp_timeout_msec: 1000
+          dhcpv4:
+            gateway_ip: 192.168.56.1
+            subnet_mask: 255.255.255.0
+            range_start: 192.168.56.2
+            range_end: 192.168.56.2
+            lease_duration: 86400
+            icmp_timeout_msec: 1000
+            options: []
+          dhcpv6:
+            range_start: 2001::1
+            lease_duration: 86400
+            ra_slaac_only: false
+            ra_allow_slaac: false
 
 2. Start the server
 
diff --git a/dhcpd/router_adv.go b/dhcpd/router_adv.go
new file mode 100644
index 00000000..f4ecae19
--- /dev/null
+++ b/dhcpd/router_adv.go
@@ -0,0 +1,239 @@
+package dhcpd
+
+import (
+	"encoding/binary"
+	"fmt"
+	"net"
+	"sync/atomic"
+	"time"
+
+	"github.com/AdguardTeam/golibs/log"
+	"golang.org/x/net/icmp"
+	"golang.org/x/net/ipv6"
+)
+
+type raCtx struct {
+	raAllowSlaac     bool   // send RA packets without MO flags
+	raSlaacOnly      bool   // send RA packets with MO flags
+	ipAddr           net.IP // source IP address (link-local-unicast)
+	dnsIPAddr        net.IP // IP address for DNS Server option
+	prefixIPAddr     net.IP // IP address for Prefix option
+	ifaceName        string
+	iface            *net.Interface
+	packetSendPeriod time.Duration // how often RA packets are sent
+
+	conn *icmp.PacketConn // ICMPv6 socket
+	stop atomic.Value     // stop the packet sending loop
+}
+
+type icmpv6RA struct {
+	managedAddressConfiguration bool
+	otherConfiguration          bool
+	prefix                      net.IP
+	prefixLen                   int
+	sourceLinkLayerAddress      net.HardwareAddr
+	recursiveDNSServer          net.IP
+	mtu                         uint32
+}
+
+// Create an ICMPv6.RouterAdvertisement packet with all necessary options.
+//
+// ICMPv6:
+// type[1]
+// code[1]
+// chksum[2]
+// body (RouterAdvertisement):
+//   Cur Hop Limit[1]
+//   Flags[1]: MO......
+//   Router Lifetime[2]
+//   Reachable Time[4]
+//   Retrans Timer[4]
+//   Option=Prefix Information(3):
+//     Type[1]
+//     Length * 8bytes[1]
+//     Prefix Length[1]
+//     Flags[1]: LA......
+//     Valid Lifetime[4]
+//     Preferred Lifetime[4]
+//     Reserved[4]
+//     Prefix[16]
+//   Option=MTU(5):
+//     Type[1]
+//     Length * 8bytes[1]
+//     Reserved[2]
+//     MTU[4]
+//   Option=Source link-layer address(1):
+//     Link-Layer Address[6]
+//   Option=Recursive DNS Server(25):
+//     Type[1]
+//     Length * 8bytes[1]
+//     Reserved[2]
+//     Lifetime[4]
+//     Addresses of IPv6 Recursive DNS Servers[16]
+func createICMPv6RAPacket(params icmpv6RA) []byte {
+	data := make([]byte, 88)
+	i := 0
+
+	// ICMPv6:
+
+	data[i] = 134 // type
+	data[i+1] = 0 // code
+	data[i+2] = 0 // chksum
+	data[i+3] = 0
+	i += 4
+
+	// RouterAdvertisement:
+
+	data[i] = 64 // Cur Hop Limit[1]
+	i++
+
+	data[i] = 0 // Flags[1]: MO......
+	if params.managedAddressConfiguration {
+		data[i] |= 0x80
+	}
+	if params.otherConfiguration {
+		data[i] |= 0x40
+	}
+	i++
+
+	binary.BigEndian.PutUint16(data[i:], 1800) // Router Lifetime[2]
+	i += 2
+	binary.BigEndian.PutUint32(data[i:], 0) // Reachable Time[4]
+	i += 4
+	binary.BigEndian.PutUint32(data[i:], 0) // Retrans Timer[4]
+	i += 4
+
+	// Option=Prefix Information:
+
+	data[i] = 3   // Type
+	data[i+1] = 4 // Length
+	i += 2
+	data[i] = byte(params.prefixLen) // Prefix Length[1]
+	i++
+	data[i] = 0xc0 // Flags[1]
+	i++
+	binary.BigEndian.PutUint32(data[i:], 3600) // Valid Lifetime[4]
+	i += 4
+	binary.BigEndian.PutUint32(data[i:], 3600) // Preferred Lifetime[4]
+	i += 4
+	binary.BigEndian.PutUint32(data[i:], 0) // Reserved[4]
+	i += 4
+	copy(data[i:], params.prefix[:8]) // Prefix[16]
+	binary.BigEndian.PutUint32(data[i+8:], 0)
+	binary.BigEndian.PutUint32(data[i+12:], 0)
+	i += 16
+
+	// Option=MTU:
+
+	data[i] = 5   // Type
+	data[i+1] = 1 // Length
+	i += 2
+	binary.BigEndian.PutUint16(data[i:], 0) // Reserved[2]
+	i += 2
+	binary.BigEndian.PutUint32(data[i:], params.mtu) // MTU[4]
+	i += 4
+
+	// Option=Source link-layer address:
+
+	data[i] = 1   // Type
+	data[i+1] = 1 // Length
+	i += 2
+	copy(data[i:], params.sourceLinkLayerAddress) // Link-Layer Address[6]
+	i += 6
+
+	// Option=Recursive DNS Server:
+
+	data[i] = 25  // Type
+	data[i+1] = 3 // Length
+	i += 2
+	binary.BigEndian.PutUint16(data[i:], 0) // Reserved[2]
+	i += 2
+	binary.BigEndian.PutUint32(data[i:], 3600) // Lifetime[4]
+	i += 4
+	copy(data[i:], params.recursiveDNSServer) // Addresses of IPv6 Recursive DNS Servers[16]
+
+	return data
+}
+
+// Init - initialize RA module
+func (ra *raCtx) Init() error {
+	ra.stop.Store(0)
+	ra.conn = nil
+	if !(ra.raAllowSlaac || ra.raSlaacOnly) {
+		return nil
+	}
+
+	log.Debug("DHCPv6 RA: source IP address: %s  DNS IP address: %s",
+		ra.ipAddr, ra.dnsIPAddr)
+
+	params := icmpv6RA{
+		managedAddressConfiguration: !ra.raSlaacOnly,
+		otherConfiguration:          !ra.raSlaacOnly,
+		mtu:                         uint32(ra.iface.MTU),
+		prefixLen:                   64,
+		recursiveDNSServer:          ra.dnsIPAddr,
+		sourceLinkLayerAddress:      ra.iface.HardwareAddr,
+	}
+	params.prefix = make([]byte, 16)
+	copy(params.prefix, ra.prefixIPAddr[:8]) // /64
+
+	data := createICMPv6RAPacket(params)
+
+	var err error
+	ipAndScope := ra.ipAddr.String() + "%" + ra.ifaceName
+	ra.conn, err = icmp.ListenPacket("ip6:ipv6-icmp", ipAndScope)
+	if err != nil {
+		return fmt.Errorf("DHCPv6 RA: icmp.ListenPacket: %s", err)
+	}
+	success := false
+	defer func() {
+		if !success {
+			ra.Close()
+		}
+	}()
+
+	con6 := ra.conn.IPv6PacketConn()
+
+	if err := con6.SetHopLimit(255); err != nil {
+		return fmt.Errorf("DHCPv6 RA: SetHopLimit: %s", err)
+	}
+
+	if err := con6.SetMulticastHopLimit(255); err != nil {
+		return fmt.Errorf("DHCPv6 RA: SetMulticastHopLimit: %s", err)
+	}
+
+	msg := &ipv6.ControlMessage{
+		HopLimit: 255,
+		Src:      ra.ipAddr,
+		IfIndex:  ra.iface.Index,
+	}
+	addr := &net.UDPAddr{
+		IP: net.ParseIP("ff02::1"),
+	}
+
+	go func() {
+		log.Debug("DHCPv6 RA: starting to send periodic RouterAdvertisement packets")
+		for ra.stop.Load() == 0 {
+			_, err = con6.WriteTo(data, msg, addr)
+			if err != nil {
+				log.Error("DHCPv6 RA: WriteTo: %s", err)
+			}
+			time.Sleep(ra.packetSendPeriod)
+		}
+		log.Debug("DHCPv6 RA: loop exit")
+	}()
+
+	success = true
+	return nil
+}
+
+// Close - close module
+func (ra *raCtx) Close() {
+	log.Debug("DHCPv6 RA: closing")
+
+	ra.stop.Store(1)
+
+	if ra.conn != nil {
+		ra.conn.Close()
+	}
+}
diff --git a/dhcpd/router_adv_test.go b/dhcpd/router_adv_test.go
new file mode 100644
index 00000000..95f3d4fa
--- /dev/null
+++ b/dhcpd/router_adv_test.go
@@ -0,0 +1,31 @@
+package dhcpd
+
+import (
+	"bytes"
+	"net"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestRA(t *testing.T) {
+	ra := icmpv6RA{
+		managedAddressConfiguration: false,
+		otherConfiguration:          true,
+		mtu:                         1500,
+		prefix:                      net.ParseIP("1234::"),
+		prefixLen:                   64,
+		recursiveDNSServer:          net.ParseIP("fe80::800:27ff:fe00:0"),
+		sourceLinkLayerAddress:      []byte{0x0a, 0x00, 0x27, 0x00, 0x00, 0x00},
+	}
+	data := createICMPv6RAPacket(ra)
+	dataCorrect := []byte{
+		0x86, 0x00, 0x00, 0x00, 0x40, 0x40, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x0e, 0x10, 0x00, 0x00, 0x00, 0x00,
+		0x12, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0xdc, 0x01, 0x01, 0x0a, 0x00, 0x27, 0x00, 0x00, 0x00,
+		0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x10, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		0x08, 0x00, 0x27, 0xff, 0xfe, 0x00, 0x00, 0x00,
+	}
+	assert.True(t, bytes.Equal(data, dataCorrect))
+}
diff --git a/dhcpd/server.go b/dhcpd/server.go
index 1701c780..240715ca 100644
--- a/dhcpd/server.go
+++ b/dhcpd/server.go
@@ -83,6 +83,9 @@ type V6ServerConf struct {
 
 	LeaseDuration uint32 `yaml:"lease_duration"` // in seconds
 
+	RaSlaacOnly  bool `yaml:"ra_slaac_only"`  // send ICMPv6.RA packets without MO flags
+	RaAllowSlaac bool `yaml:"ra_allow_slaac"` // send ICMPv6.RA packets with MO flags
+
 	ipStart    net.IP        // starting IP address for dynamic leases
 	leaseTime  time.Duration // the time during which a dynamic lease is considered valid
 	dnsIPAddrs []net.IP      // IPv6 addresses to return to DHCP clients as DNS server addresses
diff --git a/dhcpd/v6.go b/dhcpd/v6.go
index 5259777a..23c8b88b 100644
--- a/dhcpd/v6.go
+++ b/dhcpd/v6.go
@@ -25,6 +25,8 @@ type v6Server struct {
 	ipAddrs    [256]byte
 	sid        dhcpv6.Duid
 
+	ra raCtx // RA module
+
 	conf V6ServerConf
 }
 
@@ -557,6 +559,27 @@ func getIfaceIPv6(iface net.Interface) []net.IP {
 	return res
 }
 
+// initialize RA module
+func (s *v6Server) initRA(iface *net.Interface) error {
+	// choose the source IP address - should be link-local-unicast
+	s.ra.ipAddr = s.conf.dnsIPAddrs[0]
+	for _, ip := range s.conf.dnsIPAddrs {
+		if ip.IsLinkLocalUnicast() {
+			s.ra.ipAddr = ip
+			break
+		}
+	}
+
+	s.ra.raAllowSlaac = s.conf.RaAllowSlaac
+	s.ra.raSlaacOnly = s.conf.RaSlaacOnly
+	s.ra.dnsIPAddr = s.ra.ipAddr
+	s.ra.prefixIPAddr = s.conf.ipStart
+	s.ra.ifaceName = s.conf.InterfaceName
+	s.ra.iface = iface
+	s.ra.packetSendPeriod = 1 * time.Second
+	return s.ra.Init()
+}
+
 // Start - start server
 func (s *v6Server) Start() error {
 	if !s.conf.Enabled {
@@ -568,13 +591,25 @@ func (s *v6Server) Start() error {
 		return wrapErrPrint(err, "Couldn't find interface by name %s", s.conf.InterfaceName)
 	}
 
-	log.Debug("DHCPv6: starting...")
 	s.conf.dnsIPAddrs = getIfaceIPv6(*iface)
 	if len(s.conf.dnsIPAddrs) == 0 {
 		log.Debug("DHCPv6: no IPv6 address for interface %s", iface.Name)
 		return nil
 	}
 
+	err = s.initRA(iface)
+	if err != nil {
+		return err
+	}
+
+	// don't initialize DHCPv6 server if we must force the clients to use SLAAC
+	if s.conf.RaSlaacOnly {
+		log.Debug("DHCPv6: not starting DHCPv6 server due to ra_slaac_only=true")
+		return nil
+	}
+
+	log.Debug("DHCPv6: starting...")
+
 	if len(iface.HardwareAddr) != 6 {
 		return fmt.Errorf("DHCPv6: invalid MAC %s", iface.HardwareAddr)
 	}
@@ -598,11 +633,15 @@ func (s *v6Server) Start() error {
 		err = s.srv.Serve()
 		log.Debug("DHCPv6: srv.Serve: %s", err)
 	}()
+
 	return nil
 }
 
 // Stop - stop server
 func (s *v6Server) Stop() {
+	s.ra.Close()
+
+	// DHCPv6 server may not be initialized if ra_slaac_only=true
 	if s.srv == nil {
 		return
 	}
@@ -626,7 +665,7 @@ func v6Create(conf V6ServerConf) (DHCPServer, error) {
 	}
 
 	s.conf.ipStart = net.ParseIP(conf.RangeStart)
-	if s.conf.ipStart == nil {
+	if s.conf.ipStart == nil || s.conf.ipStart.To16() == nil {
 		return s, fmt.Errorf("DHCPv6: invalid range-start IP: %s", conf.RangeStart)
 	}