Go
golang

GolangでのUDP処理メモ

疑問

Go で UDP 送信する際、DialUDP() でUDPConnを作って、send()するが、DialUDP()って実際何をしているのだろうか?

func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)

socket作る?bindもしちゃう?
システムコールするものなので、大量に呼び出すものではないよね?

というあたりを確認したい。

ソース追ってみる

DialUDP() から追っかける。

net/udpsock.go
func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error) {
    switch network {
    case "udp", "udp4", "udp6":
    default:
        return nil, &OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: UnknownNetworkError(network)}
    }
    if raddr == nil {
        return nil, &OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: nil, Err: errMissingAddress}
    }
    c, err := dialUDP(context.Background(), network, laddr, raddr)
    if err != nil {
        return nil, &OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err}
    }
    return c, nil
}

DialUDP()dialUDP() を呼ぶ。

net/udpsock_posix.go
func dialUDP(ctx context.Context, net string, laddr, raddr *UDPAddr) (*UDPConn, error) {
    fd, err := internetSocket(ctx, net, laddr, raddr, syscall.SOCK_DGRAM, 0, "dial")
    if err != nil {
        return nil, err
    }
    return newUDPConn(fd), nil
}

dialUDP()internetSocket() を呼ぶ。その際、sotype に SOCK_DGRAM を、proto に 0 を、mode に "dial" を指定。mode は後で family 判定に使っているが、"listen" 以外は無視されているな。

net/ipsock_posix.go
func internetSocket(ctx context.Context, net string, laddr, raddr sockaddr, sotype, proto int, mode string) (fd *netFD, err error) {
    if (runtime.GOOS == "windows" || runtime.GOOS == "openbsd" || runtime.GOOS == "nacl") && mode == "dial" && raddr.isWildcard() {
        raddr = raddr.toLocal(net)
    }
    family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)
    return socket(ctx, net, family, sotype, proto, ipv6only, laddr, raddr)
}

internetSocket() は family を判定後、socket() を呼ぶ。family は AF_INET か AF_INET6。

net/sock_posix.go
func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr) (fd *netFD, err error) {
    s, err := sysSocket(family, sotype, proto)

    if fd, err = newFD(s, family, sotype, net); err != nil {

    if err := fd.dial(ctx, laddr, raddr); err != nil {

}

socket() は結局 socket(2) システムコールを呼んだ後、fd.dial() を呼ぶ。

sock_posix.go
func (fd *netFD) dial(ctx context.Context, laddr, raddr sockaddr) error {
    var err error
    var lsa syscall.Sockaddr
    if laddr != nil {
        if lsa, err = laddr.sockaddr(fd.family); err != nil {
            return err
        } else if lsa != nil {
            if err := syscall.Bind(fd.pfd.Sysfd, lsa); err != nil {
                return os.NewSyscallError("bind", err)
            }
        }
    }

fd.dial() 中で bind(2) システムコールを呼んでいる。ここで local address を使っているので、ソースが同じでデスティネーションの違うUDPDial()を呼ぶとエラーになるのだなぁ。なるほど。