code: func (s *UDPSession) output(buf []byte) {
  	var ecc [][]byte
  
  	// 1. FEC encoding
  	if s.fecEncoder != nil {
  		ecc = s.fecEncoder.encode(buf)
  	}
  
  	// 2&3. crc32 & encryption
  	if s.block != nil {
  		s.nonce.Fill(buf[:nonceSize])
  		checksum := crc32.ChecksumIEEE(buf[cryptHeaderSize:])
  		binary.LittleEndian.PutUint32(buf[nonceSize:], checksum)
  		s.block.Encrypt(buf, buf)
  
  		for k := range ecc {
  			s.nonce.Fill(ecc[k][:nonceSize])
  			checksum := crc32.ChecksumIEEE(ecc[k][cryptHeaderSize:])
  			binary.LittleEndian.PutUint32(ecc[k][nonceSize:], checksum)
  			s.block.Encrypt(ecc[k], ecc[k])
  		}
  	}
  
  	// 4. WriteTo kernel
  	nbytes := 0
  	npkts := 0
  	for i := 0; i < s.dup+1; i++ {
  		if n, err := s.conn.WriteTo(buf, s.remote); err == nil {
  			nbytes += n
  			npkts++
  		} else {
  			s.notifyWriteError(err)
  		}
  	}
  
  	for k := range ecc {
  		if n, err := s.conn.WriteTo(ecc[k], s.remote); err == nil {
  			nbytes += n
  			npkts++
  		} else {
  			s.notifyWriteError(err)
  		}
  	}
  	atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
  	atomic.AddUint64(&DefaultSnmp.OutBytes, uint64(nbytes))
  }
docs: // post-processing for sending a packet from kcp core
  // steps:
  // 1. FEC packet generation
  // 2. CRC32 integrity
  // 3. Encryption
  // 4. WriteTo kernel