-
Notifications
You must be signed in to change notification settings - Fork 131
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
xrootd/pool: Use login token to identify door endpoint
Motivation: On the pool, the xrootd mover provides only limited support for the possible operations in the xroot protocol. If a client makes a request that the pool doesn't support then it will try to redirect the client back to the door. To do this, the mover needs to know the door's endpoint: the hostname and port number. The door endpoint is provided as part of the ProtocolInfo. However, the ProtocolInfo is only available to the xroot handler if the client has already made a valid kXR_open request. If a client connects to the door and makes an unsupported request (without first opening a file) then the xrootd transfer service does not know to which xroot door is should redirect the client, so must fail the request. Although the door only redirects the client to the pool when that client wishes to open a file, the xrootd client sometimes caches this information and issues subsequent (unsupported) requests directly to the pool. If the client does so on a separate TCP connection then the pool cannot know from which door the client came, so cannot redirect the client. Modification: The door now provides the client with a "login token" when redirecting the client to the pool. This token is a simple encoding of the door's public endpoint. As per the xroot protocol, the client is required to present this token when first connecting to the endpoint, as part of the kXR_login procedure. This means the xrootd transfer handler (on the pool) will know the door's public endpoint, so will be able to redirect the client back to the door. Note that, due to a bug in the xrootd software, the login token from the door is ignored. This bug will be fixed with the anticipated 5.5.0 release of xrootd: xrootd/xrootd#1533 Result: dCache provides a more robust implemntation of xroot protocol. Target: master Requires-notes: no Requires-book: no Patch: https://rb.dcache.org/r/13491/ Acked-by: Albert Rossi
- Loading branch information
1 parent
22b1a8b
commit 7d79a22
Showing
6 changed files
with
232 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
modules/dcache-xrootd/src/main/java/org/dcache/xrootd/LoginTokens.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* dCache - http://www.dcache.org/ | ||
* | ||
* Copyright (C) 2022 Deutsches Elektronen-Synchrotron | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as | ||
* published by the Free Software Foundation, either version 3 of the | ||
* License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package org.dcache.xrootd; | ||
|
||
import com.google.common.base.Splitter; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.net.InetAddress; | ||
import java.net.InetSocketAddress; | ||
import java.net.UnknownHostException; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
import static com.google.common.base.Preconditions.checkArgument; | ||
|
||
/** | ||
* Utility class to handle login token. | ||
*/ | ||
public class LoginTokens | ||
{ | ||
private static final Logger LOGGER = LoggerFactory.getLogger(LoginTokens.class); | ||
|
||
private static final String JOIN_ELEMENT = "&"; | ||
private static final char KEY_VALUE_SEPARATOR = '='; | ||
private static final String HOST_AND_PORT_KEY = "org.dcache.door"; | ||
private static final char HOST_PORT_SEPERATOR = ':'; | ||
|
||
private LoginTokens() {} // Prevent instantiation. | ||
|
||
public static String encodeToken(InetSocketAddress addr) { | ||
return HOST_AND_PORT_KEY + KEY_VALUE_SEPARATOR | ||
+ addr.getHostString() + HOST_PORT_SEPERATOR + addr.getPort(); | ||
} | ||
|
||
public static Optional<InetSocketAddress> decodeToken(String token) { | ||
try { | ||
/*checkArgument(token.startsWith(INITIAL_ELEMENT), | ||
"Missing initial \"" + INITIAL_ELEMENT + "\"");*/ | ||
|
||
Map<String,String> data = Splitter.on(JOIN_ELEMENT) | ||
.withKeyValueSeparator(KEY_VALUE_SEPARATOR) | ||
.split(token); | ||
String hostAndPort = data.get(HOST_AND_PORT_KEY); | ||
checkArgument(hostAndPort != null, "Missing \"" + HOST_AND_PORT_KEY + "\" key"); | ||
|
||
int seperator = hostAndPort.indexOf(HOST_PORT_SEPERATOR); | ||
checkArgument(seperator > -1, "Missing '" + HOST_PORT_SEPERATOR + "' in " | ||
+ HOST_AND_PORT_KEY + " value"); | ||
checkArgument(seperator > 0, "'" + HOST_PORT_SEPERATOR | ||
+ "' cannot be first character in " + HOST_AND_PORT_KEY); | ||
checkArgument(seperator < hostAndPort.length()-1, "'" + HOST_PORT_SEPERATOR | ||
+ "' cannot be last character in " + HOST_AND_PORT_KEY); | ||
|
||
String host = hostAndPort.substring(0, seperator); | ||
String port = hostAndPort.substring(seperator+1); | ||
|
||
InetSocketAddress addr = new InetSocketAddress(InetAddress.getByName(host), | ||
Integer.parseInt(port)); | ||
return Optional.of(addr); | ||
} catch (UnknownHostException | IllegalArgumentException e) { | ||
LOGGER.warn("Bad kXR_login token \"{}\": {}", token, e.getMessage()); // should be DEBUG | ||
} | ||
|
||
return Optional.empty(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
modules/dcache-xrootd/src/test/java/org/dcache/xrootd/LoginTokensTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/* dCache - http://www.dcache.org/ | ||
* | ||
* Copyright (C) 2022 Deutsches Elektronen-Synchrotron | ||
* | ||
* This program is free software: you can redistribute it and/or modify | ||
* it under the terms of the GNU Affero General Public License as | ||
* published by the Free Software Foundation, either version 3 of the | ||
* License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
* GNU Affero General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Affero General Public License | ||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
package org.dcache.xrootd; | ||
|
||
import java.net.InetSocketAddress; | ||
import java.util.Optional; | ||
import java.util.OptionalInt; | ||
import org.hamcrest.BaseMatcher; | ||
import org.hamcrest.Description; | ||
import org.junit.Test; | ||
|
||
import static com.github.npathai.hamcrestopt.OptionalMatchers.isEmpty; | ||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.hamcrest.Matchers.is; | ||
import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresentAnd; | ||
|
||
public class LoginTokensTest { | ||
|
||
/** | ||
* A simple Hamcrest Matcher implementation that checks the value of some InetSocketAddress. | ||
* The port number must be specified, using a fluent-style. | ||
*/ | ||
private static class HasValue extends BaseMatcher<InetSocketAddress> | ||
{ | ||
private final String expectedHost; | ||
private OptionalInt expectedPort = OptionalInt.empty(); | ||
|
||
public HasValue(String host) | ||
{ | ||
expectedHost = host; | ||
} | ||
|
||
public HasValue andPort(int port) { | ||
expectedPort = OptionalInt.of(port); | ||
return this; | ||
} | ||
|
||
@Override | ||
public boolean matches(Object actual) { | ||
if (!(actual instanceof InetSocketAddress)) { | ||
return false; | ||
} | ||
InetSocketAddress addr = (InetSocketAddress)actual; | ||
return addr.getHostString().equals(expectedHost) | ||
&& addr.getPort() == expectedPort.getAsInt(); | ||
} | ||
|
||
@Override | ||
public void describeTo(Description description) { | ||
description.appendValue(expectedHost + ":" + expectedPort.getAsInt()); | ||
} | ||
} | ||
|
||
private InetSocketAddress addr; | ||
|
||
@Test | ||
public void shouldEncodeHostAndPort() { | ||
givenHostAndPort("localhost", 1094); | ||
|
||
String token = LoginTokens.encodeToken(addr); | ||
|
||
assertThat(token, is(equalTo("org.dcache.door=localhost:1094"))); | ||
} | ||
|
||
@Test | ||
public void shouldDecodeHostAndPort() { | ||
Optional<InetSocketAddress> door = LoginTokens.decodeToken("org.dcache.door=localhost:1094"); | ||
|
||
assertThat(door, isPresentAnd(hasHost("localhost").andPort(1094))); | ||
} | ||
|
||
@Test | ||
public void shouldIgnoreTokenWithMissingKey() { | ||
Optional<InetSocketAddress> door = LoginTokens.decodeToken("?xrd.cc=de&xrd.tz=1&xrd.appname=xrdcp&xrd.info=&xrd.hostname=sprocket.desy.de&xrd.rn=v5.1.1"); | ||
|
||
assertThat(door, isEmpty()); | ||
} | ||
|
||
@Test | ||
public void shouldIgnoreTokenWithMissingHostname() { | ||
Optional<InetSocketAddress> door = LoginTokens.decodeToken("org.dcache.door=:1094"); | ||
|
||
assertThat(door, isEmpty()); | ||
} | ||
|
||
@Test | ||
public void shouldIgnoreTokenWithMissingPort() { | ||
Optional<InetSocketAddress> door = LoginTokens.decodeToken("org.dcache.door=localhost:"); | ||
|
||
assertThat(door, isEmpty()); | ||
} | ||
|
||
@Test | ||
public void shouldIgnoreTokenWithNoSeperator() { | ||
Optional<InetSocketAddress> door = LoginTokens.decodeToken("org.dcache.door=localhost"); | ||
|
||
assertThat(door, isEmpty()); | ||
} | ||
|
||
// Support methods | ||
|
||
private void givenHostAndPort(String host, int port) | ||
{ | ||
addr = InetSocketAddress.createUnresolved(host, port); | ||
} | ||
|
||
private static HasValue hasHost(String host) | ||
{ | ||
return new HasValue(host); | ||
} | ||
} |