Skip to content

Commit

Permalink
Allow abstract UNIX socket with '\0` path prefix'
Browse files Browse the repository at this point in the history
  • Loading branch information
bew committed Nov 28, 2017
1 parent d520072 commit dc2dc7b
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 37 deletions.
56 changes: 35 additions & 21 deletions spec/std/socket_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -170,19 +170,23 @@ end

describe Socket::UNIXAddress do
it "transforms into a C struct and back" do
addr1 = Socket::UNIXAddress.new("/tmp/service.sock")
path = "/tmp/service.sock"

addr1 = Socket::UNIXAddress.new(path)
addr1.abstract?.should be_false
addr2 = Socket::UNIXAddress.from(addr1.to_unsafe, addr1.size)

addr2.family.should eq(addr1.family)
addr2.path.should eq(addr1.path)
addr2.path.should eq(path)
addr2.abstract?.should eq(addr1.abstract?)
addr2.to_s.should eq("/tmp/service.sock")
addr2.to_s.should eq(path)
end

it "transforms an abstract address into a C struct and back" do
path = "/abstract-service.sock"

addr1 = Socket::UNIXAddress.new(path, abstract: true)
addr1 = Socket::UNIXAddress.new('\0' + path)
addr1.path.should eq(path)
addr1.abstract?.should be_true

Expand All @@ -191,8 +195,8 @@ describe Socket::UNIXAddress do
String.new(sockaddr_un.sun_path.to_unsafe + 1).should eq(path)

addr2 = Socket::UNIXAddress.new(pointerof(sockaddr_un), nil)
addr2.path.should eq(path)
addr2.abstract?.should be_true
addr2.path.should eq(addr1.path)
addr2.abstract?.should eq(addr1.abstract?)
end

it "raises when path is too long" do
Expand All @@ -201,9 +205,11 @@ describe Socket::UNIXAddress do
end

it "to_s" do
path = "some_path"
Socket::UNIXAddress.new(path).to_s.should eq(path)
Socket::UNIXAddress.new(path, abstract: true).to_s.should eq(path)
# normal UNIX address
Socket::UNIXAddress.new("some_path").to_s.should eq("some_path")

# abstract UNIX address
Socket::UNIXAddress.new("\0some_path").to_s.should eq("@some_path")
end
end

Expand All @@ -217,7 +223,8 @@ describe UNIXServer do
it "creates the socket file" do
path = "/tmp/crystal-test-unix-sock"

UNIXServer.open(path) do
UNIXServer.open(path) do |server|
server.abstract?.should be_false
File.exists?(path).should be_true
end

Expand All @@ -229,7 +236,7 @@ describe UNIXServer do

File.exists?(path).should be_false

UNIXServer.open(path, abstract: true) do |server|
UNIXServer.open('\0' + path) do |server|
server.abstract?.should be_true
File.exists?(path).should be_false
end
Expand All @@ -250,11 +257,11 @@ describe UNIXServer do
it "does not delete any file on close for abstract server" do
path = "/tmp/crystal-test-close-unix-abstract-sock"

File.write(path, "")
File.touch(path)
File.exists?(path).should be_true

begin
server = UNIXServer.new(path, abstract: true)
server = UNIXServer.new('\0' + path)
server.close
File.exists?(path).should be_true
ensure
Expand All @@ -276,7 +283,7 @@ describe UNIXServer do
it "won't delete existing file on bind failure" do
path = "/tmp/crystal-test-unix.sock"

File.write(path, "")
File.touch(path)
File.exists?(path).should be_true

begin
Expand All @@ -292,20 +299,24 @@ describe UNIXServer do

describe "accept" do
it "returns the client UNIXSocket" do
UNIXServer.open("/tmp/crystal-test-unix-sock") do |server|
UNIXSocket.open("/tmp/crystal-test-unix-sock") do |_|
path = "/tmp/crystal-test-unix-sock"

UNIXServer.open(path) do |server|
UNIXSocket.open(path) do |_|
client = server.accept
client.should be_a(UNIXSocket)
client.abstract?.should be_false
client.close
end
end
end

it "returns an abstract client UNIXSocket for abstract server" do
path = "/tmp/crystal-test-abstract-unix-sock"
abstract_path = '\0' + path

UNIXServer.open(path, abstract: true) do |server|
UNIXSocket.open(path, abstract: true) do |_|
UNIXServer.open(abstract_path) do |server|
UNIXSocket.open(abstract_path) do |_|
client = server.accept
client.should be_a(UNIXSocket)
client.abstract?.should be_true
Expand Down Expand Up @@ -338,8 +349,10 @@ describe UNIXServer do

describe "accept?" do
it "returns the client UNIXSocket" do
UNIXServer.open("/tmp/crystal-test-unix-sock") do |server|
UNIXSocket.open("/tmp/crystal-test-unix-sock") do |_|
path = "/tmp/crystal-test-unix-sock"

UNIXServer.open(path) do |server|
UNIXSocket.open(path) do |_|
client = server.accept?.not_nil!
client.should be_a(UNIXSocket)
client.close
Expand Down Expand Up @@ -399,12 +412,13 @@ describe UNIXSocket do

it "sends and receives messages over an abstract STREAM socket" do
path = "/abstract-service.sock"
abstract_path = '\0' + path

UNIXServer.open(path, abstract: true) do |server|
UNIXServer.open(abstract_path) do |server|
server.local_address.abstract?.should be_true
server.local_address.path.should eq(path)

UNIXSocket.open(path, abstract: true) do |client|
UNIXSocket.open(abstract_path) do |client|
client.local_address.abstract?.should be_true
client.local_address.path.should eq(path)

Expand Down
20 changes: 16 additions & 4 deletions src/socket/address.cr
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ class Socket
# connection (e.g. `Socket#local_address`, `Socket#receive`).
#
# You may also declare an abstract UNIX address, that is a virtual file
# that will never be created on the filesystem.
# that will never be created on the filesystem. An abstract UNIX address
# path has a NUL byte (`\0`) prefix.
# NOTE: abstract UNIX addresses are supported only on some Linux systems.
#
# Example:
# ```
Expand All @@ -193,10 +195,17 @@ class Socket
# :nodoc:
MAX_PATH_SIZE = LibC::SockaddrUn.new.sun_path.size - 1

def initialize(@path : String, @abstract = false)
if @path.bytesize + 1 > MAX_PATH_SIZE
def initialize(path : String)
if path.bytesize + 1 > MAX_PATH_SIZE
raise ArgumentError.new("Path size exceeds the maximum size of #{MAX_PATH_SIZE} bytes")
end
if path[0]? == '\0'
@abstract = true
@path = path[1..-1]
else
@abstract = false
@path = path
end
@family = Family::UNIX
@size = sizeof(LibC::SockaddrUn)
end
Expand All @@ -210,7 +219,7 @@ class Socket
@family = Family::UNIX

path = sockaddr.value.sun_path
if path[0] == 0
if path[0]? == 0
@abstract = true
@path = String.new(path.to_unsafe + 1)
else
Expand All @@ -226,6 +235,9 @@ class Socket
end

def to_s(io)
if abstract?
io << '@'
end
io << path
end

Expand Down
13 changes: 8 additions & 5 deletions src/socket/unix_server.cr
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,13 @@ class UNIXServer < UNIXSocket
# ```
# UNIXServer.new("/tmp/dgram.sock", Socket::Type::DGRAM)
# ```
def initialize(@path : String, type : Type = Type::STREAM, backlog = 128, abstract is_abstract = false)
super(Family::UNIX, type, is_abstract)
def initialize(path : String, type : Type = Type::STREAM, backlog = 128)
super(Family::UNIX, type)

bind(UNIXAddress.new(path, is_abstract)) do |error|
addr = UNIXAddress.new(path)
@abstract = addr.abstract?
@path = addr.path
bind(addr) do |error|
close(delete: false)
raise error
end
Expand All @@ -50,8 +53,8 @@ class UNIXServer < UNIXSocket
# server socket when the block returns.
#
# Returns the value of the block.
def self.open(path, type : Type = Type::STREAM, backlog = 128, abstract is_abstract = false)
server = new(path, type, backlog, is_abstract)
def self.open(path, type : Type = Type::STREAM, backlog = 128)
server = new(path, type, backlog)
begin
yield server
ensure
Expand Down
25 changes: 18 additions & 7 deletions src/socket/unix_socket.cr
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ class UNIXSocket < Socket
getter path : String?
getter? abstract : Bool

# Connects a named UNIX socket, bound to a filesystem.
def initialize(@path : String, type : Type = Type::STREAM, @abstract = false)
# Connects a named UNIX socket, bound to a path.
def initialize(path : String, type : Type = Type::STREAM)
super(Family::UNIX, type, Protocol::IP)

connect(UNIXAddress.new(path, @abstract)) do |error|
addr = UNIXAddress.new(path)
@abstract = addr.abstract?
@path = addr.path
connect(addr) do |error|
close
raise error
end
Expand All @@ -37,8 +40,8 @@ class UNIXSocket < Socket
# eventually closes the socket when the block returns.
#
# Returns the value of the block.
def self.open(path, type : Type = Type::STREAM, abstract is_abstract = false)
sock = new(path, type, is_abstract)
def self.open(path, type : Type = Type::STREAM)
sock = new(path, type)
begin
yield sock
ensure
Expand Down Expand Up @@ -90,11 +93,19 @@ class UNIXSocket < Socket
end

def local_address
UNIXAddress.new(path.to_s, @abstract)
if abstract?
UNIXAddress.new('\0' + @path.to_s)
else
UNIXAddress.new(@path.to_s)
end
end

def remote_address
UNIXAddress.new(path.to_s, @abstract)
if abstract?
UNIXAddress.new('\0' + @path.to_s)
else
UNIXAddress.new(@path.to_s)
end
end

def receive
Expand Down

0 comments on commit dc2dc7b

Please sign in to comment.