diff --git a/.gitignore b/.gitignore index f246d9c..9476a35 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ shard.lock bin drop lib +rules/ +redirections/ pongd pongc diff --git a/src/ipcd.cr b/src/ipcd.cr index 99f73a5..de97527 100644 --- a/src/ipcd.cr +++ b/src/ipcd.cr @@ -67,17 +67,33 @@ module IPCd def to_s(io : IO) io << "ipcd\n" - io << @rules.to_s + "\n" + @redirections.to_s + @rules.to_s(io) + @redirections.to_s(io) end - # The client asks to ipcd to open a connection to a service - # the service name is used unless there is a redirection that is provided to the client through - # a IPC_NETWORK environment variable - # This environment variable is sent from the client to ipcd, that's what is parsed here + # Client => ipcd => Service. + # ipcd creates a network connection to the remote host, + # then provides a file descriptor to the client. + # Besides ipcd configuration, the client shares its IPC_NETWORK + # environment variable to ipcd to understand how to contact the service. - # parse_lookup_payload extract the right service URI to use from the IPC_NETWORK content - # sent by the client in the form: - # `requested-service-name;service1 uri;service2 uri;...` etc. + # IPC_NETWORK environment variable contains pairs of URIs (redirections). + # Format: "requested-service new-URI" + # Example: "pong tcp://example.com:8000/pong" + # + # The URI indicates the scheme, the domain (or directly the IP address) and the port + # to contact the remote service. The path indicates the remote service to use. + # + # Several redirections can be put into the IPC_NETWORK variable, using a semi-colon. + # Example: "pong tcp://example.com/pong;auth tls://example.com:9000/auth" + + # ipcd prioritises IPC_NETWORK over its own configuration for redirections. + # This environment variable is sent from the client to ipcd, that's what is parsed here. + + # parse_lookup_payload extracts the right service URI to use from the message sent by + # a client, containing the service to contact and the IPC_NETWORK content. + # Format: "requested-service;IPC_NETWORK content" + # Example: "pong;pong tcp://example.com:3000/pong-8" def self.parse_lookup_payload (payload : String) : URI items = payload.split (";") requested_service_name = items.delete_at(0).chomp() @@ -85,30 +101,27 @@ module IPCd services_redirections = {} of String => URI - # from each item (separated by a semicolon), get the service name and the new uri to use - # format: `service-name uri;service-name uri` - # uri can be: + # Format: "service-name URI;service-name URI" + # URI can be: # * distant service (with protocol to use): https://some.example.com/pong # * local service: "local:newpong" or simply "newpong" items.each do |item| - x = /([^ ]+) ([^ ]+)/.match(item) - unless x.nil? - service, newuri = x.captures() - if service.nil? - next - elsif newuri.nil? - next + case item + when /(?[^ ]+) +(?[^ ]+)/ + origin, destination_uri = $~["origin"], $~["destination_uri"] + uri = URI.parse destination_uri + if uri.scheme.nil? + uri = URI.parse "unix:///#{destination_uri}" end - - Baguette::Log.debug "service: #{service} redirection uri: #{newuri}" - uri = URI.parse newuri - services_redirections[service] = uri + services_redirections[origin] = uri + else + Baguette::Log.error "cannot understand configuration #{item}" end end services_redirections.each do |k, v| - Baguette::Log.debug "possible redirection (from env. var.): service: #{k} uri: #{v}" if k == requested_service_name + Baguette::Log.info "prefered redirection (from IPC_NETWORK): #{k} => #{v}" requested_service = v end end @@ -117,50 +130,67 @@ module IPCd end # XXX: WIP - def service_lookup (message : IPC::Message, fd : Int32) - payload = String.new message.payload + def service_lookup (message : IPC::Message, client_fd : Int32) + requested_service = IPCd::Service.parse_lookup_payload String.new(message.payload) - requested_service = IPCd::Service.parse_lookup_payload payload + # Now, we want to get the service used for transport. + # Network services used to carry libipc messages are named based on the scheme. + # Examples: tcp, udp, ws, etc. + network_service_name = requested_service.scheme - scheme = requested_service.scheme - if scheme.nil? - raise "no SCHEME in redirection" + if network_service_name.nil? + raise "no scheme" end - service_name = scheme - if scheme == "unix" - # scheme == unix => simple redirection - - # the URI is "unix:///service" so the path is "/service" - # first, remove its slash prefix - service_name = requested_service.path.lchop + # In case of "unix", then it's a simple redirection to do, no network required. + if requested_service.scheme == "unix" + # The URI is "unix:///service" so the path is "/service". + # Service to contact is simply the path minus the slash. + network_service_name = requested_service.path.lchop + end + + Baguette::Log.debug "connecting to #{network_service_name}" + + service = begin + IPC::Client.new network_service_name + rescue e + # Better raise message. + raise "cannot contact #{network_service_name}" + end + + # Will probably never happen in practice. + if service.fd.nil? + raise "no fd for #{network_service_name}" end - Baguette::Log.info "service name: #{service_name}" - service = IPC::Client.new service_name service_fd = service.fd.not_nil! - # TODO: for remote services, we have to connect to communication service - # these communication services need an URI to work on - # The protocol: - # ipcd sends an URI to the communication service, which responds with a "OK" message - if scheme != "unix" - service.send service_fd, 1.to_u8, "#{requested_service.to_s}\n" + # A network service is necessary for remote communications, and it requires an URI. + # TODO: protocol between ipcd and network services. + # The (draft) protocol: + # - ipcd sends an URI to the network service + # - network service responds with a "OK" message + if requested_service.scheme != "unix" + service.send service_fd, 1.to_u8, requested_service.to_s response = service.read payload = String.new response.payload if payload.chomp != "OK" - raise "service #{service_name} response was #{payload.chomp}" + raise "service #{network_service_name} response was #{payload.chomp}" end end - # Then we provide the file descriptor to the client - r = LibIPC.ipc_provide_fd(fd, service_fd) + Baguette::Log.debug "providing the fd from #{network_service_name} to the client #{client_fd}" + + # Provide the file descriptor to the client. + r = LibIPC.ipc_provide_fd(client_fd, service_fd) if r.error_code != 0 m = String.new r.error_message.to_slice raise Exception.new "cannot send the file descriptor of the requested service: #{m}" end - # finally, the service should be closed in ipcd + Baguette::Log.debug "everything went well, closing the service fd" + + # Finally, the service should be closed in ipcd. service.close end end