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 }