mirror of
https://github.com/miekg/dns.git
synced 2025-10-10 17:31:01 +02:00
Previously, the oob data was just stored and sent to WriteMsgUDP but it ignores the Src field when writing. Instead, now it is setting the Src to the original Dst and handling IPv4 IPs over IPv6 correctly.
221 lines
5.9 KiB
Go
221 lines
5.9 KiB
Go
// +build linux,!appengine
|
|
|
|
package dns
|
|
|
|
// See:
|
|
// * http://stackoverflow.com/questions/3062205/setting-the-source-ip-for-a-udp-socket and
|
|
// * http://blog.powerdns.com/2012/10/08/on-binding-datagram-udp-sockets-to-the-any-addresses/
|
|
//
|
|
// Why do we need this: When listening on 0.0.0.0 with UDP so kernel decides what is the outgoing
|
|
// interface, this might not always be the correct one. This code will make sure the egress
|
|
// packet's interface matched the ingress' one.
|
|
|
|
import (
|
|
"net"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/miekg/dns/internal/socket"
|
|
)
|
|
|
|
const (
|
|
sizeofInet6Pktinfo = 0x14
|
|
sizeofInetPktinfo = 0xc
|
|
protocolIP = 0
|
|
protocolIPv6 = 41
|
|
)
|
|
|
|
type inetPktinfo struct {
|
|
Ifindex int32
|
|
Spec_dst [4]byte /* in_addr */
|
|
Addr [4]byte /* in_addr */
|
|
}
|
|
|
|
type inet6Pktinfo struct {
|
|
Addr [16]byte /* in6_addr */
|
|
Ifindex int32
|
|
}
|
|
|
|
type inetControlMessage struct {
|
|
Src net.IP // source address, specifying only
|
|
Dst net.IP // destination address, receiving only
|
|
}
|
|
|
|
// setUDPSocketOptions sets the UDP socket options.
|
|
// This function is implemented on a per platform basis. See udp_*.go for more details
|
|
func setUDPSocketOptions(conn *net.UDPConn) error {
|
|
sa, err := getUDPSocketName(conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch sa.(type) {
|
|
case *syscall.SockaddrInet6:
|
|
v6only, err := getUDPSocketOptions6Only(conn)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
setUDPSocketOptions6(conn)
|
|
if !v6only {
|
|
setUDPSocketOptions4(conn)
|
|
}
|
|
case *syscall.SockaddrInet4:
|
|
setUDPSocketOptions4(conn)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// setUDPSocketOptions4 prepares the v4 socket for sessions.
|
|
func setUDPSocketOptions4(conn *net.UDPConn) error {
|
|
file, err := conn.File()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1); err != nil {
|
|
file.Close()
|
|
return err
|
|
}
|
|
// Calling File() above results in the connection becoming blocking, we must fix that.
|
|
// See https://github.com/miekg/dns/issues/279
|
|
err = syscall.SetNonblock(int(file.Fd()), true)
|
|
if err != nil {
|
|
file.Close()
|
|
return err
|
|
}
|
|
file.Close()
|
|
return nil
|
|
}
|
|
|
|
// setUDPSocketOptions6 prepares the v6 socket for sessions.
|
|
func setUDPSocketOptions6(conn *net.UDPConn) error {
|
|
file, err := conn.File()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil {
|
|
file.Close()
|
|
return err
|
|
}
|
|
err = syscall.SetNonblock(int(file.Fd()), true)
|
|
if err != nil {
|
|
file.Close()
|
|
return err
|
|
}
|
|
file.Close()
|
|
return nil
|
|
}
|
|
|
|
// getUDPSocketOption6Only return true if the socket is v6 only and false when it is v4/v6 combined
|
|
// (dualstack).
|
|
func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) {
|
|
file, err := conn.File()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
// dual stack. See http://stackoverflow.com/questions/1618240/how-to-support-both-ipv4-and-ipv6-connections
|
|
v6only, err := syscall.GetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY)
|
|
if err != nil {
|
|
file.Close()
|
|
return false, err
|
|
}
|
|
file.Close()
|
|
return v6only == 1, nil
|
|
}
|
|
|
|
func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) {
|
|
file, err := conn.File()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
return syscall.Getsockname(int(file.Fd()))
|
|
}
|
|
|
|
// marshalInetPacketInfo marshals a ipv4 control message, returning
|
|
// the byte slice for the next marshal, if any
|
|
func marshalInetPacketInfo(b []byte, cm *inetControlMessage) []byte {
|
|
m := socket.ControlMessage(b)
|
|
m.MarshalHeader(protocolIP, syscall.IP_PKTINFO, sizeofInetPktinfo)
|
|
if cm != nil {
|
|
pi := (*inetPktinfo)(unsafe.Pointer(&m.Data(sizeofInetPktinfo)[0]))
|
|
if ip := cm.Src.To4(); ip != nil {
|
|
copy(pi.Spec_dst[:], ip)
|
|
}
|
|
}
|
|
return m.Next(sizeofInetPktinfo)
|
|
}
|
|
|
|
// marshalInet6PacketInfo marshals a ipv6 control message, returning
|
|
// the byte slice for the next marshal, if any
|
|
func marshalInet6PacketInfo(b []byte, cm *inetControlMessage) []byte {
|
|
m := socket.ControlMessage(b)
|
|
m.MarshalHeader(protocolIPv6, syscall.IPV6_PKTINFO, sizeofInet6Pktinfo)
|
|
if cm != nil {
|
|
pi := (*inet6Pktinfo)(unsafe.Pointer(&m.Data(sizeofInet6Pktinfo)[0]))
|
|
if ip := cm.Src.To16(); ip != nil && ip.To4() == nil {
|
|
copy(pi.Addr[:], ip)
|
|
}
|
|
}
|
|
return m.Next(sizeofInet6Pktinfo)
|
|
}
|
|
|
|
func parseInetPacketInfo(cm *inetControlMessage, b []byte) {
|
|
pi := (*inetPktinfo)(unsafe.Pointer(&b[0]))
|
|
if len(cm.Dst) < net.IPv4len {
|
|
cm.Dst = make(net.IP, net.IPv4len)
|
|
}
|
|
copy(cm.Dst, pi.Addr[:])
|
|
}
|
|
|
|
func parseInet6PacketInfo(cm *inetControlMessage, b []byte) {
|
|
pi := (*inet6Pktinfo)(unsafe.Pointer(&b[0]))
|
|
if len(cm.Dst) < net.IPv6len {
|
|
cm.Dst = make(net.IP, net.IPv6len)
|
|
}
|
|
copy(cm.Dst, pi.Addr[:])
|
|
}
|
|
|
|
// parseUDPSocketDst takes out-of-band data from ReadMsgUDP and parses it for
|
|
// the Dst address
|
|
func parseUDPSocketDst(oob []byte) (net.IP, error) {
|
|
cm := new(inetControlMessage)
|
|
ms, err := socket.ControlMessage(oob).Parse()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, m := range ms {
|
|
lvl, typ, l, err := m.ParseHeader()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if lvl == protocolIPv6 { // IPv6
|
|
if typ == syscall.IPV6_PKTINFO && l >= sizeofInet6Pktinfo {
|
|
parseInet6PacketInfo(cm, m.Data(l))
|
|
}
|
|
} else if lvl == protocolIP { // IPv4
|
|
if typ == syscall.IP_PKTINFO && l >= sizeofInetPktinfo {
|
|
parseInetPacketInfo(cm, m.Data(l))
|
|
}
|
|
}
|
|
}
|
|
return cm.Dst, nil
|
|
}
|
|
|
|
// marshalUDPSocketSrc takes the given src address and returns out-of-band data
|
|
// to give to WriteMsgUDP
|
|
func marshalUDPSocketSrc(src net.IP) []byte {
|
|
var oob []byte
|
|
// If the dst is definitely an ipv6, then use ipv6 control to respond
|
|
// otherwise use ipv4 because the ipv6 marshal ignores ipv4 messages.
|
|
// See marshalInet6PacketInfo
|
|
cm := new(inetControlMessage)
|
|
cm.Src = src
|
|
if src.To4() == nil {
|
|
oob = make([]byte, socket.ControlMessageSpace(sizeofInet6Pktinfo))
|
|
marshalInet6PacketInfo(oob, cm)
|
|
} else {
|
|
oob = make([]byte, socket.ControlMessageSpace(sizeofInetPktinfo))
|
|
marshalInetPacketInfo(oob, cm)
|
|
}
|
|
return oob
|
|
}
|