Skip to content

Commit

Permalink
Merge pull request #21 from JuliaWeb/session-socket
Browse files Browse the repository at this point in the history
Allow creating a Session from a user-supplied socket/file descriptor
  • Loading branch information
JamesWrigley authored Oct 12, 2024
2 parents 4c3b5b9 + bf62604 commit 8f5aa6c
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 6 deletions.
7 changes: 6 additions & 1 deletion docs/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ Changelog](https://keepachangelog.com).

## Unreleased

### Added

- Added support for setting the file descriptor for a [`Session`](@ref) during
construction ([#21]).

### Fixed

- Improved handling of possible errors in [`Base.readdir()`](@ref).
- Improved handling of possible errors in [`Base.readdir()`](@ref) ([#20]).

## [v0.6.0] - 2024-10-11

Expand Down
2 changes: 1 addition & 1 deletion src/channel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ function Base.show(io::IO, f::Forwarder)
if !isopen(f)
print(io, Forwarder, "()")
else
if isnothing(forwarder.out)
if isnothing(f.out)
print(io, Forwarder, "($(f.localinterface):$(f.localport)$(f.remotehost):$(f.remoteport))")
else
print(io, Forwarder, "($(f.out)$(f.remotehost):$(f.remoteport))")
Expand Down
19 changes: 15 additions & 4 deletions src/session.jl
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ server.
# Arguments
- `host`: The host to connect to.
- `port=22`: The port to connect to.
- `socket=nothing`: Can be an open `TCPSocket` or `RawFD` to connect to
directly. If this is not `nothing` it will be used instead of `port`. You will
need to close the socket afterwards, the `Session` will not do it for you.
- `user=nothing`: Set the user to connect as. If unset the current
username will be used.
- `log_verbosity=nothing`: Set the log verbosity for the session.
Expand All @@ -159,6 +162,7 @@ julia> session = ssh.Session(ip"12.34.56.78", 2222)
```
"""
function Session(host::Union{AbstractString, Sockets.IPAddr}, port=22;
socket::Union{Sockets.TCPSocket, RawFD, Nothing}=nothing,
user=nothing, log_verbosity=nothing, auto_connect=true)
session_ptr = lib.ssh_new()
if session_ptr == C_NULL
Expand All @@ -173,7 +177,12 @@ function Session(host::Union{AbstractString, Sockets.IPAddr}, port=22;
# if initialization fails for some reason.
try
session.host = host_str
session.port = port

if isnothing(socket)
session.port = port
else
session.fd = socket isa RawFD ? socket : Base._fd(socket)
end

if isnothing(user)
# Explicitly initialize the user, otherwise an error will be thrown when
Expand Down Expand Up @@ -281,6 +290,7 @@ end
# Mapping from option name to the corresponding enum and C type
const SESSION_PROPERTY_OPTIONS = Dict(:host => (SSH_OPTIONS_HOST, Cstring),
:port => (SSH_OPTIONS_PORT, Cuint),
:fd => (SSH_OPTIONS_FD, Cint),
:user => (SSH_OPTIONS_USER, Cstring),
:ssh_dir => (SSH_OPTIONS_SSH_DIR, Cstring),
:known_hosts => (SSH_OPTIONS_KNOWNHOSTS, Cstring),
Expand Down Expand Up @@ -312,11 +322,13 @@ function Base.getproperty(session::Session, name::Symbol)
value = nothing
is_string = false

if name == :port
if name === :port
# The port is a special option with its own function
port = Ref{Cuint}(0)
ret = lib.ssh_options_get_port(session, port; throw=false)
value = UInt(port[])
elseif name === :fd
value = RawFD(lib.ssh_get_fd(session))
else
# All properties supported by ssh_options_get() are strings, so we know
# that this option must be a string.
Expand Down Expand Up @@ -434,9 +446,8 @@ function _wait_loop(session::Session)
readable = (poll_flags & lib.SSH_READ_PENDING) > 0
writable = (poll_flags & lib.SSH_WRITE_PENDING) > 0

fd = RawFD(lib.ssh_get_fd(session))
while !(@atomic session._waiter_stop_flag) && isopen(session)
result = @lock session _safe_poll_fd(fd, 0.1; readable, writable)
result = @lock session _safe_poll_fd(session.fd, 0.1; readable, writable)
if isnothing(result)
# This means the session's file descriptor has been closed (see the
# comments for _safe_poll_fd()).
Expand Down
25 changes: 25 additions & 0 deletions test/LibSSHTests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ end
@test session.known_hosts == "/tmp/foo"
session.gssapi_server_identity = "foo.com"
@test session.gssapi_server_identity == "foo.com"
@test session.fd == RawFD(-1)

# Test setting an initial user
ssh.Session("localhost"; user="foo", auto_connect=false) do session2
Expand All @@ -375,6 +376,24 @@ end
# And we shouldn't be able to wait on a closed session
@test_throws InvalidStateException wait(session)

# Test initializing with a socket instead of a port. We do this by setting
# up two dummy servers, the one on port 2222 is what we want to connect to
# and the one on port 2223 is the simulated jump host we have to go
# through. It has to be done this way because we only know whether setting
# the socket worked after connecting.
demo_server_with_session(2222) do server_session
demo_server_with_session(2223; verbose=false) do jump_session
# Make the jump session forward the desired server port and connect
# to it directly by its socket.
ssh.Forwarder(jump_session, "localhost", 2222) do forwarder
client_session = ssh.Session("localhost"; socket=forwarder.out)
@test ssh.isconnected(client_session)
@test client_session.fd == Base._fd(forwarder.out)
close(client_session)
end
end
end

@testset "Password authentication" begin
# Test connecting to a server and doing password authentication
DemoServer(2222; password="foo") do
Expand Down Expand Up @@ -555,6 +574,9 @@ end
# Test forwarding to a port
demo_server_with_session(2222) do session
ssh.Forwarder(session, 8080, "localhost", 9090) do forwarder
# Smoke test
show(IOBuffer(), forwarder)

http_server(9090) do
curl_proc = run(ignorestatus(`$(curl()) localhost:8080`); wait=false)
try
Expand All @@ -571,6 +593,9 @@ end
# Test forwarding to a socket
demo_server_with_session(2222) do session
ssh.Forwarder(session, "localhost", 9090) do forwarder
# Smoke test
show(IOBuffer(), forwarder)

http_server(9090) do
socket = forwarder.out
write(socket, "foo")
Expand Down

0 comments on commit 8f5aa6c

Please sign in to comment.