Add tls obfs outbound
This commit is contained in:
@@ -192,10 +192,10 @@ impl OutboundManager {
|
||||
"http" => Box::new(obfs::HttpObfsStreamHandler::new(
|
||||
settings.path.as_bytes(),
|
||||
settings.host.as_bytes(),
|
||||
)),
|
||||
"tls" => unimplemented!(
|
||||
"Box::new(obfs::TlsObfsStreamHandler::new(settings.host))"
|
||||
),
|
||||
)) as _,
|
||||
"tls" => {
|
||||
Box::new(obfs::TlsObfsStreamHandler::new(settings.host.as_bytes())) as _
|
||||
}
|
||||
method => {
|
||||
return Err(anyhow!(
|
||||
"invalid [{}] outbound settings: unknown obfs method {}",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is generated by rust-protobuf 3.2.0. Do not edit
|
||||
// .proto file is parsed by protoc 3.21.12
|
||||
// .proto file is parsed by protoc 22.2
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is generated by rust-protobuf 3.2.0. Do not edit
|
||||
// .proto file is parsed by protoc 3.21.12
|
||||
// .proto file is parsed by protoc 22.2
|
||||
// @generated
|
||||
|
||||
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||
|
||||
@@ -7,6 +7,7 @@ use base64::prelude::*;
|
||||
use memchr::memmem;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use tokio::io::ReadBuf;
|
||||
use tokio_util::io::poll_write_buf;
|
||||
|
||||
use crate::proxy::*;
|
||||
|
||||
@@ -16,17 +17,22 @@ pub struct Handler {
|
||||
req_line: Arc<[u8]>,
|
||||
}
|
||||
|
||||
enum StreamState {
|
||||
Initial { req_line: Arc<[u8]> },
|
||||
WritingRequest { req: Cursor<Vec<u8>> },
|
||||
enum ReadState {
|
||||
AwaitingResponse { res_buf: Vec<u8> },
|
||||
ConsumingResponse { res: Cursor<Vec<u8>> },
|
||||
Transfer,
|
||||
}
|
||||
|
||||
enum WriteState {
|
||||
Initial { req_line: Arc<[u8]> },
|
||||
WritingRequest(Cursor<Vec<u8>>),
|
||||
Transfer,
|
||||
}
|
||||
|
||||
struct Stream {
|
||||
stream: AnyStream,
|
||||
state: StreamState,
|
||||
read_state: ReadState,
|
||||
write_state: WriteState,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
@@ -61,16 +67,21 @@ impl OutboundStreamHandler for Handler {
|
||||
_sess: &'a Session,
|
||||
stream: Option<AnyStream>,
|
||||
) -> io::Result<AnyStream> {
|
||||
let Some(stream) = stream else {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "invalid tls input"));
|
||||
};
|
||||
let stream = stream.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid input"))?;
|
||||
|
||||
Ok(Box::new(Stream {
|
||||
Ok(Box::new(Stream::new(self.req_line.clone(), stream)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
fn new(req_line: Arc<[u8]>, stream: AnyStream) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
state: StreamState::Initial {
|
||||
req_line: self.req_line.clone(),
|
||||
read_state: ReadState::AwaitingResponse {
|
||||
res_buf: Vec::with_capacity(RESPONSE_BUFFER_SIZE),
|
||||
},
|
||||
}))
|
||||
write_state: WriteState::Initial { req_line },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,23 +92,11 @@ impl AsyncRead for Stream {
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
loop {
|
||||
let Self { state, stream } = &mut *self;
|
||||
match state {
|
||||
StreamState::Initial { req_line } => {
|
||||
// Client expects a response before sending a request.
|
||||
// Generate a request with content length 0.
|
||||
let req = generate_http_request(req_line, &[]);
|
||||
*state = StreamState::WritingRequest {
|
||||
req: Cursor::new(req),
|
||||
};
|
||||
continue;
|
||||
}
|
||||
StreamState::WritingRequest { .. } => {
|
||||
// Finish writing the request as much as possible.
|
||||
ready!(self.as_mut().poll_flush(cx))?;
|
||||
continue;
|
||||
}
|
||||
StreamState::AwaitingResponse { res_buf } => {
|
||||
let Self {
|
||||
read_state, stream, ..
|
||||
} = &mut *self;
|
||||
match read_state {
|
||||
ReadState::AwaitingResponse { res_buf } => {
|
||||
if res_buf.len() >= RESPONSE_BUFFER_SIZE {
|
||||
// The response may be too large. This should not happen in obfs.
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
@@ -105,7 +104,8 @@ impl AsyncRead for Stream {
|
||||
"obfs response too large",
|
||||
)));
|
||||
}
|
||||
let read_len = ready!(tokio_util::io::poll_read_buf(Pin::new(stream), cx, res_buf))?;
|
||||
let read_len =
|
||||
ready!(tokio_util::io::poll_read_buf(Pin::new(stream), cx, res_buf))?;
|
||||
if read_len == 0 {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
@@ -118,20 +118,19 @@ impl AsyncRead for Stream {
|
||||
};
|
||||
let mut payload = Cursor::new(std::mem::take(res_buf));
|
||||
payload.set_position(req_body_pos as u64);
|
||||
*state = StreamState::ConsumingResponse { res: payload };
|
||||
continue;
|
||||
*read_state = ReadState::ConsumingResponse { res: payload };
|
||||
}
|
||||
StreamState::ConsumingResponse { res } => {
|
||||
ReadState::ConsumingResponse { res } => {
|
||||
let remaining = &res.get_ref()[res.position() as usize..];
|
||||
let to_write = remaining.len().min(buf.remaining());
|
||||
buf.put_slice(&remaining[..to_write]);
|
||||
res.set_position(res.position() + to_write as u64);
|
||||
if res.position() as usize == res.get_ref().len() {
|
||||
*state = StreamState::Transfer;
|
||||
*read_state = ReadState::Transfer;
|
||||
}
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
StreamState::Transfer => return Pin::new(stream).poll_read(cx, buf),
|
||||
ReadState::Transfer => return Pin::new(stream).poll_read(cx, buf),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -143,53 +142,47 @@ impl AsyncWrite for Stream {
|
||||
cx: &mut Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
let Self { state, stream } = &mut *self;
|
||||
let Self {
|
||||
write_state,
|
||||
stream,
|
||||
..
|
||||
} = &mut *self;
|
||||
loop {
|
||||
match state {
|
||||
StreamState::Initial { req_line } => {
|
||||
match write_state {
|
||||
WriteState::Initial { req_line } => {
|
||||
let req = generate_http_request(req_line, buf);
|
||||
*state = StreamState::WritingRequest {
|
||||
req: Cursor::new(req),
|
||||
};
|
||||
|
||||
continue;
|
||||
*write_state = WriteState::WritingRequest(Cursor::new(req));
|
||||
}
|
||||
StreamState::WritingRequest { req } => {
|
||||
ready!(tokio_util::io::poll_write_buf(Pin::new(stream), cx, req))?;
|
||||
WriteState::WritingRequest(req) => {
|
||||
ready!(poll_write_buf(Pin::new(stream), cx, req))?;
|
||||
if req.position() as usize == req.get_ref().len() {
|
||||
*state = StreamState::AwaitingResponse {
|
||||
res_buf: Vec::with_capacity(RESPONSE_BUFFER_SIZE),
|
||||
};
|
||||
*write_state = WriteState::Transfer;
|
||||
return Poll::Ready(Ok(buf.len()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
StreamState::AwaitingResponse { .. } => break,
|
||||
StreamState::ConsumingResponse { .. } => break,
|
||||
StreamState::Transfer => break,
|
||||
WriteState::Transfer => break,
|
||||
}
|
||||
}
|
||||
Pin::new(&mut *stream).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
let Self { state, stream } = &mut *self;
|
||||
let Self {
|
||||
write_state,
|
||||
stream,
|
||||
..
|
||||
} = &mut *self;
|
||||
loop {
|
||||
match state {
|
||||
StreamState::Initial { .. } => return Poll::Ready(Ok(())),
|
||||
StreamState::WritingRequest { req } => {
|
||||
ready!(tokio_util::io::poll_write_buf(Pin::new(stream), cx, req))?;
|
||||
match write_state {
|
||||
WriteState::Initial { .. } => return Poll::Ready(Ok(())),
|
||||
WriteState::WritingRequest(req) => {
|
||||
ready!(poll_write_buf(Pin::new(stream), cx, req))?;
|
||||
if req.position() as usize == req.get_ref().len() {
|
||||
*state = StreamState::AwaitingResponse {
|
||||
res_buf: Vec::with_capacity(RESPONSE_BUFFER_SIZE),
|
||||
};
|
||||
*write_state = WriteState::Transfer;
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
StreamState::AwaitingResponse { .. } => break,
|
||||
StreamState::ConsumingResponse { .. } => break,
|
||||
StreamState::Transfer => break,
|
||||
WriteState::Transfer => break,
|
||||
}
|
||||
}
|
||||
Pin::new(&mut *stream).poll_flush(cx)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod http;
|
||||
pub mod tls;
|
||||
|
||||
pub use self::http::Handler as HttpObfsStreamHandler;
|
||||
pub use self::tls::Handler as TlsObfsStreamHandler;
|
||||
|
||||
327
leaf/src/proxy/obfs/tls.rs
Normal file
327
leaf/src/proxy/obfs/tls.rs
Normal file
@@ -0,0 +1,327 @@
|
||||
use std::io::{Cursor, IoSlice};
|
||||
use std::mem::MaybeUninit;
|
||||
use std::pin::Pin;
|
||||
use std::task::{ready, Context, Poll};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use rand::{thread_rng, RngCore};
|
||||
use tokio::io::ReadBuf;
|
||||
use tokio_util::io::poll_write_buf;
|
||||
|
||||
use crate::proxy::*;
|
||||
|
||||
mod packet;
|
||||
mod template;
|
||||
|
||||
const RESPONSE_HANDSHAKE_SIZE: usize = 96 /* server hello */
|
||||
+ 6 /* change cipher spec */ + 3 /* encrypted handshake */ ;
|
||||
const LEN_HEADER_BUFFER_SIZE: usize = 3;
|
||||
const LEN_BUFFER_SIZE: usize = 5;
|
||||
const MAX_TLS_CHUNK_SIZE: u16 = 16 * 1024;
|
||||
|
||||
pub struct Handler {
|
||||
host: Arc<[u8]>,
|
||||
}
|
||||
|
||||
enum ReadState {
|
||||
AwaitingResponse { remaining_read_len: usize },
|
||||
HeaderIncomplete(Cursor<[u8; LEN_BUFFER_SIZE]>),
|
||||
HeaderComplete { chunk_remaining: usize },
|
||||
}
|
||||
|
||||
enum WriteState {
|
||||
Initial {
|
||||
host: Arc<[u8]>,
|
||||
},
|
||||
WritingRequest(Cursor<Vec<u8>>),
|
||||
WritingHeader {
|
||||
payload_len: u16,
|
||||
write_offset: usize,
|
||||
},
|
||||
WritingPayload {
|
||||
chunk_remaining: usize,
|
||||
},
|
||||
}
|
||||
|
||||
struct Stream {
|
||||
stream: AnyStream,
|
||||
read_state: ReadState,
|
||||
write_state: WriteState,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub fn new(host: &[u8]) -> Self {
|
||||
Self { host: host.into() }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OutboundStreamHandler for Handler {
|
||||
fn connect_addr(&self) -> OutboundConnect {
|
||||
OutboundConnect::Next
|
||||
}
|
||||
|
||||
async fn handle<'a>(
|
||||
&'a self,
|
||||
_sess: &'a Session,
|
||||
stream: Option<AnyStream>,
|
||||
) -> io::Result<AnyStream> {
|
||||
let stream = stream.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid input"))?;
|
||||
|
||||
Ok(Box::new(Stream::new(self.host.clone(), stream)))
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadState {
|
||||
fn merge_chunks(&mut self, data: &mut [u8]) -> usize {
|
||||
let mut write_index = 0;
|
||||
let mut read_index = 0;
|
||||
while read_index < data.len() {
|
||||
let mut part = &mut data[read_index..];
|
||||
match self {
|
||||
ReadState::AwaitingResponse { remaining_read_len } => {
|
||||
let len = part.len().min(*remaining_read_len);
|
||||
read_index += len;
|
||||
*remaining_read_len -= len;
|
||||
if *remaining_read_len == 0 {
|
||||
let mut cursor = Cursor::new([0; LEN_BUFFER_SIZE]);
|
||||
cursor.set_position(LEN_HEADER_BUFFER_SIZE as u64);
|
||||
*self = ReadState::HeaderIncomplete(cursor);
|
||||
}
|
||||
}
|
||||
ReadState::HeaderIncomplete(header_buf) => {
|
||||
let pos = header_buf.position() as usize;
|
||||
let len = part.len().min(LEN_BUFFER_SIZE - pos);
|
||||
part = &mut part[..len];
|
||||
read_index += len;
|
||||
header_buf.get_mut()[pos..][..len].copy_from_slice(part);
|
||||
header_buf.set_position(pos as u64 + len as u64);
|
||||
if header_buf.position() as usize == LEN_BUFFER_SIZE {
|
||||
let len = u16::from_be_bytes(
|
||||
header_buf.get_ref()[LEN_HEADER_BUFFER_SIZE..]
|
||||
.try_into()
|
||||
.expect("obfs tls packet len size != 2"),
|
||||
) as usize;
|
||||
*self = ReadState::HeaderComplete {
|
||||
chunk_remaining: len,
|
||||
};
|
||||
}
|
||||
}
|
||||
ReadState::HeaderComplete { chunk_remaining } => {
|
||||
let len = part.len().min(*chunk_remaining);
|
||||
*chunk_remaining -= len;
|
||||
data.copy_within(read_index..(read_index + len), write_index);
|
||||
write_index += len;
|
||||
read_index += len;
|
||||
if *chunk_remaining == 0 {
|
||||
*self = ReadState::HeaderIncomplete(Default::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
write_index
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
fn new(host: Arc<[u8]>, stream: AnyStream) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
read_state: ReadState::AwaitingResponse {
|
||||
remaining_read_len: RESPONSE_HANDSHAKE_SIZE,
|
||||
},
|
||||
write_state: WriteState::Initial { host },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for Stream {
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
let mut total_read_len = 0;
|
||||
let mut new_inited = 0;
|
||||
while total_read_len == 0 {
|
||||
let Self {
|
||||
read_state, stream, ..
|
||||
} = &mut *self;
|
||||
total_read_len = if let ReadState::AwaitingResponse { remaining_read_len } = read_state
|
||||
{
|
||||
let mut header_buf = [MaybeUninit::<u8>::uninit(); RESPONSE_HANDSHAKE_SIZE];
|
||||
let mut read_buf = ReadBuf::uninit(&mut header_buf[..*remaining_read_len]);
|
||||
ready!(Pin::new(stream).poll_read(cx, &mut read_buf))?;
|
||||
if read_buf.filled().is_empty() {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"obfs tls server hello eof",
|
||||
)));
|
||||
}
|
||||
read_state.merge_chunks(read_buf.filled_mut()); // Data should not contain any chunks.
|
||||
continue;
|
||||
} else {
|
||||
// Safety: the uninitialized part is managed by the new ReadBuf.
|
||||
let mut read_buf = ReadBuf::uninit(unsafe { buf.unfilled_mut() });
|
||||
ready!(Pin::new(stream).poll_read(cx, &mut read_buf))?;
|
||||
if read_buf.filled().is_empty() {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"obfs tls eof",
|
||||
)));
|
||||
}
|
||||
new_inited = new_inited.max(read_buf.initialized().len());
|
||||
read_state.merge_chunks(read_buf.filled_mut())
|
||||
};
|
||||
}
|
||||
// Safety: new_inited bytes is initialized by an inner read_buf.
|
||||
unsafe {
|
||||
buf.assume_init(new_inited);
|
||||
}
|
||||
buf.advance(total_read_len);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for Stream {
|
||||
fn poll_write(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
mut buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
let mut payload_written = 0;
|
||||
while payload_written == 0 {
|
||||
let Self {
|
||||
stream,
|
||||
write_state,
|
||||
..
|
||||
} = &mut *self;
|
||||
match write_state {
|
||||
WriteState::Initial { host } => {
|
||||
buf = &buf[..buf.len().min(MAX_TLS_CHUNK_SIZE as usize)];
|
||||
let req = generate_tls_request(&host, buf);
|
||||
*write_state = WriteState::WritingRequest(Cursor::new(req));
|
||||
}
|
||||
WriteState::WritingRequest(req) => {
|
||||
ready!(poll_write_buf(Pin::new(stream), cx, req))?;
|
||||
if req.position() as usize == req.get_ref().len() {
|
||||
*write_state = WriteState::WritingPayload { chunk_remaining: 0 };
|
||||
return Poll::Ready(Ok(buf.len()));
|
||||
}
|
||||
}
|
||||
WriteState::WritingHeader {
|
||||
payload_len,
|
||||
write_offset,
|
||||
} => {
|
||||
let header = generate_header(*payload_len);
|
||||
let buf_len = buf.len().min(*payload_len as usize);
|
||||
let iov = [
|
||||
IoSlice::new(&header[*write_offset..]),
|
||||
IoSlice::new(&buf[..buf_len]),
|
||||
];
|
||||
let write_len = ready!(Pin::new(stream).poll_write_vectored(cx, &iov))?;
|
||||
*write_offset += write_len;
|
||||
if let Some(chunk_written) = write_offset.checked_sub(LEN_BUFFER_SIZE) {
|
||||
*write_state = WriteState::WritingPayload {
|
||||
chunk_remaining: *payload_len as usize - chunk_written,
|
||||
};
|
||||
payload_written += chunk_written;
|
||||
buf = &buf[chunk_written..];
|
||||
}
|
||||
}
|
||||
WriteState::WritingPayload { chunk_remaining: 0 } => {
|
||||
*write_state = WriteState::WritingHeader {
|
||||
payload_len: buf.len().try_into().unwrap_or(MAX_TLS_CHUNK_SIZE),
|
||||
write_offset: 0,
|
||||
};
|
||||
}
|
||||
WriteState::WritingPayload { chunk_remaining } => {
|
||||
let buf_len = buf.len().min(*chunk_remaining);
|
||||
let write_len = ready!(Pin::new(stream).poll_write(cx, &buf[..buf_len]))?;
|
||||
*chunk_remaining -= write_len;
|
||||
payload_written += write_len;
|
||||
buf = &buf[write_len..];
|
||||
}
|
||||
}
|
||||
}
|
||||
Poll::Ready(Ok(payload_written))
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
let Self {
|
||||
stream,
|
||||
write_state,
|
||||
..
|
||||
} = &mut *self;
|
||||
loop {
|
||||
match write_state {
|
||||
WriteState::Initial { .. } => return Poll::Ready(Ok(())),
|
||||
WriteState::WritingRequest(req) => {
|
||||
ready!(poll_write_buf(Pin::new(stream), cx, req))?;
|
||||
if req.position() as usize == req.get_ref().len() {
|
||||
*write_state = WriteState::WritingPayload { chunk_remaining: 0 };
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Pin::new(&mut *stream).poll_flush(cx)
|
||||
}
|
||||
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut self.stream).poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_tls_request(host: &[u8], payload: &[u8]) -> Vec<u8> {
|
||||
use std::mem::{size_of_val, transmute};
|
||||
use std::time::SystemTime;
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let mut hello = template::CLIENT_HELLO;
|
||||
let mut server_name = template::EXT_SERVER_NAME;
|
||||
let mut ticket = template::EXT_SESSION_TICKET;
|
||||
let other = template::EXT_OTHERS;
|
||||
let total_len = payload.len()
|
||||
+ size_of_val(&hello)
|
||||
+ size_of_val(&server_name)
|
||||
+ host.len()
|
||||
+ size_of_val(&ticket)
|
||||
+ size_of_val(&other);
|
||||
|
||||
hello.0.len = (total_len as u16 - 5).to_be();
|
||||
hello.0.handshake_len_2 = (total_len as u16 - 9).to_be();
|
||||
hello.0.random_unix_time = (SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs() as u32)
|
||||
.to_be();
|
||||
rng.fill_bytes(&mut hello.0.random_bytes);
|
||||
rng.fill_bytes(&mut hello.0.session_id);
|
||||
hello.0.ext_len = ((total_len - size_of_val(&hello)) as u16).to_be();
|
||||
ticket.0.session_ticket_ext_len = (payload.len() as u16).to_be();
|
||||
server_name.0.ext_len = (host.len() as u16 + 3 + 2).to_be();
|
||||
server_name.0.server_name_list_len = (host.len() as u16 + 3).to_be();
|
||||
server_name.0.server_name_len = host.len() as u16;
|
||||
|
||||
let mut req = Vec::with_capacity(total_len);
|
||||
unsafe {
|
||||
req.extend_from_slice(&transmute::<_, [u8; 138]>(hello));
|
||||
req.extend_from_slice(&transmute::<_, [u8; 4]>(ticket));
|
||||
req.extend_from_slice(payload);
|
||||
req.extend_from_slice(&transmute::<_, [u8; 9]>(server_name));
|
||||
req.extend_from_slice(host);
|
||||
req.extend_from_slice(&transmute::<_, [u8; 66]>(other));
|
||||
}
|
||||
req
|
||||
}
|
||||
|
||||
fn generate_header(payload_len: u16) -> [u8; LEN_BUFFER_SIZE] {
|
||||
let mut tls_data_header = [
|
||||
0x17, 0x03, 0x03, /* 2 bytes of len goes here */ 0x00, 0x00,
|
||||
];
|
||||
tls_data_header[3] = payload_len.to_be_bytes()[0];
|
||||
tls_data_header[4] = payload_len.to_be_bytes()[1];
|
||||
tls_data_header
|
||||
}
|
||||
82
leaf/src/proxy/obfs/tls/packet.rs
Normal file
82
leaf/src/proxy/obfs/tls/packet.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C, align(1))]
|
||||
pub struct tls_client_hello(pub tls_client_hello_Inner);
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C, packed)]
|
||||
pub struct tls_client_hello_Inner {
|
||||
pub content_type: u8,
|
||||
pub version: u16,
|
||||
pub len: u16,
|
||||
pub handshake_type: u8,
|
||||
pub handshake_len_1: u8,
|
||||
pub handshake_len_2: u16,
|
||||
pub handshake_version: u16,
|
||||
pub random_unix_time: u32,
|
||||
pub random_bytes: [u8; 28],
|
||||
pub session_id_len: u8,
|
||||
pub session_id: [u8; 32],
|
||||
pub cipher_suites_len: u16,
|
||||
pub cipher_suites: [u8; 56],
|
||||
pub comp_methods_len: u8,
|
||||
pub comp_methods: [u8; 1],
|
||||
pub ext_len: u16,
|
||||
}
|
||||
#[allow(dead_code, non_upper_case_globals)]
|
||||
const tls_client_hello_PADDING: usize =
|
||||
::std::mem::size_of::<tls_client_hello>() - ::std::mem::size_of::<tls_client_hello_Inner>();
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C, align(1))]
|
||||
pub struct tls_ext_server_name(pub tls_ext_server_name_Inner);
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C, packed)]
|
||||
pub struct tls_ext_server_name_Inner {
|
||||
pub ext_type: u16,
|
||||
pub ext_len: u16,
|
||||
pub server_name_list_len: u16,
|
||||
pub server_name_type: u8,
|
||||
pub server_name_len: u16,
|
||||
}
|
||||
#[allow(dead_code, non_upper_case_globals)]
|
||||
const tls_ext_server_name_PADDING: usize = ::std::mem::size_of::<tls_ext_server_name>()
|
||||
- ::std::mem::size_of::<tls_ext_server_name_Inner>();
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C, align(1))]
|
||||
pub struct tls_ext_session_ticket(pub tls_ext_session_ticket_Inner);
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C, packed)]
|
||||
pub struct tls_ext_session_ticket_Inner {
|
||||
pub session_ticket_type: u16,
|
||||
pub session_ticket_ext_len: u16,
|
||||
}
|
||||
#[allow(dead_code, non_upper_case_globals)]
|
||||
const tls_ext_session_ticket_PADDING: usize = ::std::mem::size_of::<tls_ext_session_ticket>()
|
||||
- ::std::mem::size_of::<tls_ext_session_ticket_Inner>();
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C, align(1))]
|
||||
pub struct tls_ext_others(pub tls_ext_others_Inner);
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C, packed)]
|
||||
pub struct tls_ext_others_Inner {
|
||||
pub ec_point_formats_ext_type: u16,
|
||||
pub ec_point_formats_ext_len: u16,
|
||||
pub ec_point_formats_len: u8,
|
||||
pub ec_point_formats: [u8; 3],
|
||||
pub elliptic_curves_type: u16,
|
||||
pub elliptic_curves_ext_len: u16,
|
||||
pub elliptic_curves_len: u16,
|
||||
pub elliptic_curves: [u8; 8],
|
||||
pub sig_algos_type: u16,
|
||||
pub sig_algos_ext_len: u16,
|
||||
pub sig_algos_len: u16,
|
||||
pub sig_algos: [u8; 30],
|
||||
pub encrypt_then_mac_type: u16,
|
||||
pub encrypt_then_mac_ext_len: u16,
|
||||
pub extended_master_secret_type: u16,
|
||||
pub extended_master_secret_ext_len: u16,
|
||||
}
|
||||
#[allow(dead_code, non_upper_case_globals)]
|
||||
const tls_ext_others_PADDING: usize =
|
||||
::std::mem::size_of::<tls_ext_others>() - ::std::mem::size_of::<tls_ext_others_Inner>();
|
||||
75
leaf/src/proxy/obfs/tls/template.rs
Normal file
75
leaf/src/proxy/obfs/tls/template.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use super::packet;
|
||||
|
||||
pub const CLIENT_HELLO: packet::tls_client_hello =
|
||||
packet::tls_client_hello(packet::tls_client_hello_Inner {
|
||||
content_type: 0x16,
|
||||
version: 0x0301u16.to_be(),
|
||||
len: 0,
|
||||
|
||||
handshake_type: 1,
|
||||
handshake_len_1: 0,
|
||||
handshake_len_2: 0,
|
||||
handshake_version: 0x0303u16.to_be(),
|
||||
|
||||
random_unix_time: 0,
|
||||
random_bytes: [0; 28],
|
||||
|
||||
session_id_len: 32,
|
||||
session_id: [0; 32],
|
||||
|
||||
cipher_suites_len: 56u16.to_be(),
|
||||
cipher_suites: [
|
||||
0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b,
|
||||
0xc0, 0x2f, 0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27,
|
||||
0x00, 0x67, 0xc0, 0x0a, 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33,
|
||||
0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d, 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff,
|
||||
],
|
||||
|
||||
comp_methods_len: 1,
|
||||
comp_methods: [0],
|
||||
|
||||
ext_len: 0,
|
||||
});
|
||||
|
||||
pub const EXT_SERVER_NAME: packet::tls_ext_server_name =
|
||||
packet::tls_ext_server_name(packet::tls_ext_server_name_Inner {
|
||||
ext_type: 0,
|
||||
ext_len: 0,
|
||||
server_name_list_len: 0,
|
||||
server_name_type: 0,
|
||||
server_name_len: 0,
|
||||
});
|
||||
|
||||
pub const EXT_SESSION_TICKET: packet::tls_ext_session_ticket =
|
||||
packet::tls_ext_session_ticket(packet::tls_ext_session_ticket_Inner {
|
||||
session_ticket_type: 0x0023u16.to_be(),
|
||||
session_ticket_ext_len: 0,
|
||||
});
|
||||
|
||||
pub const EXT_OTHERS: packet::tls_ext_others =
|
||||
packet::tls_ext_others(packet::tls_ext_others_Inner {
|
||||
ec_point_formats_ext_type: 0x000Bu16.to_be(),
|
||||
ec_point_formats_ext_len: 4u16.to_be(),
|
||||
ec_point_formats_len: 3,
|
||||
ec_point_formats: [0x01, 0x00, 0x02],
|
||||
|
||||
elliptic_curves_type: 0x000au16.to_be(),
|
||||
elliptic_curves_ext_len: 10u16.to_be(),
|
||||
elliptic_curves_len: 8u16.to_be(),
|
||||
elliptic_curves: [0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18],
|
||||
|
||||
sig_algos_type: 0x000du16.to_be(),
|
||||
sig_algos_ext_len: 32u16.to_be(),
|
||||
sig_algos_len: 30u16.to_be(),
|
||||
sig_algos: [
|
||||
0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01,
|
||||
0x04, 0x02, 0x04, 0x03, 0x03, 0x01, 0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02,
|
||||
0x02, 0x03,
|
||||
],
|
||||
|
||||
encrypt_then_mac_type: 0x0016u16.to_be(),
|
||||
encrypt_then_mac_ext_len: 0,
|
||||
|
||||
extended_master_secret_type: 0x0017u16.to_be(),
|
||||
extended_master_secret_ext_len: 0,
|
||||
});
|
||||
Reference in New Issue
Block a user