Skip to content

Commit 37bacd1

Browse files
committed
Add PPP over Hysteria2
1 parent 14e9fff commit 37bacd1

91 files changed

Lines changed: 26423 additions & 43 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/cmd/client.go

Lines changed: 114 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/apernet/hysteria/app/v2/internal/url"
3939
"github.com/apernet/hysteria/app/v2/internal/utils"
4040
"github.com/apernet/hysteria/core/v2/client"
41+
coreErrs "github.com/apernet/hysteria/core/v2/errors"
4142
"github.com/apernet/hysteria/extras/v2/correctnet"
4243
"github.com/apernet/hysteria/extras/v2/obfs"
4344
"github.com/apernet/hysteria/extras/v2/realm"
@@ -92,6 +93,28 @@ type clientConfig struct {
9293
UDPTProxy *udpTProxyConfig `mapstructure:"udpTProxy"`
9394
TCPRedirect *tcpRedirectConfig `mapstructure:"tcpRedirect"`
9495
TUN *tunConfig `mapstructure:"tun"`
96+
PPP *pppConfig `mapstructure:"ppp"`
97+
}
98+
99+
type pppSSTPConfig struct {
100+
BinaryPath string `mapstructure:"binaryPath"`
101+
Listen string `mapstructure:"listen"`
102+
CertDir string `mapstructure:"certDir"`
103+
Endpoint string `mapstructure:"endpoint"`
104+
User string `mapstructure:"user"`
105+
Password string `mapstructure:"password"`
106+
MSSClamp *int `mapstructure:"mssClamp"` // nil=auto, 0=off, >0=forced
107+
ServerRoute *bool `mapstructure:"serverRoute"`
108+
LogLevel string `mapstructure:"logLevel"`
109+
}
110+
111+
type pppConfig struct {
112+
Mode string `mapstructure:"mode"`
113+
MTU uint32 `mapstructure:"mtu"`
114+
PPPDPath string `mapstructure:"pppdPath"`
115+
PPPDArgs []string `mapstructure:"pppdArgs"`
116+
DataStreams int `mapstructure:"dataStreams"`
117+
SSTP *pppSSTPConfig `mapstructure:"sstp"`
95118
}
96119

97120
type mimicConfig struct {
@@ -644,6 +667,9 @@ func (c *clientConfig) Config() (*client.Config, error) {
644667
return nil, err
645668
}
646669
}
670+
if c.PPP != nil && c.PPP.DataStreams == 0 {
671+
hyConfig.PPPMode = true
672+
}
647673
return hyConfig, nil
648674
}
649675

@@ -836,12 +862,46 @@ func runClientCmd(cmd *cobra.Command, args []string) {
836862
runClient(defaultViper)
837863
}
838864

865+
// authRetryDelay is how long to wait before asking a second time about a refused
866+
// password. Short enough to stay inside the restart floor that is being spent
867+
// anyway, long enough to outlast the momentary gap it exists to tolerate.
868+
const authRetryDelay = 2 * time.Second
869+
870+
// connectClient builds the client, and when the server refuses the password asks
871+
// once more before believing it.
872+
//
873+
// AuthError carries no more meaning than "the server answered with something
874+
// other than 233". A server whose auth backend is momentarily unavailable serves
875+
// the request through its masquerade handler instead -- a 404 -- which is the
876+
// same answer a genuinely wrong password gets, and the client cannot tell them
877+
// apart. With lazy off this handshake is a single un-retried attempt, and the
878+
// caller turns an AuthError into a status file that makes the netifd handler
879+
// call proto_block_restart. One unlucky moment would then hold the WAN down
880+
// until somebody logged in and ran ifup, where before it recovered by itself.
881+
//
882+
// Asking twice separates the two: a wrong password is refused both times, a gap
883+
// almost never lasts across the delay. Only an auth failure is retried -- every
884+
// other error is already treated as retryable and is left to netifd.
885+
func connectClient(config clientConfig, connected func(client.Client, *client.HandshakeInfo, int)) (client.Client, error) {
886+
c, err := client.NewReconnectableClient(config.Config, connected, config.Lazy)
887+
var authErr coreErrs.AuthError
888+
if err == nil || !errors.As(err, &authErr) {
889+
return c, err
890+
}
891+
logger.Warn("server refused the password, confirming before reporting it as final",
892+
zap.Int("status", authErr.StatusCode))
893+
time.Sleep(authRetryDelay)
894+
return client.NewReconnectableClient(config.Config, connected, config.Lazy)
895+
}
896+
839897
func runClient(v *viper.Viper) {
840898
if err := v.ReadInConfig(); err != nil {
899+
holdPPPRestart()
841900
logger.Fatal("failed to read client config", zap.Error(err))
842901
}
843902
var config clientConfig
844903
if err := v.Unmarshal(&config); err != nil {
904+
holdPPPRestart()
845905
logger.Fatal("failed to parse client config", zap.Error(err))
846906
}
847907

@@ -851,20 +911,32 @@ func runClient(v *viper.Viper) {
851911
mimicInst := config.startMimic()
852912
defer mimicInst.Close()
853913

854-
c, err := client.NewReconnectableClient(
855-
config.Config,
856-
func(c client.Client, info *client.HandshakeInfo, count int) {
857-
connectLog(info, count)
858-
// On the client side, we start checking for updates after we successfully connect
859-
// to the server, which, depending on whether lazy mode is enabled, may or may not
860-
// be immediately after the client starts. We don't want the update check request
861-
// to interfere with the lazy mode option.
862-
if count == 1 && !disableUpdateCheck {
863-
go runCheckUpdateClient(c)
864-
}
865-
}, config.Lazy,
866-
)
914+
connected := func(c client.Client, info *client.HandshakeInfo, count int) {
915+
connectLog(info, count)
916+
// On the client side, we start checking for updates after we successfully connect
917+
// to the server, which, depending on whether lazy mode is enabled, may or may not
918+
// be immediately after the client starts. We don't want the update check request
919+
// to interfere with the lazy mode option.
920+
if count == 1 && !disableUpdateCheck {
921+
go runCheckUpdateClient(c)
922+
}
923+
}
924+
925+
c, err := connectClient(config, connected)
867926
if err != nil {
927+
// Two unrelated things share this line. The status write is where a
928+
// refused Hysteria2 password is reported from: with lazy off the
929+
// handshake has already happened here, so it never reaches the runner
930+
// below. The hold is for the opposite case -- a dropped upstream fails
931+
// this dial in microseconds, and without it that becomes a restart loop.
932+
writePPPStatus(err)
933+
// And the state file, which reports every failure rather than only the
934+
// permanent ones. A handshake refused here never reaches PPP mode, so
935+
// without this the link's last published state is whatever it was before
936+
// the interface was rebuilt -- which for a link that was working a moment
937+
// ago is "connected".
938+
writePPPStateDown(err)
939+
holdPPPRestart()
868940
logger.Fatal("failed to initialize client", zap.Error(err))
869941
}
870942
defer c.Close()
@@ -919,6 +991,11 @@ func runClient(v *viper.Viper) {
919991
return clientTUN(*config.TUN, c)
920992
})
921993
}
994+
if config.PPP != nil {
995+
runner.Add("PPP", func() error {
996+
return clientPPP(*config.PPP, c, strings.EqualFold(config.Obfs.Type, "salamander"))
997+
})
998+
}
922999

9231000
signalChan := make(chan os.Signal, 1)
9241001
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
@@ -936,7 +1013,31 @@ func runClient(v *viper.Viper) {
9361013
if r.OK {
9371014
logger.Info(r.Msg)
9381015
} else {
1016+
// A connect that fails only reaches here in lazy mode, where the
1017+
// handshake is deferred until PPP mode asks for the conn. With lazy
1018+
// off -- which is what the OpenWrt handler generates, since it never
1019+
// writes the key -- the handshake has already happened above, and a
1020+
// refused password fails there instead. Both sites write the status.
1021+
writePPPStatus(r.Err)
1022+
writePPPStateDown(r.Err)
9391023
_ = c.Close() // Close the client here as Fatal will exit the program without running defer
1024+
// Hand SIGTERM back to the runtime before holding. The deferred Stop
1025+
// never runs -- Fatal exits -- so without this an ifdown arriving
1026+
// during the hold would sit unread in signalChan and be ignored for
1027+
// the length of it, keeping netifd waiting on a process that has
1028+
// already decided to die.
1029+
signal.Stop(signalChan)
1030+
// Stop only restores the default disposition for signals still to
1031+
// come. One that arrived while this select was being decided is
1032+
// already buffered, and the select is free to have picked the runner
1033+
// case instead -- so it has to be looked for, not waited for. Finding
1034+
// one means an ifdown is in progress and nothing is going to restart
1035+
// us, which is exactly when holding is pure delay.
1036+
select {
1037+
case <-signalChan:
1038+
default:
1039+
holdPPPRestart()
1040+
}
9401041
if r.Err != nil {
9411042
logger.Fatal(r.Msg, zap.Error(r.Err))
9421043
} else {

0 commit comments

Comments
 (0)