github.com/diamondburned/arikawa/v2@v2.1.0/voice/udp/udp.go (about)

     1  package udp
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/binary"
     7  	"io"
     8  	"net"
     9  	"time"
    10  
    11  	"github.com/pkg/errors"
    12  	"golang.org/x/crypto/nacl/secretbox"
    13  )
    14  
    15  const (
    16  	packetHeaderSize = 12
    17  )
    18  
    19  // Dialer is the default dialer that this package uses for all its dialing.
    20  var (
    21  	ErrDecryptionFailed = errors.New("decryption failed")
    22  	Dialer              = net.Dialer{
    23  		Timeout: 10 * time.Second,
    24  	}
    25  )
    26  
    27  // Packet represents a voice packet. It is not thread-safe.
    28  type Packet struct {
    29  	VersionFlags byte
    30  	Type         byte
    31  	SSRC         uint32
    32  	Sequence     uint16
    33  	Timestamp    uint32
    34  	Opus         []byte
    35  }
    36  
    37  // Connection represents a voice connection. It is not thread-safe.
    38  type Connection struct {
    39  	GatewayIP   string
    40  	GatewayPort uint16
    41  
    42  	context context.Context
    43  	conn    net.Conn
    44  	ssrc    uint32
    45  
    46  	// frequency rate.Limiter
    47  	frequency *time.Ticker
    48  	timeIncr  uint32
    49  
    50  	packet [12]byte
    51  	secret [32]byte
    52  
    53  	sequence  uint16
    54  	timestamp uint32
    55  	nonce     [24]byte
    56  
    57  	// recv fields
    58  	recvNonce  [24]byte
    59  	recvBuf    []byte  // len 1400
    60  	recvOpus   []byte  // len 1400
    61  	recvPacket *Packet // uses recvOpus' backing array
    62  }
    63  
    64  func DialConnectionCtx(ctx context.Context, addr string, ssrc uint32) (*Connection, error) {
    65  	// Create a new UDP connection.
    66  	conn, err := Dialer.DialContext(ctx, "udp", addr)
    67  	if err != nil {
    68  		return nil, errors.Wrap(err, "failed to dial host")
    69  	}
    70  
    71  	// https://discord.com/developers/docs/topics/voice-connections#ip-discovery
    72  	ssrcBuffer := [70]byte{
    73  		0x1, 0x2,
    74  	}
    75  	binary.BigEndian.PutUint16(ssrcBuffer[2:4], 70)
    76  	binary.BigEndian.PutUint32(ssrcBuffer[4:8], ssrc)
    77  
    78  	_, err = conn.Write(ssrcBuffer[:])
    79  	if err != nil {
    80  		return nil, errors.Wrap(err, "failed to write SSRC buffer")
    81  	}
    82  
    83  	var ipBuffer [70]byte
    84  
    85  	// ReadFull makes sure to read all 70 bytes.
    86  	_, err = io.ReadFull(conn, ipBuffer[:])
    87  	if err != nil {
    88  		return nil, errors.Wrap(err, "failed to read IP buffer")
    89  	}
    90  
    91  	ipbody := ipBuffer[4:68]
    92  
    93  	nullPos := bytes.Index(ipbody, []byte{'\x00'})
    94  	if nullPos < 0 {
    95  		return nil, errors.New("UDP IP discovery did not contain a null terminator")
    96  	}
    97  
    98  	ip := ipbody[:nullPos]
    99  	port := binary.LittleEndian.Uint16(ipBuffer[68:70])
   100  
   101  	// https://discord.com/developers/docs/topics/voice-connections#encrypting-and-sending-voice
   102  	packet := [12]byte{
   103  		0: 0x80, // Version + Flags
   104  		1: 0x78, // Payload Type
   105  		// [2:4] // Sequence
   106  		// [4:8] // Timestamp
   107  	}
   108  
   109  	// Write SSRC to the header.
   110  	binary.BigEndian.PutUint32(packet[8:12], ssrc) // SSRC
   111  
   112  	return &Connection{
   113  		GatewayIP:   string(ip),
   114  		GatewayPort: port,
   115  		frequency:   time.NewTicker(20 * time.Millisecond),
   116  		timeIncr:    960,
   117  		context:     context.Background(),
   118  		packet:      packet,
   119  		ssrc:        ssrc,
   120  		conn:        conn,
   121  		recvBuf:     make([]byte, 1400),
   122  		recvOpus:    make([]byte, 1400),
   123  		recvPacket:  &Packet{},
   124  	}, nil
   125  }
   126  
   127  // ResetFrequency resets the internal frequency ticker as well as the timestamp
   128  // incremental number. For more information, refer to
   129  // https://tools.ietf.org/html/rfc7587#section-4.2.
   130  //
   131  // frameDuration controls the Opus frame duration used by the UDP connection to
   132  // control the frequency of packets sent over. 20ms is the default by libopus.
   133  //
   134  // timestampIncr is the timestamp to increment for each Opus packet. This should
   135  // be consistent with th given frameDuration. For the right combination, refer
   136  // to the Valid Parameters section below.
   137  //
   138  // Valid Parameters
   139  //
   140  // The following table lists the recommended parameters for these variables.
   141  //
   142  //    +---------+-----+-----+------+------+
   143  //    |   Mode  |  10 |  20 |  40  |  60  |
   144  //    +---------+-----+-----+------+------+
   145  //    | ts incr | 480 | 960 | 1920 | 2880 |
   146  //    +---------+-----+-----+------+------+
   147  //
   148  // Note that audio mode is omitted, as it is not recommended. For the full
   149  // table, refer to the IETF RFC7587 section 4.2 link above.
   150  func (c *Connection) ResetFrequency(frameDuration time.Duration, timeIncr uint32) {
   151  	c.frequency.Stop()
   152  	c.frequency = time.NewTicker(frameDuration)
   153  	c.timeIncr = timeIncr
   154  }
   155  
   156  // UseSecret uses the given secret. This method is not thread-safe, so it should
   157  // only be used right after initialization.
   158  func (c *Connection) UseSecret(secret [32]byte) {
   159  	c.secret = secret
   160  }
   161  
   162  // UseContext lets the connection use the given context for its Write method.
   163  // WriteCtx will override this context.
   164  func (c *Connection) UseContext(ctx context.Context) error {
   165  	return c.useContext(ctx)
   166  }
   167  
   168  func (c *Connection) useContext(ctx context.Context) error {
   169  	if c.context == ctx {
   170  		return nil
   171  	}
   172  
   173  	c.context = ctx
   174  
   175  	if deadline, ok := c.context.Deadline(); ok {
   176  		return c.conn.SetWriteDeadline(deadline)
   177  	} else {
   178  		return c.conn.SetWriteDeadline(time.Time{})
   179  	}
   180  }
   181  
   182  func (c *Connection) Close() error {
   183  	c.frequency.Stop()
   184  	return c.conn.Close()
   185  }
   186  
   187  // Write sends bytes into the voice UDP connection using the preset context.
   188  func (c *Connection) Write(b []byte) (int, error) {
   189  	return c.write(b)
   190  }
   191  
   192  // WriteCtx sends bytes into the voice UDP connection with a timeout using the
   193  // given context. It ignores the context inside the connection, but will restore
   194  // the deadline after this call is done.
   195  func (c *Connection) WriteCtx(ctx context.Context, b []byte) (int, error) {
   196  	oldCtx := c.context
   197  
   198  	c.useContext(ctx)
   199  	defer c.useContext(oldCtx)
   200  
   201  	return c.write(b)
   202  }
   203  
   204  func (c *Connection) write(b []byte) (int, error) {
   205  	// Write a new sequence.
   206  	binary.BigEndian.PutUint16(c.packet[2:4], c.sequence)
   207  	c.sequence++
   208  
   209  	binary.BigEndian.PutUint32(c.packet[4:8], c.timestamp)
   210  	c.timestamp += c.timeIncr
   211  
   212  	copy(c.nonce[:], c.packet[:])
   213  
   214  	toSend := secretbox.Seal(c.packet[:], b, &c.nonce, &c.secret)
   215  
   216  	select {
   217  	case <-c.frequency.C:
   218  
   219  	case <-c.context.Done():
   220  		return 0, c.context.Err()
   221  	}
   222  
   223  	n, err := c.conn.Write(toSend)
   224  	if err != nil {
   225  		return n, errors.Wrap(err, "failed to write to UDP connection")
   226  	}
   227  
   228  	// We're not really returning everything, since we're "sealing" the bytes.
   229  	return len(b), nil
   230  }
   231  
   232  // ReadPacket reads the UDP connection and returns a packet if successful. This
   233  // packet is not thread-safe to use, as it shares recvBuf's buffer. Byte slices
   234  // inside it must be copied or used before the next call to ReadPacket happens.
   235  func (c *Connection) ReadPacket() (*Packet, error) {
   236  	for {
   237  		rlen, err := c.conn.Read(c.recvBuf)
   238  
   239  		if err != nil {
   240  			return nil, err
   241  		}
   242  
   243  		if rlen < packetHeaderSize || (c.recvBuf[0] != 0x80 && c.recvBuf[0] != 0x90) {
   244  			continue
   245  		}
   246  
   247  		c.recvPacket.VersionFlags = c.recvBuf[0]
   248  		c.recvPacket.Type = c.recvBuf[1]
   249  		c.recvPacket.Sequence = binary.BigEndian.Uint16(c.recvBuf[2:4])
   250  		c.recvPacket.Timestamp = binary.BigEndian.Uint32(c.recvBuf[4:8])
   251  		c.recvPacket.SSRC = binary.BigEndian.Uint32(c.recvBuf[8:12])
   252  
   253  		copy(c.recvNonce[:], c.recvBuf[0:packetHeaderSize])
   254  
   255  		var ok bool
   256  
   257  		c.recvPacket.Opus, ok = secretbox.Open(
   258  			c.recvOpus[:0], c.recvBuf[packetHeaderSize:rlen], &c.recvNonce, &c.secret)
   259  		if !ok {
   260  			return nil, ErrDecryptionFailed
   261  		}
   262  
   263  		// Partial structure of the RTP header for reference
   264  		//
   265  		//     0                   1                   2                   3
   266  		//     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   267  		//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   268  		//    |V=2|P|X|  CC   |M|     PT      |       sequence number         |
   269  		//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   270  		//    |                           timestamp                           |
   271  		//    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   272  		//
   273  		// References
   274  		//
   275  		//    https://tools.ietf.org/html/rfc3550#section-5.1
   276  		//
   277  
   278  		// We first check VersionFlags (8-bit) for whether or not the 4th bit
   279  		// (extension) is set. The value of 0x10 is 0b00010000. RFC3550 section
   280  		// 5.1 explains the extension bit as:
   281  		//
   282  		//    If the extension bit is set, the fixed header MUST be followed by
   283  		//    exactly one header extension, with a format defined in Section
   284  		//    5.3.1.
   285  		//
   286  		isExtension := c.recvPacket.VersionFlags&0x10 == 0x10
   287  
   288  		// We then check for whether or not the marker bit (9th bit) is set. The
   289  		// 9th bit is carried over to the second byte (Type), so we check its
   290  		// presence with 0x80, or 0b10000000. RFC3550 section 5.1 explains the
   291  		// marker bit as:
   292  		//
   293  		//     The interpretation of the marker is defined by a profile.  It is
   294  		//     intended to allow significant events such as frame boundaries to
   295  		//     be marked in the packet stream.  A profile MAY define additional
   296  		//     marker bits or specify that there is no marker bit by changing
   297  		//     the number of bits in the payload type field (see Section 5.3).
   298  		//
   299  		// RFC3350 section 12.1 also writes:
   300  		//
   301  		//    When the RTCP packet type field is compared to the corresponding
   302  		//    octet of the RTP header, this range corresponds to the marker bit
   303  		//    being 1 (which it usually is not in data packets) and to the high
   304  		//    bit of the standard payload type field being 1 (since the static
   305  		//    payload types are typically defined in the low half).
   306  		//
   307  		// This implies that, when the marker bit is 1, the received packet is
   308  		// an RTCP packet and NOT an RTP packet; therefore, we must ignore the
   309  		// unknown sections, so we do a (NOT isMarker) check below.
   310  		isMarker := c.recvPacket.Type&0x80 != 0x0
   311  
   312  		if isExtension && !isMarker {
   313  			extLen := binary.BigEndian.Uint16(c.recvPacket.Opus[2:4])
   314  			shift := 4 + 4*int(extLen)
   315  
   316  			if len(c.recvPacket.Opus) > shift {
   317  				c.recvPacket.Opus = c.recvPacket.Opus[shift:]
   318  			}
   319  		}
   320  
   321  		return c.recvPacket, nil
   322  	}
   323  }