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
|
|
}
|